Procedural workbench redraws + 3-season tree variety
Workbenches: replace atlas sprites (which read as chest-of-drawers, candle base, kitchen stove, cushion stack) with procedural _draw_ methods following CremationPyre._draw_pyre's pattern. Carpenter shows a wood bench with saw + log slabs; Smelter a stone furnace with smoking chimney; Hearth a tall h=2 stone fireplace with arched opening + log fire; Millstone a wood frame supporting a round grindstone wheel. Trees: add Summer + Fall atlases alongside Spring (12 visual variants from 4 silhouettes × 3 seasons). Selection hash mixes season independently so neighbouring tiles don't all share the same palette.
This commit is contained in:
parent
840db55b44
commit
c97ada80d7
6 changed files with 278 additions and 113 deletions
BIN
art/sprites/FG_Tree_Fall.png
Normal file
BIN
art/sprites/FG_Tree_Fall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
40
art/sprites/FG_Tree_Fall.png.import
Normal file
40
art/sprites/FG_Tree_Fall.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://qen8u4g1oee4"
|
||||
path="res://.godot/imported/FG_Tree_Fall.png-63225671846c6354f20ce0b5e0a5e31e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/sprites/FG_Tree_Fall.png"
|
||||
dest_files=["res://.godot/imported/FG_Tree_Fall.png-63225671846c6354f20ce0b5e0a5e31e.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
|
||||
BIN
art/sprites/FG_Tree_Summer.png
Normal file
BIN
art/sprites/FG_Tree_Summer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
40
art/sprites/FG_Tree_Summer.png.import
Normal file
40
art/sprites/FG_Tree_Summer.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cepguvswk8ecj"
|
||||
path="res://.godot/imported/FG_Tree_Summer.png-1bc1b92f64d4677ada2e125e991dc553.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/sprites/FG_Tree_Summer.png"
|
||||
dest_files=["res://.godot/imported/FG_Tree_Summer.png-1bc1b92f64d4677ada2e125e991dc553.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
|
||||
|
|
@ -34,14 +34,23 @@ var chop_designated: bool = false
|
|||
# Preloaded scene for spawned wood items.
|
||||
const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
|
||||
|
||||
## ElvGames Grasslands tree pack — 4 variants laid out left-to-right.
|
||||
## Each variant is 64×80 px; trunk base sits in the bottom ~10 rows. We anchor
|
||||
## the sprite center 32 px above tile origin so the trunk bottom lands at the
|
||||
## tile's bottom edge and the canopy rises into the cells above.
|
||||
const _TREE_TEX: Texture2D = preload("res://art/sprites/FG_Tree_Spring.png")
|
||||
## ElvGames Grasslands tree pack — 4 silhouettes laid out left-to-right (64×80
|
||||
## each). Trunk base sits in the bottom ~10 rows; we anchor the sprite centre
|
||||
## 32 px above tile origin so the trunk bottom lands at the tile's bottom edge
|
||||
## and the canopy rises into the cells above.
|
||||
##
|
||||
## Three season palettes (Spring / Summer / Fall) give 12 visual variants from
|
||||
## the same silhouette set. Winter is omitted — snowy trees look out of place
|
||||
## in the current biome. When a season-cycle system lands later, swap the
|
||||
## active texture by season globally instead of per-tree.
|
||||
const _TREE_TEXES: Array[Texture2D] = [
|
||||
preload("res://art/sprites/FG_Tree_Spring.png"),
|
||||
preload("res://art/sprites/FG_Tree_Summer.png"),
|
||||
preload("res://art/sprites/FG_Tree_Fall.png"),
|
||||
]
|
||||
const _TREE_VARIANT_W: int = 64
|
||||
const _TREE_VARIANT_H: int = 80
|
||||
const _TREE_VARIANT_COUNT: int = 4
|
||||
const _TREE_SILHOUETTES: int = 4 # silhouettes per atlas (columns)
|
||||
|
||||
|
||||
# ── lifecycle ─────────────────────────────────────────────────────────────────
|
||||
|
|
@ -55,16 +64,20 @@ func _ready() -> void:
|
|||
World.register_tree(self)
|
||||
|
||||
|
||||
## Adds a Sprite2D child painted with one of the 4 ElvGames tree variants.
|
||||
## Variant chosen deterministically from the tile coord so the same tile always
|
||||
## gets the same tree silhouette across boots and load/save.
|
||||
## Adds a Sprite2D child painted with one of the 12 ElvGames tree variants
|
||||
## (4 silhouettes × 3 season palettes). Variant chosen deterministically
|
||||
## from the tile coord so the same tile always gets the same tree silhouette
|
||||
## across boots and load/save.
|
||||
func _build_sprite() -> void:
|
||||
var sprite := Sprite2D.new()
|
||||
sprite.name = "Sprite"
|
||||
sprite.texture = _TREE_TEX
|
||||
var hash_seed: int = tile.x * 31 + tile.y * 17
|
||||
var silhouette: int = hash_seed % _TREE_SILHOUETTES
|
||||
# Independent hash mix for season so neighbouring tiles don't all match.
|
||||
var season: int = ((hash_seed / _TREE_SILHOUETTES) + tile.x * 7 + tile.y * 11) % _TREE_TEXES.size()
|
||||
sprite.texture = _TREE_TEXES[season]
|
||||
sprite.region_enabled = true
|
||||
var variant: int = (tile.x * 31 + tile.y * 17) % _TREE_VARIANT_COUNT
|
||||
sprite.region_rect = Rect2(variant * _TREE_VARIANT_W, 0, _TREE_VARIANT_W, _TREE_VARIANT_H)
|
||||
sprite.region_rect = Rect2(silhouette * _TREE_VARIANT_W, 0, _TREE_VARIANT_W, _TREE_VARIANT_H)
|
||||
sprite.centered = true
|
||||
# Lift the sprite up so its bottom edge sits at the tile's bottom row.
|
||||
# Sprite center is at offset.y; sprite half-height is _TREE_VARIANT_H/2 = 40.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
class_name Workbench extends Node2D
|
||||
## Workbench entity — buildable structure where pawns craft items per bills.
|
||||
##
|
||||
## Rendered as a bottom-anchored sprite (Y-sorted) matching the 3/4-perspective
|
||||
## convention from Wall/Door. Ghost state (40% alpha) while construction is
|
||||
## in progress; solid once _completed.
|
||||
## Rendered procedurally (Y-sorted) matching the 3/4-perspective convention
|
||||
## from Wall/Door. Ghost state (40% alpha) while construction is in progress;
|
||||
## solid once _completed.
|
||||
##
|
||||
## Variant appearance is driven by label_text:
|
||||
## "Carpenter" → warm-brown wood bench with a vise detail
|
||||
## "Smelter" → dark grey stone block with an orange ember glow
|
||||
## "Carpenter" → wooden workbench with saw + log slabs on top
|
||||
## "Smelter" → dark stone furnace with chimney and ember glow
|
||||
## "Hearth" → tall stone fireplace with mantle + log fire (h=2 tiles)
|
||||
## "Millstone" → wooden frame supporting a round grindstone wheel
|
||||
## Other → generic warm-grey fallback
|
||||
##
|
||||
## Bill model (architecture.md "Production: workbenches, recipes, bills"):
|
||||
|
|
@ -42,45 +44,34 @@ const HEARTH_LIGHT_RADIUS: int = 5
|
|||
## Pixel size of the procedural radial gradient used for PointLight2D.
|
||||
const LIGHT_TEXTURE_SIZE: int = 64
|
||||
|
||||
# ── sprite atlas (replaces procedural _draw for the four named variants) ─────
|
||||
## Variant → (texture, atlas top-left coord, height in tiles). Selected from
|
||||
## the ElvGames House Interior + Marketplace tilesets in the 2026-05-12 visual
|
||||
## pass; see /tmp/workbench_candidates_v2.png from that session for the diff.
|
||||
# ── variant rendering ─────────────────────────────────────────────────────────
|
||||
## All four named workbench variants render procedurally via _draw(). The
|
||||
## atlas-sprite approach was abandoned in the 2026-05-15 polish pass after
|
||||
## visual review: the chosen ElvGames atlas tiles read as a chest-of-drawers
|
||||
## (Carpenter), a tiny candle base (Smelter), a 2-burner stove (Hearth), and
|
||||
## a stack of cushions (Millstone). Procedural draws give us shape control to
|
||||
## hit the silhouettes those names imply. See CremationPyre._draw_pyre() for
|
||||
## precedent — same pattern, local coords centered at (0, 0) at the BOTTOM of
|
||||
## the workbench tile, drawing UP into negative y.
|
||||
##
|
||||
## h_tiles = 2 sprites bottom-anchor and extend UP into the tile above (Bed
|
||||
## pattern) so the carpenter's tall cabinet reads as a piece of furniture
|
||||
## standing in the room rather than a flat decal. h_tiles = 1 sprites stay
|
||||
## within the workbench tile (anvil, stove top, barrel — squat shapes).
|
||||
##
|
||||
## Unrecognised label_texts fall through to procedural _draw_generic, so
|
||||
## ad-hoc workbench variants keep rendering until a sprite is picked for them.
|
||||
const _INTERIOR_TEX: Texture2D = preload("res://art/tiles/FG_Interior.png")
|
||||
const _MARKETPLACE_TEX: Texture2D = preload("res://art/tiles/FG_Marketplace.png")
|
||||
const _VARIANT_SPRITES: Dictionary = {
|
||||
"Carpenter": {"tex": _INTERIOR_TEX, "coord": Vector2i(24, 20), "h_tiles": 2},
|
||||
"Smelter": {"tex": _MARKETPLACE_TEX, "coord": Vector2i(8, 30), "h_tiles": 1},
|
||||
"Hearth": {"tex": _INTERIOR_TEX, "coord": Vector2i(16, 32), "h_tiles": 1},
|
||||
"Millstone": {"tex": _INTERIOR_TEX, "coord": Vector2i(17, 40), "h_tiles": 1},
|
||||
}
|
||||
## Tall variants (Hearth, h=2 logically) draw above y=-16 into the tile north
|
||||
## of the bench; pawns stand correctly behind them because y_sort_enabled is
|
||||
## on and position.y is anchored at the bench-tile's bottom edge.
|
||||
|
||||
# ── exports ───────────────────────────────────────────────────────────────────
|
||||
|
||||
## Tile position of this workbench in world-tile coordinates.
|
||||
@export var tile: Vector2i = Vector2i.ZERO
|
||||
|
||||
## Player-visible label. Also drives the sprite variant (see _VARIANT_SPRITES)
|
||||
## and procedural _draw fallback for unrecognised values.
|
||||
## Setter rebuilds the sprite child idempotently — callers can assign
|
||||
## label_text either before OR after setup() and end up with the right sprite.
|
||||
## Player-visible label. Also drives the procedural _draw() variant dispatch.
|
||||
## Setter triggers a redraw + lazy light build — callers can assign label_text
|
||||
## either before OR after setup() and the visual catches up.
|
||||
## (World.gd assigns it after setup(); SaveSystem._spawn_workbench too.)
|
||||
@export var label_text: String = "Workbench":
|
||||
set(value):
|
||||
label_text = value
|
||||
# Setter fires from .tscn property initialisation BEFORE _ready, so
|
||||
# guard the rebuild until the node is actually in the tree (children
|
||||
# can't be added safely before then).
|
||||
if is_inside_tree():
|
||||
_build_sprite()
|
||||
queue_redraw()
|
||||
# Hearth-light catch-up: _ready() builds the PointLight2D only when
|
||||
# label_text is already "Hearth", but the project's call pattern
|
||||
# (add_child first, then set label_text) means _ready always saw the
|
||||
|
|
@ -165,8 +156,6 @@ func _exit_tree() -> void:
|
|||
|
||||
|
||||
## One-shot initialiser. Call after add_child() so _ready() has fired.
|
||||
## Builds the variant sprite using the current label_text — if the caller
|
||||
## hasn't assigned label_text yet, the setter rebuilds the sprite on assignment.
|
||||
## Idempotent (safe under save-load's instantiate → setup → from_dict → setup chain).
|
||||
func setup(p_tile: Vector2i) -> void:
|
||||
tile = p_tile
|
||||
|
|
@ -174,52 +163,12 @@ 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 so a 16×32 Carpenter sprite (which rises into the tile north of
|
||||
# the bench) occludes pawns standing behind it. Matches Bed / Wall.
|
||||
# Y-sort so tall variants (Hearth) drawing into the tile north of the bench
|
||||
# occlude pawns standing behind. Matches Bed / Wall.
|
||||
y_sort_enabled = true
|
||||
_build_sprite()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
## Build the variant Sprite2D child (or no-op when label_text isn't in the
|
||||
## sprite table — those fall through to procedural _draw rendering).
|
||||
## Idempotent: frees any previous Sprite child first. Called from setup() AND
|
||||
## from the label_text setter, so the sprite always matches the current variant.
|
||||
func _build_sprite() -> void:
|
||||
var prev := get_node_or_null("Sprite")
|
||||
if prev != null:
|
||||
prev.queue_free()
|
||||
var data = _VARIANT_SPRITES.get(label_text)
|
||||
if data == null:
|
||||
# Generic / unknown variants keep procedural rendering. _draw_generic
|
||||
# fires through the existing match in _draw().
|
||||
return
|
||||
var sprite := Sprite2D.new()
|
||||
sprite.name = "Sprite"
|
||||
sprite.texture = data["tex"]
|
||||
sprite.region_enabled = true
|
||||
var coord: Vector2i = data["coord"]
|
||||
var h_tiles: int = data["h_tiles"]
|
||||
var pixels_h: int = TILE_SIZE_PX * h_tiles
|
||||
sprite.region_rect = Rect2(
|
||||
coord.x * TILE_SIZE_PX,
|
||||
coord.y * TILE_SIZE_PX,
|
||||
TILE_SIZE_PX,
|
||||
pixels_h,
|
||||
)
|
||||
sprite.centered = true
|
||||
# Parent position.y is at the BOTTOM of the workbench tile (see setup()).
|
||||
# Bottom-anchor the sprite by offsetting it up by half its height, so a
|
||||
# 16×16 sprite spans local y −16..0 (within the bench tile) and a 16×32
|
||||
# sprite spans local y −32..0 (bench tile + the tile above it, like Bed
|
||||
# but extending UPWARD — workbenches don't have a "foot tile").
|
||||
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)
|
||||
|
||||
|
||||
# ── BuildJob interface ────────────────────────────────────────────────────────
|
||||
|
||||
## True while the workbench still needs construction work.
|
||||
|
|
@ -367,29 +316,156 @@ func from_dict(d: Dictionary) -> void:
|
|||
# ── render ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
func _draw() -> void:
|
||||
# Sprite-backed variants (Carpenter / Smelter / Hearth) render entirely
|
||||
# through their Sprite2D child — no procedural fallback needed. Millstone
|
||||
# also has a sprite but keeps a small dark-grey wheel overlay so the
|
||||
# wood barrel below reads as "grinding station" rather than a plain barrel.
|
||||
# Unrecognised label_texts fall through to _draw_generic so ad-hoc
|
||||
# benches still render until a sprite is picked for them.
|
||||
var alpha: float = 1.0 if _completed else 0.4
|
||||
if label_text == "Millstone":
|
||||
_draw_millstone_overlay(alpha)
|
||||
return
|
||||
if _VARIANT_SPRITES.has(label_text):
|
||||
return
|
||||
_draw_generic(alpha)
|
||||
match label_text:
|
||||
"Carpenter": _draw_carpenter(alpha)
|
||||
"Smelter": _draw_smelter(alpha)
|
||||
"Hearth": _draw_hearth(alpha)
|
||||
"Millstone": _draw_millstone(alpha)
|
||||
_: _draw_generic(alpha)
|
||||
|
||||
|
||||
## Stone-wheel overlay drawn on top of the Millstone barrel sprite. Without
|
||||
## this, the barrel reads as "water/grain storage" rather than a millstone.
|
||||
## The circle sits inside the top half of the barrel's tile.
|
||||
func _draw_millstone_overlay(alpha: float) -> void:
|
||||
var wheel := Color(0.40, 0.40, 0.36, alpha)
|
||||
var rim := Color(0.22, 0.22, 0.20, alpha)
|
||||
draw_circle(Vector2(0.0, -10.0), 4.5, wheel)
|
||||
draw_arc(Vector2(0.0, -10.0), 4.5, 0.0, TAU, 12, rim, 1.0)
|
||||
## Carpenter — wooden plank top with two visible legs and a hand-saw + log
|
||||
## slabs on top. Reads as a workshop bench at 16×16 thanks to the saw blade
|
||||
## silhouette breaking the plain top.
|
||||
func _draw_carpenter(alpha: float) -> void:
|
||||
var plank_top := Color(0.70, 0.50, 0.30, alpha)
|
||||
var plank_front := Color(0.55, 0.38, 0.22, alpha)
|
||||
var plank_edge := Color(0.35, 0.22, 0.12, alpha)
|
||||
var leg := Color(0.30, 0.20, 0.10, alpha)
|
||||
var saw_blade := Color(0.78, 0.78, 0.82, alpha)
|
||||
var saw_handle := Color(0.55, 0.30, 0.15, alpha)
|
||||
var log_face := Color(0.62, 0.42, 0.24, alpha)
|
||||
var log_ring := Color(0.42, 0.27, 0.14, alpha)
|
||||
var outline := Color(0.15, 0.10, 0.05, 0.7 * alpha)
|
||||
|
||||
# Two legs at the corners (front face).
|
||||
draw_rect(Rect2(Vector2(-7.0, -10.0), Vector2(2.0, 10.0)), leg)
|
||||
draw_rect(Rect2(Vector2( 5.0, -10.0), Vector2(2.0, 10.0)), leg)
|
||||
# Plank front face — thick band.
|
||||
draw_rect(Rect2(Vector2(-8.0, -12.0), Vector2(16.0, 5.0)), plank_front)
|
||||
# Plank top — slimmer band above the front, suggesting depth.
|
||||
draw_rect(Rect2(Vector2(-8.0, -15.0), Vector2(16.0, 3.0)), plank_top)
|
||||
# Edge highlight between top and front.
|
||||
draw_line(Vector2(-8.0, -12.0), Vector2(8.0, -12.0), plank_edge, 1.0)
|
||||
# Two short log slabs sitting on the left side of the top.
|
||||
draw_rect(Rect2(Vector2(-6.0, -17.0), Vector2(3.0, 2.0)), log_face)
|
||||
draw_rect(Rect2(Vector2(-3.0, -17.0), Vector2(3.0, 2.0)), log_face)
|
||||
draw_line(Vector2(-4.5, -17.0), Vector2(-4.5, -15.0), log_ring, 1.0)
|
||||
# Saw on the right — handle + blade silhouette.
|
||||
draw_rect(Rect2(Vector2(1.0, -16.0), Vector2(6.0, 1.5)), saw_blade)
|
||||
draw_rect(Rect2(Vector2(5.5, -17.0), Vector2(2.0, 2.0)), saw_handle)
|
||||
# Outline.
|
||||
draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 16.0)), outline, false, 1.0)
|
||||
|
||||
|
||||
## Smelter — stone furnace block with a stubby chimney puffing smoke and a
|
||||
## bright ember-glow opening on the front face. Stone-grey base separates it
|
||||
## visually from the Carpenter's warm wood.
|
||||
func _draw_smelter(alpha: float) -> void:
|
||||
var stone_top := Color(0.55, 0.55, 0.55, alpha)
|
||||
var stone_front := Color(0.42, 0.42, 0.43, alpha)
|
||||
var stone_shad := Color(0.30, 0.30, 0.32, alpha)
|
||||
var ember := Color(0.98, 0.55, 0.10, alpha)
|
||||
var ember_core := Color(1.00, 0.85, 0.30, alpha)
|
||||
var chimney := Color(0.32, 0.30, 0.30, alpha)
|
||||
var smoke := Color(0.75, 0.73, 0.70, alpha * 0.7)
|
||||
var outline := Color(0.15, 0.12, 0.10, 0.7 * alpha)
|
||||
|
||||
# Stone front body.
|
||||
draw_rect(Rect2(Vector2(-8.0, -12.0), Vector2(16.0, 12.0)), stone_front)
|
||||
# Top face — slightly lighter band.
|
||||
draw_rect(Rect2(Vector2(-8.0, -15.0), Vector2(16.0, 3.0)), stone_top)
|
||||
# Furnace mouth — dark recess with bright ember inside.
|
||||
draw_rect(Rect2(Vector2(-4.0, -9.0), Vector2(8.0, 5.0)), stone_shad)
|
||||
draw_rect(Rect2(Vector2(-3.0, -8.0), Vector2(6.0, 3.0)), ember)
|
||||
draw_rect(Rect2(Vector2(-2.0, -7.0), Vector2(4.0, 1.0)), ember_core)
|
||||
# Mortar lines across the front for stone-block feel.
|
||||
draw_line(Vector2(-8.0, -8.0), Vector2(-4.0, -8.0), stone_shad, 1.0)
|
||||
draw_line(Vector2( 4.0, -8.0), Vector2( 8.0, -8.0), stone_shad, 1.0)
|
||||
# Chimney + smoke wisps rising above.
|
||||
draw_rect(Rect2(Vector2(2.0, -19.0), Vector2(3.0, 4.0)), chimney)
|
||||
draw_rect(Rect2(Vector2(3.0, -22.0), Vector2(1.0, 3.0)), smoke)
|
||||
draw_rect(Rect2(Vector2(2.0, -24.0), Vector2(1.0, 2.0)), smoke)
|
||||
# Outline.
|
||||
draw_rect(Rect2(Vector2(-8.0, -16.0), Vector2(16.0, 16.0)), outline, false, 1.0)
|
||||
|
||||
|
||||
## Hearth — tall (h=2) stone fireplace with mantle, arched opening, log fire
|
||||
## with embers, and a flame licking up. Draws above y=-16 into the tile north
|
||||
## of the bench (y_sort handles occlusion). Light-emitting via _maybe_build_light.
|
||||
func _draw_hearth(alpha: float) -> void:
|
||||
var stone := Color(0.60, 0.58, 0.55, alpha)
|
||||
var stone_dark := Color(0.42, 0.40, 0.38, alpha)
|
||||
var mantle := Color(0.50, 0.34, 0.20, alpha)
|
||||
var mantle_edge := Color(0.32, 0.20, 0.10, alpha)
|
||||
var opening := Color(0.08, 0.04, 0.02, alpha)
|
||||
var log_wood := Color(0.55, 0.32, 0.15, alpha)
|
||||
var ember := Color(0.98, 0.55, 0.10, alpha)
|
||||
var flame_inner := Color(1.00, 0.85, 0.30, alpha)
|
||||
var flame_outer := Color(0.95, 0.40, 0.05, alpha)
|
||||
var outline := Color(0.15, 0.10, 0.05, 0.7 * alpha)
|
||||
|
||||
# Stone surround — fills the bench tile (y −16..0) and the tile above
|
||||
# (y −32..-16) so the fireplace is a 16×32 silhouette.
|
||||
draw_rect(Rect2(Vector2(-8.0, -32.0), Vector2(16.0, 32.0)), stone)
|
||||
# Stone block mortar — a couple of horizontal seams.
|
||||
draw_line(Vector2(-8.0, -22.0), Vector2(8.0, -22.0), stone_dark, 1.0)
|
||||
draw_line(Vector2(-8.0, -28.0), Vector2(8.0, -28.0), stone_dark, 1.0)
|
||||
draw_line(Vector2(-2.0, -32.0), Vector2(-2.0, -28.0), stone_dark, 1.0)
|
||||
draw_line(Vector2( 3.0, -28.0), Vector2( 3.0, -22.0), stone_dark, 1.0)
|
||||
# Wooden mantle — horizontal beam across the middle.
|
||||
draw_rect(Rect2(Vector2(-8.0, -19.0), Vector2(16.0, 3.0)), mantle)
|
||||
draw_line(Vector2(-8.0, -19.0), Vector2(8.0, -19.0), mantle_edge, 1.0)
|
||||
draw_line(Vector2(-8.0, -16.0), Vector2(8.0, -16.0), mantle_edge, 1.0)
|
||||
# Arched opening — dark recess in the lower stone block.
|
||||
draw_rect(Rect2(Vector2(-6.0, -14.0), Vector2(12.0, 14.0)), opening)
|
||||
# Two stacked logs sitting in the opening.
|
||||
draw_rect(Rect2(Vector2(-5.0, -4.0), Vector2(10.0, 2.0)), log_wood)
|
||||
draw_rect(Rect2(Vector2(-4.0, -6.0), Vector2(8.0, 2.0)), log_wood)
|
||||
# Ember strip glowing under the logs.
|
||||
draw_rect(Rect2(Vector2(-4.0, -2.0), Vector2(8.0, 2.0)), ember)
|
||||
# Flame — tapered teardrop above the logs.
|
||||
draw_rect(Rect2(Vector2(-3.0, -10.0), Vector2(6.0, 4.0)), flame_outer)
|
||||
draw_rect(Rect2(Vector2(-2.0, -12.0), Vector2(4.0, 2.0)), flame_outer)
|
||||
draw_rect(Rect2(Vector2(-1.0, -13.0), Vector2(2.0, 1.0)), flame_outer)
|
||||
draw_rect(Rect2(Vector2(-2.0, -9.0), Vector2(4.0, 2.0)), flame_inner)
|
||||
draw_rect(Rect2(Vector2(-1.0, -11.0), Vector2(2.0, 2.0)), flame_inner)
|
||||
# Outline around the full 16×32 silhouette.
|
||||
draw_rect(Rect2(Vector2(-8.0, -32.0), Vector2(16.0, 32.0)), outline, false, 1.0)
|
||||
|
||||
|
||||
## Millstone — wooden frame supporting a large round grindstone, viewed
|
||||
## 3/4-perspective so the wheel reads as both round (top) and solid (front).
|
||||
func _draw_millstone(alpha: float) -> void:
|
||||
var frame_top := Color(0.55, 0.36, 0.18, alpha)
|
||||
var frame_front := Color(0.42, 0.26, 0.12, alpha)
|
||||
var frame_edge := Color(0.25, 0.14, 0.06, alpha)
|
||||
var wheel := Color(0.55, 0.53, 0.50, alpha)
|
||||
var wheel_dark := Color(0.34, 0.32, 0.30, alpha)
|
||||
var wheel_rim := Color(0.18, 0.16, 0.14, alpha)
|
||||
var groove := Color(0.28, 0.26, 0.24, alpha)
|
||||
var pin := Color(0.20, 0.18, 0.16, alpha)
|
||||
var outline := Color(0.15, 0.10, 0.05, 0.7 * alpha)
|
||||
|
||||
# Wooden frame base — front + top faces.
|
||||
draw_rect(Rect2(Vector2(-8.0, -7.0), Vector2(16.0, 7.0)), frame_front)
|
||||
draw_rect(Rect2(Vector2(-8.0, -10.0), Vector2(16.0, 3.0)), frame_top)
|
||||
draw_line(Vector2(-8.0, -7.0), Vector2(8.0, -7.0), frame_edge, 1.0)
|
||||
# Grindstone — large dark-grey disc, rim slightly darker. Centred over
|
||||
# the top of the frame, sticking up into the tile above only slightly.
|
||||
var c := Vector2(0.0, -12.0)
|
||||
draw_circle(c, 7.0, wheel_rim)
|
||||
draw_circle(c, 6.0, wheel)
|
||||
# Front-face shadow band across the lower half of the disc.
|
||||
draw_rect(Rect2(Vector2(-6.0, -12.0), Vector2(12.0, 5.0)), wheel_dark)
|
||||
# Two radial grooves — pie-slice indicators that the stone spins.
|
||||
draw_line(c, c + Vector2(5.0, -3.5), groove, 1.0)
|
||||
draw_line(c, c + Vector2(-5.0, -3.5), groove, 1.0)
|
||||
# Centre pin / spindle.
|
||||
draw_circle(c, 1.2, pin)
|
||||
# Outline.
|
||||
draw_rect(Rect2(Vector2(-8.0, -19.0), Vector2(16.0, 19.0)), outline, false, 1.0)
|
||||
|
||||
|
||||
func _draw_generic(alpha: float) -> void:
|
||||
|
|
@ -410,11 +486,7 @@ func _draw_generic(alpha: float) -> void:
|
|||
|
||||
func _complete() -> void:
|
||||
_completed = true
|
||||
# Solidify the ghost: sprite child (if any) goes from 40% to full opacity.
|
||||
# Procedural-only variants reread alpha through _draw() via queue_redraw.
|
||||
var sprite: Sprite2D = get_node_or_null("Sprite")
|
||||
if sprite != null:
|
||||
sprite.modulate.a = 1.0
|
||||
# Procedural-only variants re-read alpha through _draw() via queue_redraw.
|
||||
# Phase 11: enable PointLight2D for light-emitting workbenches on completion.
|
||||
if _light != null:
|
||||
_light.enabled = is_on()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue