extends DialogicSubsystem ## Subsystem that handles input, Auto-Advance, and skipping. ## ## This subsystem can be accessed via GDScript: `Dialogic.Inputs`. ## Emitted when [method handle_input] is activated. ## You can listen to this and set [member action_was_consumed] to `true` ## to use the input and stop dialogic from using it further. signal dialogic_action_priority ## Emittedn when [method handle_input] is activated and the action hasn't been consumed by something else. ## Used by various subsystems to advance the timeline. signal dialogic_action ## Whenever the Auto-Skip timer finishes, this signal is emitted. ## Configure Auto-Skip settings via [member auto_skip]. signal autoskip_timer_finished const _SETTING_INPUT_ACTION := "dialogic/text/input_action" const _SETTING_INPUT_ACTION_DEFAULT := "dialogic_default_action" ## When this timer is running, input is blocked. Use ith with [method block_input]. var input_block_timer := Timer.new() var _auto_skip_timer_left: float = 0.0 ## Set this method when reacting to [signal dialogic_action_priority] if you want to stop further actions. var action_was_consumed := false ## Will be `true` if the input currently handled (while you are ## reacting to [signal dialogic_action] or [signal dialogic_priority_action]) ## was generated by a mouse button. Will be reset at the end of [method handle_action]. var input_was_mouse_input := false ## The [DialogicAutoSkip] object used. var auto_skip: DialogicAutoSkip = null ## The [DialogicAutoAdvance] object used. var auto_advance: DialogicAutoAdvance = null ## The [DialogicManualAdvance] object used. var manual_advance: DialogicManualAdvance = null @export_group("State") @export var manual_advance_info := { DialogicManualAdvance.ENABLED_STATE_KEY: true, DialogicManualAdvance.DISABLED_UNTIL_NEXT_EVENT_STATE_KEY: false} #region SUBSYSTEM METHODS ################################################################################ func _clear_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: if not is_node_ready(): await ready manual_advance.disabled_until_next_event = false manual_advance.system_enabled = true func _pause() -> void: auto_advance.autoadvance_timer.paused = true input_block_timer.paused = true set_process(false) func _resume() -> void: auto_advance.autoadvance_timer.paused = false input_block_timer.paused = false var is_autoskip_timer_done := _auto_skip_timer_left > 0.0 set_process(not is_autoskip_timer_done) func _post_install() -> void: auto_skip = DialogicAutoSkip.new() auto_advance = DialogicAutoAdvance.new() manual_advance = DialogicManualAdvance.new() dialogic.Settings.connect_to_change("autoadvance_delay_modifier", auto_advance._update_autoadvance_delay_modifier) auto_skip.toggled.connect(_on_autoskip_toggled) auto_skip._init() add_child(input_block_timer) input_block_timer.one_shot = true #endregion #region MAIN METHODS ################################################################################ ## This method is called when the dialogic action is called. ## You can call it yourself if you want to simulate a click. [br][br] ## It won't do anything if dialogic is paused or the input is currently blocked by a previous call to [method block_input]. [br][br] ## This method will emit the signals [signal dialogic_priority_action] and then ## [signal dialogic_action] if [member action_was_consumed] has not been set ## to `true` inbetween. func handle_input() -> void: if dialogic.paused or is_input_blocked(): return if not action_was_consumed: # We want to stop auto-advancing that cancels on user inputs. if (auto_advance.is_enabled() and auto_advance.enabled_until_user_input): auto_advance.enabled_until_user_input = false action_was_consumed = true # We want to stop auto-skipping if it's enabled, we are listening # to user inputs, and it's not instant skipping. if (auto_skip.disable_on_user_input and auto_skip.enabled): auto_skip.enabled = false action_was_consumed = true dialogic_action_priority.emit() if action_was_consumed: action_was_consumed = false return dialogic_action.emit() input_was_mouse_input = false ## All non mouse based input is handled in here. ## Mouse based input is handled by the InputCatcher and Textbox nodes calling [method handle_node_gui_input]. ## Calls [method handle_input]. func _unhandled_input(event:InputEvent) -> void: if is_input_pressed(event, true): if event is InputEventMouse or event is InputEventScreenTouch: return input_was_mouse_input = false handle_input() ## Input is used for all mouse based inputs. ## If any DialogicInputNode is present this won't do anything (because that node handles MouseInput then). func _input(event:InputEvent) -> void: if is_input_pressed(event): if not event is InputEventMouse: return if get_tree().get_nodes_in_group("dialogic_input").any(func(node):return node.is_visible_in_tree()): return input_was_mouse_input = true handle_input() ## Used to quickly check whether the event is the input action that is setup. func is_input_pressed(event: InputEvent, exact := false) -> bool: var action: String = ProjectSettings.get_setting(_SETTING_INPUT_ACTION, _SETTING_INPUT_ACTION_DEFAULT) return (event is InputEventAction and event.action == action) or Input.is_action_just_pressed(action, exact) ## This is called from the `_gui_input` of the InputCatcher and DialogText nodes. ## Calls [method handle_input]. func handle_node_gui_input(event:InputEvent) -> void: if Input.is_action_just_pressed(ProjectSettings.get_setting(_SETTING_INPUT_ACTION, _SETTING_INPUT_ACTION_DEFAULT)): if event is InputEventMouseButton and event.pressed: input_was_mouse_input = true handle_input() ## Returns true if a previous call to [method block_input] is still active. func is_input_blocked() -> bool: return input_block_timer.time_left > 0.0 and not auto_skip.enabled ## Blocks input for the given time. Dialogic does this at certain moments automatically, ## e.g. for a few milliseconds after text finishes revealing. ## You can call this yourself as well. func block_input(time:=0.1) -> void: if time > 0: input_block_timer.wait_time = max(time, input_block_timer.time_left) input_block_timer.start() func _ready() -> void: # We use the process method to count down the auto-start_autoskip_timer timer. set_process(false) ## Stops all timers of the subsystem. Used by the Text subsystem. func stop_timers() -> void: auto_advance.autoadvance_timer.stop() input_block_timer.stop() _auto_skip_timer_left = 0.0 #endregion #region AUTO-SKIP ################################################################################ ## This method will advance the timeline based on Auto-Skip settings. ## The state, whether Auto-Skip is enabled, is ignored. func start_autoskip_timer() -> void: _auto_skip_timer_left = auto_skip.time_per_event set_process(true) await autoskip_timer_finished ## If Auto-Skip disables, we want to stop the timer. func _on_autoskip_toggled(enabled: bool) -> void: if not enabled: _auto_skip_timer_left = 0.0 ## Handles fine-grained Auto-Skip logic. ## The [method _process] method allows for a more precise timer than the ## [Timer] class. func _process(delta: float) -> void: if _auto_skip_timer_left > 0: _auto_skip_timer_left -= delta if _auto_skip_timer_left <= 0: autoskip_timer_finished.emit() else: autoskip_timer_finished.emit() set_process(false) #endregion #region TEXT EFFECTS ################################################################################ ## The input effect (`[input]`) stops the text until a dialogic input is recieved. ## On the surface this seems similar to [n+] but it works quite differently. func effect_input(_text_node:Control, skipped:bool, _argument:String) -> void: if skipped: return dialogic.Text.show_next_indicators() await dialogic.Inputs.dialogic_action_priority dialogic.Text.hide_next_indicators() dialogic.Inputs.action_was_consumed = true ## The noskip effect (`[ns]`) disables skipping and manual advance for one event and enables autoadvance for one event. ## An argument can be given to control the timing of the autoadvance. func effect_noskip(text_node:Control, skipped:bool, argument:String) -> void: dialogic.Text.set_text_reveal_skippable(false, true) manual_advance.disabled_until_next_event = true effect_autoadvance(text_node, skipped, argument) ## The no reveal skip effect (`[nrs]`) disables reveal skipping for one event. func effect_norevealskip(_text_node:Control, _skipped:bool, _argument:String) -> void: dialogic.Text.set_text_reveal_skippable(false, true) ## The autoadvance effect (`[aa]` enables autoadvance for one event (while still allowing manual advance and skipping). ## An argument can be given to control the delay of the autoadvance. ## If the argument ends with `?` the effect will not enable auto-advance and only set the delay for an already enable auto advance. func effect_autoadvance(_text_node: Control, _skipped:bool, argument:String) -> void: if argument.ends_with("?"): argument = argument.trim_suffix("?") else: auto_advance.enabled_until_next_event = true if argument.is_valid_float(): auto_advance.override_delay_for_current_event = float(argument) #endregion