Door sprite — swap procedural draw for FG_Fortress arched door

Closed wooden door with stone arch from FG_Fortress (4..5, 19..20) — a 32×32
sprite bottom-anchored on the door tile. The 2-tile-wide sprite extends 8 px
into each flanking tile so the stone arch merges into adjacent wall sprites,
and the lintel rises one tile above the door tile (Y-sorted, occludes pawns).

Ghost state stays at 40% alpha until build completes, matching the Wall/Bed/
Workbench convention. _draw() is now a no-op; the sprite handles everything.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-12 15:49:50 +01:00
parent 7274ada5d1
commit ac21443037

View file

@ -13,6 +13,13 @@ class_name Door extends Node2D
## complete: it registers with World for future open/close logic, does NOT
## call set_cell_walkable(false), and transitions from ghost to solid.
##
## Render: a 32×32 closed-wood-door sprite from FG_Fortress (atlas (4..5, 19..20))
## is bottom-anchored on the door tile. The sprite is 2 tiles wide so the stone
## arch extends 8 px into each flanking tile — which is correct, since walls
## typically sit on those tiles and the arch is supposed to merge into them.
## The sprite is 2 tiles tall so the lintel rises one tile above the door tile
## (matching Wall's bottom-anchored sprite convention).
##
## World registration: World.register_build_site / World.unregister_build_site
## are called from _ready / _exit_tree. Methods land in the Opus integration pass.
@ -21,6 +28,13 @@ 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
## ElvGames Fortress atlas. Stone-arched closed wooden door — top-left of the
## 32×32 region at (4, 19); covers tiles (4..5, 19..20).
const _DOOR_TEX: Texture2D = preload("res://art/tiles/FG_Fortress.png")
const _DOOR_COORD: Vector2i = Vector2i(4, 19)
const _DOOR_W_TILES: int = 2
const _DOOR_H_TILES: int = 2
# ── state ──────────────────────────────────────────────────────────────────────
@export var tile: Vector2i = Vector2i.ZERO
@ -52,10 +66,43 @@ func setup(p_tile: Vector2i) -> void:
tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
tile.y * TILE_SIZE_PX + TILE_SIZE_PX
)
y_sort_enabled = true
_build_sprite()
queue_redraw()
Audit.log("door", "door ghost placed at %s" % tile)
## Builds the 32×32 FG_Fortress closed-door Sprite2D child. Bottom-anchored
## so it sits flush with the door tile's bottom edge — the lintel rises into
## the tile above and the stone arch extends one half-tile into each adjacent
## tile. Idempotent: re-running drops the previous sprite first.
func _build_sprite() -> void:
var prev := get_node_or_null("Sprite")
if prev != null:
prev.queue_free()
var sprite := Sprite2D.new()
sprite.name = "Sprite"
sprite.texture = _DOOR_TEX
sprite.region_enabled = true
var pixels_w: int = TILE_SIZE_PX * _DOOR_W_TILES
var pixels_h: int = TILE_SIZE_PX * _DOOR_H_TILES
sprite.region_rect = Rect2(
_DOOR_COORD.x * TILE_SIZE_PX,
_DOOR_COORD.y * TILE_SIZE_PX,
pixels_w,
pixels_h,
)
sprite.centered = true
# Centred at (0, -16): bottom edge of sprite at y=0 (= tile bottom), top at
# y=-32. Horizontally centred so the 32-wide sprite extends ±16 px around
# the door tile's centre, overlapping the two neighbouring wall tiles.
sprite.offset = Vector2(0.0, -float(pixels_h) / 2.0)
sprite.z_index = 0
# Ghost state — translucent until built. Solidified in _complete().
sprite.modulate.a = 1.0 if _completed else 0.4
add_child(sprite)
## True while the door still needs construction work.
func is_buildable() -> bool:
return not _completed
@ -107,29 +154,11 @@ static func from_dict(d: Dictionary) -> Dictionary:
# ── render ─────────────────────────────────────────────────────────────────────
func _draw() -> void:
# 3/4-perspective door — fits strictly within the tile (16×16) so it
# slots cleanly between adjacent walls. Origin (0,0) is at the tile's
# bottom-centre. Tile spans local Y: -16 to 0.
var alpha: float = 1.0 if _completed else 0.4
# Matches Wall's 3/4-band layout so doors and walls share a top horizon.
var lintel_color := Color(0.55, 0.40, 0.22, alpha) # stone-warmed top lintel
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)
var outline := Color(0.16, 0.10, 0.04, 0.7 * alpha)
# Top lintel band (matches wall top-face height of 5 px so it lines up).
draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 5.0)), lintel_color)
# Door frame — fills the front-face band (matches wall front 11 px).
draw_rect(Rect2(Vector2(-8.0, -11.0), Vector2(16.0, 11.0)), frame_color)
# Door panel (inset 1 px each side, leaves 2 px frame visible left/right).
draw_rect(Rect2(Vector2(-6.0, -10.0), Vector2(12.0, 10.0)), panel_color)
# Hinge dot.
draw_circle(Vector2(-5.0, -5.0), 1.0, hinge_color)
# Top/bottom borders + tile outline.
draw_line(Vector2(-8.0, -11.0), Vector2(8.0, -11.0), Color(0.20, 0.14, 0.06, alpha), 1.0)
draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 16.0)), outline, false, 1.0)
# Door body is rendered by the Sprite2D child (see _build_sprite()).
# No procedural overlay needed — the FG_Fortress closed-door sprite already
# shows the wood panels, stone arch, and lintel. Ghost alpha is handled
# via sprite.modulate.a in _build_sprite / _complete.
pass
# ── internal ───────────────────────────────────────────────────────────────────
@ -148,5 +177,9 @@ func _complete() -> void:
# Phase 13 — notify RoomDetector so the door tile is eligible as an
# interior boundary tile for room BFS.
World.mark_door_tile(tile)
# Solidify the ghost sprite from 40% to full opacity.
var sprite: Sprite2D = get_node_or_null("Sprite")
if sprite != null:
sprite.modulate.a = 1.0
queue_redraw()
Audit.log("door", "door completed at %s" % tile)