class_name Corpse extends Node2D ## Phase 14 — pawn corpse. Spawns when a Pawn dies, carries the deceased ## pawn's identity for the burial / cremation / grave-marker pipeline. ## ## DECAY MODEL (design.md "Death & corpses"): ## decay 0..50 — fresh (no dirt contribution beyond the existing tile) ## decay 50..100 — rotting (DirtinessSystem.bump per tick, "rotting body in colony" thought severity ramps) ## decay 100 — rotted: corpse is destroyed, EventBus.corpse_rotted_away fires ## ## Hauling: corpses haul-route through StorageDestinations exactly like Items, ## but accept-filter is gated by Item.TYPE_CORPSE on the destination side. ## GraveyardZone (Agent B) is the destination class that opts in. ## ## When a corpse reaches a GraveSlot (Agent B), the slot consumes it and spawns ## a GraveMarker carrying the deceased identity. EventBus.corpse_buried fires. ## ## When a corpse is fed to a CremationPyre (Agent C), it's consumed and an ## ash item drops + "cremated friend" mood thought fires. ## ## Save round-trip carries: tile, deceased_name, deceased_portrait_color, ## decay, death_tick, death_cause. const DECAY_PER_TICK: float = 0.05 ## ~33 in-game min from fresh→rotted at 1× const DECAY_FRESH_MAX: float = 50.0 const DECAY_ROTTED: float = 100.0 ## Tile position (mirrors Pawn/Item convention). @export var tile: Vector2i = Vector2i.ZERO ## Deceased identity — carried to the GraveMarker on burial. @export var deceased_name: String = "" @export var deceased_portrait_color: Color = Color.WHITE ## Decay 0..100. Drives visual + dirty contribution + mood-thought severity. @export var decay: float = 0.0 ## Sim tick at death; useful for "how long ago" alerts. @export var death_tick: int = 0 ## Brief categorization, used by mood thoughts ("died of bleeding", "killed by wolf"). @export var death_cause: StringName = &"" func _ready() -> void: World.register_corpse(self) EventBus.sim_tick.connect(_on_sim_tick) z_index = 4 # above floors, below pawns func _exit_tree() -> void: World.unregister_corpse(self) ## Public setup helper called by the death pipeline. Mirrors Wall.setup() shape. func setup(p_tile: Vector2i, p_name: String, p_color: Color, p_cause: StringName) -> void: tile = p_tile global_position = Vector2(tile.x * 16 + 8, tile.y * 16 + 8) deceased_name = p_name deceased_portrait_color = p_color death_cause = p_cause death_tick = Sim.tick queue_redraw() ## True for HaulingProvider — corpses haul like items. ## The matching filter on the StorageDestination side is `Item.TYPE_CORPSE`. func get_item_type() -> StringName: return &"corpse" ## True when decay is past the rotting threshold; mood thought severity ramps from here. func is_rotting() -> bool: return decay >= DECAY_FRESH_MAX func _on_sim_tick(_n: int) -> void: decay += DECAY_PER_TICK if is_rotting(): # Bump dirtiness at this tile (Phase 13 hook). +5/h at decay=100 per design.md. if World.dirtiness_system != null: World.dirtiness_system.bump(tile, 0.04) # ~+8/in-game-min if decay >= DECAY_ROTTED: Audit.log("corpse", "%s rotted away at %s" % [deceased_name, tile]) EventBus.corpse_rotted_away.emit(self) queue_free() # ── visual: simple procedural sprite ───────────────────────────────────────── func _draw() -> void: # Body color desaturates as decay progresses; turns olive-green when rotting. var t := clampf(decay / DECAY_ROTTED, 0.0, 1.0) var fresh := Color(0.70, 0.60, 0.55) var rotten := Color(0.45, 0.50, 0.30) var body_color := fresh.lerp(rotten, t) # Lay-down silhouette: 14×6 rect centered. draw_rect(Rect2(-7, -3, 14, 6), body_color) # Head dot draw_circle(Vector2(-9, 0), 3, deceased_portrait_color.lerp(body_color, 0.3)) # ── save / load ────────────────────────────────────────────────────────────── func to_dict() -> Dictionary: return { "class_id": &"corpse", "tile_x": tile.x, "tile_y": tile.y, "name": deceased_name, "color_r": deceased_portrait_color.r, "color_g": deceased_portrait_color.g, "color_b": deceased_portrait_color.b, "decay": decay, "death_tick": death_tick, "cause": String(death_cause), } func from_dict(d: Dictionary) -> void: tile = Vector2i(int(d.get("tile_x", 0)), int(d.get("tile_y", 0))) global_position = Vector2(tile.x * 16 + 8, tile.y * 16 + 8) deceased_name = d.get("name", "") deceased_portrait_color = Color(d.get("color_r", 1.0), d.get("color_g", 1.0), d.get("color_b", 1.0)) decay = d.get("decay", 0.0) death_tick = int(d.get("death_tick", 0)) death_cause = StringName(d.get("cause", "")) queue_redraw()