class_name Floor extends Node2D ## Floor entity — built by a pawn with a Build job. Stamps the Floor TileMap ## layer once complete. Floors do NOT block pathfinding. ## ## Floor is ground-level: its render origin sits at the tile centre (not ## bottom-anchored like Wall/Door). Y-sort is not needed because floor sprites ## always sort below any tall entity at the same tile. ## ## 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 floor is ## complete: it stamps the data-layer TileMap (World.mark_floor_tile) and ## transitions from ghost (40% alpha) 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 lay a floor at 1× speed (30 ticks = 1.5 sim seconds). const BUILD_TICKS: int = 30 const MATERIAL_WOOD: StringName = &"wood" const MATERIAL_STONE: StringName = &"stone" const MATERIAL_DIRT: StringName = &"dirt" # ── state ────────────────────────────────────────────────────────────────────── @export var floor_material: StringName = MATERIAL_WOOD @export var tile: Vector2i = Vector2i.ZERO ## 0..BUILD_TICKS. var build_progress: int = 0 var _completed: bool = false # ── 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, p_material: StringName) -> void: tile = p_tile floor_material = p_material # Top-anchor (origin at tile's top edge) so Y-sort places the floor under # pawns standing on the same tile (pawns anchor at mid-tile, so pawn.y > floor.y). # _draw offsets compensate (rects span y=0..TILE_SIZE_PX, not -half..+half). position = Vector2( tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0, tile.y * TILE_SIZE_PX ) queue_redraw() Audit.log("floor", "%s floor ghost placed at %s" % [floor_material, tile]) ## True while the floor still needs construction work. func is_buildable() -> bool: return not _completed ## Human-readable label for job descriptions and Audit logs. func label() -> String: return "%s floor" % floor_material ## 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 floor has been fully laid. func is_completed() -> bool: return _completed # ── save / load ──────────────────────────────────────────────────────────────── func to_dict() -> Dictionary: return { "class_id": &"floor", "tile_x": tile.x, "tile_y": tile.y, "material": str(floor_material), "build_progress": build_progress, "completed": _completed, } static func from_dict(d: Dictionary) -> Dictionary: return { "tile_x": int(d.get("tile_x", 0)), "tile_y": int(d.get("tile_y", 0)), "material": str(d.get("material", "wood")), "build_progress": int(d.get("build_progress", 0)), "completed": bool(d.get("completed", false)), } # ── render ───────────────────────────────────────────────────────────────────── func _draw() -> void: # 16×16 tile centred on origin. var alpha: float = 1.0 if _completed else 0.4 var half: float = TILE_SIZE_PX / 2.0 match floor_material: MATERIAL_WOOD: _draw_wood_floor(alpha, half) MATERIAL_STONE: _draw_stone_floor(alpha, half) _: # MATERIAL_DIRT and any future materials _draw_dirt_floor(alpha, half) func _draw_wood_floor(alpha: float, half: float) -> void: # Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX). var base := Color(0.58, 0.40, 0.20, alpha) var plank := Color(0.50, 0.34, 0.16, alpha) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base) # Horizontal plank lines (offsets are from tile centre = +half from origin). for y_offset in [5.0, 10.0]: draw_line( Vector2(-half, y_offset), Vector2(half, y_offset), plank, 1.0 ) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0) func _draw_stone_floor(alpha: float, half: float) -> void: # Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX). var base := Color(0.60, 0.60, 0.57, alpha) var joint := Color(0.45, 0.45, 0.43, alpha) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base) # Stone tile grid lines (cross at tile centre). draw_line(Vector2(0.0, 0.0), Vector2(0.0, TILE_SIZE_PX), joint, 1.0) draw_line(Vector2(-half, half), Vector2(half, half), joint, 1.0) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.3 * alpha), false, 1.0) func _draw_dirt_floor(alpha: float, half: float) -> void: # Top-anchored origin: rect spans (-half, 0) to (+half, TILE_SIZE_PX). var base := Color(0.42, 0.30, 0.18, alpha) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), base) draw_rect(Rect2(Vector2(-half, 0.0), Vector2(TILE_SIZE_PX, TILE_SIZE_PX)), Color(0.0, 0.0, 0.0, 0.25 * alpha), false, 1.0) # ── internal ─────────────────────────────────────────────────────────────────── func _complete() -> void: _completed = true # Floors do NOT block pathfinding — no pathfinder call here. World.mark_floor_tile(tile, floor_material) queue_redraw() Audit.log("floor", "%s floor completed at %s" % [floor_material, tile])