class_name StockpileZone extends StorageDestination ## Concrete floor stockpile zone. A rectangular region (in tile coords) that ## accepts items matching the filter and deposits them one-per-tile. ## ## Rendered as a translucent priority-tinted overlay via _draw(). The overlay ## is always visible — Phase 17 will toggle it when the Zones panel is open. ## z_index = -1 keeps the tint below items (z_index 0) and pawns. ## ## Register/unregister with World.stockpiles happen automatically in ## _ready / _exit_tree — no external wiring needed. ## ## One-stack-per-tile, one-type-per-tile rule (design.md). ## ## See docs/architecture.md "StockpileZone". ## Region in tile coordinates. (0,0) relative to this node's map position; ## in practice this node lives at world origin so region is in world-tile space. @export var region: Rect2i = Rect2i(0, 0, 4, 4) ## Player-visible label shown in zone inspect UI (Phase 17). @export var label: String = "Stockpile" ## Pixel size of one tile — must match World.TILE_SIZE_PX. const _TILE_PX: int = 16 ## Priority-keyed fill colors for the overlay (Color(r, g, b, a)). const _PRIORITY_COLORS: Dictionary = { StorageDestination.Priority.CRITICAL: Color(0.9, 0.3, 0.3, 0.15), StorageDestination.Priority.HIGH: Color(0.9, 0.6, 0.3, 0.15), StorageDestination.Priority.NORMAL: Color(0.9, 0.9, 0.3, 0.12), StorageDestination.Priority.LOW: Color(0.3, 0.9, 0.3, 0.10), StorageDestination.Priority.OFF: Color(0.3, 0.3, 0.3, 0.10), } func _ready() -> void: z_index = -1 World.register_stockpile(self) queue_redraw() func _exit_tree() -> void: World.unregister_stockpile(self) # ── StorageDestination overrides ───────────────────────────────────────────── func accepts(item) -> bool: return _filter_accepts(item) func covers_tile(tile: Vector2i) -> bool: return region.has_point(tile) ## Scan region cells in row-major order; return the first tile not occupied by ## an item that is not being carried. Returns Vector2i(-1, -1) if the zone is ## full or the item fails the filter. func find_drop_position(item) -> Vector2i: if not accepts(item): return Vector2i(-1, -1) for x in range(region.position.x, region.position.x + region.size.x): for y in range(region.position.y, region.position.y + region.size.y): var cell := Vector2i(x, y) if _is_cell_free(cell): return cell return Vector2i(-1, -1) # ── drawing ─────────────────────────────────────────────────────────────────── func _draw() -> void: var fill_color: Color = _PRIORITY_COLORS.get(priority, Color(0.5, 0.5, 0.5, 0.12)) var border_color := Color(fill_color.r, fill_color.g, fill_color.b, 0.6) var tile_px := float(_TILE_PX) # Filled rectangle covering the entire region. var rect_px := Rect2( Vector2(region.position) * tile_px, Vector2(region.size) * tile_px ) draw_rect(rect_px, fill_color, true) # 1-px border outline. draw_rect(rect_px, border_color, false, 1.0) # ── internal helpers ────────────────────────────────────────────────────────── ## Returns true when no un-carried item is sitting on `cell`. ## One-stack-per-tile rule: the first occupied, non-carried item blocks the cell. func _is_cell_free(cell: Vector2i) -> bool: for it in World.items: if it.tile == cell and not it.being_carried: return false return true