Add explicit stairs and measure elevator congestion with real actors

This commit is contained in:
Takeshi
2026-09-09 14:26:38 +09:00
parent e246ce382b
commit fb4ad23190
57 changed files with 3030 additions and 162 deletions
+74 -2
View File
@@ -21,6 +21,8 @@ const MVP_FACILITY_IDS: Array[StringName] = [
&"massage_room",
&"guest_elevator",
&"service_elevator",
&"guest_stairs",
&"service_stairs",
&"staff_door",
&"guest_corridor",
&"service_corridor",
@@ -532,6 +534,7 @@ func _create_side_panel() -> Control:
selected_entity_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
selected_row.add_child(selected_entity_icon)
selected_detail_label = Label.new()
selected_detail_label.mouse_filter = Control.MOUSE_FILTER_PASS
selected_detail_label.text = "地図上の施設をクリックすると、営業状態を確認できます。"
selected_detail_label.custom_minimum_size.y = 112
selected_detail_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@@ -751,11 +754,11 @@ func _set_business_panel_open(open: bool) -> void:
func _on_opening_action(action: StringName) -> void:
match action:
&"build_reception", &"build_room", &"build_corridor":
&"build_reception", &"build_room", &"build_corridor", &"build_stairs":
_set_ledger_panel_open(false)
_set_management_panel_open(false)
_set_build_palette_open(true)
var definitions := {&"build_reception": &"reception", &"build_room": &"standard_room", &"build_corridor": &"guest_corridor"}
var definitions := {&"build_reception": &"reception", &"build_room": &"standard_room", &"build_corridor": &"guest_corridor", &"build_stairs": &"guest_stairs"}
_select_build_definition(definitions[action])
&"staff":
_activate_management_mode(&"staff")
@@ -773,6 +776,7 @@ func _connect_signals() -> void:
GameClock.phase_changed.connect(_on_phase_changed)
GameState.resource_changed.connect(_on_resource_changed)
world_preview.facility_selected.connect(_on_facility_selected)
world_preview.legacy_stair_selected.connect(_on_legacy_stair_selected)
world_preview.construction_selected.connect(_on_construction_selected)
world_preview.cell_clicked.connect(_on_map_cell_clicked)
world_preview.build_preview_changed.connect(_on_build_preview_changed)
@@ -1290,6 +1294,17 @@ func _on_facility_selected(instance_id: StringName) -> void:
_refresh_selected_entity()
func _on_legacy_stair_selected(stair_id: StringName) -> void:
_set_ledger_panel_open(false)
_set_supply_panel_open(false)
_set_management_panel_open(true)
_set_excavation_mode(false, false)
_selected_entity_id = stair_id
_selected_entity_kind = &"legacy_stairs"
world_preview.selected_entity_id = stair_id
_refresh_selected_entity()
func _on_construction_selected(job_id: StringName) -> void:
_set_ledger_panel_open(false)
_set_supply_panel_open(false)
@@ -1520,6 +1535,7 @@ func _operations_dashboard_text() -> String:
func _refresh_selected_entity() -> void:
selected_elevator_controls.visible = false
selected_detail_label.tooltip_text = ""
if _selected_entity_id.is_empty():
_move_selected_detail_first(false)
selected_entity_icon.texture = null
@@ -1537,6 +1553,8 @@ func _refresh_selected_entity() -> void:
_move_selected_detail_first(false)
_refresh_selected_excavation()
return
if _refresh_selected_stairs():
return
var shaft := _selected_elevator_report()
if not shaft.is_empty():
_refresh_selected_elevator(shaft)
@@ -1581,6 +1599,45 @@ func _refresh_selected_entity() -> void:
)
func _refresh_selected_stairs() -> bool:
var from_cell := Vector3i.ZERO
var to_cell := Vector3i.ZERO
var definition_id: StringName = &""
var legacy := _selected_entity_kind == &"legacy_stairs"
var legacy_access: StringName = &""
if legacy:
for stair: Dictionary in world_preview.get_legacy_stair_records():
if StringName(stair[&"id"]) == _selected_entity_id:
from_cell = stair[&"from"]
legacy_access = StringName(stair.get(&"access_id", &""))
to_cell = stair[&"to"]
definition_id = &"guest_stairs" if from_cell.z == 0 else &"service_stairs"
break
else:
var facility := _construction_facility(_selected_entity_id)
definition_id = StringName(facility.get(&"definition_id", &""))
if definition_id not in [&"guest_stairs", &"service_stairs"]:
return false
var origin: Variant = facility.get(&"origin", Vector3i.ZERO)
if origin is Array and origin.size() == 3:
origin = Vector3i(int(origin[0]), int(origin[1]), int(origin[2]))
to_cell = origin
from_cell = to_cell+Vector3i(0,-1,0)
if definition_id.is_empty():
return false
_move_selected_detail_first(true)
var staff_only := legacy_access == &"staff" if legacy else definition_id == &"service_stairs"
selected_heading_label.text = "既設の階段" if legacy else ("業務用階段" if staff_only else "客用階段")
selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null
selected_detail_label.text = (
"接続階 %s ↔ %s\n" % [_elevator_floor_name(mini(from_cell.y,to_cell.y)), _elevator_floor_name(maxi(from_cell.y,to_cell.y))]
+ "利用者 %s\n" % ("従業員専用・物資運搬可" if staff_only else "客・従業員用")
+ "階段で昇り降りします。魔力は不要です。\n"
+ "座標 (%d, %d) %s" % [to_cell.x,maxi(from_cell.y,to_cell.y),"BACK" if to_cell.z == 1 else "FRONT"]
)
return true
func _selected_elevator_report() -> Dictionary:
if _selected_entity_kind != &"facility" or _selected_entity_id.is_empty():
return {}
@@ -1620,12 +1677,27 @@ func _refresh_selected_elevator(shaft: Dictionary) -> void:
+ "停止階 %s\n" % stop_text
+ "魔力 %s  %s" % ["供給中" if powered else "不足", "客・従業員用" if definition_id == &"guest_elevator" else "従業員・物資用"]
)
selected_detail_label.text += _elevator_wait_text(StringName(shaft.get(&"shaft_id", &"")))
selected_elevator_controls.visible = true
elevator_operation_button.disabled = _pending_elevator_commands.values().has(shaft.get(&"shaft_id", &""))
elevator_operation_button.text = "指示を受理済み…" if elevator_operation_button.disabled else ("昇降機を停止" if operator_open else "運行を再開")
elevator_operation_hint.text = "停止すると最寄りの階で降車し、待機中の呼び出しを解除します。" if operator_open else ("再開すると新しい呼び出しを受け付けます。" if powered else "魔力が供給されると運行を再開できます。")
func _elevator_wait_text(shaft_id: StringName) -> String:
selected_detail_label.tooltip_text = "保持している直近256件の出来事のうち、過去60分の乗車を集計。現在の最長待ちは、いま並んでいる人の待ち時間です。"
if _runtime == null or not _runtime.has_method(&"get_elevator_wait_statistics"):
return "\n最近の乗車記録 計測待ち"
var statistics: Dictionary = _runtime.call(&"get_elevator_wait_statistics", shaft_id)
var samples := int(statistics.get(&"sample_count", 0))
var text := "\n最近の乗車記録 %d人" % samples
if samples == 0:
text += "(計測待ち)"
else:
text += "\n平均待ち %.1f分 / 95%% %.1f分" % [float(statistics.get(&"mean_wait_minutes",0)),float(statistics.get(&"p95_wait_minutes",0))]
return text+"\n現在の最長待ち %d分" % int(statistics.get(&"oldest_wait_minutes",0))
func _move_selected_detail_first(first: bool) -> void:
var column := selected_heading_label.get_parent()
var selected_row := selected_detail_label.get_parent()
+56 -1
View File
@@ -3,6 +3,7 @@ extends Control
signal facility_selected(instance_id: StringName)
signal construction_selected(job_id: StringName)
signal legacy_stair_selected(stair_id: StringName)
signal cell_clicked(cell: Vector3i)
signal hovered_cell_changed(cell: Variant)
signal build_preview_changed(report: Dictionary)
@@ -320,6 +321,12 @@ func _gui_input(event: InputEvent) -> void:
cell_clicked.emit(cell)
accept_event()
return
var stair_id := legacy_stair_at_canvas(event.position)
if not stair_id.is_empty():
selected_entity_id = stair_id
legacy_stair_selected.emit(stair_id)
accept_event()
return
var world: Variant = _service(&"world_service")
if world != null and world.has_method(&"get_occupant"):
var occupant_id := StringName(world.call(&"get_occupant", cell))
@@ -353,6 +360,7 @@ func _draw() -> void:
if show_grid:
_draw_congestion_overlay(bounds)
_draw_world_entity_icons()
_draw_legacy_stairs()
_draw_elevators()
_draw_construction_labels()
_draw_facility_labels()
@@ -537,6 +545,53 @@ func _draw_world_entity_icons() -> void:
draw_rect(rect.grow(-p),Color("f5d582"),false,2*p)
func get_legacy_stair_records() -> Array[Dictionary]:
var result: Array[Dictionary] = []
var service: Variant = _service(&"legacy_stairs")
if service == null or not service.has_method(&"create_snapshot"):
return result
var snapshot: Variant = service.call(&"create_snapshot")
if not snapshot is Dictionary:
return result
for row: Variant in snapshot.get(&"stairs", []):
if row is Dictionary and row.get(&"from") is Vector3i and row.get(&"to") is Vector3i:
var record: Dictionary = row.duplicate(true)
var path: Variant = _service(&"path_service")
var connector: Dictionary = path.call(&"get_connector", StringName(row[&"id"])) if path != null else {}
record[&"access_id"] = connector.get(&"access_id", &"")
result.append(record)
return result
func legacy_stair_at_canvas(canvas_position: Vector2) -> StringName:
for stair: Dictionary in get_legacy_stair_records():
var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"]
if upper.z == layer_mode and _legacy_stair_rect(upper).has_point(canvas_position):
return StringName(stair[&"id"])
return &""
func _legacy_stair_rect(upper: Vector3i) -> Rect2:
var rect := get_cell_rect(upper)
# A small target leaves the rest of an existing room available for selection.
return Rect2(rect.position+rect.size*Vector2(0.49,0.24), rect.size*Vector2(0.5,0.7))
func _draw_legacy_stairs() -> void:
for stair: Dictionary in get_legacy_stair_records():
var upper: Vector3i = stair[&"to"] if stair[&"to"].y > stair[&"from"].y else stair[&"from"]
if upper.z != layer_mode:
continue
var rect := _legacy_stair_rect(upper)
if not rect.intersects(Rect2(Vector2.ZERO, size)):
continue
var p := rect.size.x/14.0
draw_rect(rect, Color("3c4e40",0.5))
PixelArt.stairs(self,rect,p,upper.z == LayerMode.BACK,true)
if StringName(stair[&"id"]) == selected_entity_id:
draw_rect(rect,Color("f5d582"),false,maxf(p,1))
func _cell_bounds_rect(cells: Array[Vector3i]) -> Rect2:
if cells.is_empty():
return Rect2()
@@ -605,7 +660,7 @@ func _draw_facility_labels() -> void:
var selected := StringName(facility.get(&"instance_id",&""))==selected_entity_id
if not hovered and not selected and rect.size.x<100:
continue
if id in [&"guest_corridor",&"service_corridor",&"staff_door"] and not hovered and not selected:
if id in [&"guest_corridor",&"service_corridor",&"staff_door",&"guest_stairs",&"service_stairs"] and not hovered and not selected:
continue
var definition: Variant = registry.call(&"get_facility",id) if registry != null else null
var label := String(definition.display_name) if definition != null else String(id)