diff --git a/addons/dialogic/Core/DialogicGameHandler.gd b/addons/dialogic/Core/DialogicGameHandler.gd index 6bf1d3b04..b9e582c80 100644 --- a/addons/dialogic/Core/DialogicGameHandler.gd +++ b/addons/dialogic/Core/DialogicGameHandler.gd @@ -22,7 +22,7 @@ enum States { enum ClearFlags { FULL_CLEAR = 0, ## Clears all subsystems KEEP_VARIABLES = 1, ## Clears all subsystems and info except for variables - TIMLEINE_INFO_ONLY = 2 ## Doesn't clear subsystems but current timeline and index + TIMELINE_INFO_ONLY = 2 ## Doesn't clear subsystems but current timeline and index } ## Reference to the currently executed timeline. @@ -30,56 +30,69 @@ var current_timeline: DialogicTimeline = null ## Copy of the [member current_timeline]'s events. var current_timeline_events: Array = [] -## Index of the event the timeline handeling is currently at. +## Index of the event the timeline handling is currently at. var current_event_idx: int = 0 -## Contains all information that subsystems consider -## relevant for the current situation +## Contains all information that subsystems consider relevant for +## the current situation var current_state_info: Dictionary = {} -## Current state (see [member States] enum) + +## Current state (see [member States] enum). var current_state := States.IDLE: get: return current_state + set(new_state): current_state = new_state - emit_signal('state_changed', new_state) + state_changed.emit(new_state) + ## Emitted when [member current_state] change. signal state_changed(new_state:States) -## When `true`, many dialogic processes won't continue until it's false again. +## When `true`, many dialogic processes won't continue until it's `false` again. var paused := false: set(value): paused = value + if paused: + for subsystem in get_children(): + if subsystem is DialogicSubsystem: (subsystem as DialogicSubsystem).pause() + dialogic_paused.emit() + else: for subsystem in get_children(): + if subsystem is DialogicSubsystem: (subsystem as DialogicSubsystem).resume() + dialogic_resumed.emit() -## Emitted when [member paused] changes to true. +## Emitted when [member paused] changes to `true`. signal dialogic_paused -## Emitted when [member paused] changes to false. +## Emitted when [member paused] changes to `false`. signal dialogic_resumed -## Emitted when dialog ends (by a timeline end being reached OR end_timeline() being called). +## Emitted when the timeline ends. +## This can be a timeline ending or [method end_timeline] being called. signal timeline_ended -## Emitted when a timeline was started with [method start] or [method start_timeline]. +## Emitted when a timeline starts by calling either [method start] +## or [method start_timeline]. signal timeline_started -## Emitted when an event has just been executed (not necessarily when the event finished). -signal event_handled(resource:DialogicEvent) +## Emitted when an event starts being executed. +## The event may not have finished executing yet. +signal event_handled(resource: DialogicEvent) -## Emitted when the `Signal Event` was reached -signal signal_event(argument:Variant) -## Emitted when `[signal]` effect was reached in text. -signal text_signal(argument:String) +## Emitted when a [class SignalEvent] event was reached. +signal signal_event(argument: Variant) +## Emitted when a signal event gets fired from a [class TextEvent] event. +signal text_signal(argument: String) -# Careful, this section is repopulated automatically at certain moments +# Careful, this section is repopulated automatically at certain moments. #region SUBSYSTEMS var Audio := preload("res://addons/dialogic/Modules/Audio/subsystem_audio.gd").new(): @@ -227,13 +240,13 @@ func preload_timeline(timeline_resource:Variant) -> Variant: ## Clears and stops the current timeline. func end_timeline() -> void: - clear(ClearFlags.TIMLEINE_INFO_ONLY) + clear(ClearFlags.TIMELINE_INFO_ONLY) _on_timeline_ended() timeline_ended.emit() ## Handles the next event. -func handle_next_event(ignore_argument:Variant = "") -> void: +func handle_next_event(_ignore_argument: Variant = "") -> void: handle_event(current_event_idx+1) @@ -269,12 +282,13 @@ func handle_event(event_index:int) -> void: event_handled.emit(current_timeline_events[event_index]) -## Resets dialogics state fully or partially. -## By using the clear flags from the [member ClearFlags] enum you can specify what info should be kept. -## For example at timeline end usually it doesn't clear node or subsystem info -func clear(clear_flags:=ClearFlags.FULL_CLEAR) -> bool: +## Resets Dialogic's state fully or partially. +## By using the clear flags from the [member ClearFlags] enum you can specify +## what info should be kept. +## For example, at timeline end usually it doesn't clear node or subsystem info. +func clear(clear_flags := ClearFlags.FULL_CLEAR) -> bool: - if !clear_flags & ClearFlags.TIMLEINE_INFO_ONLY: + if !clear_flags & ClearFlags.TIMELINE_INFO_ONLY: for subsystem in get_children(): if subsystem is DialogicSubsystem: (subsystem as DialogicSubsystem).clear_game_state(clear_flags) diff --git a/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd b/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd index bbca3a5d6..2658995f3 100644 --- a/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd +++ b/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd @@ -19,13 +19,13 @@ func _ready() -> void: get_tree().quit() DialogicUtil.autoload().start(current_timeline) DialogicUtil.autoload().timeline_ended.connect(get_tree().quit) - DialogicUtil.autoload().signal_event.connect(recieve_event_signal) - DialogicUtil.autoload().text_signal.connect(recieve_text_signal) + DialogicUtil.autoload().signal_event.connect(receive_event_signal) + DialogicUtil.autoload().text_signal.connect(receive_text_signal) -func recieve_event_signal(argument:String) -> void: +func receive_event_signal(argument:String) -> void: print("[Dialogic] Encountered a signal event: ", argument) -func recieve_text_signal(argument:String) -> void: +func receive_text_signal(argument:String) -> void: print("[Dialogic] Encountered a signal in text: ", argument) func _input(event:InputEvent) -> void: diff --git a/addons/dialogic/Modules/Audio/subsystem_audio.gd b/addons/dialogic/Modules/Audio/subsystem_audio.gd index a6cab5f45..5a22680fd 100644 --- a/addons/dialogic/Modules/Audio/subsystem_audio.gd +++ b/addons/dialogic/Modules/Audio/subsystem_audio.gd @@ -1,22 +1,57 @@ extends DialogicSubsystem - -## Subsystem that manages music and sounds. - -signal music_started(info:Dictionary) -signal sound_started(info:Dictionary) - +## Subsystem for managing background music and one-shot sound effects. +## +## This subsystem has many different helper methods for managing audio +## in your timeline. +## For instance, you can listen to music changes via [signal music_started]. + + +## Whenever a new background music is started, this signal is emitted and +## contains a dictionary with the following keys: [br] +## [br] +## Key | Value Type | Value [br] +## ----------- | ------------- | ----- [br] +## `path` | [type String] | The path to the audio resource file. [br] +## `volume` | [type float] | The volume of the audio resource that will be set to the [member base_music_player]. [br] +## `audio_bus` | [type String] | The audio bus name that the [member base_music_player] will use. [br] +## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br] +signal music_started(info: Dictionary) + + +## Whenever a new sound effect is set, this signal is emitted and contains a +## dictionary with the following keys: [br] +## [br] +## Key | Value Type | Value [br] +## ----------- | ------------- | ----- [br] +## `path` | [type String] | The path to the audio resource file. [br] +## `volume` | [type float] | The volume of the audio resource that will be set to [member base_sound_player]. [br] +## `audio_bus` | [type String] | The audio bus name that the [member base_sound_player] will use. [br] +## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br] +signal sound_started(info: Dictionary) + + +## Audio player base duplicated to play background music. +## +## Background music is long audio. var base_music_player := AudioStreamPlayer.new() +## Audio player base, that will be duplicated to play sound effects. +## +## Sound effects are short audio. var base_sound_player := AudioStreamPlayer.new() #region STATE #################################################################################################### -func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: +## Clears the state on this subsystem and stops all audio. +## +## If you want to stop sounds only, use [method stop_all_sounds]. +func clear_game_state(clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: update_music() stop_all_sounds() +## Loads the state on this subsystem from the current state info. func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void: if load_flag == LoadFlags.ONLY_DNODES: return @@ -27,11 +62,13 @@ func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void: update_music(info.path, info.volume, info.audio_bus, 0, info.loop) +## Pauses playing audio. func pause() -> void: for child in get_children(): child.stream_paused = true +## Resumes playing audio. func resume() -> void: for child in get_children(): child.stream_paused = false @@ -85,12 +122,13 @@ func update_music(path := "", volume := 0.0, audio_bus := "Master", fade_time := fader.tween_callback(prev_node.queue_free) +## Whether music is playing. func has_music() -> bool: return !dialogic.current_state_info.get('music', {}).get('path', '').is_empty() ## Plays a given sound file. -func play_sound(path:String, volume := 0.0, audio_bus := "Master", loop := false) -> void: +func play_sound(path: String, volume := 0.0, audio_bus := "Master", loop := false) -> void: if base_sound_player != null and !path.is_empty(): sound_started.emit({'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop}) var new_sound_node := base_sound_player.duplicate() @@ -110,6 +148,7 @@ func play_sound(path:String, volume := 0.0, audio_bus := "Master", loop := false new_sound_node.finished.connect(new_sound_node.queue_free) +## Stops all audio. func stop_all_sounds() -> void: for node in get_children(): if node == base_sound_player: @@ -118,7 +157,9 @@ func stop_all_sounds() -> void: node.queue_free() -func interpolate_volume_linearly(value:float, node:Node) -> void: +## Converts a linear loudness value to decibel and sets that volume to +## the given [param node]. +func interpolate_volume_linearly(value: float, node: Node) -> void: node.volume_db = linear_to_db(value) diff --git a/addons/dialogic/Modules/Background/subsystem_backgrounds.gd b/addons/dialogic/Modules/Background/subsystem_backgrounds.gd index b5b9f58cc..5312334e9 100644 --- a/addons/dialogic/Modules/Background/subsystem_backgrounds.gd +++ b/addons/dialogic/Modules/Background/subsystem_backgrounds.gd @@ -1,22 +1,37 @@ extends DialogicSubsystem - ## Subsystem for managing backgrounds. - -signal background_changed(info:Dictionary) - - +## +## This subsystem has many different helper methods for managing backgrounds. +## For instance, you can listen to background changes via +## [signal background_changed]. + + +## Whenever a new background is set, this signal is emitted and contains a +## dictionary with the following keys: [br] +## [br] +## Key | Value Type | Value [br] +## ----------- | ------------- | ----- [br] +## `scene` | [type String] | The scene path of the new background. [br] +## `argument` | [type String] | Information given to the background on its update routine. [br] +## `fade_time` | [type float] | The time the background may take to transition in. [br] +## `same_scene`| [type bool] | If the new background uses the same Godot scene. [br] +signal background_changed(info: Dictionary) + +## The default background scene Dialogic will use. var default_background_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('DefaultBackgroundScene/default_background.tscn')) +## The default transition Dialogic will use. var default_transition: String = get_script().resource_path.get_base_dir().path_join("Transitions/Defaults/simple_fade.gd") #region STATE #################################################################################################### -func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR): +## Empties the current background state. +func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: update_background() - -func load_game_state(load_flag:=LoadFlags.FULL_LOAD): +## Loads the background state from the current state info. +func load_game_state(_load_flag := LoadFlags.FULL_LOAD) -> void: update_background(dialogic.current_state_info.get('background_scene', ''), dialogic.current_state_info.get('background_argument', ''), 0.0, default_transition, true) #endregion @@ -128,7 +143,8 @@ func _on_transition_finished(background_node:DialogicNode_BackgroundHolder, tran background_node.color = Color.TRANSPARENT transition_node.queue_free() - +## Adds sub-viewport with the given background scene as child to +## Dialogic scene. func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder) -> SubViewportContainer: var v_con := SubViewportContainer.new() var viewport := SubViewport.new() @@ -160,7 +176,7 @@ func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder return v_con - +## Whether a background is set. func has_background() -> bool: return !dialogic.current_state_info.get('background_scene', '').is_empty() or !dialogic.current_state_info.get('background_argument','').is_empty() diff --git a/addons/dialogic/Modules/Core/subsystem_input.gd b/addons/dialogic/Modules/Core/subsystem_input.gd index ea3381aba..f0912a569 100644 --- a/addons/dialogic/Modules/Core/subsystem_input.gd +++ b/addons/dialogic/Modules/Core/subsystem_input.gd @@ -1,9 +1,14 @@ extends DialogicSubsystem +## Subsystem that handles input, Auto-Advance, and skipping. +## +## This subsystem can be accessed via GDScript: `Dialogic.Inputs`. -## Subsystem that handles input, autoadvance & skipping. signal dialogic_action_priority 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 @@ -18,7 +23,8 @@ var auto_advance : DialogicAutoAdvance = null #region SUBSYSTEM METHODS ################################################################################ -func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: + +func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: if not is_node_ready(): await ready @@ -142,7 +148,7 @@ func _on_autoskip_toggled(enabled: bool) -> void: ## Handles fine-grained Auto-Skip logic. ## The [method _process] method allows for a more precise timer than the ## [Timer] class. -func _process(delta): +func _process(delta: float) -> void: if _auto_skip_timer_left > 0: _auto_skip_timer_left -= delta diff --git a/addons/dialogic/Modules/Save/subsystem_save.gd b/addons/dialogic/Modules/Save/subsystem_save.gd index f3af72a51..0d66ef6b3 100644 --- a/addons/dialogic/Modules/Save/subsystem_save.gd +++ b/addons/dialogic/Modules/Save/subsystem_save.gd @@ -1,12 +1,19 @@ extends DialogicSubsystem - -### Subsystem that manages saving and loading data. +## Subsystem to save and load game states. +## +## This subsystem has many different helper methods to save Dialogic or custom +## game data to named save slots. +## +## You can listen to saves via [signal saved]. \ +## If you want to save, you can call [method save]. \ -## Emitted when a save happened. -## The [param info] contains the following keys: -## - slot_name: The `String` name of the slot that the game state was saved to. -## - is_autosave: `true` if the save was an autosave. +## Emitted when a save happened with the following info: +## [br] +## Key | Value Type | Value [br] +## ----------- | ------------- | ----- [br] +## `slot_name` | [type String] | The name of the slot that the game state was saved to. [br] +## `is_autosave` | [type bool] | `true`, if the save was an autosave. [br] signal saved(info: Dictionary) diff --git a/addons/dialogic/Modules/Voice/subsystem_voice.gd b/addons/dialogic/Modules/Voice/subsystem_voice.gd index 19b765c7c..d289a123b 100644 --- a/addons/dialogic/Modules/Voice/subsystem_voice.gd +++ b/addons/dialogic/Modules/Voice/subsystem_voice.gd @@ -1,24 +1,54 @@ extends DialogicSubsystem - ## Subsystem that manages setting voice lines for text events. +## +## It's recommended to use the [class DialogicVoiceEvent] to set the voice lines +## for text events and not start playing them directly. + + +## Emitted whenever a new voice line starts playing. +## The [param info] contains the following keys and values: +## [br] +## Key | Value Type | Value [br] +## -------- | ------------- | ----- [br] +## `file` | [type String] | The path to file played. [br] +signal voiceline_started(info: Dictionary) -signal voiceline_started(info:Dictionary) -signal voiceline_finished(info:Dictionary) -# Emitted if the voiceline didn't end but was cut off -signal voiceline_stopped(info:Dictionary) +## Emitted whenever a voice line finished playing. +## The [param info] contains the following keys and values: +## [br] +## Key | Value Type | Value [br] +## ---------------- | ------------- | ----- [br] +## `file` | [type String] | The path to file played. [br] +## `remaining_time` | [type float] | The remaining time of the voiceline. [br] +signal voiceline_finished(info: Dictionary) +## Emitted whenever a voice line gets interrupted and does not finish playing. +## The [param info] contains the following keys and values: +## [br] +## Key | Value Type | Value [br] +## ---------------- | ------------- | ----- [br] +## `file` | [type String] | The path to file played. [br] +## `remaining_time` | [type float] | The remaining time of the voiceline. [br] +signal voiceline_stopped(info: Dictionary) + + +## The current audio file being played. var current_audio_file: String + +## The audio player for the voiceline. var voice_player := AudioStreamPlayer.new() #region STATE #################################################################################################### +## Stops the current voice from playing. func pause() -> void: voice_player.stream_paused = true +## Resumes a paused voice. func resume() -> void: voice_player.stream_paused = false @@ -30,53 +60,78 @@ func resume() -> void: func _ready() -> void: add_child(voice_player) - voice_player.finished.connect(_on_voice_finnished) + voice_player.finished.connect(_on_voice_finished) -func is_voiced(index:int) -> bool: +## Whether the current event is a text event and has a voice +## event before it. +func is_voiced(index: int) -> bool: if dialogic.current_timeline_events[index] is DialogicTextEvent: + if dialogic.current_timeline_events[index-1] is DialogicVoiceEvent: return true + return false +## Plays the voice line. This will be invoked by Dialogic. +## Requires [method set_file] to be called before or nothing plays. func play_voice() -> void: voice_player.play() - voiceline_started.emit({'file':current_audio_file}) + voiceline_started.emit({'file': current_audio_file}) -func set_file(path:String) -> void: +## Set a voice file [param path] to be played, then invoke [method play_voice]. +## +## This method does not check if [param path] is a valid file. +func set_file(path: String) -> void: if current_audio_file == path: return + current_audio_file = path var audio: AudioStream = load(path) voice_player.stream = audio -func set_volume(value:float) -> void: +## Set the volume to a [param value] in decibels. +func set_volume(value: float) -> void: voice_player.volume_db = value -func set_bus(value:String) -> void: - voice_player.bus = value +## Set the voice player's bus to a [param bus_name]. +func set_bus(bus_name: String) -> void: + voice_player.bus = bus_name +## Stops the current voice line from playing. func stop_audio() -> void: if voice_player.playing: voiceline_stopped.emit({'file':current_audio_file, 'remaining_time':get_remaining_time()}) + voice_player.stop() -func _on_voice_finnished() -> void: +## Called when the voice line finishes playing. +## Connected to [signal finished] on [member voice_player] +func _on_voice_finished() -> void: voiceline_finished.emit({'file':current_audio_file, 'remaining_time':get_remaining_time()}) +## Returns the remaining time of the current voice line in seconds. +## +## If there is no voice line playing, returns `0`. func get_remaining_time() -> float: - if not voice_player or !voice_player.playing: + if not voice_player or not voice_player.playing: return 0.0 - return voice_player.stream.get_length()-voice_player.get_playback_position() + + var stream_length := voice_player.stream.get_length() + var playback_position := voice_player.get_playback_position() + var remaining_seconds := stream_length - playback_position + + return remaining_seconds +## Whether there is still positive time remaining for the current voiceline. func is_running() -> bool: return get_remaining_time() > 0.0