First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
39
addons/dialogic/Modules/Text/DNextIndicator_icon.svg.import
Normal file
39
addons/dialogic/Modules/Text/DNextIndicator_icon.svg.import
Normal file
@@ -0,0 +1,39 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2fkk71l86stl"
|
||||
path="res://.godot/imported/DNextIndicator_icon.svg-d8f962ed97aa9f929346c153ee39c52b.ctex"
|
||||
metadata={
|
||||
"editor_scale": 1.25,
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Events/Text/DNextIndicator_icon.svg"
|
||||
dest_files=["res://.godot/imported/DNextIndicator_icon.svg-d8f962ed97aa9f929346c153ee39c52b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
232
addons/dialogic/Modules/Text/auto_advance.gd
Normal file
232
addons/dialogic/Modules/Text/auto_advance.gd
Normal file
@@ -0,0 +1,232 @@
|
||||
class_name DialogicAutoAdvance
|
||||
extends RefCounted
|
||||
## This class holds the settings for the Auto-Advance feature.
|
||||
## Changing the variables will alter the behaviour of Auto-Advance.
|
||||
##
|
||||
## Auto-Advance is a feature that automatically advances the timeline after
|
||||
## a player-specific amount of time.
|
||||
## This is useful for visual novels that want the player to read the text
|
||||
## without having to press.
|
||||
##
|
||||
## Unlike [class DialogicAutoSkip], Auto-Advance uses multiple enable flags,
|
||||
## allowing to track the different instances that enabled Auto-Advance.
|
||||
## For instance, if a timeline event forces Auto-Advance to be enabled and later
|
||||
## disables it, the Auto-Advance will still be enabled if the player didn't
|
||||
## cancel it.
|
||||
|
||||
signal autoadvance
|
||||
signal toggled(enabled: bool)
|
||||
|
||||
var autoadvance_timer := Timer.new()
|
||||
|
||||
var fixed_delay: float = 1.0
|
||||
var delay_modifier: float = 1.0
|
||||
|
||||
var per_word_delay: float = 0.0
|
||||
var per_character_delay: float = 0.1
|
||||
|
||||
var ignored_characters_enabled := false
|
||||
var ignored_characters := {}
|
||||
|
||||
var await_playing_voice := true
|
||||
|
||||
var override_delay_for_current_event: float = -1.0
|
||||
|
||||
## Private variable to track the last Auto-Advance state.
|
||||
## This will be used to emit the [signal toggled] signal.
|
||||
var _last_enable_state := false
|
||||
|
||||
## If true, Auto-Advance will be active until the next event.
|
||||
##
|
||||
## Use this flag to create a temporary Auto-Advance mode.
|
||||
## You can utilise [variable override_delay_for_current_event] to set a
|
||||
## temporary Auto-Advance delay for this event.
|
||||
##
|
||||
## Stacks with [variable enabled_forced] and [variable enabled_until_user_input].
|
||||
var enabled_until_next_event := false :
|
||||
set(enabled):
|
||||
enabled_until_next_event = enabled
|
||||
_try_emit_toggled()
|
||||
|
||||
## If true, Auto-Advance will stay enabled until this is set to false.
|
||||
##
|
||||
## This boolean can be used to create an automatic text display.
|
||||
##
|
||||
## Stacks with [variable enabled_until_next_event] and [variable enabled_until_user_input].
|
||||
var enabled_forced := false :
|
||||
set(enabled):
|
||||
enabled_forced = enabled
|
||||
_try_emit_toggled()
|
||||
|
||||
## If true, Auto-Advance will be active until the player presses a button.
|
||||
##
|
||||
## Use this flag when the player wants to enable Auto-Advance.
|
||||
##
|
||||
## Stacks with [variable enabled_forced] and [variable enabled_until_next_event].
|
||||
var enabled_until_user_input := false :
|
||||
set(enabled):
|
||||
enabled_until_user_input = enabled
|
||||
_try_emit_toggled()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
DialogicUtil.autoload().Inputs.add_child(autoadvance_timer)
|
||||
autoadvance_timer.one_shot = true
|
||||
autoadvance_timer.timeout.connect(_on_autoadvance_timer_timeout)
|
||||
toggled.connect(_on_toggled)
|
||||
|
||||
enabled_forced = ProjectSettings.get_setting('dialogic/text/autoadvance_enabled', false)
|
||||
fixed_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_fixed_delay', 1)
|
||||
per_word_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_word_delay', 0)
|
||||
per_character_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_character_delay', 0.1)
|
||||
ignored_characters_enabled = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters_enabled', true)
|
||||
ignored_characters = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters', {})
|
||||
|
||||
#region AUTOADVANCE INTERNALS
|
||||
|
||||
func start() -> void:
|
||||
if not is_enabled():
|
||||
return
|
||||
|
||||
var parsed_text: String = DialogicUtil.autoload().current_state_info['text_parsed']
|
||||
var delay := _calculate_autoadvance_delay(parsed_text)
|
||||
|
||||
await DialogicUtil.autoload().get_tree().process_frame
|
||||
if delay == 0:
|
||||
_on_autoadvance_timer_timeout()
|
||||
else:
|
||||
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(text: String = "") -> float:
|
||||
var delay := 0.0
|
||||
|
||||
# Check for temporary time override
|
||||
if override_delay_for_current_event >= 0:
|
||||
delay = override_delay_for_current_event
|
||||
else:
|
||||
# Add per word and per character delay
|
||||
delay = _calculate_per_word_delay(text) + _calculate_per_character_delay(text)
|
||||
|
||||
delay *= delay_modifier
|
||||
# Apply fixed delay last, so it's not affected by the delay modifier
|
||||
delay += fixed_delay
|
||||
|
||||
delay = max(0, delay)
|
||||
|
||||
# Wait for the voice clip (if longer than the current delay)
|
||||
if await_playing_voice and DialogicUtil.autoload().has_subsystem('Voice') and DialogicUtil.autoload().Voice.is_running():
|
||||
delay = max(delay, DialogicUtil.autoload().Voice.get_remaining_time())
|
||||
|
||||
return delay
|
||||
|
||||
|
||||
## 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) -> float:
|
||||
return float(text.split(' ', false).size() * per_word_delay)
|
||||
|
||||
|
||||
## Checks how many characters can be found by iterating each letter.
|
||||
func _calculate_per_character_delay(text: String) -> float:
|
||||
var calculated_delay: float = 0
|
||||
|
||||
if per_character_delay > 0:
|
||||
# If we have characters to ignore, we will iterate each letter.
|
||||
if ignored_characters_enabled:
|
||||
for character in text:
|
||||
if character in 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 enabled].
|
||||
func _on_toggled(enabled: bool) -> void:
|
||||
# If auto-advance is enabled and we are not auto-advancing yet,
|
||||
# we will initiate the auto-advance mode.
|
||||
if (enabled and !is_advancing()
|
||||
and DialogicUtil.autoload().current_state == DialogicGameHandler.States.IDLE
|
||||
and not DialogicUtil.autoload().current_state_info.get('text', '').is_empty()):
|
||||
start()
|
||||
|
||||
# If auto-advance is disabled and we are auto-advancing,
|
||||
# we want to cancel the auto-advance mode.
|
||||
elif !enabled and is_advancing():
|
||||
DialogicUtil.autoload().Inputs.stop_timers()
|
||||
#endregion
|
||||
|
||||
#region AUTOADVANCE HELPERS
|
||||
func is_advancing() -> bool:
|
||||
return !autoadvance_timer.is_stopped()
|
||||
|
||||
|
||||
func get_time_left() -> float:
|
||||
return autoadvance_timer.time_left
|
||||
|
||||
|
||||
func get_time() -> float:
|
||||
return autoadvance_timer.wait_time
|
||||
|
||||
|
||||
## Returns whether Auto-Advance is currently considered enabled.
|
||||
## Auto-Advance uses three different enable flags:
|
||||
## - enabled_until_user_input (becomes false on any dialogic input action)
|
||||
## - enabled_until_next_event (becomes false on each text event)
|
||||
## - enabled_forced (becomes false only when disabled via code)
|
||||
##
|
||||
## All three can be set with dedicated methods.
|
||||
func is_enabled() -> bool:
|
||||
return (enabled_until_next_event
|
||||
or enabled_until_user_input
|
||||
or enabled_forced)
|
||||
|
||||
|
||||
## Updates the [member _autoadvance_enabled] variable to properly check if the value has changed.
|
||||
## If it changed, emits the [member toggled] signal.
|
||||
func _try_emit_toggled() -> void:
|
||||
var old_autoadvance_state := _last_enable_state
|
||||
_last_enable_state = is_enabled()
|
||||
|
||||
if old_autoadvance_state != _last_enable_state:
|
||||
toggled.emit(_last_enable_state)
|
||||
|
||||
|
||||
## An internal method connected to changes on the Delay Modifier setting.
|
||||
func _update_autoadvance_delay_modifier(delay_modifier_value: float) -> void:
|
||||
delay_modifier = delay_modifier_value
|
||||
|
||||
|
||||
## 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_progress() -> float:
|
||||
if !is_advancing():
|
||||
return -1
|
||||
|
||||
var total_time: float = get_time()
|
||||
var time_left: float = get_time_left()
|
||||
var progress: float = (total_time - time_left) / total_time
|
||||
|
||||
return progress
|
||||
#endregion
|
||||
1
addons/dialogic/Modules/Text/auto_advance.gd.uid
Normal file
1
addons/dialogic/Modules/Text/auto_advance.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b4k3lq0gimaf8
|
||||
70
addons/dialogic/Modules/Text/auto_skip.gd
Normal file
70
addons/dialogic/Modules/Text/auto_skip.gd
Normal file
@@ -0,0 +1,70 @@
|
||||
class_name DialogicAutoSkip
|
||||
extends RefCounted
|
||||
## This class holds the settings for the Auto-Skip feature.
|
||||
## Changing the variables will alter the behaviour of Auto-Skip.
|
||||
##
|
||||
## Auto-Skip must be implemented per event.
|
||||
|
||||
## Emitted whenever the Auto-Skip state changes, from `true` to `false` or
|
||||
## vice-versa.
|
||||
signal toggled(is_enabled: bool)
|
||||
|
||||
## Whether Auto-Skip is enabled or not.
|
||||
## If Auto-Skip is referred to be [i]disabled[/i], it refers to setting this
|
||||
## this variable to `false`.
|
||||
## This variable will automatically emit [signal autoskip_changed] when changed.
|
||||
var enabled := false : set = _set_enabled
|
||||
|
||||
## If `true`, Auto-Skip will be disabled when the user presses a recognised
|
||||
## input action.
|
||||
var disable_on_user_input := true
|
||||
|
||||
## If `true`, Auto-Skip will be disabled when the timeline advances to a
|
||||
## unread Text event or an event requesting user input.
|
||||
var disable_on_unread_text := false
|
||||
|
||||
## If `true`, Auto-Skip will be enabled when the timeline advances to a
|
||||
## previously visited Text event.
|
||||
## Useful if the player always wants to skip already-visited Text events.
|
||||
var enable_on_visited := false
|
||||
|
||||
## If `true`, Auto-Skip will skip Voice events instead of playing them.
|
||||
var skip_voice := true
|
||||
|
||||
## The amount of seconds each event may take.
|
||||
## This is not enforced, each event must implement this behaviour.
|
||||
var time_per_event: float = 0.1
|
||||
|
||||
|
||||
## Setting up Auto-Skip.
|
||||
func _init() -> void:
|
||||
time_per_event = ProjectSettings.get_setting('dialogic/text/autoskip_time_per_event', time_per_event)
|
||||
|
||||
if DialogicUtil.autoload().has_subsystem("History") and not DialogicUtil.autoload().History.visited_event.is_connected(_handle_seen_event):
|
||||
DialogicUtil.autoload().History.visited_event.connect(_handle_seen_event)
|
||||
DialogicUtil.autoload().History.unvisited_event.connect(_handle_unseen_event)
|
||||
|
||||
|
||||
## Called when Auto-Skip is enabled or disabled.
|
||||
## Emits [signal autoskip_changed] if the state changed.
|
||||
func _set_enabled(is_enabled: bool) -> void:
|
||||
var previous_enabled := enabled
|
||||
enabled = is_enabled
|
||||
|
||||
if enabled != previous_enabled:
|
||||
toggled.emit(enabled)
|
||||
|
||||
|
||||
func _handle_seen_event() -> void:
|
||||
# If Auto-Skip is disabled but reacts to seen events, we
|
||||
# enable Auto-Skip.
|
||||
if not enabled and enable_on_visited:
|
||||
enabled = true
|
||||
|
||||
|
||||
func _handle_unseen_event() -> void:
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
if disable_on_unread_text:
|
||||
enabled = false
|
||||
1
addons/dialogic/Modules/Text/auto_skip.gd.uid
Normal file
1
addons/dialogic/Modules/Text/auto_skip.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://doet4o4t1cwuo
|
||||
@@ -0,0 +1,230 @@
|
||||
@tool
|
||||
extends DialogicCharacterEditorMainSection
|
||||
|
||||
## Character editor section that allows editing typing sound moods.
|
||||
|
||||
var current_mood := ''
|
||||
var current_moods_info := {}
|
||||
var default_mood := ''
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
hint_text = 'Typing sound moods allow you to vary the "typing" sounds of your character. \nThey can be changed based on the portrait or with the [mood=something] text effect.'
|
||||
|
||||
|
||||
func _get_title() -> String:
|
||||
return "Typing Sounds"
|
||||
|
||||
################################################################################
|
||||
## COMMUNICATION WITH EDITOR
|
||||
################################################################################
|
||||
|
||||
func _load_character(character:DialogicCharacter):
|
||||
default_mood = character.custom_info.get('sound_mood_default', '')
|
||||
|
||||
current_moods_info = character.custom_info.get('sound_moods', {}).duplicate(true)
|
||||
|
||||
current_mood = ""
|
||||
update_mood_list()
|
||||
|
||||
|
||||
func _save_changes(character:DialogicCharacter) -> DialogicCharacter:
|
||||
# Quickly save latest mood
|
||||
if current_mood:
|
||||
current_moods_info[current_mood] = get_mood_info()
|
||||
|
||||
character.custom_info['sound_mood_default'] = default_mood
|
||||
character.custom_info['sound_moods'] = current_moods_info.duplicate(true)
|
||||
return character
|
||||
|
||||
|
||||
func get_portrait_data() -> Dictionary:
|
||||
if character_editor.selected_item and is_instance_valid(character_editor.selected_item):
|
||||
return character_editor.selected_item.get_metadata(0)
|
||||
return {}
|
||||
|
||||
|
||||
func set_portrait_data(data:Dictionary) -> void:
|
||||
if character_editor.selected_item and is_instance_valid(character_editor.selected_item):
|
||||
character_editor.selected_item.set_metadata(0, data)
|
||||
|
||||
|
||||
################################################################################
|
||||
## OWN STUFF
|
||||
################################################################################
|
||||
|
||||
func _ready() -> void:
|
||||
%ListPanel.self_modulate = get_theme_color("base_color", "Editor")
|
||||
%Add.icon = get_theme_icon("Add", "EditorIcons")
|
||||
%Delete.icon = get_theme_icon("Remove", "EditorIcons")
|
||||
%Duplicate.icon = get_theme_icon("Duplicate", "EditorIcons")
|
||||
%Play.icon = get_theme_icon("Play", "EditorIcons")
|
||||
%Default.icon = get_theme_icon("NonFavorite", "EditorIcons")
|
||||
|
||||
%NameWarning.texture = get_theme_icon("StatusWarning", "EditorIcons")
|
||||
|
||||
|
||||
func update_mood_list(selected_name := "") -> void:
|
||||
%MoodList.clear()
|
||||
|
||||
for mood in current_moods_info:
|
||||
var idx: int = %MoodList.add_item(mood, get_theme_icon("AudioStreamPlayer", "EditorIcons"))
|
||||
if mood == selected_name:
|
||||
%MoodList.select(idx)
|
||||
_on_mood_list_item_selected(idx)
|
||||
if !%MoodList.is_anything_selected() and %MoodList.item_count:
|
||||
%MoodList.select(0)
|
||||
_on_mood_list_item_selected(0)
|
||||
|
||||
if %MoodList.item_count == 0:
|
||||
current_mood = ""
|
||||
|
||||
%Delete.disabled = !%MoodList.is_anything_selected()
|
||||
%Play.disabled = !%MoodList.is_anything_selected()
|
||||
%Duplicate.disabled = !%MoodList.is_anything_selected()
|
||||
%Default.disabled = !%MoodList.is_anything_selected()
|
||||
%Settings.visible = %MoodList.is_anything_selected()
|
||||
|
||||
%MoodList.custom_minimum_size.y = min(%MoodList.item_count*45, 100)
|
||||
%MoodList.visible = %MoodList.item_count != 0
|
||||
|
||||
character_editor.get_settings_section_by_name('Typing Sound Mood', false).update_visibility(%MoodList.item_count != 0)
|
||||
|
||||
|
||||
|
||||
func _input(event:InputEvent) -> void:
|
||||
if !is_visible_in_tree() or (get_viewport().gui_get_focus_owner() and !name+'/' in str(get_viewport().gui_get_focus_owner().get_path())):
|
||||
return
|
||||
if event is InputEventKey and event.keycode == KEY_F2 and event.pressed:
|
||||
if %MoodList.is_anything_selected():
|
||||
%Name.grab_focus()
|
||||
%Name.select_all()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_mood_list_item_selected(index:int) -> void:
|
||||
if current_mood:
|
||||
current_moods_info[current_mood] = get_mood_info()
|
||||
|
||||
current_mood = %MoodList.get_item_text(index)
|
||||
load_mood_info(current_moods_info[current_mood])
|
||||
|
||||
%Delete.disabled = !%MoodList.is_anything_selected()
|
||||
%Play.disabled = !%MoodList.is_anything_selected()
|
||||
%Duplicate.disabled = !%MoodList.is_anything_selected()
|
||||
%Default.disabled = !%MoodList.is_anything_selected()
|
||||
%Settings.visible = %MoodList.is_anything_selected()
|
||||
|
||||
|
||||
func load_mood_info(dict:Dictionary) -> void:
|
||||
%Name.text = dict.get('name', '')
|
||||
%NameWarning.hide()
|
||||
set_default_button(default_mood == dict.get('name', ''))
|
||||
%SoundFolder.set_value(dict.get('sound_path', ''))
|
||||
%Mode.select(dict.get('mode', 0))
|
||||
%PitchBase.set_value(dict.get('pitch_base', 1))
|
||||
%PitchVariance.set_value(dict.get('pitch_variance', 0))
|
||||
%VolumeBase.set_value(dict.get('volume_base', 0))
|
||||
%VolumeVariance.set_value(dict.get('volume_variance', 0))
|
||||
%Skip.set_value(dict.get('skip_characters', 0))
|
||||
|
||||
|
||||
func get_mood_info() -> Dictionary:
|
||||
var dict := {}
|
||||
dict['name'] = %Name.text
|
||||
dict['sound_path'] = %SoundFolder.current_value
|
||||
dict['mode'] = %Mode.selected
|
||||
dict['pitch_base'] = %PitchBase.value
|
||||
dict['pitch_variance'] = %PitchVariance.value
|
||||
dict['volume_base'] = %VolumeBase.value
|
||||
dict['volume_variance'] = %VolumeVariance.value
|
||||
dict['skip_characters'] = %Skip.value
|
||||
return dict
|
||||
|
||||
|
||||
func _on_add_pressed() -> void:
|
||||
if !current_mood.is_empty():
|
||||
current_moods_info[current_mood] = get_mood_info()
|
||||
|
||||
var new_name := 'Mood '
|
||||
var counter := 1
|
||||
while new_name+str(counter) in current_moods_info:
|
||||
counter+=1
|
||||
new_name += str(counter)
|
||||
|
||||
current_moods_info[new_name] = {'name':new_name}
|
||||
|
||||
update_mood_list(new_name)
|
||||
|
||||
|
||||
func _on_duplicate_pressed() -> void:
|
||||
if !current_mood.is_empty():
|
||||
current_moods_info[current_mood] = get_mood_info()
|
||||
|
||||
current_moods_info[current_mood+"_copy"] = get_mood_info()
|
||||
current_moods_info[current_mood+"_copy"].name = current_mood+"_copy"
|
||||
update_mood_list(current_mood+"_copy")
|
||||
|
||||
|
||||
func _on_delete_pressed() -> void:
|
||||
if current_mood.is_empty():
|
||||
return
|
||||
current_moods_info.erase(current_mood)
|
||||
current_mood = ""
|
||||
update_mood_list()
|
||||
|
||||
|
||||
func _on_name_text_changed(new_text:String) -> void:
|
||||
if new_text.is_empty():
|
||||
%NameWarning.show()
|
||||
%NameWarning.tooltip_text = "Name cannot be empty!"
|
||||
elif new_text in current_moods_info and new_text != current_mood:
|
||||
%NameWarning.show()
|
||||
%NameWarning.tooltip_text = "Name is already in use!"
|
||||
else:
|
||||
%NameWarning.hide()
|
||||
|
||||
|
||||
func _on_name_text_submitted(new_text:String) -> void:
|
||||
if %NameWarning.visible:
|
||||
new_text = current_mood
|
||||
%NameWarning.hide()
|
||||
else:
|
||||
%MoodList.set_item_text(%MoodList.get_selected_items()[0], new_text)
|
||||
current_moods_info.erase(current_mood)
|
||||
current_moods_info[new_text] = get_mood_info()
|
||||
current_mood = new_text
|
||||
|
||||
|
||||
func _on_name_focus_exited() -> void:
|
||||
_on_name_text_submitted(%Name.text)
|
||||
|
||||
|
||||
func _on_default_toggled(button_pressed:bool) -> void:
|
||||
if button_pressed:
|
||||
default_mood = current_mood
|
||||
else:
|
||||
default_mood = ''
|
||||
set_default_button(button_pressed)
|
||||
|
||||
|
||||
func set_default_button(enabled:bool) -> void:
|
||||
%Default.set_pressed_no_signal(enabled)
|
||||
if enabled:
|
||||
%Default.icon = get_theme_icon("Favorites", "EditorIcons")
|
||||
else:
|
||||
%Default.icon = get_theme_icon("NonFavorite", "EditorIcons")
|
||||
|
||||
|
||||
func preview() -> void:
|
||||
$Preview.load_overwrite(get_mood_info())
|
||||
var preview_timer := Timer.new()
|
||||
DialogicUtil.update_timer_process_callback(preview_timer)
|
||||
add_child(preview_timer)
|
||||
preview_timer.start(ProjectSettings.get_setting('dialogic/text/letter_speed', 0.01))
|
||||
|
||||
for i in range(20):
|
||||
$Preview._on_continued_revealing_text("a")
|
||||
await preview_timer.timeout
|
||||
|
||||
preview_timer.queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bnvr6kitj8u3j
|
||||
@@ -0,0 +1,235 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://8ad1pwbjuqpt"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/character_settings/character_moods_settings.gd" id="1_3px07"]
|
||||
[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_e1vyd"]
|
||||
[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="3_yjcns"]
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="5_yscws"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y7t05"]
|
||||
content_margin_left = 10.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 10.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
corner_radius_top_left = 20
|
||||
corner_radius_top_right = 20
|
||||
|
||||
[sub_resource type="Image" id="Image_ylh4a"]
|
||||
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",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_drtd2"]
|
||||
image = SubResource("Image_ylh4a")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_40fkd"]
|
||||
content_margin_top = 10.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(1, 1, 1, 0.0588235)
|
||||
border_width_left = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_bottom_right = 10
|
||||
corner_radius_bottom_left = 10
|
||||
|
||||
[node name="Typing Sounds" type="VBoxContainer"]
|
||||
offset_right = 443.0
|
||||
offset_bottom = 144.0
|
||||
script = ExtResource("1_3px07")
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="ListPanel" type="PanelContainer" parent="VBox"]
|
||||
unique_name_in_owner = true
|
||||
self_modulate = Color(0, 0, 0, 1)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_y7t05")
|
||||
|
||||
[node name="Vbox" type="VBoxContainer" parent="VBox/ListPanel"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBox/ListPanel/Vbox"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="Add" type="Button" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Add type sound mood"
|
||||
icon = SubResource("ImageTexture_drtd2")
|
||||
|
||||
[node name="Duplicate" type="Button" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Duplicate"
|
||||
icon = SubResource("ImageTexture_drtd2")
|
||||
|
||||
[node name="Delete" type="Button" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Delete mood"
|
||||
icon = SubResource("ImageTexture_drtd2")
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Play" type="Button" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Preview"
|
||||
icon = SubResource("ImageTexture_drtd2")
|
||||
|
||||
[node name="Default" type="Button" parent="VBox/ListPanel/Vbox/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Default"
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_drtd2")
|
||||
|
||||
[node name="MoodList" type="ItemList" parent="VBox/ListPanel/Vbox"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Settings" type="PanelContainer" parent="VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_40fkd")
|
||||
|
||||
[node name="Grid" type="GridContainer" parent="VBox/Settings"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Name:"
|
||||
|
||||
[node name="Name" type="LineEdit" parent="VBox/Settings/Grid"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
tooltip_text = "Mood name"
|
||||
text = "New Mood"
|
||||
placeholder_text = "Enter Mood Name"
|
||||
caret_blink = true
|
||||
|
||||
[node name="NameWarning" type="TextureRect" parent="VBox/Settings/Grid/Name"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 11
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -31.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
texture = SubResource("ImageTexture_drtd2")
|
||||
stretch_mode = 3
|
||||
|
||||
[node name="Label6" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Mode:"
|
||||
|
||||
[node name="Mode" type="OptionButton" parent="VBox/Settings/Grid"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Interrupt = The next sound will stop the previous
|
||||
Overlap = Multiple sounds may play at once
|
||||
Await = A sound will only be played if the previous has finished"
|
||||
item_count = 3
|
||||
selected = 0
|
||||
popup/item_0/text = "Interrupt"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Overlap"
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Await"
|
||||
popup/item_2/id = 2
|
||||
|
||||
[node name="Label4" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "File/Folder:"
|
||||
|
||||
[node name="SoundFolder" parent="VBox/Settings/Grid" instance=ExtResource("2_e1vyd")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
file_filter = "*.ogg, *.mp3, *.wav"
|
||||
file_mode = 3
|
||||
|
||||
[node name="Label2" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Pitch:"
|
||||
|
||||
[node name="Pitch" type="HBoxContainer" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = -6
|
||||
alignment = 2
|
||||
|
||||
[node name="PitchBase" parent="VBox/Settings/Grid/Pitch" instance=ExtResource("3_yjcns")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
enforce_step = false
|
||||
max = 4.0
|
||||
|
||||
[node name="Label4" type="Label" parent="VBox/Settings/Grid/Pitch"]
|
||||
layout_mode = 2
|
||||
text = "+/- "
|
||||
|
||||
[node name="PitchVariance" parent="VBox/Settings/Grid/Pitch" instance=ExtResource("3_yjcns")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
enforce_step = false
|
||||
|
||||
[node name="Label3" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Volume:"
|
||||
|
||||
[node name="Volume" type="HBoxContainer" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = -6
|
||||
alignment = 2
|
||||
|
||||
[node name="VolumeBase" parent="VBox/Settings/Grid/Volume" instance=ExtResource("3_yjcns")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
min = -60.0
|
||||
max = 30.0
|
||||
|
||||
[node name="Label4" type="Label" parent="VBox/Settings/Grid/Volume"]
|
||||
layout_mode = 2
|
||||
text = "+/- "
|
||||
|
||||
[node name="VolumeVariance" parent="VBox/Settings/Grid/Volume" instance=ExtResource("3_yjcns")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label5" type="Label" parent="VBox/Settings/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Skip:"
|
||||
|
||||
[node name="Skip" parent="VBox/Settings/Grid" instance=ExtResource("3_yjcns")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
step = 1.0
|
||||
|
||||
[node name="Preview" type="AudioStreamPlayer" parent="."]
|
||||
script = ExtResource("5_yscws")
|
||||
play_every_character = 0
|
||||
|
||||
[connection signal="pressed" from="VBox/ListPanel/Vbox/HBoxContainer/Add" to="." method="_on_add_pressed"]
|
||||
[connection signal="pressed" from="VBox/ListPanel/Vbox/HBoxContainer/Duplicate" to="." method="_on_duplicate_pressed"]
|
||||
[connection signal="pressed" from="VBox/ListPanel/Vbox/HBoxContainer/Delete" to="." method="_on_delete_pressed"]
|
||||
[connection signal="pressed" from="VBox/ListPanel/Vbox/HBoxContainer/Play" to="." method="preview"]
|
||||
[connection signal="toggled" from="VBox/ListPanel/Vbox/HBoxContainer/Default" to="." method="_on_default_toggled"]
|
||||
[connection signal="item_selected" from="VBox/ListPanel/Vbox/MoodList" to="." method="_on_mood_list_item_selected"]
|
||||
[connection signal="focus_exited" from="VBox/Settings/Grid/Name" to="." method="_on_name_focus_exited"]
|
||||
[connection signal="text_changed" from="VBox/Settings/Grid/Name" to="." method="_on_name_text_changed"]
|
||||
[connection signal="text_submitted" from="VBox/Settings/Grid/Name" to="." method="_on_name_text_submitted"]
|
||||
@@ -0,0 +1,37 @@
|
||||
@tool
|
||||
extends DialogicCharacterEditorPortraitSection
|
||||
|
||||
|
||||
func _get_title() -> String:
|
||||
return "Typing Sound Mood"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
%PortraitMood.get_suggestions_func = mood_suggestions
|
||||
%PortraitMood.resource_icon = get_theme_icon("AudioStreamPlayer", "EditorIcons")
|
||||
|
||||
|
||||
func _load_portrait_data(data:Dictionary):
|
||||
%PortraitMood.set_value(data.get('sound_mood'))
|
||||
|
||||
|
||||
func update_visibility(show:=true):
|
||||
if !show:
|
||||
hide()
|
||||
get_parent().get_child(get_index()-1).hide()
|
||||
get_parent().get_child(get_index()+1).hide()
|
||||
else:
|
||||
get_parent().get_child(get_index()-1).show()
|
||||
|
||||
|
||||
func _on_portrait_mood_value_changed(property_name:String, value:String):
|
||||
var data: Dictionary = selected_item.get_metadata(0)
|
||||
data['sound_mood'] = value
|
||||
changed.emit()
|
||||
|
||||
|
||||
func mood_suggestions(filter:String) -> Dictionary:
|
||||
var suggestions := {}
|
||||
for mood in character_editor.get_settings_section_by_name('Typing Sounds').current_moods_info:
|
||||
suggestions[mood] = {'value':mood}
|
||||
return suggestions
|
||||
@@ -0,0 +1 @@
|
||||
uid://cvcjnwoogv8ls
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bvfiv5uhmkqq7"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/character_settings/character_portrait_mood_settings.gd" id="1_5ni5u"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="1_oggvu"]
|
||||
|
||||
[node name="Typing Sound Mood" type="HBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_5ni5u")
|
||||
|
||||
[node name="PortraitMoodLabel" type="Label" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Sound Mood:"
|
||||
|
||||
[node name="PortraitMood" parent="." instance=ExtResource("1_oggvu")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Select Mood"
|
||||
|
||||
[connection signal="value_changed" from="PortraitMood" to="." method="_on_portrait_mood_value_changed"]
|
||||
498
addons/dialogic/Modules/Text/event_text.gd
Normal file
498
addons/dialogic/Modules/Text/event_text.gd
Normal file
@@ -0,0 +1,498 @@
|
||||
@tool
|
||||
class_name DialogicTextEvent
|
||||
extends DialogicEvent
|
||||
|
||||
## 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.
|
||||
## That means you can use bbcode, but also some custom commands.
|
||||
var text := ""
|
||||
## If this is not null, the given character (as a resource) will be associated with this event.
|
||||
## The DialogicNode_NameLabel will show the characters display_name. If a typing sound is setup,
|
||||
## it will play.
|
||||
var character: DialogicCharacter = null
|
||||
## If a character is set, this setting can change the portrait of that character.
|
||||
var portrait := ""
|
||||
|
||||
### Helpers
|
||||
|
||||
## Used to set the character resource from the unique name identifier and vice versa
|
||||
var character_identifier: String:
|
||||
get:
|
||||
if character:
|
||||
var identifier := DialogicResourceUtil.get_unique_identifier(character.resource_path)
|
||||
if not identifier.is_empty():
|
||||
return identifier
|
||||
return character_identifier
|
||||
set(value):
|
||||
character_identifier = value
|
||||
character = DialogicResourceUtil.get_character_resource(value)
|
||||
if not character.portraits.has(portrait):
|
||||
portrait = ""
|
||||
ui_update_needed.emit()
|
||||
|
||||
var regex := RegEx.create_from_string(r'\s*((")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*(?<portrait>\(.*\)))?\s*(?<!\\):)?(?<text>(.|\n)*)')
|
||||
var split_regex := RegEx.create_from_string(r"((\[n\]|\[n\+\])?((?!(\[n\]|\[n\+\]))(.|\n))+)")
|
||||
|
||||
enum States {REVEALING, IDLE, DONE}
|
||||
var state := States.IDLE
|
||||
signal advance
|
||||
|
||||
|
||||
#region EXECUTION
|
||||
################################################################################
|
||||
|
||||
func _clear_state() -> void:
|
||||
dialogic.current_state_info.erase('text_sub_idx')
|
||||
_disconnect_signals()
|
||||
|
||||
func _execute() -> void:
|
||||
if text.is_empty():
|
||||
finish()
|
||||
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 dialogic.current_state_info.get('base_style') != dialogic.current_state_info.get('style'):
|
||||
dialogic.Styles.change_style(dialogic.current_state_info.get('base_style', 'Default'))
|
||||
await dialogic.get_tree().process_frame
|
||||
|
||||
var character_name_text := dialogic.Text.get_character_name_parsed(character)
|
||||
if character:
|
||||
if dialogic.has_subsystem('Styles') and character.custom_info.get('style', null):
|
||||
dialogic.Styles.change_style(character.custom_info.style, false)
|
||||
await dialogic.get_tree().process_frame
|
||||
|
||||
|
||||
if portrait and dialogic.has_subsystem('Portraits') and dialogic.Portraits.is_character_joined(character):
|
||||
dialogic.Portraits.change_character_portrait(character, portrait)
|
||||
dialogic.Portraits.change_speaker(character, portrait)
|
||||
var check_portrait: String = portrait if !portrait.is_empty() else dialogic.current_state_info['portraits'].get(character.resource_path, {}).get('portrait', '')
|
||||
|
||||
if check_portrait and character.portraits.get(check_portrait, {}).get('sound_mood', '') in character.custom_info.get('sound_moods', {}):
|
||||
dialogic.Text.update_typing_sound_mood(character.custom_info.get('sound_moods', {}).get(character.portraits[check_portrait].get('sound_mood', {}), {}))
|
||||
elif !character.custom_info.get('sound_mood_default', '').is_empty():
|
||||
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)
|
||||
dialogic.Text.update_name_label(null)
|
||||
dialogic.Text.update_typing_sound_mood()
|
||||
|
||||
_connect_signals()
|
||||
|
||||
var final_text: String = get_property_translated('text')
|
||||
if ProjectSettings.get_setting('dialogic/text/split_at_new_lines', false):
|
||||
match ProjectSettings.get_setting('dialogic/text/split_at_new_lines_as', 0):
|
||||
0:
|
||||
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+]'))
|
||||
|
||||
dialogic.current_state_info['text_sub_idx'] = dialogic.current_state_info.get('text_sub_idx', -1)
|
||||
|
||||
var reveal_next_segment: bool = dialogic.current_state_info['text_sub_idx'] == -1
|
||||
|
||||
for section_idx in range(min(max(0, dialogic.current_state_info['text_sub_idx']), len(split_text)-1), len(split_text)):
|
||||
dialogic.Inputs.block_input(ProjectSettings.get_setting('dialogic/text/text_reveal_skip_delay', 0.1))
|
||||
|
||||
if reveal_next_segment:
|
||||
dialogic.Text.hide_next_indicators()
|
||||
|
||||
dialogic.current_state_info['text_sub_idx'] = section_idx
|
||||
|
||||
var segment: String = dialogic.Text.parse_text(split_text[section_idx][0])
|
||||
var is_append: bool = split_text[section_idx][1]
|
||||
|
||||
final_text = segment
|
||||
dialogic.Text.about_to_show_text.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append})
|
||||
|
||||
await dialogic.Text.update_textbox(final_text, false)
|
||||
|
||||
state = States.REVEALING
|
||||
_try_play_current_line_voice()
|
||||
final_text = dialogic.Text.update_dialog_text(final_text, false, is_append)
|
||||
|
||||
_mark_as_read(character_name_text, final_text)
|
||||
|
||||
# We must skip text animation before we potentially return when there
|
||||
# is a Choice event.
|
||||
if dialogic.Inputs.auto_skip.enabled:
|
||||
dialogic.Text.skip_text_reveal()
|
||||
else:
|
||||
await dialogic.Text.text_finished
|
||||
|
||||
state = States.IDLE
|
||||
else:
|
||||
reveal_next_segment = true
|
||||
|
||||
# Handling potential Choice Events.
|
||||
if section_idx == len(split_text)-1 and dialogic.has_subsystem('Choices') and dialogic.Choices.is_question(dialogic.current_event_idx):
|
||||
dialogic.Text.show_next_indicators(true)
|
||||
|
||||
finish()
|
||||
return
|
||||
|
||||
elif dialogic.Inputs.auto_advance.is_enabled():
|
||||
dialogic.Text.show_next_indicators(false, true)
|
||||
dialogic.Inputs.auto_advance.start()
|
||||
else:
|
||||
dialogic.Text.show_next_indicators()
|
||||
|
||||
if section_idx == len(split_text)-1:
|
||||
state = States.DONE
|
||||
|
||||
# If Auto-Skip is enabled and there are multiple parts of this text
|
||||
# we need to skip the text after the defined time per event.
|
||||
if dialogic.Inputs.auto_skip.enabled:
|
||||
await dialogic.Inputs.start_autoskip_timer()
|
||||
|
||||
# Check if Auto-Skip is still enabled.
|
||||
if not dialogic.Inputs.auto_skip.enabled:
|
||||
await advance
|
||||
|
||||
else:
|
||||
await advance
|
||||
|
||||
|
||||
finish()
|
||||
|
||||
|
||||
func _mark_as_read(character_name_text: String, final_text: String) -> void:
|
||||
if dialogic.has_subsystem('History'):
|
||||
if character:
|
||||
dialogic.History.store_simple_history_entry(final_text, event_name, {'character':character_name_text, 'character_color':character.color})
|
||||
else:
|
||||
dialogic.History.store_simple_history_entry(final_text, event_name)
|
||||
dialogic.History.mark_event_as_visited()
|
||||
|
||||
|
||||
func _connect_signals() -> void:
|
||||
if not dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action):
|
||||
dialogic.Inputs.dialogic_action.connect(_on_dialogic_input_action)
|
||||
|
||||
dialogic.Inputs.auto_skip.toggled.connect(_on_auto_skip_enable)
|
||||
|
||||
if not dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance):
|
||||
dialogic.Inputs.auto_advance.autoadvance.connect(_on_dialogic_input_autoadvance)
|
||||
|
||||
|
||||
## If the event is done, this method can clean-up signal connections.
|
||||
func _disconnect_signals() -> void:
|
||||
if dialogic.Inputs.dialogic_action.is_connected(_on_dialogic_input_action):
|
||||
dialogic.Inputs.dialogic_action.disconnect(_on_dialogic_input_action)
|
||||
if dialogic.Inputs.auto_advance.autoadvance.is_connected(_on_dialogic_input_autoadvance):
|
||||
dialogic.Inputs.auto_advance.autoadvance.disconnect(_on_dialogic_input_autoadvance)
|
||||
if dialogic.Inputs.auto_skip.toggled.is_connected(_on_auto_skip_enable):
|
||||
dialogic.Inputs.auto_skip.toggled.disconnect(_on_auto_skip_enable)
|
||||
|
||||
|
||||
## Tries to play the voice clip for the current line.
|
||||
func _try_play_current_line_voice() -> void:
|
||||
# If Auto-Skip is enabled and we skip voice clips, we don't want to play.
|
||||
if (dialogic.Inputs.auto_skip.enabled
|
||||
and dialogic.Inputs.auto_skip.skip_voice):
|
||||
return
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
func _on_dialogic_input_action() -> void:
|
||||
match state:
|
||||
States.REVEALING:
|
||||
if dialogic.Text.is_text_reveal_skippable():
|
||||
dialogic.Text.skip_text_reveal()
|
||||
dialogic.Inputs.stop_timers()
|
||||
_:
|
||||
if dialogic.Inputs.manual_advance.is_enabled():
|
||||
advance.emit()
|
||||
dialogic.Inputs.stop_timers()
|
||||
|
||||
|
||||
func _on_dialogic_input_autoadvance() -> void:
|
||||
if state == States.IDLE or state == States.DONE:
|
||||
advance.emit()
|
||||
|
||||
|
||||
func _on_auto_skip_enable(enabled: bool) -> void:
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
match state:
|
||||
States.DONE:
|
||||
await dialogic.Inputs.start_autoskip_timer()
|
||||
|
||||
# If Auto-Skip is still enabled, advance the text.
|
||||
if dialogic.Inputs.auto_skip.enabled:
|
||||
advance.emit()
|
||||
|
||||
States.REVEALING:
|
||||
dialogic.Text.skip_text_reveal()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region INITIALIZE
|
||||
################################################################################
|
||||
|
||||
func _init() -> void:
|
||||
event_name = "Text"
|
||||
set_default_color('Color1')
|
||||
event_category = "Main"
|
||||
event_sorting_index = 0
|
||||
expand_by_default = true
|
||||
help_page_path = "https://docs.dialogic.pro/writing-text-events.html"
|
||||
|
||||
|
||||
|
||||
################################################################################
|
||||
## SAVING/LOADING
|
||||
################################################################################
|
||||
|
||||
func to_text() -> String:
|
||||
var result := text.replace('\n', '\\\n')
|
||||
result = result.replace(':', '\\:')
|
||||
if result.is_empty():
|
||||
result = "<Empty Text Event>"
|
||||
|
||||
if character:
|
||||
var name := DialogicResourceUtil.get_unique_identifier(character.resource_path)
|
||||
if name.count(" ") > 0:
|
||||
name = '"' + name + '"'
|
||||
if not portrait.is_empty():
|
||||
result = name+" ("+portrait+"): "+result
|
||||
else:
|
||||
result = name+": "+result
|
||||
for event in DialogicResourceUtil.get_event_cache():
|
||||
if not event is DialogicTextEvent and event.is_valid_event(result):
|
||||
result = '\\'+result
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func from_text(string:String) -> void:
|
||||
# Load default character
|
||||
# This is only of relevance if the default has been overriden (usually not)
|
||||
character = DialogicResourceUtil.get_character_resource(character_identifier)
|
||||
|
||||
var result := regex.search(string.trim_prefix('\\'))
|
||||
if result and not result.get_string('name').is_empty():
|
||||
var name := result.get_string('name').strip_edges()
|
||||
|
||||
if name == '_':
|
||||
character = null
|
||||
else:
|
||||
character = DialogicResourceUtil.get_character_resource(name)
|
||||
|
||||
if character == null and Engine.is_editor_hint() == false:
|
||||
character = DialogicCharacter.new()
|
||||
character.display_name = name
|
||||
character.resource_path = "user://"+name+".dch"
|
||||
DialogicResourceUtil.add_resource_to_directory(character.resource_path, DialogicResourceUtil.get_character_directory())
|
||||
|
||||
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 == '<Empty Text Event>':
|
||||
text = ""
|
||||
|
||||
|
||||
func is_valid_event(_string:String) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func is_string_full_event(string:String) -> bool:
|
||||
return !string.ends_with('\\')
|
||||
|
||||
|
||||
# this is only here to provide a list of default values
|
||||
# this way the module manager can add custom default overrides to this event.
|
||||
func get_shortcode_parameters() -> Dictionary:
|
||||
return {
|
||||
#param_name : property_info
|
||||
"character" : {"property": "character_identifier", "default": ""},
|
||||
"portrait" : {"property": "portrait", "default": ""},
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region TRANSLATIONS
|
||||
################################################################################
|
||||
|
||||
func _get_translatable_properties() -> Array:
|
||||
return ['text']
|
||||
|
||||
|
||||
func _get_property_original_translation(property:String) -> String:
|
||||
match property:
|
||||
'text':
|
||||
return text
|
||||
return ''
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region EVENT EDITOR
|
||||
################################################################################
|
||||
|
||||
func _enter_visual_editor(editor:DialogicEditor):
|
||||
editor.opened.connect(func(): ui_update_needed.emit())
|
||||
|
||||
|
||||
func build_event_editor() -> void:
|
||||
add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS,
|
||||
{'file_extension' : '.dch',
|
||||
'mode' : 2,
|
||||
'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.DYNAMIC_OPTIONS,
|
||||
{'suggestions_func' : get_portrait_suggestions,
|
||||
'placeholder' : "(Don't change)",
|
||||
'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg"),
|
||||
'collapse_when_empty': true,},
|
||||
'should_show_portrait_selector()')
|
||||
add_body_edit('text', ValueType.MULTILINE_TEXT, {'autofocus':true})
|
||||
|
||||
|
||||
func should_show_portrait_selector() -> bool:
|
||||
return character and not character.portraits.is_empty() and not character.portraits.size() == 1
|
||||
|
||||
|
||||
func do_any_characters_exist() -> bool:
|
||||
return not DialogicResourceUtil.get_character_directory().is_empty()
|
||||
|
||||
|
||||
func get_character_suggestions(search_text:String) -> Dictionary:
|
||||
return DialogicUtil.get_character_suggestions(search_text, character, true, false, editor_node)
|
||||
|
||||
|
||||
func get_portrait_suggestions(search_text:String) -> Dictionary:
|
||||
return DialogicUtil.get_portrait_suggestions(search_text, character, true, "Don't change")
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region CODE COMPLETION
|
||||
################################################################################
|
||||
|
||||
var completion_text_character_getter_regex := RegEx.new()
|
||||
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 completion_character := completion_text_character_getter_regex.search(line).get_string().trim_prefix('"').trim_suffix('"')
|
||||
CodeCompletionHelper.suggest_portraits(TextNode, completion_character)
|
||||
|
||||
if symbol == '[':
|
||||
suggest_bbcode(TextNode)
|
||||
for effect in completion_text_effects.values():
|
||||
if effect.get('arg', false):
|
||||
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, effect.command, effect.command+'=', TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"))
|
||||
else:
|
||||
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 completion_character := completion_text_character_getter_regex.search(line).get_string('name')
|
||||
CodeCompletionHelper.suggest_portraits(TextNode, completion_character, ']')
|
||||
|
||||
|
||||
func _get_start_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
|
||||
CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_CLASS, true)
|
||||
|
||||
|
||||
func suggest_bbcode(TextNode:CodeEdit):
|
||||
for i in [['b (bold)', 'b'], ['i (italics)', 'i'], ['color', 'color='], ['font size','font_size=']]:
|
||||
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"),)
|
||||
TextNode.add_code_completion_option(CodeEdit.KIND_CLASS, 'end '+i[0], '/'+i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("RichTextEffect", "EditorIcons"), ']')
|
||||
for i in [['new event', 'n'],['new event (same box)', 'n+']]:
|
||||
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i[0], i[1], TextNode.syntax_highlighter.normal_color, TextNode.get_theme_icon("ArrowRight", "EditorIcons"),)
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region SYNTAX HIGHLIGHTING
|
||||
################################################################################
|
||||
|
||||
var text_effects := ""
|
||||
var text_effects_regex := RegEx.new()
|
||||
func load_text_effects() -> void:
|
||||
if text_effects.is_empty():
|
||||
for idx in DialogicUtil.get_indexers():
|
||||
for effect in idx._get_text_effects():
|
||||
text_effects+= effect['command']+'|'
|
||||
text_effects += "b|i|u|s|code|p|center|left|right|fill|n\\+|n|indent|url|img|font|font_size|opentype_features|color|bg_color|fg_color|outline_size|outline_color|table|cell|ul|ol|lb|rb|br"
|
||||
if text_effects_regex.get_pattern().is_empty():
|
||||
text_effects_regex.compile("(?<!\\\\)\\[\\s*/?(?<command>"+text_effects+")\\s*(=\\s*(?<value>.+?)\\s*)?\\]")
|
||||
|
||||
|
||||
var text_random_word_regex := RegEx.new()
|
||||
var text_effect_color := Color('#898276')
|
||||
func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
|
||||
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
|
||||
if Highlighter.mode == Highlighter.Modes.FULL_HIGHLIGHTING:
|
||||
if result.get_string('name'):
|
||||
dict[result.get_start('name')] = {"color":Highlighter.character_name_color}
|
||||
dict[result.get_end('name')] = {"color":Highlighter.normal_color}
|
||||
if result.get_string('portrait'):
|
||||
dict[result.get_start('portrait')] = {"color":Highlighter.character_portrait_color}
|
||||
dict[result.get_end('portrait')] = {"color":Highlighter.normal_color}
|
||||
if result.get_string('text'):
|
||||
var effects_result := text_effects_regex.search_all(line)
|
||||
for eff in effects_result:
|
||||
dict[eff.get_start()] = {"color":text_effect_color}
|
||||
dict[eff.get_end()] = {"color":Highlighter.normal_color}
|
||||
dict = Highlighter.color_region(dict, Highlighter.variable_color, line, '{', '}', result.get_start('text'))
|
||||
|
||||
for replace_mod_match in text_random_word_regex.search_all(result.get_string('text')):
|
||||
var color: Color = Highlighter.string_color
|
||||
color = color.lerp(Highlighter.normal_color, 0.4)
|
||||
dict[replace_mod_match.get_start()+result.get_start('text')] = {'color':Highlighter.string_color}
|
||||
var offset := 1
|
||||
for b in replace_mod_match.get_string().trim_suffix('>').trim_prefix('<').split('/'):
|
||||
color.h = wrap(color.h+0.2, 0, 1)
|
||||
dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':color}
|
||||
offset += len(b)
|
||||
dict[replace_mod_match.get_start()+result.get_start('text')+offset] = {'color':Highlighter.string_color}
|
||||
offset += 1
|
||||
dict[replace_mod_match.get_end()+result.get_start('text')] = {'color':Highlighter.normal_color}
|
||||
return dict
|
||||
|
||||
#endregion
|
||||
1
addons/dialogic/Modules/Text/event_text.gd.uid
Normal file
1
addons/dialogic/Modules/Text/event_text.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dwf2f3cc4k5rk
|
||||
3
addons/dialogic/Modules/Text/icon.svg
Normal file
3
addons/dialogic/Modules/Text/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25.4118 7H6.58824C6.26336 7 6 7.24895 6 7.55604V20.3096C6 20.616 6.26228 20.8647 6.58645 20.8656L18.3547 20.8993C18.6789 20.9002 18.9412 21.1489 18.9412 21.4553V24.4428C18.9412 24.9541 19.6097 25.1944 19.9691 24.8123L23.4755 21.0835C23.5848 20.9672 23.7407 20.8995 23.9053 20.8969L25.4216 20.873C25.7426 20.868 26 20.6205 26 20.3171V7.55604C26 7.24895 25.7366 7 25.4118 7Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
44
addons/dialogic/Modules/Text/icon.svg.import
Normal file
44
addons/dialogic/Modules/Text/icon.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3m5lcfk68rid"
|
||||
path="res://.godot/imported/icon.svg-e2501ddcdef01c7f2c3538940f5abfc1.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Modules/Text/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-e2501ddcdef01c7f2c3538940f5abfc1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
38
addons/dialogic/Modules/Text/index.gd
Normal file
38
addons/dialogic/Modules/Text/index.gd
Normal file
@@ -0,0 +1,38 @@
|
||||
@tool
|
||||
extends DialogicIndexer
|
||||
|
||||
|
||||
func _get_events() -> Array:
|
||||
return [this_folder.path_join('event_text.gd')]
|
||||
|
||||
|
||||
func _get_subsystems() -> Array:
|
||||
return [{'name':'Text', 'script':this_folder.path_join('subsystem_text.gd')}]
|
||||
|
||||
|
||||
func _get_settings_pages() -> Array:
|
||||
return [this_folder.path_join('settings_text.tscn')]
|
||||
|
||||
|
||||
func _get_character_editor_sections() -> Array:
|
||||
return [this_folder.path_join('character_settings/character_moods_settings.tscn'),
|
||||
this_folder.path_join('character_settings/character_portrait_mood_settings.tscn'),
|
||||
]
|
||||
|
||||
|
||||
func _get_text_effects() -> Array[Dictionary]:
|
||||
return [
|
||||
{'command':'speed', 'subsystem':'Text', 'method':'effect_speed', 'arg':true},
|
||||
{'command':'lspeed', 'subsystem':'Text', 'method':'effect_lspeed', 'arg':true},
|
||||
{'command':'pause', 'subsystem':'Text', 'method':'effect_pause', 'arg':true},
|
||||
{'command':'signal', 'subsystem':'Text', 'method':'effect_signal', 'arg':true},
|
||||
{'command':'mood', 'subsystem':'Text', 'method':'effect_mood', 'arg':true},
|
||||
]
|
||||
|
||||
|
||||
func _get_text_modifiers() -> Array[Dictionary]:
|
||||
return [
|
||||
{'subsystem':'Text', 'method':'modifier_autopauses'},
|
||||
{'subsystem':'Text', 'method':'modifier_random_selection', 'mode':-1},
|
||||
{'subsystem':'Text', 'method':"modifier_break", 'command':'br', 'mode':-1},
|
||||
]
|
||||
1
addons/dialogic/Modules/Text/index.gd.uid
Normal file
1
addons/dialogic/Modules/Text/index.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bamb4snmw0lbv
|
||||
60
addons/dialogic/Modules/Text/manual_advance.gd
Normal file
60
addons/dialogic/Modules/Text/manual_advance.gd
Normal file
@@ -0,0 +1,60 @@
|
||||
extends RefCounted
|
||||
class_name DialogicManualAdvance
|
||||
## This class holds the settings for the Manual-Advance feature.
|
||||
## Changing the variables will alter the behaviour of manually advancing
|
||||
## the timeline, e.g. using the input action.
|
||||
|
||||
## The key giving access to the state info of Manual-Advance.
|
||||
const STATE_INFO_KEY := "manual_advance"
|
||||
## The key for the enabled state in the current state info.
|
||||
const ENABLED_STATE_KEY := "enabled"
|
||||
## The key for the temporary event state in the current state info.
|
||||
const DISABLED_UNTIL_NEXT_EVENT_STATE_KEY := "temp_disabled"
|
||||
|
||||
|
||||
## If `true`, Manual-Advance will be deactivated until the next event.
|
||||
##
|
||||
## Use this flag to create a temporary Manual-Advance block.
|
||||
##
|
||||
## Overrides [variable system_enabled] when true.
|
||||
var disabled_until_next_event := false :
|
||||
set(enabled):
|
||||
disabled_until_next_event = enabled
|
||||
DialogicUtil.autoload().current_state_info[STATE_INFO_KEY][DISABLED_UNTIL_NEXT_EVENT_STATE_KEY] = enabled
|
||||
|
||||
|
||||
## If `true`, Manual-Advance will stay enabled until this is set to `false`.
|
||||
##
|
||||
## Use this flag to activate or disable Manual-Advance mode.
|
||||
##
|
||||
## Can be temporarily overwritten by [variable disabled_until_next_event].
|
||||
var system_enabled := true :
|
||||
set(enabled):
|
||||
system_enabled = enabled
|
||||
DialogicUtil.autoload().current_state_info[STATE_INFO_KEY][ENABLED_STATE_KEY] = enabled
|
||||
|
||||
|
||||
## Checks if the current state info has the Manual-Advance settings.
|
||||
## If not, populates the current state info with the default settings.
|
||||
func _init() -> void:
|
||||
if DialogicUtil.autoload().current_state_info.has(STATE_INFO_KEY):
|
||||
var state_info := DialogicUtil.autoload().current_state_info
|
||||
var manual_advance: Dictionary = state_info[STATE_INFO_KEY]
|
||||
|
||||
disabled_until_next_event = manual_advance.get(DISABLED_UNTIL_NEXT_EVENT_STATE_KEY, disabled_until_next_event)
|
||||
system_enabled = manual_advance.get(ENABLED_STATE_KEY, system_enabled)
|
||||
|
||||
else:
|
||||
DialogicUtil.autoload().current_state_info[STATE_INFO_KEY] = {
|
||||
ENABLED_STATE_KEY: system_enabled,
|
||||
DISABLED_UNTIL_NEXT_EVENT_STATE_KEY: disabled_until_next_event,
|
||||
}
|
||||
|
||||
|
||||
#region MANUAL ADVANCE HELPERS
|
||||
|
||||
## Whether the player can use Manual-Advance to advance the timeline.
|
||||
func is_enabled() -> bool:
|
||||
return system_enabled and not disabled_until_next_event
|
||||
|
||||
#endregion
|
||||
1
addons/dialogic/Modules/Text/manual_advance.gd.uid
Normal file
1
addons/dialogic/Modules/Text/manual_advance.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://co1m4ypooxigm
|
||||
158
addons/dialogic/Modules/Text/node_dialog_text.gd
Normal file
158
addons/dialogic/Modules/Text/node_dialog_text.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
@icon("node_dialog_text_icon.svg")
|
||||
class_name DialogicNode_DialogText
|
||||
extends RichTextLabel
|
||||
|
||||
## Dialogic node that can reveal text at a given (changeable speed).
|
||||
|
||||
signal started_revealing_text()
|
||||
signal continued_revealing_text(new_character : String)
|
||||
signal finished_revealing_text()
|
||||
enum Alignment {LEFT, CENTER, RIGHT}
|
||||
|
||||
@export var enabled := true
|
||||
@export var alignment := Alignment.LEFT
|
||||
@export var textbox_root: Node = self
|
||||
|
||||
@export var hide_when_empty := false
|
||||
@export var start_hidden := true
|
||||
|
||||
var revealing := false
|
||||
var base_visible_characters := 0
|
||||
|
||||
# The used speed per revealed character.
|
||||
# May be overwritten when syncing reveal speed to voice.
|
||||
var active_speed: float = 0.01
|
||||
|
||||
var speed_counter: float = 0
|
||||
|
||||
func _set(property: StringName, what: Variant) -> bool:
|
||||
if property == 'text' and typeof(what) == TYPE_STRING:
|
||||
|
||||
text = what
|
||||
|
||||
if hide_when_empty:
|
||||
textbox_root.visible = !what.is_empty()
|
||||
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# add to necessary
|
||||
add_to_group('dialogic_dialog_text')
|
||||
meta_hover_ended.connect(_on_meta_hover_ended)
|
||||
meta_hover_started.connect(_on_meta_hover_started)
|
||||
meta_clicked.connect(_on_meta_clicked)
|
||||
gui_input.connect(on_gui_input)
|
||||
bbcode_enabled = true
|
||||
if textbox_root == null:
|
||||
textbox_root = self
|
||||
|
||||
if start_hidden:
|
||||
textbox_root.hide()
|
||||
text = ""
|
||||
|
||||
|
||||
# this is called by the DialogicGameHandler to set text
|
||||
|
||||
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:
|
||||
text = '[right]'+text
|
||||
visible_characters = 0
|
||||
|
||||
else:
|
||||
base_visible_characters = len(text)
|
||||
visible_characters = len(get_parsed_text())
|
||||
text = text + _text
|
||||
|
||||
# If Auto-Skip is enabled and we append the text (keep_previous),
|
||||
# we can skip revealing the text and just show it all at once.
|
||||
if DialogicUtil.autoload().Inputs.auto_skip.enabled:
|
||||
visible_characters = 1
|
||||
return
|
||||
|
||||
revealing = true
|
||||
speed_counter = 0
|
||||
started_revealing_text.emit()
|
||||
|
||||
|
||||
func set_speed(delay_per_character:float) -> void:
|
||||
if DialogicUtil.autoload().Text.is_text_voice_synced() and DialogicUtil.autoload().Voice.is_running():
|
||||
var total_characters := get_total_character_count() as float
|
||||
var remaining_time: float = DialogicUtil.autoload().Voice.get_remaining_time()
|
||||
var synced_speed := remaining_time / total_characters
|
||||
active_speed = synced_speed
|
||||
|
||||
else:
|
||||
active_speed = delay_per_character
|
||||
|
||||
|
||||
## Reveals one additional character.
|
||||
func continue_reveal() -> void:
|
||||
if visible_characters <= get_total_character_count():
|
||||
revealing = false
|
||||
|
||||
var current_index := visible_characters - base_visible_characters
|
||||
await DialogicUtil.autoload().Text.execute_effects(current_index, 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:
|
||||
finish_text()
|
||||
# if the text finished organically, add a small input block
|
||||
# this prevents accidental skipping when you expected the text to be longer
|
||||
DialogicUtil.autoload().Inputs.block_input(ProjectSettings.get_setting('dialogic/text/advance_delay', 0.1))
|
||||
|
||||
|
||||
## Reveals the entire text instantly.
|
||||
func finish_text() -> void:
|
||||
visible_ratio = 1
|
||||
DialogicUtil.autoload().Text.execute_effects(-1, self, true)
|
||||
revealing = false
|
||||
DialogicUtil.autoload().current_state = DialogicGameHandler.States.IDLE
|
||||
|
||||
finished_revealing_text.emit()
|
||||
|
||||
|
||||
## Checks if the next character in the text can be revealed.
|
||||
func _process(delta: float) -> void:
|
||||
if !revealing or DialogicUtil.autoload().paused:
|
||||
return
|
||||
|
||||
speed_counter += delta
|
||||
|
||||
while speed_counter > active_speed and revealing and !DialogicUtil.autoload().paused:
|
||||
speed_counter -= active_speed
|
||||
continue_reveal()
|
||||
|
||||
|
||||
|
||||
func _on_meta_hover_started(_meta:Variant) -> void:
|
||||
DialogicUtil.autoload().Inputs.action_was_consumed = true
|
||||
|
||||
func _on_meta_hover_ended(_meta:Variant) -> void:
|
||||
DialogicUtil.autoload().Inputs.action_was_consumed = false
|
||||
|
||||
func _on_meta_clicked(_meta:Variant) -> void:
|
||||
DialogicUtil.autoload().Inputs.action_was_consumed = true
|
||||
|
||||
|
||||
## Handle mouse input
|
||||
func on_gui_input(event:InputEvent) -> void:
|
||||
DialogicUtil.autoload().Inputs.handle_node_gui_input(event)
|
||||
1
addons/dialogic/Modules/Text/node_dialog_text.gd.uid
Normal file
1
addons/dialogic/Modules/Text/node_dialog_text.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://sv8obsrlo5h4
|
||||
9
addons/dialogic/Modules/Text/node_dialog_text_icon.svg
Normal file
9
addons/dialogic/Modules/Text/node_dialog_text_icon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 3H8" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M9 3H14" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M2 6H14" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M7 9L14 9" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M2 9L6 9" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M2 12H9" stroke="#7DFFF7" stroke-width="2"/>
|
||||
<path d="M10 12H14" stroke="#7DFFF7" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bxvjpd6uwhaw7"
|
||||
path="res://.godot/imported/node_dialog_text_icon.svg-3cf845e99d76137d8156865c09f39e3e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Modules/Text/node_dialog_text_icon.svg"
|
||||
dest_files=["res://.godot/imported/node_dialog_text_icon.svg-3cf845e99d76137d8156865c09f39e3e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
21
addons/dialogic/Modules/Text/node_input.gd
Normal file
21
addons/dialogic/Modules/Text/node_input.gd
Normal file
@@ -0,0 +1,21 @@
|
||||
class_name DialogicNode_Input
|
||||
extends Control
|
||||
|
||||
## A node that handles mouse input. This allows limiting mouse input to a
|
||||
## specific region and avoiding conflicts with other UI elements.
|
||||
## If no Input node is used, the input subsystem will handle mouse input instead.
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group('dialogic_input')
|
||||
gui_input.connect(_on_gui_input)
|
||||
|
||||
|
||||
func _input(_event: InputEvent) -> void:
|
||||
if Input.is_action_pressed(ProjectSettings.get_setting('dialogic/text/input_action', 'dialogic_default_action')):
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
else:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
|
||||
func _on_gui_input(event:InputEvent) -> void:
|
||||
DialogicUtil.autoload().Inputs.handle_node_gui_input(event)
|
||||
1
addons/dialogic/Modules/Text/node_input.gd.uid
Normal file
1
addons/dialogic/Modules/Text/node_input.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dygxlmjj8cfe2
|
||||
25
addons/dialogic/Modules/Text/node_name_label.gd
Normal file
25
addons/dialogic/Modules/Text/node_name_label.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
@icon("node_name_label_icon.svg")
|
||||
extends Label
|
||||
|
||||
class_name DialogicNode_NameLabel
|
||||
|
||||
# If true, the label will be hidden if no character speaks.
|
||||
@export var hide_when_empty := true
|
||||
@export var name_label_root: Node = self
|
||||
@export var use_character_color := true
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group('dialogic_name_label')
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = false
|
||||
text = ""
|
||||
|
||||
|
||||
func _set(property, what):
|
||||
if property == 'text' and typeof(what) == TYPE_STRING:
|
||||
text = what
|
||||
if hide_when_empty:
|
||||
name_label_root.visible = !what.is_empty()
|
||||
else:
|
||||
name_label_root.show()
|
||||
return true
|
||||
1
addons/dialogic/Modules/Text/node_name_label.gd.uid
Normal file
1
addons/dialogic/Modules/Text/node_name_label.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://qkdtvkpiquha
|
||||
3
addons/dialogic/Modules/Text/node_name_label_icon.svg
Normal file
3
addons/dialogic/Modules/Text/node_name_label_icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.00003 3C5.73484 3.00006 5.48052 3.10545 5.29303 3.293L1.29303 7.293C1.10556 7.48053 1.00024 7.73484 1.00024 8C1.00024 8.26516 1.10556 8.51947 1.29303 8.707L5.29303 12.707C5.48052 12.8945 5.73484 12.9999 6.00003 13H14C14.2652 13 14.5196 12.8946 14.7071 12.7071C14.8947 12.5196 15 12.2652 15 12V4C15 3.73478 14.8947 3.48043 14.7071 3.29289C14.5196 3.10536 14.2652 3 14 3H6.00003ZM5.00003 7C5.26525 7 5.5196 7.10536 5.70714 7.29289C5.89467 7.48043 6.00003 7.73478 6.00003 8C6.00003 8.26522 5.89467 8.51957 5.70714 8.70711C5.5196 8.89464 5.26525 9 5.00003 9C4.73481 9 4.48046 8.89464 4.29292 8.70711C4.10539 8.51957 4.00003 8.26522 4.00003 8C4.00003 7.73478 4.10539 7.48043 4.29292 7.29289C4.48046 7.10536 4.73481 7 5.00003 7Z" fill="#7DFFF7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 856 B |
43
addons/dialogic/Modules/Text/node_name_label_icon.svg.import
Normal file
43
addons/dialogic/Modules/Text/node_name_label_icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://k0g15mxi14ba"
|
||||
path="res://.godot/imported/node_name_label_icon.svg-d482939b74b41ec2da6edc97e41c8bf8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Modules/Text/node_name_label_icon.svg"
|
||||
dest_files=["res://.godot/imported/node_name_label_icon.svg-d482939b74b41ec2da6edc97e41c8bf8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
89
addons/dialogic/Modules/Text/node_next_indicator.gd
Normal file
89
addons/dialogic/Modules/Text/node_next_indicator.gd
Normal file
@@ -0,0 +1,89 @@
|
||||
@icon("node_next_indicator_icon.svg")
|
||||
class_name DialogicNode_NextIndicator
|
||||
extends Control
|
||||
|
||||
## Node that is shown when the text is fully revealed.
|
||||
## The default implementation allows to set an icon and animation.
|
||||
|
||||
|
||||
@export var enabled := true
|
||||
|
||||
## If true the next indicator will also be shown if the text is a question.
|
||||
@export var show_on_questions := false
|
||||
## If true the next indicator will be shown even if dialogic will autocontinue.
|
||||
@export var show_on_autoadvance := false
|
||||
|
||||
enum Animations {BOUNCE, BLINK, NONE}
|
||||
|
||||
## What animation should the indicator do.
|
||||
@export var animation := Animations.BOUNCE
|
||||
|
||||
var texture_rect: TextureRect
|
||||
|
||||
## Set the image to use as the indicator.
|
||||
@export var texture: Texture2D = preload("res://addons/dialogic/Example Assets/next-indicator/next-indicator.png") as Texture2D:
|
||||
set(_texture):
|
||||
texture = _texture
|
||||
if texture_rect:
|
||||
texture_rect.texture = texture
|
||||
|
||||
@export var texture_size := Vector2(32,32):
|
||||
set(_texture_size):
|
||||
texture_size = _texture_size
|
||||
if has_node('Texture'):
|
||||
get_node('Texture').size = _texture_size
|
||||
get_node('Texture').position = -_texture_size
|
||||
|
||||
|
||||
var tween: Tween
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group('dialogic_next_indicator')
|
||||
|
||||
# Creating TextureRect if missing
|
||||
if not texture_rect:
|
||||
var icon := TextureRect.new()
|
||||
icon.name = 'Texture'
|
||||
icon.ignore_texture_size = true
|
||||
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon.size = texture_size
|
||||
icon.position = -icon.size
|
||||
add_child(icon)
|
||||
texture_rect = icon
|
||||
|
||||
texture_rect.texture = texture
|
||||
|
||||
hide()
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
play_animation(animation, 1.0)
|
||||
|
||||
|
||||
func play_animation(current_animation: int, time:float) -> void:
|
||||
# clean up previous tween to prevent slipping
|
||||
if tween:
|
||||
tween.stop()
|
||||
|
||||
match current_animation:
|
||||
Animations.BOUNCE:
|
||||
tween = (create_tween() as Tween)
|
||||
var distance := 4
|
||||
tween.set_parallel(false)
|
||||
tween.set_trans(Tween.TRANS_SINE)
|
||||
tween.set_ease(Tween.EASE_IN_OUT)
|
||||
tween.set_loops()
|
||||
|
||||
tween.tween_property(self, 'position', Vector2(0,distance), time*0.3).as_relative()
|
||||
tween.tween_property(self, 'position', - Vector2(0,distance), time*0.3).as_relative()
|
||||
Animations.BLINK:
|
||||
tween = (create_tween() as Tween)
|
||||
tween.set_parallel(false)
|
||||
tween.set_trans(Tween.TRANS_SINE)
|
||||
tween.set_ease(Tween.EASE_IN_OUT)
|
||||
tween.set_loops()
|
||||
|
||||
tween.tween_property(self, 'modulate:a', 0, time*0.3)
|
||||
tween.tween_property(self, 'modulate:a', 1, time*0.3)
|
||||
1
addons/dialogic/Modules/Text/node_next_indicator.gd.uid
Normal file
1
addons/dialogic/Modules/Text/node_next_indicator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ceitutnrgf6q4
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0971 4L5.00122 4C4.26622 4 3.78236 4.76633 4.09836 5.42993L7.00122 11.604C7.36317 12.3641 8.63928 12.3641 9.00122 11.604L12 5.42993C12.316 4.76633 11.8321 4 11.0971 4Z" stroke="#7DFFF7" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 321 B |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ekyju8t2e3we"
|
||||
path="res://.godot/imported/node_next_indicator_icon.svg-06167a3b3e290941c1c34386ebd60af8.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Modules/Text/node_next_indicator_icon.svg"
|
||||
dest_files=["res://.godot/imported/node_next_indicator_icon.svg-06167a3b3e290941c1c34386ebd60af8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
138
addons/dialogic/Modules/Text/node_type_sound.gd
Normal file
138
addons/dialogic/Modules/Text/node_type_sound.gd
Normal file
@@ -0,0 +1,138 @@
|
||||
@tool
|
||||
class_name DialogicNode_TypeSounds
|
||||
extends AudioStreamPlayer
|
||||
|
||||
## Node that allows playing sounds when text characters are revealed.
|
||||
## Should be the child of a DialogicNode_DialogText node!
|
||||
|
||||
## Usefull if you want to change the sounds of different node's sounds
|
||||
@export var enabled := true
|
||||
enum Modes {INTERRUPT, OVERLAP, AWAIT}
|
||||
## If true, interrupts the current sound to play a new one
|
||||
@export var mode := Modes.INTERRUPT
|
||||
## Array of sounds. Will pick a random one each time.
|
||||
@export var sounds: Array[AudioStream] = []
|
||||
## A sound to be played as the last sound.
|
||||
@export var end_sound:AudioStream
|
||||
## Determines how many characters are between each sound. Default is 1 for playing it every character.
|
||||
@export var play_every_character := 1
|
||||
## Allows changing the pitch by a random value from (pitch - pitch_variance) to (pitch + pitch_variance)
|
||||
@export_range(0, 3, 0.01) var pitch_variance := 0.0
|
||||
## Allows changing the volume by a random value from (volume - volume_variance) to (volume + volume_variance)
|
||||
@export_range(0, 10, 0.01) var volume_variance := 0.0
|
||||
## Characters that don't increase the 'characters_since_last_sound' variable, useful for the space or fullstop
|
||||
@export var ignore_characters: String = ' .,'
|
||||
|
||||
var characters_since_last_sound: int = 0
|
||||
var base_pitch: float = pitch_scale
|
||||
var base_volume: float = volume_db
|
||||
var RNG := RandomNumberGenerator.new()
|
||||
|
||||
var current_overwrite_data := {}
|
||||
|
||||
func _ready() -> void:
|
||||
# add to necessary group
|
||||
add_to_group('dialogic_type_sounds')
|
||||
|
||||
if bus == "Master":
|
||||
bus = ProjectSettings.get_setting("dialogic/audio/type_sound_bus", "Master")
|
||||
|
||||
if !Engine.is_editor_hint() and get_parent() is DialogicNode_DialogText:
|
||||
get_parent().started_revealing_text.connect(_on_started_revealing_text)
|
||||
get_parent().continued_revealing_text.connect(_on_continued_revealing_text)
|
||||
get_parent().finished_revealing_text.connect(_on_finished_revealing_text)
|
||||
|
||||
|
||||
func _on_started_revealing_text() -> void:
|
||||
if !enabled or (get_parent() is DialogicNode_DialogText and !get_parent().enabled):
|
||||
return
|
||||
characters_since_last_sound = current_overwrite_data.get('skip_characters', play_every_character-1)+1
|
||||
|
||||
|
||||
func _on_continued_revealing_text(new_character:String) -> void:
|
||||
if !enabled or (get_parent() is DialogicNode_DialogText and !get_parent().enabled):
|
||||
return
|
||||
|
||||
# We don't want to play type sounds if Auto-Skip is enabled.
|
||||
if !Engine.is_editor_hint() and DialogicUtil.autoload().Inputs.auto_skip.enabled:
|
||||
return
|
||||
|
||||
# don't play if a voice-track is running
|
||||
if !Engine.is_editor_hint() and get_parent() is DialogicNode_DialogText:
|
||||
if DialogicUtil.autoload().has_subsystem("Voice") and DialogicUtil.autoload().Voice.is_running():
|
||||
return
|
||||
|
||||
# if sound playing and can't interrupt
|
||||
if playing and current_overwrite_data.get('mode', mode) == Modes.AWAIT:
|
||||
return
|
||||
|
||||
# if no sounds were given
|
||||
if current_overwrite_data.get('sounds', sounds).size() == 0:
|
||||
return
|
||||
|
||||
# if the new character is not allowed
|
||||
if new_character in ignore_characters:
|
||||
return
|
||||
|
||||
characters_since_last_sound += 1
|
||||
if characters_since_last_sound < current_overwrite_data.get('skip_characters', play_every_character-1)+1:
|
||||
return
|
||||
|
||||
characters_since_last_sound = 0
|
||||
|
||||
var audio_player: AudioStreamPlayer = self
|
||||
if current_overwrite_data.get('mode', mode) == Modes.OVERLAP:
|
||||
audio_player = AudioStreamPlayer.new()
|
||||
audio_player.bus = bus
|
||||
add_child(audio_player)
|
||||
elif current_overwrite_data.get('mode', mode) == Modes.INTERRUPT:
|
||||
stop()
|
||||
|
||||
#choose the random sound
|
||||
audio_player.stream = current_overwrite_data.get('sounds', sounds)[RNG.randi_range(0, current_overwrite_data.get('sounds', sounds).size() - 1)]
|
||||
|
||||
#choose a random pitch and volume
|
||||
audio_player.pitch_scale = max(0, current_overwrite_data.get('pitch_base', base_pitch) + current_overwrite_data.get('pitch_variance', pitch_variance) * RNG.randf_range(-1.0, 1.0))
|
||||
audio_player.volume_db = current_overwrite_data.get('volume_base', base_volume) + current_overwrite_data.get('volume_variance',volume_variance) * RNG.randf_range(-1.0, 1.0)
|
||||
|
||||
#play the sound
|
||||
audio_player.play(0)
|
||||
|
||||
if current_overwrite_data.get('mode', mode) == Modes.OVERLAP:
|
||||
audio_player.finished.connect(audio_player.queue_free)
|
||||
|
||||
|
||||
func _on_finished_revealing_text() -> void:
|
||||
# We don't want to play type sounds if Auto-Skip is enabled.
|
||||
if !Engine.is_editor_hint() and DialogicUtil.autoload().Inputs.auto_skip.enabled:
|
||||
return
|
||||
|
||||
if end_sound != null:
|
||||
stream = end_sound
|
||||
play()
|
||||
|
||||
|
||||
func load_overwrite(dictionary:Dictionary) -> void:
|
||||
current_overwrite_data = dictionary
|
||||
if dictionary.has('sound_path'):
|
||||
current_overwrite_data['sounds'] = DialogicNode_TypeSounds.load_sounds_from_path(dictionary.sound_path)
|
||||
|
||||
|
||||
static func load_sounds_from_path(path:String) -> Array[AudioStream]:
|
||||
if path.get_extension().to_lower() in ['mp3', 'wav', 'ogg'] and load(path) is AudioStream:
|
||||
return [load(path)]
|
||||
var _sounds: Array[AudioStream] = []
|
||||
for file in DialogicUtil.listdir(path, true, false, true, true):
|
||||
if !file.ends_with('.import'):
|
||||
continue
|
||||
if file.trim_suffix('.import').get_extension().to_lower() in ['mp3', 'wav', 'ogg'] and ResourceLoader.load(file.trim_suffix('.import')) is AudioStream:
|
||||
_sounds.append(ResourceLoader.load(file.trim_suffix('.import')))
|
||||
return _sounds
|
||||
|
||||
|
||||
############# USER INTERFACE ###################################################
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
if not get_parent() is DialogicNode_DialogText:
|
||||
return ["This should be the child of a DialogText node!"]
|
||||
return []
|
||||
1
addons/dialogic/Modules/Text/node_type_sound.gd.uid
Normal file
1
addons/dialogic/Modules/Text/node_type_sound.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://beqdjhq0e2wyc
|
||||
240
addons/dialogic/Modules/Text/settings_text.gd
Normal file
240
addons/dialogic/Modules/Text/settings_text.gd
Normal file
@@ -0,0 +1,240 @@
|
||||
@tool
|
||||
extends DialogicSettingsPage
|
||||
|
||||
var autopause_sets := {}
|
||||
|
||||
const _SETTING_LETTER_SPEED := 'dialogic/text/letter_speed'
|
||||
|
||||
const _SETTING_INPUT_ACTION := 'dialogic/text/input_action'
|
||||
|
||||
const _SETTING_TEXT_REVEAL_SKIPPABLE := 'dialogic/text/initial_text_reveal_skippable'
|
||||
const _SETTING_TEXT_REVEAL_SKIPPABLE_DELAY := 'dialogic/text/text_reveal_skip_delay'
|
||||
const _SETTING_TEXT_ADVANCE_DELAY := 'dialogic/text/advance_delay'
|
||||
|
||||
const _SETTING_AUTOCOLOR_NAMES := 'dialogic/text/autocolor_names'
|
||||
const _SETTING_SPLIT_AT_NEW_LINES := 'dialogic/text/split_at_new_lines'
|
||||
const _SETTING_SPLIT_AT_NEW_LINES_AS := 'dialogic/text/split_at_new_lines_as'
|
||||
|
||||
const _SETTING_AUTOSKIP_TIME_PER_EVENT := 'dialogic/text/autoskip_time_per_event'
|
||||
|
||||
const _SETTING_AUTOADVANCE_ENABLED := 'dialogic/text/autoadvance_enabled'
|
||||
const _SETTING_AUTOADVANCE_FIXED_DELAY := 'dialogic/text/autoadvance_fixed_delay'
|
||||
const _SETTING_AUTOADVANCE_WORD_DELAY := 'dialogic/text/autoadvance_per_word_delay'
|
||||
const _SETTING_AUTOADVANCE_CHARACTER_DELAY := 'dialogic/text/autoadvance_per_character_delay'
|
||||
const _SETTING_AUTOADVANCE_IGNORED_CHARACTERS_ENABLED := 'dialogic/text/autoadvance_ignored_characters_enabled'
|
||||
const _SETTING_AUTOADVANCE_IGNORED_CHARACTERS := 'dialogic/text/autoadvance_ignored_characters'
|
||||
|
||||
const _SETTING_ABSOLUTE_AUTOPAUSES := 'dialogic/text/absolute_autopauses'
|
||||
const _SETTING_AUTOPAUSES := 'dialogic/text/autopauses'
|
||||
|
||||
|
||||
func _get_priority() -> int:
|
||||
return 98
|
||||
|
||||
|
||||
func _get_title() -> String:
|
||||
return "Text"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
%DefaultSpeed.value_changed.connect(_on_float_set.bind(_SETTING_LETTER_SPEED))
|
||||
|
||||
%Skippable.toggled.connect(_on_bool_set.bind(_SETTING_TEXT_REVEAL_SKIPPABLE))
|
||||
%SkippableDelay.value_changed.connect(_on_float_set.bind(_SETTING_TEXT_REVEAL_SKIPPABLE_DELAY))
|
||||
%AdvanceDelay.value_changed.connect(_on_float_set.bind(_SETTING_TEXT_ADVANCE_DELAY))
|
||||
|
||||
%AutocolorNames.toggled.connect(_on_bool_set.bind(_SETTING_AUTOCOLOR_NAMES))
|
||||
|
||||
%NewEvents.toggled.connect(_on_bool_set.bind(_SETTING_SPLIT_AT_NEW_LINES))
|
||||
|
||||
%AutoAdvance.toggled.connect(_on_bool_set.bind(_SETTING_AUTOADVANCE_ENABLED))
|
||||
%FixedDelay.value_changed.connect(_on_float_set.bind(_SETTING_AUTOADVANCE_FIXED_DELAY))
|
||||
%IgnoredCharactersEnabled.toggled.connect(_on_bool_set.bind(_SETTING_AUTOADVANCE_IGNORED_CHARACTERS_ENABLED))
|
||||
|
||||
%AutoskipTimePerEvent.value_changed.connect(_on_float_set.bind(_SETTING_AUTOSKIP_TIME_PER_EVENT))
|
||||
|
||||
%AutoPausesAbsolute.toggled.connect(_on_bool_set.bind(_SETTING_ABSOLUTE_AUTOPAUSES))
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
## BEHAVIOUR
|
||||
%DefaultSpeed.value = ProjectSettings.get_setting(_SETTING_LETTER_SPEED, 0.01)
|
||||
|
||||
%InputAction.resource_icon = get_theme_icon(&"Mouse", &"EditorIcons")
|
||||
%InputAction.set_value(ProjectSettings.get_setting(_SETTING_INPUT_ACTION, &'dialogic_default_action'))
|
||||
%InputAction.get_suggestions_func = suggest_actions
|
||||
|
||||
%Skippable.button_pressed = ProjectSettings.get_setting(_SETTING_TEXT_REVEAL_SKIPPABLE, true)
|
||||
%SkippableDelay.value = ProjectSettings.get_setting(_SETTING_TEXT_REVEAL_SKIPPABLE_DELAY, 0.1)
|
||||
%AdvanceDelay.value = ProjectSettings.get_setting(_SETTING_TEXT_ADVANCE_DELAY, 0.1)
|
||||
|
||||
%AutocolorNames.button_pressed = ProjectSettings.get_setting(_SETTING_AUTOCOLOR_NAMES, false)
|
||||
|
||||
%NewEvents.button_pressed = ProjectSettings.get_setting(_SETTING_SPLIT_AT_NEW_LINES, false)
|
||||
%NewEventOption.select(ProjectSettings.get_setting(_SETTING_SPLIT_AT_NEW_LINES_AS, 0))
|
||||
|
||||
## AUTO-ADVANCE
|
||||
%AutoAdvance.button_pressed = ProjectSettings.get_setting(_SETTING_AUTOADVANCE_ENABLED, false)
|
||||
%FixedDelay.value = ProjectSettings.get_setting(_SETTING_AUTOADVANCE_FIXED_DELAY, 1)
|
||||
|
||||
var per_character_delay: float = ProjectSettings.get_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, 0.1)
|
||||
var per_word_delay: float = ProjectSettings.get_setting(_SETTING_AUTOADVANCE_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(_SETTING_AUTOADVANCE_IGNORED_CHARACTERS_ENABLED, true)
|
||||
var ignored_characters: String = ''
|
||||
var ignored_characters_dict: Dictionary = ProjectSettings.get_setting(_SETTING_AUTOADVANCE_IGNORED_CHARACTERS, {})
|
||||
for ignored_character in ignored_characters_dict.keys():
|
||||
ignored_characters += ignored_character
|
||||
%IgnoredCharacters.text = ignored_characters
|
||||
|
||||
## AUTO-SKIP
|
||||
%AutoskipTimePerEvent.value = ProjectSettings.get_setting(_SETTING_AUTOSKIP_TIME_PER_EVENT, 0.1)
|
||||
|
||||
## AUTO-PAUSES
|
||||
%AutoPausesAbsolute.button_pressed = ProjectSettings.get_setting(_SETTING_ABSOLUTE_AUTOPAUSES, false)
|
||||
load_autopauses(ProjectSettings.get_setting(_SETTING_AUTOPAUSES, {}))
|
||||
|
||||
|
||||
func _about_to_close() -> void:
|
||||
save_autopauses()
|
||||
|
||||
|
||||
func _on_bool_set(button_pressed:bool, setting:String) -> void:
|
||||
ProjectSettings.set_setting(setting, button_pressed)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
func _on_float_set(value:float, setting:String) -> void:
|
||||
ProjectSettings.set_setting(setting, value)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
#region BEHAVIOUR
|
||||
################################################################################
|
||||
|
||||
func _on_InputAction_value_changed(property_name:String, value:String) -> void:
|
||||
ProjectSettings.set_setting(_SETTING_INPUT_ACTION, value)
|
||||
ProjectSettings.save()
|
||||
|
||||
func suggest_actions(search:String) -> Dictionary:
|
||||
var suggs := {}
|
||||
for prop in ProjectSettings.get_property_list():
|
||||
if prop.name.begins_with('input/') and not prop.name.begins_with('input/ui_') :
|
||||
suggs[prop.name.trim_prefix('input/')] = {'value':prop.name.trim_prefix('input/')}
|
||||
return suggs
|
||||
|
||||
|
||||
func _on_new_event_option_item_selected(index:int) -> void:
|
||||
ProjectSettings.set_setting(_SETTING_SPLIT_AT_NEW_LINES_AS, index)
|
||||
ProjectSettings.save()
|
||||
|
||||
#endregion
|
||||
|
||||
#region AUTO-ADVANCE
|
||||
################################################################################
|
||||
|
||||
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(_SETTING_AUTOADVANCE_WORD_DELAY, 0)
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, 0)
|
||||
1: # PER WORD
|
||||
%AdditionalDelay.show()
|
||||
%AutoadvanceIgnoreCharacters.hide()
|
||||
if delay != -1:
|
||||
%AdditionalDelay.value = delay
|
||||
else:
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_WORD_DELAY, %AdditionalDelay.value)
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, 0)
|
||||
2: # PER CHARACTER
|
||||
%AdditionalDelay.show()
|
||||
%AutoadvanceIgnoreCharacters.show()
|
||||
if delay != -1:
|
||||
%AdditionalDelay.value = delay
|
||||
else:
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, %AdditionalDelay.value)
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_WORD_DELAY, 0)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
func _on_additional_delay_value_changed(value:float) -> void:
|
||||
match %AdditionalDelayMode.selected:
|
||||
0: # NONE
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, 0)
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_WORD_DELAY, 0)
|
||||
1: # PER WORD
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_WORD_DELAY, value)
|
||||
2: # PER CHARACTER
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_CHARACTER_DELAY, value)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
func _on_IgnoredCharacters_text_changed(text_input):
|
||||
ProjectSettings.set_setting(_SETTING_AUTOADVANCE_IGNORED_CHARACTERS, DialogicUtil.str_to_hash_set(text_input))
|
||||
ProjectSettings.save()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
## AUTO-PAUSES
|
||||
################################################################################
|
||||
|
||||
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])
|
||||
|
||||
|
||||
func save_autopauses() -> void:
|
||||
var dictionary := {}
|
||||
for i in autopause_sets:
|
||||
if is_instance_valid(autopause_sets[i].time):
|
||||
dictionary[autopause_sets[i].text.text] = autopause_sets[i].time.value
|
||||
ProjectSettings.set_setting(_SETTING_AUTOPAUSES, dictionary)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
func _on_add_autopauses_set_pressed() -> void:
|
||||
add_autopause_set('', 0.1)
|
||||
|
||||
|
||||
func add_autopause_set(text: String, time: float) -> void:
|
||||
var info := {}
|
||||
var line_edit := LineEdit.new()
|
||||
line_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
line_edit.placeholder_text = 'e.g. "?!.,;:"'
|
||||
line_edit.text = text
|
||||
info['text'] = line_edit
|
||||
%AutoPauseSets.add_child(line_edit)
|
||||
var spin_box := SpinBox.new()
|
||||
spin_box.min_value = 0.1
|
||||
spin_box.step = 0.01
|
||||
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]:
|
||||
autopause_sets[index][key].queue_free()
|
||||
autopause_sets.erase(index)
|
||||
|
||||
1
addons/dialogic/Modules/Text/settings_text.gd.uid
Normal file
1
addons/dialogic/Modules/Text/settings_text.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6fx5artfhpgb
|
||||
468
addons/dialogic/Modules/Text/settings_text.tscn
Normal file
468
addons/dialogic/Modules/Text/settings_text.tscn
Normal file
@@ -0,0 +1,468 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cf3qks3v18xmr"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/settings_text.gd" id="2"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3"]
|
||||
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="3_s7xhj"]
|
||||
|
||||
[sub_resource type="Image" id="Image_0sqes"]
|
||||
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",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_3xcp4"]
|
||||
image = SubResource("Image_0sqes")
|
||||
|
||||
[node name="DialogText" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_bottom = -156.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("2")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Title3" type="Label" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicSettingsSection"
|
||||
text = "Behaviour"
|
||||
|
||||
[node name="VBox" type="GridContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="DefaultSpeedLabel" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/VBox/DefaultSpeedLabel"]
|
||||
layout_mode = 2
|
||||
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_3xcp4")
|
||||
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"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
step = 0.001
|
||||
|
||||
[node name="InputActionLabel" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label2" type="Label" parent="VBoxContainer/VBox/InputActionLabel"]
|
||||
layout_mode = 2
|
||||
text = "Input action"
|
||||
|
||||
[node name="HintTooltip3" parent="VBoxContainer/VBox/InputActionLabel" instance=ExtResource("3_s7xhj")]
|
||||
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_3xcp4")
|
||||
hint_text = "The action that skips text and generally advances to the next event.
|
||||
You can modify actions in the Project Settings > Input Map."
|
||||
|
||||
[node name="InputAction" parent="VBoxContainer/VBox" instance=ExtResource("3")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label3" type="Label" parent="VBoxContainer/VBox/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Text Reveal Skippable"
|
||||
|
||||
[node name="HintTooltip4" parent="VBoxContainer/VBox/HBoxContainer" instance=ExtResource("3_s7xhj")]
|
||||
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."
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
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."
|
||||
|
||||
[node name="Skippable" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Skippable" type="CheckBox" parent="VBoxContainer/VBox/Skippable"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/VBox/Skippable"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label3" type="Label" parent="VBoxContainer/VBox/Skippable/HBoxContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Skip Delay:"
|
||||
|
||||
[node name="HintTooltip4" parent="VBoxContainer/VBox/Skippable/HBoxContainer2" instance=ExtResource("3_s7xhj")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Delay before you can skip.
|
||||
|
||||
Use this to prevent users from skipping through your timeline to quickly."
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
hint_text = "Delay before you can skip.
|
||||
|
||||
Use this to prevent users from skipping through your timeline too quickly."
|
||||
|
||||
[node name="SkippableDelay" type="SpinBox" parent="VBoxContainer/VBox/Skippable/HBoxContainer2"]
|
||||
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/Skippable"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label3" type="Label" parent="VBoxContainer/VBox/Skippable/HBoxContainer3"]
|
||||
layout_mode = 2
|
||||
text = "Advance Delay:"
|
||||
|
||||
[node name="HintTooltip4" parent="VBoxContainer/VBox/Skippable/HBoxContainer3" instance=ExtResource("3_s7xhj")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Delay before you can advance (if the text finishes revealing on its own).
|
||||
|
||||
This is used to prevent players from advancing when they actually wanted to skip the revealing, but did so very shortly after the text was already fully revealed."
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
hint_text = "Delay before you can advance (only if the text finishes revealing on its own).
|
||||
|
||||
This is used to prevent players from advancing when they actually wanted to skip the revealing, but did so very shortly after the text was already fully revealed."
|
||||
|
||||
[node name="AdvanceDelay" type="SpinBox" parent="VBoxContainer/VBox/Skippable/HBoxContainer3"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
step = 0.01
|
||||
suffix = "s"
|
||||
|
||||
[node name="ColorNames" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label4" type="Label" parent="VBoxContainer/VBox/ColorNames"]
|
||||
layout_mode = 2
|
||||
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_3xcp4")
|
||||
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="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label5" type="Label" parent="VBoxContainer/VBox/HBoxContainer3"]
|
||||
layout_mode = 2
|
||||
text = "New lines as new events"
|
||||
|
||||
[node name="HintTooltip7" parent="VBoxContainer/VBox/HBoxContainer3" instance=ExtResource("3_s7xhj")]
|
||||
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_3xcp4")
|
||||
hint_text = "If enabled dialogic, new lines will be treated as [n] effects,
|
||||
seemingly waiting for input before starting a new text."
|
||||
|
||||
[node name="HBoxContainer4" type="HBoxContainer" parent="VBoxContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NewEvents" type="CheckBox" parent="VBoxContainer/VBox/HBoxContainer4"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NewEventOption" type="OptionButton" parent="VBoxContainer/VBox/HBoxContainer4"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
item_count = 2
|
||||
selected = 0
|
||||
fit_to_longest_item = false
|
||||
popup/item_0/text = "As new event"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Appended"
|
||||
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 Auto-Advance from code using either:
|
||||
- Dialogic.Inputs.auto_advance.enabled_until_user_input = true
|
||||
- Dialogic.Inputs.auto_advance.enabled_until_next_event = true
|
||||
- Dialogic.Inputs.auto_advance.enabled_forced = true
|
||||
These add up, so if any of them is true, Auto-Advance will happen.
|
||||
Unless manual advancement is disabled, the Auto-Advance time can always be skipped by the player.
|
||||
|
||||
The Auto-Advance will wait for Voice audio to finish playing. This behaviour can be disabled via code. "
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
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 Auto-Advance from code using either:
|
||||
- Dialogic.Inputs.auto_advance.enabled_until_user_input = true
|
||||
- Dialogic.Inputs.auto_advance.enabled_until_next_event = true
|
||||
- Dialogic.Inputs.auto_advance.enabled_forced = true
|
||||
These add up, so if any of them is true, Auto-Advance will happen.
|
||||
Unless manual advancement is disabled, the Auto-Advance time can always be skipped by the player.
|
||||
|
||||
The Auto-Advance 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_3xcp4")
|
||||
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_3xcp4")
|
||||
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_3xcp4")
|
||||
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 Auto-Advance via code,
|
||||
if this is true it will be initially enabled.
|
||||
This kind of Auto-Advance (system) only stops when disabled via code. "
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
hint_text = "While you would usually enable Auto-Advance via code,
|
||||
if this is true it will be initially enabled.
|
||||
This kind of Auto-Advance (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 AutoSkip" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="VBoxContainer/HBoxContainer AutoSkip"]
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicSettingsSection"
|
||||
text = "Auto-Skip"
|
||||
|
||||
[node name="HintTooltip" parent="VBoxContainer/HBoxContainer AutoSkip" instance=ExtResource("3_s7xhj")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Auto-Skip is the concept of automatically skipping Timeline Events to the next unread Text Event or Event demanding user inputs (e.g. Choice, Wait Input, and Text Input).
|
||||
|
||||
You can enable Auto-Skip from code via:
|
||||
Dialogic.Inputs.auto_skip.enabled = true
|
||||
|
||||
By default, Auto-Skip will cancel on user input.
|
||||
You can disable this by calling:
|
||||
Dialogic.Inputs.auto_skip.disable_on_user_input = false"
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
hint_text = "Auto-Skip is the concept of automatically skipping Timeline Events to the next unread Text Event or Event demanding user inputs (e.g. Choice, Wait Input, and Text Input).
|
||||
|
||||
You can enable Auto-Skip from code via:
|
||||
Dialogic.Inputs.auto_skip.enabled = true
|
||||
|
||||
By default, Auto-Skip will cancel on user input.
|
||||
You can disable this by calling:
|
||||
Dialogic.Inputs.auto_skip.disable_on_user_input = false"
|
||||
|
||||
[node name="AutoskipSettings" type="GridContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="HBox_BaseDelay2" type="HBoxContainer" parent="VBoxContainer/AutoskipSettings"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer/AutoskipSettings/HBox_BaseDelay2"]
|
||||
layout_mode = 2
|
||||
text = "Time per Event"
|
||||
|
||||
[node name="HintTooltip" parent="VBoxContainer/AutoskipSettings/HBox_BaseDelay2" instance=ExtResource("3_s7xhj")]
|
||||
layout_mode = 2
|
||||
tooltip_text = "The time until Auto-Skip will execute the next event.
|
||||
|
||||
If this is set to 0.1s, each event should finish within that time.
|
||||
Custom events must respect this time, built-in events already handle Auto-Skip."
|
||||
texture = SubResource("ImageTexture_3xcp4")
|
||||
hint_text = "The time until Auto-Skip will execute the next event.
|
||||
|
||||
If this is set to 0.1s, each event should finish within that time.
|
||||
Custom events must respect this time, built-in events already handle Auto-Skip."
|
||||
|
||||
[node name="AutoskipTimePerEvent" type="SpinBox" parent="VBoxContainer/AutoskipSettings"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 2
|
||||
step = 0.01
|
||||
value = 0.1
|
||||
suffix = "s"
|
||||
|
||||
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title2" type="Label" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DialogicSettingsSection"
|
||||
text = "Auto-Pauses"
|
||||
|
||||
[node name="HintTooltip" parent="VBoxContainer/HBoxContainer" instance=ExtResource("3_s7xhj")]
|
||||
layout_mode = 2
|
||||
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_3xcp4")
|
||||
hint_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."
|
||||
|
||||
[node name="Add" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
size_flags_vertical = 4
|
||||
text = "Add set"
|
||||
|
||||
[node name="AutoPauseSets" type="GridContainer" parent="VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
columns = 3
|
||||
|
||||
[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label5" type="Label" parent="VBoxContainer/HBoxContainer3"]
|
||||
layout_mode = 2
|
||||
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_3xcp4")
|
||||
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"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[connection signal="value_changed" from="VBoxContainer/VBox/InputAction" to="." method="_on_InputAction_value_changed"]
|
||||
[connection signal="value_changed" from="VBoxContainer/VBox/Skippable/HBoxContainer3/AdvanceDelay" to="." method="_on_skippable_delay_value_changed"]
|
||||
[connection signal="item_selected" from="VBoxContainer/VBox/HBoxContainer4/NewEventOption" to="." method="_on_new_event_option_item_selected"]
|
||||
[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="text_changed" from="VBoxContainer/AutoadvanceSettings/HBoxContainer2/AutoadvanceIgnoreCharacters/IgnoredCharacters" to="." method="_on_IgnoredCharacters_text_changed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Add" to="." method="_on_add_autopauses_set_pressed"]
|
||||
600
addons/dialogic/Modules/Text/subsystem_text.gd
Normal file
600
addons/dialogic/Modules/Text/subsystem_text.gd
Normal file
@@ -0,0 +1,600 @@
|
||||
extends DialogicSubsystem
|
||||
|
||||
## Subsystem that handles showing of dialog text (+text effects & modifiers), name label, and next indicator
|
||||
|
||||
#region SIGNALS
|
||||
|
||||
signal about_to_show_text(info:Dictionary)
|
||||
signal text_finished(info:Dictionary)
|
||||
signal speaker_updated(character:DialogicCharacter)
|
||||
signal textbox_visibility_changed(visible:bool)
|
||||
|
||||
signal animation_textbox_new_text
|
||||
signal animation_textbox_show
|
||||
signal animation_textbox_hide
|
||||
|
||||
# forwards of the dialog_text signals of all present dialog_text nodes
|
||||
signal meta_hover_ended(meta:Variant)
|
||||
signal meta_hover_started(meta:Variant)
|
||||
signal meta_clicked(meta:Variant)
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
# used to color names without searching for all characters each time
|
||||
var character_colors := {}
|
||||
var color_regex := RegEx.new()
|
||||
var text_already_read := false
|
||||
|
||||
var text_effects := {}
|
||||
var parsed_text_effect_info: Array[Dictionary] = []
|
||||
var text_effects_regex := RegEx.new()
|
||||
enum TextModifierModes {ALL=-1, TEXT_ONLY=0, CHOICES_ONLY=1}
|
||||
enum TextTypes {DIALOG_TEXT, CHOICE_TEXT}
|
||||
var text_modifiers := []
|
||||
|
||||
|
||||
## set by the [speed] effect, multies the letter speed and [pause] effects
|
||||
var _speed_multiplier := 1.0
|
||||
## stores the pure letter speed (unmultiplied)
|
||||
var _pure_letter_speed := 0.1
|
||||
var _letter_speed_absolute := false
|
||||
|
||||
var _voice_synced_text := false
|
||||
|
||||
var _autopauses := {}
|
||||
|
||||
|
||||
#region STATE
|
||||
####################################################################################################
|
||||
|
||||
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
|
||||
update_dialog_text('', true)
|
||||
update_name_label(null)
|
||||
dialogic.current_state_info['speaker'] = ""
|
||||
dialogic.current_state_info['text'] = ''
|
||||
|
||||
set_text_reveal_skippable(ProjectSettings.get_setting('dialogic/text/initial_text_reveal_skippable', true))
|
||||
|
||||
# TODO check whether this can happen on the node directly
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
if text_node.start_hidden:
|
||||
text_node.textbox_root.hide()
|
||||
|
||||
|
||||
func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
|
||||
update_textbox(dialogic.current_state_info.get('text', ''), true)
|
||||
update_dialog_text(dialogic.current_state_info.get('text', ''), true)
|
||||
var character: DialogicCharacter = null
|
||||
if dialogic.current_state_info.get('speaker', ""):
|
||||
character = load(dialogic.current_state_info.get('speaker', ""))
|
||||
|
||||
if character:
|
||||
update_name_label(character)
|
||||
|
||||
|
||||
func post_install() -> void:
|
||||
dialogic.Settings.connect_to_change('text_speed', _update_user_speed)
|
||||
|
||||
collect_text_effects()
|
||||
collect_text_modifiers()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region MAIN METHODS
|
||||
####################################################################################################
|
||||
|
||||
## Applies modifiers, effects and coloring to the text
|
||||
func parse_text(text:String, type:int=TextTypes.DIALOG_TEXT, variables := true, glossary := true, modifiers:= true, effects:= true, color_names:= true) -> String:
|
||||
if modifiers:
|
||||
text = parse_text_modifiers(text, type)
|
||||
if variables and dialogic.has_subsystem('VAR'):
|
||||
text = dialogic.VAR.parse_variables(text)
|
||||
if effects:
|
||||
text = parse_text_effects(text)
|
||||
if color_names:
|
||||
text = color_character_names(text)
|
||||
if glossary and dialogic.has_subsystem('Glossary'):
|
||||
text = dialogic.Glossary.parse_glossary(text)
|
||||
return text
|
||||
|
||||
|
||||
## When an event updates the text spoken, this can adjust the state of
|
||||
## the dialog text box.
|
||||
## This method is async.
|
||||
func update_textbox(text: String, instant := false) -> void:
|
||||
if text.is_empty():
|
||||
await hide_textbox(instant)
|
||||
else:
|
||||
await show_textbox(instant)
|
||||
|
||||
if !dialogic.current_state_info['text'].is_empty():
|
||||
animation_textbox_new_text.emit()
|
||||
|
||||
if dialogic.Animations.is_animating():
|
||||
await dialogic.Animations.finished
|
||||
|
||||
|
||||
## Shows the given text on all visible DialogText nodes.
|
||||
## Instant can be used to skip all revieling.
|
||||
## If additional is true, the previous text will be kept.
|
||||
func update_dialog_text(text: String, instant := false, additional := false) -> String:
|
||||
update_text_speed()
|
||||
|
||||
if !instant: dialogic.current_state = dialogic.States.REVEALING_TEXT
|
||||
|
||||
if additional:
|
||||
dialogic.current_state_info['text'] += text
|
||||
else:
|
||||
dialogic.current_state_info['text'] = text
|
||||
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
connect_meta_signals(text_node)
|
||||
|
||||
if text_node.enabled and (text_node == text_node.textbox_root or text_node.textbox_root.is_visible_in_tree()):
|
||||
|
||||
if instant:
|
||||
text_node.text = text
|
||||
|
||||
else:
|
||||
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()
|
||||
|
||||
# Reset speed multiplier
|
||||
update_text_speed(-1, false, 1)
|
||||
# Reset Auto-Advance temporarily and the No-Skip setting:
|
||||
dialogic.Inputs.auto_advance.enabled_until_next_event = false
|
||||
dialogic.Inputs.auto_advance.override_delay_for_current_event = -1
|
||||
dialogic.Inputs.manual_advance.disabled_until_next_event = false
|
||||
|
||||
set_text_reveal_skippable(true, true)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
func _on_dialog_text_finished() -> void:
|
||||
text_finished.emit({'text':dialogic.current_state_info['text'], 'character':dialogic.current_state_info['speaker']})
|
||||
|
||||
|
||||
## Updates the visible name on all name labels nodes.
|
||||
## If a name changes, the [signal speaker_updated] signal is emitted.
|
||||
func update_name_label(character:DialogicCharacter):
|
||||
var character_path := character.resource_path if character else ""
|
||||
var current_character_path: String = dialogic.current_state_info.get("speaker", "")
|
||||
|
||||
if character_path != current_character_path:
|
||||
dialogic.current_state_info['speaker'] = character_path
|
||||
speaker_updated.emit(character)
|
||||
|
||||
var name_label_text := get_character_name_parsed(character)
|
||||
|
||||
for name_label in get_tree().get_nodes_in_group('dialogic_name_label'):
|
||||
name_label.text = name_label_text
|
||||
if character:
|
||||
if !'use_character_color' in name_label or name_label.use_character_color:
|
||||
name_label.self_modulate = character.color
|
||||
else:
|
||||
name_label.self_modulate = Color(1,1,1,1)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
## instant skips the signal and thus possible animations
|
||||
func show_textbox(instant:=false) -> void:
|
||||
var emitted := instant
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
if not text_node.enabled:
|
||||
continue
|
||||
if not text_node.textbox_root.visible and not emitted:
|
||||
animation_textbox_show.emit()
|
||||
text_node.textbox_root.show()
|
||||
if dialogic.Animations.is_animating():
|
||||
await dialogic.Animations.finished
|
||||
textbox_visibility_changed.emit(true)
|
||||
emitted = true
|
||||
else:
|
||||
text_node.textbox_root.show()
|
||||
|
||||
|
||||
## Instant skips the signal and thus possible animations
|
||||
func hide_textbox(instant:=false) -> void:
|
||||
dialogic.current_state_info['text'] = ''
|
||||
var emitted := instant
|
||||
for name_label in get_tree().get_nodes_in_group('dialogic_name_label'):
|
||||
name_label.text = ""
|
||||
if !emitted and !get_tree().get_nodes_in_group('dialogic_dialog_text').is_empty() and get_tree().get_nodes_in_group('dialogic_dialog_text')[0].textbox_root.visible:
|
||||
animation_textbox_hide.emit()
|
||||
if dialogic.Animations.is_animating():
|
||||
await dialogic.Animations.finished
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
if text_node.textbox_root.visible and !emitted:
|
||||
textbox_visibility_changed.emit(false)
|
||||
emitted = true
|
||||
text_node.textbox_root.hide()
|
||||
|
||||
|
||||
func is_textbox_visible() -> bool:
|
||||
return get_tree().get_nodes_in_group('dialogic_dialog_text').any(func(x): return x.textbox_root.visible)
|
||||
|
||||
|
||||
func show_next_indicators(question:=false, autoadvance:=false) -> void:
|
||||
for next_indicator in get_tree().get_nodes_in_group('dialogic_next_indicator'):
|
||||
if next_indicator.enabled:
|
||||
if (question and 'show_on_questions' in next_indicator and next_indicator.show_on_questions) or \
|
||||
(autoadvance and 'show_on_autoadvance' in next_indicator and next_indicator.show_on_autoadvance) or (!question and !autoadvance):
|
||||
next_indicator.show()
|
||||
else:
|
||||
next_indicator.hide()
|
||||
|
||||
|
||||
func hide_next_indicators(_fake_arg :Variant= null) -> void:
|
||||
for next_indicator in get_tree().get_nodes_in_group('dialogic_next_indicator'):
|
||||
next_indicator.hide()
|
||||
|
||||
|
||||
## This method will sync the text speed to the voice audio clip length, if a
|
||||
## voice is playing.
|
||||
## For instance, if the voice is playing for four seconds, the text will finish
|
||||
## revealing after this time.
|
||||
## This feature ignores Auto-Pauses on letters. Pauses via BBCode will desync
|
||||
## the reveal.
|
||||
func set_text_voice_synced(enabled: bool = true) -> void:
|
||||
_voice_synced_text = enabled
|
||||
update_text_speed()
|
||||
|
||||
|
||||
## Returns whether voice-synced text is enabled.
|
||||
func is_text_voice_synced() -> bool:
|
||||
return _voice_synced_text
|
||||
|
||||
|
||||
## Sets how fast text will be revealed.
|
||||
## [br][br]
|
||||
## [param letter_speed] is the speed a single text character takes to appear
|
||||
## on the textbox.
|
||||
## [br][br]
|
||||
## [param absolute] will force text to display at the given speed, regardless
|
||||
## of the user's text speed setting.
|
||||
## [br][br]
|
||||
## [param _speed_multiplier] adjusts the speed of the text, if set to -1,
|
||||
## the value won't be updated and the current value will persist.
|
||||
## [br][br]
|
||||
## [param _user_speed] adjusts the speed of the text, if set to -1, the
|
||||
## project setting 'text_speed' will be used.operator
|
||||
func update_text_speed(letter_speed: float = -1,
|
||||
absolute := false,
|
||||
speed_multiplier := _speed_multiplier,
|
||||
user_speed: float = dialogic.Settings.get_setting('text_speed', 1)) -> void:
|
||||
|
||||
if letter_speed == -1:
|
||||
letter_speed = ProjectSettings.get_setting('dialogic/text/letter_speed', 0.01)
|
||||
|
||||
_pure_letter_speed = letter_speed
|
||||
_letter_speed_absolute = absolute
|
||||
_speed_multiplier = speed_multiplier
|
||||
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
if absolute:
|
||||
text_node.set_speed(letter_speed)
|
||||
else:
|
||||
text_node.set_speed(letter_speed * _speed_multiplier * user_speed)
|
||||
|
||||
|
||||
func set_text_reveal_skippable(skippable:= true, temp:=false) -> void:
|
||||
if !dialogic.current_state_info.has('text_reveal_skippable'):
|
||||
dialogic.current_state_info['text_reveal_skippable'] = {'enabled':false, 'temp_enabled':false}
|
||||
|
||||
if temp:
|
||||
dialogic.current_state_info['text_reveal_skippable']['temp_enabled'] = skippable
|
||||
else:
|
||||
dialogic.current_state_info['text_reveal_skippable']['enabled'] = skippable
|
||||
|
||||
|
||||
func is_text_reveal_skippable() -> bool:
|
||||
return dialogic.current_state_info['text_reveal_skippable']['enabled'] and dialogic.current_state_info['text_reveal_skippable'].get('temp_enabled', true)
|
||||
|
||||
|
||||
func skip_text_reveal() -> void:
|
||||
for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'):
|
||||
if text_node.is_visible_in_tree():
|
||||
text_node.finish_text()
|
||||
if dialogic.has_subsystem('Voice'):
|
||||
dialogic.Voice.stop_audio()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region TEXT EFFECTS & MODIFIERS
|
||||
####################################################################################################
|
||||
|
||||
func collect_text_effects() -> void:
|
||||
var text_effect_names := ""
|
||||
text_effects.clear()
|
||||
for indexer in DialogicUtil.get_indexers(true):
|
||||
for effect in indexer._get_text_effects():
|
||||
text_effects[effect.command] = {}
|
||||
if effect.has('subsystem') and effect.has('method'):
|
||||
text_effects[effect.command]['callable'] = Callable(dialogic.get_subsystem(effect.subsystem), effect.method)
|
||||
elif effect.has('node_path') and effect.has('method'):
|
||||
text_effects[effect.command]['callable'] = Callable(get_node(effect.node_path), effect.method)
|
||||
else:
|
||||
continue
|
||||
text_effect_names += effect.command +"|"
|
||||
text_effects_regex.compile("(?<!\\\\)\\[\\s*(?<command>"+text_effect_names.trim_suffix("|")+")\\s*(=\\s*(?<value>.+?)\\s*)?\\]")
|
||||
|
||||
|
||||
## Returns the string with all text effects removed
|
||||
## Use get_parsed_text_effects() after calling this to get all effect information
|
||||
func parse_text_effects(text:String) -> String:
|
||||
parsed_text_effect_info.clear()
|
||||
var rtl := RichTextLabel.new()
|
||||
rtl.bbcode_enabled = true
|
||||
var position_correction := 0
|
||||
var bbcode_correction := 0
|
||||
for effect_match in text_effects_regex.search_all(text):
|
||||
rtl.text = text.substr(0, effect_match.get_start()-position_correction)
|
||||
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()
|
||||
return text
|
||||
|
||||
|
||||
func execute_effects(current_index:int, text_node:Control, skipping := false) -> void:
|
||||
# might have to execute multiple effects
|
||||
while true:
|
||||
if parsed_text_effect_info.is_empty():
|
||||
return
|
||||
if current_index != -1 and current_index < parsed_text_effect_info[0]['index']:
|
||||
return
|
||||
var effect: Dictionary = parsed_text_effect_info.pop_front()
|
||||
await (effect['execution_info']['callable'] as Callable).call(text_node, skipping, effect['value'])
|
||||
|
||||
|
||||
func collect_text_modifiers() -> void:
|
||||
text_modifiers.clear()
|
||||
for indexer in DialogicUtil.get_indexers(true):
|
||||
for modifier in indexer._get_text_modifiers():
|
||||
if modifier.has('subsystem') and modifier.has('method'):
|
||||
text_modifiers.append({'method':Callable(dialogic.get_subsystem(modifier.subsystem), modifier.method)})
|
||||
elif modifier.has('node_path') and modifier.has('method'):
|
||||
text_modifiers.append({'method':Callable(get_node(modifier.node_path), modifier.method)})
|
||||
text_modifiers[-1]['mode'] = modifier.get('mode', TextModifierModes.TEXT_ONLY)
|
||||
|
||||
|
||||
func parse_text_modifiers(text:String, type:int=TextTypes.DIALOG_TEXT) -> String:
|
||||
for mod in text_modifiers:
|
||||
if mod.mode != TextModifierModes.ALL and type != -1 and type != mod.mode:
|
||||
continue
|
||||
text = mod.method.call(text)
|
||||
return text
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region HELPERS & OTHER STUFF
|
||||
####################################################################################################
|
||||
|
||||
func _ready() -> void:
|
||||
dialogic.event_handled.connect(hide_next_indicators)
|
||||
|
||||
_autopauses = {}
|
||||
var autopause_data: Dictionary = ProjectSettings.get_setting('dialogic/text/autopauses', {})
|
||||
for i in autopause_data.keys():
|
||||
_autopauses[RegEx.create_from_string(r"(?<!(\[|\{))["+i+r"](?!([^{}\[\]]*[\]\}]|$))")] = autopause_data[i]
|
||||
|
||||
|
||||
## Parses the character's display_name and returns the text that
|
||||
## should be rendered. Note that characters may have variables in their
|
||||
## name, therefore this function should be called to evaluate
|
||||
## any potential variables in a character's name.
|
||||
func get_character_name_parsed(character:DialogicCharacter) -> String:
|
||||
if character:
|
||||
var translated_display_name := character.get_display_name_translated()
|
||||
if dialogic.has_subsystem('VAR'):
|
||||
return dialogic.VAR.parse_variables(translated_display_name)
|
||||
else:
|
||||
return translated_display_name
|
||||
return ""
|
||||
|
||||
|
||||
## Returns the [class DialogicCharacter] of the current speaker.
|
||||
## If there is no current speaker or the speaker is not found, returns null.
|
||||
func get_current_speaker() -> DialogicCharacter:
|
||||
var speaker_path: String = dialogic.current_state_info.get("speaker", "")
|
||||
|
||||
if speaker_path.is_empty():
|
||||
return null
|
||||
|
||||
var speaker_resource := load(speaker_path)
|
||||
|
||||
if speaker_resource == null:
|
||||
return null
|
||||
|
||||
var speaker_character := speaker_resource as DialogicCharacter
|
||||
|
||||
return speaker_character
|
||||
|
||||
|
||||
func _update_user_speed(_user_speed:float) -> void:
|
||||
update_text_speed(_pure_letter_speed, _letter_speed_absolute)
|
||||
|
||||
|
||||
func connect_meta_signals(text_node: Node) -> void:
|
||||
if not text_node.meta_clicked.is_connected(emit_meta_signal):
|
||||
text_node.meta_clicked.connect(emit_meta_signal.bind("meta_clicked"))
|
||||
|
||||
if not text_node.meta_hover_started.is_connected(emit_meta_signal):
|
||||
text_node.meta_hover_started.connect(emit_meta_signal.bind("meta_hover_started"))
|
||||
|
||||
if not text_node.meta_hover_ended.is_connected(emit_meta_signal):
|
||||
text_node.meta_hover_ended.connect(emit_meta_signal.bind("meta_hover_ended"))
|
||||
|
||||
|
||||
func emit_meta_signal(meta:Variant, sig:String) -> void:
|
||||
emit_signal(sig, meta)
|
||||
|
||||
#endregion
|
||||
|
||||
#region AUTOCOLOR NAMES
|
||||
################################################################################
|
||||
|
||||
func color_character_names(text:String) -> String:
|
||||
if not ProjectSettings.get_setting('dialogic/text/autocolor_names', false):
|
||||
return text
|
||||
|
||||
collect_character_names()
|
||||
|
||||
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 not ProjectSettings.get_setting("dialogic/text/autocolor_names", false):
|
||||
return
|
||||
|
||||
character_colors = {}
|
||||
|
||||
for dch_path in DialogicResourceUtil.get_character_directory().values():
|
||||
var character := (load(dch_path) as DialogicCharacter)
|
||||
|
||||
if character.display_name:
|
||||
if "{" in character.display_name and "}" in character.display_name:
|
||||
character_colors[dialogic.VAR.parse_variables(character.display_name)] = character.color
|
||||
else:
|
||||
character_colors[character.display_name] = character.color
|
||||
|
||||
for nickname in character.get_nicknames_translated():
|
||||
nickname = nickname.strip_edges()
|
||||
if nickname:
|
||||
if "{" in nickname and "}" in nickname:
|
||||
character_colors[dialogic.VAR.parse_variables(nickname)] = character.color
|
||||
else:
|
||||
character_colors[nickname] = character.color
|
||||
|
||||
if dialogic.has_subsystem("Glossary"):
|
||||
dialogic.Glossary.color_overrides.merge(character_colors, true)
|
||||
|
||||
var sorted_keys := character_colors.keys()
|
||||
sorted_keys.sort_custom(sort_by_length)
|
||||
|
||||
var character_names := ""
|
||||
for key in sorted_keys:
|
||||
character_names += r"\Q" + key + r"\E|"
|
||||
|
||||
character_names = character_names.trim_suffix("|")
|
||||
color_regex.compile(r"(?<=\W|^)(?<name>" + character_names + r")(?=\W|$)")
|
||||
|
||||
|
||||
func sort_by_length(a:String, b:String) -> bool:
|
||||
if a.length() > b.length():
|
||||
return true
|
||||
return false
|
||||
#endregion+
|
||||
|
||||
|
||||
#region DEFAULT TEXT EFFECTS & MODIFIERS
|
||||
################################################################################
|
||||
|
||||
func effect_pause(_text_node:Control, skipped:bool, argument:String) -> void:
|
||||
if skipped:
|
||||
return
|
||||
|
||||
# We want to ignore pauses if we're skipping.
|
||||
if dialogic.Inputs.auto_skip.enabled:
|
||||
return
|
||||
|
||||
var text_speed: float = 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 text_speed != 0:
|
||||
await get_tree().create_timer(float(argument) * _speed_multiplier * text_speed).timeout
|
||||
|
||||
elif _speed_multiplier != 0 and text_speed != 0:
|
||||
await get_tree().create_timer(0.5 * _speed_multiplier * text_speed).timeout
|
||||
|
||||
|
||||
func effect_speed(_text_node:Control, skipped:bool, argument:String) -> void:
|
||||
if skipped:
|
||||
return
|
||||
if argument:
|
||||
update_text_speed(-1, false, float(argument))
|
||||
else:
|
||||
update_text_speed(-1, false, 1)
|
||||
|
||||
|
||||
func effect_lspeed(_text_node:Control, skipped:bool, argument:String) -> void:
|
||||
if skipped:
|
||||
return
|
||||
if argument:
|
||||
if argument.ends_with('!'):
|
||||
update_text_speed(float(argument.trim_suffix('!')), true)
|
||||
else:
|
||||
update_text_speed(float(argument), false)
|
||||
else:
|
||||
update_text_speed()
|
||||
|
||||
|
||||
func effect_signal(_text_node:Control, _skipped:bool, argument:String) -> void:
|
||||
dialogic.text_signal.emit(argument)
|
||||
|
||||
|
||||
func effect_mood(_text_node:Control, _skipped:bool, argument:String) -> void:
|
||||
if argument.is_empty(): return
|
||||
if dialogic.current_state_info.get('speaker', ""):
|
||||
update_typing_sound_mood(
|
||||
load(dialogic.current_state_info.speaker).custom_info.get('sound_moods', {}).get(argument, {}))
|
||||
|
||||
|
||||
var modifier_words_select_regex := RegEx.create_from_string(r"(?<!\\)\<[^\[\>]+(\/[^\>]*)\>")
|
||||
func modifier_random_selection(text:String) -> String:
|
||||
for replace_mod_match in modifier_words_select_regex.search_all(text):
|
||||
var string: String = replace_mod_match.get_string().trim_prefix("<").trim_suffix(">")
|
||||
string = string.replace('//', '<slash>')
|
||||
var list: PackedStringArray = string.split('/')
|
||||
var item: String = list[randi()%len(list)]
|
||||
item = item.replace('<slash>', '/')
|
||||
text = text.replace(replace_mod_match.get_string(), item.strip_edges())
|
||||
return text
|
||||
|
||||
|
||||
func modifier_break(text:String) -> String:
|
||||
return text.replace('[br]', '\n')
|
||||
|
||||
|
||||
func modifier_autopauses(text:String) -> String:
|
||||
var absolute: bool = ProjectSettings.get_setting('dialogic/text/absolute_autopauses', false)
|
||||
for i in _autopauses.keys():
|
||||
var offset := 0
|
||||
for result in i.search_all(text):
|
||||
if absolute:
|
||||
text = text.insert(result.get_end()+offset, '[pause='+str(_autopauses[i])+'!]')
|
||||
offset += len('[pause='+str(_autopauses[i])+'!]')
|
||||
else:
|
||||
text = text.insert(result.get_end()+offset, '[pause='+str(_autopauses[i])+']')
|
||||
offset += len('[pause='+str(_autopauses[i])+']')
|
||||
return text
|
||||
#endregion
|
||||
1
addons/dialogic/Modules/Text/subsystem_text.gd.uid
Normal file
1
addons/dialogic/Modules/Text/subsystem_text.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b6l71pof1b02t
|
||||
Reference in New Issue
Block a user