Add BigRock: 2×2 mineable boulder with full mining/path/save support
A new entity for multi-tile rock formations. Same duck-typed contract as
single-tile Rock so MineProvider scans both transparently via World.rocks.
Differences from Rock:
• Occupies a 2×2 footprint anchored at origin_tile (top-left).
• Renders a single 32×32 Sprite2D drawn from the FG_Grasslands_Spring 2×2
cluster sprites at (22, 3) brown and (30, 3) gray.
• Blocks pathfinding on all four footprint tiles — pawns route around it.
• MineProvider asks `rock.approach_tile_for(pawn.tile)` for the walk
destination, so the pawn stands beside the boulder instead of trying to
path into the blocked footprint. Rock returns its own tile (walkable);
BigRock picks the nearest walkable perimeter neighbour.
• Mining takes 480 ticks (4× Rock) and drops 4 stone, one per footprint tile.
All init work happens in setup() rather than _ready(): the calling pattern is
`add_child(big); big.setup(origin)`, and _ready fires inside add_child with
origin_tile still at its zero default — anything reading origin_tile from
_ready would stamp the pathfinder at the wrong tile.
Wired through SaveSystem: factory preload, spawn-priority tier 0 (same as
Rock — static structures spawn before pawns), and a `&"big_rock"` factory.
World seed adds two demo boulders near the small-rock cluster
(65, 58) + (56, 64) so the visual contrast is on-screen from boot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
725d3fb701
commit
938e871bf1
6 changed files with 292 additions and 4 deletions
|
|
@ -28,6 +28,7 @@ const SAVE_VERSION: int = 2
|
||||||
const _PAWN_SCENE: PackedScene = preload("res://scenes/pawn/pawn.tscn")
|
const _PAWN_SCENE: PackedScene = preload("res://scenes/pawn/pawn.tscn")
|
||||||
const _TREE_SCENE: PackedScene = preload("res://scenes/entities/tree.tscn")
|
const _TREE_SCENE: PackedScene = preload("res://scenes/entities/tree.tscn")
|
||||||
const _ROCK_SCENE: PackedScene = preload("res://scenes/entities/rock.tscn")
|
const _ROCK_SCENE: PackedScene = preload("res://scenes/entities/rock.tscn")
|
||||||
|
const _BIG_ROCK_SCENE: PackedScene = preload("res://scenes/entities/big_rock.tscn")
|
||||||
const _ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
|
const _ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
|
||||||
const _WALL_SCENE: PackedScene = preload("res://scenes/entities/wall.tscn")
|
const _WALL_SCENE: PackedScene = preload("res://scenes/entities/wall.tscn")
|
||||||
const _FLOOR_SCENE: PackedScene = preload("res://scenes/entities/floor.tscn")
|
const _FLOOR_SCENE: PackedScene = preload("res://scenes/entities/floor.tscn")
|
||||||
|
|
@ -314,6 +315,7 @@ func _collect_entities() -> Array:
|
||||||
const _SPAWN_PRIORITY: Dictionary = {
|
const _SPAWN_PRIORITY: Dictionary = {
|
||||||
&"tree": 0,
|
&"tree": 0,
|
||||||
&"rock": 0,
|
&"rock": 0,
|
||||||
|
&"big_rock": 0,
|
||||||
&"wall": 0,
|
&"wall": 0,
|
||||||
&"floor": 0,
|
&"floor": 0,
|
||||||
&"door": 1,
|
&"door": 1,
|
||||||
|
|
@ -348,6 +350,7 @@ func _sort_by_spawn_priority(entity_dicts: Array) -> Array:
|
||||||
func _register_factories() -> void:
|
func _register_factories() -> void:
|
||||||
_factories[&"tree"] = _spawn_tree
|
_factories[&"tree"] = _spawn_tree
|
||||||
_factories[&"rock"] = _spawn_rock
|
_factories[&"rock"] = _spawn_rock
|
||||||
|
_factories[&"big_rock"] = _spawn_big_rock
|
||||||
_factories[&"item"] = _spawn_item
|
_factories[&"item"] = _spawn_item
|
||||||
_factories[&"wall"] = _spawn_wall
|
_factories[&"wall"] = _spawn_wall
|
||||||
_factories[&"floor"] = _spawn_floor
|
_factories[&"floor"] = _spawn_floor
|
||||||
|
|
@ -387,6 +390,14 @@ func _spawn_rock(world_scene: Node, d: Dictionary) -> void:
|
||||||
ent.queue_redraw()
|
ent.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func _spawn_big_rock(world_scene: Node, d: Dictionary) -> void:
|
||||||
|
var ent = _BIG_ROCK_SCENE.instantiate()
|
||||||
|
world_scene.add_child(ent)
|
||||||
|
ent.setup(Vector2i(int(d.get("origin_x", 0)), int(d.get("origin_y", 0))))
|
||||||
|
ent.mine_progress = int(d.get("mine_progress", 0))
|
||||||
|
ent.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _spawn_item(world_scene: Node, d: Dictionary) -> void:
|
func _spawn_item(world_scene: Node, d: Dictionary) -> void:
|
||||||
var ent = _ITEM_SCENE.instantiate()
|
var ent = _ITEM_SCENE.instantiate()
|
||||||
world_scene.add_child(ent)
|
world_scene.add_child(ent)
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ class_name MineProvider extends WorkProvider
|
||||||
## pathfinder).
|
## pathfinder).
|
||||||
##
|
##
|
||||||
## Duck-typing note: Rock is referenced without class_name (class may not be
|
## Duck-typing note: Rock is referenced without class_name (class may not be
|
||||||
## registered yet when this provider loads). We rely only on:
|
## registered yet when this provider loads). World.rocks may also hold BigRock
|
||||||
## rock.tile: Vector2i
|
## instances; both expose the same interface so we just iterate the array:
|
||||||
|
## rock.tile: Vector2i — for distance scoring
|
||||||
## rock.is_mineable() -> bool
|
## rock.is_mineable() -> bool
|
||||||
|
## rock.approach_tile_for(pawn_tile) -> Vector2i — walk destination
|
||||||
## rock.get_path() -> NodePath
|
## rock.get_path() -> NodePath
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,6 +50,12 @@ func find_best_for(pawn) -> Job:
|
||||||
var j := Job.new()
|
var j := Job.new()
|
||||||
j.label = "Mine rock at %s" % best.tile
|
j.label = "Mine rock at %s" % best.tile
|
||||||
j.target_node = best
|
j.target_node = best
|
||||||
j.toils.append(Toil.walk_to(best.tile))
|
# Ask the entity where the pawn should stand. Single rocks return their own
|
||||||
|
# tile (walkable). BigRocks return a perimeter tile so the pawn doesn't try
|
||||||
|
# to pathfind into the blocked 2×2 footprint.
|
||||||
|
var walk_tile: Vector2i = (
|
||||||
|
best.approach_tile_for(pawn.tile) if best.has_method("approach_tile_for") else best.tile
|
||||||
|
)
|
||||||
|
j.toils.append(Toil.walk_to(walk_tile))
|
||||||
j.toils.append(Toil.interact(best.get_path(), &"on_mine_tick"))
|
j.toils.append(Toil.interact(best.get_path(), &"on_mine_tick"))
|
||||||
return j
|
return j
|
||||||
|
|
|
||||||
240
scenes/entities/big_rock.gd
Normal file
240
scenes/entities/big_rock.gd
Normal file
|
|
@ -0,0 +1,240 @@
|
||||||
|
## BigRock — 2×2 boulder formation. Same mining contract as Rock but occupies
|
||||||
|
## four tiles, drops four stone, and blocks pathfinding on its footprint so
|
||||||
|
## pawns route around it.
|
||||||
|
##
|
||||||
|
## Duck-typed to Rock's public interface (tile, is_mineable, on_mine_tick,
|
||||||
|
## approach_tile_for) so MineProvider can scan World.rocks without checking
|
||||||
|
## the entity's concrete type.
|
||||||
|
##
|
||||||
|
## Position semantics (different from single Rock):
|
||||||
|
## • origin_tile is the TOP-LEFT of the 2×2 footprint (player-friendly anchor).
|
||||||
|
## • The Sprite2D is 32×32 and is centred at the intersection of the four
|
||||||
|
## tiles, so its visual centre sits at (origin_tile + (1, 1)) * 16 px.
|
||||||
|
## • For MineProvider distance, `tile` aliases to origin_tile (cheap, stable).
|
||||||
|
## • For the pawn's walk destination, approach_tile_for(pawn_tile) returns
|
||||||
|
## the closest walkable perimeter neighbour, so the pawn stands BESIDE the
|
||||||
|
## boulder instead of trying to path onto a blocked tile.
|
||||||
|
|
||||||
|
class_name BigRock extends Node2D
|
||||||
|
|
||||||
|
const TILE_SIZE_PX: int = 16
|
||||||
|
|
||||||
|
## Sim ticks to mine a big rock. Roughly 4× a small rock (120 ticks) since the
|
||||||
|
## footprint represents four tiles' worth of stone.
|
||||||
|
const MINE_TICKS: int = 480
|
||||||
|
## Stone Items dropped on a successful mine (one per footprint tile).
|
||||||
|
const STONE_DROPS_ON_MINE: int = 4
|
||||||
|
|
||||||
|
## Footprint dimensions. Locked at 2×2 for the first BigRock pass; if larger
|
||||||
|
## boulders ever ship, generalise here and the perimeter math in approach_tile_for.
|
||||||
|
const FOOTPRINT_W: int = 2
|
||||||
|
const FOOTPRINT_H: int = 2
|
||||||
|
|
||||||
|
# Preloaded scene for spawned stone items.
|
||||||
|
const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
|
||||||
|
|
||||||
|
## ElvGames Grasslands tileset — 2×2 cluster sprites starting at these
|
||||||
|
## top-left coords. Visually confirmed against /tmp/rocks_labeled_grid.png
|
||||||
|
## in the 2026-05-12 visual pass.
|
||||||
|
const _ROCK_TEX: Texture2D = preload("res://art/tiles/FG_Grasslands_Spring.png")
|
||||||
|
const _BIG_ROCK_ATLAS_COORDS: Array[Vector2i] = [
|
||||||
|
Vector2i(22, 3), # brown 2×2 boulder
|
||||||
|
Vector2i(30, 3), # gray 2×2 boulder
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ── state ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
## Top-left tile of the 2×2 footprint.
|
||||||
|
var origin_tile: Vector2i = Vector2i.ZERO
|
||||||
|
|
||||||
|
## 0..MINE_TICKS. Advanced by on_mine_tick(); rock is mined when equal to MINE_TICKS.
|
||||||
|
var mine_progress: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── lifecycle ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
## All init work happens inside setup() (not _ready) because callers
|
||||||
|
## always do `add_child(node); node.setup(origin)` — _ready fires inside
|
||||||
|
## add_child with origin_tile still at its zero default, so anything that
|
||||||
|
## reads origin_tile from _ready would stamp the pathfinder / position at
|
||||||
|
## the wrong tile. setup() is the single authoritative entry point.
|
||||||
|
func _ready() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
World.unregister_rock(self)
|
||||||
|
_set_footprint_walkable(true)
|
||||||
|
|
||||||
|
|
||||||
|
# ── public API ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
## One-shot initialiser. Builds the sprite, positions the node, marks the four
|
||||||
|
## footprint tiles unwalkable, and registers with World.rocks. Call after
|
||||||
|
## add_child().
|
||||||
|
func setup(p_origin: Vector2i) -> void:
|
||||||
|
origin_tile = p_origin
|
||||||
|
mine_progress = 0
|
||||||
|
position = Vector2(
|
||||||
|
(origin_tile.x + 1) * TILE_SIZE_PX,
|
||||||
|
(origin_tile.y + 1) * TILE_SIZE_PX,
|
||||||
|
)
|
||||||
|
_build_sprite()
|
||||||
|
_set_footprint_walkable(false)
|
||||||
|
World.register_rock(self)
|
||||||
|
queue_redraw()
|
||||||
|
Audit.log("big_rock", "spawned 2×2 at %s" % origin_tile)
|
||||||
|
|
||||||
|
|
||||||
|
## Alias used by MineProvider's distance calc (`rock.tile`). Returns the
|
||||||
|
## top-left so the manhattan distance is stable; refining to the centre would
|
||||||
|
## require fractional tiles.
|
||||||
|
var tile: Vector2i:
|
||||||
|
get:
|
||||||
|
return origin_tile
|
||||||
|
|
||||||
|
|
||||||
|
## True when the boulder hasn't been fully mined yet.
|
||||||
|
func is_mineable() -> bool:
|
||||||
|
return mine_progress < MINE_TICKS
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the perimeter tile closest to `pawn_tile` that the pawn can stand
|
||||||
|
## on while mining. Mirrors Rock.approach_tile_for so MineProvider can ask the
|
||||||
|
## entity for its walk destination without knowing which kind of rock it is.
|
||||||
|
## Falls back to the origin tile when no perimeter is walkable — the pathfinder
|
||||||
|
## will then return an empty path and the job runner cancels cleanly.
|
||||||
|
func approach_tile_for(pawn_tile: Vector2i) -> Vector2i:
|
||||||
|
var perimeter := _perimeter_tiles()
|
||||||
|
var best: Vector2i = origin_tile
|
||||||
|
var best_d: int = 0x7fffffff
|
||||||
|
var found_any: bool = false
|
||||||
|
for t in perimeter:
|
||||||
|
if World.pathfinder == null:
|
||||||
|
continue
|
||||||
|
if not World.pathfinder.is_walkable(t):
|
||||||
|
continue
|
||||||
|
var d: int = abs(t.x - pawn_tile.x) + abs(t.y - pawn_tile.y)
|
||||||
|
if d < best_d:
|
||||||
|
best_d = d
|
||||||
|
best = t
|
||||||
|
found_any = true
|
||||||
|
if not found_any:
|
||||||
|
return origin_tile
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
## Called by the INTERACT toil in JobRunner once per sim tick while the pawn
|
||||||
|
## works this boulder. Advances mine_progress and triggers mined() when done.
|
||||||
|
func on_mine_tick() -> void:
|
||||||
|
if not is_mineable():
|
||||||
|
return
|
||||||
|
mine_progress += 1
|
||||||
|
queue_redraw()
|
||||||
|
if mine_progress >= MINE_TICKS:
|
||||||
|
mined()
|
||||||
|
|
||||||
|
|
||||||
|
## Drop four stone Items (one per footprint tile) and free this node. Called
|
||||||
|
## by on_mine_tick() automatically; can also be called for scripted removal.
|
||||||
|
func mined() -> void:
|
||||||
|
for ft in footprint_tiles():
|
||||||
|
var item: Item = ITEM_SCENE.instantiate()
|
||||||
|
get_parent().add_child(item)
|
||||||
|
item.setup(Item.TYPE_STONE, 1, ft)
|
||||||
|
Audit.log("big_rock", "mined 2×2 at %s; %d stone drops" % [origin_tile, STONE_DROPS_ON_MINE])
|
||||||
|
queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
## The four tiles this boulder occupies (origin + the three south/east neighbours).
|
||||||
|
func footprint_tiles() -> Array[Vector2i]:
|
||||||
|
return [
|
||||||
|
origin_tile,
|
||||||
|
origin_tile + Vector2i(1, 0),
|
||||||
|
origin_tile + Vector2i(0, 1),
|
||||||
|
origin_tile + Vector2i(1, 1),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ── save / load ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func to_dict() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"class_id": &"big_rock",
|
||||||
|
"origin_x": origin_tile.x,
|
||||||
|
"origin_y": origin_tile.y,
|
||||||
|
"mine_progress": mine_progress,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static func from_dict(d: Dictionary) -> Dictionary:
|
||||||
|
return {
|
||||||
|
"origin_x": int(d.get("origin_x", 0)),
|
||||||
|
"origin_y": int(d.get("origin_y", 0)),
|
||||||
|
"mine_progress": int(d.get("mine_progress", 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── render ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
# Sprite child draws the boulder body. _draw renders only the mine-progress
|
||||||
|
# crack overlay so the player sees mining damage.
|
||||||
|
if mine_progress > 0:
|
||||||
|
var ratio := float(mine_progress) / float(MINE_TICKS)
|
||||||
|
var crack_len := ratio * 12.0
|
||||||
|
draw_line(
|
||||||
|
Vector2(-4.0, -2.0),
|
||||||
|
Vector2(-4.0 + crack_len, 4.0),
|
||||||
|
Color(0.15, 0.12, 0.10, 0.85),
|
||||||
|
1.5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── internal ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
## Build a single 32×32 Sprite2D from a 2×2 region of FG_Grasslands_Spring.
|
||||||
|
## Variant chosen deterministically from origin_tile so the same boulder renders
|
||||||
|
## the same colour across boots and load/save.
|
||||||
|
func _build_sprite() -> void:
|
||||||
|
var sprite := Sprite2D.new()
|
||||||
|
sprite.name = "Sprite"
|
||||||
|
sprite.texture = _ROCK_TEX
|
||||||
|
sprite.region_enabled = true
|
||||||
|
var coord_idx: int = (origin_tile.x * 31 + origin_tile.y * 17) % _BIG_ROCK_ATLAS_COORDS.size()
|
||||||
|
var coord: Vector2i = _BIG_ROCK_ATLAS_COORDS[coord_idx]
|
||||||
|
sprite.region_rect = Rect2(
|
||||||
|
coord.x * TILE_SIZE_PX,
|
||||||
|
coord.y * TILE_SIZE_PX,
|
||||||
|
TILE_SIZE_PX * FOOTPRINT_W,
|
||||||
|
TILE_SIZE_PX * FOOTPRINT_H,
|
||||||
|
)
|
||||||
|
sprite.centered = true
|
||||||
|
sprite.offset = Vector2.ZERO
|
||||||
|
add_child(sprite)
|
||||||
|
|
||||||
|
|
||||||
|
## Mark or unmark the four footprint tiles in the pathfinder. Called from
|
||||||
|
## _ready / _exit_tree so the boulder appears as an obstacle while it exists.
|
||||||
|
func _set_footprint_walkable(walkable: bool) -> void:
|
||||||
|
if World.pathfinder == null:
|
||||||
|
return
|
||||||
|
for ft in footprint_tiles():
|
||||||
|
World.pathfinder.set_cell_walkable(ft, walkable)
|
||||||
|
|
||||||
|
|
||||||
|
## The eight perimeter tiles around the 2×2 footprint (cardinal + diagonals
|
||||||
|
## of the bounding rect). Used by approach_tile_for to find a stand-and-mine
|
||||||
|
## tile near the pawn.
|
||||||
|
func _perimeter_tiles() -> Array[Vector2i]:
|
||||||
|
var out: Array[Vector2i] = []
|
||||||
|
# Top + bottom rows.
|
||||||
|
for dx in range(-1, FOOTPRINT_W + 1):
|
||||||
|
out.append(origin_tile + Vector2i(dx, -1))
|
||||||
|
out.append(origin_tile + Vector2i(dx, FOOTPRINT_H))
|
||||||
|
# Left + right columns (skip the corners — already in the top/bottom rows).
|
||||||
|
for dy in range(0, FOOTPRINT_H):
|
||||||
|
out.append(origin_tile + Vector2i(-1, dy))
|
||||||
|
out.append(origin_tile + Vector2i(FOOTPRINT_W, dy))
|
||||||
|
return out
|
||||||
6
scenes/entities/big_rock.tscn
Normal file
6
scenes/entities/big_rock.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://big_rock_entity"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/entities/big_rock.gd" id="1_big_rock"]
|
||||||
|
|
||||||
|
[node name="BigRock" type="Node2D"]
|
||||||
|
script = ExtResource("1_big_rock")
|
||||||
|
|
@ -91,6 +91,15 @@ func is_mineable() -> bool:
|
||||||
return mine_progress < MINE_TICKS
|
return mine_progress < MINE_TICKS
|
||||||
|
|
||||||
|
|
||||||
|
## Walk destination for a pawn approaching this rock. Small rocks are walkable
|
||||||
|
## (Phase 4 simplification), so the pawn stands on the rock tile while mining.
|
||||||
|
## BigRock overrides this to return a perimeter neighbour because its footprint
|
||||||
|
## is blocked. MineProvider always asks the entity for this rather than reading
|
||||||
|
## `.tile` directly, so single Rock and BigRock plug into the same walk toil.
|
||||||
|
func approach_tile_for(_pawn_tile: Vector2i) -> Vector2i:
|
||||||
|
return tile
|
||||||
|
|
||||||
|
|
||||||
## Called by the INTERACT toil in JobRunner once per sim tick while the pawn
|
## Called by the INTERACT toil in JobRunner once per sim tick while the pawn
|
||||||
## works this rock. Advances mine_progress and triggers mined() when complete.
|
## works this rock. Advances mine_progress and triggers mined() when complete.
|
||||||
func on_mine_tick() -> void:
|
func on_mine_tick() -> void:
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const EVENT_CATALOG_SCRIPT: Script = preload("res://scenes/storyteller/event_cat
|
||||||
const PAWN_SCENE: PackedScene = preload("res://scenes/pawn/pawn.tscn")
|
const PAWN_SCENE: PackedScene = preload("res://scenes/pawn/pawn.tscn")
|
||||||
const TREE_SCENE: PackedScene = preload("res://scenes/entities/tree.tscn")
|
const TREE_SCENE: PackedScene = preload("res://scenes/entities/tree.tscn")
|
||||||
const ROCK_SCENE: PackedScene = preload("res://scenes/entities/rock.tscn")
|
const ROCK_SCENE: PackedScene = preload("res://scenes/entities/rock.tscn")
|
||||||
|
const BIG_ROCK_SCENE: PackedScene = preload("res://scenes/entities/big_rock.tscn")
|
||||||
const STOCKPILE_SCENE: PackedScene = preload("res://scenes/world/stockpile_zone.tscn")
|
const STOCKPILE_SCENE: PackedScene = preload("res://scenes/world/stockpile_zone.tscn")
|
||||||
const WALL_SCENE: PackedScene = preload("res://scenes/entities/wall.tscn")
|
const WALL_SCENE: PackedScene = preload("res://scenes/entities/wall.tscn")
|
||||||
const FLOOR_SCENE: PackedScene = preload("res://scenes/entities/floor.tscn")
|
const FLOOR_SCENE: PackedScene = preload("res://scenes/entities/floor.tscn")
|
||||||
|
|
@ -66,6 +67,13 @@ const SAMPLE_ROCKS: Array[Vector2i] = [
|
||||||
Vector2i(60, 60), Vector2i(62, 60), Vector2i(63, 62), Vector2i(58, 62),
|
Vector2i(60, 60), Vector2i(62, 60), Vector2i(63, 62), Vector2i(58, 62),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 2×2 boulder formations (top-left anchor tiles). Two near the small-rock
|
||||||
|
# cluster so the player sees the size contrast on first scroll.
|
||||||
|
const SAMPLE_BIG_ROCKS: Array[Vector2i] = [
|
||||||
|
Vector2i(65, 58), # brown/gray (deterministic per-tile)
|
||||||
|
Vector2i(56, 64),
|
||||||
|
]
|
||||||
|
|
||||||
# HaulingProvider re-flow cadence — every 5 sim seconds at 1× (100 ticks).
|
# HaulingProvider re-flow cadence — every 5 sim seconds at 1× (100 ticks).
|
||||||
const HAUL_SWEEP_INTERVAL_TICKS: int = 100
|
const HAUL_SWEEP_INTERVAL_TICKS: int = 100
|
||||||
|
|
||||||
|
|
@ -409,7 +417,13 @@ func _spawn_sample_harvestables() -> void:
|
||||||
var rock = ROCK_SCENE.instantiate()
|
var rock = ROCK_SCENE.instantiate()
|
||||||
add_child(rock)
|
add_child(rock)
|
||||||
rock.setup(r_tile)
|
rock.setup(r_tile)
|
||||||
Audit.log("world", "spawned %d trees + %d rocks" % [SAMPLE_TREES.size(), SAMPLE_ROCKS.size()])
|
for br_origin in SAMPLE_BIG_ROCKS:
|
||||||
|
var big = BIG_ROCK_SCENE.instantiate()
|
||||||
|
add_child(big)
|
||||||
|
big.setup(br_origin)
|
||||||
|
Audit.log("world", "spawned %d trees + %d rocks + %d big rocks" % [
|
||||||
|
SAMPLE_TREES.size(), SAMPLE_ROCKS.size(), SAMPLE_BIG_ROCKS.size()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
func _seed_phase5_demo_buildings() -> void:
|
func _seed_phase5_demo_buildings() -> void:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue