Renewable resources: tree growth + WildGrowth + Quarry on BigRockNode
Trees: 4 growth stages (Sapling→Young→Growing→Mature), only Mature yields wood. WildGrowth ticker fires every in-game hour; rejection- samples grass tiles and plants a sapling with ~30% probability (capped at MAP_TREE_LIMIT=60). New `paint_plant_tree` designation lets the player manually plant — ghost sapling registered as a build_site that ConstructionProvider fulfils. Stage round-trips through save/load. Initial seed mixes 4 saplings + 6 mature so growth is visible day 1. Quarry: new BigRockNode entity (2×2 permanent stone outcrop, never depletes). 3 nodes seeded far from cabin. New QuarryWorkbench (extends Workbench, auto-FOREVER `quarry_stone` bill, recipe drops 1 stone per 300 work-ticks). New `paint_quarry` designation only accepts BigRockNode tiles. CraftingProvider now supports recipes with `ingredient_count == 0` — skips ingredient-fetch and goes straight to walk+craft toils. Recipe gains `ingredient_count` field (defaults 0). Save/load layering: big_rock_node spawns at priority 0 (same as rock/tree), quarry_workbench at priority 2 (after the node). UI: Plant tree + Build quarry buttons added to Build drawer. build_drawer_thumb gains `plant_tree` (sapling sprout in dirt) and `paint_quarry` (stone block + chisel + cut-stone pile) shapes. inspect_tooltip recognises BigRockNode + shows tree growth stage on hover. Delegation: gdscript-refactor (Sonnet ×2) for trees full impl + quarry skeleton; quick-edit (Haiku) for CraftingProvider no-ingredient plumbing + TopBar polish; integration handled on Opus.
This commit is contained in:
parent
296894ff7a
commit
d98d2c2425
20 changed files with 716 additions and 38 deletions
140
scenes/entities/big_rock_node.gd
Normal file
140
scenes/entities/big_rock_node.gd
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
class_name BigRockNode extends Node2D
|
||||
## Permanent stone outcrop — a 2×2 immovable boulder that never depletes.
|
||||
##
|
||||
## BigRockNode marks its footprint impassable in the pathfinder and registers
|
||||
## itself with World so the designation system can locate it. A QuarryWorkbench
|
||||
## can be built on one of its tiles; external code sets is_quarry_site = true
|
||||
## and quarry_workbench once construction completes.
|
||||
##
|
||||
## Draw convention: position is stamped at the TOP-LEFT pixel corner of the
|
||||
## 2×2 footprint (tile * TILE_SIZE_PX). The visual centre of the 32×32 area
|
||||
## sits at local (16, 16), so all _draw_* helpers are centred there.
|
||||
##
|
||||
## Save/load: to_dict / from_dict round-trip tile + is_quarry_site.
|
||||
## The quarry_workbench reference is an entity pointer reconstructed by the
|
||||
## save layer (SaveSystem wires it after both entities are spawned).
|
||||
|
||||
const TILE_SIZE_PX: int = 16
|
||||
|
||||
## Top-left tile of the 2×2 footprint.
|
||||
var tile: Vector2i = Vector2i.ZERO
|
||||
|
||||
## Footprint size in tiles (always 2×2; declared as a var so external code can
|
||||
## read it uniformly without special-casing BigRockNode vs hypothetical future nodes).
|
||||
var footprint: Vector2i = Vector2i(2, 2)
|
||||
|
||||
## True once a Quarry workbench has been completed on this outcrop.
|
||||
## Flipped by external code (designation / world.gd) — this file only declares it.
|
||||
var is_quarry_site: bool = false
|
||||
|
||||
## The QuarryWorkbench that sits on this node after construction.
|
||||
## null until assigned by the designation completion handler.
|
||||
var quarry_workbench = null
|
||||
|
||||
|
||||
# ── lifecycle ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func _ready() -> void:
|
||||
# Position at top-left pixel corner so footprint_tiles() aligns with world coords.
|
||||
position = Vector2(tile.x * TILE_SIZE_PX, tile.y * TILE_SIZE_PX)
|
||||
# Block pathfinding on every tile in the footprint.
|
||||
for t in footprint_tiles():
|
||||
if World.pathfinder != null:
|
||||
World.pathfinder.set_cell_walkable(t, false)
|
||||
World.register_big_rock_node(self)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
World.unregister_big_rock_node(self)
|
||||
|
||||
|
||||
# ── public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
## One-shot initialiser. Call after add_child() so _ready() has fired.
|
||||
func setup(p_tile: Vector2i) -> void:
|
||||
tile = p_tile
|
||||
position = Vector2(tile.x * TILE_SIZE_PX, tile.y * TILE_SIZE_PX)
|
||||
queue_redraw()
|
||||
Audit.log("big_rock_node", "spawned at tile %s" % tile)
|
||||
|
||||
|
||||
## Returns the four tiles covered by this node's 2×2 footprint.
|
||||
## Used by designation, save/load, and inspection code.
|
||||
func footprint_tiles() -> Array[Vector2i]:
|
||||
return [
|
||||
tile,
|
||||
tile + Vector2i(1, 0),
|
||||
tile + Vector2i(0, 1),
|
||||
tile + Vector2i(1, 1),
|
||||
]
|
||||
|
||||
|
||||
## True when tile_to_check falls inside the 2×2 footprint.
|
||||
func is_at(tile_to_check: Vector2i) -> bool:
|
||||
return (
|
||||
tile_to_check.x >= tile.x and tile_to_check.x < tile.x + footprint.x
|
||||
and tile_to_check.y >= tile.y and tile_to_check.y < tile.y + footprint.y
|
||||
)
|
||||
|
||||
|
||||
# ── save / load ───────────────────────────────────────────────────────────────
|
||||
|
||||
func to_dict() -> Dictionary:
|
||||
return {
|
||||
"class_id": &"big_rock_node",
|
||||
"tile_x": tile.x,
|
||||
"tile_y": tile.y,
|
||||
"is_quarry_site": is_quarry_site,
|
||||
}
|
||||
|
||||
|
||||
## Restore from a dict produced by to_dict(). quarry_workbench is reconnected
|
||||
## by the save layer after both entities are spawned.
|
||||
static func from_dict(d: Dictionary) -> Dictionary:
|
||||
return {
|
||||
"tile_x": int(d.get("tile_x", 0)),
|
||||
"tile_y": int(d.get("tile_y", 0)),
|
||||
"is_quarry_site": bool(d.get("is_quarry_site", false)),
|
||||
}
|
||||
|
||||
|
||||
# ── render ────────────────────────────────────────────────────────────────────
|
||||
|
||||
## Draw a procedural pile of grey rocks centred at local (16, 16) —
|
||||
## the geometric centre of the 32×32 footprint area.
|
||||
## Three layers create depth: large base blob → medium mid rock → small cap.
|
||||
func _draw() -> void:
|
||||
var cx: float = 16.0
|
||||
var cy: float = 16.0
|
||||
|
||||
# Colour palette — warm grey tones suggesting weathered granite.
|
||||
var base_fill := Color(0.60, 0.58, 0.55) # large base ellipse
|
||||
var mid_fill := Color(0.45, 0.44, 0.42) # medium middle rock
|
||||
var top_fill := Color(0.55, 0.53, 0.50) # small perched cap
|
||||
var outline_col := Color(0.20, 0.18, 0.16, 0.70) # subtle dark rim
|
||||
|
||||
# Bottom: large flattened blob — widest at the base to read as a ground-hugging mass.
|
||||
var base_w: float = 24.0
|
||||
var base_h: float = 16.0
|
||||
var base_rect := Rect2(Vector2(cx - base_w / 2.0, cy - base_h / 2.0 + 2.0), Vector2(base_w, base_h))
|
||||
draw_rect(base_rect, base_fill)
|
||||
|
||||
# Dark outline arc around the base blob.
|
||||
draw_rect(base_rect, outline_col, false, 1.0)
|
||||
|
||||
# Middle: slightly elevated, narrower rock sitting on top of the base.
|
||||
var mid_w: float = 16.0
|
||||
var mid_h: float = 12.0
|
||||
var mid_oy: float = -4.0 # shift up from centre
|
||||
var mid_rect := Rect2(Vector2(cx - mid_w / 2.0, cy - mid_h / 2.0 + mid_oy), Vector2(mid_w, mid_h))
|
||||
draw_rect(mid_rect, mid_fill)
|
||||
draw_rect(mid_rect, outline_col, false, 1.0)
|
||||
|
||||
# Top: small cap perched on the very top.
|
||||
var top_w: float = 8.0
|
||||
var top_h: float = 6.0
|
||||
var top_oy: float = -10.0 # above the mid rock
|
||||
var top_rect := Rect2(Vector2(cx - top_w / 2.0, cy - top_h / 2.0 + top_oy), Vector2(top_w, top_h))
|
||||
draw_rect(top_rect, top_fill)
|
||||
draw_rect(top_rect, outline_col, false, 1.0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue