Chop/mine designation gate + reachability gates on Doctor & Eat
Player reported pawns ignoring chop designations. Root cause: ChopProvider/MineProvider iterated World.trees/World.rocks unconditionally — paint set a null sentinel and never touched the entity, so designation was cosmetic only. Pawns auto-chopped nearest unfelled tree. * Added chop_designated: bool to Tree, mine_designated: bool to Rock and BigRock (footprint-aware: paint on any of the 4 footprint cells flags the boulder). Save/load round-trips the flag. * world.gd._on_designation_added 'chop'/'mine' cases now find the entity at the painted tile and flip the flag. _on_designation_cleared inverts. * Boot seed auto-designates SAMPLE_TREES / SAMPLE_ROCKS / SAMPLE_BIG_ROCKS so the cabin demo still produces wood + stone end-to-end without requiring the player to paint first. Also from the same audit (researcher mapped all 11 WorkProviders): * DoctorProvider + EatProvider now pre-check reachability with pathfinder.find_path before issuing a job, mirroring HaulingProvider's pattern. Previously they handed out doomed walks that JobRunner had to cancel, busy-spinning at 20 Hz. Verified end-to-end via MCP runtime: undesignated tree/rock returns null from provider; paint flips the flag and provider returns a chop/mine job; un-paint clears the flag; BigRock footprint paint works on any of the 4 cells. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
922f269a6c
commit
a4163ba222
9 changed files with 80 additions and 10 deletions
|
|
@ -409,18 +409,24 @@ func _spawn_sample_harvestables() -> void:
|
|||
# Untyped vars — Godot's class-name cache for class_name'd classes is
|
||||
# scan-time and intermittently lags behind file changes. Duck typing is
|
||||
# safer here and the calls below are all spec'd on the entity types.
|
||||
# Boot seed auto-designates so the production-chain demo runs end-to-end
|
||||
# without requiring a player to paint chop/mine first. Real player-painted
|
||||
# trees / rocks still gate on chop_designated / mine_designated (Rimworld parity).
|
||||
for t_tile in SAMPLE_TREES:
|
||||
var tree = TREE_SCENE.instantiate()
|
||||
add_child(tree)
|
||||
tree.setup(t_tile)
|
||||
tree.chop_designated = true
|
||||
for r_tile in SAMPLE_ROCKS:
|
||||
var rock = ROCK_SCENE.instantiate()
|
||||
add_child(rock)
|
||||
rock.setup(r_tile)
|
||||
rock.mine_designated = true
|
||||
for br_origin in SAMPLE_BIG_ROCKS:
|
||||
var big = BIG_ROCK_SCENE.instantiate()
|
||||
add_child(big)
|
||||
big.setup(br_origin)
|
||||
big.mine_designated = true
|
||||
Audit.log("world", "spawned %d trees + %d rocks + %d big rocks" % [
|
||||
SAMPLE_TREES.size(), SAMPLE_ROCKS.size(), SAMPLE_BIG_ROCKS.size()
|
||||
])
|
||||
|
|
@ -674,16 +680,30 @@ func _on_designation_added(cell: Vector2i, tool: StringName) -> void:
|
|||
add_child(gs)
|
||||
gs.setup(cell)
|
||||
entity = gs
|
||||
# Phase 17 — chop / mine: designation ghost only; providers auto-scan
|
||||
# World.trees / World.rocks and will service the nearest entity.
|
||||
# _build_sites_by_tile tracks the ghost so cancel works.
|
||||
&"chop", &"mine":
|
||||
# No entity to spawn — the ghost tile on the designation layer IS the
|
||||
# marker. Register a sentinel (null body) so _build_sites_by_tile can
|
||||
# clear the ghost on cancel without double-spawning.
|
||||
# Chop / mine: flag the Tree / Rock entity at this tile so ChopProvider /
|
||||
# MineProvider treat it as work. Designation ghost on the TileMap is the
|
||||
# visual cue; _build_sites_by_tile holds a sentinel so cancel can clear.
|
||||
&"chop":
|
||||
for t in World.trees:
|
||||
if t.tile == cell:
|
||||
t.chop_designated = true
|
||||
break
|
||||
_build_sites_by_tile[cell] = null
|
||||
Audit.log("world", "designation ghost '%s' at %s (no entity)" % [tool, cell])
|
||||
return # skip the entity/site registration below
|
||||
Audit.log("world", "chop designation at %s" % cell)
|
||||
return
|
||||
&"mine":
|
||||
for r in World.rocks:
|
||||
# BigRock occupies a 2×2 footprint; flag if cell is any of them.
|
||||
if r.has_method("footprint_tiles"):
|
||||
if cell in r.footprint_tiles():
|
||||
r.mine_designated = true
|
||||
break
|
||||
elif r.tile == cell:
|
||||
r.mine_designated = true
|
||||
break
|
||||
_build_sites_by_tile[cell] = null
|
||||
Audit.log("world", "mine designation at %s" % cell)
|
||||
return
|
||||
# Phase 17 — crate.
|
||||
&"build_crate":
|
||||
entity = CRATE_SCENE.instantiate()
|
||||
|
|
@ -743,8 +763,21 @@ func _on_designation_cleared(cell: Vector2i) -> void:
|
|||
return
|
||||
var entity = _build_sites_by_tile[cell]
|
||||
_build_sites_by_tile.erase(cell)
|
||||
# Phase 17 — chop/mine designations store null as their sentinel; nothing to free.
|
||||
# Phase 17 — chop/mine designations store null as their sentinel; nothing to
|
||||
# free, but clear the corresponding flag on any Tree / Rock at that tile.
|
||||
if entity == null:
|
||||
for t in World.trees:
|
||||
if t.tile == cell:
|
||||
t.chop_designated = false
|
||||
break
|
||||
for r in World.rocks:
|
||||
if r.has_method("footprint_tiles"):
|
||||
if cell in r.footprint_tiles():
|
||||
r.mine_designated = false
|
||||
break
|
||||
elif r.tile == cell:
|
||||
r.mine_designated = false
|
||||
break
|
||||
return
|
||||
if not is_instance_valid(entity):
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue