diff --git a/addons/dialogic/Modules/Settings/subsystem_settings.gd b/addons/dialogic/Modules/Settings/subsystem_settings.gd index 6f3fa1e86..b3638e729 100644 --- a/addons/dialogic/Modules/Settings/subsystem_settings.gd +++ b/addons/dialogic/Modules/Settings/subsystem_settings.gd @@ -2,9 +2,10 @@ extends DialogicSubsystem ## Subsystem that allows setting and getting settings that are automatically saved slot independent. ## All settings that are stored in the project settings dialogic/settings section are supported. -## For example the text_speed setting is stored there. +## For example the text_speed setting is stored there. ## Thus it can be acessed like this: ## Dialogic.Settings.text_speed = 0.05 +## ## Settings stored there can also be changed with the Settings event. var settings := {} @@ -25,7 +26,7 @@ func _reload_settings() -> void: for prop in ProjectSettings.get_property_list(): if prop.name.begins_with('dialogic/settings'): settings[prop.name.trim_prefix('dialogic/settings/')] = ProjectSettings.get_setting(prop.name) - + if dialogic.has_subsystem('Save'): for i in settings: settings[i] = dialogic.Save.get_global_info(i, settings[i]) @@ -49,7 +50,7 @@ func _get(property:StringName) -> Variant: func _setting_changed(property:StringName, value:Variant) -> void: if !property in _connections: return - + for i in _connections[property]: i.call(value) diff --git a/addons/dialogic/Modules/Text/default_input_handler.gd b/addons/dialogic/Modules/Text/default_input_handler.gd index da4198adb..88c218fe6 100644 --- a/addons/dialogic/Modules/Text/default_input_handler.gd +++ b/addons/dialogic/Modules/Text/default_input_handler.gd @@ -14,17 +14,24 @@ var action_was_consumed := false ################################################################################ ## INPUT ################################################################################ -func _input(event:InputEvent) -> void: +func _input(event: InputEvent) -> void: if event.is_action_pressed(ProjectSettings.get_setting('dialogic/text/input_action', 'dialogic_default_action')): - + if Dialogic.paused or is_input_blocked(): return - + + # We want to stop auto-advancing that cancels on user inputs. + if (!action_was_consumed and Dialogic.Text.is_autoadvance_enabled() + and Dialogic.Text.get_autoadvance_info()['waiting_for_user_input']): + Dialogic.Text.set_autoadvance_until_user_input(false) + return + dialogic_action_priority.emit() + if action_was_consumed: action_was_consumed = false return - + dialogic_action.emit() @@ -42,21 +49,95 @@ func block_input(time:=skip_delay) -> void: #################################################################################################### ## AUTO-ADVANCING #################################################################################################### -func _ready() -> void: - add_child(autoadvance_timer) - autoadvance_timer.one_shot = true - autoadvance_timer.timeout.connect(_on_autoadvance_timer_timeout) - add_child(input_block_timer) - input_block_timer.one_shot = true +func start_autoadvance() -> void: + if not Dialogic.Text.is_autoadvance_enabled(): + return + + var delay := _calculate_autoadvance_delay( + Dialogic.Text.get_autoadvance_info(), + Dialogic.current_state_info['text_parsed']) + if delay == 0: + _on_autoadvance_timer_timeout() + else: + await get_tree().process_frame + autoadvance_timer.start(delay) + + +## Calculates the autoadvance-time based on settings and text. +## +## Takes into account: +## - temporary delay time override +## - delay per word +## - delay per character +## - fixed delay +## - text time taken +## - autoadvance delay modifier +## - voice audio +func _calculate_autoadvance_delay(info:Dictionary, text:String="") -> float: + var delay := 0.0 + + # Check for temporary time override + if info['override_delay_for_current_event'] >= 0: + delay = info['override_delay_for_current_event'] + else: + # Add per word and per character delay + delay = _calculate_per_word_delay(text, info) + _calculate_per_character_delay(text, info) + delay *= Dialogic.Settings.get_setting('autoadvance_delay_modifier', 1) + # Apply fixed delay last, so it's not affected by the delay modifier + delay += info['fixed_delay'] + + delay = max(0, delay) + + # Wait for the voice clip (if longer than the current delay) + if info['await_playing_voice'] and Dialogic.has_subsystem('Voice') and Dialogic.Voice.is_running(): + delay = max(delay, Dialogic.Voice.get_remaining_time()) + + return delay -func start_autoadvance() -> void: - autoadvance_timer.start(Dialogic.Text.get_autoadvance_time()) +## Checks how many words can be found by separating the text by whitespace. +## (Uses ` ` aka SPACE right now, could be extended in the future) +func _calculate_per_word_delay(text: String, info:Dictionary) -> float: + return float(text.split(' ', false).size() * info['per_word_delay']) + + +## Checks how many characters can be found by iterating each letter. +func _calculate_per_character_delay(text: String, info:Dictionary) -> float: + var per_character_delay: float = info['per_character_delay'] + var calculated_delay: float = 0 + + if per_character_delay > 0: + # If we have characters to ignore, we will iterate each letter. + if info['ignored_characters_enabled']: + for character in text: + if character in info['ignored_characters']: + continue + calculated_delay += per_character_delay + + # Otherwise, we can just multiply the length of the text by the delay. + else: + calculated_delay = text.length() * per_character_delay + + return calculated_delay func _on_autoadvance_timer_timeout() -> void: autoadvance.emit() + autoadvance_timer.stop() + + +## Switches the auto-advance mode on or off based on [param is_enabled]. +func _on_autoadvance_enabled_change(is_enabled: bool) -> void: + # If auto-advance is enabled and we are not auto-advancing yet, + # we will initiate the auto-advance mode. + if (is_enabled and !is_autoadvancing() and Dialogic.current_state == Dialogic.States.IDLE and not Dialogic.current_state_info['text'].is_empty()): + start_autoadvance() + + # If auto-advance is disabled and we are auto-advancing, + # we want to cancel the auto-advance mode. + elif !is_enabled and is_autoadvancing(): + stop() func is_autoadvancing() -> bool: @@ -67,10 +148,32 @@ func get_autoadvance_time_left() -> float: return autoadvance_timer.time_left +func get_autoadvance_time() -> float: + return autoadvance_timer.wait_time + + + + +func _ready() -> void: + add_child(autoadvance_timer) + autoadvance_timer.one_shot = true + autoadvance_timer.timeout.connect(_on_autoadvance_timer_timeout) + Dialogic.Text.autoadvance_changed.connect(_on_autoadvance_enabled_change) + + add_child(input_block_timer) + input_block_timer.one_shot = true + + func pause() -> void: autoadvance_timer.paused = true input_block_timer.paused = true + +func stop() -> void: + autoadvance_timer.stop() + input_block_timer.stop() + + func resume() -> void: autoadvance_timer.paused = false input_block_timer.paused = false diff --git a/addons/dialogic/Modules/Text/event_text.gd b/addons/dialogic/Modules/Text/event_text.gd index a2eb6ea81..b04764edf 100644 --- a/addons/dialogic/Modules/Text/event_text.gd +++ b/addons/dialogic/Modules/Text/event_text.gd @@ -2,14 +2,14 @@ class_name DialogicTextEvent extends DialogicEvent -## Event that stores text. Can be said by a character. +## Event that stores text. Can be said by a character. ## Should be shown by a DialogicNode_DialogText. ### Settings -## This is the content of the text event. -## It is supposed to be displayed by a DialogicNode_DialogText node. +## This is the content of the text event. +## It is supposed to be displayed by a DialogicNode_DialogText node. ## That means you can use bbcode, but also some custom commands. var text: String = "" ## If this is not null, the given character (as a resource) will be associated with this event. @@ -23,7 +23,7 @@ var portrait: String = "" ### Helpers ## This returns the unique name_identifier of the character. This is used by the editor. -var _character_from_directory: String: +var _character_from_directory: String: get: for item in _character_directory.keys(): if _character_directory[item]['resource'] == character: @@ -36,7 +36,7 @@ var _character_from_directory: String: character = null elif value in _character_directory.keys(): character = _character_directory[value]['resource'] - + ## Used by [_character_from_directory] to fetch the unique name_identifier or resource. var _character_directory: Dictionary = {} # Reference regex without Godot escapes: ((")?(?(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?.*)\))?\s*(?(.|\n)*) @@ -58,10 +58,10 @@ func _execute() -> void: return if (not character or character.custom_info.get('style', '').is_empty()) and dialogic.has_subsystem('Styles'): - # if previous characters had a custom style change back to base style + # if previous characters had a custom style change back to base style if dialogic.current_state_info.get('base_style') != dialogic.current_state_info.get('style'): dialogic.Styles.add_layout_style(dialogic.current_state_info.get('base_style', 'Default')) - + if character: if dialogic.has_subsystem('Styles') and character.custom_info.get('style', null): dialogic.Styles.add_layout_style(character.custom_info.style, false) @@ -77,7 +77,7 @@ func _execute() -> void: dialogic.Text.update_typing_sound_mood(character.custom_info.get('sound_moods', {}).get(character.custom_info.get('sound_mood_default'), {})) else: dialogic.Text.update_typing_sound_mood() - + dialogic.Text.update_name_label(character) else: dialogic.Portraits.change_speaker(null) @@ -95,23 +95,23 @@ func _execute() -> void: final_text = final_text.replace('\n', '[n]') 1: final_text = final_text.replace('\n', '[n+][br]') - + var split_text := [] for i in split_regex.search_all(final_text): split_text.append([i.get_string().trim_prefix('[n]').trim_prefix('[n+]')]) split_text[-1].append(i.get_string().begins_with('[n+]')) - + for section_idx in range(len(split_text)): dialogic.Text.hide_next_indicators() state = States.REVEALING final_text = dialogic.Text.parse_text(split_text[section_idx][0]) dialogic.Text.about_to_show_text.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append':split_text[section_idx][1]}) final_text = await dialogic.Text.update_dialog_text(final_text, false, split_text[section_idx][1]) - + # Plays the audio region for the current line. if dialogic.has_subsystem('Voice') and dialogic.Voice.is_voiced(dialogic.current_event_idx): dialogic.Voice.play_voice() - + await dialogic.Text.text_finished state = States.IDLE #end of dialog @@ -120,24 +120,24 @@ func _execute() -> void: dialogic.Choices.show_current_choices(false) dialogic.current_state = dialogic.States.AWAITING_CHOICE return - elif Dialogic.Text.should_autoadvance(): + elif Dialogic.Text.is_autoadvance_enabled(): dialogic.Text.show_next_indicators(false, true) dialogic.Text.input_handler.start_autoadvance() else: dialogic.Text.show_next_indicators() - + if section_idx == len(split_text)-1: state = States.DONE - + if dialogic.has_subsystem('History'): if character: dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character.display_name, 'character_color':character.color}) else: dialogic.History.store_simple_history_entry(final_text, event_name) dialogic.History.event_was_read(self) - + await advance - + finish() @@ -147,10 +147,12 @@ func _on_dialogic_input_action(): if Dialogic.Text.can_skip(): Dialogic.Text.skip_text_animation() Dialogic.Text.input_handler.block_input() + Dialogic.Text.input_handler.stop() _: if Dialogic.Text.can_manual_advance(): advance.emit() Dialogic.Text.input_handler.block_input() + Dialogic.Text.input_handler.stop() func _on_dialogic_input_autoadvance(): @@ -180,7 +182,7 @@ func to_text() -> String: text_to_use = text_to_use.replace(':', '\\:') if text_to_use.is_empty(): text_to_use = "" - + if character: var name := "" for path in _character_directory.keys(): @@ -208,12 +210,12 @@ func from_text(string:String) -> void: _character_directory = Dialogic.character_directory else: _character_directory = self.get_meta("editor_character_directory") - + # load default character if !_character_from_directory.is_empty() and _character_directory != null and _character_directory.size() > 0: if _character_from_directory in _character_directory.keys(): character = _character_directory[_character_from_directory]['resource'] - + var result := regex.search(string) if result and !result.get_string('name').is_empty(): var name := result.get_string('name').strip_edges() @@ -230,7 +232,7 @@ func from_text(string:String) -> void: if name in _character_directory[character]['full_path']: character = _character_directory[character]['resource'] break - + # If it doesn't exist, at runtime we'll consider it a guest and create a temporary character if character == null: if Engine.is_editor_hint() == false: @@ -243,7 +245,7 @@ func from_text(string:String) -> void: if !result.get_string('portrait').is_empty(): portrait = result.get_string('portrait').strip_edges().trim_prefix('(').trim_suffix(')') - + if result: text = result.get_string('text').replace("\\\n", "\n").replace('\\:', ':').strip_edges().trim_prefix('\\') if text == '': @@ -287,16 +289,16 @@ func _get_property_original_translation(property:String) -> String: ################################################################################ func build_event_editor(): - add_header_edit('_character_from_directory', ValueType.COMPLEX_PICKER, - {'file_extension' : '.dch', - 'suggestions_func' : get_character_suggestions, + add_header_edit('_character_from_directory', ValueType.COMPLEX_PICKER, + {'file_extension' : '.dch', + 'suggestions_func' : get_character_suggestions, 'empty_text' : '(No one)', 'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")}, 'do_any_characters_exist()') - add_header_edit('portrait', ValueType.COMPLEX_PICKER, - {'suggestions_func' : get_portrait_suggestions, - 'placeholder' : "(Don't change)", + add_header_edit('portrait', ValueType.COMPLEX_PICKER, + {'suggestions_func' : get_portrait_suggestions, + 'placeholder' : "(Don't change)", 'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"), - 'collapse_when_empty':true,}, + 'collapse_when_empty':true,}, 'character != null and !has_no_portraits()') add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true}) @@ -309,20 +311,20 @@ func has_no_portraits() -> bool: func get_character_suggestions(search_text:String) -> Dictionary: var suggestions := {} - + #override the previous _character_directory with the meta, specifically for searching otherwise new nodes wont work _character_directory = Engine.get_main_loop().get_meta('dialogic_character_directory') - + var icon = load("res://addons/dialogic/Editor/Images/Resources/character.svg") suggestions['(No one)'] = {'value':null, 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]} - + for resource in _character_directory.keys(): suggestions[resource] = { - 'value' : resource, - 'tooltip' : _character_directory[resource]['full_path'], + 'value' : resource, + 'tooltip' : _character_directory[resource]['full_path'], 'icon' : icon.duplicate()} return suggestions - + func get_portrait_suggestions(search_text:String) -> Dictionary: var suggestions := {} @@ -342,15 +344,15 @@ var completion_text_effects := {} func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, word:String, symbol:String) -> void: if completion_text_character_getter_regex.get_pattern().is_empty(): completion_text_character_getter_regex.compile("(\"[^\"]*\"|[^\\s:]*)") - + if completion_text_effects.is_empty(): for idx in DialogicUtil.get_indexers(): for effect in idx._get_text_effects(): completion_text_effects[effect['command']] = effect - + if not ':' in line.substr(0, TextNode.get_caret_column()) and symbol == '(': var character := completion_text_character_getter_regex.search(line).get_string().trim_prefix('"').trim_suffix('"') - + CodeCompletionHelper.suggest_portraits(TextNode, character) if symbol == '[': suggest_bbcode(TextNode) @@ -361,7 +363,7 @@ func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:Str TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command, TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']') if symbol == '{': CodeCompletionHelper.suggest_variables(TextNode) - + if symbol == '=': if CodeCompletionHelper.get_line_untill_caret(line).ends_with('[portrait='): var character := completion_text_character_getter_regex.search(line).get_string('name') @@ -400,7 +402,7 @@ func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, li load_text_effects() if text_random_word_regex.get_pattern().is_empty(): text_random_word_regex.compile("(?]+(\\/[^\\>]*)\\>") - + var result := regex.search(line) if !result: return dict diff --git a/addons/dialogic/Modules/Text/node_dialog_text.gd b/addons/dialogic/Modules/Text/node_dialog_text.gd index 143a62462..a118f7be1 100644 --- a/addons/dialogic/Modules/Text/node_dialog_text.gd +++ b/addons/dialogic/Modules/Text/node_dialog_text.gd @@ -1,7 +1,7 @@ class_name DialogicNode_DialogText extends RichTextLabel -## Dialogic node that can reveal text at a given (changeable speed). +## Dialogic node that can reveal text at a given (changeable speed). signal started_revealing_text() signal continued_revealing_text(new_character) @@ -22,7 +22,6 @@ var lspeed:float = 0.01 var speed_counter:float = 0 - func _set(property, what): if property == 'text' and typeof(what) == TYPE_STRING: text = what @@ -34,7 +33,7 @@ func _set(property, what): func _ready() -> void: # add to necessary add_to_group('dialogic_dialog_text') - + bbcode_enabled = true if start_hidden: textbox_root.hide() @@ -46,10 +45,11 @@ func reveal_text(_text:String, keep_previous:=false) -> void: if !enabled: return show() - + if !keep_previous: text = _text base_visible_characters = 0 + if alignment == Alignment.CENTER: text = '[center]'+text elif alignment == Alignment.RIGHT: @@ -59,7 +59,7 @@ func reveal_text(_text:String, keep_previous:=false) -> void: base_visible_characters = len(text) visible_characters = len(text) text = text+_text - + revealing = true speed_counter = 0 started_revealing_text.emit() @@ -70,10 +70,12 @@ func continue_reveal() -> void: if visible_characters <= get_total_character_count(): revealing = false await Dialogic.Text.execute_effects(visible_characters-base_visible_characters, self, false) + if visible_characters == -1: return revealing = true visible_characters += 1 + if visible_characters > -1 and visible_characters <= len(get_parsed_text()): continued_revealing_text.emit(get_parsed_text()[visible_characters-1]) else: @@ -90,6 +92,7 @@ func finish_text() -> void: Dialogic.Text.execute_effects(-1, self, true) revealing = false Dialogic.current_state = Dialogic.States.IDLE + emit_signal("finished_revealing_text") @@ -97,7 +100,9 @@ func finish_text() -> void: func _process(delta:float) -> void: if !revealing or Dialogic.paused: return + speed_counter += delta + while speed_counter > lspeed and revealing and !Dialogic.paused: speed_counter -= lspeed continue_reveal() diff --git a/addons/dialogic/Modules/Text/settings_text.gd b/addons/dialogic/Modules/Text/settings_text.gd index a4a84b603..41d484221 100644 --- a/addons/dialogic/Modules/Text/settings_text.gd +++ b/addons/dialogic/Modules/Text/settings_text.gd @@ -1,12 +1,12 @@ @tool extends DialogicSettingsPage - var autopause_sets := {} func _get_priority() -> int: return 98 + func _get_title() -> String: return "Text" @@ -14,17 +14,40 @@ func _get_title() -> String: func _get_info_section(): return $InformationSection + func _refresh(): %DefaultSpeed.value = ProjectSettings.get_setting('dialogic/text/letter_speed', 0.01) %Skippable.button_pressed = ProjectSettings.get_setting('dialogic/text/skippable', true) %SkippableDelay.value = ProjectSettings.get_setting('dialogic/text/skippable_delay', 0.1) - %Autoadvance.button_pressed = ProjectSettings.get_setting('dialogic/text/autoadvance', false) - %AutoadvanceDelay.value = ProjectSettings.get_setting('dialogic/text/autoadvance_delay', 1) + + %AutoAdvance.button_pressed = ProjectSettings.get_setting('dialogic/text/autoadvance_enabled', false) + %FixedDelay.value = ProjectSettings.get_setting('dialogic/text/autoadvance_fixed_delay', 1) + + var per_character_delay := ProjectSettings.get_setting('dialogic/text/autoadvance_per_character_delay', 0.1) + var per_word_delay := ProjectSettings.get_setting('dialogic/text/autoadvance_per_word_delay', 0) + if per_character_delay == 0 and per_word_delay == 0: + _on_additional_delay_mode_item_selected(0) + elif per_word_delay == 0: + _on_additional_delay_mode_item_selected(2, per_character_delay) + else: + _on_additional_delay_mode_item_selected(1, per_word_delay) + + + %IgnoredCharactersEnabled.button_pressed = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters_enabled', true) + + var ignored_characters: String = '' + var ignored_characters_dict: Dictionary = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters', {}) + + for ignored_character in ignored_characters_dict.keys(): + ignored_characters += ignored_character + + %IgnoredCharacters.text = ignored_characters + %AutocolorNames.button_pressed = ProjectSettings.get_setting('dialogic/text/autocolor_names', false) %InputAction.resource_icon = get_theme_icon("Mouse", "EditorIcons") %InputAction.set_value(ProjectSettings.get_setting('dialogic/text/input_action', 'dialogic_default_action')) %InputAction.get_suggestions_func = suggest_actions - + %AutoPausesAbsolute.button_pressed = ProjectSettings.get_setting('dialogic/text/absolute_autopauses', false) %NewEvents.button_pressed = ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false) %NewEventOption.select(ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0)) @@ -34,13 +57,63 @@ func _refresh(): func _about_to_close(): save_autopauses() -func _on_AutoadvanceDelay_value_changed(value): - ProjectSettings.set_setting('dialogic/text/autoadvance_delay', value) + +func _on_Autoadvance_toggled(button_pressed): + ProjectSettings.set_setting('dialogic/text/autoadvance_enabled', button_pressed) ProjectSettings.save() -func _on_Autoadvance_toggled(button_pressed): - ProjectSettings.set_setting('dialogic/text/autoadvance', button_pressed) +func _on_FixedDelay_value_changed(value): + ProjectSettings.set_setting('dialogic/text/autoadvance_fixed_delay', value) + ProjectSettings.save() + + +func _on_additional_delay_mode_item_selected(index:int, delay:float=-1) -> void: + %AdditionalDelayMode.selected = index + match index: + 0: # NONE + %AdditionalDelay.hide() + %AutoadvanceIgnoreCharacters.hide() + ProjectSettings.set_setting('dialogic/text/autoadvance_per_word_delay', 0) + ProjectSettings.set_setting('dialogic/text/autoadvance_per_character_delay', 0) + 1: # PER WORD + %AdditionalDelay.show() + %AutoadvanceIgnoreCharacters.hide() + if delay != -1: + %AdditionalDelay.value = delay + else: + ProjectSettings.set_setting('dialogic/text/autoadvance_per_word_delay', %AdditionalDelay.value) + ProjectSettings.set_setting('dialogic/text/autoadvance_per_character_delay', 0) + 2: # PER CHARACTER + %AdditionalDelay.show() + %AutoadvanceIgnoreCharacters.show() + if delay != -1: + %AdditionalDelay.value = delay + else: + ProjectSettings.set_setting('dialogic/text/autoadvance_per_character_delay', %AdditionalDelay.value) + ProjectSettings.set_setting('dialogic/text/autoadvance_per_word_delay', 0) + ProjectSettings.save() + + +func _on_additional_delay_value_changed(value:float) -> void: + match %AdditionalDelayMode.selected: + 0: # NONE + ProjectSettings.set_setting('dialogic/text/autoadvance_per_character_delay', 0) + ProjectSettings.set_setting('dialogic/text/autoadvance_per_word_delay', 0) + 1: # PER WORD + ProjectSettings.set_setting('dialogic/text/autoadvance_per_word_delay', value) + 2: # PER CHARACTER + ProjectSettings.set_setting('dialogic/text/autoadvance_per_character_delay', value) + ProjectSettings.save() + + +func _on_IgnoredCharactersEnabled_toggled(button_pressed): + ProjectSettings.set_setting('dialogic/text/autoadvance_ignored_characters_enabled', button_pressed) + ProjectSettings.save() + + +func _on_IgnoredCharacters_text_changed(text_input): + ProjectSettings.set_setting('dialogic/text/autoadvance_ignored_characters', DialogicUtil.str_to_hash_set(text_input)) ProjectSettings.save() @@ -80,7 +153,7 @@ func _on_AutocolorNames_toggled(button_pressed:bool) -> void: func load_autopauses(dictionary:Dictionary) -> void: for i in %AutoPauseSets.get_children(): i.queue_free() - + for i in dictionary.keys(): add_autopause_set(i, dictionary[i]) @@ -114,14 +187,14 @@ func add_autopause_set(text:String, time:float) -> void: spin_box.value = time info['time'] = spin_box %AutoPauseSets.add_child(spin_box) - + var remove_btn := Button.new() remove_btn.icon = get_theme_icon('Remove', 'EditorIcons') remove_btn.pressed.connect(_on_remove_autopauses_set_pressed.bind(len(autopause_sets))) info['delete'] = remove_btn %AutoPauseSets.add_child(remove_btn) autopause_sets[len(autopause_sets)] = info - + func _on_remove_autopauses_set_pressed(index:int) -> void: for key in autopause_sets[index]: @@ -142,3 +215,4 @@ func _on_new_events_toggled(button_pressed:bool) -> void: func _on_new_event_option_item_selected(index:int) -> void: ProjectSettings.set_setting('dialogic/text/split_at_new_lines_as', index) ProjectSettings.save() + diff --git a/addons/dialogic/Modules/Text/settings_text.tscn b/addons/dialogic/Modules/Text/settings_text.tscn index 877c0ff61..378cf4c7d 100644 --- a/addons/dialogic/Modules/Text/settings_text.tscn +++ b/addons/dialogic/Modules/Text/settings_text.tscn @@ -6,7 +6,7 @@ [ext_resource type="PackedScene" uid="uid://dyp7m2nvab1aj" path="res://addons/dialogic/Editor/Events/Fields/MultilineText.tscn" id="4_0hlwb"] [ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd" id="5_cc70e"] -[sub_resource type="Image" id="Image_18n8m"] +[sub_resource type="Image" id="Image_ga21t"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -15,8 +15,8 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_5nfkt"] -image = SubResource("Image_18n8m") +[sub_resource type="ImageTexture" id="ImageTexture_e0cmw"] +image = SubResource("Image_ga21t") [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5tav8"] @@ -60,7 +60,7 @@ text = "Default letter speed" [node name="HintTooltip2" parent="VBoxContainer/VBox/DefaultSpeedLabel" instance=ExtResource("3_s7xhj")] layout_mode = 2 tooltip_text = "The speed in seconds per character. A speed of 0 will reveal the full text instantly (still taking pauses into consideration)." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "The speed in seconds per character. A speed of 0 will reveal the full text instantly (still taking pauses into consideration)." [node name="DefaultSpeed" type="SpinBox" parent="VBoxContainer/VBox"] @@ -79,7 +79,7 @@ text = "Input action" layout_mode = 2 tooltip_text = "The action that skips text and generally advances to the next event. You can modify actions in the Project Settings > Input Map." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "The action that skips text and generally advances to the next event. You can modify actions in the Project Settings > Input Map." @@ -100,7 +100,7 @@ layout_mode = 2 tooltip_text = "If enabled the revealing of text can be skipped with the input action. If disabled you can only advance to the next event when revealing has finnished. To avoid rapid skipping, you can shortly block skipping with a delay." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "If enabled the revealing of text can be skipped with the input action. If disabled you can only advance to the next event when revealing has finnished. To avoid rapid skipping, you can shortly block skipping with a delay." @@ -130,40 +130,13 @@ text = "Autocolor names" [node name="HintTooltip5" parent="VBoxContainer/VBox/ColorNames" instance=ExtResource("3_s7xhj")] layout_mode = 2 tooltip_text = "If enabled character names will be colored in the characters color in text events." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "If enabled character names will be colored in the characters color in text events." [node name="AutocolorNames" type="CheckBox" parent="VBoxContainer/VBox"] unique_name_in_owner = true layout_mode = 2 -[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VBox"] -layout_mode = 2 - -[node name="Label5" type="Label" parent="VBoxContainer/VBox/HBoxContainer2"] -layout_mode = 2 -text = "Autocontinue" - -[node name="HintTooltip7" parent="VBoxContainer/VBox/HBoxContainer2" instance=ExtResource("3_s7xhj")] -layout_mode = 2 -tooltip_text = "If enabled dialogic will automatically continue to the next event when text has finished revealing (after a delay set in seconds)." -texture = SubResource("ImageTexture_5nfkt") -hint_text = "If enabled dialogic will automatically continue to the next event when text has finished revealing (after a delay set in seconds)." - -[node name="Autoadvance" type="HBoxContainer" parent="VBoxContainer/VBox"] -layout_mode = 2 - -[node name="Autoadvance" type="CheckBox" parent="VBoxContainer/VBox/Autoadvance"] -unique_name_in_owner = true -layout_mode = 2 - -[node name="AutoadvanceDelay" type="SpinBox" parent="VBoxContainer/VBox/Autoadvance"] -unique_name_in_owner = true -layout_mode = 2 -size_flags_horizontal = 3 -step = 0.01 -suffix = "s" - [node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer/VBox"] layout_mode = 2 @@ -175,7 +148,7 @@ text = "New lines as new events" layout_mode = 2 tooltip_text = "If enabled dialogic, new lines will be treated as [n] effects, seemingly waiting for input before starting a new text." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "If enabled dialogic, new lines will be treated as [n] effects, seemingly waiting for input before starting a new text." @@ -200,6 +173,155 @@ popup/item_1/id = 1 [node name="HSeparator" type="HSeparator" parent="VBoxContainer"] layout_mode = 2 +[node name="HBoxContainer AutoAdvance" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="Title" type="Label" parent="VBoxContainer/HBoxContainer AutoAdvance"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Auto-Advance" + +[node name="HintTooltip" parent="VBoxContainer/HBoxContainer AutoAdvance" instance=ExtResource("3_s7xhj")] +layout_mode = 2 +tooltip_text = "Autoadvance is the concept of automatically progressing to the next event upon completing text display, usually after a certain delay. + +You can enabled autoadvance from code using either: +- Dialogic.Text.set_autoadvance_until_user_input(true) +- Dialogic.Text.set_autoadvance_until_next_event(true) +- Dialogic.Text.set_autoadvance_system(true) +These add up, so if any of them is true, autoadvance will happen. +Unless manual advancement is disabled, the autoadvance time can always be skipped by the player. + +The autoadvance will wait for Voice audio to finish playing. This behaviour can be disabled via code. " +texture = SubResource("ImageTexture_e0cmw") +hint_text = "Autoadvance is the concept of automatically progressing to the next event upon completing text display, usually after a certain delay. + +You can enabled autoadvance from code using either: +- Dialogic.Text.set_autoadvance_until_user_input(true) +- Dialogic.Text.set_autoadvance_until_next_event(true) +- Dialogic.Text.set_autoadvance_system(true) +These add up, so if any of them is true, autoadvance will happen. +Unless manual advancement is disabled, the autoadvance time can always be skipped by the player. + +The autoadvance will wait for Voice audio to finish playing. This behaviour can be disabled via code. " + +[node name="AutoadvanceSettings" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +columns = 2 + +[node name="HBox_BaseDelay2" type="HBoxContainer" parent="VBoxContainer/AutoadvanceSettings"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/AutoadvanceSettings/HBox_BaseDelay2"] +layout_mode = 2 +text = "Base Delay" + +[node name="HintTooltip" parent="VBoxContainer/AutoadvanceSettings/HBox_BaseDelay2" instance=ExtResource("3_s7xhj")] +layout_mode = 2 +tooltip_text = "This is the base delay for autoadvancment." +texture = SubResource("ImageTexture_e0cmw") +hint_text = "This is the base delay for autoadvancment." + +[node name="FixedDelay" type="SpinBox" parent="VBoxContainer/AutoadvanceSettings"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 2 +step = 0.01 +value = 1.0 +suffix = "s" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/AutoadvanceSettings"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer"] +layout_mode = 2 +text = "Additional Delay" + +[node name="HintTooltip2" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer" instance=ExtResource("3_s7xhj")] +layout_mode = 2 +tooltip_text = "An additional delay per character or word can be added. + +Note: When changing values via code, you can actually use both modes simultaniously." +texture = SubResource("ImageTexture_e0cmw") +hint_text = "An additional delay per character or word can be added. + +Note: When changing values via code, you can actually use both modes simultaniously." + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/AutoadvanceSettings"] +layout_mode = 2 + +[node name="AdditionalDelayMode" type="OptionButton" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +item_count = 3 +selected = 0 +fit_to_longest_item = false +popup/item_0/text = "None" +popup/item_0/id = 0 +popup/item_1/text = "Per Word" +popup/item_1/id = 1 +popup/item_2/text = "Per Character" +popup/item_2/id = 2 + +[node name="AdditionalDelay" type="SpinBox" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +step = 0.001 + +[node name="AutoadvanceIgnoreCharacters" type="HBoxContainer" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Label3" type="Label" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters"] +layout_mode = 2 +text = "Ignored Characters" + +[node name="HintTooltip3" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters" instance=ExtResource("3_s7xhj")] +layout_mode = 2 +tooltip_text = "An ignored character will add no delay, this is useful to exclude interpunction and whitespaces. + +If disabled, the general line of text length will be used, stripping the BBCode tags first. +If enabled, the text will be scanned and the matching characters will be skipped." +texture = SubResource("ImageTexture_e0cmw") +hint_text = "An ignored character will add no delay, this is useful to exclude interpunction and whitespaces. + +If disabled, the general line of text length will be used, stripping the BBCode tags first. +If enabled, the text will be scanned and the matching characters will be skipped." + +[node name="IgnoredCharactersEnabled" type="CheckBox" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="IgnoredCharacters" type="LineEdit" parent="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters"] +unique_name_in_owner = true +layout_mode = 2 +text = "/\\\\,.( ); ?!-+\"'" +expand_to_text_length = true + +[node name="HBox_BaseDelay" type="HBoxContainer" parent="VBoxContainer/AutoadvanceSettings"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/AutoadvanceSettings/HBox_BaseDelay"] +layout_mode = 2 +text = "Enabled at the start" + +[node name="HintTooltip" parent="VBoxContainer/AutoadvanceSettings/HBox_BaseDelay" instance=ExtResource("3_s7xhj")] +layout_mode = 2 +tooltip_text = "While you would usually enable autoadvance via code, +if this is true it will be initially enabled. +This kind of autoadvance (system) only stops when disabled via code. " +texture = SubResource("ImageTexture_e0cmw") +hint_text = "While you would usually enable autoadvance via code, +if this is true it will be initially enabled. +This kind of autoadvance (system) only stops when disabled via code. " + +[node name="AutoAdvance" type="CheckBox" parent="VBoxContainer/AutoadvanceSettings"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"] +layout_mode = 2 + [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] layout_mode = 2 @@ -214,7 +336,7 @@ tooltip_text = "Adds pauses after certain letters. Each set can contain multiple letters that will (individually) have a pause of the given length added after them." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "Adds pauses after certain letters. Each set can contain multiple letters that will (individually) @@ -242,7 +364,7 @@ text = "Absolute auto-pause times" [node name="HintTooltip7" parent="VBoxContainer/HBoxContainer3" instance=ExtResource("3_s7xhj")] layout_mode = 2 tooltip_text = "If not enabled autopauses will be multiplied by the speed and user speed. When enabled those will be ignored." -texture = SubResource("ImageTexture_5nfkt") +texture = SubResource("ImageTexture_e0cmw") hint_text = "If not enabled autopauses will be multiplied by the speed and user speed. When enabled those will be ignored." [node name="AutoPausesAbsolute" type="CheckBox" parent="VBoxContainer/HBoxContainer3"] @@ -303,9 +425,13 @@ fit_content = true [connection signal="toggled" from="VBoxContainer/VBox/Skippable/Skippable" to="." method="_on_Skippable_toggled"] [connection signal="value_changed" from="VBoxContainer/VBox/Skippable/SkippableDelay" to="." method="_on_skippable_delay_value_changed"] [connection signal="toggled" from="VBoxContainer/VBox/AutocolorNames" to="." method="_on_AutocolorNames_toggled"] -[connection signal="toggled" from="VBoxContainer/VBox/Autoadvance/Autoadvance" to="." method="_on_Autoadvance_toggled"] -[connection signal="value_changed" from="VBoxContainer/VBox/Autoadvance/AutoadvanceDelay" to="." method="_on_AutoadvanceDelay_value_changed"] [connection signal="toggled" from="VBoxContainer/VBox/HBoxContainer4/NewEvents" to="." method="_on_new_events_toggled"] [connection signal="item_selected" from="VBoxContainer/VBox/HBoxContainer4/NewEventOption" to="." method="_on_new_event_option_item_selected"] +[connection signal="value_changed" from="VBoxContainer/AutoadvanceSettings/FixedDelay" to="." method="_on_FixedDelay_value_changed"] +[connection signal="item_selected" from="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AdditionalDelayMode" to="." method="_on_additional_delay_mode_item_selected"] +[connection signal="value_changed" from="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AdditionalDelay" to="." method="_on_additional_delay_value_changed"] +[connection signal="toggled" from="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters/IgnoredCharactersEnabled" to="." method="_on_IgnoredCharactersEnabled_toggled"] +[connection signal="text_changed" from="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters/IgnoredCharacters" to="." method="_on_IgnoredCharacters_text_changed"] +[connection signal="toggled" from="VBoxContainer/AutoadvanceSettings/AutoAdvance" to="." method="_on_Autoadvance_toggled"] [connection signal="pressed" from="VBoxContainer/HBoxContainer/Add" to="." method="_on_add_autopauses_set_pressed"] [connection signal="toggled" from="VBoxContainer/HBoxContainer3/AutoPausesAbsolute" to="." method="_on_auto_pauses_absolute_toggled"] diff --git a/addons/dialogic/Modules/Text/subsystem_text.gd b/addons/dialogic/Modules/Text/subsystem_text.gd index d1cd0b13c..b1f190239 100644 --- a/addons/dialogic/Modules/Text/subsystem_text.gd +++ b/addons/dialogic/Modules/Text/subsystem_text.gd @@ -6,6 +6,7 @@ signal about_to_show_text(info:Dictionary) signal text_finished(info:Dictionary) signal speaker_updated(character:DialogicCharacter) signal textbox_visibility_changed(visible:bool) +signal autoadvance_changed(enabled: bool) signal animation_textbox_new_text signal animation_textbox_show signal animation_textbox_hide @@ -23,6 +24,8 @@ enum TextTypes {DIALOG_TEXT, CHOICE_TEXT} var text_modifiers := [] var input_handler :Node = null +var _autoadvance_enabled = false + # set by the [speed] effect, multies the letter speed and [pause] effects var speed_multiplier := 1.0 # stores the pure letter speed (unmultiplied) @@ -40,13 +43,21 @@ func clear_game_state(clear_flag:=Dialogic.ClearFlags.FULL_CLEAR) -> void: update_name_label(null) dialogic.current_state_info['speaker'] = null dialogic.current_state_info['text'] = '' - + set_skippable(ProjectSettings.get_setting('dialogic/text/skippable', true)) - set_autoadvance(ProjectSettings.get_setting('dialogic/text/autoadvance', false), ProjectSettings.get_setting('dialogic/text/autoadvance_delay', 1)) + + set_autoadvance_system(ProjectSettings.get_setting('dialogic/text/autoadvance_enabled', false)) + var autoadvance_info := get_autoadvance_info() + autoadvance_info['fixed_delay'] = ProjectSettings.get_setting('dialogic/text/autoadvance_fixed_delay', 1) + autoadvance_info['per_word_delay'] = ProjectSettings.get_setting('dialogic/text/autoadvance_per_word_delay', 0) + autoadvance_info['per_character_delay'] = ProjectSettings.get_setting('dialogic/text/autoadvance_per_character_delay', 0.1) + autoadvance_info['ignored_characters_enabled'] = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters_enabled', true) + autoadvance_info['ignored_characters'] = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters', {}) + for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'): if text_node.start_hidden: text_node.textbox_root.hide() - + set_manualadvance(true) @@ -112,12 +123,16 @@ func update_dialog_text(text:String, instant:bool= false, additional:= false) -> text_node.reveal_text(text, additional) if !text_node.finished_revealing_text.is_connected(_on_dialog_text_finished): text_node.finished_revealing_text.connect(_on_dialog_text_finished) - + dialogic.current_state_info['text_parsed'] = (text_node as RichTextLabel).get_parsed_text() + # also resets temporary autoadvance and noskip settings: speed_multiplier = 1 - set_autoadvance(false, 1, true) + + set_autoadvance_until_next_event(false) + set_autoadvance_override_delay_for_current_event(-1) set_skippable(true, true) set_manualadvance(true, true) + return text @@ -143,35 +158,6 @@ func update_name_label(character:DialogicCharacter) -> void: name_label.self_modulate = Color(1,1,1,1) -func set_autoadvance(enabled:=true, wait_time:Variant=1.0, temp:= false) -> void: - if !dialogic.current_state_info.has('autoadvance'): - dialogic.current_state_info['autoadvance'] = {'enabled':false, 'temp_enabled':false, 'wait_time':0, 'temp_wait_time':0} - if temp: - dialogic.current_state_info['autoadvance']['temp_enabled'] = enabled - dialogic.current_state_info['autoadvance']['temp_wait_time'] = wait_time - else: - dialogic.current_state_info['autoadvance']['enabled'] = enabled - dialogic.current_state_info['autoadvance']['wait_time'] = wait_time - - -func set_manualadvance(enabled:=true, temp:= false) -> void: - if !dialogic.current_state_info.has('manual_advance'): - dialogic.current_state_info['manual_advance'] = {'enabled':true, 'temp_enabled':true} - if temp: - dialogic.current_state_info['manual_advance']['temp_enabled'] = enabled - else: - dialogic.current_state_info['manual_advance']['enabled'] = enabled - - -func set_skippable(skippable:= true, temp:=false) -> void: - if !dialogic.current_state_info.has('skippable'): - dialogic.current_state_info['skippable'] = {'enabled':false, 'temp_enabled':false} - if temp: - dialogic.current_state_info['skippable']['temp_enabled'] = skippable - else: - dialogic.current_state_info['skippable']['enabled'] = skippable - - func update_typing_sound_mood(mood:Dictionary = {}) -> void: for typing_sound in get_tree().get_nodes_in_group('dialogic_type_sounds'): typing_sound.load_overwrite(mood) @@ -230,16 +216,16 @@ func update_text_speed(letter_speed:float = -1, absolute:bool = false, _speed_mu letter_speed = ProjectSettings.get_setting('dialogic/text/letter_speed', 0.01) _pure_letter_speed = letter_speed _letter_speed_absolute = absolute - + if _speed_multiplier == -1: _speed_multiplier = speed_multiplier else: speed_multiplier = _speed_multiplier - + if _user_speed == -1: _user_speed = Dialogic.Settings.get_setting('text_speed', 1) - - + + for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'): if absolute: text_node.lspeed = letter_speed @@ -248,39 +234,136 @@ func update_text_speed(letter_speed:float = -1, absolute:bool = false, _speed_mu +##################### AUTOADVANCE SYSTEM ########################################################### #################################################################################################### -## HELPERS -#################################################################################################### -func should_autoadvance() -> bool: - return dialogic.current_state_info['autoadvance']['enabled'] or dialogic.current_state_info['autoadvance'].get('temp_enabled', false) +## Returns whether autoadvance is currently considered enabled. +## Autoadvance is considered on if any of these flags is true: +## - waiting_for_user_input (becomes false on any dialogic input action) +## - waiting_for_next_event (becomes false on each text event) +## - waiting_for_system (becomes false only when disabled via code) +## +## All three can be set with dedicated methods. +func is_autoadvance_enabled() -> bool: + return (get_autoadvance_info()['waiting_for_next_event'] + or get_autoadvance_info()['waiting_for_user_input'] + or get_autoadvance_info()['waiting_for_system']) + + +## Fetches all Auto-Advance settings. +## If they don't exist, returns the default settings. +## The key's values will be changed upon setting them. +func get_autoadvance_info() -> Dictionary: + if not dialogic.current_state_info.has('autoadvance'): + dialogic.current_state_info['autoadvance'] = { + 'waiting_for_next_event' : false, + 'waiting_for_user_input' : false, + 'waiting_for_system' : false, + 'fixed_delay' : 1, + 'per_word_delay' : 0, + 'per_character_delay' : 0.1, + 'ignored_characters_enabled' : false, + 'ignored_characters' : {}, + 'override_delay_for_current_event' : -1, + 'await_playing_voice' : true, + } + return dialogic.current_state_info['autoadvance'] + + +## Updates the [member _autoadvance_enabled] variable to properly check if the value has changed. +## If it changed, emits the [member autoadvance_changed] signal. +func _emit_autoadvance_enabled() -> void: + var old_autoadvance_state = _autoadvance_enabled + _autoadvance_enabled = is_autoadvance_enabled() + + if old_autoadvance_state != _autoadvance_enabled: + autoadvance_changed.emit(_autoadvance_enabled) + + +## Sets the autoadvance waiting_for_user_input flag to [param enabled]. +func set_autoadvance_until_user_input(enabled: bool) -> void: + var info := get_autoadvance_info() + info['waiting_for_user_input'] = enabled + + _emit_autoadvance_enabled() -func can_manual_advance() -> bool: - return dialogic.current_state_info['manual_advance']['enabled'] and dialogic.current_state_info['manual_advance'].get('temp_enabled', true) + +## Sets the autoadvance waiting_for_system flag to [param enabled]. +func set_autoadvance_system(enabled: bool) -> void: + var info := get_autoadvance_info() + info['waiting_for_system'] = enabled + + _emit_autoadvance_enabled() + + +## Sets the autoadvance waiting_for_next_event flag to [param enabled]. +func set_autoadvance_until_next_event(enabled: bool) -> void: + var info := get_autoadvance_info() + info['waiting_for_next_event'] = enabled + + _emit_autoadvance_enabled() + + +func _update_autoadvance_delay_modifier(delay_modifier: float) -> void: + var info: Dictionary = get_autoadvance_info() + info['delay_modifier'] = delay_modifier + + +func set_autoadvance_override_delay_for_current_event(delay_time := -1.0) -> void: + var info := get_autoadvance_info() + info['override_delay_for_current_event'] = delay_time func get_autoadvance_time() -> float: - if dialogic.current_state_info['autoadvance'].get('temp_enabled', false): - var wait_time:Variant = dialogic.current_state_info['autoadvance']['temp_wait_time'] - if typeof(wait_time) == TYPE_STRING and wait_time.begins_with('v'): - if '+' in wait_time: - return Dialogic.Voice.get_remaining_time() + float(wait_time.split('+')[1]) - return Dialogic.Voice.get_remaining_time() - return float(dialogic.current_state_info['autoadvance']['temp_wait_time']) - else: - return float(dialogic.current_state_info['autoadvance']['wait_time']) + return input_handler.get_autoadvance_time() +## Returns the progress of the auto-advance timer on a scale between 0 and 1. +## The higher the value, the closer the timer is to finishing. +## If auto-advancing is disabled, returns -1. func get_autoadvance_progress() -> float: if !input_handler.is_autoadvancing(): return -1 - return (get_autoadvance_time()-input_handler.get_autoadvance_time_left())/get_autoadvance_time() + + var total_time: float = get_autoadvance_time() + var time_left: float = input_handler.get_autoadvance_time_left() + var progress: float = (total_time - time_left) / total_time + + return progress + + +##################### MANUAL ADVANCE ############################################################### +#################################################################################################### + +func set_manualadvance(enabled:=true, temp:= false) -> void: + if !dialogic.current_state_info.has('manual_advance'): + dialogic.current_state_info['manual_advance'] = {'enabled':false, 'temp_enabled':false} + if temp: + dialogic.current_state_info['manual_advance']['temp_enabled'] = enabled + else: + dialogic.current_state_info['manual_advance']['enabled'] = enabled + + +func can_manual_advance() -> bool: + return dialogic.current_state_info['manual_advance']['enabled'] and dialogic.current_state_info['manual_advance'].get('temp_enabled', true) + + +func set_skippable(skippable:= true, temp:=false) -> void: + if !dialogic.current_state_info.has('skippable'): + dialogic.current_state_info['skippable'] = {'enabled':false, 'temp_enabled':false} + if temp: + dialogic.current_state_info['skippable']['temp_enabled'] = skippable + else: + dialogic.current_state_info['skippable']['enabled'] = skippable func can_skip() -> bool: return dialogic.current_state_info['skippable']['enabled'] and dialogic.current_state_info['skippable'].get('temp_enabled', true) +################### Text Effects & Modifiers ################################################### +#################################################################################################### + func collect_text_effects() -> void: var text_effect_names := "" text_effects.clear() @@ -310,9 +393,9 @@ func parse_text_effects(text:String) -> String: bbcode_correction = effect_match.get_start()-position_correction-len(rtl.get_parsed_text()) # append [index] = [command, value] to effects dict parsed_text_effect_info.append({'index':effect_match.get_start()-position_correction-bbcode_correction, 'execution_info':text_effects[effect_match.get_string('command')], 'value': effect_match.get_string('value').strip_edges()}) - + text = text.substr(0,effect_match.get_start()-position_correction)+text.substr(effect_match.get_start()-position_correction+len(effect_match.get_string())) - + position_correction += len(effect_match.get_string()) text = text.replace('\\[', '[') rtl.queue_free() @@ -361,12 +444,15 @@ func get_current_speaker() -> DialogicCharacter: return (load(dialogic.current_state_info['speaker']) as DialogicCharacter) +#################### HELPERS & OTHER STUFF ######################################################### +#################################################################################################### + func _ready(): collect_character_names() collect_text_effects() collect_text_modifiers() Dialogic.event_handled.connect(hide_next_indicators) - + _autopauses = {} var autopause_data :Dictionary= ProjectSettings.get_setting('dialogic/text/autopauses', {}) for i in autopause_data.keys(): @@ -374,8 +460,9 @@ func _ready(): input_handler = Node.new() input_handler.set_script(load(get_script().resource_path.get_base_dir().path_join('default_input_handler.gd'))) add_child(input_handler) - + Dialogic.Settings.connect_to_change('text_speed', _update_user_speed) + Dialogic.Settings.connect_to_change('autoadvance_delay_modifier', _update_autoadvance_delay_modifier) func _update_user_speed(user_speed:float) -> void: @@ -385,32 +472,32 @@ func _update_user_speed(user_speed:float) -> void: func color_names(text:String) -> String: if !ProjectSettings.get_setting('dialogic/text/autocolor_names', false): return text - + var counter := 0 for result in color_regex.search_all(text): text = text.insert(result.get_start("name")+((9+8+8)*counter), '[color=#' + character_colors[result.get_string('name')].to_html() + ']') text = text.insert(result.get_end("name")+9+8+((9+8+8)*counter), '[/color]') counter += 1 - + return text func collect_character_names() -> void: #don't do this at all if we're not using autocolor names to begin with if !ProjectSettings.get_setting('dialogic/text/autocolor_names', false): - return - + return + character_colors = {} for dch_path in DialogicUtil.list_resources_of_type('.dch'): var dch := (load(dch_path) as DialogicCharacter) if dch.display_name: character_colors[dch.display_name] = dch.color - + for nickname in dch.nicknames: if nickname.strip_edges(): character_colors[nickname.strip_edges()] = dch.color - + color_regex.compile('(?<=\\W|^)(?'+str(character_colors.keys()).trim_prefix('["').trim_suffix('"]').replace('", "', '|')+')(?=\\W|$)') @@ -421,7 +508,11 @@ func collect_character_names() -> void: func effect_pause(text_node:Control, skipped:bool, argument:String) -> void: if skipped: return + + var text_speed = Dialogic.Settings.get_setting('text_speed', 1) + if argument: + if argument.ends_with('!'): await get_tree().create_timer(float(argument.trim_suffix('!'))).timeout elif speed_multiplier != 0 and Dialogic.Settings.get_setting('text_speed', 1) != 0: @@ -462,12 +553,6 @@ func effect_mood(text_node:Control, skipped:bool, argument:String) -> void: load(Dialogic.current_state_info.character).custom_info.get('sound_moods', {}).get(argument, {})) -func effect_noskip(text_node:Control, skipped:bool, argument:String) -> void: - set_skippable(false, true) - set_manualadvance(false, true) - effect_autoadvance(text_node, skipped, argument) - - func effect_input(text_node:Control, skipped:bool, argument:String) -> void: if skipped: return @@ -477,11 +562,20 @@ func effect_input(text_node:Control, skipped:bool, argument:String) -> void: input_handler.action_was_consumed = true -func effect_autoadvance(text_node:Control, skipped:bool, argument:String) -> void: - if argument.is_empty() or !(argument.is_valid_float() or argument.begins_with('v')): - set_autoadvance(true, ProjectSettings.get_setting('dialogic/text/autoadvance_delay', 1), true) +func effect_noskip(text_node:Control, skipped:bool, argument:String) -> void: + set_skippable(false, true) + set_manualadvance(false, true) + effect_autoadvance(text_node, skipped, argument) + + +func effect_autoadvance(text_node: Control, skipped:bool, argument:String) -> void: + if argument.ends_with('?'): + argument = argument.trim_suffix('?') else: - set_autoadvance(true, argument, true) + set_autoadvance_until_next_event(true) + + if argument.is_valid_float(): + set_autoadvance_override_delay_for_current_event(float(argument)) var modifier_words_select_regex := RegEx.create_from_string("(?]+(\\/[^\\>]*)\\>") diff --git a/addons/dialogic/Other/DialogicUtil.gd b/addons/dialogic/Other/DialogicUtil.gd index a398cb751..58f7f946b 100644 --- a/addons/dialogic/Other/DialogicUtil.gd +++ b/addons/dialogic/Other/DialogicUtil.gd @@ -3,7 +3,6 @@ class_name DialogicUtil ## Script that container helper methods for both editor and game execution. ## Used whenever the same thing is needed in different parts of the plugin. - enum AnimationType {ALL, IN, OUT, ACTION} @@ -91,9 +90,9 @@ static func get_module_path(name:String, builtin:=true) -> String: static func get_indexers(include_custom := true, force_reload := false) -> Array[DialogicIndexer]: if Engine.get_main_loop().has_meta('dialogic_indexers') and !force_reload: return Engine.get_main_loop().get_meta('dialogic_indexers') - + var indexers : Array[DialogicIndexer] = [] - + for file in listdir(DialogicUtil.get_module_path(''), false): var possible_script:String = DialogicUtil.get_module_path(file).path_join("index.gd") if FileAccess.file_exists(possible_script): @@ -105,7 +104,7 @@ static func get_indexers(include_custom := true, force_reload := false) -> Array var possible_script: String = extensions_folder.path_join(file + "/index.gd") if FileAccess.file_exists(possible_script): indexers.append(load(possible_script).new()) - + Engine.get_main_loop().set_meta('dialogic_indexers', indexers) return indexers @@ -169,7 +168,7 @@ static func get_color_palette(default:bool = false) -> Dictionary: 'Color8': Color('#de5c5c'), # Red 'Color9': Color('#7c7c7c'), # Gray } - if default: + if default: return defaults return get_editor_setting('color_palette', defaults) @@ -181,7 +180,7 @@ static func get_color(value:String) -> Color: static func is_physics_timer() -> bool: return ProjectSettings.get_setting('dialogic/timer/process_in_physics', false) - + static func update_timer_process_callback(timer:Timer) -> void: timer.process_callback = Timer.TIMER_PROCESS_PHYSICS if is_physics_timer() else Timer.TIMER_PROCESS_IDLE @@ -211,11 +210,11 @@ static func get_inherited_style_overrides(style_name:String) -> Dictionary: var styles_info := ProjectSettings.get_setting('dialogic/layout/styles', {'Default':{}}) if !style_name in styles_info: return {} - + var inheritance := [styles_info[style_name].get('inherits', '')] var info :Dictionary = styles_info[style_name].get('export_overrides', {}).duplicate(true) - - + + while (!inheritance[-1].is_empty()) and inheritance[-1] in styles_info: info.merge(styles_info[inheritance[-1]].get('export_overrides', {})) if !styles_info[inheritance[-1]].get('inherits', '') in styles_info: @@ -259,11 +258,11 @@ static func apply_scene_export_overrides(node:Node, export_overrides:Dictionary) static func get_scene_export_defaults(node:Node) -> Dictionary: if !node.script: return {} - + if Engine.get_main_loop().has_meta('dialogic_scene_export_defaults') and \ node.script.resource_path in Engine.get_main_loop().get_meta('dialogic_scene_export_defaults'): return Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path] - + if !Engine.get_main_loop().has_meta('dialogic_scene_export_defaults'): Engine.get_main_loop().set_meta('dialogic_scene_export_defaults', {}) var defaults := {} @@ -342,7 +341,7 @@ static func setup_script_property_edit_node(property_info: Dictionary, value:Var input.value_changed.connect(DialogicUtil._on_export_file_submitted.bind(property_changed)) elif property_info['hint'] & PROPERTY_HINT_ENUM: input = OptionButton.new() - var options :PackedStringArray = [] + var options :PackedStringArray = [] for x in property_info['hint_string'].split(','): options.append(x.split(':')[0].strip_edges()) input.add_item(options[-1]) @@ -403,9 +402,9 @@ static func set_editor_setting(setting:String, value:Variant) -> void: var cfg := ConfigFile.new() if FileAccess.file_exists('user://dialogic/editor_settings.cfg'): cfg.load('user://dialogic/editor_settings.cfg') - + cfg.set_value('DES', setting, value) - + if !DirAccess.dir_exists_absolute('user://dialogic'): DirAccess.make_dir_absolute('user://dialogic') cfg.save('user://dialogic/editor_settings.cfg') @@ -415,8 +414,19 @@ static func get_editor_setting(setting:String, default:Variant=null) -> Variant: var cfg := ConfigFile.new() if !FileAccess.file_exists('user://dialogic/editor_settings.cfg'): return default - + if !cfg.load('user://dialogic/editor_settings.cfg') == OK: return default - + return cfg.get_value('DES', setting, default) + + +## Takes [param source] and builds a dictionary of keys only. +## The values are `null`. +static func str_to_hash_set(source: String) -> Dictionary: + var dictionary := Dictionary() + + for character in source: + dictionary[character] = null + + return dictionary