class_name Door extends Node2D ## Door entity — built by a pawn with a Build job. Unlike walls, doors remain ## walkable once complete. Phase 5: always-open (always walkable). Phase 7+ ## will add open/close animation and toggling. ## ## Door is rendered as a bottom-anchored tall sprite (Y-sorted), same as Wall, ## so it occludes pawns walking behind it — matching the 3/4-perspective ## rendering pivot (see memory.md Decisions: "Wall layer rendering"). ## ## Build model (docs/implementation.md Phase 5): ## A ConstructionProvider creates a Job whose BUILD toil calls on_build_tick() ## once per sim tick via JobRunner. After BUILD_TICKS ticks the door is ## complete: it registers with World for future open/close logic, does NOT ## call set_cell_walkable(false), and transitions from ghost to solid. ## ## World registration: World.register_build_site / World.unregister_build_site ## are called from _ready / _exit_tree. Methods land in the Opus integration pass. const TILE_SIZE_PX: int = 16 ## Sim ticks to build a door at 1× speed (80 ticks = 4 sim seconds). const BUILD_TICKS: int = 80 # ── state ────────────────────────────────────────────────────────────────────── @export var tile: Vector2i = Vector2i.ZERO ## 0..BUILD_TICKS. var build_progress: int = 0 var _completed: bool = false ## Phase 5: always-open. Phase 7+ toggles this for open/close animation. var is_open: bool = true # ── lifecycle ────────────────────────────────────────────────────────────────── func _ready() -> void: World.register_build_site(self) func _exit_tree() -> void: World.unregister_build_site(self) # ── public API ───────────────────────────────────────────────────────────────── ## One-shot initialiser. Call after add_child() so _ready() has already fired. func setup(p_tile: Vector2i) -> void: tile = p_tile # Bottom-anchor: same as Wall so it Y-sorts correctly with pawns. position = Vector2( tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0, tile.y * TILE_SIZE_PX + TILE_SIZE_PX ) queue_redraw() Audit.log("door", "door ghost placed at %s" % tile) ## True while the door still needs construction work. func is_buildable() -> bool: return not _completed ## Human-readable label for job descriptions and Audit logs. func label() -> String: return "door" ## Called by the BUILD toil in JobRunner once per sim tick. func on_build_tick() -> void: if _completed: return build_progress += 1 queue_redraw() if build_progress >= BUILD_TICKS: _complete() ## True once the door has been fully built. func is_completed() -> bool: return _completed # ── save / load ──────────────────────────────────────────────────────────────── func to_dict() -> Dictionary: return { "tile_x": tile.x, "tile_y": tile.y, "build_progress": build_progress, "completed": _completed, "is_open": is_open, } static func from_dict(d: Dictionary) -> Dictionary: return { "tile_x": int(d.get("tile_x", 0)), "tile_y": int(d.get("tile_y", 0)), "build_progress": int(d.get("build_progress", 0)), "completed": bool(d.get("completed", false)), "is_open": bool(d.get("is_open", true)), } # ── render ───────────────────────────────────────────────────────────────────── func _draw() -> void: # Door: 16 px wide, 24 px tall, bottom-anchored. Origin at bottom-centre. # (Shorter than the full 32 px wall height — visually reads as a door opening.) var alpha: float = 1.0 if _completed else 0.4 var frame_color := Color(0.32, 0.22, 0.10, alpha) var panel_color := Color(0.52, 0.36, 0.18, alpha) var hinge_color := Color(0.20, 0.18, 0.16, alpha) # Door frame (slightly wider than the panel). draw_rect(Rect2(Vector2(-8.0, -24.0), Vector2(16.0, 24.0)), frame_color) # Door panel (inset 2 px on each side). draw_rect(Rect2(Vector2(-6.0, -22.0), Vector2(12.0, 20.0)), panel_color) # Hinge dot on the left side. draw_circle(Vector2(-6.0, -18.0), 1.5, hinge_color) # Outline. draw_rect(Rect2(Vector2(-8.0, -24.0), Vector2(16.0, 24.0)), Color(0.0, 0.0, 0.0, 0.5 * alpha), false, 1.0) # ── internal ─────────────────────────────────────────────────────────────────── func _complete() -> void: _completed = true # Doors are walkable — do NOT call set_cell_walkable(false). # Register so future open/close logic can locate this door by tile. World.register_door(self) queue_redraw() Audit.log("door", "door completed at %s" % tile)