mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-14 09:33:48 +00:00
Updated dialogic
This commit is contained in:
parent
1d11462073
commit
cbb82512ee
483 changed files with 5743 additions and 2177 deletions
|
|
@ -4,18 +4,48 @@ extends DialogicSubsystem
|
|||
|
||||
#region SIGNALS
|
||||
|
||||
## Emitted when a text event is reached or a new text section is about to be shown.
|
||||
## Gives a dictionary with the following keys: [br]
|
||||
## [br]
|
||||
## Key | Value Type | Value [br]
|
||||
## ----------- | ------------- | ----- [br]
|
||||
## `text` | [type String] | The text that is being displayed. [br]
|
||||
## `character` | [type DialogicCharacter] | The character that says this text. [br]
|
||||
## `portrait` | [type String] | The name of the portrait the character will use. [br]
|
||||
## `append` | [type bool] | Whether the text will be appended to the previous text. [br]
|
||||
@warning_ignore("unused_signal") # This is emitted by the text event.
|
||||
signal about_to_show_text(info:Dictionary)
|
||||
## Emitted when a text event (or a new text section) starts displaying.
|
||||
## This will be AFTER the textox animation, while [signal about_to_show_text] is before.
|
||||
## Gives a dictionary with the same values as [signal about_to_show_text]
|
||||
@warning_ignore("unused_signal") # This is emitted by the text event.
|
||||
signal text_started(info:Dictionary)
|
||||
## When the text has finished revealing.
|
||||
## Gives a dictionary with the keys text and character.
|
||||
signal text_finished(info:Dictionary)
|
||||
## Emitted when the speaker changes.
|
||||
signal speaker_updated(character:DialogicCharacter)
|
||||
## Emitted when the textbox is shown or hidden.
|
||||
signal textbox_visibility_changed(visible:bool)
|
||||
|
||||
signal animation_textbox_new_text
|
||||
## Emitted when the textbox appears.
|
||||
## Use this together with the Animations subsystem to implement animations.
|
||||
## If you start an animation and want dialogic to wait for it to finish before showing text,
|
||||
## call Dialogic.Animations.start_animating() and then Dialogic.animation_finished() once it's done.
|
||||
signal animation_textbox_show
|
||||
## Emitted when the textbox is hiding. Use like [signal animation_textbox_show].
|
||||
signal animation_textbox_hide
|
||||
## Emitted when a new text starts. Use like [signal animation_textbox_show].
|
||||
signal animation_textbox_new_text
|
||||
|
||||
# forwards of the dialog_text signals of all present dialog_text nodes
|
||||
signal meta_hover_ended(meta:Variant)
|
||||
## Emitted when a meta text on any DialogText node is hovered.
|
||||
@warning_ignore("unused_signal") # These are emitted by the NodeDialogText
|
||||
signal meta_hover_started(meta:Variant)
|
||||
## Emitted when a meta text on any DialogText node is not hovered anymore.
|
||||
@warning_ignore("unused_signal") # These are emitted by the NodeDialogText
|
||||
signal meta_hover_ended(meta:Variant)
|
||||
## Emitted when a meta text on any DialogText node is clicked.
|
||||
@warning_ignore("unused_signal") # These are emitted by the NodeDialogText
|
||||
signal meta_clicked(meta:Variant)
|
||||
|
||||
#endregion
|
||||
|
|
@ -29,7 +59,7 @@ 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 ParserModes {ALL=-1, TEXT_ONLY=0, CHOICES_ONLY=1}
|
||||
enum TextTypes {DIALOG_TEXT, CHOICE_TEXT}
|
||||
var text_modifiers := []
|
||||
|
||||
|
|
@ -44,15 +74,17 @@ var _voice_synced_text := false
|
|||
|
||||
var _autopauses := {}
|
||||
|
||||
var parse_stack: Array[Dictionary] = []
|
||||
|
||||
|
||||
#region STATE
|
||||
####################################################################################################
|
||||
|
||||
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
|
||||
update_dialog_text('', true)
|
||||
update_dialog_text("", true)
|
||||
update_name_label(null)
|
||||
dialogic.current_state_info['speaker'] = ""
|
||||
dialogic.current_state_info['text'] = ''
|
||||
dialogic.current_state_info["speaker"] = ""
|
||||
dialogic.current_state_info["text"] = ""
|
||||
|
||||
set_text_reveal_skippable(ProjectSettings.get_setting('dialogic/text/initial_text_reveal_skippable', true))
|
||||
|
||||
|
|
@ -65,9 +97,7 @@ func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) ->
|
|||
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', ""))
|
||||
var character: DialogicCharacter = get_current_speaker()
|
||||
|
||||
if character:
|
||||
update_name_label(character)
|
||||
|
|
@ -85,21 +115,57 @@ func post_install() -> void:
|
|||
#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)
|
||||
## Applies modifiers, effects and coloring to the text.
|
||||
## Utilizes the parse stack created and sorted in [method load_parse_stack()].
|
||||
func parse_text(text:String, type:int=TextTypes.DIALOG_TEXT) -> String:
|
||||
if parse_stack.is_empty():
|
||||
load_parse_stack()
|
||||
|
||||
for i in parse_stack:
|
||||
if i.type != ParserModes.ALL and type != -1 and i.type != type:
|
||||
continue
|
||||
text = i.method.call(text)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
## Creates and sorts a stack of methods that take a text and return it.
|
||||
## This includes: variables, text modifiers, text effects, autocolor names and the glossary.
|
||||
func load_parse_stack() -> void:
|
||||
parse_stack.clear()
|
||||
|
||||
if dialogic.has_subsystem('VAR'):
|
||||
parse_stack.append(
|
||||
{
|
||||
"method":dialogic.VAR.parse_variables,
|
||||
"type": ParserModes.ALL,
|
||||
"order": 30,
|
||||
})
|
||||
|
||||
parse_stack.append(
|
||||
{
|
||||
"method": parse_text_effects,
|
||||
"type": ParserModes.TEXT_ONLY,
|
||||
"order": 50,
|
||||
})
|
||||
for i in text_modifiers:
|
||||
parse_stack.append(i)
|
||||
parse_stack.append(
|
||||
{
|
||||
"method": color_character_names,
|
||||
"type": ParserModes.TEXT_ONLY,
|
||||
"order": 90,
|
||||
})
|
||||
parse_stack.append(
|
||||
{
|
||||
"method": dialogic.Glossary.parse_glossary,
|
||||
"type": ParserModes.TEXT_ONLY,
|
||||
"order": 95,
|
||||
})
|
||||
|
||||
parse_stack.sort_custom(func(a,b):return a["order"] < b["order"])
|
||||
|
||||
|
||||
## When an event updates the text spoken, this can adjust the state of
|
||||
## the dialog text box.
|
||||
## This method is async.
|
||||
|
|
@ -138,6 +204,13 @@ func update_dialog_text(text: String, instant := false, additional := false) ->
|
|||
text_node.text = text
|
||||
|
||||
else:
|
||||
var current_character := get_current_speaker()
|
||||
|
||||
if current_character:
|
||||
var character_prefix: String = current_character.custom_info.get(DialogicCharacterPrefixSuffixSection.PREFIX_CUSTOM_KEY, DialogicCharacterPrefixSuffixSection.DEFAULT_PREFIX)
|
||||
var character_suffix: String = current_character.custom_info.get(DialogicCharacterPrefixSuffixSection.SUFFIX_CUSTOM_KEY, DialogicCharacterPrefixSuffixSection.DEFAULT_SUFFIX)
|
||||
text = character_prefix + text + character_suffix
|
||||
|
||||
text_node.reveal_text(text, additional)
|
||||
|
||||
if !text_node.finished_revealing_text.is_connected(_on_dialog_text_finished):
|
||||
|
|
@ -158,18 +231,18 @@ func update_dialog_text(text: String, instant := false, additional := false) ->
|
|||
|
||||
|
||||
func _on_dialog_text_finished() -> void:
|
||||
text_finished.emit({'text':dialogic.current_state_info['text'], 'character':dialogic.current_state_info['speaker']})
|
||||
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", "")
|
||||
var character_id := character.get_identifier() if character else ""
|
||||
var current_character_id: String = dialogic.current_state_info.get("speaker", "")
|
||||
|
||||
if character_path != current_character_path:
|
||||
dialogic.current_state_info['speaker'] = character_path
|
||||
if character_id != current_character_id:
|
||||
speaker_updated.emit(character)
|
||||
dialogic.current_state_info["speaker"] = character_id
|
||||
|
||||
var name_label_text := get_character_name_parsed(character)
|
||||
|
||||
|
|
@ -182,8 +255,19 @@ func update_name_label(character:DialogicCharacter):
|
|||
name_label.self_modulate = Color(1,1,1,1)
|
||||
|
||||
|
||||
func update_typing_sound_mood_from_character(character:DialogicCharacter, mood:String) -> void:
|
||||
if character.custom_info.get("sound_moods", {}).is_empty():
|
||||
update_typing_sound_mood()
|
||||
elif mood in character.custom_info.get("sound_moods", {}):
|
||||
update_typing_sound_mood(character.custom_info.get("sound_moods", {})[mood])
|
||||
else:
|
||||
var default_mood : String = character.custom_info.get("sound_mood_default", "")
|
||||
update_typing_sound_mood(character.custom_info.get("sound_moods", {}).get(default_mood, {}))
|
||||
|
||||
|
||||
|
||||
func update_typing_sound_mood(mood:Dictionary = {}) -> void:
|
||||
for typing_sound in get_tree().get_nodes_in_group('dialogic_type_sounds'):
|
||||
for typing_sound in get_tree().get_nodes_in_group("dialogic_type_sounds"):
|
||||
typing_sound.load_overwrite(mood)
|
||||
|
||||
|
||||
|
|
@ -335,8 +419,12 @@ func collect_text_effects() -> void:
|
|||
## 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 rtl: RichTextLabel = null
|
||||
if get_tree().get_first_node_in_group("dialogic_dialog_text"):
|
||||
rtl = get_tree().get_first_node_in_group("dialogic_dialog_text").duplicate()
|
||||
else:
|
||||
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):
|
||||
|
|
@ -361,7 +449,9 @@ func execute_effects(current_index:int, text_node:Control, skipping := false) ->
|
|||
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'])
|
||||
var callable: Callable = effect['execution_info']['callable']
|
||||
if is_instance_valid(text_node):
|
||||
await callable.call(text_node, skipping, effect['value'])
|
||||
|
||||
|
||||
func collect_text_modifiers() -> void:
|
||||
|
|
@ -372,15 +462,8 @@ func collect_text_modifiers() -> void:
|
|||
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
|
||||
text_modifiers[-1]['type'] = modifier.get('mode', ParserModes.TEXT_ONLY)
|
||||
text_modifiers[-1]['order'] = modifier.get('order', 40)
|
||||
|
||||
|
||||
#endregion
|
||||
|
|
@ -415,19 +498,12 @@ func get_character_name_parsed(character:DialogicCharacter) -> String:
|
|||
## 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", "")
|
||||
var speaker_id: String = dialogic.current_state_info.get("speaker", "")
|
||||
|
||||
if speaker_path.is_empty():
|
||||
if speaker_id.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
|
||||
return DialogicResourceUtil.get_character_resource(speaker_id)
|
||||
|
||||
|
||||
func _update_user_speed(_user_speed:float) -> void:
|
||||
|
|
@ -475,8 +551,8 @@ func collect_character_names() -> void:
|
|||
|
||||
character_colors = {}
|
||||
|
||||
for dch_path in DialogicResourceUtil.get_character_directory().values():
|
||||
var character := (load(dch_path) as DialogicCharacter)
|
||||
for dch_identifier in DialogicResourceUtil.get_character_directory():
|
||||
var character := (DialogicResourceUtil.get_character_resource(dch_identifier) as DialogicCharacter)
|
||||
|
||||
if character.display_name:
|
||||
if "{" in character.display_name and "}" in character.display_name:
|
||||
|
|
@ -564,19 +640,20 @@ func effect_signal(_text_node:Control, _skipped:bool, argument:String) -> void:
|
|||
|
||||
func effect_mood(_text_node:Control, _skipped:bool, argument:String) -> void:
|
||||
if argument.is_empty(): return
|
||||
if dialogic.current_state_info.get('speaker', ""):
|
||||
if get_current_speaker():
|
||||
update_typing_sound_mood(
|
||||
load(dialogic.current_state_info.speaker).custom_info.get('sound_moods', {}).get(argument, {}))
|
||||
get_current_speaker().custom_info.get('sound_moods', {}).get(argument, {}))
|
||||
|
||||
|
||||
var modifier_words_select_regex := RegEx.create_from_string(r"(?<!\\)\<[^\[\>]+(\/[^\>]*)\>")
|
||||
var modifier_select_regex := RegEx.create_from_string(r"(?<!\\)\<[^\>]+(\/[^\>]*)\>")
|
||||
var modifier_select_split_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):
|
||||
for replace_mod_match: RegExMatch in modifier_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>', '/')
|
||||
var options := []
|
||||
for split: RegExMatch in modifier_select_split_regex.search_all(string):
|
||||
options.append(split.get_string())
|
||||
var item: String = options.pick_random()
|
||||
text = text.replace(replace_mod_match.get_string(), item.strip_edges())
|
||||
return text
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue