Added Dialogic and a test dialog

This commit is contained in:
cblech
2024-10-28 12:06:03 +01:00
parent ea20332b4f
commit d231bb0773
683 changed files with 46264 additions and 1 deletions
@@ -0,0 +1,105 @@
@tool
## Event that can change the currently playing background music.
## This event won't play new music if it's already playing.
class_name DialogicMusicEvent
extends DialogicEvent
### Settings
## The file to play. If empty, the previous music will be faded out.
var file_path := "":
set(value):
if file_path != value:
file_path = value
ui_update_needed.emit()
## The channel to use.
var channel_id: int = 0
## The length of the fade. If 0 (by default) it's an instant change.
var fade_length: float = 0
## The volume the music will be played at.
var volume: float = 0
## The audio bus the music will be played at.
var audio_bus := ""
## If true, the audio will loop, otherwise only play once.
var loop := true
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
if not dialogic.Audio.is_music_playing_resource(file_path, channel_id):
dialogic.Audio.update_music(file_path, volume, audio_bus, fade_length, loop, channel_id)
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Music"
set_default_color('Color7')
event_category = "Audio"
event_sorting_index = 2
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_music.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "music"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"path" : {"property": "file_path", "default": ""},
"channel" : {"property": "channel_id", "default": 0},
"fade" : {"property": "fade_length", "default": 0},
"volume" : {"property": "volume", "default": 0},
"bus" : {"property": "audio_bus", "default": "",
"suggestions": get_bus_suggestions},
"loop" : {"property": "loop", "default": true},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('file_path', ValueType.FILE, {
'left_text' : 'Play',
'file_filter' : "*.mp3, *.ogg, *.wav; Supported Audio Files",
'placeholder' : "No music",
'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
add_header_edit('channel_id', ValueType.FIXED_OPTIONS, {'left_text':'on:', 'options': get_channel_list()})
add_header_edit('file_path', ValueType.AUDIO_PREVIEW)
add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Fade Time:'})
add_body_edit('volume', ValueType.NUMBER, {'left_text':'Volume:', 'mode':2}, '!file_path.is_empty()')
add_body_edit('audio_bus', ValueType.SINGLELINE_TEXT, {'left_text':'Audio Bus:'}, '!file_path.is_empty()')
add_body_edit('loop', ValueType.BOOL, {'left_text':'Loop:'}, '!file_path.is_empty() and not file_path.to_lower().ends_with(".wav")')
func get_bus_suggestions() -> Dictionary:
var bus_name_list := {}
for i in range(AudioServer.bus_count):
bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)}
return bus_name_list
func get_channel_list() -> Array:
var channel_name_list := []
for i in ProjectSettings.get_setting('dialogic/audio/max_channels', 4):
channel_name_list.append({
'label': 'Channel %s' % (i + 1),
'value': i,
})
return channel_name_list
@@ -0,0 +1,86 @@
@tool
class_name DialogicSoundEvent
extends DialogicEvent
## Event that allows to play a sound effect. Requires the Audio subsystem!
### Settings
## The path to the file to play.
var file_path := "":
set(value):
if file_path != value:
file_path = value
ui_update_needed.emit()
## The volume to play the sound at.
var volume: float = 0
## The bus to play the sound on.
var audio_bus := ""
## If true, the sound will loop infinitely. Not recommended (as there is no way to stop it).
var loop := false
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
dialogic.Audio.play_sound(file_path, volume, audio_bus, loop)
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Sound"
set_default_color('Color7')
event_category = "Audio"
event_sorting_index = 3
help_page_path = "https://dialogic.coppolaemilio.com"
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_sound.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "sound"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_name
"path" : {"property": "file_path", "default": "",},
"volume" : {"property": "volume", "default": 0},
"bus" : {"property": "audio_bus", "default": "",
"suggestions": get_bus_suggestions},
"loop" : {"property": "loop", "default": false},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('file_path', ValueType.FILE,
{'left_text' : 'Play',
'file_filter' : '*.mp3, *.ogg, *.wav; Supported Audio Files',
'placeholder' : "Select file",
'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
add_header_edit('file_path', ValueType.AUDIO_PREVIEW)
add_body_edit('volume', ValueType.NUMBER, {'left_text':'Volume:', 'mode':2}, '!file_path.is_empty()')
add_body_edit('audio_bus', ValueType.SINGLELINE_TEXT, {'left_text':'Audio Bus:'}, '!file_path.is_empty()')
func get_bus_suggestions() -> Dictionary:
var bus_name_list := {}
for i in range(AudioServer.bus_count):
bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)}
return bus_name_list
Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://buvpjsvdt4evk"
path="res://.godot/imported/icon_music.png-ffc971ba1265164a55f745186974be5f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Audio/icon_music.png"
dest_files=["res://.godot/imported/icon_music.png-ffc971ba1265164a55f745186974be5f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3ookrkto0yh6"
path="res://.godot/imported/icon_sound.png-7a1a8a5533773d97969b6311b6a9133f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Audio/icon_sound.png"
dest_files=["res://.godot/imported/icon_sound.png-7a1a8a5533773d97969b6311b6a9133f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
+14
View File
@@ -0,0 +1,14 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_music.gd'), this_folder.path_join('event_sound.gd')]
func _get_subsystems() -> Array:
return [{'name':'Audio', 'script':this_folder.path_join('subsystem_audio.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_audio.tscn')]
@@ -0,0 +1,32 @@
@tool
extends DialogicSettingsPage
## Settings page that contains settings for the audio subsystem
const MUSIC_MAX_CHANNELS := "dialogic/audio/max_channels"
const TYPE_SOUND_AUDIO_BUS := "dialogic/audio/type_sound_bus"
func _ready() -> void:
%MusicChannelCount.value_changed.connect(_on_music_channel_count_value_changed)
%TypeSoundBus.item_selected.connect(_on_type_sound_bus_item_selected)
func _refresh() -> void:
%MusicChannelCount.value = ProjectSettings.get_setting(MUSIC_MAX_CHANNELS, 4)
%TypeSoundBus.clear()
var idx := 0
for i in range(AudioServer.bus_count):
%TypeSoundBus.add_item(AudioServer.get_bus_name(i))
if AudioServer.get_bus_name(i) == ProjectSettings.get_setting(TYPE_SOUND_AUDIO_BUS, ""):
idx = i
%TypeSoundBus.select(idx)
func _on_music_channel_count_value_changed(value:float) -> void:
ProjectSettings.set_setting(MUSIC_MAX_CHANNELS, value)
ProjectSettings.save()
func _on_type_sound_bus_item_selected(index:int) -> void:
ProjectSettings.set_setting(TYPE_SOUND_AUDIO_BUS, %TypeSoundBus.get_item_text(index))
ProjectSettings.save()
@@ -0,0 +1,54 @@
[gd_scene load_steps=3 format=3 uid="uid://c2qgetjc3mfo3"]
[ext_resource type="Script" path="res://addons/dialogic/Modules/Audio/settings_audio.gd" id="1_2iyyr"]
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_o1ban"]
[node name="Audio" type="VBoxContainer"]
offset_right = 121.0
offset_bottom = 58.0
script = ExtResource("1_2iyyr")
[node name="Label" type="Label" parent="."]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Music Channels"
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer"]
layout_mode = 2
text = "Max music channels"
[node name="HintTooltip" parent="HBoxContainer" instance=ExtResource("2_o1ban")]
layout_mode = 2
texture = null
hint_text = "Lowering this value may invalidate existing music events!"
[node name="MusicChannelCount" type="SpinBox" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
min_value = 1.0
value = 1.0
[node name="TypingSoundsTitle" type="Label" parent="."]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Typing Sounds"
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="HBoxContainer2"]
layout_mode = 2
text = "Audio Bus"
[node name="HintTooltip" parent="HBoxContainer2" instance=ExtResource("2_o1ban")]
layout_mode = 2
tooltip_text = "Lowering this value may invalidate existing music events!"
texture = null
hint_text = "The default audio bus used by TypeSound nodes."
[node name="TypeSoundBus" type="OptionButton" parent="HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
@@ -0,0 +1,219 @@
extends DialogicSubsystem
## Subsystem for managing background music and one-shot sound effects.
##
## This subsystem has many different helper methods for managing audio
## in your timeline.
## For instance, you can listen to music changes via [signal music_started].
## Whenever a new background music is started, this signal is emitted and
## contains a dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `path` | [type String] | The path to the audio resource file. [br]
## `volume` | [type float] | The volume of the audio resource that will be set to the [member base_music_player]. [br]
## `audio_bus` | [type String] | The audio bus name that the [member base_music_player] will use. [br]
## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
## `channel` | [type int] | The channel ID to play the audio on. [br]
signal music_started(info: Dictionary)
## Whenever a new sound effect is set, this signal is emitted and contains a
## dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `path` | [type String] | The path to the audio resource file. [br]
## `volume` | [type float] | The volume of the audio resource that will be set to [member base_sound_player]. [br]
## `audio_bus` | [type String] | The audio bus name that the [member base_sound_player] will use. [br]
## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
signal sound_started(info: Dictionary)
var max_channels: int:
set(value):
if max_channels != value:
max_channels = value
ProjectSettings.set_setting('dialogic/audio/max_channels', value)
ProjectSettings.save()
current_music_player.resize(value)
get:
return ProjectSettings.get_setting('dialogic/audio/max_channels', 4)
## Audio player base duplicated to play background music.
##
## Background music is long audio.
var base_music_player := AudioStreamPlayer.new()
## Reference to the last used music player.
var current_music_player: Array[AudioStreamPlayer] = []
## Audio player base, that will be duplicated to play sound effects.
##
## Sound effects are short audio.
var base_sound_player := AudioStreamPlayer.new()
#region STATE
####################################################################################################
## Clears the state on this subsystem and stops all audio.
##
## If you want to stop sounds only, use [method stop_all_sounds].
func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
for idx in max_channels:
update_music('', 0.0, '', 0.0, true, idx)
stop_all_sounds()
## Loads the state on this subsystem from the current state info.
func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void:
if load_flag == LoadFlags.ONLY_DNODES:
return
var info: Dictionary = dialogic.current_state_info.get("music", {})
if not info.is_empty() and info.has('path'):
update_music(info.path, info.volume, info.audio_bus, 0, info.loop, 0)
else:
for channel_id in info.keys():
if info[channel_id].is_empty() or info[channel_id].path.is_empty():
update_music('', 0.0, '', 0.0, true, channel_id)
else:
update_music(info[channel_id].path, info[channel_id].volume, info[channel_id].audio_bus, 0, info[channel_id].loop, channel_id)
## Pauses playing audio.
func pause() -> void:
for child in get_children():
child.stream_paused = true
## Resumes playing audio.
func resume() -> void:
for child in get_children():
child.stream_paused = false
func _on_dialogic_timeline_ended() -> void:
if not dialogic.Styles.get_layout_node():
clear_game_state()
pass
#endregion
#region MAIN METHODS
####################################################################################################
func _ready() -> void:
dialogic.timeline_ended.connect(_on_dialogic_timeline_ended)
base_music_player.name = "Music"
add_child(base_music_player)
base_sound_player.name = "Sound"
add_child(base_sound_player)
current_music_player.resize(max_channels)
## Updates the background music. Will fade out previous music.
func update_music(path := "", volume := 0.0, audio_bus := "", fade_time := 0.0, loop := true, channel_id := 0) -> void:
if channel_id > max_channels:
printerr("\tChannel ID (%s) higher than Max Music Channels (%s)" % [channel_id, max_channels])
dialogic.print_debug_moment()
return
if not dialogic.current_state_info.has('music'):
dialogic.current_state_info['music'] = {}
dialogic.current_state_info['music'][channel_id] = {'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop, 'channel':channel_id}
music_started.emit(dialogic.current_state_info['music'][channel_id])
var fader: Tween = null
if is_instance_valid(current_music_player[channel_id]) and current_music_player[channel_id].playing or !path.is_empty():
fader = create_tween()
var prev_node: Node = null
if is_instance_valid(current_music_player[channel_id]) and current_music_player[channel_id].playing:
prev_node = current_music_player[channel_id]
fader.tween_method(interpolate_volume_linearly.bind(prev_node), db_to_linear(prev_node.volume_db),0.0,fade_time)
if path:
current_music_player[channel_id] = base_music_player.duplicate()
add_child(current_music_player[channel_id])
current_music_player[channel_id].stream = load(path)
current_music_player[channel_id].volume_db = volume
if audio_bus:
current_music_player[channel_id].bus = audio_bus
if not current_music_player[channel_id].stream is AudioStreamWAV:
if "loop" in current_music_player[channel_id].stream:
current_music_player[channel_id].stream.loop = loop
elif "loop_mode" in current_music_player[channel_id].stream:
if loop:
current_music_player[channel_id].stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
else:
current_music_player[channel_id].stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
current_music_player[channel_id].play(0)
fader.parallel().tween_method(interpolate_volume_linearly.bind(current_music_player[channel_id]), 0.0, db_to_linear(volume),fade_time)
if prev_node:
fader.tween_callback(prev_node.queue_free)
## Whether music is playing.
func has_music(channel_id := 0) -> bool:
return !dialogic.current_state_info.get('music', {}).get(channel_id, {}).get('path', '').is_empty()
## Plays a given sound file.
func play_sound(path: String, volume := 0.0, audio_bus := "", loop := false) -> void:
if base_sound_player != null and !path.is_empty():
sound_started.emit({'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop})
var new_sound_node := base_sound_player.duplicate()
new_sound_node.name += "Sound"
new_sound_node.stream = load(path)
if "loop" in new_sound_node.stream:
new_sound_node.stream.loop = loop
elif "loop_mode" in new_sound_node.stream:
if loop:
new_sound_node.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
else:
new_sound_node.stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
new_sound_node.volume_db = volume
if audio_bus:
new_sound_node.bus = audio_bus
add_child(new_sound_node)
new_sound_node.play()
new_sound_node.finished.connect(new_sound_node.queue_free)
## Stops all audio.
func stop_all_sounds() -> void:
for node in get_children():
if node == base_sound_player:
continue
if "Sound" in node.name:
node.queue_free()
## Converts a linear loudness value to decibel and sets that volume to
## the given [param node].
func interpolate_volume_linearly(value: float, node: Node) -> void:
node.volume_db = linear_to_db(value)
## Returns whether the currently playing audio resource is the same as this
## event's [param resource_path], for [param channel_id].
func is_music_playing_resource(resource_path: String, channel_id := 0) -> bool:
var is_playing_resource: bool = (current_music_player.size() > channel_id
and is_instance_valid(current_music_player[channel_id])
and current_music_player[channel_id].is_playing()
and current_music_player[channel_id].stream.resource_path == resource_path)
return is_playing_resource
#endregion
@@ -0,0 +1,27 @@
extends DialogicBackground
## The default background scene.
## Extend the DialogicBackground class to create your own background scene.
@onready var image_node: TextureRect = $Image
@onready var color_node: ColorRect = $ColorRect
func _ready() -> void:
image_node.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
image_node.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
image_node.anchor_right = 1
image_node.anchor_bottom = 1
func _update_background(argument:String, _time:float) -> void:
if argument.begins_with('res://'):
image_node.texture = load(argument)
color_node.color = Color.TRANSPARENT
elif argument.is_valid_html_color():
image_node.texture = null
color_node.color = Color(argument, 1)
else:
image_node.texture = null
color_node.color = Color.from_string(argument, Color.TRANSPARENT)
@@ -0,0 +1,29 @@
[gd_scene load_steps=2 format=3 uid="uid://cl6g6ymkhjven"]
[ext_resource type="Script" path="res://addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd" id="1_nkdrp"]
[node name="DefaultBackground" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_nkdrp")
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Image" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 0
@@ -0,0 +1,7 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
func _fade() -> void:
var shader := setup_push_shader()
shader.set_shader_parameter('final_offset', Vector2.DOWN)
tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
@@ -0,0 +1,7 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
func _fade() -> void:
var shader := setup_push_shader()
shader.set_shader_parameter('final_offset', Vector2.LEFT)
tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
@@ -0,0 +1,7 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
func _fade() -> void:
var shader := setup_push_shader()
shader.set_shader_parameter('final_offset', Vector2.RIGHT)
tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
@@ -0,0 +1,7 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
func _fade() -> void:
var shader := setup_push_shader()
shader.set_shader_parameter('final_offset', Vector2.UP)
tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
@@ -0,0 +1,13 @@
extends DialogicBackgroundTransition
func _fade() -> void:
var shader := set_shader()
shader.set_shader_parameter("wipe_texture", load(this_folder.path_join("simple_fade.tres")))
shader.set_shader_parameter("feather", 1)
shader.set_shader_parameter("previous_background", prev_texture)
shader.set_shader_parameter("next_background", next_texture)
tween_shader_progress()
@@ -0,0 +1,8 @@
[gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://qak7mr560k0i"]
[sub_resource type="Gradient" id="Gradient_skd6w"]
offsets = PackedFloat32Array(1)
colors = PackedColorArray(0.423651, 0.423651, 0.423651, 1)
[resource]
gradient = SubResource("Gradient_skd6w")
@@ -0,0 +1,8 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
func _fade() -> void:
var shader := setup_swipe_shader()
var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
texture.fill_from = Vector2.DOWN
texture.fill_to = Vector2.RIGHT
tween_shader_progress()
@@ -0,0 +1,10 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
func _fade() -> void:
var shader := setup_swipe_shader()
var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
texture.fill_from = Vector2.ZERO
texture.fill_to = Vector2.RIGHT
tween_shader_progress()
@@ -0,0 +1,8 @@
extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
func _fade() -> void:
var shader := setup_swipe_shader()
var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
texture.fill_from = Vector2.RIGHT
texture.fill_to = Vector2.ZERO
tween_shader_progress()
@@ -0,0 +1,56 @@
class_name DialogicBackgroundTransition
extends Node
## Helper
var this_folder: String = get_script().resource_path.get_base_dir()
## Set before _fade() is called, will be the root node of the previous bg scene.
var prev_scene: Node
## Set before _fade() is called, will be the viewport texture of the previous bg scene.
var prev_texture: ViewportTexture
## Set before _fade() is called, will be the root node of the upcoming bg scene.
var next_scene: Node
## Set before _fade() is called, will be the viewport texture of the upcoming bg scene.
var next_texture: ViewportTexture
## Set before _fade() is called, will be the requested time for the fade
var time: float
## Set before _fade() is called, will be the background holder (TextureRect)
var bg_holder: DialogicNode_BackgroundHolder
signal transition_finished
## To be overridden by transitions
func _fade() -> void:
pass
func set_shader(path_to_shader:String=DialogicUtil.get_module_path('Background').path_join("Transitions/default_transition_shader.gdshader")) -> ShaderMaterial:
if bg_holder:
if path_to_shader.is_empty():
bg_holder.material = null
bg_holder.color = Color.TRANSPARENT
return null
bg_holder.material = ShaderMaterial.new()
bg_holder.material.shader = load(path_to_shader)
return bg_holder.material
return null
func tween_shader_progress(_progress_parameter:="progress") -> PropertyTweener:
if !bg_holder:
return
if !bg_holder.material is ShaderMaterial:
return
bg_holder.material.set_shader_parameter("progress", 0.0)
var tween := create_tween()
var tweener := tween.tween_property(bg_holder, "material:shader_parameter/progress", 1.0, time).from(0.0)
tween.tween_callback(emit_signal.bind('transition_finished'))
return tweener
@@ -0,0 +1,36 @@
shader_type canvas_item;
// Indicates how far the transition is (0 start, 1 end).
uniform float progress : hint_range(0.0, 1.0);
// The previous background, transparent if there was none.
uniform sampler2D previous_background : source_color, hint_default_transparent;
// The next background, transparent if there is none.
uniform sampler2D next_background : source_color, hint_default_transparent;
// The texture used to determine how far along the progress has to be for bending in the new background.
uniform sampler2D wipe_texture : source_color;
// The size of the trailing smear of the transition.
uniform float feather : hint_range(0.0, 1.0, 0.0001) = 0.1;
// Determines if the wipe texture should keep it's aspect ratio when scaled to the screen's size.
uniform bool keep_aspect_ratio = false;
void fragment() {
vec2 frag_coord = UV;
if(keep_aspect_ratio) {
vec2 ratio = (SCREEN_PIXEL_SIZE.x > SCREEN_PIXEL_SIZE.y) // determine how to scale
? vec2(SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x, 1) // fit to width
: vec2(1, SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y); // fit to height
frag_coord *= ratio;
frag_coord += ((vec2(1,1) - ratio) / 2.0);
}
// get the blend factor between the previous and next background.
float alpha = (texture(wipe_texture, frag_coord).r) - progress;
float blend_factor = 1. - smoothstep(0., feather, alpha + (feather * (1. -progress)));
vec4 old_frag = texture(previous_background, UV);
vec4 new_frag = texture(next_background, UV);
COLOR = mix(old_frag, new_frag, blend_factor);
}
@@ -0,0 +1,17 @@
shader_type canvas_item;
uniform vec2 final_offset = vec2(0,-1);
uniform float progress: hint_range(0.0, 1.0);
uniform sampler2D previous_background: source_color, hint_default_transparent;
uniform sampler2D next_background: source_color, hint_default_transparent;
void fragment() {
vec2 uv = UV + final_offset * progress*vec2(-1, -1);
if (uv.x < 1.0 && uv.x > 0.0 && uv.y < 1.0 && uv.y > 0.0){
COLOR = texture(previous_background, uv, 1);
} else {
COLOR = texture(next_background, uv-final_offset*vec2(-1,-1));
}
}
@@ -0,0 +1,9 @@
extends DialogicBackgroundTransition
func setup_push_shader() -> ShaderMaterial:
var shader := set_shader(DialogicUtil.get_module_path('Background').path_join("Transitions/push_transition_shader.gdshader"))
shader.set_shader_parameter("previous_background", prev_texture)
shader.set_shader_parameter("next_background", next_texture)
return shader
@@ -0,0 +1,7 @@
[gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://cweb3y3xc4uw0"]
[sub_resource type="Gradient" id="Gradient_skd6w"]
colors = PackedColorArray(0, 0, 0, 1, 0.991164, 0.991164, 0.991164, 1)
[resource]
gradient = SubResource("Gradient_skd6w")
@@ -0,0 +1,14 @@
extends DialogicBackgroundTransition
func setup_swipe_shader() -> ShaderMaterial:
var shader := set_shader()
shader.set_shader_parameter("wipe_texture", load(
DialogicUtil.get_module_path('Background').path_join("Transitions/simple_swipe_gradient.tres")
))
shader.set_shader_parameter("feather", 0.3)
shader.set_shader_parameter("previous_background", prev_texture)
shader.set_shader_parameter("next_background", next_texture)
return shader
@@ -0,0 +1,38 @@
extends Node
class_name DialogicBackground
## This is the base class for dialogic backgrounds.
## Extend it and override it's methods when you create a custom background.
## You can take a look at the default background to get an idea of how it's working.
## The subviewport container that holds this background. Set when instanced.
var viewport_container: SubViewportContainer
## The viewport that holds this background. Set when instanced.
var viewport: SubViewport
## Load the new background in here.
## The time argument is given for when [_should_do_background_update] returns true
## (then you have to do a transition in here)
func _update_background(_argument:String, _time:float) -> void:
pass
## If a background event with this scene is encountered while this background is used,
## this decides whether to create a new instance and call fade_out or just call [_update_background] # on this scene. Default is false
func _should_do_background_update(_argument:String) -> bool:
return false
## Called by dialogic when first created.
## If you return false (by default) it will attempt to animate the "modulate" property.
func _custom_fade_in(_time:float) -> bool:
return false
## Called by dialogic before removing (done by dialogic).
## If you return false (by default) it will attempt to animate the "modulate" property.
func _custom_fade_out(_time:float) -> bool:
return false
@@ -0,0 +1,156 @@
@tool
class_name DialogicBackgroundEvent
extends DialogicEvent
## Event to show scenes in the background and switch between them.
### Settings
## The scene to use. If empty, this will default to the DefaultBackground.gd scene.
## This scene supports images and fading.
## If you set it to a scene path, then that scene will be instanced.
## Learn more about custom backgrounds in the Subsystem_Background.gd docs.
var scene := ""
## The argument that is passed to the background scene.
## For the default scene it's the path to the image to show.
var argument := ""
## The time the fade animation will take. Leave at 0 for instant change.
var fade: float = 0.0
## Name of the transition to use.
var transition := ""
## Helpers for visual editor
enum ArgumentTypes {IMAGE, CUSTOM}
var _arg_type := ArgumentTypes.IMAGE :
get:
if argument.begins_with("res://"):
return ArgumentTypes.IMAGE
else:
return _arg_type
set(value):
if value == ArgumentTypes.CUSTOM:
if argument.begins_with("res://"):
argument = " "+argument
_arg_type = value
enum SceneTypes {DEFAULT, CUSTOM}
var _scene_type := SceneTypes.DEFAULT :
get:
if scene.is_empty():
return _scene_type
else:
return SceneTypes.CUSTOM
set(value):
if value == SceneTypes.DEFAULT:
scene = ""
_scene_type = value
#region EXECUTION
################################################################################
func _execute() -> void:
var final_fade_duration := fade
if dialogic.Inputs.auto_skip.enabled:
var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
final_fade_duration = min(fade, time_per_event)
dialogic.Backgrounds.update_background(scene, argument, final_fade_duration, transition)
finish()
#endregion
#region INITIALIZE
################################################################################
func _init() -> void:
event_name = "Background"
set_default_color('Color8')
event_category = "Visuals"
event_sorting_index = 0
#endregion
#region SAVE & LOAD
################################################################################
func get_shortcode() -> String:
return "background"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"scene" : {"property": "scene", "default": ""},
"arg" : {"property": "argument", "default": ""},
"fade" : {"property": "fade", "default": 0},
"transition" : {"property": "transition", "default": "",
"suggestions": get_transition_suggestions},
}
#endregion
#region EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('_scene_type', ValueType.FIXED_OPTIONS, {
'left_text' :'Show',
'options': [
{
'label': 'Background',
'value': SceneTypes.DEFAULT,
'icon': ["GuiRadioUnchecked", "EditorIcons"]
},
{
'label': 'Custom Scene',
'value': SceneTypes.CUSTOM,
'icon': ["PackedScene", "EditorIcons"]
}
]})
add_header_label("with image", "_scene_type == SceneTypes.DEFAULT")
add_header_edit("scene", ValueType.FILE,
{'file_filter':'*.tscn, *.scn; Scene Files',
'placeholder': "Custom scene",
'editor_icon': ["PackedScene", "EditorIcons"],
}, '_scene_type == SceneTypes.CUSTOM')
add_header_edit('_arg_type', ValueType.FIXED_OPTIONS, {
'left_text' : 'with',
'options': [
{
'label': 'Image',
'value': ArgumentTypes.IMAGE,
'icon': ["Image", "EditorIcons"]
},
{
'label': 'Custom Argument',
'value': ArgumentTypes.CUSTOM,
'icon': ["String", "EditorIcons"]
}
], "symbol_only": true}, "_scene_type == SceneTypes.CUSTOM")
add_header_edit('argument', ValueType.FILE,
{'file_filter':'*.jpg, *.jpeg, *.png, *.webp, *.tga, *svg, *.bmp, *.dds, *.exr, *.hdr; Supported Image Files',
'placeholder': "No Image",
'editor_icon': ["Image", "EditorIcons"],
},
'_arg_type == ArgumentTypes.IMAGE or _scene_type == SceneTypes.DEFAULT')
add_header_edit('argument', ValueType.SINGLELINE_TEXT, {}, '_arg_type == ArgumentTypes.CUSTOM')
add_body_edit("transition", ValueType.DYNAMIC_OPTIONS,
{'left_text':'Transition:',
'empty_text':'Simple Fade',
'suggestions_func':get_transition_suggestions,
'editor_icon':["PopupMenu", "EditorIcons"]})
add_body_edit("fade", ValueType.NUMBER, {'left_text':'Fade time:'})
func get_transition_suggestions(_filter:String="") -> Dictionary:
var transitions := DialogicResourceUtil.list_special_resources("BackgroundTransition")
var suggestions := {}
for i in transitions:
suggestions[DialogicUtil.pretty_name(i)] = {'value': DialogicUtil.pretty_name(i), 'editor_icon': ["PopupMenu", "EditorIcons"]}
return suggestions
#endregion
Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://517mp8gfj811"
path="res://.godot/imported/icon.png-cab4c78f59b171335e340ba590cf5991.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Background/icon.png"
dest_files=["res://.godot/imported/icon.png-cab4c78f59b171335e340ba590cf5991.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,13 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_background.gd')]
func _get_subsystems() -> Array:
return [{'name':'Backgrounds', 'script':this_folder.path_join('subsystem_backgrounds.gd')}]
func _get_special_resources() -> Dictionary:
return {&"BackgroundTransition":list_special_resources("Transitions/Defaults", ".gd")}
@@ -0,0 +1,6 @@
class_name DialogicNode_BackgroundHolder
extends ColorRect
func _ready() -> void:
add_to_group('dialogic_background_holders')
@@ -0,0 +1,186 @@
extends DialogicSubsystem
## Subsystem for managing backgrounds.
##
## This subsystem has many different helper methods for managing backgrounds.
## For instance, you can listen to background changes via
## [signal background_changed].
## Whenever a new background is set, this signal is emitted and contains a
## dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `scene` | [type String] | The scene path of the new background. [br]
## `argument` | [type String] | Information given to the background on its update routine. [br]
## `fade_time` | [type float] | The time the background may take to transition in. [br]
## `same_scene`| [type bool] | If the new background uses the same Godot scene. [br]
signal background_changed(info: Dictionary)
## The default background scene Dialogic will use.
var default_background_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('DefaultBackgroundScene/default_background.tscn'))
## The default transition Dialogic will use.
var default_transition: String = get_script().resource_path.get_base_dir().path_join("Transitions/Defaults/simple_fade.gd")
#region STATE
####################################################################################################
## Empties the current background state.
func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
update_background()
## Loads the background state from the current state info.
func load_game_state(_load_flag := LoadFlags.FULL_LOAD) -> void:
update_background(dialogic.current_state_info.get('background_scene', ''), dialogic.current_state_info.get('background_argument', ''), 0.0, default_transition, true)
#endregion
#region MAIN METHODS
####################################################################################################
## Method that adds a given scene as child of the DialogicNode_BackgroundHolder.
## It will call [_update_background()] on that scene with the given argument [argument].
## It will call [_fade_in()] on that scene with the given fade time.
## Will call fade_out on previous backgrounds scene.
##
## If the scene is the same as the last background you can bypass another instantiating
## and use the same scene.
## To do so implement [_should_do_background_update()] on the custom background scene.
## Then [_update_background()] will be called directly on that previous scene.
func update_background(scene := "", argument := "", fade_time := 0.0, transition_path:=default_transition, force := false) -> void:
var background_holder: DialogicNode_BackgroundHolder
if dialogic.has_subsystem('Styles'):
background_holder = dialogic.Styles.get_first_node_in_layout('dialogic_background_holders')
else:
background_holder = get_tree().get_first_node_in_group('dialogic_background_holders')
var info := {'scene':scene, 'argument':argument, 'fade_time':fade_time, 'same_scene':false}
if background_holder == null:
background_changed.emit(info)
return
var bg_set := false
# First try just updating the existing scene.
if scene == dialogic.current_state_info.get('background_scene', ''):
if not force and argument == dialogic.current_state_info.get('background_argument', ''):
return
for old_bg in background_holder.get_children():
if !old_bg.has_meta('node') or not old_bg.get_meta('node') is DialogicBackground:
continue
var prev_bg_node: DialogicBackground = old_bg.get_meta('node')
if prev_bg_node._should_do_background_update(argument):
prev_bg_node._update_background(argument, fade_time)
bg_set = true
info['same_scene'] = true
dialogic.current_state_info['background_scene'] = scene
dialogic.current_state_info['background_argument'] = argument
if bg_set:
background_changed.emit(info)
return
var old_viewport: SubViewportContainer = null
if background_holder.has_meta('current_viewport'):
old_viewport = background_holder.get_meta('current_viewport', null)
var new_viewport: SubViewportContainer
if scene.ends_with('.tscn') and ResourceLoader.exists(scene):
new_viewport = add_background_node(load(scene), background_holder)
elif argument:
new_viewport = add_background_node(default_background_scene, background_holder)
else:
new_viewport = null
var trans_script: Script = load(DialogicResourceUtil.guess_special_resource("BackgroundTransition", transition_path, {"path":default_transition}).path)
var trans_node := Node.new()
trans_node.set_script(trans_script)
trans_node = (trans_node as DialogicBackgroundTransition)
trans_node.bg_holder = background_holder
trans_node.time = fade_time
if old_viewport:
trans_node.prev_scene = old_viewport.get_meta('node', null)
trans_node.prev_texture = old_viewport.get_child(0).get_texture()
old_viewport.get_meta('node')._custom_fade_out(fade_time)
old_viewport.hide()
# TODO We have to call this again here because of https://github.com/godotengine/godot/issues/23729
old_viewport.get_child(0).render_target_update_mode = SubViewport.UPDATE_ALWAYS
trans_node.transition_finished.connect(old_viewport.queue_free)
if new_viewport:
trans_node.next_scene = new_viewport.get_meta('node', null)
trans_node.next_texture = new_viewport.get_child(0).get_texture()
new_viewport.get_meta('node')._update_background(argument, fade_time)
new_viewport.get_meta('node')._custom_fade_in(fade_time)
else:
background_holder.remove_meta('current_viewport')
add_child(trans_node)
if fade_time == 0:
trans_node.transition_finished.emit()
_on_transition_finished(background_holder, trans_node)
else:
trans_node.transition_finished.connect(_on_transition_finished.bind(background_holder, trans_node))
# We need to break this connection if the background_holder get's removed during the transition
background_holder.tree_exited.connect(trans_node.disconnect.bind("transition_finished", _on_transition_finished))
trans_node._fade()
background_changed.emit(info)
func _on_transition_finished(background_node:DialogicNode_BackgroundHolder, transition_node:DialogicBackgroundTransition) -> void:
if background_node.has_meta("current_viewport"):
if background_node.get_meta("current_viewport").get_meta("node", null) == transition_node.next_scene:
background_node.get_meta("current_viewport").show()
background_node.material = null
background_node.color = Color.TRANSPARENT
transition_node.queue_free()
## Adds sub-viewport with the given background scene as child to
## Dialogic scene.
func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder) -> SubViewportContainer:
var v_con := SubViewportContainer.new()
var viewport := SubViewport.new()
var b_scene := scene.instantiate()
if not b_scene is DialogicBackground:
printerr("[Dialogic] Given background scene was not of type DialogicBackground! Make sure the scene has a script that extends DialogicBackground.")
v_con.queue_free()
viewport.queue_free()
b_scene.queue_free()
return null
parent.add_child(v_con)
v_con.hide()
v_con.stretch = true
v_con.size = parent.size
v_con.set_anchors_preset(Control.PRESET_FULL_RECT)
v_con.add_child(viewport)
viewport.transparent_bg = true
viewport.disable_3d = true
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
viewport.canvas_item_default_texture_filter = ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter")
viewport.add_child(b_scene)
b_scene.viewport = viewport
b_scene.viewport_container = v_con
parent.set_meta('current_viewport', v_con)
v_con.set_meta('node', b_scene)
return v_con
## Whether a background is set.
func has_background() -> bool:
return !dialogic.current_state_info.get('background_scene', '').is_empty() or !dialogic.current_state_info.get('background_argument','').is_empty()
#endregion
+263
View File
@@ -0,0 +1,263 @@
@tool
class_name DialogicCallEvent
extends DialogicEvent
## Event that allows calling a method in a node or autoload.
### Settings
## The name of the autoload to call the method on.
var autoload_name := ""
## The name of the method to call on the given autoload.
var method := "":
set(value):
method = value
if Engine.is_editor_hint():
update_argument_info()
check_arguments_and_update_warning()
## A list of arguments to give to the call.
var arguments := []:
set(value):
arguments = value
if Engine.is_editor_hint():
check_arguments_and_update_warning()
var _current_method_arg_hints := {'a':null, 'm':null, 'info':{}}
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
var object: Object = null
var obj_path := autoload_name
var autoload: Node = dialogic.get_node('/root/'+obj_path.get_slice('.', 0))
obj_path = obj_path.trim_prefix(obj_path.get_slice('.', 0)+'.')
object = autoload
if object:
while obj_path:
if obj_path.get_slice(".", 0) in object and object.get(obj_path.get_slice(".", 0)) is Object:
object = object.get(obj_path.get_slice(".", 0))
else:
break
obj_path = obj_path.trim_prefix(obj_path.get_slice('.', 0)+'.')
if object == null:
printerr("[Dialogic] Call event failed: Unable to find autoload '",autoload_name,"'")
finish()
return
if object.has_method(method):
var args := []
for arg in arguments:
if arg is String and arg.begins_with('@'):
args.append(dialogic.Expressions.execute_string(arg.trim_prefix('@')))
else:
args.append(arg)
dialogic.current_state = dialogic.States.WAITING
await object.callv(method, args)
dialogic.current_state = dialogic.States.IDLE
else:
printerr("[Dialogic] Call event failed: Autoload doesn't have the method '", method,"'.")
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Call"
set_default_color('Color6')
event_category = "Logic"
event_sorting_index = 10
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result := "do "
if autoload_name:
result += autoload_name
if method:
result += '.'+method
if arguments.is_empty():
result += '()'
else:
result += '('
for i in arguments:
if i is String and i.begins_with('@'):
result += i.trim_prefix('@')
else:
result += var_to_str(i)
result += ', '
result = result.trim_suffix(', ')+')'
return result
func from_text(string:String) -> void:
var result := RegEx.create_from_string(r"do (?<autoload>[^\(]*)\.((?<method>[^.(]*)(\((?<arguments>.*)\))?)?").search(string.strip_edges())
if result:
autoload_name = result.get_string('autoload')
method = result.get_string('method')
if result.get_string('arguments').is_empty():
arguments = []
else:
var arr := []
for i in result.get_string('arguments').split(','):
i = i.strip_edges()
if str_to_var(i) != null:
arr.append(str_to_var(i))
else:
# Mark this as a complex expression
arr.append("@"+i)
arguments = arr
func is_valid_event(string:String) -> bool:
if string.strip_edges().begins_with("do"):
return true
return false
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"autoload" : {"property": "autoload_name", "default": ""},
"method" : {"property": "method", "default": ""},
"args" : {"property": "arguments", "default": []},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('autoload_name', ValueType.DYNAMIC_OPTIONS, {'left_text':'On autoload',
'empty_text':'Autoload',
'suggestions_func':get_autoload_suggestions,
'editor_icon':["Node", "EditorIcons"]})
add_header_edit('method', ValueType.DYNAMIC_OPTIONS, {'left_text':'call',
'empty_text':'Method',
'suggestions_func':get_method_suggestions,
'editor_icon':["Callable", "EditorIcons"]}, 'autoload_name')
add_body_edit('arguments', ValueType.ARRAY, {'left_text':'Arguments:'}, 'not autoload_name.is_empty() and not method.is_empty()')
func get_autoload_suggestions(filter:String="") -> Dictionary:
var suggestions := {}
for prop in ProjectSettings.get_property_list():
if prop.name.begins_with('autoload/'):
var autoload: String = prop.name.trim_prefix('autoload/')
suggestions[autoload] = {'value': autoload, 'tooltip':autoload, 'editor_icon': ["Node", "EditorIcons"]}
if filter.begins_with(autoload):
suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
return suggestions
func get_method_suggestions(filter:String="", temp_autoload:String = "") -> Dictionary:
var suggestions := {}
var script: Script
if temp_autoload and ProjectSettings.has_setting('autoload/'+temp_autoload):
script = load(ProjectSettings.get_setting('autoload/'+temp_autoload).trim_prefix('*'))
elif autoload_name and ProjectSettings.has_setting('autoload/'+autoload_name):
var loaded_autoload := load(ProjectSettings.get_setting('autoload/'+autoload_name).trim_prefix('*'))
if loaded_autoload is PackedScene:
var packed_scene: PackedScene = loaded_autoload
script = packed_scene.instantiate().get_script()
else:
script = loaded_autoload
if script:
for script_method in script.get_script_method_list():
if script_method.name.begins_with('@') or script_method.name.begins_with('_'):
continue
suggestions[script_method.name] = {'value': script_method.name, 'tooltip':script_method.name, 'editor_icon': ["Callable", "EditorIcons"]}
if !filter.is_empty():
suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
return suggestions
func update_argument_info() -> void:
if autoload_name and method and not _current_method_arg_hints.is_empty() and (_current_method_arg_hints.a == autoload_name and _current_method_arg_hints.m == method):
if !ResourceLoader.exists(ProjectSettings.get_setting('autoload/'+autoload_name, '').trim_prefix('*')):
_current_method_arg_hints = {}
return
var script: Script = load(ProjectSettings.get_setting('autoload/'+autoload_name, '').trim_prefix('*'))
for m in script.get_script_method_list():
if m.name == method:
_current_method_arg_hints = {'a':autoload_name, 'm':method, 'info':m}
break
func check_arguments_and_update_warning() -> void:
if not _current_method_arg_hints.has("info") or _current_method_arg_hints.info.is_empty():
ui_update_warning.emit()
return
var idx := -1
for arg in arguments:
idx += 1
if len(_current_method_arg_hints.info.args) <= idx:
continue
if _current_method_arg_hints.info.args[idx].type != 0:
if _current_method_arg_hints.info.args[idx].type != typeof(arg):
if arg is String and arg.begins_with('@'):
continue
var expected_type: String = ""
match _current_method_arg_hints.info.args[idx].type:
TYPE_BOOL: expected_type = "bool"
TYPE_STRING: expected_type = "string"
TYPE_FLOAT: expected_type = "float"
TYPE_INT: expected_type = "int"
_: expected_type = "something else"
ui_update_warning.emit('Argument '+ str(idx+1)+ ' ('+_current_method_arg_hints.info.args[idx].name+') has the wrong type (method expects '+expected_type+')!')
return
if len(arguments) < len(_current_method_arg_hints.info.args)-len(_current_method_arg_hints.info.default_args):
ui_update_warning.emit("The method is expecting at least "+str(len(_current_method_arg_hints.info.args)-len(_current_method_arg_hints.info.default_args))+ " arguments, but is given only "+str(len(arguments))+".")
return
elif len(arguments) > len(_current_method_arg_hints.info.args):
ui_update_warning.emit("The method is expecting at most "+str(len(_current_method_arg_hints.info.args))+ " arguments, but is given "+str(len(arguments))+".")
return
ui_update_warning.emit()
####################### CODE COMPLETION ########################################
################################################################################
func _get_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
if line.count(' ') == 1 and not '.' in line:
for i in get_autoload_suggestions():
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'.', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), TextNode.get_theme_icon("Node", "EditorIcons"))
elif symbol == '.' and not '(' in line:
for i in get_method_suggestions('', line.get_slice('.', 0).trim_prefix('do ')):
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'(', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), TextNode.get_theme_icon("Callable", "EditorIcons"))
func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'do', 'do ', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), _get_icon())
#################### SYNTAX HIGHLIGHTING #######################################
################################################################################
func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
dict[line.find('do')] = {"color":event_color.lerp(Highlighter.normal_color, 0.3)}
dict[line.find('do')+2] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
Highlighter.color_region(dict, Highlighter.normal_color, line, '(', ')')
Highlighter.color_region(dict, Highlighter.string_color, line, '"', '"')
Highlighter.color_word(dict, Highlighter.boolean_operator_color, line, 'true')
Highlighter.color_word(dict, Highlighter.boolean_operator_color, line, 'false')
return dict
Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://duvcdvtgy4h4b"
path="res://.godot/imported/icon.png-12e444f0ed59397c7537943ea85b475c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Call/icon.png"
dest_files=["res://.godot/imported/icon.png-12e444f0ed59397c7537943ea85b475c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
+6
View File
@@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_call.gd')]
@@ -0,0 +1,16 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'position:y', base_position.y-node.get_viewport().size.y/10, time*0.4).set_trans(Tween.TRANS_EXPO)
tween.parallel().tween_property(node, 'scale:y', base_scale.y*1.05, time*0.4).set_trans(Tween.TRANS_EXPO)
tween.tween_property(node, 'position:y', base_position.y, time*0.6).set_trans(Tween.TRANS_BOUNCE)
tween.parallel().tween_property(node, 'scale:y', base_scale.y, time*0.6).set_trans(Tween.TRANS_BOUNCE)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"bounce": {"type": AnimationType.ACTION},
}
@@ -0,0 +1,39 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
var end_scale: Vector2 = node.scale
var end_modulate_alpha := 1.0
var modulation_property := get_modulation_property()
if is_reversed:
end_scale = Vector2(0, 0)
end_modulate_alpha = 0.0
else:
node.scale = Vector2(0, 0)
var original_modulation: Color = node.get(modulation_property)
original_modulation.a = 0.0
node.set(modulation_property, original_modulation)
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
(tween.tween_property(node, "scale", end_scale, time)
.set_trans(Tween.TRANS_SPRING)
.set_ease(Tween.EASE_OUT))
tween.tween_property(node, modulation_property + ":a", end_modulate_alpha, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"bounce in": {"reversed": false, "type": AnimationType.IN},
"bounce out": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,44 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
var start_height: float = base_position.y - node.get_viewport().size.y / 5
var end_height := base_position.y
var start_modulation := 0.0
var end_modulation := 1.0
if is_reversed:
end_height = start_height
start_height = base_position.y
end_modulation = 0.0
start_modulation = 1.0
node.position.y = start_height
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
var end_postion := Vector2(base_position.x, end_height)
tween.tween_property(node, "position", end_postion, time)
var property := get_modulation_property()
var original_modulation: Color = node.get(property)
original_modulation.a = start_modulation
node.set(property, original_modulation)
var modulation_alpha := property + ":a"
tween.tween_property(node, modulation_alpha, end_modulation, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"fade in down": {"reversed": false, "type": AnimationType.IN},
"fade out up": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,34 @@
extends DialogicAnimation
func animate() -> void:
var modulation_property := get_modulation_property()
var end_modulation_alpha := 1.0
if is_reversed:
end_modulation_alpha = 0.0
else:
var original_modulation: Color = node.get(modulation_property)
original_modulation.a = 0.0
node.set(modulation_property, original_modulation)
var tween := (node.create_tween() as Tween)
if is_reversed:
tween.set_ease(Tween.EASE_IN)
else:
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.tween_property(node, modulation_property + ":a", end_modulation_alpha, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"fade in": {"reversed": false, "type": AnimationType.IN},
"fade out": {"reversed": true, "type": AnimationType.OUT},
"fade cross": {"type": AnimationType.CROSSFADE},
}
@@ -0,0 +1,44 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
var start_height: float = base_position.y + node.get_viewport().size.y / 5
var end_height := base_position.y
var start_modulation := 0.0
var end_modulation := 1.0
if is_reversed:
end_height = start_height
start_height = base_position.y
end_modulation = 0.0
start_modulation = 1.0
node.position.y = start_height
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
var end_postion := Vector2(base_position.x, end_height)
tween.tween_property(node, "position", end_postion, time)
var property := get_modulation_property()
var original_modulation: Color = node.get(property)
original_modulation.a = start_modulation
node.set(property, original_modulation)
var modulation_alpha := property + ":a"
tween.tween_property(node, modulation_alpha, end_modulation, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"fade in up": {"reversed": false, "type": AnimationType.IN},
"fade out down": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,13 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.tween_property(node, 'scale', Vector2(1,1)*1.2, time*0.5).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'scale', Vector2(1,1), time*0.5).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"heartbeat": {"type": AnimationType.ACTION},
}
@@ -0,0 +1,12 @@
extends DialogicAnimation
func animate() -> void:
await node.get_tree().process_frame
finished.emit()
func _get_named_variations() -> Dictionary:
return {
"instant in": {"reversed": false, "type": AnimationType.IN},
"instant out": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,20 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
var strength: float = node.get_viewport().size.x/60
var bound_multitween := DialogicUtil.multitween.bind(node, "position", "animation_shake_x")
tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.2)
tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.1)
tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.1)
tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.2)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"shake x": {"type": AnimationType.ACTION},
}
@@ -0,0 +1,23 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
var strength: float = node.get_viewport().size.y/40
tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.2)
tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
tween.tween_property(node, 'position:y', base_position.y, time * 0.2)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"shake y": {"type": AnimationType.ACTION},
}
@@ -0,0 +1,26 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
var target_position := base_position.y
var start_position: float = -node.get_viewport().size.y
if is_reversed:
target_position = -node.get_viewport().size.y
start_position = base_position.y
node.position.y = start_position
tween.tween_property(node, 'position:y', target_position, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"slide in down": {"reversed": false, "type": AnimationType.IN},
"slide out up": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,26 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
var end_position_x: float = base_position.x
if is_reversed:
end_position_x = -node.get_viewport().size.x / 2
else:
node.position.x = -node.get_viewport().size.x / 5
tween.tween_property(node, 'position:x', end_position_x, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"slide in left": {"reversed": false, "type": AnimationType.IN},
"slide out right": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,27 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
var viewport_x: float = node.get_viewport().size.x
var start_position_x: float = viewport_x + viewport_x / 5
var end_position_x := base_position.x
if is_reversed:
start_position_x = base_position.x
end_position_x = viewport_x + node.get_viewport().size.x / 5
node.position.x = start_position_x
tween.tween_property(node, 'position:x', end_position_x, time)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"slide in right": {"reversed": false, "type": AnimationType.IN},
"slide out left": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,25 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
var start_position_y: float = node.get_viewport().size.y * 2
var end_position_y := base_position.y
if is_reversed:
start_position_y = base_position.y
end_position_y = node.get_viewport().size.y * 2
node.position.y = start_position_y
tween.tween_property(node, 'position:y', end_position_y, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"slide in up": {"reversed": false, "type": AnimationType.IN},
"slide out down": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,25 @@
extends DialogicAnimation
func animate() -> void:
var tween := (node.create_tween() as Tween)
tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
var strength: float = 0.01
tween.set_parallel(true)
tween.tween_property(node, 'scale', Vector2(1,1)*(1+strength), time*0.3)
tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.2)
tween.tween_property(node, 'rotation', strength, time*0.1).set_delay(time*0.3)
tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.4)
tween.tween_property(node, 'rotation', strength, time*0.1).set_delay(time*0.5)
tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.6)
tween.chain().tween_property(node, 'scale', Vector2(1,1), time*0.3)
tween.parallel().tween_property(node, 'rotation', 0.0, time*0.3)
tween.finished.connect(emit_signal.bind('finished_once'))
func _get_named_variations() -> Dictionary:
return {
"tada": {"type": AnimationType.ACTION},
}
@@ -0,0 +1,36 @@
extends DialogicAnimation
func animate() -> void:
var modulate_property := get_modulation_property()
var modulate_alpha_property := modulate_property + ":a"
var end_scale: Vector2 = node.scale
var end_modulation_alpha := 1.0
if is_reversed:
end_modulation_alpha = 0.0
else:
node.scale = Vector2(0, 0)
node.position.y = base_position.y - node.get_viewport().size.y * 0.5
var original_modulation: Color = node.get(modulate_property)
original_modulation.a = 0.0
node.set(modulate_property, original_modulation)
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_EXPO)
tween.set_parallel(true)
tween.tween_property(node, "scale", end_scale, time)
tween.tween_property(node, "position", base_position, time)
tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"zoom center in": {"reversed": false, "type": AnimationType.IN},
"zoom center out": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,35 @@
extends DialogicAnimation
func animate() -> void:
var modulate_property := get_modulation_property()
var modulate_alpha_property := modulate_property + ":a"
var end_scale: Vector2 = node.scale
var end_modulation_alpha := 1.0
if is_reversed:
end_scale = Vector2(0, 0)
end_modulation_alpha = 0.0
else:
node.scale = Vector2(0,0)
var original_modulation: Color = node.get(modulate_property)
original_modulation.a = 0.0
node.set(modulate_property, original_modulation)
var tween := (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
tween.set_parallel(true)
tween.tween_property(node, "scale", end_scale, time)
tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time)
await tween.finished
finished_once.emit()
func _get_named_variations() -> Dictionary:
return {
"zoom in": {"reversed": false, "type": AnimationType.IN},
"zoom out": {"reversed": true, "type": AnimationType.OUT},
}
@@ -0,0 +1,47 @@
@tool
class_name DialogicPortraitAnimationUtil
enum AnimationType {ALL=-1, IN=1, OUT=2, ACTION=3, CROSSFADE=4}
static func guess_animation(string:String, type := AnimationType.ALL) -> String:
var default := {}
var filter := {}
var ignores := []
match type:
AnimationType.ALL:
pass
AnimationType.IN:
filter = {"type":AnimationType.IN}
ignores = ["in"]
AnimationType.OUT:
filter = {"type":AnimationType.OUT}
ignores = ["out"]
AnimationType.ACTION:
filter = {"type":AnimationType.ACTION}
AnimationType.CROSSFADE:
filter = {"type":AnimationType.CROSSFADE}
ignores = ["cross"]
return DialogicResourceUtil.guess_special_resource(&"PortraitAnimation", string, default, filter, ignores).get("path", "")
static func get_portrait_animations_filtered(type := AnimationType.ALL) -> Dictionary:
var filter := {"type":type}
if type == AnimationType.ALL:
filter["type"] = [AnimationType.IN, AnimationType.OUT, AnimationType.ACTION]
return DialogicResourceUtil.list_special_resources("PortraitAnimation", filter)
static func get_suggestions(_search_text := "", current_value:= "", empty_text := "Default", action := AnimationType.ALL) -> Dictionary:
var suggestions := {}
if empty_text and current_value:
suggestions[empty_text] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
for anim_name in get_portrait_animations_filtered(action):
suggestions[DialogicUtil.pretty_name(anim_name)] = {
'value' : DialogicUtil.pretty_name(anim_name),
'editor_icon' : ["Animation", "EditorIcons"]
}
return suggestions
@@ -0,0 +1,77 @@
class_name DialogicAnimation
extends Node
## Class that can be used to animate portraits. Can be extended to create animations.
enum AnimationType {IN=1, OUT=2, ACTION=3, CROSSFADE=4}
signal finished_once
signal finished
## Set at runtime, will be the node to animate.
var node: Node
## Set at runtime, will be the length of the animation.
var time: float
## Set at runtime, will be the base position of the node.
## Depending on the animation, this might be the start, end or both.
var base_position: Vector2
## Set at runtime, will be the base scale of the node.
var base_scale: Vector2
## Used to repeate the animation for a number of times.
var repeats: int
## If `true`, the animation will be reversed.
## This must be implemented by each animation or it will have no effect.
var is_reversed: bool = false
func _ready() -> void:
finished_once.connect(finished_one_loop)
## To be overridden. Do the actual animating/tweening in here.
## Use the properties [member node], [member time], [member base_position], etc.
func animate() -> void:
pass
## This method controls whether to repeat the animation or not.
## Animations must call this once they finished an animation.
func finished_one_loop() -> void:
repeats -= 1
if repeats > 0:
animate()
else:
finished.emit()
func pause() -> void:
if node:
node.process_mode = Node.PROCESS_MODE_DISABLED
func resume() -> void:
if node:
node.process_mode = Node.PROCESS_MODE_INHERIT
func _get_named_variations() -> Dictionary:
return {}
## If the animation wants to change the modulation, this method
## will return the property to change.
##
## The [class CanvasGroup] can use `self_modulate` instead of `modulate`
## to uniformly change the modulation of all children without additively
## overlaying the modulations.
func get_modulation_property() -> String:
if node is CanvasGroup:
return "self_modulate"
else:
return "modulate"
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c5tu88x32sjkf"
path="res://.godot/imported/custom_portrait_thumbnail.png-0513583853d87342a634d56bc0bec965.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/custom_portrait_thumbnail.png"
dest_files=["res://.godot/imported/custom_portrait_thumbnail.png-0513583853d87342a634d56bc0bec965.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,15 @@
@tool
extends DialogicPortrait
## Default portrait scene.
## The parent class has a character and portrait variable.
@export_group('Main')
@export_file var image := ""
## Load anything related to the given character and portrait
func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
apply_character_and_portrait(passed_character, passed_portrait)
apply_texture($Portrait, image)
@@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://b32paf0ll6um8"]
[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/default_portrait.gd" id="1_wn77n"]
[node name="DefaultPortrait" type="Node2D"]
script = ExtResource("1_wn77n")
[node name="Portrait" type="Sprite2D" parent="."]
centered = false
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b0eisk30btlx2"
path="res://.godot/imported/default_portrait_thumbnail.png-f9f92adb946f409def18655d6ab5c5df.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/default_portrait_thumbnail.png"
dest_files=["res://.godot/imported/default_portrait_thumbnail.png-f9f92adb946f409def18655d6ab5c5df.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,121 @@
class_name DialogicPortrait
extends Node
## Default portrait class. Should be extended by custom portraits.
## Stores the character that this scene displays.
var character: DialogicCharacter
## Stores the name of the current portrait.
var portrait: String
#region MAIN OVERRIDES
################################################################################
## This function can be overridden.
## If this returns true, it won't instance a new scene, but call
## [method _update_portrait] on this one.
## This is only relevant if the next portrait uses the same scene.
## This allows implementing transitions between portraits that use the same scene.
func _should_do_portrait_update(_character: DialogicCharacter, _portrait: String) -> bool:
return false
## If the custom portrait accepts a change, then accept it here
## You should position your portrait so that the root node is at the pivot point*.
## For example for a simple sprite this code would work:
## >>> $Sprite.position = $Sprite.get_rect().size * Vector2(-0.5, -1)
##
## * this depends on the portrait containers, but it will most likely be the bottom center (99% of cases)
func _update_portrait(_passed_character: DialogicCharacter, _passed_portrait: String) -> void:
pass
## This should be implemented. It is used for sizing in the
## character editor preview and in portrait containers.
## Scale and offset will be applied by Dialogic.
## For example, a simple sprite:
## >>> return Rect2($Sprite.position, $Sprite.get_rect().size)
##
## This will only work as expected if the portrait is positioned so that the
## root is at the pivot point.
##
## If you've used apply_texture this should work automatically.
func _get_covered_rect() -> Rect2:
if has_meta('texture_holder_node') and get_meta('texture_holder_node', null) != null and is_instance_valid(get_meta('texture_holder_node')):
var node: Node = get_meta('texture_holder_node')
if node is Sprite2D or node is TextureRect:
return Rect2(node.position, node.get_rect().size)
return Rect2()
## If implemented, this is called when the mirror changes
func _set_mirror(mirror:bool) -> void:
if has_meta('texture_holder_node') and get_meta('texture_holder_node', null) != null and is_instance_valid(get_meta('texture_holder_node')):
var node: Node = get_meta('texture_holder_node')
if node is Sprite2D or node is TextureRect:
node.flip_h = mirror
## Function to accept and use the extra data, if the custom portrait wants to accept it
func _set_extra_data(_data: String) -> void:
pass
#endregion
#region HIGHLIGHT OVERRIDES
################################################################################
## Called when this becomes the active speaker
func _highlight() -> void:
pass
## Called when this stops being the active speaker
func _unhighlight() -> void:
pass
#endregion
#region HELPERS
################################################################################
## Helper that quickly setups and checks the character and portrait.
func apply_character_and_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
if passed_portrait == "" or not passed_portrait in passed_character.portraits.keys():
passed_portrait = passed_character.default_portrait
portrait = passed_portrait
character = passed_character
func apply_texture(node:Node, texture_path:String) -> void:
if not character or not character.portraits.has(portrait):
return
if not "texture" in node:
return
node.texture = null
if not ResourceLoader.exists(texture_path):
# This is a leftover from alpha.
# Removing this will break any portraits made before alpha-10
if ResourceLoader.exists(character.portraits[portrait].get('image', '')):
texture_path = character.portraits[portrait].get('image', '')
else:
return
node.texture = load(texture_path)
if node is Sprite2D or node is TextureRect:
if node is Sprite2D:
node.centered = false
node.scale = Vector2.ONE
if node is TextureRect:
if !is_inside_tree():
await ready
node.position = node.get_rect().size * Vector2(-0.5, -1)
set_meta('texture_holder_node', node)
#endregion
@@ -0,0 +1,578 @@
@tool
class_name DialogicCharacterEvent
extends DialogicEvent
## Event that allows to manipulate character portraits.
enum Actions {JOIN, LEAVE, UPDATE}
### Settings
## The type of action of this event (JOIN/LEAVE/UPDATE). See [Actions].
var action := Actions.JOIN
## The character that will join/leave/update.
var character: DialogicCharacter = null
## For Join/Update, this will be the portrait of the character that is shown.
## Not used on Leave.
## If empty, the default portrait will be used.
var portrait := ""
## The index of the position this character should move to
var transform := "center"
## Name of the animation script (extending DialogicAnimation).
## On Join/Leave empty (default) will fallback to the animations set in the settings.
## On Update empty will mean no animation.
var animation_name := ""
## Length of the animation.
var animation_length: float = 0.5
## How often the animation is repeated. Only for Update events.
var animation_repeats: int = 1
## If true, the events waits for the animation to finish before the next event starts.
var animation_wait := false
## The fade animation to use. If left empty, the default cross-fade animation AND time will be used.
var fade_animation := ""
var fade_length := 0.5
## For Update only. If bigger then 0, the portrait will tween to the
## new position (if changed) in this time (in seconds).
var transform_time: float = 0.0
var transform_ease := Tween.EaseType.EASE_IN_OUT
var transform_trans := Tween.TransitionType.TRANS_SINE
var ease_options := [
{'label': 'In', 'value': Tween.EASE_IN},
{'label': 'Out', 'value': Tween.EASE_OUT},
{'label': 'In_Out', 'value': Tween.EASE_IN_OUT},
{'label': 'Out_In', 'value': Tween.EASE_OUT_IN},
]
var trans_options := [
{'label': 'Linear', 'value': Tween.TRANS_LINEAR},
{'label': 'Sine', 'value': Tween.TRANS_SINE},
{'label': 'Quint', 'value': Tween.TRANS_QUINT},
{'label': 'Quart', 'value': Tween.TRANS_QUART},
{'label': 'Quad', 'value': Tween.TRANS_QUAD},
{'label': 'Expo', 'value': Tween.TRANS_EXPO},
{'label': 'Elastic', 'value': Tween.TRANS_ELASTIC},
{'label': 'Cubic', 'value': Tween.TRANS_CUBIC},
{'label': 'Circ', 'value': Tween.TRANS_CIRC},
{'label': 'Bounce', 'value': Tween.TRANS_BOUNCE},
{'label': 'Back', 'value': Tween.TRANS_BACK},
{'label': 'Spring', 'value': Tween.TRANS_SPRING}
]
## The z_index that the portrait should have.
var z_index: int = 0
## If true, the portrait will be set to mirrored.
var mirrored := false
## If set, will be passed to the portrait scene.
var extra_data := ""
### Helpers
## Indicators for whether something should be updated (UPDATE mode only)
var set_portrait := false
var set_transform := false
var set_z_index := false
var set_mirrored := false
## Used to set the character resource from the unique name identifier and vice versa
var character_identifier: String:
get:
if character_identifier == '--All--':
return '--All--'
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 character and not character.portraits.has(portrait):
portrait = ""
ui_update_needed.emit()
var regex := RegEx.create_from_string(r'(?<type>join|update|leave)\s*(")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?<portrait>.*)\))?(\s*(?<transform>[^\[]*))?(\s*\[(?<shortcode>.*)\])?')
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
if not character and not character_identifier == "--All--":
finish()
return
# Calculate animation time (can be shortened during skipping)
var final_animation_length: float = animation_length
var final_position_move_time: float = transform_time
if dialogic.Inputs.auto_skip.enabled:
var max_time: float = dialogic.Inputs.auto_skip.time_per_event
final_animation_length = min(max_time, animation_length)
final_position_move_time = min(max_time, transform_time)
# JOIN -------------------------------------
if action == Actions.JOIN:
if dialogic.has_subsystem('History') and !dialogic.Portraits.is_character_joined(character):
var character_name_text := dialogic.Text.get_character_name_parsed(character)
dialogic.History.store_simple_history_entry(character_name_text + " joined", event_name, {'character': character_name_text, 'mode':'Join'})
await dialogic.Portraits.join_character(
character, portrait, transform,
mirrored, z_index, extra_data,
animation_name, final_animation_length, animation_wait)
# LEAVE -------------------------------------
elif action == Actions.LEAVE:
if character_identifier == '--All--':
if dialogic.has_subsystem('History') and len(dialogic.Portraits.get_joined_characters()):
dialogic.History.store_simple_history_entry("Everyone left", event_name, {'character': "All", 'mode':'Leave'})
await dialogic.Portraits.leave_all_characters(
animation_name,
final_animation_length,
animation_wait
)
elif character:
if dialogic.has_subsystem('History') and dialogic.Portraits.is_character_joined(character):
var character_name_text := dialogic.Text.get_character_name_parsed(character)
dialogic.History.store_simple_history_entry(character_name_text+" left", event_name, {'character': character_name_text, 'mode':'Leave'})
await dialogic.Portraits.leave_character(
character,
animation_name,
final_animation_length,
animation_wait
)
# UPDATE -------------------------------------
elif action == Actions.UPDATE:
if not character or not dialogic.Portraits.is_character_joined(character):
finish()
return
dialogic.Portraits.change_character_extradata(character, extra_data)
if set_portrait:
dialogic.Portraits.change_character_portrait(character, portrait, fade_animation, fade_length)
if set_mirrored:
dialogic.Portraits.change_character_mirror(character, mirrored)
if set_z_index:
dialogic.Portraits.change_character_z_index(character, z_index)
if set_transform:
dialogic.Portraits.move_character(character, transform, final_position_move_time, transform_ease, transform_trans)
if animation_name:
var final_animation_repetitions: int = animation_repeats
if dialogic.Inputs.auto_skip.enabled:
var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
var time_for_repetitions: float = time_per_event / animation_repeats
final_animation_length = time_for_repetitions
var animation := dialogic.Portraits.animate_character(
character,
animation_name,
final_animation_length,
final_animation_repetitions,
)
if animation_wait:
dialogic.current_state = DialogicGameHandler.States.ANIMATING
await animation.finished
dialogic.current_state = DialogicGameHandler.States.IDLE
finish()
#region INITIALIZE
###############################################################################
func _init() -> void:
event_name = "Character"
set_default_color('Color2')
event_category = "Main"
event_sorting_index = 2
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon.svg'))
#endregion
#region SAVING, LOADING, DEFAULTS
################################################################################
func to_text() -> String:
var result_string := ""
# ACTIONS
match action:
Actions.JOIN: result_string += "join "
Actions.LEAVE: result_string += "leave "
Actions.UPDATE: result_string += "update "
var default_values := DialogicUtil.get_custom_event_defaults(event_name)
# CHARACTER IDENTIFIER
if action == Actions.LEAVE and character_identifier == '--All--':
result_string += "--All--"
elif character:
var name := DialogicResourceUtil.get_unique_identifier(character.resource_path)
if name.count(" ") > 0:
name = '"' + name + '"'
result_string += name
# PORTRAIT
if portrait.strip_edges() != default_values.get('portrait', ''):
if action != Actions.LEAVE and (action != Actions.UPDATE or set_portrait):
result_string += " (" + portrait + ")"
# TRANSFORM
if action == Actions.JOIN or (action == Actions.UPDATE and set_transform):
result_string += " " + str(transform)
# SETS:
if action == Actions.JOIN or action == Actions.LEAVE:
set_mirrored = mirrored != default_values.get("mirrored", false)
set_z_index = z_index != default_values.get("z_index", 0)
var shortcode := store_to_shortcode_parameters()
if shortcode != "":
result_string += " [" + shortcode + "]"
return result_string
func from_text(string:String) -> void:
# Load default character
character = DialogicResourceUtil.get_character_resource(character_identifier)
var result := regex.search(string)
# ACTION
match result.get_string('type'):
"join": action = Actions.JOIN
"leave": action = Actions.LEAVE
"update": action = Actions.UPDATE
# CHARACTER
var given_name := result.get_string('name').strip_edges()
var given_portrait := result.get_string('portrait').strip_edges()
var given_transform := result.get_string('transform').strip_edges()
if given_name:
if action == Actions.LEAVE and given_name == "--All--":
character_identifier = '--All--'
else:
character = DialogicResourceUtil.get_character_resource(given_name)
# PORTRAIT
if given_portrait:
portrait = given_portrait.trim_prefix('(').trim_suffix(')')
set_portrait = true
# TRANSFORM
if given_transform:
transform = given_transform
set_transform = true
# SHORTCODE
if not result.get_string('shortcode'):
return
load_from_shortcode_parameters(result.get_string('shortcode'))
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"action" : {"property": "action", "default": 0, "custom_stored":true,
"suggestions": func(): return {'Join':
{'value':Actions.JOIN},
'Leave':{'value':Actions.LEAVE},
'Update':{'value':Actions.UPDATE}}},
"character" : {"property": "character_identifier", "default": "", "custom_stored":true,},
"portrait" : {"property": "portrait", "default": "", "custom_stored":true,},
"transform" : {"property": "transform", "default": "center", "custom_stored":true,},
"animation" : {"property": "animation_name", "default": ""},
"length" : {"property": "animation_length", "default": 0.5},
"wait" : {"property": "animation_wait", "default": false},
"repeat" : {"property": "animation_repeats", "default": 1},
"z_index" : {"property": "z_index", "default": 0},
"mirrored" : {"property": "mirrored", "default": false},
"fade" : {"property": "fade_animation", "default":""},
"fade_length" : {"property": "fade_length", "default":0.5},
"move_time" : {"property": "transform_time", "default": 0.0},
"move_ease" : {"property": "transform_ease", "default": Tween.EaseType.EASE_IN_OUT,
"suggestions": func(): return list_to_suggestions(ease_options)},
"move_trans" : {"property": "transform_trans", "default": Tween.TransitionType.TRANS_SINE,
"suggestions": func(): return list_to_suggestions(trans_options)},
"extra_data" : {"property": "extra_data", "default": ""},
}
func is_valid_event(string:String) -> bool:
if string.begins_with("join") or string.begins_with("leave") or string.begins_with("update"):
return true
return false
#endregion
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('action', ValueType.FIXED_OPTIONS, {
'options': [
{
'label': 'Join',
'value': Actions.JOIN,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/join.svg")
},
{
'label': 'Leave',
'value': Actions.LEAVE,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/leave.svg")
},
{
'label': 'Update',
'value': Actions.UPDATE,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/update.svg")
}
]
})
add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS,
{'placeholder' : 'Character',
'file_extension' : '.dch',
'mode' : 2,
'suggestions_func' : get_character_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg"),
'autofocus' : true})
add_header_edit('set_portrait', ValueType.BOOL_BUTTON,
{'icon':load("res://addons/dialogic/Modules/Character/update_portrait.svg"),
'tooltip':'Change Portrait'}, "should_show_portrait_selector() and action == Actions.UPDATE")
add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS,
{'placeholder' : 'Default',
'collapse_when_empty':true,
'suggestions_func' : get_portrait_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")},
'should_show_portrait_selector() and (action != Actions.UPDATE or set_portrait)')
add_header_edit('set_transform', ValueType.BOOL_BUTTON,
{'icon': load("res://addons/dialogic/Modules/Character/update_position.svg"), 'tooltip':'Change Position'}, "character != null and !has_no_portraits() and action == Actions.UPDATE")
add_header_label('at position', 'character != null and !has_no_portraits() and action == Actions.JOIN')
add_header_label('to position', 'character != null and !has_no_portraits() and action == Actions.UPDATE and set_transform')
add_header_edit('transform', ValueType.DYNAMIC_OPTIONS,
{'placeholder' : 'center',
'mode' : 0,
'suggestions_func' : get_position_suggestions,
'tooltip' : "You can use a predefined position or a custom transform like 'pos=x0.5y1 size=x0.5y1 rot=10'.\nLearn more about this in the documentation."},
'character != null and !has_no_portraits() and action != %s and (action != Actions.UPDATE or set_transform)' %Actions.LEAVE)
# Body
add_body_edit('fade_animation', ValueType.DYNAMIC_OPTIONS,
{'left_text' : 'Fade:',
'suggestions_func' : get_fade_suggestions,
'editor_icon' : ["Animation", "EditorIcons"],
'placeholder' : 'Default',
'enable_pretty_name' : true},
'should_show_fade_options()')
add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
'should_show_fade_options() and !fade_animation.is_empty()')
add_body_line_break("should_show_fade_options()")
add_body_edit('animation_name', ValueType.DYNAMIC_OPTIONS,
{'left_text' : 'Animation:',
'suggestions_func' : get_animation_suggestions,
'editor_icon' : ["Animation", "EditorIcons"],
'placeholder' : 'Default',
'enable_pretty_name' : true},
'should_show_animation_options()')
add_body_edit('animation_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_wait', ValueType.BOOL, {'left_text':'Await end:'},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_repeats', ValueType.NUMBER, {'left_text':'Repeat:', 'mode':1, "min":1},
'should_show_animation_options() and !animation_name.is_empty() and action == %s)' %Actions.UPDATE)
add_body_line_break()
add_body_edit('transform_time', ValueType.NUMBER, {'left_text':'Movement duration:', "min":0},
"should_show_transform_options()")
add_body_edit("transform_trans", ValueType.FIXED_OPTIONS, {'options':trans_options, 'left_text':"Trans:"}, 'should_show_transform_options() and transform_time > 0')
add_body_edit("transform_ease", ValueType.FIXED_OPTIONS, {'options':ease_options, 'left_text':"Ease:"}, 'should_show_transform_options() and transform_time > 0')
add_body_edit('set_z_index', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_z_index.svg"), 'tooltip':'Change Z-Index'}, "character != null and action == Actions.UPDATE")
add_body_edit('z_index', ValueType.NUMBER, {'left_text':'Z-index:', 'mode':1},
'action != %s and (action != Actions.UPDATE or set_z_index)' %Actions.LEAVE)
add_body_edit('set_mirrored', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_mirror.svg"), 'tooltip':'Change Mirroring'}, "character != null and action == Actions.UPDATE")
add_body_edit('mirrored', ValueType.BOOL, {'left_text':'Mirrored:'},
'action != %s and (action != Actions.UPDATE or set_mirrored)' %Actions.LEAVE)
add_body_edit('extra_data', ValueType.SINGLELINE_TEXT, {'left_text':'Extra Data:'}, 'action != Actions.LEAVE')
func should_show_transform_options() -> bool:
return action == Actions.UPDATE and set_transform
func should_show_animation_options() -> bool:
return (character and !character.portraits.is_empty()) or character_identifier == '--All--'
func should_show_fade_options() -> bool:
return action == Actions.UPDATE and set_portrait and character and not character.portraits.is_empty()
func should_show_portrait_selector() -> bool:
return character and len(character.portraits) > 1 and action != Actions.LEAVE
func has_no_portraits() -> bool:
return character and character.portraits.is_empty()
func get_character_suggestions(search_text:String) -> Dictionary:
return DialogicUtil.get_character_suggestions(search_text, character, false, action == Actions.LEAVE, editor_node)
func get_portrait_suggestions(search_text:String) -> Dictionary:
var empty_text := "Don't Change"
if action == Actions.JOIN:
empty_text = "Default portrait"
return DialogicUtil.get_portrait_suggestions(search_text, character, true, empty_text)
func get_position_suggestions(search_text:String='') -> Dictionary:
return DialogicUtil.get_portrait_position_suggestions(search_text)
func get_animation_suggestions(search_text:String='') -> Dictionary:
var DPAU := DialogicPortraitAnimationUtil
match action:
Actions.JOIN:
return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.IN)
Actions.LEAVE:
return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.OUT)
Actions.UPDATE:
return DPAU.get_suggestions(search_text, animation_name, "None", DPAU.AnimationType.ACTION)
return {}
func get_fade_suggestions(search_text:String='') -> Dictionary:
return DialogicPortraitAnimationUtil.get_suggestions(search_text, fade_animation, "Default", DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
####################### CODE COMPLETION ########################################
################################################################################
func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
var line_until_caret: String = CodeCompletionHelper.get_line_untill_caret(line)
if symbol == ' ' and line_until_caret.count(' ') == 1:
CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_MEMBER)
if line.begins_with('leave'):
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'All', '--All-- ', event_color, TextNode.get_theme_icon("GuiEllipsis", "EditorIcons"))
if symbol == '(':
var completion_character := regex.search(line).get_string('name')
CodeCompletionHelper.suggest_portraits(TextNode, completion_character)
elif not '[' in line_until_caret and symbol == ' ':
if not line.begins_with("leave"):
for position in get_position_suggestions():
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, position, position+' ', TextNode.syntax_highlighter.normal_color)
# Shortcode Part
if '[' in line_until_caret:
# Suggest Parameters
if symbol == '[' or symbol == ' ' and line_until_caret.count('"')%2 == 0:# and (symbol == "[" or (symbol == " " and line_until_caret.rfind('="') < line_until_caret.rfind('"')-1)):
suggest_parameter("animation", line, TextNode)
if "animation=" in line:
for param in ["length", "wait"]:
suggest_parameter(param, line, TextNode)
if line.begins_with('update'):
suggest_parameter("repeat", line, TextNode)
if line.begins_with("update"):
for param in ["move_time", "move_trans", "move_ease"]:
suggest_parameter(param, line, TextNode)
if not line.begins_with('leave'):
for param in ["mirrored", "z_index", "extra_data"]:
suggest_parameter(param, line, TextNode)
# Suggest Values
else:
var current_param: RegExMatch = CodeCompletionHelper.completion_shortcode_param_getter_regex.search(line)
if not current_param:
return
match current_param.get_string("param"):
"animation":
var animations := {}
if line.begins_with('join'):
animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.IN)
elif line.begins_with('update'):
animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.ACTION)
elif line.begins_with('leave'):
animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.OUT)
for script: String in animations:
TextNode.add_code_completion_option(CodeEdit.KIND_VARIABLE, DialogicUtil.pretty_name(script), DialogicUtil.pretty_name(script), TextNode.syntax_highlighter.normal_color, null, '" ')
"wait", "mirrored":
CodeCompletionHelper.suggest_bool(TextNode, TextNode.syntax_highlighter.normal_color)
"move_trans":
CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(trans_options), TextNode, TextNode.syntax_highlighter.normal_color)
"move_ease":
CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(ease_options), TextNode, TextNode.syntax_highlighter.normal_color)
func suggest_parameter(parameter:String, line:String, TextNode:TextEdit) -> void:
if not parameter + "=" in line:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, parameter, parameter + '="', TextNode.syntax_highlighter.normal_color)
func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'join', 'join ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/join.svg'))
TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'leave', 'leave ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/leave.svg'))
TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'update', 'update ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/update.svg'))
#################### SYNTAX HIGHLIGHTING #######################################
################################################################################
func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
var word := line.get_slice(' ', 0)
dict[line.find(word)] = {"color":event_color}
dict[line.find(word)+len(word)] = {"color":Highlighter.normal_color}
var result := regex.search(line)
if result.get_string('name'):
dict[result.get_start('name')] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
dict[result.get_end('name')] = {"color":Highlighter.normal_color}
if result.get_string('portrait'):
dict[result.get_start('portrait')] = {"color":event_color.lerp(Highlighter.normal_color, 0.6)}
dict[result.get_end('portrait')] = {"color":Highlighter.normal_color}
if result.get_string('shortcode'):
dict = Highlighter.color_shortcode_content(dict, line, result.get_start('shortcode'), result.get_end('shortcode'), event_color)
return dict
## HELPER
func list_to_suggestions(list:Array) -> Dictionary:
return list.reduce(
func(accum, value):
accum[value.label] = value
accum[value.label]["text_alt"] = [value.label.to_lower()]
return accum,
{})
@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dn5jx2ucynfio"
path="res://.godot/imported/icon.png-a6ef7c3eeb0fb100c7d0b0c505ea4b6f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon.png"
dest_files=["res://.godot/imported/icon.png-a6ef7c3eeb0fb100c7d0b0c505ea4b6f.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
@@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.0625 10.483C21.0625 13.5111 18.6077 15.9659 15.5795 15.9659C12.5514 15.9659 10.0966 13.5111 10.0966 10.483C10.0966 7.4548 12.5514 5 15.5795 5C18.6077 5 21.0625 7.4548 21.0625 10.483Z" fill="white"/>
<path d="M22.3158 24.1393C22.3158 27 19.3349 27 15.6579 27C11.9808 27 9 27 9 24.1393C9 19.0046 11.9808 14.8421 15.6579 14.8421C19.3349 14.8421 22.3158 19.0046 22.3158 24.1393Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 507 B

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://q6lanmf18ii6"
path="res://.godot/imported/icon.svg-4f340f6efbb83004dbd5c761dd1dc448.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/icon.svg"
dest_files=["res://.godot/imported/icon.svg-4f340f6efbb83004dbd5c761dd1dc448.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu6cj55m6dr78"
path="res://.godot/imported/icon_position.png-221b7c348ec45b22fd4df49fdb92a7aa.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/icon_position.png"
dest_files=["res://.godot/imported/icon_position.png-221b7c348ec45b22fd4df49fdb92a7aa.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,58 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_character.gd')]
func _get_subsystems() -> Array:
return [{'name':'Portraits', 'script':this_folder.path_join('subsystem_portraits.gd')}, {'name':'PortraitContainers', 'script':this_folder.path_join('subsystem_containers.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_portraits.tscn')]
func _get_text_effects() -> Array[Dictionary]:
return [
{'command':'portrait', 'subsystem':'Portraits', 'method':'text_effect_portrait', 'arg':true},
{'command':'extra_data', 'subsystem':'Portraits', 'method':'text_effect_extradata', 'arg':true},
]
func _get_special_resources() -> Dictionary:
return {&'PortraitAnimation': list_animations("DefaultAnimations")}
func _get_portrait_scene_presets() -> Array[Dictionary]:
return [
{
"path": "",
"name": "Default Scene",
"description": "The default scene defined in Settings>Portraits.",
"author":"Dialogic",
"type": "Default",
"icon":"",
"preview_image":[this_folder.path_join("default_portrait_thumbnail.png")],
"documentation":"",
},
{
"path": "CUSTOM",
"name": "Custom Scene",
"description": "A custom scene. Should extend DialogicPortrait and be in @tool mode.",
"author":"Dialogic",
"type": "Custom",
"icon":"",
"preview_image":[this_folder.path_join("custom_portrait_thumbnail.png")],
"documentation":"https://docs.dialogic.pro/custom-portraits.html",
},
{
"path": this_folder.path_join("default_portrait.tscn"),
"name": "Simple Image Portrait",
"description": "Can display images as portraits. Does nothing else.",
"author":"Dialogic",
"type": "General",
"icon":"",
"preview_image":[this_folder.path_join("simple_image_portrait_thumbnail.png")],
"documentation":"",
}
]
@@ -0,0 +1,258 @@
@tool
class_name DialogicNode_PortraitContainer
extends Control
## Node that defines a position for dialogic portraits and how to display portraits at that position.
enum PositionModes {
POSITION, ## This container can be joined/moved to with the Character Event
SPEAKER, ## This container is joined/left automatically based on the speaker.
}
@export var mode := PositionModes.POSITION
@export_subgroup('Mode: Position')
## The position this node corresponds to.
@export var container_ids: PackedStringArray = ["1"]
@export_subgroup('Mode: Speaker')
## Can be used to use a different portrait.
## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
@export var portrait_prefix := ''
@export_subgroup('Portrait Placement')
enum SizeModes {
KEEP, ## The height and width of the container have no effect, only the origin.
FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
}
## Defines how to affect the scale of the portrait
@export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT :
set(mode):
size_mode = mode
_update_debug_portrait_transform()
## If true, portraits will be mirrored in this position.
@export var mirrored := false :
set(mirror):
mirrored = mirror
_update_debug_portrait_scene()
@export_group('Origin', 'origin')
enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
## The portrait will be placed relative to this point in the container.
@export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER :
set(anchor):
origin_anchor = anchor
_update_debug_origin()
## An offset to apply to the origin. Rarely useful.
@export var origin_offset := Vector2() :
set(offset):
origin_offset = offset
_update_debug_origin()
enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
## Usually you want to rotate or scale around the portrait origin.
## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
@export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
@export var pivot_value := Vector2()
@export_group('Debug', 'debug')
## A character that will be displayed in the editor, useful for getting the right size.
@export var debug_character: DialogicCharacter = null:
set(character):
debug_character = character
_update_debug_portrait_scene()
@export var debug_character_portrait := "":
set(portrait):
debug_character_portrait = portrait
_update_debug_portrait_scene()
var debug_character_holder_node: Node2D = null
var debug_character_scene_node: Node = null
var debug_origin: Sprite2D = null
var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
# Used if no debug character is specified
var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))
var ignore_resize := false
func _ready() -> void:
match mode:
PositionModes.POSITION:
add_to_group('dialogic_portrait_con_position')
PositionModes.SPEAKER:
add_to_group('dialogic_portrait_con_speaker')
if Engine.is_editor_hint():
resized.connect(_update_debug_origin)
if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
debug_origin = Sprite2D.new()
add_child(debug_origin)
debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")
_update_debug_origin()
_update_debug_portrait_scene()
else:
resized.connect(update_portrait_transforms)
################################################################################
## MAIN METHODS
################################################################################
func update_portrait_transforms() -> void:
if ignore_resize:
return
match pivot_mode:
PivotModes.AT_ORIGIN:
pivot_offset = _get_origin_position()
PivotModes.PERCENTAGE:
pivot_offset = size*pivot_value
PivotModes.PIXELS:
pivot_offset = pivot_value
for child in get_children():
DialogicUtil.autoload().Portraits._update_character_transform(child)
## Returns a Rect2 with the position as the position and the scale as the size.
func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
var transform := Rect2()
transform.position = _get_origin_position()
# Mode that ignores the containers size
if size_mode == SizeModes.KEEP:
transform.size = Vector2(1,1) * character_scale
# Mode that makes sure neither height nor width go out of container
elif size_mode == SizeModes.FIT_IGNORE_SCALE:
if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
else:
transform.size = Vector2(1,1) * size.y/portrait_rect.size.y
# Mode that stretches the portrait to fill the whole container
elif size_mode == SizeModes.FIT_STRETCH:
transform.size = size/portrait_rect.size
# Mode that size the character so 100% size fills the height
elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale
return transform
## Returns the current origin position
func _get_origin_position(rect_size = null) -> Vector2:
if rect_size == null:
rect_size = size
return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset
func is_container(id:Variant) -> bool:
return str(id) in container_ids
#region DEBUG METHODS
################################################################################
### USE THIS TO DEBUG THE POSITIONS
#func _draw():
#draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
#draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
#
#func _process(delta:float) -> void:
#queue_redraw()
## Loads the debug_character with the debug_character_portrait
## Creates a holder node and applies mirror
func _update_debug_portrait_scene() -> void:
if !Engine.is_editor_hint():
return
if is_instance_valid(debug_character_holder_node):
for child in get_children():
if child != debug_origin:
child.free()
# Get character
var character := _get_debug_character()
if not character is DialogicCharacter or character.portraits.is_empty():
return
# Determine portrait
var debug_portrait := debug_character_portrait
if debug_portrait.is_empty():
debug_portrait = character.default_portrait
if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
if portrait_prefix+debug_portrait in character.portraits:
debug_portrait = portrait_prefix+debug_portrait
if not debug_portrait in character.portraits:
debug_portrait = character.default_portrait
var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)
# Determine scene
var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene)
if portrait_scene_path.is_empty():
portrait_scene_path = default_portrait_scene
debug_character_scene_node = load(portrait_scene_path).instantiate()
if !is_instance_valid(debug_character_scene_node):
return
# Load portrait
DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
debug_character_scene_node._update_portrait(character, debug_portrait)
# Add character node
if !is_instance_valid(debug_character_holder_node):
debug_character_holder_node = Node2D.new()
add_child(debug_character_holder_node)
# Add portrait node
debug_character_holder_node.add_child(debug_character_scene_node)
move_child(debug_character_holder_node, 0)
debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))
_update_debug_portrait_transform()
## Set's the size and position of the holder and scene node
## according to the size_mode
func _update_debug_portrait_transform() -> void:
if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
return
var character := _get_debug_character()
var portrait_info := character.get_portrait_info(debug_character_portrait)
var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
debug_character_holder_node.position = transform.position
debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset
debug_character_holder_node.scale = transform.size
## Updates the debug origins position. Also calls _update_debug_portrait_transform()
func _update_debug_origin() -> void:
if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
return
debug_origin.position = _get_origin_position()
_update_debug_portrait_transform()
## Returns the debug character or the default debug character
func _get_debug_character() -> DialogicCharacter:
return debug_character if debug_character != null else default_debug_character
#endregion
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" version="1.1" id="svg13818" sodipodi:docname="event_portrait_position.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs13822" />
<sodipodi:namedview id="namedview13820" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="false" inkscape:zoom="6.5390625" inkscape:cx="-20.109916" inkscape:cy="11.622461" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg13818" />
<path d="m 41.97648,22.585683 c 0,5.509774 -4.466676,9.976311 -9.976424,9.976311 -5.509775,0 -9.976338,-4.466537 -9.976338,-9.976311 0,-5.509775 4.466563,-9.976328 9.976338,-9.976328 5.509748,0 9.976424,4.466553 9.976424,9.976328 z" fill="#ffffff" id="path13814" style="stroke-width:2.74349" />
<path d="m 43.971538,47.35052 c 0,5.164069 -5.359673,5.164069 -11.971482,5.164069 -6.611724,0 -11.971595,0 -11.971595,-5.164069 0,-9.269422 5.359871,-16.783783 11.971595,-16.783783 6.611808,0 11.971482,7.514361 11.971482,16.783783 z" fill="#ffffff" id="path13816" style="stroke-width:2.74349" />
<rect style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:4.081;stroke-linecap:round;stroke-miterlimit:3.2;stroke-dasharray:4.081,8.162;stroke-dashoffset:0" id="rect14304" width="35.873577" height="49.97176" x="14.06331" y="6.9472136" ry="0.19193064" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bn3nq7gw67kye"
path="res://.godot/imported/portrait_position.svg-f025767e40032b9ebdeab1f9e882467a.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/portrait_position.svg"
dest_files=["res://.godot/imported/portrait_position.svg-f025767e40032b9ebdeab1f9e882467a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,32 @@
[gd_resource type="Resource" script_class="DialogicCharacter" load_steps=2 format=3 uid="uid://dykf1j17ct5mo"]
[ext_resource type="Script" path="res://addons/dialogic/Resources/character.gd" id="1_qsljv"]
[resource]
script = ExtResource("1_qsljv")
display_name = "Preview"
nicknames = [""]
color = Color(1, 1, 1, 1)
description = ""
scale = 1.0
offset = Vector2(0, 0)
mirror = false
default_portrait = "character"
portraits = {
"character": {
"image": "res://addons/dialogic/Editor/Images/preview_character.png",
"offset": Vector2(0, 0),
"scene": "res://addons/dialogic/Modules/Character/default_portrait.tscn"
},
"speaker": {
"image": "res://addons/dialogic/Editor/Images/preview_character_speaker.png",
"offset": Vector2(0, 0),
"scene": "res://addons/dialogic/Modules/Character/default_portrait.tscn"
}
}
custom_info = {
"sound_mood_default": "",
"sound_moods": {},
"style": ""
}
metadata/timeline_not_saved = true
@@ -0,0 +1,92 @@
@tool
extends DialogicSettingsPage
const POSITION_SUGGESTION_KEY := 'dialogic/portraits/position_suggestion_names'
const DEFAULT_PORTRAIT_SCENE_KEY := 'dialogic/portraits/default_portrait'
const ANIMATION_JOIN_DEFAULT_KEY := 'dialogic/animations/join_default'
const ANIMATION_JOIN_DEFAULT_LENGTH_KEY := 'dialogic/animations/join_default_length'
const ANIMATION_JOIN_DEFAULT_WAIT_KEY := 'dialogic/animations/join_default_wait'
const ANIMATION_LEAVE_DEFAULT_KEY := 'dialogic/animations/leave_default'
const ANIMATION_LEAVE_DEFAULT_LENGTH_KEY := 'dialogic/animations/leave_default_length'
const ANIMATION_LEAVE_DEFAULT_WAIT_KEY := 'dialogic/animations/leave_default_wait'
const ANIMATION_CROSSFADE_DEFAULT_KEY := 'dialogic/animations/cross_fade_default'
const ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY:= 'dialogic/animations/cross_fade_default_length'
func _ready():
%JoinDefault.get_suggestions_func = get_join_animation_suggestions
%JoinDefault.mode = 1
%LeaveDefault.get_suggestions_func = get_leave_animation_suggestions
%LeaveDefault.mode = 1
%CrossFadeDefault.get_suggestions_func = get_crossfade_animation_suggestions
%CrossFadeDefault.mode = 1
%PositionSuggestions.text_submitted.connect(save_setting.bind(POSITION_SUGGESTION_KEY))
%CustomPortraitScene.value_changed.connect(save_setting_with_name.bind(DEFAULT_PORTRAIT_SCENE_KEY))
%JoinDefault.value_changed.connect(
save_setting_with_name.bind(ANIMATION_JOIN_DEFAULT_KEY))
%JoinDefaultLength.value_changed.connect(
save_setting.bind(ANIMATION_JOIN_DEFAULT_LENGTH_KEY))
%JoinDefaultWait.toggled.connect(
save_setting.bind(ANIMATION_JOIN_DEFAULT_WAIT_KEY))
%LeaveDefault.value_changed.connect(
save_setting_with_name.bind(ANIMATION_LEAVE_DEFAULT_KEY))
%LeaveDefaultLength.value_changed.connect(
save_setting.bind(ANIMATION_LEAVE_DEFAULT_LENGTH_KEY))
%LeaveDefaultWait.toggled.connect(
save_setting.bind(ANIMATION_LEAVE_DEFAULT_WAIT_KEY))
%CrossFadeDefault.value_changed.connect(
save_setting_with_name.bind(ANIMATION_CROSSFADE_DEFAULT_KEY))
%CrossFadeDefaultLength.value_changed.connect(
save_setting.bind(ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY))
func _refresh():
%PositionSuggestions.text = ProjectSettings.get_setting(POSITION_SUGGESTION_KEY, 'leftmost, left, center, right, rightmost')
%CustomPortraitScene.resource_icon = get_theme_icon(&"PackedScene", &"EditorIcons")
%CustomPortraitScene.set_value(ProjectSettings.get_setting(DEFAULT_PORTRAIT_SCENE_KEY, ''))
# JOIN
%JoinDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
%JoinDefault.set_value(ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_KEY, "Fade In Up"))
%JoinDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_LENGTH_KEY, 0.5))
%JoinDefaultWait.button_pressed = ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_WAIT_KEY, true)
# LEAVE
%LeaveDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
%LeaveDefault.set_value(ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_KEY, "Fade Out Down"))
%LeaveDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_LENGTH_KEY, 0.5))
%LeaveDefaultWait.button_pressed = ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_WAIT_KEY, true)
# CROSS FADE
%CrossFadeDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
%CrossFadeDefault.set_value(ProjectSettings.get_setting(ANIMATION_CROSSFADE_DEFAULT_KEY, "Fade Cross"))
%CrossFadeDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY, 0.5))
func save_setting_with_name(property_name:String, value:Variant, settings_key:String) -> void:
save_setting(value, settings_key)
func save_setting(value:Variant, settings_key:String) -> void:
ProjectSettings.set_setting(settings_key, value)
ProjectSettings.save()
func get_join_animation_suggestions(search_text:String) -> Dictionary:
return DialogicPortraitAnimationUtil.get_suggestions(search_text, %JoinDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.IN)
func get_leave_animation_suggestions(search_text:String) -> Dictionary:
return DialogicPortraitAnimationUtil.get_suggestions(search_text, %LeaveDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.OUT)
func get_crossfade_animation_suggestions(search_text:String) -> Dictionary:
return DialogicPortraitAnimationUtil.get_suggestions(search_text, %CrossFadeDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
@@ -0,0 +1,160 @@
[gd_scene load_steps=5 format=3 uid="uid://cp463rpri5j8a"]
[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/settings_portraits.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_dce78"]
[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://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="3_m06d8"]
[node name="Portraits" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("2")
[node name="PositionsTitle" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Title2" type="Label" parent="PositionsTitle"]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Position Suggestions
"
[node name="HintTooltip" parent="PositionsTitle" instance=ExtResource("2_dce78")]
layout_mode = 2
tooltip_text = "You can change the position names that will be suggested in the timeline editor here."
texture = null
hint_text = "You can change the position names that will be suggested in the timeline editor here."
[node name="PositionSuggestions" type="LineEdit" parent="."]
unique_name_in_owner = true
layout_mode = 2
[node name="DefaultSceneTitle" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Title2" type="Label" parent="DefaultSceneTitle"]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Default Portrait Scene
"
[node name="HintTooltip" parent="DefaultSceneTitle" instance=ExtResource("2_dce78")]
layout_mode = 2
tooltip_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified"
texture = null
hint_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified"
[node name="DefaultScene" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Label" type="Label" parent="DefaultScene"]
layout_mode = 2
text = "Scene"
[node name="CustomPortraitScene" parent="DefaultScene" instance=ExtResource("3_m06d8")]
unique_name_in_owner = true
layout_mode = 2
file_filter = "*.tscn, *.scn; PortraitScene"
placeholder = "Default Scene"
[node name="Animations2" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Title2" type="Label" parent="Animations2"]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Default Animations
"
[node name="HintTooltip" parent="Animations2" instance=ExtResource("2_dce78")]
layout_mode = 2
tooltip_text = "These settings are used for Leave and Join events if no animation is selected.
The Cross-Fade will play if the portrait of a character changes and
no animation is set."
texture = null
hint_text = "These settings are used for Leave and Join events if no animation is selected.
The Cross-Fade will play if the portrait of a character changes and
no animation is set."
[node name="GridContainer" type="GridContainer" parent="."]
layout_mode = 2
columns = 2
[node name="DefaultJoinLabel" type="Label" parent="GridContainer"]
layout_mode = 2
text = "Join"
[node name="DefaultIn" type="HBoxContainer" parent="GridContainer"]
layout_mode = 2
[node name="JoinDefault" parent="GridContainer/DefaultIn" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
mode = 1
[node name="JoinDefaultLength" type="SpinBox" parent="GridContainer/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
step = 0.1
[node name="JoinDefaultWait" type="CheckButton" parent="GridContainer/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
text = "Wait:"
[node name="DefaultOutLabel" type="Label" parent="GridContainer"]
layout_mode = 2
text = "Leave"
[node name="DefaultOut" type="HBoxContainer" parent="GridContainer"]
layout_mode = 2
[node name="LeaveDefault" parent="GridContainer/DefaultOut" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
mode = 1
[node name="LeaveDefaultLength" type="SpinBox" parent="GridContainer/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
step = 0.1
[node name="LeaveDefaultWait" type="CheckButton" parent="GridContainer/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
text = "Wait:"
[node name="CrossFadeLabel" type="Label" parent="GridContainer"]
layout_mode = 2
text = "Cross-Fade"
[node name="DefaultCrossFade" type="HBoxContainer" parent="GridContainer"]
layout_mode = 2
[node name="CrossFadeDefault" parent="GridContainer/DefaultCrossFade" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
mode = 1
[node name="CrossFadeDefaultLength" type="SpinBox" parent="GridContainer/DefaultCrossFade"]
unique_name_in_owner = true
layout_mode = 2
step = 0.1
[connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefault" to="." method="_on_JoinDefault_value_changed"]
[connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefaultLength" to="." method="_on_JoinDefaultLength_value_changed"]
[connection signal="toggled" from="GridContainer/DefaultIn/JoinDefaultWait" to="." method="_on_JoinDefaultWait_toggled"]
[connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefault" to="." method="_on_LeaveDefault_value_changed"]
[connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
[connection signal="toggled" from="GridContainer/DefaultOut/LeaveDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"]
[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefault" to="." method="_on_LeaveDefault_value_changed"]
[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://qihj11n7kx3m"
path="res://.godot/imported/simple_image_portrait_thumbnail.png-3d6c6de8187fd7911017e2ef9d3c40a4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png"
dest_files=["res://.godot/imported/simple_image_portrait_thumbnail.png-3d6c6de8187fd7911017e2ef9d3c40a4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,284 @@
extends DialogicSubsystem
## Subsystem that manages portrait positions.
signal position_changed(info: Dictionary)
var transform_regex := r"(?<part>position|pos|size|siz|rotation|rot)\W*=(?<value>((?!(pos|siz|rot)).)*)"
#region STATE
####################################################################################################
#endregion
#region MAIN METHODS
####################################################################################################
func get_container(position_id: String) -> DialogicNode_PortraitContainer:
for portrait_position:DialogicNode_PortraitContainer in get_tree().get_nodes_in_group(&'dialogic_portrait_con_position'):
if portrait_position.is_visible_in_tree() and portrait_position.is_container(position_id):
return portrait_position
return null
func get_containers(position_id: String) -> Array[DialogicNode_PortraitContainer]:
return get_tree().get_nodes_in_group(&'dialogic_portrait_con_position').filter(
func(node:DialogicNode_PortraitContainer):
return node.is_visible_in_tree() and node.is_container(position_id))
func get_container_container() -> CanvasItem:
var any_portrait_container := get_tree().get_first_node_in_group(&'dialogic_portrait_con_position')
if any_portrait_container:
return any_portrait_container.get_parent()
return null
## Creates a new portrait container node.
## It will copy it's size and most settings from the first p_container in the tree.
## It will be added as a sibling of the first p_container in the tree.
func add_container(position_id: String) -> DialogicNode_PortraitContainer:
var example_position := get_tree().get_first_node_in_group(&'dialogic_portrait_con_position')
if example_position:
var new_position := DialogicNode_PortraitContainer.new()
example_position.get_parent().add_child(new_position)
new_position.name = "Portrait_"+position_id.validate_node_name()
copy_container_setup(example_position, new_position)
new_position.container_ids = [position_id]
position_changed.emit({&'change':'added', &'container_node':new_position, &'position_id':position_id})
return new_position
return null
## Moves the [container] to the [destionation] (using [tween] and [time]).
## The destination can be a position_id (e.g. "center") or translation, roataion and scale.
## When moving to a preset container, then some more will be "copied" (e.g. anchors, etc.)
func move_container(container:DialogicNode_PortraitContainer, destination:String, tween:Tween = null, time:float=1.0) -> void:
var target_position: Vector2 = container.position + container._get_origin_position()
var target_rotation: float = container.rotation
var target_size: Vector2 = container.size
var destination_container := get_container(destination)
if destination_container:
container.set_meta("target_container", destination_container)
target_position = destination_container.position + destination_container._get_origin_position()
target_rotation = destination_container.rotation_degrees
target_size = destination_container.size
else:
var regex := RegEx.create_from_string(transform_regex)
for found in regex.search_all(destination):
match found.get_string('part'):
'pos', 'position':
target_position = str_to_vector(found.get_string("value"), target_position)
'rot', 'rotation':
target_rotation = float(found.get_string("value"))
'siz', 'size':
target_size = str_to_vector(found.get_string("value"), target_size)
translate_container(container, target_position, false, tween, time)
rotate_container(container, target_rotation, false, tween, time)
resize_container(container, target_size, false, tween, time)
if destination_container:
if time:
tween.finished.connect(func():
if container.has_meta("target_container"):
if container.get_meta("target_container") == destination_container:
copy_container_setup(destination_container, container)
)
else:
copy_container_setup(destination_container, container)
func copy_container_setup(from:DialogicNode_PortraitContainer, to:DialogicNode_PortraitContainer) -> void:
to.ignore_resize = true
to.layout_mode = from.layout_mode
to.anchors_preset = from.anchors_preset
to.anchor_bottom = from.anchor_bottom
to.anchor_left = from.anchor_left
to.anchor_right = from.anchor_right
to.anchor_top = from.anchor_top
to.offset_bottom = from.offset_bottom
to.offset_top = from.offset_top
to.offset_right = from.offset_right
to.offset_left = from.offset_left
to.size_mode = from.size_mode
to.origin_anchor = from.origin_anchor
to.ignore_resize = false
to.update_portrait_transforms()
## Translates the given container.
## The given translation should be the target position of the ORIGIN point, not the container!
func translate_container(container:DialogicNode_PortraitContainer, translation:Variant, relative := false, tween:Tween=null, time:float=1.0) -> void:
if !container.has_meta(&'default_translation'):
container.set_meta(&'default_translation', container.position + container._get_origin_position())
var final_translation: Vector2
if typeof(translation) == TYPE_STRING:
final_translation = str_to_vector(translation, container.position+container._get_origin_position())
elif typeof(translation) == TYPE_VECTOR2:
final_translation = translation
if relative:
final_translation += container.position
else:
final_translation -= container._get_origin_position()
if tween:
tween.tween_method(DialogicUtil.multitween.bind(container, "position", "base"), container.position, final_translation, time)
if not tween.finished.is_connected(save_position_container):
tween.finished.connect(save_position_container.bind(container))
else:
container.position = final_translation
save_position_container(container)
position_changed.emit({&'change':'moved', &'container_node':container})
func rotate_container(container:DialogicNode_PortraitContainer, rotation:float, relative := false, tween:Tween=null, time:float=1.0) -> void:
if !container.has_meta(&'default_rotation'):
container.set_meta(&'default_rotation', container.rotation_degrees)
var final_rotation := rotation
if relative:
final_rotation += container.rotation_degrees
container.pivot_offset = container._get_origin_position()
if tween:
tween.tween_property(container, 'rotation_degrees', final_rotation, time)
if not tween.finished.is_connected(save_position_container):
tween.finished.connect(save_position_container.bind(container))
else:
container.rotation_degrees = final_rotation
save_position_container(container)
position_changed.emit({&'change':'rotated', &'container_node':container})
func resize_container(container: DialogicNode_PortraitContainer, rect_size: Variant, relative := false, tween:Tween=null, time:float=1.0) -> void:
if !container.has_meta(&'default_size'):
container.set_meta(&'default_size', container.size)
var final_rect_resize: Vector2
if typeof(rect_size) == TYPE_STRING:
final_rect_resize = str_to_vector(rect_size, container.size)
elif typeof(rect_size) == TYPE_VECTOR2:
final_rect_resize = rect_size
if relative:
final_rect_resize += container.rect_size
var relative_position_change := container._get_origin_position()-container._get_origin_position(final_rect_resize)
if tween:
tween.tween_method(DialogicUtil.multitween.bind(container, "position", "resize_move"), Vector2(), relative_position_change, time)
tween.tween_property(container, 'size', final_rect_resize, time)
if not tween.finished.is_connected(save_position_container):
tween.finished.connect(save_position_container.bind(container))
else:
container.position = container.position + relative_position_change
container.size = final_rect_resize
save_position_container(container)
position_changed.emit({&'change':'resized', &'container_node':container})
func save_position_container(container: DialogicNode_PortraitContainer) -> void:
if not dialogic.current_state_info.has('portrait_containers'):
dialogic.current_state_info['portrait_containers'] = {}
var info := {
"container_ids" : container.container_ids,
"position" : container.position,
"rotation" : container.rotation_degrees,
"size" : container.size,
"pivot_mode" : container.pivot_mode,
"pivot_value" : container.pivot_value,
"origin_anchor" : container.origin_anchor,
"anchor_left" : container.anchor_left,
"anchor_right" : container.anchor_right,
"anchor_top" : container.anchor_top,
"anchor_bottom" : container.anchor_bottom,
"offset_left" : container.offset_left,
"offset_right" : container.offset_right,
"offset_top" : container.offset_top,
"offset_bottom" : container.offset_bottom,
}
dialogic.current_state_info.portrait_containers[container.container_ids[0]] = info
func load_position_container(position_id: String) -> DialogicNode_PortraitContainer:
# First check whether the container already exists:
var container := get_container(position_id)
if container:
return container
if not dialogic.current_state_info.has('portrait_containers') or not dialogic.current_state_info.portrait_containers.has(position_id):
return null
var info: Dictionary = dialogic.current_state_info.portrait_containers[position_id]
container = add_container(position_id)
if not container:
return null
container.container_ids = info.container_ids
container.position = info.position
container.rotation = info.rotation
container.size = info.size
container.pivot_mode = info.pivot_mode
container.pivot_value = info.pivot_value
container.origin_anchor = info.origin_anchor
container.anchor_left = info.anchor_left
container.anchor_right = info.anchor_right
container.anchor_top = info.anchor_top
container.anchor_bottom = info.anchor_bottom
container.offset_left = info.offset_left
container.offset_right = info.offset_right
container.offset_top = info.offset_top
container.offset_bottom = info.offset_bottom
return container
func str_to_vector(input: String, base_vector:=Vector2()) -> Vector2:
var vector_regex := RegEx.create_from_string(r"(?<part>x|y)\s*(?<number>(-|\+)?(\d|\.|)*)(\s*(?<type>%|px))?")
var vec := base_vector
for i in vector_regex.search_all(input):
var value := float(i.get_string(&'number'))
match i.get_string(&'type'):
'px':
pass # Keep values as they are
'%', _:
match i.get_string(&'part'):
'x': value *= get_viewport().get_visible_rect().size.x
'y': value *= get_viewport().get_visible_rect().size.y
match i.get_string(&'part'):
'x': vec.x = value
'y': vec.y = value
return vec
func vector_to_str(vec:Vector2) -> String:
return "x" + str(vec.x) + "px y" + str(vec.y) + "px"
func reset_all_containers(time:= 0.0, tween:Tween = null) -> void:
for container in get_tree().get_nodes_in_group(&'dialogic_portrait_con_position'):
reset_container(container, time, tween)
func reset_container(container:DialogicNode_PortraitContainer, time := 0.0, tween: Tween = null ) -> void:
if container.has_meta(&'default_translation'):
translate_container(container, vector_to_str(container.get_meta(&'default_translation')), false, tween, time)
if container.has_meta(&'default_rotation'):
rotate_container(container, container.get_meta(&'default_rotation'), false, tween, time)
if container.has_meta(&'default_size'):
resize_container(container, vector_to_str(container.get_meta(&'default_size')), false, tween, time)
@@ -0,0 +1,714 @@
extends DialogicSubsystem
## Subsystem that manages portraits and portrait positions.
signal character_joined(info:Dictionary)
signal character_left(info:Dictionary)
signal character_portrait_changed(info:Dictionary)
signal character_moved(info:Dictionary)
## Emitted when a portrait starts animating.
#signal portrait_animating(character_node: Node, portrait_node: Node, animation_name: String, animation_length: float)
## The default portrait scene.
var default_portrait_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('default_portrait.tscn'))
#region STATE
####################################################################################################
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
for character in dialogic.current_state_info.get('portraits', {}).keys():
remove_character(load(character))
dialogic.current_state_info['portraits'] = {}
func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
if not "portraits" in dialogic.current_state_info:
dialogic.current_state_info["portraits"] = {}
# Load Position Portraits
var portraits_info: Dictionary = dialogic.current_state_info.portraits.duplicate()
dialogic.current_state_info.portraits = {}
for character_path in portraits_info:
var character_info: Dictionary = portraits_info[character_path]
var character: DialogicCharacter = load(character_path)
var container := dialogic.PortraitContainers.load_position_container(character.get_character_name())
add_character(character, container, character_info.portrait, character_info.position_id)
change_character_mirror(character, character_info.get('custom_mirror', false))
change_character_z_index(character, character_info.get('z_index', 0))
change_character_extradata(character, character_info.get('extra_data', ""))
# Load Speaker Portrait
var speaker: Variant = dialogic.current_state_info.get('speaker', "")
if speaker:
dialogic.current_state_info['speaker'] = ""
change_speaker(load(speaker))
dialogic.current_state_info['speaker'] = speaker
func pause() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.node.has_meta('animation_node'):
portrait.node.get_meta('animation_node').pause()
func resume() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.node.has_meta('animation_node'):
portrait.node.get_meta('animation_node').resume()
func _ready() -> void:
if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
default_portrait_scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
#region MAIN METHODS
####################################################################################################
## The following methods allow manipulating portraits.
## A portrait is made up of a character node [Node2D] that instances the portrait scene as it's child.
## The character node is then always the child of a portrait container.
## - Position (PortraitContainer)
## ---- character_node (Node2D)
## --------- portrait_node (e.g. default_portrait.tscn, or a custom portrait)
##
## Using these main methods a character can be present multiple times.
## For a VN style, the "character" methods (next section) provide access based on the character.
## - (That is what the character event uses)
## Creates a new [character node] for the given [character], and add it to the given [portrait container].
func _create_character_node(character:DialogicCharacter, container:DialogicNode_PortraitContainer) -> Node:
if container == null:
return null
var character_node := Node2D.new()
character_node.name = character.get_character_name()
character_node.set_meta('character', character)
container.add_child(character_node)
return character_node
## Changes the portrait of a specific [character node].
func _change_portrait(character_node: Node2D, portrait: String, fade_animation:="", fade_length := 0.5) -> Dictionary:
var character: DialogicCharacter = character_node.get_meta('character')
if portrait.is_empty():
portrait = character.default_portrait
var info := {'character':character, 'portrait':portrait, 'same_scene':false}
if not portrait in character.portraits.keys():
print_debug('[Dialogic] Change to not-existing portrait will be ignored!')
return info
# Path to the scene to use.
var scene_path: String = character.portraits[portrait].get('scene', '')
var portrait_node: Node = null
var previous_portrait: Node = null
var portrait_count := character_node.get_child_count()
if portrait_count > 0:
previous_portrait = character_node.get_child(-1)
# Check if the scene is the same as the currently loaded scene.
if (not previous_portrait == null and
previous_portrait.get_meta('scene', '') == scene_path and
# Also check if the scene supports changing to the given portrait.
previous_portrait._should_do_portrait_update(character, portrait)):
portrait_node = previous_portrait
info['same_scene'] = true
else:
if ResourceLoader.exists(scene_path):
var packed_scene: PackedScene = load(scene_path)
if packed_scene:
portrait_node = packed_scene.instantiate()
else:
push_error('[Dialogic] Portrait node "' + str(scene_path) + '" for character [' + character.display_name + '] could not be loaded. Your portrait might not show up on the screen. Confirm the path is correct.')
if !portrait_node:
portrait_node = default_portrait_scene.instantiate()
portrait_node.set_meta('scene', scene_path)
if portrait_node:
portrait_node.set_meta('portrait', portrait)
character_node.set_meta('portrait', portrait)
DialogicUtil.apply_scene_export_overrides(portrait_node, character.portraits[portrait].get('export_overrides', {}))
if portrait_node.has_method('_update_portrait'):
portrait_node._update_portrait(character, portrait)
if not portrait_node.is_inside_tree():
character_node.add_child(portrait_node)
_update_portrait_transform(portrait_node)
## Handle Cross-Animating
if previous_portrait and previous_portrait != portrait_node:
if not fade_animation.is_empty() and fade_length > 0:
var fade_out := _animate_node(previous_portrait, fade_animation, fade_length, 1, true)
var _fade_in := _animate_node(portrait_node, fade_animation, fade_length, 1, false)
fade_out.finished.connect(previous_portrait.queue_free)
else:
previous_portrait.queue_free()
return info
## Changes the mirroring of the given portrait.
## Unless @force is false, this will take into consideration the character mirror,
## portrait mirror and portrait position mirror settings.
func _change_portrait_mirror(character_node: Node2D, mirrored := false, force := false) -> void:
var latest_portrait := character_node.get_child(-1)
if latest_portrait.has_method('_set_mirror'):
var character: DialogicCharacter = character_node.get_meta('character')
var current_portrait_info := character.get_portrait_info(character_node.get_meta('portrait'))
latest_portrait._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false)))
func _change_portrait_extradata(character_node: Node2D, extra_data := "") -> void:
var latest_portrait := character_node.get_child(-1)
if latest_portrait.has_method('_set_extra_data'):
latest_portrait._set_extra_data(extra_data)
func _update_character_transform(character_node:Node, time := 0.0) -> void:
for child in character_node.get_children():
_update_portrait_transform(child, time)
func _update_portrait_transform(portrait_node: Node, time:float = 0.0) -> void:
var character_node: Node = portrait_node.get_parent()
var character: DialogicCharacter = character_node.get_meta('character')
var portrait_info: Dictionary = character.portraits.get(portrait_node.get_meta('portrait'), {})
# ignore the character scale on custom portraits that have 'ignore_char_scale' set to true
var apply_character_scale: bool = not portrait_info.get('ignore_char_scale', false)
var transform: Rect2 = character_node.get_parent().get_local_portrait_transform(
portrait_node._get_covered_rect(),
(character.scale * portrait_info.get('scale', 1))*int(apply_character_scale)+portrait_info.get('scale', 1)*int(!apply_character_scale))
var tween: Tween
if character_node.has_meta('move_tween'):
if character_node.get_meta('move_tween').is_running():
time = character_node.get_meta('move_time')-character_node.get_meta('move_tween').get_total_elapsed_time()
tween = character_node.get_meta('move_tween')
tween.stop()
if time == 0:
character_node.position = transform.position
portrait_node.position = character.offset + portrait_info.get('offset', Vector2())
portrait_node.scale = transform.size
else:
if not tween:
tween = character_node.create_tween().set_parallel().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
character_node.set_meta('move_tween', tween)
character_node.set_meta('move_time', time)
tween.tween_method(DialogicUtil.multitween.bind(character_node, "position", "base"), character_node.position, transform.position, time)
tween.tween_property(portrait_node, 'position',character.offset + portrait_info.get('offset', Vector2()), time)
tween.tween_property(portrait_node, 'scale', transform.size, time)
## Animates the node with the given animation.
## Is used both on the character node (most animations) and the portrait nodes (cross-fade animations)
func _animate_node(node: Node, animation_path: String, length: float, repeats := 1, is_reversed := false) -> DialogicAnimation:
if node.has_meta('animation_node') and is_instance_valid(node.get_meta('animation_node')):
node.get_meta('animation_node').queue_free()
var anim_script: Script = load(animation_path)
var anim_node := Node.new()
anim_node.set_script(anim_script)
anim_node = (anim_node as DialogicAnimation)
anim_node.node = node
anim_node.base_position = node.position
anim_node.base_scale = node.scale
anim_node.time = length
anim_node.repeats = repeats
anim_node.is_reversed = is_reversed
add_child(anim_node)
anim_node.animate()
node.set_meta("animation_path", animation_path)
node.set_meta("animation_length", length)
node.set_meta("animation_node", anim_node)
#if not is_silent:
#portrait_animating.emit(portrait_node.get_parent(), portrait_node, animation_path, length)
return anim_node
## Moves the given portrait to the given container.
func _move_character(character_node: Node2D, transform:="", time := 0.0, easing:= Tween.EASE_IN_OUT, trans:= Tween.TRANS_SINE) -> void:
var tween := character_node.create_tween().set_ease(easing).set_trans(trans).set_parallel()
if time == 0:
tween.kill()
tween = null
var container: DialogicNode_PortraitContainer = character_node.get_parent()
dialogic.PortraitContainers.move_container(container, transform, tween, time)
for portrait_node in character_node.get_children():
_update_portrait_transform(portrait_node, time)
## Changes the given portraits z_index.
func _change_portrait_z_index(character_node: Node, z_index:int, update_zindex:= true) -> void:
if update_zindex:
character_node.get_parent().set_meta('z_index', z_index)
var sorted_children := character_node.get_parent().get_parent().get_children()
sorted_children.sort_custom(z_sort_portrait_containers)
var idx := 0
for con in sorted_children:
con.get_parent().move_child(con, idx)
idx += 1
## Checks if [para, character] has joined the scene, if so, returns its
## active [DialogicPortrait] node.
##
## The difference between an active and inactive nodes is whether the node is
## the latest node. [br]
## If a portrait is fading/animating from portrait A and B, both will exist
## in the scene, but only the new portrait is active, even if it is not
## fully visible yet.
func get_character_portrait(character: DialogicCharacter) -> DialogicPortrait:
if is_character_joined(character):
var portrait_node: DialogicPortrait = dialogic.current_state_info['portraits'][character.resource_path].node.get_child(-1)
return portrait_node
return null
func z_sort_portrait_containers(con1: DialogicNode_PortraitContainer, con2: DialogicNode_PortraitContainer) -> bool:
if con1.get_meta('z_index', 0) < con2.get_meta('z_index', 0):
return true
return false
## Private method to remove a [param portrait_node].
func _remove_portrait(portrait_node: Node) -> void:
portrait_node.get_parent().remove_child(portrait_node)
portrait_node.queue_free()
## Gets the default animation length for joining characters
## If Auto-Skip is enabled, limits the time.
func _get_join_default_length() -> float:
var default_time: float = ProjectSettings.get_setting('dialogic/animations/join_default_length', 0.5)
if dialogic.Inputs.auto_skip.enabled:
default_time = min(default_time, dialogic.Inputs.auto_skip.time_per_event)
return default_time
## Gets the default animation length for leaving characters
## If Auto-Skip is enabled, limits the time.
func _get_leave_default_length() -> float:
var default_time: float = ProjectSettings.get_setting('dialogic/animations/leave_default_length', 0.5)
if dialogic.Inputs.auto_skip.enabled:
default_time = min(default_time, dialogic.Inputs.auto_skip.time_per_event)
return default_time
## Checks multiple cases to return a valid portrait to use.
func get_valid_portrait(character:DialogicCharacter, portrait:String) -> String:
if character == null:
printerr('[Dialogic] Tried to use portrait "', portrait, '" on <null> character.')
dialogic.print_debug_moment()
return ""
if "{" in portrait and dialogic.has_subsystem("Expressions"):
var test: Variant = dialogic.Expressions.execute_string(portrait)
if test:
portrait = str(test)
if not portrait in character.portraits:
if not portrait.is_empty():
printerr('[Dialogic] Tried to use invalid portrait "', portrait, '" on character "', DialogicResourceUtil.get_unique_identifier(character.resource_path), '". Using default portrait instead.')
dialogic.print_debug_moment()
portrait = character.default_portrait
if portrait.is_empty():
portrait = character.default_portrait
return portrait
#endregion
#region Character Methods
####################################################################################################
## The following methods are used to manage character portraits with the following rules:
## - a character can only be present once with these methods.
## Most of them will fail silently if the character isn't joined yet.
## Adds a character at a position and sets it's portrait.
## If the character is already joined it will only update, portrait, position, etc.
func join_character(character:DialogicCharacter, portrait:String, position_id:String, mirrored:= false, z_index:= 0, extra_data:= "", animation_name:= "", animation_length:= 0.0, animation_wait := false) -> Node:
if is_character_joined(character):
change_character_portrait(character, portrait)
if animation_name.is_empty():
animation_length = _get_join_default_length()
if animation_wait:
dialogic.current_state = DialogicGameHandler.States.ANIMATING
await get_tree().create_timer(animation_length).timeout
dialogic.current_state = DialogicGameHandler.States.IDLE
move_character(character, position_id, animation_length)
change_character_mirror(character, mirrored)
return
var container := dialogic.PortraitContainers.add_container(character.get_character_name())
var character_node := add_character(character, container, portrait, position_id)
if character_node == null:
return null
dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_id':position_id, 'custom_mirror':mirrored}
_change_portrait_mirror(character_node, mirrored)
_change_portrait_extradata(character_node, extra_data)
_change_portrait_z_index(character_node, z_index)
var info := {'character':character}
info.merge(dialogic.current_state_info['portraits'][character.resource_path])
character_joined.emit(info)
if animation_name.is_empty():
animation_name = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up")
animation_length = _get_join_default_length()
animation_wait = ProjectSettings.get_setting('dialogic/animations/join_default_wait', true)
animation_name = DialogicPortraitAnimationUtil.guess_animation(animation_name, DialogicPortraitAnimationUtil.AnimationType.IN)
if animation_name and animation_length > 0:
var anim: DialogicAnimation = _animate_node(character_node, animation_name, animation_length)
if animation_wait:
dialogic.current_state = DialogicGameHandler.States.ANIMATING
await anim.finished
dialogic.current_state = DialogicGameHandler.States.IDLE
return character_node
func add_character(character:DialogicCharacter, container: DialogicNode_PortraitContainer, portrait:String, position_id:String) -> Node:
if is_character_joined(character):
printerr('[DialogicError] Cannot add a already joined character. If this is intended call _create_character_node manually.')
return null
portrait = get_valid_portrait(character, portrait)
if portrait.is_empty():
return null
if not character:
printerr('[DialogicError] Cannot call add_portrait() with null character.')
return null
var character_node := _create_character_node(character, container)
if character_node == null:
printerr('[Dialogic] Failed to join character to position ', position_id, ". Could not find position container.")
return null
dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_id':position_id}
_move_character(character_node, position_id)
_change_portrait(character_node, portrait)
return character_node
## Changes the portrait of a character. Only works with joined characters.
func change_character_portrait(character: DialogicCharacter, portrait: String, fade_animation:="DEFAULT", fade_length := -1.0) -> void:
if not is_character_joined(character):
return
portrait = get_valid_portrait(character, portrait)
if dialogic.current_state_info.portraits[character.resource_path].portrait == portrait:
return
if fade_animation == "DEFAULT":
fade_animation = ProjectSettings.get_setting('dialogic/animations/cross_fade_default', "Fade Cross")
fade_length = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5)
fade_animation = DialogicPortraitAnimationUtil.guess_animation(fade_animation, DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
var info := _change_portrait(dialogic.current_state_info.portraits[character.resource_path].node, portrait, fade_animation, fade_length)
dialogic.current_state_info.portraits[character.resource_path].portrait = info.portrait
_change_portrait_mirror(
dialogic.current_state_info.portraits[character.resource_path].node,
dialogic.current_state_info.portraits[character.resource_path].get('custom_mirror', false)
)
character_portrait_changed.emit(info)
## Changes the mirror of the given character. Only works with joined characters
func change_character_mirror(character:DialogicCharacter, mirrored:= false, force:= false) -> void:
if !is_character_joined(character):
return
_change_portrait_mirror(dialogic.current_state_info.portraits[character.resource_path].node, mirrored, force)
dialogic.current_state_info.portraits[character.resource_path]['custom_mirror'] = mirrored
## Changes the z_index of a character. Only works with joined characters
func change_character_z_index(character:DialogicCharacter, z_index:int, update_zindex:= true) -> void:
if !is_character_joined(character):
return
_change_portrait_z_index(dialogic.current_state_info.portraits[character.resource_path].node, z_index, update_zindex)
if update_zindex:
dialogic.current_state_info.portraits[character.resource_path]['z_index'] = z_index
## Changes the extra data on the given character. Only works with joined characters
func change_character_extradata(character:DialogicCharacter, extra_data:="") -> void:
if !is_character_joined(character):
return
_change_portrait_extradata(dialogic.current_state_info.portraits[character.resource_path].node, extra_data)
dialogic.current_state_info.portraits[character.resource_path]['extra_data'] = extra_data
## Starts the given animation on the given character. Only works with joined characters
func animate_character(character: DialogicCharacter, animation_path: String, length: float, repeats := 1, is_reversed := false) -> DialogicAnimation:
if not is_character_joined(character):
return null
animation_path = DialogicPortraitAnimationUtil.guess_animation(animation_path)
var character_node: Node = dialogic.current_state_info.portraits[character.resource_path].node
return _animate_node(character_node, animation_path, length, repeats, is_reversed)
## Moves the given character to the given position. Only works with joined characters
func move_character(character:DialogicCharacter, position_id:String, time:= 0.0, easing:=Tween.EASE_IN_OUT, trans:=Tween.TRANS_SINE) -> void:
if !is_character_joined(character):
return
if dialogic.current_state_info.portraits[character.resource_path].position_id == position_id:
return
_move_character(dialogic.current_state_info.portraits[character.resource_path].node, position_id, time, easing, trans)
dialogic.current_state_info.portraits[character.resource_path].position_id = position_id
character_moved.emit({'character':character, 'position_id':position_id, 'time':time})
## Removes a character with a given animation or the default animation.
func leave_character(character: DialogicCharacter, animation_name:= "", animation_length:= 0.0, animation_wait := false) -> void:
if not is_character_joined(character):
return
if animation_name.is_empty():
animation_name = ProjectSettings.get_setting('dialogic/animations/leave_default', "Fade Out Down")
animation_length = _get_leave_default_length()
animation_wait = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true)
animation_name = DialogicPortraitAnimationUtil.guess_animation(animation_name, DialogicPortraitAnimationUtil.AnimationType.OUT)
if not animation_name.is_empty():
var character_node := get_character_node(character)
var animation := _animate_node(character_node, animation_name, animation_length, 1, true)
if animation_length > 0:
if animation_wait:
dialogic.current_state = DialogicGameHandler.States.ANIMATING
await animation.finished
dialogic.current_state = DialogicGameHandler.States.IDLE
remove_character(character)
else:
animation.finished.connect(func(): remove_character(character))
else:
remove_character(character)
## Removes all joined characters with a given animation or the default animation.
func leave_all_characters(animation_name:="", animation_length:=0.0, animation_wait := false) -> void:
for character in get_joined_characters():
await leave_character(character, animation_name, animation_length, animation_wait)
## Finds the character node for a [param character].
## Return `null` if the [param character] is not part of the scene.
func get_character_node(character: DialogicCharacter) -> Node:
if is_character_joined(character):
if is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].node):
return dialogic.current_state_info['portraits'][character.resource_path].node
return null
## Removes the given characters portrait.
## Only works with joined characters.
func remove_character(character: DialogicCharacter) -> void:
var character_node := get_character_node(character)
if is_instance_valid(character_node) and character_node is Node:
var container := character_node.get_parent()
container.get_parent().remove_child(container)
container.queue_free()
character_node.queue_free()
character_left.emit({'character': character})
dialogic.current_state_info['portraits'].erase(character.resource_path)
func get_current_character() -> DialogicCharacter:
if dialogic.current_state_info.get('speaker', null):
return load(dialogic.current_state_info.speaker)
return null
## Returns true if the given character is currently joined.
func is_character_joined(character: DialogicCharacter) -> bool:
if character == null or not character.resource_path in dialogic.current_state_info['portraits']:
return false
return true
## Returns a list of the joined charcters (as resources)
func get_joined_characters() -> Array[DialogicCharacter]:
var chars: Array[DialogicCharacter] = []
for char_path: String in dialogic.current_state_info.get('portraits', {}).keys():
chars.append(load(char_path))
return chars
## Returns a dictionary with info on a given character.
## Keys can be [joined, character, node (for the portrait node), position_id]
## Only joined is included (and false) for not joined characters
func get_character_info(character:DialogicCharacter) -> Dictionary:
if is_character_joined(character):
var info: Dictionary = dialogic.current_state_info['portraits'][character.resource_path]
info['joined'] = true
return info
else:
return {'joined':false}
#endregion
#region SPEAKER PORTRAIT CONTAINERS
####################################################################################################
## Updates all portrait containers set to SPEAKER.
func change_speaker(speaker: DialogicCharacter = null, portrait := "") -> void:
for container: Node in get_tree().get_nodes_in_group('dialogic_portrait_con_speaker'):
var just_joined := true
for character_node: Node in container.get_children():
if not character_node.get_meta('character') == speaker:
var leave_animation: String = ProjectSettings.get_setting('dialogic/animations/leave_default', "Fade Out")
leave_animation = DialogicPortraitAnimationUtil.guess_animation(leave_animation, DialogicPortraitAnimationUtil.AnimationType.OUT)
var leave_animation_length := _get_leave_default_length()
if leave_animation and leave_animation_length:
var animate_out := _animate_node(character_node, leave_animation, leave_animation_length, 1, true)
animate_out.finished.connect(character_node.queue_free)
else:
character_node.get_parent().remove_child(character_node)
character_node.queue_free()
else:
just_joined = false
if speaker == null or speaker.portraits.is_empty():
continue
if just_joined:
_create_character_node(speaker, container)
elif portrait.is_empty():
continue
if portrait.is_empty():
portrait = speaker.default_portrait
var character_node := container.get_child(-1)
var fade_animation: String = ProjectSettings.get_setting('dialogic/animations/cross_fade_default', "Fade Cross")
var fade_length: float = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5)
fade_animation = DialogicPortraitAnimationUtil.guess_animation(fade_animation, DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
if container.portrait_prefix+portrait in speaker.portraits:
portrait = container.portrait_prefix+portrait
_change_portrait(character_node, portrait, fade_animation, fade_length)
# if the character has no portraits _change_portrait won't actually add a child node
if character_node.get_child_count() == 0:
continue
if just_joined:
# Change speaker is called before the text is changed.
# In styles where the speaker is IN the textbox,
# this can mean the portrait container isn't sized correctly yet.
character_node.hide()
if not container.is_visible_in_tree():
await get_tree().process_frame
character_node.show()
var join_animation: String = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up")
join_animation = DialogicPortraitAnimationUtil.guess_animation(join_animation, DialogicPortraitAnimationUtil.AnimationType.IN)
var join_animation_length := _get_join_default_length()
if join_animation and join_animation_length:
_animate_node(character_node, join_animation, join_animation_length)
_change_portrait_mirror(character_node)
if speaker:
if speaker.resource_path != dialogic.current_state_info['speaker']:
if dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])):
dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight()
if speaker and is_character_joined(speaker):
dialogic.current_state_info['portraits'][speaker.resource_path].node.get_child(-1)._highlight()
elif dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])):
dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight()
#endregion
#region TEXT EFFECTS
####################################################################################################
## Called from the [portrait=something] text effect.
func text_effect_portrait(_text_node:Control, _skipped:bool, argument:String) -> void:
if argument:
if dialogic.current_state_info.get('speaker', null):
change_character_portrait(load(dialogic.current_state_info.speaker), argument)
change_speaker(load(dialogic.current_state_info.speaker), argument)
## Called from the [extra_data=something] text effect.
func text_effect_extradata(_text_node:Control, _skipped:bool, argument:String) -> void:
if argument:
if dialogic.current_state_info.get('speaker', null):
change_character_extradata(load(dialogic.current_state_info.speaker), argument)
#endregion
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="31" height="22" viewBox="0 0 31 22" fill="none" version="1.1" id="svg4" sodipodi:docname="update_mirror.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="true" inkscape:zoom="16" inkscape:cx="21.84375" inkscape:cy="14.0625" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4">
<inkscape:grid type="xygrid" id="grid6918" originx="0" originy="0" />
</sodipodi:namedview>
<g inkscape:groupmode="layer" id="layer2" inkscape:label="Mirror" style="display:inline">
<path id="path8742" style="fill:#ffffff;fill-opacity:1;stroke-width:0.759831" d="m 10.967882,3.1351682 c 0.863708,-0.038595 2.312143,1.467492 2.312143,3.2198562 v 0 c 0,1.7523639 -1.491571,3.2198509 -2.312143,3.2198509" sodipodi:nodetypes="cssc" class="UnoptimicedTransforms" transform="matrix(1.1995187,0,0,1.1995187,-2.6822942,-1.4596729)" />
<path id="path16944" style="fill:#ffffff;fill-opacity:1;stroke-width:0.914608" d="m 7.5210109,10.015357 c -0.023367,7.6e-4 -0.046802,8.86e-4 -0.070302,0.0012 -0.9139878,0.009 -2.8630987,-1.7296781 -2.8630987,-3.8633837 0,-2.1337061 1.9491109,-3.8466572 2.8630988,-3.8634194 0.023497,-4.31e-4 0.046938,3.82e-4 0.070302,0.00113" sodipodi:nodetypes="csssc" />
<path id="path8744" style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.866873" d="m 10.468766,9.4004107 c 0.0017,-5.3e-6 0.0035,-8.8e-6 0.0051,-8.8e-6 1.704613,0 3.086415,2.9100071 3.086415,6.4996661 v 0 c 0,1.999827 -1.381802,1.999829 -3.086415,1.999829 h -0.0052" />
<path id="path16885" style="display:inline;fill:#ffffff;fill-opacity:1;stroke-width:0.874663" d="m 7.5210109,17.899897 c -1.7328976,-2e-6 -3.1367969,-0.002 -3.1367969,-1.999829 0,-3.586045 1.4039606,-6.4938047 3.1369101,-6.4996573" />
<path style="display:none;fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="M 4.3862586,18.660184 H 13.902208" id="path11459" />
<path style="display:none;fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 5.0553488,16.987459 -1.3381805,1.635554 1.3010089,1.970099" id="path11461" />
<path style="display:none;fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 13.567664,16.875944 0.854949,1.821412 -0.817777,1.9701" id="path11463" />
<path style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.37801;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.981752" d="m 9.0131471,1.539982 -0.03659,16.903546" id="path16998" />
</g>
<path id="path26928" style="display:inline;fill:#ff4596;fill-opacity:1;stroke-width:0.613366" inkscape:label="path26928" d="m 17.19844,8.281361 c -0.272575,-8.2e-6 -0.500185,9.5e-6 -0.687018,0.012976 -0.194489,0.013249 -0.37819,0.041877 -0.556302,0.1156474 -0.4133,0.1711984 -0.741702,0.4995528 -0.912902,0.9128597 -0.08914,0.2151709 -0.113203,0.4419435 -0.122463,0.6884219 -0.0075,0.198126 -0.107439,0.363433 -0.25499,0.44863 -0.147562,0.08519 -0.340728,0.08912 -0.516018,-0.0037 -0.218061,-0.115251 -0.426533,-0.207727 -0.657444,-0.238134 -0.443521,-0.05839 -0.892077,0.06181 -1.246989,0.33413 -0.152944,0.117382 -0.269558,0.262171 -0.378298,0.423977 -0.104461,0.155439 -0.218268,0.352519 -0.354578,0.588618 l -0.01552,0.02687 c -0.136286,0.236087 -0.250093,0.433158 -0.332475,0.601337 -0.08575,0.17508 -0.152839,0.348492 -0.178013,0.53963 -0.05839,0.443529 0.06182,0.892086 0.334128,1.247025 0.141774,0.184685 0.326035,0.319018 0.534849,0.450213 0.167922,0.105499 0.261112,0.274802 0.2611,0.445256 -4e-6,0.170395 -0.09319,0.339684 -0.2611,0.445179 -0.208839,0.131218 -0.393112,0.265542 -0.534887,0.450286 -0.272334,0.354894 -0.39255,0.803431 -0.334165,1.246956 0.02519,0.191135 0.09222,0.364576 0.178014,0.53963 0.08239,0.168183 0.196157,0.365293 0.332477,0.601374 l 0.01552,0.02687 c 0.136299,0.236083 0.250113,0.433152 0.354577,0.588576 0.108764,0.161802 0.225385,0.306641 0.378336,0.423982 0.35491,0.272332 0.803465,0.392553 1.246989,0.334165 0.230896,-0.03043 0.439318,-0.122887 0.657371,-0.238137 0.175301,-0.09269 0.368508,-0.08873 0.516089,-0.0034 0.147577,0.08519 0.247617,0.250486 0.255026,0.448666 0.0092,0.246453 0.03333,0.473216 0.122463,0.688386 0.171199,0.413282 0.4996,0.741708 0.912902,0.912899 0.178115,0.0738 0.361807,0.102372 0.556303,0.115607 0.186836,0.01298 0.414443,0.01298 0.687018,0.01298 h 0.03098 c 0.272641,0 0.500197,0 0.687087,-0.01298 0.194489,-0.01325 0.378223,-0.04182 0.556344,-0.11562 0.413285,-0.171184 0.741671,-0.499617 0.912862,-0.912901 0.08914,-0.21517 0.113203,-0.441908 0.122409,-0.68842 0.0075,-0.198111 0.107425,-0.36345 0.254987,-0.448709 0.147577,-0.08522 0.340795,-0.08914 0.516092,0.0034 0.218051,0.115251 0.426474,0.207726 0.657404,0.2381 0.443522,0.05839 0.892099,-0.06174 1.246993,-0.334094 0.152961,-0.117441 0.269546,-0.262206 0.378297,-0.424009 0.104448,-0.155439 0.218252,-0.352497 0.354541,-0.588579 l 0.01569,-0.02687 c 0.136272,-0.236049 0.2501,-0.433192 0.332472,-0.601376 0.08573,-0.17506 0.152807,-0.348467 0.178015,-0.539596 0.05839,-0.443585 -0.06185,-0.89213 -0.334163,-1.247026 C 22.966705,15.585721 22.782388,15.451451 22.573601,15.320252 22.405679,15.214753 22.3125,15.045453 22.3125,14.875 c 0,-0.170395 0.09314,-0.339612 0.261028,-0.445112 0.208903,-0.131191 0.393243,-0.265477 0.534992,-0.450283 0.272333,-0.354903 0.392559,-0.803461 0.334166,-1.246992 -0.0252,-0.191132 -0.09228,-0.364516 -0.178014,-0.539595 -0.08236,-0.168164 -0.196157,-0.365264 -0.332511,-0.601337 l -0.01552,-0.02685 c -0.136362,-0.236031 -0.25012,-0.433148 -0.354576,-0.588582 -0.108763,-0.161803 -0.225359,-0.306611 -0.378332,-0.423976 -0.354895,-0.27233 -0.80344,-0.392521 -1.247026,-0.33413 -0.230874,0.03042 -0.439283,0.122886 -0.657331,0.238135 -0.17532,0.09266 -0.368517,0.08873 -0.516097,0.0034 -0.147577,-0.0852 -0.247635,-0.250542 -0.25506,-0.448696 C 19.499068,9.7645223 19.474933,9.5377607 19.385811,9.3226061 19.21463,8.9093853 18.886236,8.5810255 18.472951,8.4098311 18.294838,8.336061 18.111106,8.3074462 17.916607,8.2941837 17.729734,8.281208 17.502159,8.281208 17.229518,8.281208 Z m 0.01569,4.293575 c 1.270347,0 2.300127,1.029802 2.300127,2.300124 0,1.270344 -1.02978,2.300124 -2.300127,2.300124 -1.270314,0 -2.300123,-1.02978 -2.300123,-2.300124 0,-1.270322 1.029809,-2.300124 2.300123,-2.300124 z" />
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c735ss4h37y8i"
path="res://.godot/imported/update_mirror.svg-d6fa4832a7758cad02a7f32282433230.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/update_mirror.svg"
dest_files=["res://.godot/imported/update_mirror.svg-d6fa4832a7758cad02a7f32282433230.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="31" height="22" viewBox="0 0 31 22" fill="none" version="1.1" id="svg4" sodipodi:docname="update_portrait.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="true" inkscape:zoom="16" inkscape:cx="23.75" inkscape:cy="14.0625" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4">
<inkscape:grid type="xygrid" id="grid6918" originx="0" originy="0" />
</sodipodi:namedview>
<path fill-rule="evenodd" clip-rule="evenodd" d="m 9.9375,15.875 c 3.576743,0 6.476256,-2.960429 6.476256,-6.612317 0,-3.6518789 -2.899513,-6.612317 -6.476256,-6.612317 -3.5767353,0 -6.4762575,2.9604381 -6.4762575,6.612317 0,3.651888 2.8995222,6.612317 6.4762575,6.612317 z M 5.9096842,8.375405 c 0.332121,-0.339136 0.7825817,-0.5296473 1.2522769,-0.5296473 0.4696953,0 0.9201559,0.1905113 1.2522768,0.5296473 L 8.6852232,8.652084 9.339464,7.984097 9.0684787,7.7074187 C 8.5628126,7.1911667 7.8770324,6.9011417 7.1619611,6.9011417 c -0.7150713,0 -1.4008514,0.290025 -1.9064807,0.806277 L 4.9845045,7.984097 5.6386991,8.652084 Z M 12.713037,7.8457577 c -0.469713,0 -0.920183,0.1905113 -1.252322,0.5296473 L 11.189823,8.652084 10.535537,7.984097 10.806521,7.7074187 c 0.505703,-0.516252 1.191445,-0.806277 1.906516,-0.806277 0.715072,0 1.400815,0.290025 1.906519,0.806277 L 14.890541,7.984097 14.236254,8.652084 13.965361,8.375405 C 13.633222,8.036269 13.182751,7.8457577 12.713037,7.8457577 Z M 9.9375,13.985766 c -1.8503592,0 -3.2381288,-1.889233 -3.7007186,-2.833849 h 7.4014356 c -0.46259,0.944616 -1.850358,2.833849 -3.700717,2.833849 z" fill="#ffffff" id="path2" style="display:inline;stroke-width:0.934854" inkscape:label="Portrait" />
<path id="path26928" style="display:inline;fill:#ff4596;fill-opacity:1;stroke-width:0.613366" inkscape:label="path26928" d="m 19.13594,6.968861 c -0.272575,-8.2e-6 -0.500185,9.5e-6 -0.687018,0.012976 -0.194489,0.013249 -0.37819,0.041877 -0.556302,0.1156474 -0.4133,0.1711984 -0.741702,0.4995528 -0.912902,0.9128597 -0.08914,0.2151709 -0.113203,0.4419435 -0.122463,0.6884214 -0.0075,0.1981263 -0.107439,0.3634338 -0.25499,0.4486307 -0.147562,0.085189 -0.340728,0.089123 -0.516018,-0.00369 -0.218061,-0.1152514 -0.426533,-0.207727 -0.657444,-0.2381338 -0.443521,-0.058391 -0.892077,0.061805 -1.246989,0.3341293 -0.152944,0.1173821 -0.269558,0.2621716 -0.378298,0.4239769 -0.104461,0.1554392 -0.218268,0.3525194 -0.354578,0.5886184 l -0.01552,0.02687 c -0.136286,0.236087 -0.250093,0.433158 -0.332475,0.601337 -0.08575,0.17508 -0.152839,0.348492 -0.178013,0.53963 -0.05839,0.443529 0.06182,0.892086 0.334128,1.247025 0.141774,0.184685 0.326035,0.319018 0.534849,0.450213 0.167922,0.105499 0.261112,0.274802 0.2611,0.445256 -4e-6,0.170395 -0.09319,0.339684 -0.2611,0.445179 -0.208839,0.131218 -0.393112,0.265542 -0.534887,0.450286 -0.272334,0.354894 -0.39255,0.803431 -0.334165,1.246956 0.02519,0.191135 0.09222,0.364576 0.178014,0.53963 0.08239,0.168183 0.196157,0.365293 0.332477,0.601374 l 0.01552,0.02687 c 0.136299,0.236083 0.250113,0.433152 0.354577,0.588576 0.108764,0.161802 0.225385,0.306641 0.378336,0.423982 0.35491,0.272332 0.803465,0.392553 1.246989,0.334165 0.230896,-0.03043 0.439318,-0.122887 0.657371,-0.238137 0.175301,-0.09269 0.368508,-0.08873 0.516089,-0.0034 0.147577,0.08519 0.247617,0.250486 0.255026,0.448666 0.0092,0.246453 0.03333,0.473216 0.122463,0.688386 0.171199,0.413282 0.4996,0.741708 0.912902,0.912899 0.178115,0.0738 0.361807,0.102372 0.556303,0.115607 0.186836,0.01298 0.414443,0.01298 0.687018,0.01298 h 0.03098 c 0.272641,0 0.500197,0 0.687087,-0.01298 0.194489,-0.01325 0.378223,-0.04182 0.556344,-0.11562 0.413285,-0.171184 0.741671,-0.499617 0.912862,-0.912901 0.08914,-0.21517 0.113203,-0.441908 0.122409,-0.68842 0.0075,-0.198111 0.107425,-0.36345 0.254987,-0.448709 0.147577,-0.08522 0.340795,-0.08914 0.516092,0.0034 0.218051,0.115251 0.426474,0.207726 0.657404,0.2381 0.443522,0.05839 0.892099,-0.06174 1.246993,-0.334094 0.152961,-0.117441 0.269546,-0.262206 0.378297,-0.424009 0.104448,-0.155439 0.218252,-0.352497 0.354541,-0.588579 l 0.01569,-0.02687 c 0.136272,-0.236049 0.2501,-0.433192 0.332472,-0.601376 0.08573,-0.17506 0.152807,-0.348467 0.178015,-0.539596 0.05839,-0.443585 -0.06185,-0.89213 -0.334163,-1.247026 C 24.904205,14.273221 24.719888,14.138951 24.511101,14.007752 24.343179,13.902253 24.25,13.732953 24.25,13.5625 c 0,-0.170395 0.09314,-0.339612 0.261028,-0.445112 0.208903,-0.131191 0.393243,-0.265477 0.534992,-0.450283 0.272333,-0.354903 0.392559,-0.803461 0.334166,-1.246992 -0.0252,-0.191132 -0.09228,-0.364516 -0.178014,-0.539595 -0.08236,-0.168164 -0.196157,-0.365264 -0.332511,-0.601337 l -0.01552,-0.02685 C 24.717779,10.016297 24.604021,9.8191797 24.499565,9.6637461 24.390802,9.5019435 24.274206,9.3571349 24.121233,9.2397705 23.766338,8.9674396 23.317793,8.8472493 22.874207,8.9056399 22.643333,8.9360576 22.434924,9.0285264 22.216876,9.143775 22.041556,9.236435 21.848359,9.232501 21.700779,9.147185 21.553202,9.0619873 21.453144,8.8966429 21.445719,8.6984893 21.436568,8.4520223 21.412433,8.2252607 21.323311,8.0101061 21.15213,7.5968853 20.823736,7.2685255 20.410451,7.0973311 20.232338,7.023561 20.048606,6.9949462 19.854107,6.9816837 19.667234,6.968708 19.439659,6.968708 19.167018,6.968708 Z m 0.01569,4.293575 c 1.270347,0 2.300127,1.029802 2.300127,2.300124 0,1.270344 -1.02978,2.300124 -2.300127,2.300124 -1.270314,0 -2.300123,-1.02978 -2.300123,-2.300124 0,-1.270322 1.029809,-2.300124 2.300123,-2.300124 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://qx5bntelnslj"
path="res://.godot/imported/update_portrait.svg-b90fa6163d3720d34df8578ce2aa35e1.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/update_portrait.svg"
dest_files=["res://.godot/imported/update_portrait.svg-b90fa6163d3720d34df8578ce2aa35e1.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="31" height="22" viewBox="0 0 31 22" fill="none" version="1.1" id="svg4" sodipodi:docname="update_position.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="true" inkscape:zoom="16" inkscape:cx="23.84375" inkscape:cy="14.0625" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4">
<inkscape:grid type="xygrid" id="grid6918" originx="0" originy="0" />
</sodipodi:namedview>
<path fill-rule="evenodd" clip-rule="evenodd" d="m 9.6329576,18.73815 c 4.1893454,0 7.5854694,-3.467473 7.5854694,-7.744834 0,-4.2773499 -3.396124,-7.7448337 -7.5854694,-7.7448337 -4.1893365,0 -7.5854711,3.4674838 -7.5854711,7.7448337 0,4.277361 3.3961346,7.744834 7.5854711,7.744834 z M 4.9152823,9.9540703 c 0.3890046,-0.397221 0.9166174,-0.6203616 1.4667592,-0.6203616 0.5501418,0 1.0777545,0.2231406 1.4667591,0.6203616 L 8.1661985,10.278137 8.9324937,9.4957421 8.6150957,9.1716755 C 8.0228222,8.5670037 7.2195858,8.2273042 6.3820415,8.2273042 c -0.8375444,0 -1.6407807,0.3396995 -2.233011,0.9443713 l -0.317387,0.3240666 0.766241,0.7823949 z M 12.883872,9.3337087 c -0.550163,0 -1.077787,0.2231406 -1.466813,0.6203616 L 11.099771,10.278137 10.333423,9.4957421 10.650819,9.1716755 c 0.592317,-0.6046718 1.395509,-0.9443713 2.233053,-0.9443713 0.837545,0 1.640738,0.3396995 2.233056,0.9443713 l 0.317397,0.3240666 -0.766349,0.7823949 -0.31729,-0.3240667 C 13.96166,9.5568493 13.434035,9.3337087 12.883872,9.3337087 Z M 9.6329576,16.52534 c -2.1672773,0 -3.7927356,-2.21281 -4.3345549,-3.319213 h 8.6691083 c -0.54182,1.106403 -2.167276,3.319213 -4.3345534,3.319213 z" fill="#ffffff" id="path2" style="display:none;stroke-width:1.09497" inkscape:label="Portrait" />
<path style="display:none;fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 17.531776,3.4508426 1.936447,3.0983145 3.063553,4.9016859 -5,8" id="path4576" sodipodi:nodetypes="cccc" />
<g inkscape:groupmode="layer" id="layer3" inkscape:label="Position" style="display:inline">
<path d="m 15.435727,5.7204944 c 0,2.118883 -1.717741,3.8365686 -3.836615,3.8365686 -2.1188833,0 -3.8365806,-1.7176856 -3.8365806,-3.8365686 0,-2.1188838 1.7176973,-3.8365775 3.8365806,-3.8365775 2.118874,0 3.836615,1.7176937 3.836615,3.8365775 z" fill="#ffffff" id="path19723" style="stroke-width:1.05506" />
<path d="m 16.202965,15.244259 c 0,1.985936 -2.061163,1.985936 -4.603853,1.985936 -2.542658,0 -4.6038923,0 -4.6038923,-1.985936 0,-3.564724 2.0612343,-6.454507 4.6038923,-6.454507 2.54269,0 4.603853,2.889783 4.603853,6.454507 z" fill="#ffffff" id="path19725" style="stroke-width:1.05506" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.744;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 7.2613947,9.27472 h -4" id="path22754" sodipodi:nodetypes="cc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 5.2613947,6.2747209 -2,2.9999991 2,3" id="path22756" sodipodi:nodetypes="ccc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.64208;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 15.767666,9.21222 h 3.487458" id="path25041" sodipodi:nodetypes="cc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.60066;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="M 17.511395,6.1617262 19.255124,9.21222 17.511395,12.262715" id="path25043" sodipodi:nodetypes="ccc" />
</g>
<path id="path26928" style="display:inline;fill:#ff4596;fill-opacity:1;stroke-width:0.613366" inkscape:label="path26928" d="m 20.13594,8.406361 c -0.272575,-8.2e-6 -0.500185,9.5e-6 -0.687018,0.012976 -0.194489,0.013249 -0.37819,0.041877 -0.556302,0.1156474 -0.4133,0.1711984 -0.741702,0.4995528 -0.912902,0.9128597 -0.08914,0.2151709 -0.113203,0.4419435 -0.122463,0.6884219 -0.0075,0.198126 -0.107439,0.363433 -0.25499,0.44863 -0.147562,0.08519 -0.340728,0.08912 -0.516018,-0.0037 -0.218061,-0.115251 -0.426533,-0.207727 -0.657444,-0.238134 -0.443521,-0.05839 -0.892077,0.06181 -1.246989,0.33413 -0.152944,0.117382 -0.269558,0.262171 -0.378298,0.423977 -0.104461,0.155439 -0.218268,0.352519 -0.354578,0.588618 l -0.01552,0.02687 c -0.136286,0.236087 -0.250093,0.433158 -0.332475,0.601337 -0.08575,0.17508 -0.152839,0.348492 -0.178013,0.53963 -0.05839,0.443529 0.06182,0.892086 0.334128,1.247025 0.141774,0.184685 0.326035,0.319018 0.534849,0.450213 0.167922,0.105499 0.261112,0.274802 0.2611,0.445256 -4e-6,0.170395 -0.09319,0.339684 -0.2611,0.445179 -0.208839,0.131218 -0.393112,0.265542 -0.534887,0.450286 -0.272334,0.354894 -0.39255,0.803431 -0.334165,1.246956 0.02519,0.191135 0.09222,0.364576 0.178014,0.53963 0.08239,0.168183 0.196157,0.365293 0.332477,0.601374 l 0.01552,0.02687 c 0.136299,0.236083 0.250113,0.433152 0.354577,0.588576 0.108764,0.161802 0.225385,0.306641 0.378336,0.423982 0.35491,0.272332 0.803465,0.392553 1.246989,0.334165 0.230896,-0.03043 0.439318,-0.122887 0.657371,-0.238137 0.175301,-0.09269 0.368508,-0.08873 0.516089,-0.0034 0.147577,0.08519 0.247617,0.250486 0.255026,0.448666 0.0092,0.246453 0.03333,0.473216 0.122463,0.688386 0.171199,0.413282 0.4996,0.741708 0.912902,0.912899 0.178115,0.0738 0.361807,0.102372 0.556303,0.115607 0.186836,0.01298 0.414443,0.01298 0.687018,0.01298 h 0.03098 c 0.272641,0 0.500197,0 0.687087,-0.01298 0.194489,-0.01325 0.378223,-0.04182 0.556344,-0.11562 0.413285,-0.171184 0.741671,-0.499617 0.912862,-0.912901 0.08914,-0.21517 0.113203,-0.441908 0.122409,-0.68842 0.0075,-0.198111 0.107425,-0.36345 0.254987,-0.448709 0.147577,-0.08522 0.340795,-0.08914 0.516092,0.0034 0.218051,0.115251 0.426474,0.207726 0.657404,0.2381 0.443522,0.05839 0.892099,-0.06174 1.246993,-0.334094 0.152961,-0.117441 0.269546,-0.262206 0.378297,-0.424009 0.104448,-0.155439 0.218252,-0.352497 0.354541,-0.588579 l 0.01569,-0.02687 c 0.136272,-0.236049 0.2501,-0.433192 0.332472,-0.601376 0.08573,-0.17506 0.152807,-0.348467 0.178015,-0.539596 0.05839,-0.443585 -0.06185,-0.89213 -0.334163,-1.247026 C 25.904205,15.710721 25.719888,15.576451 25.511101,15.445252 25.343179,15.339753 25.25,15.170453 25.25,15 c 0,-0.170395 0.09314,-0.339612 0.261028,-0.445112 0.208903,-0.131191 0.393243,-0.265477 0.534992,-0.450283 0.272333,-0.354903 0.392559,-0.803461 0.334166,-1.246992 -0.0252,-0.191132 -0.09228,-0.364516 -0.178014,-0.539595 -0.08236,-0.168164 -0.196157,-0.365264 -0.332511,-0.601337 l -0.01552,-0.02685 c -0.136362,-0.236031 -0.25012,-0.433148 -0.354576,-0.588582 -0.108763,-0.161803 -0.225359,-0.306611 -0.378332,-0.423976 -0.354895,-0.27233 -0.80344,-0.392521 -1.247026,-0.33413 -0.230874,0.03042 -0.439283,0.122886 -0.657331,0.238135 -0.17532,0.09266 -0.368517,0.08873 -0.516097,0.0034 -0.147577,-0.0852 -0.247635,-0.250542 -0.25506,-0.448696 C 22.436568,9.8895223 22.412433,9.6627607 22.323311,9.4476061 22.15213,9.0343853 21.823736,8.7060255 21.410451,8.5348311 21.232338,8.461061 21.048606,8.4324462 20.854107,8.4191837 20.667234,8.406208 20.439659,8.406208 20.167018,8.406208 Z m 0.01569,4.293575 c 1.270347,0 2.300127,1.029802 2.300127,2.300124 0,1.270344 -1.02978,2.300124 -2.300127,2.300124 -1.270314,0 -2.300123,-1.02978 -2.300123,-2.300124 0,-1.270322 1.029809,-2.300124 2.300123,-2.300124 z" />
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="31" height="22" viewBox="0 0 31 22" fill="none" version="1.1" id="svg4" sodipodi:docname="update_position.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="true" inkscape:zoom="16" inkscape:cx="23.84375" inkscape:cy="14.0625" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4">
<inkscape:grid type="xygrid" id="grid6918" originx="0" originy="0" />
</sodipodi:namedview>
<path fill-rule="evenodd" clip-rule="evenodd" d="m 9.6329576,18.73815 c 4.1893454,0 7.5854694,-3.467473 7.5854694,-7.744834 0,-4.2773499 -3.396124,-7.7448337 -7.5854694,-7.7448337 -4.1893365,0 -7.5854711,3.4674838 -7.5854711,7.7448337 0,4.277361 3.3961346,7.744834 7.5854711,7.744834 z M 4.9152823,9.9540703 c 0.3890046,-0.397221 0.9166174,-0.6203616 1.4667592,-0.6203616 0.5501418,0 1.0777545,0.2231406 1.4667591,0.6203616 L 8.1661985,10.278137 8.9324937,9.4957421 8.6150957,9.1716755 C 8.0228222,8.5670037 7.2195858,8.2273042 6.3820415,8.2273042 c -0.8375444,0 -1.6407807,0.3396995 -2.233011,0.9443713 l -0.317387,0.3240666 0.766241,0.7823949 z M 12.883872,9.3337087 c -0.550163,0 -1.077787,0.2231406 -1.466813,0.6203616 L 11.099771,10.278137 10.333423,9.4957421 10.650819,9.1716755 c 0.592317,-0.6046718 1.395509,-0.9443713 2.233053,-0.9443713 0.837545,0 1.640738,0.3396995 2.233056,0.9443713 l 0.317397,0.3240666 -0.766349,0.7823949 -0.31729,-0.3240667 C 13.96166,9.5568493 13.434035,9.3337087 12.883872,9.3337087 Z M 9.6329576,16.52534 c -2.1672773,0 -3.7927356,-2.21281 -4.3345549,-3.319213 h 8.6691083 c -0.54182,1.106403 -2.167276,3.319213 -4.3345534,3.319213 z" fill="#ffffff" id="path2" style="display:none;stroke-width:1.09497" inkscape:label="Portrait" />
<path style="display:none;fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 17.531776,3.4508426 1.936447,3.0983145 3.063553,4.9016859 -5,8" id="path4576" sodipodi:nodetypes="cccc" />
<g inkscape:groupmode="layer" id="layer3" inkscape:label="Position" style="display:inline">
<path d="m 15.435727,5.7204944 c 0,2.118883 -1.717741,3.8365686 -3.836615,3.8365686 -2.1188833,0 -3.8365806,-1.7176856 -3.8365806,-3.8365686 0,-2.1188838 1.7176973,-3.8365775 3.8365806,-3.8365775 2.118874,0 3.836615,1.7176937 3.836615,3.8365775 z" fill="#ffffff" id="path19723" style="stroke-width:1.05506" />
<path d="m 16.202965,15.244259 c 0,1.985936 -2.061163,1.985936 -4.603853,1.985936 -2.542658,0 -4.6038923,0 -4.6038923,-1.985936 0,-3.564724 2.0612343,-6.454507 4.6038923,-6.454507 2.54269,0 4.603853,2.889783 4.603853,6.454507 z" fill="#ffffff" id="path19725" style="stroke-width:1.05506" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.744;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 7.2613947,9.27472 h -4" id="path22754" sodipodi:nodetypes="cc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 5.2613947,6.2747209 -2,2.9999991 2,3" id="path22756" sodipodi:nodetypes="ccc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.64208;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="m 15.767666,9.21222 h 3.487458" id="path25041" sodipodi:nodetypes="cc" />
<path style="fill:none;stroke:#ffffff;stroke-width:1.60066;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.236785;stroke-opacity:1" d="M 17.511395,6.1617262 19.255124,9.21222 17.511395,12.262715" id="path25043" sodipodi:nodetypes="ccc" />
</g>
<path id="path26928" style="display:inline;fill:#ff4596;fill-opacity:1;stroke-width:0.613366" inkscape:label="path26928" d="m 20.13594,8.406361 c -0.272575,-8.2e-6 -0.500185,9.5e-6 -0.687018,0.012976 -0.194489,0.013249 -0.37819,0.041877 -0.556302,0.1156474 -0.4133,0.1711984 -0.741702,0.4995528 -0.912902,0.9128597 -0.08914,0.2151709 -0.113203,0.4419435 -0.122463,0.6884219 -0.0075,0.198126 -0.107439,0.363433 -0.25499,0.44863 -0.147562,0.08519 -0.340728,0.08912 -0.516018,-0.0037 -0.218061,-0.115251 -0.426533,-0.207727 -0.657444,-0.238134 -0.443521,-0.05839 -0.892077,0.06181 -1.246989,0.33413 -0.152944,0.117382 -0.269558,0.262171 -0.378298,0.423977 -0.104461,0.155439 -0.218268,0.352519 -0.354578,0.588618 l -0.01552,0.02687 c -0.136286,0.236087 -0.250093,0.433158 -0.332475,0.601337 -0.08575,0.17508 -0.152839,0.348492 -0.178013,0.53963 -0.05839,0.443529 0.06182,0.892086 0.334128,1.247025 0.141774,0.184685 0.326035,0.319018 0.534849,0.450213 0.167922,0.105499 0.261112,0.274802 0.2611,0.445256 -4e-6,0.170395 -0.09319,0.339684 -0.2611,0.445179 -0.208839,0.131218 -0.393112,0.265542 -0.534887,0.450286 -0.272334,0.354894 -0.39255,0.803431 -0.334165,1.246956 0.02519,0.191135 0.09222,0.364576 0.178014,0.53963 0.08239,0.168183 0.196157,0.365293 0.332477,0.601374 l 0.01552,0.02687 c 0.136299,0.236083 0.250113,0.433152 0.354577,0.588576 0.108764,0.161802 0.225385,0.306641 0.378336,0.423982 0.35491,0.272332 0.803465,0.392553 1.246989,0.334165 0.230896,-0.03043 0.439318,-0.122887 0.657371,-0.238137 0.175301,-0.09269 0.368508,-0.08873 0.516089,-0.0034 0.147577,0.08519 0.247617,0.250486 0.255026,0.448666 0.0092,0.246453 0.03333,0.473216 0.122463,0.688386 0.171199,0.413282 0.4996,0.741708 0.912902,0.912899 0.178115,0.0738 0.361807,0.102372 0.556303,0.115607 0.186836,0.01298 0.414443,0.01298 0.687018,0.01298 h 0.03098 c 0.272641,0 0.500197,0 0.687087,-0.01298 0.194489,-0.01325 0.378223,-0.04182 0.556344,-0.11562 0.413285,-0.171184 0.741671,-0.499617 0.912862,-0.912901 0.08914,-0.21517 0.113203,-0.441908 0.122409,-0.68842 0.0075,-0.198111 0.107425,-0.36345 0.254987,-0.448709 0.147577,-0.08522 0.340795,-0.08914 0.516092,0.0034 0.218051,0.115251 0.426474,0.207726 0.657404,0.2381 0.443522,0.05839 0.892099,-0.06174 1.246993,-0.334094 0.152961,-0.117441 0.269546,-0.262206 0.378297,-0.424009 0.104448,-0.155439 0.218252,-0.352497 0.354541,-0.588579 l 0.01569,-0.02687 c 0.136272,-0.236049 0.2501,-0.433192 0.332472,-0.601376 0.08573,-0.17506 0.152807,-0.348467 0.178015,-0.539596 0.05839,-0.443585 -0.06185,-0.89213 -0.334163,-1.247026 C 25.904205,15.710721 25.719888,15.576451 25.511101,15.445252 25.343179,15.339753 25.25,15.170453 25.25,15 c 0,-0.170395 0.09314,-0.339612 0.261028,-0.445112 0.208903,-0.131191 0.393243,-0.265477 0.534992,-0.450283 0.272333,-0.354903 0.392559,-0.803461 0.334166,-1.246992 -0.0252,-0.191132 -0.09228,-0.364516 -0.178014,-0.539595 -0.08236,-0.168164 -0.196157,-0.365264 -0.332511,-0.601337 l -0.01552,-0.02685 c -0.136362,-0.236031 -0.25012,-0.433148 -0.354576,-0.588582 -0.108763,-0.161803 -0.225359,-0.306611 -0.378332,-0.423976 -0.354895,-0.27233 -0.80344,-0.392521 -1.247026,-0.33413 -0.230874,0.03042 -0.439283,0.122886 -0.657331,0.238135 -0.17532,0.09266 -0.368517,0.08873 -0.516097,0.0034 -0.147577,-0.0852 -0.247635,-0.250542 -0.25506,-0.448696 C 22.436568,9.8895223 22.412433,9.6627607 22.323311,9.4476061 22.15213,9.0343853 21.823736,8.7060255 21.410451,8.5348311 21.232338,8.461061 21.048606,8.4324462 20.854107,8.4191837 20.667234,8.406208 20.439659,8.406208 20.167018,8.406208 Z m 0.01569,4.293575 c 1.270347,0 2.300127,1.029802 2.300127,2.300124 0,1.270344 -1.02978,2.300124 -2.300127,2.300124 -1.270314,0 -2.300123,-1.02978 -2.300123,-2.300124 0,-1.270322 1.029809,-2.300124 2.300123,-2.300124 z" />
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://deu1h3rsp3p8y"
path="res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-d66e70a05e9de92a595b70fa88fca0d4.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg"
dest_files=["res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-d66e70a05e9de92a595b70fa88fca0d4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rslog4jar4gu"
path="res://.godot/imported/update_position.svg-4be50608dc0768e715ff659ca62cf187.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/update_position.svg"
dest_files=["res://.godot/imported/update_position.svg-4be50608dc0768e715ff659ca62cf187.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="31" height="22" viewBox="0 0 31 22" fill="none" version="1.1" id="svg4" sodipodi:docname="update_z_index.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#505050" bordercolor="#eeeeee" borderopacity="1" inkscape:showpageshadow="0" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#505050" showgrid="true" inkscape:zoom="16" inkscape:cx="23.84375" inkscape:cy="14.0625" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4">
<inkscape:grid type="xygrid" id="grid6918" originx="0" originy="0" />
</sodipodi:namedview>
<g inkscape:groupmode="layer" id="layer1" inkscape:label="Z_Index" style="display:inline" transform="matrix(0.90945359,0,0,0.90945359,0.03431233,1.6441321)">
<path style="fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="M 4.0841125,6.8565774 V 16.856578 H 12.084113 V 6.8565774 Z" id="path6916" sodipodi:nodetypes="ccccc" />
<path style="fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 12.084113,13.856578 h 3 V 3.8565774 H 7.0841125 v 3" id="path6920" sodipodi:nodetypes="ccccc" />
<path style="fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 15.084113,10.856578 h 3 V 0.8565774 h -8.000001 v 3" id="path6922" sodipodi:nodetypes="ccccc" />
<path style="fill:none;fill-opacity:0.980952;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" d="m 6.5841125,9.8565775 h 3 l -3,4.0000005 h 3" id="path6924" sodipodi:nodetypes="cccc" />
</g>
<path id="path26928" style="display:inline;fill:#ff4596;fill-opacity:1;stroke-width:0.613366" inkscape:label="path26928" d="m 19.07344,7.281361 c -0.272575,-8.2e-6 -0.500185,9.5e-6 -0.687018,0.012976 -0.194489,0.013249 -0.37819,0.041877 -0.556302,0.1156474 -0.4133,0.1711984 -0.741702,0.4995528 -0.912902,0.9128597 -0.08914,0.2151709 -0.113203,0.4419435 -0.122463,0.6884214 -0.0075,0.1981263 -0.107439,0.3634338 -0.25499,0.4486307 -0.147562,0.085189 -0.340728,0.089123 -0.516018,-0.00369 -0.218061,-0.1152514 -0.426533,-0.207727 -0.657444,-0.2381338 -0.443521,-0.058391 -0.892077,0.061805 -1.246989,0.3341293 -0.152944,0.1173821 -0.269558,0.2621716 -0.378298,0.4239769 -0.104461,0.1554394 -0.218268,0.3525194 -0.354578,0.5886184 l -0.01552,0.02687 c -0.136286,0.236087 -0.250093,0.433158 -0.332475,0.601337 -0.08575,0.17508 -0.152839,0.348492 -0.178013,0.53963 -0.05839,0.443529 0.06182,0.892086 0.334128,1.247025 0.141774,0.184685 0.326035,0.319018 0.534849,0.450213 0.167922,0.105499 0.261112,0.274802 0.2611,0.445256 -4e-6,0.170395 -0.09319,0.339684 -0.2611,0.445179 -0.208839,0.131218 -0.393112,0.265542 -0.534887,0.450286 -0.272334,0.354894 -0.39255,0.803431 -0.334165,1.246956 0.02519,0.191135 0.09222,0.364576 0.178014,0.53963 0.08239,0.168183 0.196157,0.365293 0.332477,0.601374 l 0.01552,0.02687 c 0.136299,0.236083 0.250113,0.433152 0.354577,0.588576 0.108764,0.161802 0.225385,0.306641 0.378336,0.423982 0.35491,0.272332 0.803465,0.392553 1.246989,0.334165 0.230896,-0.03043 0.439318,-0.122887 0.657371,-0.238137 0.175301,-0.09269 0.368508,-0.08873 0.516089,-0.0034 0.147577,0.08519 0.247617,0.250486 0.255026,0.448666 0.0092,0.246453 0.03333,0.473216 0.122463,0.688386 0.171199,0.413282 0.4996,0.741708 0.912902,0.912899 0.178115,0.0738 0.361807,0.102372 0.556303,0.115607 0.186836,0.01298 0.414443,0.01298 0.687018,0.01298 h 0.03098 c 0.272641,0 0.500197,0 0.687087,-0.01298 0.194489,-0.01325 0.378223,-0.04182 0.556344,-0.11562 0.413285,-0.171184 0.741671,-0.499617 0.912862,-0.912901 0.08914,-0.21517 0.113203,-0.441908 0.122409,-0.68842 0.0075,-0.198111 0.107425,-0.36345 0.254987,-0.448709 0.147577,-0.08522 0.340795,-0.08914 0.516092,0.0034 0.218051,0.115251 0.426474,0.207726 0.657404,0.2381 0.443522,0.05839 0.892099,-0.06174 1.246993,-0.334094 0.152961,-0.117441 0.269546,-0.262206 0.378297,-0.424009 0.104448,-0.155439 0.218252,-0.352497 0.354541,-0.588579 l 0.01569,-0.02687 c 0.136272,-0.236049 0.2501,-0.433192 0.332472,-0.601376 0.08573,-0.17506 0.152807,-0.348467 0.178015,-0.539596 0.05839,-0.443585 -0.06185,-0.89213 -0.334163,-1.247026 C 24.841705,14.585721 24.657388,14.451451 24.448601,14.320252 24.280679,14.214753 24.1875,14.045453 24.1875,13.875 c 0,-0.170395 0.09314,-0.339612 0.261028,-0.445112 0.208903,-0.131191 0.393243,-0.265477 0.534992,-0.450283 0.272333,-0.354903 0.392559,-0.803461 0.334166,-1.246992 -0.0252,-0.191132 -0.09228,-0.364516 -0.178014,-0.539595 -0.08236,-0.168164 -0.196157,-0.365264 -0.332511,-0.601337 l -0.01552,-0.02685 C 24.655279,10.328797 24.541521,10.13168 24.437065,9.9762461 24.328302,9.8144435 24.211706,9.6696349 24.058733,9.5522705 23.703838,9.2799396 23.255293,9.1597493 22.811707,9.2181399 22.580833,9.2485576 22.372424,9.3410264 22.154376,9.456275 21.979056,9.548935 21.785859,9.545001 21.638279,9.459685 21.490702,9.3744873 21.390644,9.2091429 21.383219,9.0109893 21.374068,8.7645223 21.349933,8.5377607 21.260811,8.3226061 21.08963,7.9093853 20.761236,7.5810255 20.347951,7.4098311 20.169838,7.336061 19.986106,7.3074462 19.791607,7.2941837 19.604734,7.281208 19.377159,7.281208 19.104518,7.281208 Z m 0.01569,4.293575 c 1.270347,0 2.300127,1.029802 2.300127,2.300124 0,1.270344 -1.02978,2.300124 -2.300127,2.300124 -1.270314,0 -2.300123,-1.02978 -2.300123,-2.300124 0,-1.270322 1.029809,-2.300124 2.300123,-2.300124 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c5xa3i541igyk"
path="res://.godot/imported/update_z_index.svg-204064db9241de79bf8dfd23af57c6c6.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Character/update_z_index.svg"
dest_files=["res://.godot/imported/update_z_index.svg-204064db9241de79bf8dfd23af57c6c6.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
@@ -0,0 +1,232 @@
@tool
class_name DialogicChoiceEvent
extends DialogicEvent
## Event that allows adding choices. Needs to go after a text event (or another choices EndBranch).
enum ElseActions {HIDE=0, DISABLE=1, DEFAULT=2}
### Settings
## The text that is displayed on the choice button.
var text := ""
## If not empty this condition will determine if this choice is active.
var condition := ""
## Determines what happens if [condition] is false. Default will use the action set in the settings.
var else_action := ElseActions.DEFAULT
## The text that is displayed if [condition] is false and [else_action] is Disable.
## If empty [text] will be used for disabled button as well.
var disabled_text := ""
## A dictionary that can be filled with arbitrary information
## This can then be interpreted by a custom choice layer
var extra_data := {}
## UI helper
var _has_condition := false
#endregion
var regex := RegEx.create_from_string(r'- (?<text>(?>\\\||(?(?=.*\|)[^|]|(?!\[if)[^|]))*)\|?\s*(\[if(?<condition>([^\]\[]|\[[^\]]*\])+)\])?\s*(\[(?<shortcode>[^]]*)\])?')
#region EXECUTION
################################################################################
func _execute() -> void:
if dialogic.Choices.is_question(dialogic.current_event_idx):
dialogic.Choices.show_current_question(false)
dialogic.current_state = dialogic.States.AWAITING_CHOICE
#endregion
#region INITIALIZE
################################################################################
func _init() -> void:
event_name = "Choice"
set_default_color('Color3')
event_category = "Flow"
event_sorting_index = 0
can_contain_events = true
wants_to_group = true
# return a control node that should show on the END BRANCH node
func get_end_branch_control() -> Control:
return load(get_script().resource_path.get_base_dir().path_join('ui_choice_end.tscn')).instantiate()
#endregion
#region SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string := ""
result_string = "- "+text.strip_edges()
var shortcode := store_to_shortcode_parameters()
if (condition and _has_condition) or shortcode or extra_data:
result_string += " |"
if condition and _has_condition:
result_string += " [if " + condition + "]"
if shortcode or extra_data:
result_string += " [" + shortcode
if extra_data:
var extra_data_string := ""
for i in extra_data:
extra_data_string += " " + i + '="' + value_to_string(extra_data[i]) + '"'
if shortcode:
result_string += " "
result_string += extra_data_string.strip_edges()
result_string += "]"
return result_string
func from_text(string:String) -> void:
var result := regex.search(string.strip_edges())
if result == null:
return
text = result.get_string('text').strip_edges()
condition = result.get_string('condition').strip_edges()
_has_condition = not condition.is_empty()
if result.get_string('shortcode'):
load_from_shortcode_parameters(result.get_string("shortcode"))
var shortcode := parse_shortcode_parameters(result.get_string('shortcode'))
shortcode.erase("else")
shortcode.erase("alt_text")
extra_data = shortcode.duplicate()
func get_shortcode_parameters() -> Dictionary:
return {
"else" : {"property": "else_action", "default": ElseActions.DEFAULT,
"suggestions": func(): return {
"Default" :{'value':ElseActions.DEFAULT, 'text_alt':['default']},
"Hide" :{'value':ElseActions.HIDE,'text_alt':['hide'] },
"Disable" :{'value':ElseActions.DISABLE,'text_alt':['disable']}}},
"alt_text" : {"property": "disabled_text", "default": ""},
"extra_data" : {"property": "extra_data", "default": {}, "custom_stored":true},
}
func is_valid_event(string:String) -> bool:
if string.strip_edges().begins_with("-"):
return true
return false
#endregion
#region TRANSLATIONS
################################################################################
func _get_translatable_properties() -> Array:
return ['text', 'disabled_text']
func _get_property_original_translation(property:String) -> String:
match property:
'text':
return text
'disabled_text':
return disabled_text
return ''
#endregion
#region EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit("text", ValueType.SINGLELINE_TEXT, {'autofocus':true})
add_body_edit("", ValueType.LABEL, {"text":"Condition:"})
add_body_edit("_has_condition", ValueType.BOOL_BUTTON, {"editor_icon":["Add", "EditorIcons"], "tooltip":"Add Condition"}, "not _has_condition")
add_body_edit("condition", ValueType.CONDITION, {}, "_has_condition")
add_body_edit("_has_condition", ValueType.BOOL_BUTTON, {"editor_icon":["Remove", "EditorIcons"], "tooltip":"Remove Condition"}, "_has_condition")
add_body_edit("else_action", ValueType.FIXED_OPTIONS, {'left_text':'Else:',
'options': [
{
'label': 'Default',
'value': ElseActions.DEFAULT,
},
{
'label': 'Hide',
'value': ElseActions.HIDE,
},
{
'label': 'Disable',
'value': ElseActions.DISABLE,
}
]}, '_has_condition')
add_body_edit("disabled_text", ValueType.SINGLELINE_TEXT, {
'left_text':'Disabled text:',
'placeholder':'(Empty for same)'}, 'allow_alt_text()')
add_body_line_break()
add_body_edit("extra_data", ValueType.DICTIONARY, {"left_text":"Extra Data:"})
func allow_alt_text() -> bool:
return _has_condition and (
else_action == ElseActions.DISABLE or
(else_action == ElseActions.DEFAULT and
ProjectSettings.get_setting("dialogic/choices/def_false_behaviour", 0) == 1))
#endregion
#region CODE COMPLETION
################################################################################
func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
line = CodeCompletionHelper.get_line_untill_caret(line)
if !'[if' in line:
if symbol == '{':
CodeCompletionHelper.suggest_variables(TextNode)
return
if symbol == '[':
if !'[if' in line and line.count('[') - line.count(']') == 1:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'if', 'if ', TextNode.syntax_highlighter.code_flow_color)
elif '[if' in line:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'else', 'else="', TextNode.syntax_highlighter.code_flow_color)
if symbol == ' ' and '[else' in line:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'alt_text', 'alt_text="', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5))
elif symbol == '{':
CodeCompletionHelper.suggest_variables(TextNode)
if (symbol == '=' or symbol == '"') and line.count('[') > 1 and !'" ' in line:
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'default', "default", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'hide', "hide", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'disable', "disable", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
#endregion
#region SYNTAX HIGHLIGHTING
################################################################################
func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
var result := regex.search(line)
dict[0] = {'color':event_color}
if not result:
return dict
var condition_begin := result.get_start("condition")
var condition_end := result.get_end("condition")
var shortcode_begin := result.get_start("shortcode")
dict = Highlighter.color_region(dict, event_color.lerp(Highlighter.variable_color, 0.5), line, '{','}', 0, condition_begin, event_color)
if condition_begin > 0:
var from := line.find('[if')
dict[from] = {"color":Highlighter.normal_color}
dict[from+1] = {"color":Highlighter.code_flow_color}
dict[condition_begin] = {"color":Highlighter.normal_color}
dict = Highlighter.color_condition(dict, line, condition_begin, condition_end)
if shortcode_begin:
dict = Highlighter.color_shortcode_content(dict, line, shortcode_begin, 0, event_color)
return dict
#endregion
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="63.999996" height="63.999996" viewBox="0 0 16.933332 16.933332" version="1.1" id="svg5" inkscape:export-filename="choice-icon.svg" inkscape:export-xdpi="96" inkscape:export-ydpi="96" sodipodi:docname="icon.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview id="namedview7" pagecolor="#464646" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" showguides="true" inkscape:zoom="1.0544998" inkscape:cx="-184.44765" inkscape:cy="-1.8966339" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg5" />
<defs id="defs2" />
<rect style="stroke-width:3.57045;stroke-dasharray:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0;stroke-opacity:1" id="rect26013" width="58.831448" height="24.065563" x="-167.86046" y="-31.618748" ry="12.032781" transform="matrix(0.15519359,0,0,0.15519359,31.232208,8.9372117)" />
<path sodipodi:type="star" style="stroke-width:8.16565;stroke-dasharray:none;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0;stroke-opacity:1" id="path26015" inkscape:flatsided="false" sodipodi:sides="3" sodipodi:cx="-138.47092" sodipodi:cy="-7.3761749" sodipodi:r1="20.010386" sodipodi:r2="10.457433" sodipodi:arg1="0.52359878" sodipodi:arg2="1.5707963" inkscape:rounded="0.02640344" inkscape:randomized="0" d="m -121.14141,2.6290179 c -0.22886,0.3963922 -16.87179,0.4522399 -17.32951,0.4522399 -0.45771,0 -17.10064,-0.055848 -17.3295,-0.45224 -0.22886,-0.3963922 8.04424,-14.8375168 8.2731,-15.2339088 0.22886,-0.396392 8.59869,-14.781669 9.0564,-14.781669 0.45772,0 8.82755,14.385276 9.05641,14.781668 0.22885,0.396393 8.50195,14.8375177 8.2731,15.2339099 z" transform="matrix(0,-0.07693147,-0.06541836,0,2.2708052,-4.7551753)" inkscape:transform-center-x="-0.73902511" />
<path id="path28608" style="stroke-width:3.3313;stroke-dasharray:none;fill:#ffffff;color:#000000;fill-opacity:0.45;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none" d="m -155.82823,2.6553184 c -7.56011,0 -13.69881,6.1387113 -13.69881,13.6988156 0,7.560104 6.1387,13.698816 13.69881,13.698816 h 34.76649 c 7.56011,0 13.69882,-6.138712 13.69882,-13.698816 0,-7.5601043 -6.13871,-13.6988156 -13.69882,-13.6988156 z" transform="matrix(0.15519359,0,0,0.15519359,31.232208,8.9372117)" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://wd4fnxjfmj5m"
path="res://.godot/imported/icon.svg-372a9bfce8bea67fff1988d7acf09a9a.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Modules/Choice/icon.svg"
dest_files=["res://.godot/imported/icon.svg-372a9bfce8bea67fff1988d7acf09a9a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
+14
View File
@@ -0,0 +1,14 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_choice.gd')]
func _get_subsystems() -> Array:
return [{'name':'Choices', 'script':this_folder.path_join('subsystem_choices.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_choices.tscn')]
@@ -0,0 +1,52 @@
class_name DialogicNode_ButtonSound
extends AudioStreamPlayer
## Node that is used for playing sound effects on hover/focus/press of sibling DialogicNode_ChoiceButtons.
## Sound to be played if one of the sibling ChoiceButtons is pressed.
## If sibling ChoiceButton has a sound_pressed set, that is prioritized.
@export var sound_pressed: AudioStream
## Sound to be played on hover. See [sound_pressed] for more.
@export var sound_hover: AudioStream
## Sound to be played on focus. See [sound_pressed] for more.
@export var sound_focus: AudioStream
func _ready() -> void:
add_to_group('dialogic_button_sound')
_connect_all_buttons()
#basic play sound
func play_sound(sound) -> void:
if sound != null:
stream = sound
play()
func _connect_all_buttons() -> void:
for child in get_parent().get_children():
if child is DialogicNode_ChoiceButton:
child.button_up.connect(_on_pressed.bind(child.sound_pressed))
child.mouse_entered.connect(_on_hover.bind(child.sound_hover))
child.focus_entered.connect(_on_focus.bind(child.sound_focus))
#the custom_sound argument comes from the specifec button and get used
#if none are found, it uses the above sounds
func _on_pressed(custom_sound) -> void:
if custom_sound != null:
play_sound(custom_sound)
else:
play_sound(sound_pressed)
func _on_hover(custom_sound) -> void:
if custom_sound != null:
play_sound(custom_sound)
else:
play_sound(sound_hover)
func _on_focus(custom_sound) -> void:
if custom_sound != null:
play_sound(custom_sound)
else:
play_sound(sound_focus)
@@ -0,0 +1,45 @@
class_name DialogicNode_ChoiceButton
extends Button
## The button allows the player to make a choice in the Dialogic system.
##
## This class is used in the Choice Layer. [br]
## You may change the [member text_node] to any [class Node] that has a
## `text` property. [br]
## If you don't set the [member text_node], the text will be set on this
## button instead.
##
## Using a different node may allow using rich text effects; they are
## not supported on buttons at this point.
## Used to identify what choices to put on. If you leave it at -1, choices will be distributed automatically.
@export var choice_index: int = -1
## Can be set to play this sound when pressed. Requires a sibling DialogicNode_ButtonSound node.
@export var sound_pressed: AudioStream
## Can be set to play this sound when hovered. Requires a sibling DialogicNode_ButtonSound node.
@export var sound_hover: AudioStream
## Can be set to play this sound when focused. Requires a sibling DialogicNode_ButtonSound node.
@export var sound_focus: AudioStream
## If set, the text will be set on this node's `text` property instead.
@export var text_node: Node
func _ready() -> void:
add_to_group('dialogic_choice_button')
shortcut_in_tooltip = false
hide()
func _load_info(choice_info: Dictionary) -> void:
set_choice_text(choice_info.text)
visible = choice_info.visible
disabled = choice_info.disabled
## Called when the text changes.
func set_choice_text(new_text: String) -> void:
if text_node:
text_node.text = new_text
else:
text = new_text
@@ -0,0 +1,67 @@
@tool
extends DialogicSettingsPage
func _refresh() -> void:
%Autofocus.button_pressed = ProjectSettings.get_setting('dialogic/choices/autofocus_first', true)
%Delay.value = ProjectSettings.get_setting('dialogic/choices/delay', 0.2)
%FalseBehaviour.select(ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', 0))
%HotkeyType.select(ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', 0))
var reveal_delay: float = ProjectSettings.get_setting('dialogic/choices/reveal_delay', 0)
var reveal_by_input: bool = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', false)
if not reveal_by_input and reveal_delay == 0:
_on_appear_mode_item_selected(0)
if not reveal_by_input and reveal_delay != 0:
_on_appear_mode_item_selected(1)
if reveal_by_input and reveal_delay == 0:
_on_appear_mode_item_selected(2)
if reveal_by_input and reveal_delay != 0:
_on_appear_mode_item_selected(3)
%RevealDelay.value = reveal_delay
func _on_Autofocus_toggled(button_pressed: bool) -> void:
ProjectSettings.set_setting('dialogic/choices/autofocus_first', button_pressed)
ProjectSettings.save()
func _on_FalseBehaviour_item_selected(index) -> void:
ProjectSettings.set_setting('dialogic/choices/def_false_behaviour', index)
ProjectSettings.save()
func _on_HotkeyType_item_selected(index) -> void:
ProjectSettings.set_setting('dialogic/choices/hotkey_behaviour', index)
ProjectSettings.save()
func _on_Delay_value_changed(value) -> void:
ProjectSettings.set_setting('dialogic/choices/delay', value)
ProjectSettings.save()
func _on_reveal_delay_value_changed(value) -> void:
ProjectSettings.set_setting('dialogic/choices/reveal_delay', value)
ProjectSettings.save()
func _on_appear_mode_item_selected(index:int) -> void:
%AppearMode.selected = index
match index:
0:
ProjectSettings.set_setting('dialogic/choices/reveal_delay', 0)
ProjectSettings.set_setting('dialogic/choices/reveal_by_input', false)
%RevealDelay.hide()
1:
ProjectSettings.set_setting('dialogic/choices/reveal_delay', %RevealDelay.value)
ProjectSettings.set_setting('dialogic/choices/reveal_by_input', false)
%RevealDelay.show()
2:
ProjectSettings.set_setting('dialogic/choices/reveal_delay', 0)
ProjectSettings.set_setting('dialogic/choices/reveal_by_input', true)
%RevealDelay.hide()
3:
ProjectSettings.set_setting('dialogic/choices/reveal_delay', %RevealDelay.value)
ProjectSettings.set_setting('dialogic/choices/reveal_by_input', true)
%RevealDelay.show()
ProjectSettings.save()
@@ -0,0 +1,176 @@
[gd_scene load_steps=5 format=3 uid="uid://uarvgnbrcltm"]
[ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/settings_choices.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_nxutt"]
[sub_resource type="Image" id="Image_2imc3"]
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_udy8i"]
image = SubResource("Image_2imc3")
[node name="Choices" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_bottom = -227.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("2")
[node name="VBoxContainer2" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Title" type="Label" parent="VBoxContainer2"]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Behaviour"
[node name="VBoxContainer" type="GridContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
columns = 2
[node name="AutofocusLabel" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/AutofocusLabel"]
layout_mode = 2
text = "Autofocus first choice"
[node name="Autofocus" type="CheckBox" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="AppearModeLabel" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label2" type="Label" parent="VBoxContainer/AppearModeLabel"]
layout_mode = 2
text = "Choices appear"
[node name="HintTooltip" parent="VBoxContainer/AppearModeLabel" instance=ExtResource("2_nxutt")]
layout_mode = 2
tooltip_text = "Choices can appear either instantly when the text finished, after a delay, a click or either."
texture = SubResource("ImageTexture_udy8i")
hint_text = "Choices can appear either instantly when the text finished, after a delay, a click or either."
[node name="RevealDelayLabel" type="HBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="AppearMode" type="OptionButton" parent="VBoxContainer/RevealDelayLabel"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
item_count = 4
selected = 0
fit_to_longest_item = false
popup/item_0/text = "Instantly"
popup/item_0/id = 0
popup/item_1/text = "After delay"
popup/item_1/id = 1
popup/item_2/text = "After another click"
popup/item_2/id = 2
popup/item_3/text = "After delay or click"
popup/item_3/id = 3
[node name="RevealDelay" type="SpinBox" parent="VBoxContainer/RevealDelayLabel"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Delay after which choices will appear (in seconds)."
step = 0.01
[node name="DelayLabel" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label2" type="Label" parent="VBoxContainer/DelayLabel"]
layout_mode = 2
text = "Delay before choices can be pressed"
[node name="HintTooltip2" parent="VBoxContainer/DelayLabel" instance=ExtResource("2_nxutt")]
layout_mode = 2
tooltip_text = "Adding a small delay before choices can be activated can prevent accidentally choosing an option."
texture = SubResource("ImageTexture_udy8i")
hint_text = "Adding a small delay before choices can be activated can prevent accidentally choosing an option."
[node name="Delay" type="SpinBox" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
step = 0.01
[node name="DefaultFalseBehaviourLabel" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label3" type="Label" parent="VBoxContainer/DefaultFalseBehaviourLabel"]
layout_mode = 2
text = "Default behaviour for false choices"
[node name="HintTooltip3" parent="VBoxContainer/DefaultFalseBehaviourLabel" instance=ExtResource("2_nxutt")]
layout_mode = 2
tooltip_text = "Define the default behaviour (hide or disable) for choices that have a condition that isn't met.
Choices can overwrite this setting individually."
texture = SubResource("ImageTexture_udy8i")
hint_text = "Define the default behaviour (hide or disable) for choices that have a condition that isn't met.
Choices can overwrite this setting individually."
[node name="FalseBehaviour" type="OptionButton" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
item_count = 2
selected = 0
popup/item_0/text = "Hide"
popup/item_0/id = 0
popup/item_1/text = "Disable"
popup/item_1/id = 1
[node name="HSeparator" type="HSeparator" parent="."]
layout_mode = 2
[node name="HotkeySelection" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Title2" type="Label" parent="HotkeySelection"]
layout_mode = 2
theme_type_variation = &"DialogicSettingsSection"
text = "Choice Hotkeys"
[node name="HintTooltip4" parent="HotkeySelection" instance=ExtResource("2_nxutt")]
layout_mode = 2
tooltip_text = "You can add more complex hotkeys (or individual ones) by editing the choice buttons of your layout scene."
texture = SubResource("ImageTexture_udy8i")
hint_text = "You can add more complex hotkeys (or individual ones) by editing the choice buttons of your layout scene."
[node name="VBoxContainer3" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label4" type="Label" parent="VBoxContainer3"]
layout_mode = 2
text = "Hotkey type"
[node name="HotkeyType" type="OptionButton" parent="VBoxContainer3"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 4
item_count = 2
selected = 0
popup/item_0/text = "No Hotkeys"
popup/item_0/id = 0
popup/item_1/text = "Default (1-9)"
popup/item_1/id = 1
[connection signal="toggled" from="VBoxContainer/Autofocus" to="." method="_on_Autofocus_toggled"]
[connection signal="item_selected" from="VBoxContainer/RevealDelayLabel/AppearMode" to="." method="_on_appear_mode_item_selected"]
[connection signal="value_changed" from="VBoxContainer/RevealDelayLabel/RevealDelay" to="." method="_on_reveal_delay_value_changed"]
[connection signal="value_changed" from="VBoxContainer/Delay" to="." method="_on_Delay_value_changed"]
[connection signal="item_selected" from="VBoxContainer/FalseBehaviour" to="." method="_on_FalseBehaviour_item_selected"]
[connection signal="item_selected" from="VBoxContainer3/HotkeyType" to="." method="_on_HotkeyType_item_selected"]
@@ -0,0 +1,281 @@
extends DialogicSubsystem
## Subsystem that manages showing and activating of choices.
## Emitted when a choice button was pressed. Info includes the keys 'button_index', 'text', 'event_index'.
signal choice_selected(info:Dictionary)
## Emitted when a set of choices is reached and shown.
## Info includes the keys 'choices' (an array of dictionaries with infos on all the choices).
signal question_shown(info:Dictionary)
## Contains information on the latest question.
var last_question_info := {}
## The delay between the text finishing revealing and the choices appearing
var reveal_delay := 0.0
## If true the player has to click to reveal choices when they are reached
var reveal_by_input := false
## The delay between the choices becoming visible and being clickable. Can prevent accidental selection.
var block_delay := 0.2
## If true, the first (top-most) choice will be focused
var autofocus_first_choice := true
## If true the dialogic input action is used to trigger choices.
## However mouse events will be ignored no matter what.
var use_input_action := false
enum FalseBehaviour {HIDE=0, DISABLE=1}
## The behaviour of choices with a false condition and else_action set to DEFAULT.
var default_false_behaviour := FalseBehaviour.HIDE
enum HotkeyBehaviour {NONE, NUMBERS}
## Will add some hotkeys to the choices if different then HotkeyBehaviour.NONE.
var hotkey_behaviour := HotkeyBehaviour.NONE
### INTERNALS
## Used to block choices from being clicked for a couple of seconds (if delay is set in settings).
var _choice_blocker := Timer.new()
#region STATE
####################################################################################################
func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
hide_all_choices()
func _ready() -> void:
_choice_blocker.one_shot = true
DialogicUtil.update_timer_process_callback(_choice_blocker)
add_child(_choice_blocker)
reveal_delay = float(ProjectSettings.get_setting('dialogic/choices/reveal_delay', reveal_delay))
reveal_by_input = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', reveal_by_input)
block_delay = ProjectSettings.get_setting('dialogic/choices/delay', block_delay)
autofocus_first_choice = ProjectSettings.get_setting('dialogic/choices/autofocus_first', autofocus_first_choice)
hotkey_behaviour = ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', hotkey_behaviour)
default_false_behaviour = ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', default_false_behaviour)
func post_install() -> void:
dialogic.Inputs.dialogic_action.connect(_on_dialogic_action)
#endregion
#region MAIN METHODS
####################################################################################################
## Hides all choice buttons.
func hide_all_choices() -> void:
for node in get_tree().get_nodes_in_group('dialogic_choice_button'):
node.hide()
if node.is_connected('button_up', _on_choice_selected):
node.disconnect('button_up', _on_choice_selected)
## Collects information on all the choices of the current question.
## The result is a dictionary like this:
## {'choices':
## [
## {'event_index':10, 'button_index':1, 'disabled':false, 'text':"My Choice", 'visible':true},
## {'event_index':15, 'button_index':2, 'disabled':false, 'text':"My Choice2", 'visible':true},
## ]
func get_current_question_info() -> Dictionary:
var question_info := {'choices':[]}
var button_idx := 1
last_question_info = {'choices':[]}
for choice_index in get_current_choice_indexes():
var event: DialogicEvent = dialogic.current_timeline_events[choice_index]
if not event is DialogicChoiceEvent:
continue
var choice_event: DialogicChoiceEvent = event
var choice_info := {}
choice_info['event_index'] = choice_index
choice_info['button_index'] = button_idx
# Check Condition
var condition: String = choice_event.condition
if condition.is_empty() or dialogic.Expressions.execute_condition(choice_event.condition):
choice_info['disabled'] = false
choice_info['text'] = choice_event.get_property_translated('text')
choice_info['visible'] = true
button_idx += 1
else:
choice_info['disabled'] = true
if not choice_event.disabled_text.is_empty():
choice_info['text'] = choice_event.get_property_translated('disabled_text')
else:
choice_info['text'] = choice_event.get_property_translated('text')
var hide := choice_event.else_action == DialogicChoiceEvent.ElseActions.HIDE
hide = hide or choice_event.else_action == DialogicChoiceEvent.ElseActions.DEFAULT and default_false_behaviour == DialogicChoiceEvent.ElseActions.HIDE
choice_info['visible'] = not hide
if not hide:
button_idx += 1
choice_info.text = dialogic.Text.parse_text(choice_info.text, true, true, false, true, false, false)
choice_info.merge(choice_event.extra_data)
if dialogic.has_subsystem('History'):
choice_info['visited_before'] = dialogic.History.has_event_been_visited(choice_index)
question_info['choices'].append(choice_info)
return question_info
## Lists all current choices and shows buttons.
func show_current_question(instant:=true) -> void:
hide_all_choices()
_choice_blocker.stop()
if !instant and (reveal_delay != 0 or reveal_by_input):
if reveal_delay != 0:
_choice_blocker.start(reveal_delay)
_choice_blocker.timeout.connect(show_current_question)
if reveal_by_input:
dialogic.Inputs.dialogic_action.connect(show_current_question)
return
if _choice_blocker.timeout.is_connected(show_current_question):
_choice_blocker.timeout.disconnect(show_current_question)
if dialogic.Inputs.dialogic_action.is_connected(show_current_question):
dialogic.Inputs.dialogic_action.disconnect(show_current_question)
var missing_button := false
var question_info := get_current_question_info()
for choice in question_info.choices:
var node: DialogicNode_ChoiceButton = get_choice_button_node(choice.button_index)
if not node:
missing_button = true
continue
node._load_info(choice)
if choice.button_index == 1 and autofocus_first_choice:
node.grab_focus()
match hotkey_behaviour:
## Add 1 to 9 as shortcuts if it's enabled
HotkeyBehaviour.NUMBERS:
if choice.button_index > 0 or choice.button_index < 10:
var shortcut: Shortcut
if node.shortcut != null:
shortcut = node.shortcut
else:
shortcut = Shortcut.new()
var input_key := InputEventKey.new()
input_key.keycode = OS.find_keycode_from_string(str(choice.button_index))
shortcut.events.append(input_key)
node.shortcut = shortcut
if node.pressed.is_connected(_on_choice_selected):
node.pressed.disconnect(_on_choice_selected)
node.pressed.connect(_on_choice_selected.bind(choice))
_choice_blocker.start(block_delay)
question_shown.emit(question_info)
if missing_button:
printerr("[Dialogic] The layout you are using doesn't have enough Choice Buttons for the choices you are trying to display.")
func get_choice_button_node(button_index:int) -> DialogicNode_ChoiceButton:
var idx := 1
for node: DialogicNode_ChoiceButton in get_tree().get_nodes_in_group('dialogic_choice_button'):
if !node.get_parent().is_visible_in_tree():
continue
if node.choice_index == button_index or (node.choice_index == -1 and idx == button_index):
return node
if node.choice_index > 0:
idx = node.choice_index
idx += 1
return null
func _on_choice_selected(choice_info := {}) -> void:
if dialogic.paused or not _choice_blocker.is_stopped():
return
if dialogic.has_subsystem('History'):
var all_choices: Array = dialogic.Choices.last_question_info['choices']
if dialogic.has_subsystem('VAR'):
dialogic.History.store_simple_history_entry(dialogic.VAR.parse_variables(choice_info.text), "Choice", {'all_choices': all_choices})
else:
dialogic.History.store_simple_history_entry(choice_info.text, "Choice", {'all_choices': all_choices})
if dialogic.has_subsystem("History"):
dialogic.History.mark_event_as_visited(choice_info.event_index)
choice_selected.emit(choice_info)
hide_all_choices()
dialogic.current_state = dialogic.States.IDLE
dialogic.handle_event(choice_info.event_index + 1)
func get_current_choice_indexes() -> Array:
var choices := []
var evt_idx := dialogic.current_event_idx
var ignore := 0
while true:
if evt_idx >= len(dialogic.current_timeline_events):
break
if dialogic.current_timeline_events[evt_idx] is DialogicChoiceEvent:
if ignore == 0:
choices.append(evt_idx)
ignore += 1
elif dialogic.current_timeline_events[evt_idx].can_contain_events:
ignore += 1
else:
if ignore == 0:
break
if dialogic.current_timeline_events[evt_idx] is DialogicEndBranchEvent:
ignore -= 1
evt_idx += 1
return choices
func _on_dialogic_action() -> void:
if get_viewport().gui_get_focus_owner() is DialogicNode_ChoiceButton and use_input_action and not dialogic.Inputs.input_was_mouse_input:
get_viewport().gui_get_focus_owner().pressed.emit()
#endregion
#region HELPERS
####################################################################################################
func is_question(index:int) -> bool:
if dialogic.current_timeline_events[index] is DialogicTextEvent:
if len(dialogic.current_timeline_events)-1 != index:
if dialogic.current_timeline_events[index+1] is DialogicChoiceEvent:
return true
if dialogic.current_timeline_events[index] is DialogicChoiceEvent:
if index != 0 and dialogic.current_timeline_events[index-1] is DialogicEndBranchEvent:
if dialogic.current_timeline_events[dialogic.current_timeline_events[index-1].find_opening_index(index-1)] is DialogicChoiceEvent:
return false
else:
return true
else:
return true
return false
#endregion
@@ -0,0 +1,28 @@
@tool
extends HBoxContainer
var parent_resource: DialogicChoiceEvent = null
func refresh() -> void:
$AddChoice.icon = get_theme_icon("Add", "EditorIcons")
if parent_resource is DialogicChoiceEvent:
show()
if len(parent_resource.text) > 12:
$Label.text = "End of choice ("+parent_resource.text.substr(0,12)+"...)"
else:
$Label.text = "End of choice ("+parent_resource.text+")"
else:
hide()
func _on_add_choice_pressed() -> void:
var timeline := find_parent('VisualEditor')
if timeline:
var resource := DialogicChoiceEvent.new()
resource.created_by_button = true
timeline.add_event_undoable(resource, get_parent().get_index()+1)
timeline.indent_events()
timeline.something_changed()
# Prevent focusing on future redos
resource.created_by_button = false

Some files were not shown because too many files have changed in this diff Show More