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
|
|
@ -41,6 +41,7 @@ const _TORCH_SCENE: PackedScene = preload("res://scenes/entities/torch.t
|
|||
const _STOCKPILE_SCENE: PackedScene = preload("res://scenes/world/stockpile_zone.tscn")
|
||||
const _CRATE_SCENE: PackedScene = preload("res://scenes/world/crate.tscn")
|
||||
const _WOLF_SCENE: PackedScene = preload("res://scenes/entities/wolf.tscn")
|
||||
const _CROP_SCRIPT: Script = preload("res://scenes/entities/crop.gd")
|
||||
const _CORPSE_SCENE: PackedScene = preload("res://scenes/entities/corpse.tscn")
|
||||
|
||||
# Script-only entities (no .tscn); spawned via Node2D.new() + set_script().
|
||||
|
|
@ -258,6 +259,9 @@ func apply_save(payload: Dictionary, slot: StringName = &"manual") -> void:
|
|||
if saved_speed_int >= 0 and saved_speed_int < Sim.Speed.size():
|
||||
saved_speed = saved_speed_int as Sim.Speed
|
||||
|
||||
# Resolve cross-entity references that require all nodes to be spawned first.
|
||||
_post_load_resolve_references()
|
||||
|
||||
var ok: bool = error_count == 0
|
||||
Audit.log("save", "applied slot '%s': %d entities, %d errors, tick=%d, away=%ds" % [
|
||||
slot, entity_count, error_count, Sim.tick, real_seconds_away
|
||||
|
|
@ -430,6 +434,13 @@ func _spawn_item(world_scene: Node, d: Dictionary) -> void:
|
|||
)
|
||||
ent.quality = int(d.get("quality", 1)) as Item.Quality
|
||||
ent.subtype = StringName(d.get("subtype", ""))
|
||||
# Hauling-retry state — defaults keep older v2 saves loading cleanly.
|
||||
ent.haul_retry_count = int(d.get("haul_retry_count", 0))
|
||||
ent.haul_rejected = bool(d.get("haul_rejected", false))
|
||||
# haul_rejected items must NOT be in items_needing_haul — undo the
|
||||
# automatic enqueue that World.register_item() does inside setup().
|
||||
if ent.haul_rejected:
|
||||
World.items_needing_haul.erase(ent)
|
||||
ent.queue_redraw()
|
||||
|
||||
|
||||
|
|
@ -499,15 +510,15 @@ func _spawn_workbench(world_scene: Node, d: Dictionary) -> void:
|
|||
func _spawn_crop(world_scene: Node, d: Dictionary) -> void:
|
||||
var ent = _CROP_SCENE.instantiate()
|
||||
world_scene.add_child(ent)
|
||||
if ent.has_method("from_dict"):
|
||||
ent.from_dict(d)
|
||||
else:
|
||||
# Fallback for pre-from_dict Crop: set up with kind + default stage.
|
||||
ent.setup(
|
||||
Vector2i(int(d.get("tile_x", 0)), int(d.get("tile_y", 0))),
|
||||
StringName(d.get("kind", "wheat")),
|
||||
int(d.get("stage", 0))
|
||||
)
|
||||
# Crop.from_dict() is static and returns a spec Dictionary — it cannot mutate
|
||||
# the instance. Use the spec to call setup() so tile/kind/stage are applied.
|
||||
var spec: Dictionary = _CROP_SCRIPT.from_dict(d) if d else {}
|
||||
ent.setup(
|
||||
Vector2i(int(spec.get("tile_x", 0)), int(spec.get("tile_y", 0))),
|
||||
StringName(spec.get("crop_kind", &"wheat")),
|
||||
int(spec.get("stage", 0))
|
||||
)
|
||||
ent.stage_progress = int(spec.get("stage_progress", 0))
|
||||
|
||||
|
||||
func _spawn_bed(world_scene: Node, d: Dictionary) -> void:
|
||||
|
|
@ -668,6 +679,56 @@ func _apply_tilemap_layers_safe(data: Dictionary) -> void:
|
|||
Audit.log("save", "apply_tilemap_layers: World helper not yet available — skipping")
|
||||
|
||||
|
||||
# ── post-load reference resolution ────────────────────────────────────────────
|
||||
|
||||
## Resolve entity cross-references that require all nodes to be spawned first.
|
||||
## Called once at the end of apply_save(), before load_finished is emitted.
|
||||
##
|
||||
## Beds — _pending_owner_name → _owner_pawn (Pawn node reference).
|
||||
## Wolves — _pending_target_name → target_pawn (Pawn node reference).
|
||||
func _post_load_resolve_references() -> void:
|
||||
# Build a name→pawn lookup once; both bed and wolf passes share it.
|
||||
var pawn_by_name: Dictionary = {}
|
||||
for pawn in World.pawns:
|
||||
if is_instance_valid(pawn):
|
||||
var n: String = str(pawn.get("pawn_name"))
|
||||
if n != "":
|
||||
pawn_by_name[n] = pawn
|
||||
|
||||
# Beds: re-wire _owner_pawn from _pending_owner_name.
|
||||
var beds_resolved: int = 0
|
||||
for bed in World.beds:
|
||||
if not is_instance_valid(bed):
|
||||
continue
|
||||
var pending: String = str(bed.get("_pending_owner_name"))
|
||||
if pending == "":
|
||||
continue
|
||||
if pawn_by_name.has(pending):
|
||||
bed._owner_pawn = pawn_by_name[pending]
|
||||
beds_resolved += 1
|
||||
else:
|
||||
Audit.log("save", "bed at %s: owner pawn '%s' not found — left unowned" % [bed.tile, pending])
|
||||
bed._pending_owner_name = ""
|
||||
|
||||
# Wolves: re-wire target_pawn from _pending_target_name.
|
||||
var wolves_resolved: int = 0
|
||||
for wolf in World.wolves:
|
||||
if not is_instance_valid(wolf):
|
||||
continue
|
||||
var pending: String = str(wolf.get("_pending_target_name"))
|
||||
if pending == "":
|
||||
continue
|
||||
if pawn_by_name.has(pending):
|
||||
wolf.target_pawn = pawn_by_name[pending]
|
||||
wolves_resolved += 1
|
||||
else:
|
||||
Audit.log("save", "wolf at %s: target pawn '%s' not found — will pick new target next tick" % [wolf.tile, pending])
|
||||
wolf._pending_target_name = ""
|
||||
|
||||
if beds_resolved > 0 or wolves_resolved > 0:
|
||||
Audit.log("save", "_post_load_resolve_references: %d bed owners, %d wolf targets re-wired" % [beds_resolved, wolves_resolved])
|
||||
|
||||
|
||||
# ── beauty / dirt helpers ──────────────────────────────────────────────────────
|
||||
|
||||
func _save_beauty_safe() -> Dictionary:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue