rimlike/scenes/entities/big_rock_node.gd
megaproxy d98d2c2425 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.
2026-05-16 16:36:16 +01:00

140 lines
5.6 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)