Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
182 lines
5.9 KiB
GDScript
182 lines
5.9 KiB
GDScript
class_name DialogueCoordinator
|
|
## Dialogue snapshot consumers and signal handlers extracted from main.gd (#775).
|
|
##
|
|
## Owns NPC identity tracking state (_last_dialogue_npc_id/name) shared between
|
|
## consuming dialogue snapshots and handling dialogue_box signals.
|
|
## Registered with SnapshotEventRouter; reads from GameState directly.
|
|
|
|
var dialogue_box: Node = null
|
|
var monologue_display: Node = null
|
|
var journal_panel: Node = null
|
|
|
|
# Shared mutable reference — GDScript arrays are reference types.
|
|
# main.gd and this coordinator both append to the same array.
|
|
var _pending_record_inputs: Array = []
|
|
|
|
var _last_dialogue_npc_id: int = -1 # D-064: NPC entity_id for WalkAway input
|
|
var _last_dialogue_npc_name: String = "" # #535: NPC name for dialogue_response attribution
|
|
var _last_dialogue_tick: int = -1
|
|
var _last_confrontation_tick: int = -1
|
|
|
|
|
|
func init(refs: Dictionary, pending_record_inputs: Array) -> DialogueCoordinator:
|
|
dialogue_box = refs.get("dialogue_box")
|
|
monologue_display = refs.get("monologue_display")
|
|
journal_panel = refs.get("journal_panel")
|
|
_pending_record_inputs = pending_record_inputs
|
|
return self
|
|
|
|
|
|
## Connect dialogue_box signals. Call from main.gd._ready() after init().
|
|
func connect_signals() -> void:
|
|
if not dialogue_box:
|
|
return
|
|
dialogue_box.option_selected.connect(on_dialogue_option_selected)
|
|
dialogue_box.dialogue_dismissed.connect(on_dialogue_dismissed)
|
|
dialogue_box.confrontation_monologue.connect(on_confrontation_monologue)
|
|
dialogue_box.pause_requested.connect(on_dialogue_pause_requested)
|
|
dialogue_box.unpause_requested.connect(on_dialogue_unpause_requested)
|
|
# D-020 (#558): Decoupled signals — coordinator routes state changes.
|
|
dialogue_box.dialogue_state_changed.connect(on_dialogue_state_changed)
|
|
dialogue_box.audio_dip_requested.connect(on_audio_dip_requested)
|
|
dialogue_box.audio_dip_cleared.connect(on_audio_dip_cleared)
|
|
|
|
|
|
# -- Snapshot consumers (registered with SnapshotEventRouter) -----------------
|
|
|
|
|
|
# Consume-once per tick with ID tracking: show dialogue, then clear.
|
|
func consume_dialogue() -> void:
|
|
if GameState.current_dialogue == null or not dialogue_box:
|
|
return
|
|
if GameState.current_tick == _last_dialogue_tick:
|
|
return
|
|
if dialogue_box.is_dialogue_active():
|
|
GameState.current_dialogue = null
|
|
return
|
|
_last_dialogue_tick = GameState.current_tick
|
|
# #264: Close journal when dialogue opens
|
|
if journal_panel and journal_panel.has_method("close"):
|
|
journal_panel.close()
|
|
var dlg: Dictionary = GameState.current_dialogue
|
|
_last_dialogue_npc_id = dlg.get("npc_entity_id", -1)
|
|
_last_dialogue_npc_name = dlg.get("npc_name", "")
|
|
dialogue_box.show_dialogue(
|
|
dlg.get("npc_name", ""),
|
|
dlg.get("speech", ""),
|
|
dlg.get("options", []),
|
|
_last_dialogue_npc_id
|
|
)
|
|
GameState.current_dialogue = null
|
|
|
|
|
|
# #535: Consume overheard NPC-NPC conversation events (D-078).
|
|
func consume_conversation_events() -> void:
|
|
if not dialogue_box:
|
|
return
|
|
for event in GameState.conversation_events:
|
|
dialogue_box.append_conversation_event(event)
|
|
GameState.conversation_events = []
|
|
|
|
|
|
# #535: Handle conversation_ended events.
|
|
func consume_conversation_ended() -> void:
|
|
if not dialogue_box:
|
|
return
|
|
for event in GameState.conversation_ended:
|
|
dialogue_box.on_conversation_ended(event)
|
|
GameState.conversation_ended = []
|
|
|
|
|
|
# #535: Consume dialogue_response — NPC follow-up line after player picks an option.
|
|
func consume_dialogue_response() -> void:
|
|
if GameState.dialogue_response == null or not dialogue_box:
|
|
return
|
|
var dr: Dictionary = GameState.dialogue_response
|
|
var speaker_entity_id: int = dr.get("speaker_entity_id", _last_dialogue_npc_id)
|
|
var speaker_color_index: int = dr.get("speaker_color_index", -1)
|
|
var speaker_name: String = dr.get("speaker_name", _last_dialogue_npc_name)
|
|
dialogue_box.update_entity_display(speaker_entity_id, speaker_name, speaker_color_index)
|
|
dialogue_box.append_dialogue_response(speaker_name, dr.get("text", ""), speaker_entity_id)
|
|
GameState.dialogue_response = null
|
|
|
|
|
|
# -- Signal handlers ----------------------------------------------------------
|
|
|
|
|
|
# D-061: Handle dialogue option selection -> send to server
|
|
func on_dialogue_option_selected(response_id: String, _text: String) -> void:
|
|
(
|
|
SimBridge
|
|
. send_input(
|
|
{
|
|
"action": InputMapper.Action.INTERACT,
|
|
"timestamp_msec": Time.get_ticks_msec(),
|
|
"action_data":
|
|
{
|
|
"target_entity_id": null,
|
|
"verb": "DialogueResponse",
|
|
"response_id": response_id,
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
# D-063: Confrontation beat monologue -> show on monologue display (layer 7)
|
|
func on_confrontation_monologue(text: String, duration: float) -> void:
|
|
if not monologue_display:
|
|
return
|
|
if GameState.current_tick == _last_confrontation_tick:
|
|
return
|
|
_last_confrontation_tick = GameState.current_tick
|
|
monologue_display.show_monologue(text, duration, 3, true)
|
|
|
|
|
|
# D-061: Auto-pause on dialogue open
|
|
func on_dialogue_pause_requested() -> void:
|
|
var input := {"action": InputMapper.Action.PAUSE, "timestamp_msec": Time.get_ticks_msec()}
|
|
SimBridge.send_input(input)
|
|
_pending_record_inputs.append(input)
|
|
|
|
|
|
# D-061: Auto-unpause on dialogue close
|
|
func on_dialogue_unpause_requested() -> void:
|
|
var input := {"action": InputMapper.Action.UNPAUSE, "timestamp_msec": Time.get_ticks_msec()}
|
|
SimBridge.send_input(input)
|
|
_pending_record_inputs.append(input)
|
|
|
|
|
|
# D-064: Handle walk-away -> send WalkAway{npc_id} to server
|
|
func on_dialogue_dismissed() -> void:
|
|
(
|
|
SimBridge
|
|
. send_input(
|
|
{
|
|
"action": InputMapper.Action.INTERACT,
|
|
"timestamp_msec": Time.get_ticks_msec(),
|
|
"action_data":
|
|
{
|
|
"target_entity_id":
|
|
_last_dialogue_npc_id if _last_dialogue_npc_id >= 0 else null,
|
|
"verb": "WalkAway",
|
|
},
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
# D-020 (#558): Coordinator handles dialogue state changes.
|
|
func on_dialogue_state_changed(active: bool) -> void:
|
|
GameState.dialogue_active = active
|
|
|
|
|
|
# D-020 (#558): Route audio dip requests.
|
|
func on_audio_dip_requested(profile: String) -> void:
|
|
AudioManager.apply_dip(profile)
|
|
|
|
|
|
# D-020 (#558): Route audio dip clear.
|
|
func on_audio_dip_cleared() -> void:
|
|
AudioManager.clear_dip()
|