P24-02: 操作と通知の全受入

This commit is contained in:
Agent Control Runner
2026-10-13 12:53:16 +09:00
parent 1a5abc6516
commit 666c87bd7d
17 changed files with 1952 additions and 12 deletions
+115
View File
@@ -22,6 +22,8 @@ const SupplyManagementPanelScript := preload("res://scripts/ui/supply_management
const BusinessManagementPanelScript := preload("res://scripts/ui/business_management_panel.gd")
const DefenseManagementPanelScript := preload("res://scripts/ui/defense_management_panel.gd")
const WorldPreviewScript := preload("res://scenes/main/world_preview.gd")
const OperationsCameraControlsScript := preload("res://scripts/ui/operations_camera_controls.gd")
const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd")
const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd")
const DefaultVisualTheme := preload("res://assets/themes/japanese_fantasy/visual-theme-v1.tres")
var _last_presented_close_minute := 0
@@ -145,6 +147,8 @@ var alert_icon_node: TextureRect
var resource_labels: Dictionary = {}
var resource_icon_nodes: Dictionary = {}
var world_preview: WorldPreview
var operations_camera_controls: OperationsCameraControls
var important_warning_cutin: OperationFeedback.ImportantWarningCutinDialog
var front_button: Button
var back_button: Button
var excavation_button: Button
@@ -206,6 +210,7 @@ var _pending_excavation_commands: Dictionary = {}
var _pending_hire_commands: Dictionary = {}
var _staff_candidate_panel_open := false
var _operations_start_report: Dictionary = {}
var _subscribed_event_stream: Variant = null
func _ready() -> void:
@@ -244,6 +249,7 @@ func _ready() -> void:
func _exit_tree() -> void:
_unbind_domain_event_stream()
var tree := get_tree()
if tree != null:
if tree.root.close_requested.is_connected(_on_window_close_requested):
@@ -402,6 +408,18 @@ func _build_interface() -> void:
management_panel.offset_bottom = -8
management_panel.visible = false
operations_camera_controls = OperationsCameraControlsScript.new()
operations_camera_controls.name = "OperationsCameraControls"
operations_camera_controls.configure(world_preview, self)
world_preview.add_child(operations_camera_controls)
operations_camera_controls.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_RIGHT)
operations_camera_controls.offset_right = -12
operations_camera_controls.offset_bottom = -12
important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new()
important_warning_cutin.visibility_changed.connect(_update_modal_input_guard)
add_child(important_warning_cutin)
layout.add_child(_create_bottom_bar())
@@ -441,6 +459,7 @@ func _create_top_bar() -> Control:
_set_status(message,success)
_refresh_dashboard()
)
rank_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(rank_dialog)
rank_button.pressed.connect(rank_dialog.show_rank)
@@ -756,6 +775,7 @@ func _create_bottom_bar() -> Control:
row.add_child(management_toggle_button)
event_log_dialog = EventLogDialogScript.new()
event_log_dialog.configure(_runtime)
event_log_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(event_log_dialog)
var event_log_button := _button("記録", event_log_dialog.show_events, 62)
event_log_button.name = "EventLogButton"
@@ -947,6 +967,9 @@ func _connect_signals() -> void:
world_preview.staff_assignment_dropped.connect(func(data: Dictionary, facility_id: StringName) -> void:
staff_management_panel.assign_from_drop(data, facility_id)
)
world_preview.staff_assignment_clicked.connect(func(staff_id: StringName, facility_id: StringName) -> void:
assign_staff_by_click(facility_id, staff_id)
)
world_preview.legacy_stair_selected.connect(_on_legacy_stair_selected)
world_preview.legacy_stair_selected.connect(_on_audio_world_selection)
world_preview.construction_selected.connect(_on_construction_selected)
@@ -965,6 +988,7 @@ func _connect_signals() -> void:
_runtime.connect(&"save_operation_completed", Callable(self, "_on_save_operation_completed"))
if _runtime.has_signal(&"command_completed"):
_runtime.connect(&"command_completed", Callable(self, "_on_command_completed"))
_bind_domain_event_stream()
func _populate_build_palette() -> void:
@@ -2276,6 +2300,7 @@ func _ensure_manual_save_slots() -> Variant:
manual_save_slots_dialog = ManualSaveSlotsDialogScript.new()
manual_save_slots_dialog.connect(&"new_slot_requested", _save_new_manual_slot)
manual_save_slots_dialog.connect(&"load_slot_requested", _load_selected_manual_slot)
manual_save_slots_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(manual_save_slots_dialog)
return manual_save_slots_dialog
@@ -2352,6 +2377,7 @@ func _apply_loaded_interface(result: Dictionary) -> void:
_restore_product_interface_selection(result.get(&"manifest", {}))
_refresh_dashboard()
_update_pause_and_speed()
_bind_domain_event_stream()
func _valid_interface_selection_version(value: Variant) -> bool:
@@ -2937,6 +2963,7 @@ func _ensure_guest_observer() -> GuestObservationPanel:
content.size_flags_horizontal = Control.SIZE_EXPAND_FILL
guest_observer.add_child(scroll)
scroll.add_child(content)
guest_observer.visibility_changed.connect(_update_modal_input_guard)
add_child(guest_observer)
return guest_observer.find_child("GuestObservationPanel", true, false) as GuestObservationPanel
@@ -2964,6 +2991,7 @@ func _preview_dimension_migration() -> void:
dimension_migration_dialog.cancel_button_text="戻る"
dimension_migration_dialog.confirmed.connect(_confirm_dimension_migration)
dimension_migration_dialog.canceled.connect(func(): _dimension_migration_digest="")
dimension_migration_dialog.visibility_changed.connect(_update_modal_input_guard)
add_child(dimension_migration_dialog)
var lines: PackedStringArray=[]
for change: Dictionary in report[&"changes"]:
@@ -3083,3 +3111,90 @@ func _submit_ui_command(
if _audio_feedback != null:
return _audio_feedback.submit_command(command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key)
return _runtime.call(&"submit_command", command_type, payload, actor_id, target_id, priority, execute_at, idempotency_key)
func assign_staff_by_click(facility_id: StringName, staff_id: StringName = &"") -> void:
if staff_management_panel == null:
return
var target_staff := staff_id
if target_staff.is_empty():
target_staff = StringName(staff_management_panel.get("selected_staff_id"))
if target_staff.is_empty():
_set_status("配属する従業員を一覧から選択してください。", false)
return
var queue_id: int = int(staff_management_panel.get("_queue_instance_id"))
var template_val: Variant = staff_management_panel.get("_draft_template_id")
var drop_data := {
&"kind": &"staff_assignment",
&"staff_id": target_staff,
&"template_id": template_val if template_val != null else &"DAY",
&"queue_instance_id": queue_id,
}
staff_management_panel.assign_from_drop(drop_data, facility_id)
func _update_modal_input_guard() -> void:
if world_preview == null:
return
var any_visible := false
for dialog: Variant in [manual_save_slots_dialog, event_log_dialog, rank_dialog, dimension_migration_dialog, guest_observer, defense_manager, important_warning_cutin]:
if dialog != null and is_instance_valid(dialog) and dialog is Window and bool(dialog.visible):
any_visible = true
break
world_preview.modal_input_blocked = any_visible
func _ensure_important_warning_cutin() -> OperationFeedback.ImportantWarningCutinDialog:
if important_warning_cutin == null:
important_warning_cutin = OperationFeedbackScript.ImportantWarningCutinDialog.new()
important_warning_cutin.visibility_changed.connect(_update_modal_input_guard)
add_child(important_warning_cutin)
return important_warning_cutin
func show_warning_cutin(kind: StringName, heading: String, message: String) -> void:
_ensure_important_warning_cutin().show_cutin(kind, heading, message)
func _bind_domain_event_stream() -> void:
_unbind_domain_event_stream()
if _runtime == null or not is_instance_valid(_runtime):
return
var stream: Variant = _runtime.get(&"domain_events") if _runtime is Object and "domain_events" in _runtime else null
if stream != null and is_instance_valid(stream) and stream.has_method(&"subscribe"):
stream.subscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene", Callable(self, "_on_domain_event_published"))
_subscribed_event_stream = stream
func _unbind_domain_event_stream() -> void:
if _subscribed_event_stream != null and is_instance_valid(_subscribed_event_stream):
if _subscribed_event_stream.has_method(&"unsubscribe"):
_subscribed_event_stream.unsubscribe(DomainEventStream.WILDCARD_EVENT_TYPE, &"main_scene")
_subscribed_event_stream = null
func _on_domain_event_published(event: Variant) -> void:
var event_type: StringName = &""
var payload: Dictionary = {}
if event is Dictionary:
event_type = StringName(event.get(&"event_type", event.get("event_type", &"")))
var raw_payload: Variant = event.get(&"payload", event.get("payload", {}))
if raw_payload is Dictionary:
payload = raw_payload
elif event is Object:
if "event_type" in event:
event_type = StringName(event.get(&"event_type"))
if "payload" in event:
var raw_payload: Variant = event.get(&"payload")
if raw_payload is Dictionary:
payload = raw_payload
match event_type:
&"DEFENSE_RAID_CREATED":
show_warning_cutin(OperationFeedbackScript.CUTIN_DEFENSE, "防衛戦開始演出", "敵の襲撃が発生しました!防衛部隊を配置してください。")
&"RELATIONSHIP_SHIELD_CHANGED":
# Existing love-shield vanishing transition
var old_val: int = int(payload.get(&"old_value", payload.get("old_value", -1)))
var new_val: int = int(payload.get(&"new_value", payload.get("new_value", -1)))
if old_val > 0 and new_val == 0:
show_warning_cutin(OperationFeedbackScript.CUTIN_INVASION, "重要警告:愛の盾消失", "愛の盾が消失しました。人物関係を確認してください。")
+70 -1
View File
@@ -3,6 +3,7 @@ extends Control
signal facility_selected(instance_id: StringName)
signal staff_assignment_dropped(data: Dictionary, facility_id: StringName)
signal staff_assignment_clicked(staff_id: StringName, facility_id: StringName)
signal construction_selected(job_id: StringName)
signal legacy_stair_selected(stair_id: StringName)
signal cell_clicked(cell: Vector3i)
@@ -15,6 +16,7 @@ enum LayerMode {
BACK,
}
const OperationFeedbackScript := preload("res://scripts/ui/operation_feedback.gd")
const RoomPresentation := preload("res://scripts/ui/room_presentation.gd")
const People := preload("res://scripts/ui/pixel_people.gd")
const PixelArt := preload("res://scripts/ui/pixel_tower_art.gd")
@@ -57,6 +59,11 @@ var zoom_level := 1.0
var pan_offset := Vector2.ZERO
var show_grid := false
var pan_mode := false
var modal_input_blocked := false:
set(value):
modal_input_blocked = value
if modal_input_blocked:
_end_pan_drag()
var _drawing := false
var _draw_cache: Dictionary = {}
@@ -420,6 +427,9 @@ func facility_at_canvas(canvas_position: Vector2) -> StringName:
func _gui_input(event: InputEvent) -> void:
if modal_input_blocked:
accept_event()
return
if event is InputEventMouseButton:
if not event.pressed and event.button_index == _drag_button:
_end_pan_drag()
@@ -1145,6 +1155,10 @@ func _draw_actors() -> void:
else:
position += _actor_offset(guest.current_cell,counts)
_draw_pixel_actor(position,key,false,int(guest.state),receiving,false,_guest_activity(guest))
var rel_guest: Variant = _guest_relationship(guest.guest_id)
var guest_bubble := OperationFeedbackScript.resolve_guest_bubble(guest, rel_guest)
if guest_bubble != OperationFeedbackScript.BUBBLE_NONE:
_draw_unit_bubble(position, guest_bubble)
var staff_service: Variant = _service(&"staff_service")
if staff_service != null and staff_service.has_method(&"get_staff_members"):
for staff: Variant in staff_service.call(&"get_staff_members"):
@@ -1171,7 +1185,12 @@ func _draw_actors() -> void:
position = _rescue_treatment_position(staff.staff_id, position)
if position.is_finite(): _draw_pixel_actor(position,key,true,int(staff.state),false,false)
continue
_draw_pixel_actor(_queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts))),key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
var staff_draw_pos: Vector2 = _queue_positions.get(key, _staff_work_position(staff, position+_actor_offset(staff.current_cell,counts)))
_draw_pixel_actor(staff_draw_pos,key,true,int(staff.state),false,not String(staff.carried_inventory_id).is_empty(),_staff_work_animation(staff))
var rel_staff: Variant = _staff_relationship(staff.staff_id)
var staff_bubble := OperationFeedbackScript.resolve_staff_bubble(staff, rel_staff)
if staff_bubble != OperationFeedbackScript.BUBBLE_NONE:
_draw_unit_bubble(staff_draw_pos, staff_bubble)
for key: Variant in _actor_tracks.keys():
if not alive.has(key):
_actor_tracks.erase(key)
@@ -1300,6 +1319,54 @@ func _draw_pixel_actor(center: Vector2, key: String, staff: bool, state: int, re
People.draw(self, at, p, mode, frame, variant, staff, float(track.get(&"direction", 1.0)) < 0.0)
func _draw_unit_bubble(actor_center: Vector2, bubble_type: StringName) -> void:
var world_pixel := _cell_dimensions().x / 16.0
var p := maxf(floorf(world_pixel * 0.75), 1.0) if world_pixel >= 2.0 else world_pixel * 0.75
var bubble_pos := actor_center + Vector2(-9 * p, _cell_dimensions().y * 0.5 - 3 * world_pixel - 32 * p)
var bubble_size := Vector2(18 * p, 13 * p)
var rect := Rect2(bubble_pos.round(), bubble_size.round())
draw_rect(rect, Color("ffffff", 0.94))
draw_rect(rect, Color("303c40", 0.85), false, 1.0)
var tri_p1 := Vector2(rect.get_center().x - 2.5 * p, rect.end.y)
var tri_p2 := Vector2(rect.get_center().x + 2.5 * p, rect.end.y)
var tri_p3 := Vector2(rect.get_center().x, rect.end.y + 3.5 * p)
draw_colored_polygon(PackedVector2Array([tri_p1, tri_p2, tri_p3]), Color("ffffff", 0.94))
draw_line(tri_p1, tri_p3, Color("303c40", 0.85), 1.0)
draw_line(tri_p2, tri_p3, Color("303c40", 0.85), 1.0)
var font := ThemeDB.fallback_font
var font_size := clampi(int(9 * p), 9, 14)
match bubble_type:
OperationFeedbackScript.BUBBLE_ZZZ:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "Zzz", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("2d68c4"))
OperationFeedbackScript.BUBBLE_ANGER:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "💢", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("d63031"))
OperationFeedbackScript.BUBBLE_HEART:
draw_string(font, rect.position + Vector2(2 * p, rect.size.y - 3 * p), "♥", HORIZONTAL_ALIGNMENT_CENTER, rect.size.x - 4 * p, font_size, Color("e84393"))
func _staff_relationship(staff_id: StringName) -> Variant:
var service: Variant = _service(&"relationship_service")
if service == null or not service.has_method(&"get_relationships"):
return null
for rel_val: Variant in service.call(&"get_relationships"):
if rel_val is RelationshipState:
if rel_val.actor_a_id == staff_id or rel_val.actor_b_id == staff_id:
return rel_val
return null
func _guest_relationship(guest_id: StringName) -> Variant:
var service: Variant = _service(&"relationship_service")
if service == null or not service.has_method(&"get_relationships"):
return null
for rel_val: Variant in service.call(&"get_relationships"):
if rel_val is RelationshipState:
if rel_val.actor_a_id == guest_id or rel_val.actor_b_id == guest_id:
return rel_val
return null
func _draw_excavation_jobs() -> void:
var excavation: Variant = _service(&"excavation_service")
if excavation == null or not excavation.has_method(&"get_jobs"):
@@ -1594,6 +1661,8 @@ func _resting_staff_position(staff_id: StringName, fallback: Vector2) -> Vector2
func _can_drop_data(_position: Vector2, data: Variant) -> bool:
if modal_input_blocked:
return false
return data is Dictionary and data.get(&"kind", &"") == &"staff_assignment" and data.get(&"staff_id", &"") is StringName