Door designation on a wall now demolishes the wall in place

Adds the Going-Medieval / Rimworld "door replaces wall" convention.
Painting a door on a tile occupied by a Wall (ghost OR completed):

* Reverses Wall._complete: erases the wall_layer stamp, marks the
  pathfinder cell walkable, triggers room recompute.
* queue_free's the wall entity.
* Spawns the door ghost in its place via the normal designation flow.

Source of truth for "is there a wall here?" is World.build_queue, so the
rule covers both designation-painted walls and pre-built seeds (cabin,
test shed) which self-register via Wall._ready but aren't in
_build_sites_by_tile.

Verified via MCP: completed wall + door paint → wall gone, door ghost,
tile walkable, layer unstamped. Ghost wall + door paint → wall replaced
cleanly with no leftover ghosts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-15 15:13:30 +01:00
parent 69a1c0de44
commit fb07a3fa15

View file

@ -642,6 +642,17 @@ var _build_sites_by_tile: Dictionary = {}
func _on_designation_added(cell: Vector2i, tool: StringName) -> void:
# Replacement rule: painting a door on a tile occupied by a Wall (ghost or
# completed) demolishes the wall in place so the door build can proceed.
# Mirrors the Going-Medieval / Rimworld "door replaces wall" convention.
# Source of truth is World.build_queue — pre-built walls (test shed, cabin
# seed) aren't in _build_sites_by_tile but ARE in build_queue via _ready.
if tool == &"build_door":
var wall_at_cell = _find_wall_at(cell)
if wall_at_cell != null:
_demolish_wall_in_place(wall_at_cell, cell)
_build_sites_by_tile.erase(cell) # may not be present; erase is no-op
# Fall through to spawn the door ghost below.
if _build_sites_by_tile.has(cell):
return # already a build site here
# Phase 17 — read material override from the Designation controller (may be "").
@ -747,6 +758,45 @@ func _on_designation_added(cell: Vector2i, tool: StringName) -> void:
Audit.log("world", "queued %s at %s" % [tool, cell])
## True if `entity` is a Wall (ghost or completed). Duck-typed by script path
## to avoid registration-order issues with class_name Wall.
func _entity_is_wall(entity) -> bool:
if entity == null:
return false
var s = entity.get_script()
return s != null and s.resource_path.ends_with("/wall.gd")
## Finds the Wall entity at `cell` (ghost or completed), or null. Searches
## World.build_queue first since every Wall self-registers there on _ready;
## that covers both designation-painted walls and pre-built _spawn_complete_wall
## seeds.
func _find_wall_at(cell: Vector2i):
for site in World.build_queue:
if not is_instance_valid(site):
continue
if site.get("tile") != cell:
continue
if _entity_is_wall(site):
return site
return null
## Atomically remove a Wall entity at `cell`. Reverses Wall._complete() effects
## if the wall was already built: unstamps the wall_layer TileMap, marks the
## pathfinder cell walkable, and triggers a room recompute. Then frees the
## entity. Used by the door-replaces-wall replacement rule.
func _demolish_wall_in_place(wall, cell: Vector2i) -> void:
var was_completed: bool = wall.has_method("is_completed") and wall.is_completed()
if was_completed:
wall_layer.erase_cell(cell)
pathfinder.set_cell_walkable(cell, true)
if room_detector != null:
room_detector.recompute_around(cell)
wall.queue_free()
Audit.log("world", "wall at %s demolished (replaced by door designation)" % cell)
## Instantiate a Workbench ghost (in build-queue state) at `tile` with the
## given label and accepted skill. Returns the entity (already add_child'd).
func _spawn_workbench(tile: Vector2i, label: String, skill: StringName):