diff --git a/art/tiles/FG_Interior.png b/art/tiles/FG_Interior.png new file mode 100644 index 0000000..0be72be Binary files /dev/null and b/art/tiles/FG_Interior.png differ diff --git a/art/tiles/FG_Interior.png.import b/art/tiles/FG_Interior.png.import new file mode 100644 index 0000000..e0d656d --- /dev/null +++ b/art/tiles/FG_Interior.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c32nie0v2b852" +path="res://.godot/imported/FG_Interior.png-b7c7beab844222dbec65c4ef01454eae.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/tiles/FG_Interior.png" +dest_files=["res://.godot/imported/FG_Interior.png-b7c7beab844222dbec65c4ef01454eae.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/entities/bed.gd b/scenes/entities/bed.gd index d0a0b4d..4ce2d33 100644 --- a/scenes/entities/bed.gd +++ b/scenes/entities/bed.gd @@ -40,6 +40,25 @@ const BUILD_TICKS: int = 80 ## SHODDY=-8, NORMAL=-2, EXCELLENT=0, MASTERWORK=5, LEGENDARY=8 const SLEEP_MOOD_BY_QUALITY: Array[int] = [-8, -2, 0, 5, 8] +## ElvGames House Interior atlas — 16×32 single-column bed crops. Each variant +## shows a complete narrow bed: head with rounded pillow + frame on top tile, +## body + wood foot on the bottom tile. See /tmp/bed_candidates.png from the +## 2026-05-12 visual pass for the visual diff that picked these coords. +## +## The sprite spans the bed's tile + the tile immediately south. The southern +## tile stays walkable in the pathfinder — pawns can pass through the visual +## foot of the bed — matching the Phase 4 "rocks are walkable" simplification. +const _BED_TEX: Texture2D = preload("res://art/tiles/FG_Interior.png") +const _BED_TILE_W: int = 16 +const _BED_TILE_H: int = 32 # 2 tiles tall — head row + body row +## Atlas top-left coords (x, y) for each variant. Picked by deterministic hash +## from the bed's tile so the same bed renders the same colour each session. +const _BED_VARIANT_COORDS: Array[Vector2i] = [ + Vector2i(32, 22), # brown bed (warm wood frame) + Vector2i(35, 22), # blue bed (cool quilt) + Vector2i(38, 22), # pink bed (rosy quilt) +] + # ── exports ─────────────────────────────────────────────────────────────────── ## Tile position of this bed in world-tile coordinates. @@ -94,15 +113,58 @@ func _exit_tree() -> void: ## One-shot initialiser. Call after add_child() so _ready() has fired. +## Builds the bed sprite here (not _ready) because _ready fires before the +## caller passes in the real tile — building the sprite earlier would just +## attach it to the (0,0) default position. func setup(p_tile: Vector2i) -> void: tile = p_tile position = Vector2( tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0, tile.y * TILE_SIZE_PX + TILE_SIZE_PX ) + # Y-sort so a pawn standing south of the bed (in the foot tile) draws over + # the bed sprite, while a pawn sleeping IN the bed (same tile) layers under + # the procedural medical-cross overlay. World scene has y_sort_enabled = true. + y_sort_enabled = true + _build_sprite() queue_redraw() +## Adds a 16×32 Sprite2D child painted with one of the bed variants. Variant +## chosen deterministically from the tile so the same bed renders the same +## colour across boots and load/save. The sprite is centred vertically on the +## border between the bed tile and the tile below, so the head shows in the +## bed's tile and the body extends visually into the southern tile. +func _build_sprite() -> void: + # Idempotency: if a sprite was added on an earlier setup call, drop it. + var prev := get_node_or_null("Sprite") + if prev != null: + prev.queue_free() + var sprite := Sprite2D.new() + sprite.name = "Sprite" + sprite.texture = _BED_TEX + sprite.region_enabled = true + var idx: int = (tile.x * 31 + tile.y * 17) % _BED_VARIANT_COORDS.size() + var coord: Vector2i = _BED_VARIANT_COORDS[idx] + sprite.region_rect = Rect2( + coord.x * TILE_SIZE_PX, + coord.y * TILE_SIZE_PX, + _BED_TILE_W, + _BED_TILE_H, + ) + sprite.centered = true + # Node position.y already sits at the bottom of the bed tile (top of the + # foot tile). A centred 16×32 sprite at offset (0, 0) then spans y -16..+16, + # covering bed-tile + foot-tile vertically. No extra offset needed. + sprite.offset = Vector2.ZERO + # Sprite stays z=0; procedural _draw overlay (cross + ghost) renders on top + # of the parent Node2D via its own _draw(), which fires after children. + 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) + + # ── BuildJob interface (matches Wall / Crate / Workbench shape) ─────────────── ## True while the bed still needs construction work. @@ -203,82 +265,28 @@ func from_dict(d: Dictionary) -> void: # ── render ───────────────────────────────────────────────────────────────────── func _draw() -> void: - # 3/4-perspective bed — fits within the tile (16×16 local box). - # Origin (0, 0) = tile bottom-centre. Tile spans local Y: -16 to 0. - # - # Layout (bottom-anchored, same as Wall / Workbench): - # Top band (5 px, Y -16..-11) — top surface of the bed frame (lit) - # Body band (8 px, Y -11..-3) — sheet / quilt, quality-tinted - # Leg band (3 px, Y -3.. 0) — bed-frame legs / base - # - # The pillow is a 6×3 rect inset at the top of the body band. - # Quality tints the sheet; the frame/legs stay a constant warm brown. - # Ghost state draws at 0.4 alpha. + # Bed body / pillow now come from the Sprite2D child (see _build_sprite()). + # This _draw renders only the small red medical-cross overlay over the + # pillow region of the sprite. Sprite handles ghost alpha via modulate.a. + if not is_medical: + return var alpha: float = 1.0 if _completed else 0.4 - _draw_bed(alpha) - - -func _draw_bed(alpha: float) -> void: - # Frame colours — same for all quality tiers (wood frame). - var frame_top := Color(0.52, 0.38, 0.20, alpha) # lit top surface - var frame_dark := Color(0.34, 0.24, 0.12, alpha) # shaded legs / base - var frame_edge := Color(0.28, 0.18, 0.08, alpha) # top-front horizon - var outline := Color(0.20, 0.12, 0.04, 0.70 * alpha) - - # Sheet colour varies by quality tier. - var sheet_color := _sheet_color_for_quality(quality, alpha) - - # Pillow: light cream, inset at top-centre of the body band. - var pillow := Color(0.95, 0.92, 0.85, alpha) - - # ── top surface (lit band) ──────────────────────────────────────────────── - draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 5.0)), frame_top) - - # ── sheet / body band ───────────────────────────────────────────────────── - draw_rect(Rect2(Vector2(-7.0, -11.0), Vector2(14.0, 8.0)), sheet_color) - - # Pillow: 6×3, horizontally centred, flush with the top of the body band. - draw_rect(Rect2(Vector2(-3.0, -11.0), Vector2(6.0, 3.0)), pillow) - - # Medical cross: small red + over the pillow area. - # Two overlapping rects form a classic cross — universal medical symbol. - if is_medical: - var cross := Color(0.85, 0.10, 0.10, alpha) - draw_rect(Rect2(Vector2(-3.0, -8.0), Vector2(6.0, 2.0)), cross) # horizontal bar - draw_rect(Rect2(Vector2(-1.0, -10.0), Vector2(2.0, 6.0)), cross) # vertical bar - - # ── base / legs band ────────────────────────────────────────────────────── - draw_rect(Rect2(Vector2(-8.0, -3.0), Vector2(16.0, 3.0)), frame_dark) - - # ── horizon line (top → front depth edge) ──────────────────────────────── - draw_line(Vector2(-8.0, -11.0), Vector2(8.0, -11.0), frame_edge, 1.0) - - # ── outline ─────────────────────────────────────────────────────────────── - draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 16.0)), outline, false, 1.0) - - -## Returns the sheet fill colour for the given quality int. -## Quality indices: 0=SHODDY, 1=NORMAL, 2=EXCELLENT, 3=MASTERWORK, 4=LEGENDARY. -func _sheet_color_for_quality(q: int, alpha: float) -> Color: - match q: - 0: # SHODDY — drab grey-brown - return Color(0.45, 0.40, 0.35, alpha) - 1: # NORMAL — warm tan - return Color(0.55, 0.40, 0.30, alpha) - 2: # EXCELLENT — cool blue - return Color(0.30, 0.45, 0.65, alpha) - 3: # MASTERWORK — gold-brown - return Color(0.65, 0.45, 0.20, alpha) - 4: # LEGENDARY — regal pink - return Color(0.75, 0.40, 0.55, alpha) - _: # fallback — same as NORMAL - return Color(0.55, 0.40, 0.30, alpha) + var cross := Color(0.85, 0.10, 0.10, alpha) + # Pillow on the FG_Interior bed sprite sits roughly at local-y -12..-8 inside + # the head tile. Drop a small + centred there so the player can tell medical + # beds apart at a glance. + draw_rect(Rect2(Vector2(-3.0, -11.0), Vector2(6.0, 2.0)), cross) # horizontal bar + draw_rect(Rect2(Vector2(-1.0, -13.0), Vector2(2.0, 6.0)), cross) # vertical bar # ── internal ────────────────────────────────────────────────────────────────── func _complete() -> void: _completed = true + # Solidify the ghost: sprite goes 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("bed", "%s built at %s" % [label_text, tile]) # Phase 13 — notify BeautySystem so nearby tile beauty scores update. diff --git a/scenes/entities/big_rock.gd.uid b/scenes/entities/big_rock.gd.uid new file mode 100644 index 0000000..63d95ee --- /dev/null +++ b/scenes/entities/big_rock.gd.uid @@ -0,0 +1 @@ +uid://cvkygssrufgs4