Save The Tree implementation and acceptance work in progress
Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
@@ -82,6 +82,9 @@ var _drag_button := MOUSE_BUTTON_NONE
|
||||
|
||||
var _runtime: Variant = null
|
||||
var _hovered_cell: Variant = null
|
||||
var _preview_topology_key: Array = []
|
||||
var _connection_cache_key: Array = []
|
||||
var _connection_cache_report: Dictionary = {}
|
||||
var _last_preview_report: Dictionary = INVALID_PREVIEW.duplicate(true)
|
||||
var _last_excavation_preview_report: Dictionary = INVALID_EXCAVATION_PREVIEW.duplicate(true)
|
||||
|
||||
@@ -112,6 +115,8 @@ func _ready() -> void:
|
||||
|
||||
|
||||
func set_runtime(value: Variant) -> void:
|
||||
_connection_cache_key.clear()
|
||||
_connection_cache_report.clear()
|
||||
_disconnect_runtime()
|
||||
_runtime = value
|
||||
_actor_tracks.clear()
|
||||
@@ -201,7 +206,11 @@ func get_build_preview_report(cell: Variant = null) -> Dictionary:
|
||||
build_rotation
|
||||
)
|
||||
if report != null and report.has_method(&"to_dictionary"):
|
||||
return report.call(&"to_dictionary") as Dictionary
|
||||
var result: Dictionary = report.call(&"to_dictionary")
|
||||
result[&"connection"] = _build_connection_report(
|
||||
_cells_from_serialized(result.get(&"cells", [])),
|
||||
Vector3i(target.x, target.y, layer_mode))
|
||||
return result
|
||||
return {
|
||||
&"can_build": _fallback_cells_are_available(Vector3i(target.x, target.y, layer_mode)),
|
||||
&"error_codes": [],
|
||||
@@ -338,6 +347,9 @@ func _process(delta: float) -> void:
|
||||
lens_material.set_shader_parameter("extent", size)
|
||||
lens_material.set_shader_parameter("progress", _layer_transition_elapsed / LAYER_TRANSITION_SECONDS)
|
||||
_layer_lens.visible = _layer_transition_elapsed < LAYER_TRANSITION_SECONDS
|
||||
if build_definition != null and _hovered_cell is Vector3i and _current_preview_topology() != _preview_topology_key:
|
||||
_update_build_preview()
|
||||
queue_redraw()
|
||||
var clock := get_node_or_null("/root/GameClock")
|
||||
if clock != null and bool(clock.get("paused")):
|
||||
return
|
||||
@@ -421,7 +433,13 @@ func _gui_input(event: InputEvent) -> void:
|
||||
accept_event()
|
||||
return
|
||||
if event.pressed and event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]:
|
||||
set_zoom(zoom_level + (0.5 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -0.5), event.position)
|
||||
if pan_mode and not event.ctrl_pressed:
|
||||
var direction := 1.0 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -1.0
|
||||
pan_offset.y += direction * _cell_dimensions().y * maxf(event.factor, 0.0)
|
||||
_clamp_pan()
|
||||
queue_redraw()
|
||||
else:
|
||||
set_zoom(zoom_level + (0.5 if event.button_index == MOUSE_BUTTON_WHEEL_UP else -0.5), event.position)
|
||||
accept_event()
|
||||
return
|
||||
if event is InputEventMouseMotion and _dragging:
|
||||
@@ -657,6 +675,16 @@ func _draw_congestion_overlay(bounds: Dictionary) -> void:
|
||||
draw_line(Vector2(rect.end.x, rect.position.y), Vector2(rect.position.x, rect.end.y), color, 2.0)
|
||||
|
||||
|
||||
## Visual-only level comes from the existing live FacilityInstance, not the
|
||||
## construction footprint dictionary. No upgrade or simulation value is written.
|
||||
func _facility_visual_level(instance_id: StringName) -> int:
|
||||
var service: Variant = _service(&"facility_service")
|
||||
if service == null or not service.has_method(&"get_instance"):
|
||||
return 1
|
||||
var instance: Variant = service.get_instance(instance_id)
|
||||
return int(instance.level) if instance != null else 1
|
||||
|
||||
|
||||
func _completed_facilities_for_draw() -> Array:
|
||||
if _drawing and _draw_cache.has(&"completed_facilities"):
|
||||
return _draw_cache[&"completed_facilities"]
|
||||
@@ -685,7 +713,8 @@ func _other_layer_rooms() -> Array[Dictionary]:
|
||||
if cells.is_empty(): continue
|
||||
var rect := _cell_bounds_rect(cells)
|
||||
if rect.intersects(Rect2(Vector2.ZERO, size)):
|
||||
rooms.append({&"id": facility[&"instance_id"], &"rect": rect, &"definition_id": facility[&"definition_id"], &"origin_x": cells[0].x})
|
||||
rooms.append({&"id": facility[&"instance_id"], &"rect": rect, &"definition_id": facility[&"definition_id"], &"origin_x": cells[0].x,
|
||||
&"level": _facility_visual_level(StringName(facility[&"instance_id"]))})
|
||||
for job: ConstructionJob in _construction_jobs_for_draw():
|
||||
if job.layer == layer_mode or job.state in [ConstructionJob.State.COMPLETED, ConstructionJob.State.CANCELLED]: continue
|
||||
if job.reserved_cells.is_empty(): continue
|
||||
@@ -748,7 +777,7 @@ func _draw_world_entity_icons() -> void:
|
||||
if not rect.intersects(Rect2(Vector2.ZERO,size)):
|
||||
continue
|
||||
var id := StringName(facility.get(&"definition_id",&""))
|
||||
PixelArt.room(self,rect,id,p,dungeon,town,layer_mode==LayerMode.BACK,cells[0].x, _room_presentations.get(StringName(facility.get(&"instance_id", &"")), {}))
|
||||
PixelArt.room(self,rect,id,p,dungeon,town,layer_mode==LayerMode.BACK,cells[0].x, _room_presentations.get(StringName(facility.get(&"instance_id", &"")), {}), _facility_visual_level(StringName(facility.get(&"instance_id", &""))))
|
||||
var instance_id := StringName(facility.get(&"instance_id",&""))
|
||||
# Thin status lamp conveys operational state without repainting a room.
|
||||
draw_rect(Rect2(rect.end.x-4*p,rect.position.y+2*p,2*p,2*p),_facility_color(instance_id))
|
||||
@@ -1255,7 +1284,7 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re
|
||||
if staff and state == StaffRuntime.State.INCAPACITATED:
|
||||
mode = &"collapsed"
|
||||
elif receiving and activity in [&"standard_room", &"premium_room"]:
|
||||
mode = &"sleep"
|
||||
mode = &"standard_room_sleep" if activity == &"standard_room" else &"sleep"
|
||||
elif moving:
|
||||
mode = &"carry_walk" if carrying else &"walk"
|
||||
elif carrying:
|
||||
@@ -1322,6 +1351,7 @@ func _draw_build_preview() -> void:
|
||||
draw_rect(rect, fill)
|
||||
draw_rect(rect, edge, false, 2.0)
|
||||
|
||||
_draw_build_connection(_last_preview_report.get(&"connection", {}))
|
||||
|
||||
func _draw_excavation_preview() -> void:
|
||||
if not excavation_mode or not _hovered_cell is Vector3i:
|
||||
@@ -1434,6 +1464,7 @@ func _set_hovered_cell(value: Variant) -> void:
|
||||
|
||||
|
||||
func _update_build_preview() -> void:
|
||||
_preview_topology_key = _current_preview_topology()
|
||||
_last_preview_report = get_build_preview_report()
|
||||
build_preview_changed.emit(_last_preview_report.duplicate(true))
|
||||
|
||||
@@ -1451,6 +1482,8 @@ func _on_mouse_exited() -> void:
|
||||
|
||||
|
||||
func _on_snapshot_published(_snapshot: Dictionary = {}, _digest: String = "") -> void:
|
||||
_connection_cache_key.clear()
|
||||
_connection_cache_report.clear()
|
||||
queue_redraw()
|
||||
_update_build_preview()
|
||||
_update_excavation_preview()
|
||||
@@ -1567,3 +1600,88 @@ func _can_drop_data(_position: Vector2, data: Variant) -> bool:
|
||||
func _drop_data(position: Vector2, data: Variant) -> void:
|
||||
if _can_drop_data(position, data):
|
||||
staff_assignment_dropped.emit(data, facility_at_canvas(position))
|
||||
|
||||
|
||||
func _current_preview_topology() -> Array:
|
||||
var world: Variant = _service(&"world_service")
|
||||
var path: Variant = _service(&"path_service")
|
||||
return [world.revision if world != null else -1,
|
||||
path.graph_revision if path != null else -1,
|
||||
path.traffic_revision if path != null else -1]
|
||||
|
||||
|
||||
## Read-only connection preview. Shares ordinary facility access candidates and
|
||||
## accessible graph edges, but does not issue live path queries or change routing.
|
||||
## The displayed path proves connectivity; it is not a cheapest-route promise.
|
||||
func _build_connection_report(cells: Array[Vector3i], origin: Vector3i) -> Dictionary:
|
||||
var path: Variant = _service(&"path_service")
|
||||
if path == null or _runtime == null or cells.is_empty():
|
||||
return {&"status": &"UNAVAILABLE", &"reachable": false, &"path": []}
|
||||
var entrance: Variant = _runtime.call(&"_operations_entrance_cell")
|
||||
var key: Array = [origin, cells, _current_preview_topology(), entrance]
|
||||
if key == _connection_cache_key:
|
||||
return _connection_cache_report.duplicate(true)
|
||||
var result := {&"status": &"UNAVAILABLE", &"reachable": false,
|
||||
&"entrance": entrance, &"origin": origin, &"path": [], &"access_points": []}
|
||||
if entrance is Vector3i:
|
||||
var future := FacilityInstance.new()
|
||||
future.origin = origin
|
||||
future.occupied_cells.assign(cells)
|
||||
var candidates: Array = _runtime.call(&"_facility_access_candidates", future)
|
||||
result[&"access_points"] = candidates
|
||||
var targets: Dictionary = {}
|
||||
for cell: Vector3i in candidates: targets[cell] = true
|
||||
var queue: Array[Vector3i] = [entrance]
|
||||
var parents: Dictionary = {}
|
||||
parents[entrance] = entrance
|
||||
var cursor := 0
|
||||
var target: Variant = null
|
||||
while cursor < queue.size():
|
||||
var current := queue[cursor]
|
||||
cursor += 1
|
||||
if targets.has(current):
|
||||
target = current
|
||||
break
|
||||
for edge: Dictionary in path.get_neighbors(current):
|
||||
var next: Vector3i = edge[&"to"]
|
||||
if parents.has(next) or path.get_edge_report(current, next).is_empty():
|
||||
continue
|
||||
parents[next] = current
|
||||
queue.append(next)
|
||||
var route: Array[Vector3i] = []
|
||||
if target is Vector3i:
|
||||
var current: Vector3i = target
|
||||
while current != entrance:
|
||||
route.push_front(current)
|
||||
current = parents[current]
|
||||
route.push_front(entrance)
|
||||
result[&"reachable"] = not route.is_empty()
|
||||
result[&"status"] = &"CONNECTED" if result[&"reachable"] else &"DISCONNECTED"
|
||||
result[&"path"] = route
|
||||
_connection_cache_key = key.duplicate(true)
|
||||
_connection_cache_report = result.duplicate(true)
|
||||
return result
|
||||
|
||||
|
||||
func _draw_build_connection(report: Dictionary) -> void:
|
||||
if report.is_empty() or not _hovered_cell is Vector3i: return
|
||||
var status: StringName = report.get(&"status", &"UNAVAILABLE")
|
||||
var color := Color("8ff0ab") if status == &"CONNECTED" else Color("ffd080")
|
||||
var target := cell_to_canvas(_hovered_cell)
|
||||
var route: Array = report.get(&"path", [])
|
||||
if status == &"CONNECTED":
|
||||
for i in range(1, route.size()):
|
||||
var from: Vector3i = route[i-1]
|
||||
var to: Vector3i = route[i]
|
||||
if from.z == layer_mode and to.z == layer_mode:
|
||||
draw_line(cell_to_canvas(from), cell_to_canvas(to), color, 2.0)
|
||||
if not route.is_empty() and (route.back() as Vector3i).z == layer_mode:
|
||||
draw_line(cell_to_canvas(route.back()), target, color, 2.0)
|
||||
elif report.get(&"entrance") is Vector3i:
|
||||
# Dashed straight guide is a warning, never a traversable-path claim.
|
||||
draw_dashed_line(cell_to_canvas(report[&"entrance"]), target, color, 2.0, 8.0)
|
||||
draw_line(target-Vector2(6,6),target+Vector2(6,6),color,2.0)
|
||||
draw_line(target+Vector2(-6,6),target+Vector2(6,-6),color,2.0)
|
||||
var message := "入口への経路接続済" if status == &"CONNECTED" else "入口への経路未接続" if status == &"DISCONNECTED" else "入口・経路を確認できません"
|
||||
draw_string(ThemeDB.fallback_font, target+Vector2(8,-12), message,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, color)
|
||||
|
||||
Reference in New Issue
Block a user