Connect physical elevators and staff commutes to construction, saves, and cutaway UI

This commit is contained in:
Takeshi
2026-09-09 12:58:40 +09:00
parent 4d72de8973
commit 0f12c748e9
46 changed files with 3267 additions and 165 deletions
+140
View File
@@ -35,6 +35,7 @@ const FACILITY_STATE_NAMES := [
"低下運転",
"営業中",
]
const ELEVATOR_PHASE_NAMES := {&"IDLE": "待機", &"PICKUP": "迎車中", &"BOARDING": "乗車中", &"TRAVEL": "運行中", &"ALIGHTING": "降車中"}
const FACILITY_REASON_NAMES := {
&"BROKEN": "故障",
&"NO_ACCESS": "経路なし",
@@ -125,6 +126,9 @@ var staff_candidate_buttons: Dictionary = {}
var selected_heading_label: Label
var selected_detail_label: Label
var selected_entity_icon: TextureRect
var selected_elevator_controls: VBoxContainer
var elevator_operation_button: Button
var elevator_operation_hint: Label
var build_status_label: Label
var build_buttons: Dictionary = {}
var navigation_buttons: Dictionary = {}
@@ -152,6 +156,8 @@ var _build_rotation := 0
var _build_sequence := 1
var _excavation_sequence := 1
var _hire_sequence := 1
var _elevator_sequence := 1
var _pending_elevator_commands: Dictionary = {}
var _pending_build_commands: Dictionary = {}
var _excavation_mode := false
var _pending_excavation_commands: Dictionary = {}
@@ -533,6 +539,18 @@ func _create_side_panel() -> Control:
selected_detail_label.add_theme_color_override("font_color", Color("4d341f"))
selected_detail_label.add_theme_font_size_override("font_size", 13)
selected_row.add_child(selected_detail_label)
selected_elevator_controls = VBoxContainer.new()
selected_elevator_controls.name = "SelectedElevatorControls"
selected_elevator_controls.visible = false
column.add_child(selected_elevator_controls)
elevator_operation_button = _button("昇降機を停止", _on_elevator_operation_pressed, 160)
elevator_operation_button.name = "ElevatorOperationButton"
selected_elevator_controls.add_child(elevator_operation_button)
elevator_operation_hint = Label.new()
elevator_operation_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
elevator_operation_hint.add_theme_font_size_override("font_size", 12)
elevator_operation_hint.add_theme_color_override("font_color", Color("654d37"))
selected_elevator_controls.add_child(elevator_operation_hint)
operating_ledger_panel = OperatingLedgerPanelScript.new()
root_column.add_child(operating_ledger_panel)
@@ -1138,6 +1156,8 @@ func _on_map_cell_clicked(cell: Vector3i) -> void:
_selected_entity_id = &""
_selected_entity_kind = &""
world_preview.selected_entity_id = &""
selected_elevator_controls.visible = false
selected_entity_icon.texture = null
selected_heading_label.text = "選択中のマス"
selected_detail_label.text = "座標 (%d, %d)\nレイヤー %s\n\n施設のないマスです。" % [cell.x, cell.y, "FRONT / 表層" if cell.z == 0 else "BACK / 裏層"]
return
@@ -1499,7 +1519,9 @@ func _operations_dashboard_text() -> String:
func _refresh_selected_entity() -> void:
selected_elevator_controls.visible = false
if _selected_entity_id.is_empty():
_move_selected_detail_first(false)
selected_entity_icon.texture = null
selected_heading_label.text = "掘削モード" if _excavation_mode else "選択中の施設"
selected_detail_label.text = (
@@ -1508,11 +1530,18 @@ func _refresh_selected_entity() -> void:
)
return
if _selected_entity_kind == &"construction":
_move_selected_detail_first(false)
_refresh_selected_construction()
return
if _selected_entity_kind == &"excavation":
_move_selected_detail_first(false)
_refresh_selected_excavation()
return
var shaft := _selected_elevator_report()
if not shaft.is_empty():
_refresh_selected_elevator(shaft)
return
_move_selected_detail_first(false)
var facility_service: Variant = _service(&"facility_service")
if facility_service == null or not facility_service.has_method(&"get_instance"):
selected_detail_label.text = "施設サービスを利用できません。"
@@ -1552,6 +1581,106 @@ func _refresh_selected_entity() -> void:
)
func _selected_elevator_report() -> Dictionary:
if _selected_entity_kind != &"facility" or _selected_entity_id.is_empty():
return {}
var transport: Variant = _service(&"elevators")
if transport == null or not transport.has_method(&"get_report"):
return {}
for shaft: Dictionary in transport.call(&"get_report").get(&"shafts", []):
if (shaft.get(&"module_ids", []) as Array).has(_selected_entity_id):
return shaft
return {}
func _refresh_selected_elevator(shaft: Dictionary) -> void:
_move_selected_detail_first(true)
var construction_facility := _construction_facility(_selected_entity_id)
var definition_id := StringName(construction_facility.get(&"definition_id", &"guest_elevator"))
var definition: Variant = _facility_definition(definition_id)
selected_heading_label.text = String(_definition_value(definition, &"display_name", "昇降機"))
selected_entity_icon.texture = visual_theme.facility_icon(definition_id) if visual_theme != null else null
var powered := bool(shaft.get(&"powered", true))
var operator_open := bool(shaft.get(&"operator_open", true))
var state_text := String(ELEVATOR_PHASE_NAMES.get(StringName(shaft.get(&"phase", &"IDLE")), "待機"))
if not operator_open:
state_text = "手動停止"
elif not powered:
state_text = "魔力供給なし"
var floor_index := int(shaft.get(&"current_floor", 0))
var target_index := int(shaft.get(&"boarding_floor", floor_index) if StringName(shaft.get(&"phase", &"IDLE")) == &"PICKUP" else shaft.get(&"target_floor", floor_index))
var stops: Array = shaft.get(&"stops", [])
var stop_text := "—"
if not stops.is_empty():
stop_text = "%s 〜 %s" % [_elevator_floor_name(stops.front().y), _elevator_floor_name(stops.back().y)]
selected_detail_label.text = (
"状態 %s\n" % state_text
+ "現在階 %s  目的階 %s\n" % [_elevator_floor_name(floor_index), _elevator_floor_name(target_index)]
+ "乗客 %d / %d人  待機 %d人\n" % [int(shaft.get(&"passenger_count", 0)), int(shaft.get(&"capacity", 0)), int(shaft.get(&"waiting_count", 0))]
+ "停止階 %s\n" % stop_text
+ "魔力 %s  %s" % ["供給中" if powered else "不足", "客・従業員用" if definition_id == &"guest_elevator" else "従業員・物資用"]
)
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 _move_selected_detail_first(first: bool) -> void:
var column := selected_heading_label.get_parent()
var selected_row := selected_detail_label.get_parent()
# The transport action fits without scrolling; other management views keep
# their existing staff-first layout.
var desired_index := 0 if first else column.get_child_count()-3
if selected_heading_label.get_index() == desired_index:
return
column.move_child(selected_heading_label, 0 if first else -1)
column.move_child(selected_row, 1 if first else -1)
column.move_child(selected_elevator_controls, 2 if first else -1)
func _elevator_floor_name(floor_index: int) -> String:
return "%dF" % (floor_index+1) if floor_index >= 0 else "B%d" % -floor_index
func _on_elevator_operation_pressed() -> void:
var shaft := _selected_elevator_report()
if shaft.is_empty() or _runtime == null:
return
var shaft_id := StringName(shaft[&"shaft_id"])
if _pending_elevator_commands.values().has(shaft_id):
return
var key := StringName("ui.elevator.%08d" % _elevator_sequence)
_elevator_sequence += 1
var open_value := not bool(shaft.get(&"operator_open", true))
var result: Variant = _runtime.call(&"submit_command", &"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": open_value}, &"player", &"", 50, -1, key)
if result == null or not result.is_success():
_set_status("昇降機の指示を反映できませんでした。", false)
return
if not result.details.has(&"shaft_id"):
_pending_elevator_commands[int(result.command_id)] = shaft_id
_set_status("昇降機の運行指示を受理しました。", true)
else:
var message := "昇降機を停止しました。"
if open_value:
message = "昇降機の運行を再開しました。" if bool(shaft.get(&"powered", true)) else "運行を許可しました。魔力の供給待ちです。"
_set_status(message, true)
_refresh_selected_entity()
func _synchronize_elevator_pending() -> void:
var queue: Variant = _service(&"command_queue")
if queue == null:
return
if queue.pending_count() == 0:
_pending_elevator_commands.clear()
return
_pending_elevator_commands.clear()
for command: Dictionary in queue.create_snapshot().get("pending", []):
if StringName(command.get("command_type", &"")) == &"set_elevator_operation":
_pending_elevator_commands[int(command.get("command_id", 0))] = StringName((command.get("payload", {}) as Dictionary).get(&"shaft_id", &""))
func _refresh_selected_construction() -> void:
var construction: Variant = _service(&"construction_service")
if construction == null or not construction.has_method(&"get_job"):
@@ -1764,6 +1893,7 @@ func _load_manual() -> void:
_pending_build_commands.clear()
_pending_excavation_commands.clear()
_pending_hire_commands.clear()
_pending_elevator_commands.clear()
_synchronize_ui_command_state()
if staff_management_panel != null:
staff_management_panel.refresh()
@@ -1820,6 +1950,8 @@ func _synchronize_ui_command_state() -> void:
if command_id <= 0:
continue
match command_type:
&"set_elevator_operation":
_pending_elevator_commands[command_id] = StringName(payload.get(&"shaft_id", &""))
&"build_facility":
var definition_id := StringName(payload.get(&"definition_id", payload.get("definition_id", &"")))
if not definition_id.is_empty():
@@ -1850,9 +1982,12 @@ func _advance_ui_sequence_from_key(key: String) -> void:
_excavation_sequence = maxi(_excavation_sequence, next_sequence)
elif key.begins_with("ui.hire."):
_hire_sequence = maxi(_hire_sequence, next_sequence)
elif key.begins_with("ui.elevator."):
_elevator_sequence = maxi(_elevator_sequence, next_sequence)
func _on_snapshot_published(_snapshot: Dictionary, _digest: String) -> void:
_synchronize_elevator_pending()
_refresh_dashboard()
_update_pause_and_speed()
@@ -1860,6 +1995,11 @@ func _on_snapshot_published(_snapshot: Dictionary, _digest: String) -> void:
func _on_command_completed(result: Variant) -> void:
if result == null:
return
if _pending_elevator_commands.has(int(result.command_id)):
_pending_elevator_commands.erase(int(result.command_id))
_set_status("昇降機の運行設定を反映しました。" if result.is_success() else "昇降機の運行設定を反映できませんでした。", result.is_success())
_refresh_selected_entity()
return
if _pending_hire_commands.has(int(result.command_id)):
var candidate_id := StringName(_pending_hire_commands[int(result.command_id)])
_pending_hire_commands.erase(int(result.command_id))