Added Dialogic and a test dialog
@@ -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
|
||||
|
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
|
||||
|
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
|
||||
@@ -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
|
||||
|
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
|
||||
@@ -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
|
||||
|
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
|
||||
@@ -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"
|
||||
|
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
|
||||
|
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
|
||||
|
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"]
|
||||
|
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
|
||||
@@ -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
|
||||
@@ -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
|
||||