fix six critical bugs from audit sprint
save/load round-trip: workbench bills, crop static-method, bed owner, wolf target now all survive reload via Bill.from_dict reconstruction, _spawn_crop using setup(), and a new _post_load_resolve_references pass. PlantProvider: sow path added; consumes 1 grain on a TILLED crop tile. CraftingProvider: ingredient2 supported via new KIND_DEPOSIT_AT_WB toil and Workbench.deposited_inputs buffer. Cremation pyre now actually consumes wood. HaulingProvider: per-item haul_retry_count + haul_rejected after 3 orphan passes; new EventBus.stockpile_layout_changed resets rejects on any player stockpile edit. Storyteller: 14 stubbed event effects implemented. New buff registry (add_buff/get_buff_multiplier/has_buff, day-prune, save/load) drives seasonal/resource events. New request_pawn_spawn signal + WANDERER table for arrivals. New SICK status + 3 mood thoughts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
00cf8f445d
commit
d9638a4ea4
19 changed files with 711 additions and 101 deletions
|
|
@ -11,14 +11,12 @@ class_name EventCatalog
|
|||
## the i18n hook (`Strings.t(key)`) exists today without requiring it.
|
||||
## EventDef carries the rendered English string; locale switching can swap
|
||||
## it via the string table later without touching EventDef or this catalog.
|
||||
## - Effect helpers below are stubbed with Audit.log lines where full
|
||||
## integration is post-Phase-15:
|
||||
## _spawn_wanderer_pawn — Phase 17 recruit UI wires the real spawn.
|
||||
## _apply_buff_next_n_jobs — Phase 17 work-buff system.
|
||||
## _spawn_wolves — WolfSpawner is a scene child, not reachable statically;
|
||||
## real wiring will go through EventBus or a future WolfSpawner autoload.
|
||||
## _apply_pawn_status (sick) — StatusCatalog.sick() ships Phase 17.
|
||||
## All real-wired effects are noted per-event below.
|
||||
## - Effect helpers (Phase 17 — all wired):
|
||||
## _spawn_wanderer_pawn — emits EventBus.request_pawn_spawn; World scene handles.
|
||||
## _apply_buff_next_n_jobs — registers a timed yield buff on Storyteller.
|
||||
## _spawn_wolves — emits EventBus.request_wolf_spawn; WolfSpawner handles.
|
||||
## _apply_pawn_status — applies a status to a random pawn.
|
||||
## _apply_colony_mood_thought — applies a thought to all living pawns.
|
||||
##
|
||||
## The 25-event count is the Phase 15 gate. Do not commit with != 25.
|
||||
|
||||
|
|
@ -156,9 +154,10 @@ static func _event_05_spring_awakens() -> EventDef:
|
|||
# Trigger: first tick of spring. Storyteller fires this once per season boundary.
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_season() == Clock.SEASON_SPRING
|
||||
# Effect: stubbed — Phase 17 will apply +10% crop growth for the season.
|
||||
# Effect: +20% crop growth speed for 12 days (rest of spring season).
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "spring_awakens resolved — crop growth buff stubbed (Phase 17)")
|
||||
Storyteller.add_buff(&"crop_growth", 1.20, 12)
|
||||
Audit.log("storyteller", "spring_awakens: +20%% crop growth for 12 days")
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -174,9 +173,11 @@ static func _event_06_summers_heat() -> EventDef:
|
|||
d.auto_pause = false
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_season() == Clock.SEASON_SUMMER
|
||||
# Effect: stubbed — Phase 17 will apply outdoor work −5% speed modifier.
|
||||
# Effect: sleep-need decays 15% faster for 12 days (summer heat fatigue).
|
||||
# Consumer: SleepProvider / Pawn._tick_energy checks Storyteller.get_buff_multiplier(&"sleep_decay").
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "summers_heat resolved — outdoor fatigue buff stubbed (Phase 17)")
|
||||
Storyteller.add_buff(&"sleep_decay", 1.15, 12)
|
||||
Audit.log("storyteller", "summers_heat: +15%% sleep decay for 12 days")
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -192,9 +193,11 @@ static func _event_07_autumns_harvest() -> EventDef:
|
|||
d.auto_pause = false
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_season() == Clock.SEASON_AUTUMN
|
||||
# Effect: stubbed — Phase 17 will apply +15% harvest yield.
|
||||
# Effect: +25% harvest yield for 12 days (autumn bonus).
|
||||
# Consumer: Plant/HarvestToil checks Storyteller.get_buff_multiplier(&"harvest_yield").
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "autumns_harvest resolved — yield buff stubbed (Phase 17)")
|
||||
Storyteller.add_buff(&"harvest_yield", 1.25, 12)
|
||||
Audit.log("storyteller", "autumns_harvest: +25%% harvest yield for 12 days")
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -210,9 +213,12 @@ static func _event_08_winters_edge() -> EventDef:
|
|||
d.auto_pause = false
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_season() == Clock.SEASON_WINTER
|
||||
# Effect: no wanderers 5 days + raise threat weight — both stubbed Phase 17.
|
||||
# Effect: raise threat weight ×1.4 for 12 days (winter pressure); suppress
|
||||
# wanderer weight ×0.2 for 5 days (roads closed).
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "winters_edge resolved — wanderer suppress + threat weight stubbed (Phase 17)")
|
||||
Storyteller.add_buff(&"threat_weight", 1.40, 12)
|
||||
Storyteller.add_buff(&"wanderer_weight", 0.20, 5)
|
||||
Audit.log("storyteller", "winters_edge: threat_weight ×1.4 for 12d, wanderer_weight ×0.2 for 5d")
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -259,8 +265,7 @@ static func _event_10_the_refugee_family() -> EventDef:
|
|||
_spawn_wanderer_pawn({})
|
||||
_spawn_wanderer_pawn({})
|
||||
else:
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.slept_on_floor()) # placeholder thought, Phase 17 adds colony_refused_refugees
|
||||
Audit.log("storyteller", "refugee_family refused — colony mood penalty stubbed (Phase 17)")
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.refused_refugees())
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -323,7 +328,7 @@ static func _event_13_wolves_at_the_edge() -> EventDef:
|
|||
# Trigger: anytime after day 3 (season-weighting handled by Storyteller tension).
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_day() >= 3
|
||||
# Effect: spawn 1-3 wolves (stubbed until WolfSpawner is accessible statically).
|
||||
# Effect: spawn 1-3 wolves via EventBus.request_wolf_spawn.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_spawn_wolves(randi_range(1, 3))
|
||||
return d
|
||||
|
|
@ -381,9 +386,12 @@ static func _event_16_bandit_scouts() -> EventDef:
|
|||
# Trigger: post-day-25.
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_day() >= 25
|
||||
# Effect: flavor + threat-likelihood raise — stubbed (v2 raids).
|
||||
# Effect: additional threat weight ×1.3 for 3 days ("they'll be back").
|
||||
# The base THREAT fire already bumps tension +15; this adds a short-window
|
||||
# bias toward more threat events as a narrative follow-up.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "bandit_scouts resolved — threat-likelihood raise stubbed (v2 raids)")
|
||||
Storyteller.add_buff(&"threat_weight", 1.30, 3)
|
||||
Audit.log("storyteller", "bandit_scouts: threat_weight ×1.3 for 3 days")
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -403,9 +411,9 @@ static func _event_17_fever() -> EventDef:
|
|||
# Trigger: random after ~30 days.
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_day() >= 30 and not World.pawns.is_empty()
|
||||
# Effect: Sick status on random pawn — StatusCatalog.sick() ships Phase 17.
|
||||
# Effect: Sick status (severity 2) on one random pawn.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
Audit.log("storyteller", "fever resolved — StatusCatalog.sick() stubbed (Phase 17)")
|
||||
_apply_pawn_status(func() -> Status: return StatusCatalog.sick(2))
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -445,7 +453,7 @@ static func _event_19_the_sleeplessness() -> EventDef:
|
|||
if p.is_tired():
|
||||
return true
|
||||
return false
|
||||
# Effect: mood penalty 2 days — apply tired thought colony-wide (stubbed intensity).
|
||||
# Effect: apply tired thought colony-wide (reinforces existing penalty; feels like communal dread).
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.tired())
|
||||
return d
|
||||
|
|
@ -466,7 +474,7 @@ static func _event_20_bountiful_harvest() -> EventDef:
|
|||
# Trigger: autumn (harvest season) and crops are present.
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_season() == Clock.SEASON_AUTUMN and not World.crops.is_empty()
|
||||
# Effect: +25% yield — stubbed, Phase 17 crop-yield multiplier.
|
||||
# Effect: +25% harvest yield for ~5 days via Storyteller buff registry.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_apply_buff_next_n_jobs(&"harvest", 10, 1.25)
|
||||
return d
|
||||
|
|
@ -485,7 +493,7 @@ static func _event_21_lumberjacks_luck() -> EventDef:
|
|||
# Trigger: trees present (chop job context).
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return not World.trees.is_empty()
|
||||
# Effect: next 3 trees +50% wood — stubbed.
|
||||
# Effect: next ~1.5 days of chop jobs +50% wood via Storyteller buff registry.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_apply_buff_next_n_jobs(&"chop", 3, 1.5)
|
||||
return d
|
||||
|
|
@ -504,7 +512,7 @@ static func _event_22_veins_of_iron() -> EventDef:
|
|||
# Trigger: rocks present (mine job context).
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return not World.rocks.is_empty()
|
||||
# Effect: next mining yield ×2 — stubbed.
|
||||
# Effect: mining yield ×2 for ~1 day via Storyteller buff registry.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_apply_buff_next_n_jobs(&"mine", 1, 2.0)
|
||||
return d
|
||||
|
|
@ -563,11 +571,9 @@ static func _event_25_one_year_survived() -> EventDef:
|
|||
# Trigger: end of first winter (day >= 4 seasons × 12 days = 48).
|
||||
d.trigger_predicate = func() -> bool:
|
||||
return Clock.current_day() >= 48 and Clock.current_season() == Clock.SEASON_WINTER
|
||||
# Effect: +5 mood "We made it" colony 2 days — real-wired via colony thought.
|
||||
# Effect: +6 mood "We made it through a year" colony-wide for 2 in-game days.
|
||||
d.on_resolve = func(_c: int) -> void:
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.well_rested())
|
||||
# well_rested() is the closest existing +5 mood thought; Phase 17 adds
|
||||
# ThoughtCatalog.we_made_it() with correct label + longer duration.
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.we_made_it())
|
||||
return d
|
||||
|
||||
|
||||
|
|
@ -575,16 +581,27 @@ static func _event_25_one_year_survived() -> EventDef:
|
|||
## All helpers are static. Stub bodies log via Audit where the underlying
|
||||
## system is not yet available; real bodies are annotated per-helper.
|
||||
|
||||
## Instantiate a new pawn at a random map-edge tile with optional skill seed.
|
||||
## STUB — Phase 17 recruit UI will wire the real spawn via PawnFactory or similar.
|
||||
## Emit EventBus.request_pawn_spawn so the World scene instantiates a new pawn.
|
||||
## Also applies the hopeful_newcomer thought colony-wide (including the new pawn
|
||||
## if it is registered before the next thought tick — timing may vary).
|
||||
## Phase 17 recruit UI wires additional name/trait input; this is the mechanical
|
||||
## fallback that fires regardless of any future recruit dialog.
|
||||
static func _spawn_wanderer_pawn(skills: Dictionary = {}) -> void:
|
||||
Audit.log("storyteller", "_spawn_wanderer_pawn called with skills=%s — stubbed (Phase 17)" % str(skills))
|
||||
EventBus.request_pawn_spawn.emit(skills)
|
||||
_apply_colony_mood_thought(func() -> Thought: return ThoughtCatalog.hopeful_newcomer())
|
||||
Audit.log("storyteller", "_spawn_wanderer_pawn: emitted request_pawn_spawn(skills=%s)" % str(skills))
|
||||
|
||||
|
||||
## Flag the next `count` jobs of `kind` to use a yield multiplier.
|
||||
## STUB — Phase 17 work-buff system will implement the ring buffer.
|
||||
## Register a timed yield multiplier buff via Storyteller.add_buff().
|
||||
## `kind` maps to the buff key that job runners query (e.g. &"harvest_yield",
|
||||
## &"chop_yield", &"mine_yield"). `count` is used to estimate duration in days:
|
||||
## each job is assumed to take ~0.5 in-game days at average pawn count, so
|
||||
## duration = max(1, count / 2) days. Consumers call
|
||||
## Storyteller.get_buff_multiplier(kind) when computing yield.
|
||||
static func _apply_buff_next_n_jobs(kind: StringName, count: int, multiplier: float) -> void:
|
||||
Audit.log("storyteller", "_apply_buff_next_n_jobs: kind=%s count=%d ×%.2f — stubbed (Phase 17)" % [kind, count, multiplier])
|
||||
var duration: int = maxi(1, count / 2)
|
||||
Storyteller.add_buff(kind, multiplier, duration)
|
||||
Audit.log("storyteller", "_apply_buff_next_n_jobs: kind=%s ×%.2f for %d days" % [kind, multiplier, duration])
|
||||
|
||||
|
||||
## Emit EventBus.request_wolf_spawn so WolfSpawner (scene child) picks it up.
|
||||
|
|
@ -597,7 +614,7 @@ static func _spawn_wolves(count: int) -> void:
|
|||
|
||||
## Apply a status to a random pawn (or a specific pawn if non-null).
|
||||
## The factory callable returns a fresh Status — mirrors StatusCatalog pattern.
|
||||
## REAL for statuses that exist (bleeding); STUB for sick() until Phase 17.
|
||||
## All statuses in StatusCatalog are now wired (bleeding, sick).
|
||||
static func _apply_pawn_status(status_factory: Callable, pawn = null) -> void:
|
||||
var target = pawn
|
||||
if target == null:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue