Add addons

This commit is contained in:
Joey Yakimowich-Payne 2023-01-03 16:53:53 -07:00
commit 3367bad24f
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
527 changed files with 36197 additions and 0 deletions

View file

@ -0,0 +1,86 @@
@tool
class_name DialogicMusicEvent
extends DialogicEvent
## Event that can change the currently playing background music.
### Settings
## The file to play. If empty, the previous music will be faded out.
var file_path: String = ""
## 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: String = "Master"
## If true, the audio will loop, otherwise only play once.
var loop: bool = true
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
dialogic.Audio.update_music(file_path, volume, audio_bus, fade_length, loop)
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Music"
set_default_color('Color5')
event_category = Category.AudioVisual
event_sorting_index = 2
expand_by_default = false
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": ""},
"fade" : {"property": "fade_length", "default": 0},
"volume" : {"property": "volume", "default": 0},
"bus" : {"property": "audio_bus", "default": "Master",
"suggestions": get_bus_suggestions},
"loop" : {"property": "loop", "default": true,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('file_path', ValueType.File, 'Play', '',
{'file_filter' : "*.mp3, *.ogg, *.wav",
'placeholder' : "No music",
'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
add_body_edit('fade_length', ValueType.Float, 'Fade Time:')
add_body_edit('volume', ValueType.Decibel, 'volume:', '', {}, '!file_path.is_empty()')
add_body_edit('audio_bus', ValueType.SinglelineText, 'audio_bus:', '', {}, '!file_path.is_empty()')
add_body_edit('loop', ValueType.Bool, 'loop:', '', {}, '!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

View file

@ -0,0 +1,81 @@
@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: String = ""
## The volume to play the sound at.
var volume: float = 0
## The bus to play the sound on.
var audio_bus: String = "Master"
## If true, the sound will loop infinitely. Not recommended (as there is no way to stop it).
var loop: bool = 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('Color5')
event_category = Category.AudioVisual
event_sorting_index = 3
expand_by_default = false
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": "Master",
"suggestions": get_bus_suggestions},
"loop" : {"property": "loop", "default": false,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('file_path', ValueType.File, '', '',
{'file_filter' : '*.mp3, *.ogg, *.wav',
'placeholder' : "Select file",
'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
add_body_edit('volume', ValueType.Decibel, 'volume:', '', {}, '!file_path.is_empty()')
add_body_edit('audio_bus', ValueType.SinglelineText, 'audio_bus:', '', {}, '!file_path.is_empty()')
func get_bus_suggestions() -> Dictionary:
var bus_name_list := {}
for i in range(AudioServer.bus_count):
bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)}
return bus_name_list

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://buvpjsvdt4evk"
path="res://.godot/imported/icon_music.png-9629b0cbb1d40f2776216a2874ccbd27.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Audio/icon_music.png"
dest_files=["res://.godot/imported/icon_music.png-9629b0cbb1d40f2776216a2874ccbd27.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3ookrkto0yh6"
path="res://.godot/imported/icon_sound.png-00b9f09aba60dcc77e5ef32a9d1ac490.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Audio/icon_sound.png"
dest_files=["res://.godot/imported/icon_sound.png-00b9f09aba60dcc77e5ef32a9d1ac490.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

View file

@ -0,0 +1,10 @@
@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')}]

View file

@ -0,0 +1,7 @@
class_name DialogicNode_MusicPlayer
extends AudioStreamPlayer
## An audio stream player that is used by dialogic to play music.
func _ready():
add_to_group('dialogic_music_player')

View file

@ -0,0 +1,7 @@
class_name DialogicNode_SoundPlayer
extends AudioStreamPlayer
## An audio stream player that is used by dialogic to play sounds.
func _ready():
add_to_group('dialogic_sound_player')

View file

@ -0,0 +1,95 @@
extends DialogicSubsystem
## Subsystem that manages music and sounds.
####################################################################################################
## STATE
####################################################################################################
func clear_game_state():
update_music()
stop_all_sounds()
func load_game_state():
var info = dialogic.current_state_info.get('music')
if info == null or info.path.is_empty():
update_music()
else:
update_music(info.path, info.volume, info.audio_bus, 0, info.loop)
func pause() -> void:
for node in get_tree().get_nodes_in_group('dialogic_music_player'):
node.stream_paused = true
for child in get_children():
child.stream_paused = true
func resume() -> void:
for node in get_tree().get_nodes_in_group('dialogic_music_player'):
node.stream_paused = false
for child in get_children():
child.stream_paused = false
####################################################################################################
## MAIN METHODS
####################################################################################################
## Updates the background music. Will fade out previous music.
func update_music(path:String = '', volume:float = 0.0, audio_bus:String = "Master", fade_time:float = 0.0, loop:bool = true) -> void:
dialogic.current_state_info['music'] = {'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop}
for node in get_tree().get_nodes_in_group('dialogic_music_player'):
var fader: Tween = null
if node.playing or path:
fader = create_tween()
var prev_node = null
if node.playing:
prev_node = node.duplicate()
add_child(prev_node)
prev_node.play(node.get_playback_position())
prev_node.remove_from_group('dialogic_music_player')
fader.tween_method(interpolate_volume_linearly.bind(prev_node), db_to_linear(prev_node.volume_db),0.0,fade_time)
if path:
node.stream = load(path)
node.volume_db = volume
node.bus = audio_bus
if "loop" in node.stream:
node.stream.loop = loop
elif "loop_mode" in node.stream:
if loop:
node.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
else:
node.stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
node.play()
fader.parallel().tween_method(interpolate_volume_linearly.bind(node), 0.0,db_to_linear(volume),fade_time)
else:
node.stop()
if prev_node:
fader.tween_callback(prev_node.queue_free)
## Plays a given sound file.
func play_sound(path:String, volume:float = 0.0, audio_bus:String = "Master", loop :bool= false) -> void:
var sound_node: DialogicNode_SoundPlayer = get_tree().get_nodes_in_group('dialogic_sound_player').front()
if sound_node != null and !path.is_empty():
var new_sound_node: DialogicNode_SoundPlayer = sound_node.duplicate()
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
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)
func stop_all_sounds() -> void:
var sound_nodes: Array = get_tree().get_nodes_in_group('dialogic_sound_player')
if !sound_nodes.is_empty():
for sound_player in sound_nodes.front().get_children():
sound_player.queue_free()
func interpolate_volume_linearly(value :float, node:Node) -> void:
node.volume_db = linear_to_db(value)

View file

@ -0,0 +1,30 @@
extends TextureRect
## This is the DefaultBackground scene that can simply display a image
##
## You can create your own scenes. They can override the following methods:
## - _update_background(@argument:String, @time:float)
## - _fade_in(@time) (if not overriden modulate is animated)
## - _fade_out(@time) (if not overriden modulate is animated)
## make sure to free the scene at the end of the fade
## - _should_do_background_update(@arg) -> bool
func _ready() -> void:
ignore_texture_size = true
stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
anchor_right = 1
anchor_bottom = 1
# load the new background in here.
# The time argument is given for when [_should_do_background_update] returns true
# (then you have to do the transition in here)
func _update_background(argument:String, time:float) -> void:
texture = load(argument)
# 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

View file

@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://bgoisbbnvcfbf"]
[ext_resource type="Script" path="res://addons/dialogic/Events/Background/default_background.gd" id="1_nkdrp"]
[node name="DefaultBackground" type="TextureRect"]
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_nkdrp")

View file

@ -0,0 +1,74 @@
@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: String = ""
## The argument that is passed to the background scene.
## For the default scene it's the path to the image to show.
var argument: String = ""
## The time the fade animation will take. Leave at 0 for instant change.
var fade: float = 0.0
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
dialogic.Backgrounds.update_background(scene, argument, fade)
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Background"
set_default_color('Color4')
event_category = Category.AudioVisual
event_sorting_index = 0
expand_by_default = false
################################################################################
## SAVING/LOADING
################################################################################
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},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('argument', ValueType.File, '', '',
{'file_filter':'*.tscn, *.scn, *.jpg, *.jpeg, *.png, *.webp, *.tga, *svg, *.bmp, *.dds, *.exr, *.hdr',
'placeholder': "No background",
'editor_icon':["Image", "EditorIcons"]},
'scene == ""')
add_header_edit('argument', ValueType.SinglelineText, 'Argument:', '', {}, 'scene != ""')
add_body_edit("fade", ValueType.Float, "fade time: ")
add_body_edit("scene", ValueType.File, 'scene:', '',
{'file_filter':'*.tscn, *.scn',
'placeholder': "Default scene",
'editor_icon':["PackedScene", "EditorIcons"]})

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://517mp8gfj811"
path="res://.godot/imported/icon.png-5701ef95f478df093cee0e38709c3b42.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Background/icon.png"
dest_files=["res://.godot/imported/icon.png-5701ef95f478df093cee0e38709c3b42.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

View file

@ -0,0 +1,10 @@
@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')}]

View file

@ -0,0 +1,6 @@
class_name DialogicNode_BackgroundHolder
extends CanvasLayer
func _ready():
add_to_group('dialogic_background_holders')

View file

@ -0,0 +1,77 @@
extends DialogicSubsystem
## Subsystem for managing backgrounds.
var default_background_scene = load(get_script().resource_path.get_base_dir().path_join('default_background.tscn'))
####################################################################################################
## STATE
####################################################################################################
func clear_game_state():
update_background()
func load_game_state():
update_background(dialogic.current_state_info.get('background_scene', ''), dialogic.current_state_info.get('background_argument', ''))
####################################################################################################
## 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:String = '', argument:String = '', fade_time:float = 0.0) -> void:
for node in get_tree().get_nodes_in_group('dialogic_background_holders'):
if node.visible:
var bg_set: bool = false
if scene == dialogic.current_state_info['background_scene']:
for old_bg in node.get_children():
if old_bg.has_method("_should_do_background_update") and old_bg._should_do_background_update(argument):
if old_bg.has_method('_update_background'):
old_bg._update_background(argument, fade_time)
bg_set = true
if !bg_set:
# remove previous backgrounds
for old_bg in node.get_children():
if old_bg.has_method('_fade_out'):
old_bg._fade_out(fade_time)
elif "modulate" in old_bg:
var tween := old_bg.create_tween()
tween.tween_property(old_bg, "modulate", Color.TRANSPARENT, fade_time)
tween.tween_callback(old_bg.queue_free)
else:
old_bg.queue_free()
var new_node:Node
if scene.ends_with('.tscn'):
new_node = load(scene).instantiate()
elif argument:
new_node = default_background_scene.instantiate()
else:
new_node = null
if new_node:
node.add_child(new_node)
if new_node.has_method('_update_background'):
new_node._update_background(argument, fade_time)
if new_node.has_method('_fade_in'):
new_node._fade_in(fade_time)
elif "modulate" in new_node:
new_node.modulate = Color.TRANSPARENT
var tween = new_node.create_tween()
tween.tween_property(new_node, "modulate", Color.WHITE, fade_time)
dialogic.current_state_info['background_scene'] = scene
dialogic.current_state_info['background_argument'] = argument

View file

@ -0,0 +1,116 @@
@tool
class_name DialogicCallNodeEvent
extends DialogicEvent
## Event that allows calling a method in a node or autoload.
### Settings
## The path to the node you want to call. You can access autoloads directly. No need to add /root.
var path: String = ""
## The name of the method to call on the given node.
var method: String = ""
## A list of arguments to give to the call.
var arguments: Array = []
## If wait is true, the dialog will only continue once the called method is finished.
var wait: bool = false
## If this is true, the method will not be called.
## Instead you can activate it during a following text event with the [signal] command.
## Use together with [signal_name] property.
var inline: bool = false
## Only necessary if [inline] is true. Sets the argument to listen for.
var inline_signal_argument: String
## Only usefull if [inline] is true. If true, the command can only be used once.
## If false it will not stop listening to the signal (with that argument).
var inline_single_use: bool = false
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
if inline:
dialogic.timeline_ended.connect(_disconnect_signal)
if path.begins_with('root'):
path = "/"+path
if not "/" in path and dialogic.get_node('/root').has_node(path):
path = "/root/"+path
var n = dialogic.get_node_or_null(path)
if n:
if n.has_method(method):
if inline:
dialogic.text_signal.connect(_call_on_signal, CONNECT_PERSIST)
elif wait:
await n.callv(method, arguments).completed
else:
n.callv(method, arguments)
finish()
func _call_on_signal(arg:String) -> void:
if arg != inline_signal_argument:
return
if inline_single_use:
dialogic.disconnect("text_signal", _call_on_signal)
var n = dialogic.get_node_or_null(path)
n.callv(method, arguments)
func _disconnect_signal():
if dialogic.text_signal.is_connected(_call_on_signal):
dialogic.text_signal.disconnect(_call_on_signal)
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Call Node"
set_default_color('Color1')
event_category = Category.Godot
event_sorting_index = 3
expand_by_default = false
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "call_node"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"path" : {"property": "path", "default": ""},
"method" : {"property": "method", "default": ""},
"args" : {"property": "arguments", "default": []},
"wait" : {"property": "wait", "default": false,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}},
"inline" : {"property": "inline", "default": false,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}},
"signal" : {"property": "inline_signal_argument", "default": ""},
"single_use": {"property": "inline_single_use", "default": false,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}}
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('method', ValueType.SinglelineText, 'Call method')
add_header_edit('path', ValueType.SinglelineText, 'in object')
add_body_edit('wait', ValueType.Bool, 'wait for method to finsih:')
add_body_edit('inline', ValueType.Bool, 'Use as inline Command:')
add_body_edit('inline_signal_argument', ValueType.SinglelineText, 'inline Signal Name', '', {}, 'inline == true')
add_body_edit('inline_single_use', ValueType.Bool, 'Single Use:', '', {}, 'inline == true')
add_body_line_break()
add_body_edit('arguments', ValueType.StringArray, 'arguments:')

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://duvcdvtgy4h4b"
path="res://.godot/imported/icon.png-0303bccfcf0a602bb81277f73307dc34.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Call Node/icon.png"
dest_files=["res://.godot/imported/icon.png-0303bccfcf0a602bb81277f73307dc34.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

View file

@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_call_node.gd')]

View file

@ -0,0 +1,15 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.scale = Vector2()
node.modulate.a = 0
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_LINEAR)
tween.set_parallel()
tween.tween_property(node, 'scale', Vector2(1,1), time).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'modulate:a', 1.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,15 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.scale = Vector2(1,1)
node.modulate.a = 1
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_LINEAR)
tween.set_parallel()
tween.tween_property(node, 'scale', Vector2(), time).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_IN)
tween.tween_property(node, 'modulate:a', 0.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,14 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.position.y = orig_pos.y + node.get_viewport().size.y/5
node.modulate.a = 0
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
tween.tween_property(node, 'position', orig_pos, time)
tween.tween_property(node, 'modulate:a', 1.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,12 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
tween.tween_property(node, 'position:y', orig_pos.y + node.get_viewport().size.y/5, time)
tween.tween_property(node, 'modulate:a', 0.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,11 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
#tween.set_parallel(true)
#node.scale = Vector2(1,1)*1.2
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)*1.1, time*0.5).set_trans(Tween.TRANS_BACK).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'))

View file

@ -0,0 +1,5 @@
extends DialogicAnimation
func animate():
await node.get_tree().process_frame
emit_signal('finished')

View file

@ -0,0 +1,17 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
tween.set_parallel(true)
tween.set_trans(Tween.TRANS_SINE)
tween.set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'scale', Vector2(1,1)*1.1, time*0.3)
tween.tween_property(node, 'rotation', -0.1, time*0.1).set_delay(time*0.2)
tween.tween_property(node, 'rotation', 0.1, time*0.1).set_delay(time*0.3)
tween.tween_property(node, 'rotation', -0.1, time*0.1).set_delay(time*0.4)
tween.tween_property(node, 'rotation', 0.1, time*0.1).set_delay(time*0.5)
tween.tween_property(node, 'rotation', -0.1, 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'))

View file

@ -0,0 +1,47 @@
class_name DialogicAnimation
extends Node
## Class that can be used to animate portraits. Can be extended to create animations.
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 position at which to end the animation.
var end_position : Vector2
## Set at runtime. The position the node started at.
var orig_pos : Vector2
## Used to repeate the animation for a number of times.
var repeats : int
func _ready():
connect('finished_once', finished_one_loop)
## To be overridden. Do the actual animating/tweening in here.
## Use the properties [node], [time], [end_position], [orig_pos].
func animate():
pass
func finished_one_loop():
repeats -= 1
if repeats > 0:
animate()
elif repeats == 0:
emit_signal("finished")
func pause():
if node:
node.process_mode = Node.PROCESS_MODE_DISABLED
func resume():
if node:
node.process_mode = Node.PROCESS_MODE_INHERIT

View file

@ -0,0 +1,48 @@
@tool
class_name DialogicDefaultPortrait # TODO: Question should this have a class_name? To be extended or what?
extends Node
## Default portrait scene.
## Stores the character that this scene displays.
var character: DialogicCharacter
## Stores the name of the current portrait.
var portrait: String
## Function to accept and use the extra data, if the custom portrait wants to accept it
func _set_extra_data(data: String) -> void:
pass
## This function can be overridden. Defaults to true, if not overridden!
func _should_do_portrait_update(character:DialogicCharacter, portrait:String) -> bool:
return true
## If the custom portrait accepts a change, then accept it here
func _update_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
if passed_character != null:
if character == null or character != passed_character:
character = passed_character
var path :String = character.portraits[portrait].get('image', '')
$Portrait.texture = null
if !path.is_empty(): $Portrait.texture = load(path)
$Portrait.centered = false
$Portrait.position = $Portrait.get_rect().size * Vector2(-0.5, -1)
## If implemented, this is called when the mirror changes
func _set_mirror(mirror:bool) -> void:
$Portrait.flip_h = mirror
## If implemented, this is used by the editor for the "full view" mode
func _get_covered_rect() -> Rect2:
if $Portrait.texture == null:
return Rect2()
return Rect2($Portrait.position, $Portrait.get_rect().size)

View file

@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://c4yp65n7ju1br"]
[ext_resource type="Script" path="res://addons/dialogic/Events/Character/default_portrait.gd" id="1_wn77n"]
[sub_resource type="Texture2D" id="Texture2D_sny5f"]
resource_local_to_scene = false
resource_name = ""
[node name="DefaultPortrait" type="Node2D"]
script = ExtResource("1_wn77n")
[node name="Portrait" type="Sprite2D" parent="."]
texture = SubResource("Texture2D_sny5f")
centered = false

View file

@ -0,0 +1,471 @@
@tool
class_name DialogicCharacterEvent
extends DialogicEvent
## Event that allows to manipulate character portraits.
enum PortraitModes {VisualNovel, RPG}
enum ActionTypes {Join, Leave, Update}
### Settings
## The type of action of this event (JOIN/LEAVE/UPDATE). See [ActionTypes].
var action_type : int = ActionTypes.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: String = ""
## The index of the position this character should move to
var position: int = 1
## Path to an 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: String = ""
## 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: bool = false
## For Update only. If bigger then 0, the portrait will tween to the
## new position (if changed) in this time (in seconds).
var position_move_time: float = 0.0
## The z_index that the portrait should have.
var z_index: int = 0
## If true, the portrait will be set to mirrored.
var mirrored: bool = false
## If set, will be passed to the portrait scene.
var extra_data: String = ""
### Helpers
## Indicates if the z_index should be updated.
var _update_zindex: bool = false
## Used to set the character resource from the unique name identifier and vice versa
var _character_from_directory: String:
get:
if _character_from_directory == '--All--':
return '--All--'
for item in _character_directory.keys():
if _character_directory[item]['resource'] == character:
return item
break
return ""
set(value):
_character_from_directory = value
if value in _character_directory.keys():
character = _character_directory[value]['resource']
else:
character = null
## Used by [_character_from_directory]
var _character_directory: Dictionary = {}
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
match action_type:
ActionTypes.Join:
if character:
if !dialogic.Portraits.is_character_joined(character):
var n = dialogic.Portraits.add_portrait(character, portrait, position, mirrored, z_index, extra_data)
if n:
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/join_default_wait', true)
if animation_name:
var anim:DialogicAnimation = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
dialogic.Portraits.change_portrait(character, portrait)
if animation_name.is_empty():
animation_length = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
dialogic.Portraits.move_portrait(character, position, z_index, false, animation_length)
ActionTypes.Leave:
if _character_from_directory == '--All--':
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
if animation_name:
for chara in dialogic.Portraits.get_joined_characters():
var anim = dialogic.Portraits.animate_portrait(chara, animation_name, animation_length, animation_repeats)
anim.finished.connect(dialogic.Portraits.remove_portrait.bind(chara))
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
for chara in dialogic.Portraits.get_joined_characters():
dialogic.Portraits.remove_portrait(chara)
elif character:
if dialogic.Portraits.is_character_joined(character):
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
if animation_name:
var anim = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
anim.finished.connect(dialogic.Portraits.remove_portrait.bind(character))
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
dialogic.Portraits.remove_portrait(character)
ActionTypes.Update:
if character:
if dialogic.Portraits.is_character_joined(character):
dialogic.Portraits.change_portrait(character, portrait, mirrored, z_index, _update_zindex, extra_data)
if position != 0:
dialogic.Portraits.move_portrait(character, position, z_index, _update_zindex, position_move_time)
if animation_name:
var anim = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Character"
set_default_color('Color2')
event_category = Category.Main
event_sorting_index = 2
continue_at_end = true
expand_by_default = false
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_character.png'))
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string := ""
match action_type:
ActionTypes.Join: result_string += "Join "
ActionTypes.Leave: result_string += "Leave "
ActionTypes.Update: result_string += "Update "
print('to_string')
print('character ', character)
print('characterfrom ', _character_from_directory)
if character or _character_from_directory == '--All--':
if action_type == ActionTypes.Leave and _character_from_directory == '--All--':
result_string += "--All--"
else:
var name := ""
for path in _character_directory.keys():
if _character_directory[path]['resource'] == character:
name = path
break
if name.count(" ") > 0:
name = '"' + name + '"'
result_string += name
if !portrait.is_empty() and action_type != ActionTypes.Leave:
result_string+= " ("+portrait+")"
if position and action_type != ActionTypes.Leave:
result_string += " "+str(position)
if animation_name != "" or z_index != 0 or mirrored != false or position_move_time != 0.0 or extra_data != "":
result_string += " ["
if animation_name:
result_string += 'animation="'+DialogicUtil.pretty_name(animation_name)+'"'
if animation_length != 0.5:
result_string += ' length="'+str(animation_length)+'"'
if animation_wait:
result_string += ' wait="'+str(animation_wait)+'"'
if animation_repeats != 1:
result_string += ' repeat="'+str(animation_repeats)+'"'
if z_index != 0:
result_string += ' z-index="' + str(z_index) + '"'
if mirrored:
result_string += ' mirrored="' + str(mirrored) + '"'
if position_move_time != 0:
result_string += ' move_time="' + str(position_move_time) + '"'
if extra_data != "":
result_string += ' extra_data="' + extra_data + '"'
result_string += "]"
return result_string
func from_text(string:String) -> void:
if Engine.is_editor_hint() == false:
_character_directory = Dialogic.character_directory
else:
_character_directory = self.get_meta("editor_character_directory")
var regex := RegEx.new()
# Reference regex without Godot escapes: (?<type>Join|Update|Leave)\s*(")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?<portrait>.*)\))?(\s*(?<position>\d))?(\s*\[(?<shortcode>.*)\])?
regex.compile("(?<type>Join|Update|Leave)\\s*(\")?(?<name>(?(2)[^\"\\n]*|[^(: \\n]*))(?(2)\"|)(\\W*\\((?<portrait>.*)\\))?(\\s*(?<position>\\d))?(\\s*\\[(?<shortcode>.*)\\])?")
var result := regex.search(string)
match result.get_string('type'):
"Join":
action_type = ActionTypes.Join
"Leave":
action_type = ActionTypes.Leave
"Update":
action_type = ActionTypes.Update
if result.get_string('name').strip_edges():
if action_type == ActionTypes.Leave and result.get_string('name').strip_edges() == "--All--":
_character_from_directory = '--All--'
else:
var name := result.get_string('name').strip_edges()
if _character_directory != null:
if _character_directory.size() > 0:
character = null
if _character_directory.has(name):
character = _character_directory[name]['resource']
else:
name = name.replace('"', "")
# First do a full search to see if more of the path is there then necessary:
for character in _character_directory:
if name in _character_directory[character]['full_path']:
character = _character_directory[character]['resource']
break
# If it doesn't exist, we'll consider it a guest and create a temporary character
if character == null:
if Engine.is_editor_hint() == false:
character = DialogicCharacter.new()
character.display_name = name
var entry:Dictionary = {}
entry['resource'] = character
entry['full_path'] = "runtime://" + name
Dialogic.character_directory[name] = entry
if result.get_string('portrait').strip_edges():
portrait = result.get_string('portrait').strip_edges()
if result.get_string('position'):
position = result.get_string('position').to_int()
elif action_type == ActionTypes.Update:
# Override the normal default if it's an Update
position = 0
if result.get_string('shortcode'):
var shortcode_params = parse_shortcode_parameters(result.get_string('shortcode'))
animation_name = shortcode_params.get('animation', '')
if animation_name != "":
if !animation_name.ends_with('.gd'):
animation_name = guess_animation_file(animation_name)
if !animation_name.ends_with('.gd'):
printerr("[Dialogic] Couldn't identify animation '"+animation_name+"'.")
animation_name = ""
var animLength = shortcode_params.get('length', '0.5').to_float()
if typeof(animLength) == TYPE_FLOAT:
animation_length = animLength
else:
animation_length = animLength.to_float()
animation_wait = DialogicUtil.str_to_bool(shortcode_params.get('wait', 'false'))
#repeat is supported on Update, the other two should not be checking this
if action_type == ActionTypes.Update:
animation_repeats = shortcode_params.get('repeat', 1).to_int()
position_move_time = shortcode_params.get('move_time', 0.0)
#move time is only supported on Update, but it isnt part of the animations so its separate
if action_type == ActionTypes.Update:
if typeof(shortcode_params.get('move_time', 0)) == TYPE_STRING:
position_move_time = shortcode_params.get('move_time', 0.0).to_float()
if typeof(shortcode_params.get('z-index', 0)) == TYPE_STRING:
z_index = shortcode_params.get('z-index', 0).to_int()
_update_zindex = true
mirrored = DialogicUtil.str_to_bool(shortcode_params.get('mirrored', 'false'))
extra_data = shortcode_params.get('extra_data', "")
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
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('action_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'Join',
'value': ActionTypes.Join,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/join.svg")
},
{
'label': 'Leave',
'value': ActionTypes.Leave,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/leave.svg")
},
{
'label': 'Update',
'value': ActionTypes.Update,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/update.svg")
}
]
})
add_header_edit('_character_from_directory', ValueType.ComplexPicker, '', '',
{'empty_text' : 'Character',
'file_extension' : '.dch',
'suggestions_func' : get_character_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")})
add_header_button('', _on_character_edit_pressed, 'Edit character', ["Edit", "EditorIcons"], 'character != null and _character_from_directory != "--All--"')
add_header_edit('portrait', ValueType.ComplexPicker, '', '',
{'empty_text' : 'Default',
'suggestions_func' : get_portrait_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")},
'character != null and !has_no_portraits() and action_type != %s' %ActionTypes.Leave)
add_header_edit('position', ValueType.Integer, ' at position', '', {},
'character != null and !has_no_portraits() and action_type != %s' %ActionTypes.Leave)
# Body
add_body_edit('animation_name', ValueType.ComplexPicker, '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.Float, 'Length:', '', {},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_wait', ValueType.Bool, 'Wait for animation to finish:', '', {},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_repeats', ValueType.Integer, 'Repeat:', '', {},
'should_show_animation_options() and !animation_name.is_empty() and action_type == %s)' %ActionTypes.Update)
add_body_edit('z_index', ValueType.Integer, 'portrait z-index:', "",{},
'action_type != %s' %ActionTypes.Leave)
add_body_edit('mirrored', ValueType.Bool, 'mirrored:', "",{},
'action_type != %s' %ActionTypes.Leave)
add_body_edit('position_move_time', ValueType.Float, 'Transiton time to change position:', '', {},
'action_type == %s' %ActionTypes.Update)
func should_show_animation_options() -> bool:
return (character != null and !character.portraits.is_empty()) or _character_from_directory == '--All--'
func has_no_portraits() -> bool:
return character and character.portraits.is_empty()
func get_character_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
#override the previous _character_directory with the meta, specifically for searching otherwise new nodes wont work
_character_directory = Engine.get_meta('dialogic_character_directory')
var icon = load("res://addons/dialogic/Editor/Images/Resources/character.svg")
suggestions['(No one)'] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if action_type == ActionTypes.Leave:
suggestions['ALL'] = {'value':'--All--', 'tooltip':'All currently joined characters leave', 'editor_icon':["GuiEllipsis", "EditorIcons"]}
for resource in _character_directory.keys():
suggestions[resource] = {'value': resource, 'tooltip': _character_directory[resource]['full_path'], 'icon': icon.duplicate()}
return suggestions
func get_portrait_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
var icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
if action_type == ActionTypes.Update:
suggestions["Don't Change"] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if action_type == ActionTypes.Join:
suggestions["Default portrait"] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if character != null:
for portrait in character.portraits:
suggestions[portrait] = {'value':portrait, 'icon':icon.duplicate()}
return suggestions
func get_animation_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
match action_type:
ActionTypes.Join, ActionTypes.Leave:
suggestions['Default'] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
ActionTypes.Update:
suggestions['None'] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
for anim in list_animations():
match action_type:
ActionTypes.Join:
if '_in' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
ActionTypes.Leave:
if '_out' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
ActionTypes.Update:
if not ('_in' in anim.get_file() or '_out' in anim.get_file()):
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
continue
return suggestions
func list_animations() -> Array:
var list := DialogicUtil.listdir(get_script().resource_path.get_base_dir().path_join('DefaultAnimations'), true, false, true)
list.append_array(DialogicUtil.listdir(DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations'), true, false, true))
return list
func guess_animation_file(animation_name: String) -> String:
for file in list_animations():
if DialogicUtil.pretty_name(animation_name) == DialogicUtil.pretty_name(file):
return file
return animation_name
func _on_character_edit_pressed() -> void:
var editor_manager := _editor_node.find_parent('EditorsManager')
if editor_manager:
editor_manager.edit_resource(character)

View file

@ -0,0 +1,111 @@
@tool
class_name DialogicPositionEvent
extends DialogicEvent
## Event that allows moving of positions (and characters that are on that position).
## Requires the Portraits subsystem to be present!
enum ActionTypes {SetRelative, SetAbsolute, Reset, ResetAll}
### Settings
## The type of action: SetRelative, SetAbsolute, Reset, ResetAll
var action_type := ActionTypes.SetRelative
## The position that should be affected
var position: int = 0
## A vector representing a relative change or an absolute position (for SetRelative and SetAbsolute)
var vector: Vector2 = Vector2()
## The time the tweening will take.
var movement_time: float = 0
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
# If for some someone sets it to 0, ignore it entirely. 0 position is used for Update to indicate no change.
if action_type != ActionTypes.ResetAll and position == 0:
finish()
return
match action_type:
ActionTypes.SetRelative:
dialogic.Portraits.move_portrait_position(position, vector, true, movement_time)
ActionTypes.SetAbsolute:
dialogic.Portraits.move_portrait_position(position, vector, false, movement_time)
ActionTypes.ResetAll:
dialogic.Portraits.reset_portrait_positions(movement_time)
ActionTypes.Reset:
dialogic.Portraits.reset_portrait_position(position, movement_time)
finish()
##############################################file_path##################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "position"
set_default_color('Color2')
event_category = Category.Main
event_sorting_index = 2
continue_at_end = true
expand_by_default = false
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_position.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "update_position"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"action" : {"sproperty": "action_type", "default": ActionTypes.SetRelative,
"suggestions": func(): return {"Set Relative":{'value':'0'}, "Set Absolute":{'value':'1'}, "Reset":{'value':'2'}, "Reset All":{'value':'3'}}},
"position" : {"property": "position", "default": 0},
"vector" : {"property": "vector", "default": Vector2()},
"time" : {"property": "movement_time", "default": 0},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('action_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'Change',
'value': ActionTypes.SetRelative,
},
{
'label': 'Set',
'value': ActionTypes.SetAbsolute,
},
{
'label': 'Reset',
'value': ActionTypes.Reset,
},
{
'label': 'Reset All',
'value': ActionTypes.ResetAll,
}
]
})
add_header_edit("position", ValueType.Integer, "position", '', {},
'action_type != ActionTypes.ResetAll')
add_header_label('to (absolute)', 'action_type == ActionTypes.SetAbsolute')
add_header_label('by (relative)', 'action_type == ActionTypes.SetRelative')
add_header_edit("vector", ValueType.Vector2, "", '', {},
'action_type != ActionTypes.Reset and action_type != ActionTypes.ResetAll')
add_body_edit("movement_time", ValueType.Float, "AnimationTime:", "(0 for instant)")

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b1i3ttk36kghm"
path="res://.godot/imported/icon_character.png-93ea754ecc6f43d01c1116ffee9f80e3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon_character.png"
dest_files=["res://.godot/imported/icon_character.png-93ea754ecc6f43d01c1116ffee9f80e3.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu6cj55m6dr78"
path="res://.godot/imported/icon_position.png-18fc8e57ed28c84781c862b6ca4492f7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon_position.png"
dest_files=["res://.godot/imported/icon_position.png-18fc8e57ed28c84781c862b6ca4492f7.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

View file

@ -0,0 +1,14 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_character.gd'), this_folder.path_join('event_position.gd')]
func _get_subsystems() -> Array:
return [{'name':'Portraits', 'script':this_folder.path_join('subsystem_portraits.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_portraits.tscn')]

View file

@ -0,0 +1,6 @@
class_name DialogicNode_PortraitHolder
extends CanvasLayer
func _ready():
add_to_group('dialogic_portrait_holder')

View file

@ -0,0 +1,9 @@
class_name DialogicNode_PortraitPosition
extends Marker2D
## Used to identify positions in the [DialogicCharacterEvent].
@export var position_index = 0
func _ready() -> void:
add_to_group('dialogic_portrait_position')

View file

@ -0,0 +1,88 @@
@tool
extends HBoxContainer
var current_animation_path = ""
func _ready():
%JoinDefault.get_suggestions_func = get_join_animation_suggestions
%JoinDefault.enable_pretty_name = true
%LeaveDefault.get_suggestions_func = get_leave_animation_suggestions
%LeaveDefault.enable_pretty_name = true
func refresh():
%CustomAnimationsFolderOpener.icon = get_theme_icon("Folder", "EditorIcons")
%CustomAnimationsFolder.text = DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations')
%PortraitMode.select(DialogicUtil.get_project_setting('dialogic/portrait_mode', 0))
%JoinDefault.resource_icon = get_theme_icon("Animation", "EditorIcons")
%LeaveDefault.resource_icon = get_theme_icon("Animation", "EditorIcons")
%JoinDefault.set_value(DialogicUtil.pretty_name(DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))))
%LeaveDefault.set_value(DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd')))
%JoinDefaultLength.set_value(DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5))
%LeaveDefaultLength.set_value(DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5))
%LeaveDefaultWait.button_pressed = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
%JoinDefaultWait.button_pressed = DialogicUtil.get_project_setting('dialogic/animations/join_default_wait', true)
func _on_LeaveDefault_value_changed(property_name, value):
ProjectSettings.set_setting('dialogic/animations/leave_default', value)
ProjectSettings.save()
func _on_JoinDefault_value_changed(property_name, value):
ProjectSettings.set_setting('dialogic/animations/join_default', value)
ProjectSettings.save()
func _on_JoinDefaultLength_value_changed(value):
ProjectSettings.set_setting('dialogic/animations/join_default_length', value)
ProjectSettings.save()
func _on_LeaveDefaultLength_value_changed(value):
ProjectSettings.set_setting('dialogic/animations/leave_default_length', value)
ProjectSettings.save()
func _on_JoinDefaultWait_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/animations/join_default_wait', button_pressed)
ProjectSettings.save()
func _on_LeaveDefaultWait_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/animations/leave_default_wait', button_pressed)
ProjectSettings.save()
func get_join_animation_suggestions(search_text:String) -> Dictionary:
var suggestions = {}
for anim in list_animations():
if '_in' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')}
return suggestions
func get_leave_animation_suggestions(search_text:String) -> Dictionary:
var suggestions = {}
for anim in list_animations():
if '_out' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')}
return suggestions
func list_animations() -> Array:
var list = DialogicUtil.listdir(get_script().resource_path.get_base_dir().path_join('DefaultAnimations'), true, false, true)
list.append_array(DialogicUtil.listdir(DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations'), true, false, true))
return list
func _on_CustomAnimationsFolderOpener_pressed():
find_parent('EditorView').godot_file_dialog(custom_anims_folder_selected, '', EditorFileDialog.FILE_MODE_OPEN_DIR, 'Select custom animation folder')
func custom_anims_folder_selected(path):
%CustomAnimationsFolder.text = path
ProjectSettings.set_setting('dialogic/animations/custom_folder', path)
ProjectSettings.save()
func _on_PortraitMode_item_selected(index):
ProjectSettings.set_setting('dialogic/portrait_mode', index)
ProjectSettings.save()

View file

@ -0,0 +1,232 @@
[gd_scene load_steps=7 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Character/settings_portraits.gd" id="2"]
[ext_resource type="StyleBox" uid="uid://dmsjhgv22dns8" path="res://addons/dialogic/Editor/Common/TitleBgStylebox.tres" id="2_i6l15"]
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/ComplexPicker.tscn" id="3"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/HintLabelStylingScript.gd" id="3_jknrx"]
[sub_resource type="Image" id="Image_t2ykh"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
"format": "LumAlpha8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="2"]
image = SubResource("Image_t2ykh")
[node name="Portraits" type="HBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("2")
[node name="General" type="VBoxContainer" parent="."]
layout_mode = 2
offset_right = 650.0
offset_bottom = 648.0
size_flags_horizontal = 3
[node name="Title" type="Label" parent="General"]
layout_mode = 2
offset_right = 650.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Portrait Settings"
[node name="HBoxContainer" type="HBoxContainer" parent="General"]
layout_mode = 2
offset_top = 40.0
offset_right = 650.0
offset_bottom = 71.0
[node name="Label" type="Label" parent="General/HBoxContainer"]
layout_mode = 2
offset_top = 2.0
offset_right = 460.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Portraits mode"
[node name="PortraitMode" type="OptionButton" parent="General/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 464.0
offset_right = 650.0
offset_bottom = 31.0
item_count = 2
selected = 0
popup/item_0/text = "Visual Novel Mode"
popup/item_0/id = 0
popup/item_1/text = "RPG Mode"
popup/item_1/id = 1
[node name="Info" type="Label" parent="General"]
layout_mode = 2
offset_top = 75.0
offset_right = 650.0
offset_bottom = 205.0
text = "Visual Novel: Multiple characters can show up in different positions at the same time. They have to be joined and leaved manually.
RPG: Only the character that speaks is visible (always in the same location). Does not have to be joined/left manually. Uses only position_idx 1."
autowrap_mode = 3
script = ExtResource("3_jknrx")
[node name="VSeparator" type="VSeparator" parent="."]
layout_mode = 2
offset_left = 654.0
offset_right = 658.0
offset_bottom = 648.0
[node name="Animations" type="VBoxContainer" parent="."]
layout_mode = 2
offset_left = 662.0
offset_right = 1152.0
offset_bottom = 648.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Title" type="Label" parent="Animations"]
layout_mode = 2
offset_right = 490.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Animations
"
[node name="HBoxContainer" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 40.0
offset_right = 490.0
offset_bottom = 71.0
[node name="Label2" type="Label" parent="Animations/HBoxContainer"]
layout_mode = 2
offset_top = 2.0
offset_right = 229.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Custom anims folder"
[node name="CustomAnimationsFolder" type="LineEdit" parent="Animations/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 233.0
offset_right = 462.0
offset_bottom = 31.0
size_flags_horizontal = 3
caret_blink = true
[node name="CustomAnimationsFolderOpener" type="Button" parent="Animations/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 466.0
offset_right = 490.0
offset_bottom = 31.0
icon = SubResource("2")
[node name="HSeparator" type="HSeparator" parent="Animations"]
layout_mode = 2
offset_top = 75.0
offset_right = 490.0
offset_bottom = 79.0
[node name="Title2" type="Label" parent="Animations"]
layout_mode = 2
offset_top = 83.0
offset_right = 490.0
offset_bottom = 119.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Default Animations
"
[node name="DefaultIn" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 123.0
offset_right = 490.0
offset_bottom = 156.0
[node name="Label3" type="Label" parent="Animations/DefaultIn"]
layout_mode = 2
offset_top = 3.0
offset_right = 107.0
offset_bottom = 29.0
size_flags_horizontal = 3
text = "Default Join"
[node name="JoinDefault" parent="Animations/DefaultIn" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 111.0
offset_right = 311.0
offset_bottom = 33.0
[node name="JoinDefaultLength" type="SpinBox" parent="Animations/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 315.0
offset_right = 398.0
offset_bottom = 33.0
step = 0.1
[node name="JoinDefaultWait" type="CheckButton" parent="Animations/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 402.0
offset_right = 490.0
offset_bottom = 33.0
text = "Wait:"
[node name="DefaultOut" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 160.0
offset_right = 490.0
offset_bottom = 193.0
[node name="Label4" type="Label" parent="Animations/DefaultOut"]
layout_mode = 2
offset_top = 3.0
offset_right = 107.0
offset_bottom = 29.0
size_flags_horizontal = 3
text = "Default Leave"
[node name="LeaveDefault" parent="Animations/DefaultOut" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 111.0
offset_right = 311.0
offset_bottom = 33.0
[node name="LeaveDefaultLength" type="SpinBox" parent="Animations/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 315.0
offset_right = 398.0
offset_bottom = 33.0
step = 0.1
[node name="LeaveDefaultWait" type="CheckButton" parent="Animations/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 402.0
offset_right = 490.0
offset_bottom = 33.0
text = "Wait:"
[connection signal="item_selected" from="General/HBoxContainer/PortraitMode" to="." method="_on_PortraitMode_item_selected"]
[connection signal="pressed" from="Animations/HBoxContainer/CustomAnimationsFolderOpener" to="." method="_on_CustomAnimationsFolderOpener_pressed"]
[connection signal="value_changed" from="Animations/DefaultIn/JoinDefault" to="." method="_on_JoinDefault_value_changed"]
[connection signal="value_changed" from="Animations/DefaultIn/JoinDefaultLength" to="." method="_on_JoinDefaultLength_value_changed"]
[connection signal="toggled" from="Animations/DefaultIn/JoinDefaultWait" to="." method="_on_JoinDefaultWait_toggled"]
[connection signal="value_changed" from="Animations/DefaultOut/LeaveDefault" to="." method="_on_LeaveDefault_value_changed"]
[connection signal="value_changed" from="Animations/DefaultOut/LeaveDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
[connection signal="toggled" from="Animations/DefaultOut/LeaveDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"]

View file

@ -0,0 +1,306 @@
extends DialogicSubsystem
## Subsystem that manages portraits and portrait positions.
## The default portrait scene.
var default_portrait_scene = load(get_script().resource_path.get_base_dir().path_join('default_portrait.tscn'))
## Temporarily stores the default positions.
var _default_positions: Dictionary = {}
## Stores the current positions.
var current_positions: Dictionary = {}
## A reference to the current [DialogicNode_PortraitHolder].
var _portrait_holder_reference: Node = null
####################################################################################################
## STATE
####################################################################################################
func clear_game_state():
for character in dialogic.current_state_info.get('portraits', {}).keys():
remove_portrait(load(character))
dialogic.current_state_info['portraits'] = {}
func load_game_state():
for character_path in dialogic.current_state_info['portraits']:
add_portrait(
load(character_path),
dialogic.current_state_info['portraits'][character_path].portrait, dialogic.current_state_info['portraits'][character_path].position_index,
false)
func pause() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.has('animation_node'):
portrait.animation_node.pause()
func resume() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.has('animation_node'):
portrait.animation_node.resume()
####################################################################################################
## MAIN METHODS
####################################################################################################
## Joins a character and then calls change_portrait().
func add_portrait(character:DialogicCharacter, portrait:String, position_idx:int, mirrored: bool = false, z_index: int = 0, extra_data:String = "") -> Node:
var character_node = null
if portrait.is_empty():
portrait = character.default_portrait
if not character:
print_debug('[DialogicError] Cannot call add_portrait() with null character.')
return null
if not portrait in character.portraits:
print_debug("[DialogicError] Tried joining ",character.display_name, " wit not-existing portrait '", portrait, "'. Will use default portrait instead.")
portrait = character.default_portrait
if portrait.is_empty():
print_debug("[DialogicError] Character ",character.display_name, " has no default portrait to use.")
return null
check_positions_and_holder()
character_node = Node2D.new()
character_node.name = character.get_character_name()
character_node.position = current_positions[position_idx]
character_node.z_index = z_index
character_node.set_meta("position", position_idx)
_portrait_holder_reference.add_child(character_node)
if character_node:
dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_index':position_idx}
if portrait:
change_portrait(character, portrait, mirrored, z_index, false, extra_data)
return character_node
## Changes the portrait of a character. Only works with characters that joined previously.
func change_portrait(character:DialogicCharacter, portrait:String, mirrored:bool = false, z_index: int = 0, update_zindex:bool = false, extra_data:String = "") -> void:
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot change portrait of null/not joined character.')
return null
if portrait.is_empty():
portrait = character.default_portrait
if not portrait in character.portraits.keys():
print_debug('[Dialogic] Change to not-existing portrait will be ignored!')
return
var char_node :Node = dialogic.current_state_info.portraits[character.resource_path].node
if update_zindex:
char_node.z_index = z_index
# path to the scene to use
var scene_path :String = character.portraits[portrait].get('scene', '')
var portrait_node = null
# check if the scene is the same as the currently loaded scene
if (char_node.get_child_count() and
character.portraits[dialogic.current_state_info['portraits'][character.resource_path]['portrait']].get('scene', '') == scene_path and
# also check if the scene supports changing to the given portrait
(!char_node.get_child(0).has_method('_should_do_portrait_update') or char_node.get_child(0)._should_do_portrait_update(character, portrait))):
portrait_node = char_node.get_child(0)
else:
# remove previous portrait
if char_node.get_child_count():
char_node.get_child(0).queue_free()
if scene_path.is_empty():
portrait_node = default_portrait_scene.instantiate()
else:
var p = load(scene_path)
if p:
portrait_node = p.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.')
if portrait_node:
portrait_node.position = character.offset + character.portraits[portrait].get('offset', Vector2())
# ignore the character scale on custom portraits that have 'ignore_char_scale' set to true
if scene_path.is_empty() or !character.portraits[portrait].get('ignore_char_scale', false):
portrait_node.scale = Vector2(1,1)*character.scale * character.portraits[portrait].get('scale', 1)
else:
portrait_node.scale = Vector2(1,1)*character.portraits[portrait].get('scale', 1)
for property in character.portraits[portrait].get('export_overrides', {}).keys():
portrait_node.set(property, str_to_var(character.portraits[portrait]['export_overrides'][property]))
if portrait_node.has_method('_update_portrait'):
portrait_node._update_portrait(character, portrait)
if portrait_node.has_method('_set_mirror'):
portrait_node._set_mirror(mirrored)
if portrait_node.has_method('_set_extra_data'):
portrait_node._set_extra_data(extra_data)
if !portrait_node.is_inside_tree():
char_node.add_child(portrait_node)
dialogic.current_state_info['portraits'][character.resource_path]['portrait'] = portrait
## Animates the portrait of the given character with the given animation.
func animate_portrait(character:DialogicCharacter, animation_path:String, length:float, repeats = 1) -> DialogicAnimation:
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot animate portrait of null/not joined character.')
return null
var char_node = dialogic.current_state_info['portraits'][character.resource_path].node
if dialogic.current_state_info['portraits'][character.resource_path].get('animation_node', null):
if is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].animation_node):
dialogic.current_state_info['portraits'][character.resource_path].animation_node.queue_free()
var anim_script = load(animation_path)
var anim_node = Node.new()
anim_node.set_script(anim_script)
anim_node = (anim_node as DialogicAnimation)
anim_node.node = char_node
anim_node.orig_pos = char_node.position
anim_node.end_position = char_node.position
anim_node.time = length
anim_node.repeats = repeats
add_child(anim_node)
anim_node.animate()
dialogic.current_state_info['portraits'][character.resource_path]['animation_node'] = anim_node
return anim_node
## Moves the portrait of the given character to the given positions. Also allows updating the z_index.
## TODO: Question, why the z_index is set here and in change_portrait and doesn't have it's own method.
func move_portrait(character:DialogicCharacter, position_idx:int, z_index:int = 0, update_zindex:bool = false, time:float = 0.0):
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot move portrait of null/not joined character.')
return null
var char_node = dialogic.current_state_info.portraits[character.resource_path].node
if update_zindex:
char_node.z_index = z_index
char_node.set_meta('position', position_idx)
if time == 0.0:
char_node.position = current_positions[position_idx]
else:
var tween = char_node.create_tween()
tween.tween_property(char_node, "position", current_positions[position_idx], time)
dialogic.current_state_info.portraits[character.resource_path].position_index = position_idx
func remove_portrait(character:DialogicCharacter) -> void:
dialogic.current_state_info['portraits'][character.resource_path].node.queue_free()
dialogic.current_state_info['portraits'].erase(character.resource_path)
## Creates additional positions either from timeline or at runtime
## If it's an existing position, will move that position to the coordinates instead
## There's no need to actually remove them once added, but saves will need to track position updates as well, so the whole current_positions array will need to be saved
## This will always be an absolute value for new positions, existing positions will be updated as absolute values by this
func add_portrait_position(position_number: int, position:Vector2) -> void:
check_positions_and_holder()
if position_number in current_positions:
move_portrait_position(position_number, position)
else:
# Add to both current and default positions
_default_positions[position_number] = position
current_positions[position_number] = position
func reset_portrait_positions(time:float = 0.0) -> void:
for position in current_positions:
move_portrait_position(position, _default_positions[position], false, time)
func reset_portrait_position(position:int, time:float = 0.0) -> void:
move_portrait_position(position, _default_positions[position], false, time)
func move_portrait_position(position_number: int, vector:Vector2, relative:bool = false, time:float = 0.0) -> void:
check_positions_and_holder()
if !position_number in current_positions:
if !relative:
add_portrait_position(position_number, vector)
else:
print_debug('[DialogicError] Cannot move non-existent position. (Use SetAbsolute to create a new position)')
return null
if !relative:
current_positions[position_number] = vector
else:
current_positions[position_number] += vector
for child in _portrait_holder_reference.get_children():
if child.get_meta('position') == position_number:
if time != 0.0:
var tween = child.create_tween()
tween.tween_property(child, "position", current_positions[position_number], time)
else:
child.position = current_positions[position_number]
####################################################################################################
## HELPERS
####################################################################################################
func is_character_joined(character:DialogicCharacter) -> bool:
return character.resource_path in dialogic.current_state_info['portraits']
func get_joined_characters() -> Array:
var chars = []
for char_path in dialogic.current_state_info.get('portraits', {}).keys():
chars.append(load(char_path))
return chars
func update_rpg_portrait_mode(character:DialogicCharacter = null, portrait:String = "") -> void:
if DialogicUtil.get_project_setting('dialogic/portrait_mode', 0) == DialogicCharacterEvent.PortraitModes.RPG:
if !Dialogic.current_state_info.has('rpg_last_portrait'):
Dialogic.current_state_info['rpg_last_portraits'] = {}
if character != null:
if portrait == "":
if Dialogic.current_state_info['rpg_last_portraits'].has(character.resource_path):
portrait = Dialogic.current_state_info['rpg_last_portraits'][character.resource_path]
else:
portrait = character.default_portrait
Dialogic.current_state_info['rpg_last_portraits'][character.resource_path] = portrait
var char_joined = false
for joined_character in dialogic.current_state_info.portraits:
if not character or (joined_character != character.resource_path):
var AnimationName = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
var AnimationLength = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
var anim = animate_portrait(load(joined_character), AnimationName, AnimationLength)
anim.finished.connect(remove_portrait.bind(load(joined_character)))
else:
char_joined = true
if (not char_joined) and character and portrait in character.portraits:
var AnimationName = DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))
var AnimationLength = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
add_portrait(character, portrait, 1, false)
var anim = animate_portrait(character, AnimationName, AnimationLength)
# makes sure positions are listed and can be accessed
func check_positions_and_holder() -> void:
if _portrait_holder_reference == null and len(get_tree().get_nodes_in_group('dialogic_portrait_holder')) == 0:
assert(false, '[Dialogic] If you want to display portraits, you need a PortraitHolder scene!')
else:
if _portrait_holder_reference == null:
_portrait_holder_reference = get_tree().get_first_node_in_group('dialogic_portrait_holder')
if _default_positions.size() == 0:
for node in get_tree().get_nodes_in_group('dialogic_portrait_position'):
_default_positions[node['position_index']] = node['position']
if current_positions.size() == 0:
current_positions = _default_positions.duplicate()

View file

@ -0,0 +1,153 @@
@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, Disable, Default}
### Settings
## The text that is displayed on the choice button.
var text :String = ""
## If not empty this condition will determine if this choice is active.
var condition: String = ""
## 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: String = ""
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
# This event is basically a placeholder only used to indicate a position.
# It is never really reached. Instead the Subsystem_Choices queries the events
# to find the choices that belong to the question.
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Choice"
set_default_color('Color3')
event_category = Category.Logic
event_sorting_index = 0
can_contain_events = true
needs_parent_event = true
expand_by_default = false
# if needs_parent_event is true, this needs to return true if the event is that event
func is_expected_parent_event(event:DialogicEvent) -> bool:
return event is DialogicTextEvent
# 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()
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string := ""
result_string = "- "+text.strip_edges()
if condition:
result_string += " [if "+condition+"]"
var shortcode = '['
if else_action == ElseActions.Hide:
shortcode += 'else="hide"'
elif else_action == ElseActions.Disable:
shortcode += 'else="disable"'
if disabled_text:
shortcode += " alt_text="+'"'+disabled_text+'"'
if len(shortcode) > 1:
result_string += shortcode + "]"
return result_string
func from_text(string:String) -> void:
var regex = RegEx.new()
regex.compile('- (?<text>[^\\[]*)(\\[if (?<condition>[^\\]]+)])?\\s?(\\s*\\[(?<shortcode>.*)\\])?')
var result = regex.search(string.strip_edges())
if result == null:
return
text = result.get_string('text')
condition = result.get_string('condition')
if result.get_string('shortcode'):
var shortcode_params = parse_shortcode_parameters(result.get_string('shortcode'))
else_action = {
'default':ElseActions.Default,
'hide':ElseActions.Hide,
'disable':ElseActions.Disable}.get(shortcode_params.get('else', ''), ElseActions.Default)
disabled_text = shortcode_params.get('alt_text', '')
func is_valid_event(string:String) -> bool:
if string.strip_edges().begins_with("-"):
return true
return false
################################################################################
## 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 ''
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit("text", ValueType.SinglelineText)
add_body_edit("condition", ValueType.Condition, 'if ')
add_body_edit("else_action", ValueType.FixedOptionSelector, 'else ', '', {
'selector_options': [
{
'label': 'Default',
'value': ElseActions.Default,
},
{
'label': 'Hide',
'value': ElseActions.Hide,
},
{
'label': 'Disable',
'value': ElseActions.Disable,
}
]}, '!condition.is_empty()')
add_body_edit("disabled_text", ValueType.SinglelineText, 'Disabled text:', '',
{'placeholder':'(Empty for same)'}, 'allow_alt_text()')
func allow_alt_text() -> bool:
return condition and (
else_action == ElseActions.Disable or
(else_action == ElseActions.Default and
DialogicUtil.get_project_setting("dialogic/choices/def_false_behaviour", 0) == 1))

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d0p5oj7m0vp15"
path="res://.godot/imported/icon.png-5b61c4d2ebb9ccd2a839f9719c30446b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Choice/icon.png"
dest_files=["res://.godot/imported/icon.png-5b61c4d2ebb9ccd2a839f9719c30446b.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

View file

@ -0,0 +1,14 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_choice.gd')]
func _get_subsystems() -> Array:
return [{'name':'Choices', 'script':this_folder.path_join('subsystem_choices.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_choices.tscn')]

View file

@ -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():
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():
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)

View file

@ -0,0 +1,20 @@
class_name DialogicNode_ChoiceButton
extends Button
## Dialogic Node that displays choices.
## 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
func _ready():
add_to_group('dialogic_choice_button')
shortcut_in_tooltip = false
hide()

View file

@ -0,0 +1,29 @@
@tool
extends HBoxContainer
func refresh() -> void:
%Autofocus.button_pressed = DialogicUtil.get_project_setting('dialogic/choices/autofocus_first', true)
var delay_value:float = DialogicUtil.get_project_setting('dialogic/choices/delay', 0.2)
%Delay.value = delay_value
%FalseBehaviour.select(DialogicUtil.get_project_setting('dialogic/choices/def_false_behaviour', 0))
%HotkeyType.select(DialogicUtil.get_project_setting('dialogic/choices/hotkey_behaviour', 0))
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()

View file

@ -0,0 +1,157 @@
[gd_scene load_steps=4 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Choice/settings_choices.gd" id="2"]
[ext_resource type="StyleBox" uid="uid://dmsjhgv22dns8" path="res://addons/dialogic/Editor/Common/TitleBgStylebox.tres" id="2_cg22x"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/HintLabelStylingScript.gd" id="3_mpugv"]
[node name="Choices" type="HBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("2")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
offset_right = 570.0
offset_bottom = 648.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Title" type="Label" parent="VBoxContainer"]
layout_mode = 2
offset_right = 570.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_cg22x")
text = "Choice Settings"
[node name="Autofocus" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
offset_top = 40.0
offset_right = 570.0
offset_bottom = 66.0
[node name="Label" type="Label" parent="VBoxContainer/Autofocus"]
layout_mode = 2
offset_right = 542.0
offset_bottom = 26.0
size_flags_horizontal = 3
text = "Autofocus first choice"
[node name="Autofocus" type="CheckBox" parent="VBoxContainer/Autofocus"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 546.0
offset_right = 570.0
offset_bottom = 26.0
[node name="Delay" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
offset_top = 70.0
offset_right = 570.0
offset_bottom = 101.0
[node name="Label2" type="Label" parent="VBoxContainer/Delay"]
layout_mode = 2
offset_top = 2.0
offset_right = 483.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Delay before choices can be pressed"
[node name="Delay" type="SpinBox" parent="VBoxContainer/Delay"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 487.0
offset_right = 570.0
offset_bottom = 31.0
step = 0.01
[node name="DefaultFalseBehaviour" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
offset_top = 105.0
offset_right = 570.0
offset_bottom = 136.0
[node name="Label3" type="Label" parent="VBoxContainer/DefaultFalseBehaviour"]
layout_mode = 2
offset_top = 2.0
offset_right = 466.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Default behaviour for false choices"
[node name="FalseBehaviour" type="OptionButton" parent="VBoxContainer/DefaultFalseBehaviour"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 470.0
offset_right = 570.0
offset_bottom = 31.0
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="VSeparator" type="VSeparator" parent="."]
layout_mode = 2
offset_left = 574.0
offset_right = 578.0
offset_bottom = 648.0
[node name="VBoxContainer2" type="VBoxContainer" parent="."]
layout_mode = 2
offset_left = 582.0
offset_right = 1152.0
offset_bottom = 648.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Title2" type="Label" parent="VBoxContainer2"]
layout_mode = 2
offset_right = 570.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_cg22x")
text = "Choice Hotkeys"
[node name="HotkeySelection" type="HBoxContainer" parent="VBoxContainer2"]
layout_mode = 2
offset_top = 40.0
offset_right = 570.0
offset_bottom = 71.0
[node name="Label4" type="Label" parent="VBoxContainer2/HotkeySelection"]
layout_mode = 2
offset_top = 2.0
offset_right = 428.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Hotkey type"
[node name="HotkeyType" type="OptionButton" parent="VBoxContainer2/HotkeySelection"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 432.0
offset_right = 570.0
offset_bottom = 31.0
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
[node name="Info" type="Label" parent="VBoxContainer2"]
layout_mode = 2
offset_top = 75.0
offset_right = 570.0
offset_bottom = 127.0
size_flags_horizontal = 3
text = "You can add more complex hotkeys by editing the \"shortcut\" property of a ChoiceButton. "
autowrap_mode = 3
script = ExtResource("3_mpugv")
[connection signal="toggled" from="VBoxContainer/Autofocus/Autofocus" to="." method="_on_Autofocus_toggled"]
[connection signal="value_changed" from="VBoxContainer/Delay/Delay" to="." method="_on_Delay_value_changed"]
[connection signal="item_selected" from="VBoxContainer/DefaultFalseBehaviour/FalseBehaviour" to="." method="_on_FalseBehaviour_item_selected"]
[connection signal="item_selected" from="VBoxContainer2/HotkeySelection/HotkeyType" to="." method="_on_HotkeyType_item_selected"]

View file

@ -0,0 +1,132 @@
extends DialogicSubsystem
## Subsystem that manages showing and activating of choices.
## Used to block choices from being clicked for a couple of seconds (if delay is set in settings).
var choice_blocker = Timer.new()
func _ready():
choice_blocker.one_shot = true
DialogicUtil.update_timer_process_callback(choice_blocker)
add_child(choice_blocker)
####################################################################################################
## STATE
####################################################################################################
func clear_game_state():
hide_all_choices()
####################################################################################################
## 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', self.choice_selected):
node.disconnect('button_up', self.choice_selected)
## Lists all current choices and shows buttons.
func show_current_choices() -> void:
hide_all_choices()
var button_idx = 1
for choice_index in get_current_choice_indexes():
var choice_event = dialogic.current_timeline_events[choice_index]
# check if condition is false
if not choice_event.condition.is_empty() and not dialogic.execute_condition(choice_event.condition):
if choice_event.else_action == DialogicChoiceEvent.ElseActions.Default:
choice_event.else_action = DialogicUtil.get_project_setting('dialogic/choices/def_false_behaviour', 0)
# check what to do in this case
if choice_event.else_action == DialogicChoiceEvent.ElseActions.Disable:
if !choice_event.disabled_text.is_empty():
show_choice(button_idx, choice_event.get_property_translated('disabled_text'), false, choice_index)
else:
show_choice(button_idx, choice_event.get_property_translated('text'), false, choice_index)
button_idx += 1
# else just show it
else:
show_choice(button_idx, choice_event.get_property_translated('text'), true, choice_index)
button_idx += 1
if typeof(DialogicUtil.get_project_setting('dialogic/choices/delay')) != TYPE_FLOAT:
choice_blocker.start(DialogicUtil.get_project_setting('dialogic/choices/delay', 0.2).to_float())
else:
choice_blocker.start(DialogicUtil.get_project_setting('dialogic/choices/delay', 0.2))
## Adds a button with the given text that leads to the given event.
func show_choice(button_index:int, text:String, enabled:bool, event_index:int) -> void:
var idx = 1
for node 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 (idx == button_index and node.choice_index == -1):
node.show()
if dialogic.has_subsystem('VAR'):
node.text = dialogic.VAR.parse_variables(text)
else:
node.text = text
if idx == 1 and DialogicUtil.get_project_setting('dialogic/choices/autofocus_first', true):
node.grab_focus()
if DialogicUtil.get_project_setting('dialogic/choices/hotkey_behaviour', 0) == 1 and idx < 10:
var shortcut = Shortcut.new()
var input_key = InputEventKey.new()
input_key.scancode = OS.find_keycode_from_string(str(idx))
shortcut.shortcut = input_key
node.shortcut = shortcut
node.disabled = not enabled
node.button_up.connect(choice_selected.bind(event_index))
if node.choice_index > 0:
idx = node.choice_index
idx += 1
####################################################################################################
## HELPERS
####################################################################################################
func choice_selected(event_index:int) -> void:
if Dialogic.paused or not choice_blocker.is_stopped():
return
hide_all_choices()
dialogic.current_state = dialogic.states.IDLE
dialogic.handle_event(event_index)
## QUESTION/CHOICES
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
return false
func get_current_choice_indexes() -> Array:
var choices = []
var evt_idx = dialogic.current_event_idx
var ignore = 0
while true:
evt_idx += 1
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
return choices

View file

@ -0,0 +1,11 @@
@tool
extends HBoxContainer
var parent_resource: DialogicChoiceEvent = null
func refresh():
if parent_resource is DialogicChoiceEvent:
show()
$Label.text = "End of choice ("+parent_resource.text+")"
else:
hide()

View file

@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Choice/ui_choice_end.gd" id="1_7qd85"]
[node name="Choice_End" type="HBoxContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1_7qd85")
[node name="Label" type="Label" parent="."]
offset_top = 288.0
offset_right = 1.0
offset_bottom = 311.0

View file

@ -0,0 +1,59 @@
@tool
class_name DialogicCommentEvent
extends DialogicEvent
## Event that does nothing but store a comment string. Will print the comment in debug builds.
### Settings
## Content of the comment.
var text :String = ""
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
print("[Dialogic Comment] #", text)
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Comment"
set_default_color('Color6')
event_category = Category.Other
event_sorting_index = 0
continue_at_end = true
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string = "# "+text
return result_string
func from_text(string:String) -> void:
text = string.trim_prefix("# ")
func is_valid_event(string:String) -> bool:
if string.strip_edges().begins_with('#'):
return true
return false
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('text', ValueType.SinglelineText, '#')

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://hbx10ru6sxfb"
path="res://.godot/imported/icon.png-0db0f9a86499a14eda3ca834beda5746.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Comment/icon.png"
dest_files=["res://.godot/imported/icon.png-0db0f9a86499a14eda3ca834beda5746.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

View file

@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_comment.gd')]

View file

@ -0,0 +1,126 @@
@tool
class_name DialogicConditionEvent
extends DialogicEvent
## Event that allows branching a timeline based on a condition.
enum ConditionTypes {If, Elif, Else}
### Settings
## condition type (see [ConditionTypes]). Defaults to if.
var condition_type := ConditionTypes.If
## The condition as a string. Will be executed as an Expression.
var condition: String = ""
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
if condition_type == ConditionTypes.Else:
finish()
return
if condition.is_empty(): condition = "true"
var result = dialogic.execute_condition(condition)
if not result:
var idx = dialogic.current_event_idx
var ignore = 1
while true:
idx += 1
if not dialogic.current_timeline.get_event(idx):
break
if ignore == 0 and dialogic.current_timeline.get_event(idx) is DialogicConditionEvent:
break
if dialogic.current_timeline.get_event(idx).can_contain_events:
ignore += 1
elif dialogic.current_timeline.get_event(idx) is DialogicEndBranchEvent:
ignore -= 1
elif ignore == 0:
break
dialogic.current_event_idx = idx-1
finish()
## only called if the previous event was an end-branch event
## return true if this event should be executed if the previous event was an end-branch event
func should_execute_this_branch() -> bool:
return condition_type == ConditionTypes.If
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Condition"
set_default_color('Color3')
event_category = Category.Logic
event_sorting_index = 0
can_contain_events = true
continue_at_end = 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_condition_end.tscn')).instantiate()
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string := ""
match condition_type:
ConditionTypes.If:
result_string = 'if '+condition+':'
ConditionTypes.Elif:
result_string = 'elif '+condition+':'
ConditionTypes.Else:
result_string = 'else:'
return result_string
func from_text(string:String) -> void:
if string.strip_edges().begins_with('if'):
condition = string.strip_edges().trim_prefix('if ').trim_suffix(':').strip_edges()
condition_type = ConditionTypes.If
elif string.strip_edges().begins_with('elif'):
condition = string.strip_edges().trim_prefix('elif ').trim_suffix(':').strip_edges()
condition_type = ConditionTypes.Elif
elif string.strip_edges().begins_with('else'):
condition = ""
condition_type = ConditionTypes.Else
func is_valid_event(string:String) -> bool:
if (string.strip_edges().begins_with('if ') or string.strip_edges().begins_with('elif ') or string.strip_edges().begins_with('else')) and string.strip_edges().ends_with(':'):
return true
return false
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('condition_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'IF',
'value': ConditionTypes.If,
},
{
'label': 'ELIF',
'value': ConditionTypes.Elif,
},
{
'label': 'ELSE',
'value': ConditionTypes.Else,
}
], 'disabled':true})
add_header_edit('condition', ValueType.Condition, '', '', {}, 'condition_type != %s'%ConditionTypes.Else)

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b6bevon73e8mo"
path="res://.godot/imported/icon.png-e7bc292be22002b6dc9192913250e17f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Condition/icon.png"
dest_files=["res://.godot/imported/icon.png-e7bc292be22002b6dc9192913250e17f.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

View file

@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_condition.gd')]

View file

@ -0,0 +1,45 @@
@tool
extends HBoxContainer
var parent_resource = null
func _ready():
$AddElif.button_up.connect(add_elif)
$AddElse.button_up.connect(add_else)
func refresh():
if parent_resource is DialogicConditionEvent:
# hide add elif and add else button on ELSE event
$AddElif.visible = parent_resource.condition_type != DialogicConditionEvent.ConditionTypes.Else
$AddElse.visible = parent_resource.condition_type != DialogicConditionEvent.ConditionTypes.Else
$Label.text = "End of "+["IF", "ELIF", "ELSE"][parent_resource.condition_type]+" ("+parent_resource.condition+")"
# hide add add else button if followed by ELIF or ELSE event
var timeline_editor = find_parent('VisualEditor')
if timeline_editor:
var next_event = null
if timeline_editor.get_block_below(get_parent()):
next_event = timeline_editor.get_block_below(get_parent()).resource
if next_event is DialogicConditionEvent:
if next_event.condition_type != DialogicConditionEvent.ConditionTypes.If:
$AddElse.hide()
if parent_resource.condition_type == DialogicConditionEvent.ConditionTypes.Else:
$Label.text = "End of ELSE"
else:
hide()
func add_elif():
var timeline = find_parent('VisualEditor')
if timeline:
var resource = DialogicConditionEvent.new()
resource.condition_type = DialogicConditionEvent.ConditionTypes.Elif
timeline.add_event_with_end_branch(resource, get_parent().get_index()+1)
timeline.indent_events()
func add_else():
var timeline = find_parent('VisualEditor')
if timeline:
var resource = DialogicConditionEvent.new()
resource.condition_type = DialogicConditionEvent.ConditionTypes.Else
timeline.add_event_with_end_branch(resource, get_parent().get_index()+1)
timeline.indent_events()

View file

@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Condition/ui_condition_end.gd" id="1_sh52m"]
[node name="Condition_End" type="HBoxContainer"]
offset_right = 90.0
offset_bottom = 23.0
script = ExtResource("1_sh52m")
[node name="Label" type="Label" parent="."]
offset_top = 2.0
offset_right = 141.0
offset_bottom = 28.0
text = "End of condition X"
[node name="AddElif" type="Button" parent="."]
offset_left = 145.0
offset_right = 212.0
offset_bottom = 31.0
text = "Add Elif"
[node name="AddElse" type="Button" parent="."]
offset_left = 216.0
offset_right = 290.0
offset_bottom = 31.0
text = "Add Else"

View file

@ -0,0 +1,18 @@
extends Node
class_name CustomEventConverter
# This is an extension to the Converter to allow for converting custom scenes
# This will take a 1.x event node with a custom scene, and let you convert it to the new format
# One type is provided for example. If you wish to convert them into a new format, add it here as another Match case
# Otherwise, it will just create it as a commment with the parameters in it for you to do in-editor
static func convertCustomEvent(event):
var returnString = ""
match event['event_id']:
"comment_001":
# Example node, a custom event to simply store comments, as there wasn't a built-in one in 1.x
returnString += "# " + event['comment_text']
return returnString

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d0vwotbceutf6"
path="res://.godot/imported/icon.png-1a336d53bfc8ce6e2b3ee694eb2fd062.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Converter/icon.png"
dest_files=["res://.godot/imported/icon.png-1a336d53bfc8ce6e2b3ee694eb2fd062.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

View file

@ -0,0 +1,5 @@
@tool
extends DialogicIndexer
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_converter.tscn')]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Converter/settings_converter.gd" id="1_3feab"]
[ext_resource type="StyleBox" uid="uid://dmsjhgv22dns8" path="res://addons/dialogic/Editor/Common/TitleBgStylebox.tres" id="2_lrwmv"]
[node name="Dialogic 1 Converter" type="HSplitContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_3feab")
[node name="LeftPanel" type="VBoxContainer" parent="."]
layout_mode = 2
offset_right = 727.0
offset_bottom = 648.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 1.75
[node name="Label" type="Label" parent="LeftPanel"]
layout_mode = 2
offset_right = 727.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_lrwmv")
text = "Output"
[node name="ScrollContainer" type="ScrollContainer" parent="LeftPanel"]
layout_mode = 2
offset_top = 40.0
offset_right = 727.0
offset_bottom = 648.0
size_flags_vertical = 3
vertical_scroll_mode = 0
[node name="OutputLog" type="RichTextLabel" parent="LeftPanel/ScrollContainer"]
unique_name_in_owner = true
clip_contents = false
layout_mode = 2
offset_right = 727.0
offset_bottom = 608.0
size_flags_horizontal = 3
size_flags_vertical = 3
bbcode_enabled = true
text = "This is a converter script to convert Dialogic 1.4+ files into Dialogic 2.0 format.
Please copy your res://dialogic folder from a Dialogic 1.4 project into this project before proceeding.
Once you are ready, the Verify Files button will do a check to make sure all of the files are present, and prepare it for conversion."
[node name="RightPanel" type="VBoxContainer" parent="."]
layout_mode = 2
offset_left = 739.0
offset_right = 1152.0
offset_bottom = 648.0
size_flags_horizontal = 3
[node name="Label2" type="Label" parent="RightPanel"]
layout_mode = 2
offset_right = 413.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_lrwmv")
text = "Settings"
[node name="CheckBox" type="CheckBox" parent="RightPanel"]
layout_mode = 2
offset_top = 40.0
offset_right = 413.0
offset_bottom = 71.0
text = "Add prefix to duplicate characters"
[node name="Verify" type="Button" parent="RightPanel"]
layout_mode = 2
offset_top = 75.0
offset_right = 413.0
offset_bottom = 106.0
text = "Verify Files"
[node name="Begin" type="Button" parent="RightPanel"]
layout_mode = 2
offset_top = 110.0
offset_right = 413.0
offset_bottom = 141.0
disabled = true
text = "Begin Conversion"
[connection signal="toggled" from="RightPanel/CheckBox" to="." method="_on_check_box_toggled"]
[connection signal="pressed" from="RightPanel/Verify" to="." method="_on_verify_pressed"]
[connection signal="pressed" from="RightPanel/Begin" to="." method="_on_begin_pressed"]

View file

@ -0,0 +1,68 @@
@tool
class_name DialogicEndBranchEvent
extends DialogicEvent
## Event that indicates the end of a condition or choice (or custom branch).
## In text this is not stored (only as a change in indentation).
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
dialogic.current_event_idx = find_next_index()-1
finish()
func find_next_index():
var idx: int = dialogic.current_event_idx
var ignore: int = 1
while true:
idx += 1
var event = dialogic.current_timeline.get_event(idx)
if not event:
return idx
if event is DialogicEndBranchEvent:
if ignore > 1: ignore -= 1
elif event.can_contain_events and not event.should_execute_this_branch():
ignore += 1
elif ignore <= 1:
return idx
return idx
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "End Branch"
event_color = Color(1,1,1,1)
event_category = Category.Logic
event_sorting_index = 0
disable_editor_button = true
continue_at_end = true
################################################################################
## SAVING/LOADING
################################################################################
## NOTE: This event is very special. It is rarely stored at all, as it is usually
## just a placeholder for removing an indentation level.
## When copying events however, some representation of this is necessary. That's why this is half-implemented.
func to_text() -> String:
return "<<END BRANCH>>"
func from_text(string:String) -> void:
pass
func is_valid_event(string:String) -> bool:
if string.strip_edges().begins_with("<<END BRANCH>>"):
return true
return false

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b0h5mknqmos7r"
path="res://.godot/imported/icon.png-2770b68f95c6e9b6492f82019c7cb894.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/End Branch/icon.png"
dest_files=["res://.godot/imported/icon.png-2770b68f95c6e9b6492f82019c7cb894.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

View file

@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_end_branch.gd')]

View file

@ -0,0 +1,34 @@
@tool
class_name DialogicEndTimelineEvent
extends DialogicEvent
## Event that ends a timeline (even if more events come after).
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
for character in dialogic.Portraits.get_joined_characters():
dialogic.Portraits.remove_portrait(character)
dialogic.end_timeline()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "End Timeline"
set_default_color('Color4')
event_category = Category.Timeline
event_sorting_index = 10
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "end_timeline"

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cstw2y41yacgc"
path="res://.godot/imported/icon.png-a0a96e8aa3ccec20b5e27375ec04ccf1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/End/icon.png"
dest_files=["res://.godot/imported/icon.png-a0a96e8aa3ccec20b5e27375ec04ccf1.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

View file

@ -0,0 +1,6 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_end.gd')]

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.9645 2.62927L12.8459 5.22444C12.6271 5.04546 12.3835 4.89134 12.1151 4.76208C11.8565 4.62287 11.5881 4.55327 11.3097 4.55327C11.0909 4.55327 10.8821 4.59802 10.6832 4.68751C10.4943 4.77699 10.3203 4.89631 10.1612 5.04546C10.0021 5.19461 9.86293 5.36364 9.74361 5.55256C9.6243 5.73154 9.52486 5.91052 9.44532 6.08949L9.14702 6.93964C9.35583 7.37714 9.55966 7.79475 9.75853 8.19248C9.92756 8.53055 10.1016 8.87359 10.2805 9.2216C10.4595 9.55966 10.6087 9.82813 10.728 10.027C10.9169 10.3153 11.1058 10.6136 11.2948 10.9219C11.4837 11.2202 11.6925 11.4936 11.9212 11.7422C12.0206 11.8516 12.1399 11.9261 12.2791 11.9659C12.4283 11.9957 12.5625 12.0107 12.6818 12.0107C12.8707 12.0107 13.0497 11.9858 13.2188 11.9361C13.3878 11.8864 13.5469 11.8217 13.696 11.7422L14.0689 12.2195C13.9297 12.4382 13.7607 12.657 13.5618 12.8757C13.3629 13.0945 13.1442 13.2933 12.9055 13.4723C12.6769 13.6513 12.4283 13.7955 12.1598 13.9048C11.9013 14.0242 11.6378 14.0838 11.3693 14.0838C11.1307 14.0838 10.9169 14.044 10.728 13.9645C10.549 13.8949 10.3849 13.8004 10.2358 13.6811C10.0867 13.5519 9.94745 13.4027 9.81819 13.2337C9.68893 13.0646 9.55469 12.8906 9.41549 12.7117C9.30611 12.5426 9.1868 12.3388 9.05753 12.1001C8.93822 11.8516 8.81393 11.598 8.68466 11.3395C8.5554 11.081 8.42614 10.8324 8.29688 10.5938C8.16762 10.3452 8.04333 10.1364 7.92401 9.96734C7.8743 10.1364 7.82955 10.3054 7.78978 10.4744C7.75001 10.6136 7.70526 10.7578 7.65555 10.907C7.60583 11.0561 7.56109 11.1705 7.52131 11.25C7.37216 11.5682 7.18324 11.8963 6.95455 12.2344C6.72586 12.5724 6.46236 12.8807 6.16407 13.1591C5.87572 13.4276 5.55753 13.6513 5.20952 13.8303C4.86151 13.9993 4.49361 14.0838 4.10583 14.0838C3.75782 14.0838 3.41975 14.0142 3.09162 13.875C2.7635 13.7457 2.46023 13.5717 2.18182 13.353L3.12145 10.8771C3.43964 11.076 3.78765 11.255 4.16549 11.4141C4.54333 11.5632 4.92117 11.6378 5.29901 11.6378C5.41833 11.6378 5.54262 11.6278 5.67188 11.608C5.80114 11.5781 5.92543 11.5384 6.04475 11.4886C6.17401 11.429 6.28836 11.3594 6.38779 11.2798C6.48722 11.1903 6.5618 11.0859 6.61151 10.9666C6.68111 10.8374 6.75569 10.6683 6.83523 10.4595C6.91478 10.2507 6.99432 10.0419 7.07387 9.8331C7.16336 9.59447 7.25285 9.34091 7.34234 9.07245L4.53836 4.5831C4.4091 4.43395 4.25001 4.31464 4.06109 4.22515C3.88211 4.12572 3.69319 4.076 3.49432 4.076C3.32529 4.076 3.1662 4.1108 3.01705 4.1804C2.8679 4.24006 2.72373 4.32458 2.58452 4.43395L2.18182 3.91194C2.32103 3.70313 2.48509 3.4993 2.67401 3.30043C2.87287 3.09162 3.08665 2.90768 3.31535 2.74859C3.54404 2.57955 3.78765 2.44532 4.04617 2.34589C4.30469 2.23651 4.56819 2.18182 4.83665 2.18182C5.16478 2.18182 5.46805 2.26634 5.74645 2.43537C6.02486 2.59447 6.28339 2.79333 6.52202 3.03197C6.76066 3.2706 6.97941 3.52912 7.17827 3.80753C7.37714 4.08594 7.55611 4.34447 7.7152 4.5831C7.79475 4.69248 7.87927 4.83168 7.96876 5.00072C8.06819 5.15981 8.16265 5.3189 8.25214 5.47799C8.36151 5.65697 8.46591 5.85086 8.56535 6.05966C8.66478 5.82103 8.76918 5.58239 8.87856 5.34376C8.96805 5.14489 9.05753 4.94106 9.14702 4.73225C9.24645 4.5135 9.33594 4.32458 9.41549 4.16549C9.56464 3.88708 9.73367 3.62856 9.92259 3.38992C10.1115 3.15128 10.3203 2.94248 10.549 2.7635C10.7876 2.58452 11.0462 2.44532 11.3246 2.34589C11.603 2.23651 11.9063 2.18182 12.2344 2.18182C12.5426 2.18182 12.8409 2.2216 13.1293 2.30114C13.4176 2.38069 13.696 2.49006 13.9645 2.62927Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://580n3xtnq3ru"
path="res://.godot/imported/definition.svg-031b12c360f1060f573315dfb3d273d8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Glossary/definition.svg"
dest_files=["res://.godot/imported/definition.svg-031b12c360f1060f573315dfb3d273d8.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,43 @@
@tool
class_name DialogicGlossaryEvent
extends DialogicEvent
## Event that does nothing right now.
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
pass
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Glossary"
set_default_color('Color6')
event_category = Category.AudioVisual
event_sorting_index = 0
expand_by_default = false
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "glossary"
func get_shortcode_parameters() -> Dictionary:
return {
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
pass

View file

@ -0,0 +1,11 @@
class_name DialogicGlossary
extends Resource
## Resource used to store glossary entries. Can be saved to disc and used as a glossary.
## Add/create glossaries fom the settings
## Stores all entry information
@export var entries :Dictionary = {}
## If false, no entries from this glossary will be shown
@export var enabled :bool = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b6wqvg2qcjxs"
path="res://.godot/imported/icon.png-624eb6dbf7e3ab27845a397653fa2fbb.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Glossary/icon.png"
dest_files=["res://.godot/imported/icon.png-624eb6dbf7e3ab27845a397653fa2fbb.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

View file

@ -0,0 +1,13 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_glossary.gd')]
func _get_subsystems() -> Array:
return [{'name':'Glossary', 'script':this_folder.path_join('subsystem_glossary.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_glossary.tscn')]

View file

@ -0,0 +1,210 @@
@tool
extends VBoxContainer
var current_glossary :DialogicGlossary = null
var current_entry_name := ""
################################################################################
## BASICS
################################################################################
func _ready() -> void:
%AddGlossaryFile.icon = get_theme_icon('Add', 'EditorIcons')
%LoadGlossaryFile.icon = get_theme_icon('Folder', 'EditorIcons')
%DeleteGlossaryFile.icon = get_theme_icon('Remove', 'EditorIcons')
%DeleteGlossaryEntry.icon = get_theme_icon('Remove', 'EditorIcons')
%AddGlossaryEntry.icon = get_theme_icon('Add', 'EditorIcons')
%GlossaryList.item_selected.connect(_on_GlossaryList_item_selected)
%EntryList.item_selected.connect(_on_EntryList_item_selected)
%DefaultColor.color_changed.connect(set_setting.bind('dialogic/glossary/default_color'))
%DefaultCaseSensitive.toggled.connect(set_setting.bind('dialogic/glossary/default_case_sensitive'))
func set_setting(value, setting:String) -> void:
ProjectSettings.set_setting(setting, value)
ProjectSettings.save()
func refresh() -> void:
%DefaultColor.color = DialogicUtil.get_project_setting('dialogic/glossary/default_color', Color.POWDER_BLUE)
%DefaultCaseSensitive.button_pressed = DialogicUtil.get_project_setting('dialogic/glossary/default_case_sensitive', true)
%GlossaryList.clear()
var idx := 0
for file in DialogicUtil.get_project_setting('dialogic/glossary/glossary_files', []):
if FileAccess.file_exists(file):
%GlossaryList.add_item(DialogicUtil.pretty_name(file), get_theme_icon('FileList', 'EditorIcons'))
else:
%GlossaryList.add_item(DialogicUtil.pretty_name(file), get_theme_icon('FileDead', 'EditorIcons'))
%GlossaryList.set_item_tooltip(idx, file)
idx += 1
%EntryList.clear()
if %GlossaryList.item_count != 0:
%GlossaryList.select(0)
_on_GlossaryList_item_selected(0)
else:
current_glossary = null
hide_entry_editor()
################################################################################
## GLOSSARY LIST
################################################################################
func _on_GlossaryList_item_selected(idx:int) -> void:
%EntryList.clear()
if FileAccess.file_exists(%GlossaryList.get_item_tooltip(idx)):
current_glossary = load(%GlossaryList.get_item_tooltip(idx))
if not current_glossary is DialogicGlossary:
return
for entry in current_glossary.entries:
%EntryList.add_item(entry)
if %EntryList.item_count != 0:
%EntryList.select(0)
_on_EntryList_item_selected(0)
else:
hide_entry_editor()
func _on_add_glossary_file_pressed() -> void:
find_parent('EditorView').godot_file_dialog(create_new_glossary_file, '*.tres', EditorFileDialog.FILE_MODE_SAVE_FILE, 'Create new glossary resource')
func create_new_glossary_file(path:String) -> void:
var glossary := DialogicGlossary.new()
glossary.resource_path = path
ResourceSaver.save(glossary, path)
load_glossary_file(path)
func _on_load_glossary_file_pressed() -> void:
find_parent('EditorView').godot_file_dialog(load_glossary_file, '*.tres', EditorFileDialog.FILE_MODE_OPEN_FILE, 'Select glossary resource')
func load_glossary_file(path:String) -> void:
var list :Array= DialogicUtil.get_project_setting('dialogic/glossary/glossary_files', [])
if not path in list:
list.append(path)
ProjectSettings.set_setting('dialogic/glossary/glossary_files', list)
ProjectSettings.save()
%GlossaryList.add_item(DialogicUtil.pretty_name(path), get_theme_icon('FileList', 'EditorIcons'))
%GlossaryList.set_item_tooltip(%GlossaryList.item_count-1, path)
%GlossaryList.select(%GlossaryList.item_count-1)
_on_GlossaryList_item_selected(%GlossaryList.item_count-1)
func _on_delete_glossary_file_pressed() -> void:
if len(%GlossaryList.get_selected_items()) != 0:
var list :Array = DialogicUtil.get_project_setting('dialogic/glossary/glossary_files', [])
list.erase(%GlossaryList.get_item_tooltip(
%GlossaryList.get_selected_items()[0]))
ProjectSettings.set_setting('dialogic/glossary/glossary_files', list)
ProjectSettings.save()
refresh()
################################################################################
## ENTRY LIST
################################################################################
func _on_EntryList_item_selected(idx:int) -> void:
current_entry_name = %EntryList.get_item_text(idx)
var entry_info = current_glossary.entries[current_entry_name]
%EntryEditorTitle.text = "Edit entry"
%EntrySettings.show()
%EntryName.text = current_entry_name
%EntryCaseSensitive.button_pressed = entry_info.get('case_sensitive', %DefaultCaseSensitive.button_pressed)
var alts = ""
for i in entry_info.get('alternatives', []):
alts += i+", "
%EntryAlternatives.text = alts
%EntryTitle.text = entry_info.get('title', '')
%EntryText.text = entry_info.get('text', '')
%EntryExtra.text = entry_info.get('extra', '')
%EntryEnabled.button_pressed = entry_info.get('enabled', true)
%EntryColor.color = entry_info.get('color', %DefaultColor.color)
%EntryCustomColor.button_pressed = entry_info.has('color')
%EntryColor.disabled = !entry_info.has('color')
func _on_add_glossary_entry_pressed() -> void:
if !current_glossary:
return
var new_name := "New Entry"
if new_name in current_glossary.entries:
var count := 2
while new_name + " " +str(count) in current_glossary.entries:
count += 1
new_name += " " + str(count)
current_glossary.entries[new_name] = {}
ResourceSaver.save(current_glossary)
%EntryList.add_item(new_name)
%EntryList.select(%EntryList.item_count-1)
_on_EntryList_item_selected(%EntryList.item_count-1)
func _on_delete_glossary_entry_pressed() -> void:
if len(%EntryList.get_selected_items()) != 0:
if current_glossary:
current_glossary.entries.erase(%EntryList.get_item_text(
%EntryList.get_selected_items()[0]))
%EntryList.remove_item(%EntryList.get_selected_items()[0])
################################################################################
## ENTRY EDITOR
################################################################################
func hide_entry_editor() -> void:
%EntrySettings.hide()
%EntryEditorTitle.text = "No entry selected."
func _on_entry_name_text_changed(new_text:String) -> void:
if current_entry_name != new_text.strip_edges():
if new_text.strip_edges().is_empty() or new_text.strip_edges() in current_glossary.entries.keys():
%EntryList.set_item_custom_bg_color(%EntryList.get_selected_items()[0],
get_theme_color("warning_color", "Editor").darkened(0.8))
%EntryList.set_item_text(%EntryList.get_selected_items()[0], new_text.strip_edges() + " (invalid name)")
return
else:
%EntryList.set_item_custom_bg_color(%EntryList.get_selected_items()[0],
Color.TRANSPARENT)
var info :Dictionary = current_glossary.entries[current_entry_name]
current_glossary.entries.erase(current_entry_name)
current_glossary.entries[new_text.strip_edges()] = info
%EntryList.set_item_text(%EntryList.get_selected_items()[0], new_text.strip_edges())
current_entry_name = new_text.strip_edges()
ResourceSaver.save(current_glossary)
func _on_entry_case_sensitive_toggled(button_pressed:bool) -> void:
current_glossary.entries[current_entry_name]['case_sensitive'] = button_pressed
ResourceSaver.save(current_glossary)
func _on_entry_alternatives_text_changed(new_text:String) -> void:
var alts := []
for i in new_text.split(',', false):
alts.append(i.strip_edges())
current_glossary.entries[current_entry_name]['alternatives'] = alts
ResourceSaver.save(current_glossary)
func _on_entry_title_text_changed(new_text:String) -> void:
current_glossary.entries[current_entry_name]['title'] = new_text
ResourceSaver.save(current_glossary)
func _on_entry_text_text_changed() -> void:
current_glossary.entries[current_entry_name]['text'] = %EntryText.text
ResourceSaver.save(current_glossary)
func _on_entry_extra_text_changed() -> void:
current_glossary.entries[current_entry_name]['extra'] = %EntryExtra.text
ResourceSaver.save(current_glossary)
func _on_entry_enabled_toggled(button_pressed:bool) -> void:
current_glossary.entries[current_entry_name]['enabled'] = button_pressed
ResourceSaver.save(current_glossary)
func _on_entry_custom_color_toggled(button_pressed:bool) -> void:
%EntryColor.disabled = !button_pressed
if !button_pressed:
current_glossary.entries[current_entry_name].erase('color')
else:
current_glossary.entries[current_entry_name]['color'] = %EntryColor.color
func _on_entry_color_color_changed(color:Color) -> void:
current_glossary.entries[current_entry_name]['color'] = color
ResourceSaver.save(current_glossary)

View file

@ -0,0 +1,370 @@
[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Glossary/settings_glossary.gd" id="1_tf3p1"]
[ext_resource type="StyleBox" uid="uid://dmsjhgv22dns8" path="res://addons/dialogic/Editor/Common/TitleBgStylebox.tres" id="2_kj6gm"]
[sub_resource type="Image" id="Image_ruhry"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 131, 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, 93, 93, 131, 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, 93, 93, 131, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 55, 255, 97, 97, 58, 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, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 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, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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_mi8la"]
image = SubResource("Image_ruhry")
[node name="GlossaryEditor" type="VBoxContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("1_tf3p1")
[node name="Entries" type="HBoxContainer" parent="."]
offset_right = 1024.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Settings" type="VBoxContainer" parent="Entries"]
offset_right = 371.0
offset_bottom = 600.0
size_flags_horizontal = 3
[node name="Title" type="Label" parent="Entries/Settings"]
offset_right = 371.0
offset_bottom = 36.0
size_flags_horizontal = 3
theme_override_styles/normal = ExtResource("2_kj6gm")
text = "Glossary Settings"
[node name="DefColor" type="HBoxContainer" parent="Entries/Settings"]
offset_top = 40.0
offset_right = 371.0
offset_bottom = 66.0
[node name="Label" type="Label" parent="Entries/Settings/DefColor"]
offset_right = 317.0
offset_bottom = 26.0
size_flags_horizontal = 3
text = "Default color"
[node name="DefaultColor" type="ColorPickerButton" parent="Entries/Settings/DefColor"]
unique_name_in_owner = true
custom_minimum_size = Vector2(50, 0)
offset_left = 321.0
offset_right = 371.0
offset_bottom = 26.0
size_flags_horizontal = 8
[node name="DefCaseSensitive" type="HBoxContainer" parent="Entries/Settings"]
offset_top = 70.0
offset_right = 371.0
offset_bottom = 96.0
[node name="Label" type="Label" parent="Entries/Settings/DefCaseSensitive"]
offset_right = 343.0
offset_bottom = 26.0
size_flags_horizontal = 3
text = "Default case sensitive"
[node name="DefaultCaseSensitive" type="CheckBox" parent="Entries/Settings/DefCaseSensitive"]
unique_name_in_owner = true
offset_left = 347.0
offset_right = 371.0
offset_bottom = 26.0
[node name="FileList" type="VBoxContainer" parent="Entries/Settings"]
offset_top = 100.0
offset_right = 371.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
size_flags_stretch_ratio = 0.69
[node name="HBox" type="HBoxContainer" parent="Entries/Settings/FileList"]
offset_right = 371.0
offset_bottom = 36.0
[node name="Title2" type="Label" parent="Entries/Settings/FileList/HBox"]
offset_right = 218.0
offset_bottom = 36.0
size_flags_horizontal = 3
theme_override_styles/normal = ExtResource("2_kj6gm")
text = "Active Files"
[node name="LoadGlossaryFile" type="Button" parent="Entries/Settings/FileList/HBox"]
unique_name_in_owner = true
offset_left = 222.0
offset_right = 284.0
offset_bottom = 36.0
text = "Load"
icon = SubResource("ImageTexture_mi8la")
[node name="AddGlossaryFile" type="Button" parent="Entries/Settings/FileList/HBox"]
unique_name_in_owner = true
offset_left = 288.0
offset_right = 343.0
offset_bottom = 36.0
text = "Add"
icon = SubResource("ImageTexture_mi8la")
[node name="DeleteGlossaryFile" type="Button" parent="Entries/Settings/FileList/HBox"]
unique_name_in_owner = true
offset_left = 347.0
offset_right = 371.0
offset_bottom = 36.0
icon = SubResource("ImageTexture_mi8la")
[node name="ScrollContainer" type="ScrollContainer" parent="Entries/Settings/FileList"]
offset_top = 40.0
offset_right = 371.0
offset_bottom = 500.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="GlossaryList" type="ItemList" parent="Entries/Settings/FileList/ScrollContainer"]
unique_name_in_owner = true
offset_right = 371.0
offset_bottom = 460.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="VSeparator2" type="VSeparator" parent="Entries"]
offset_left = 375.0
offset_right = 379.0
offset_bottom = 600.0
[node name="EntryList" type="VBoxContainer" parent="Entries"]
offset_left = 383.0
offset_right = 640.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.69
[node name="HBox" type="HBoxContainer" parent="Entries/EntryList"]
offset_right = 257.0
offset_bottom = 36.0
[node name="Title3" type="Label" parent="Entries/EntryList/HBox"]
offset_right = 125.0
offset_bottom = 36.0
size_flags_horizontal = 3
theme_override_styles/normal = ExtResource("2_kj6gm")
text = "Entries"
[node name="AddGlossaryEntry" type="Button" parent="Entries/EntryList/HBox"]
unique_name_in_owner = true
offset_left = 129.0
offset_right = 229.0
offset_bottom = 36.0
text = "Add Entry"
icon = SubResource("ImageTexture_mi8la")
[node name="DeleteGlossaryEntry" type="Button" parent="Entries/EntryList/HBox"]
unique_name_in_owner = true
offset_left = 233.0
offset_right = 257.0
offset_bottom = 36.0
icon = SubResource("ImageTexture_mi8la")
[node name="ScrollContainer" type="ScrollContainer" parent="Entries/EntryList"]
offset_top = 40.0
offset_right = 257.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="EntryList" type="ItemList" parent="Entries/EntryList/ScrollContainer"]
unique_name_in_owner = true
offset_right = 257.0
offset_bottom = 560.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="VSeparator" type="VSeparator" parent="Entries"]
offset_left = 644.0
offset_right = 648.0
offset_bottom = 600.0
[node name="EntryEditor" type="ScrollContainer" parent="Entries"]
offset_left = 652.0
offset_right = 1024.0
offset_bottom = 600.0
size_flags_horizontal = 3
horizontal_scroll_mode = 0
metadata/_edit_layout_mode = 1
[node name="VBox" type="VBoxContainer" parent="Entries/EntryEditor"]
offset_right = 372.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="EntryEditorTitle" type="Label" parent="Entries/EntryEditor/VBox"]
unique_name_in_owner = true
offset_right = 372.0
offset_bottom = 36.0
size_flags_horizontal = 3
theme_override_styles/normal = ExtResource("2_kj6gm")
text = "Entries"
[node name="EntrySettings" type="GridContainer" parent="Entries/EntryEditor/VBox"]
unique_name_in_owner = true
offset_top = 40.0
offset_right = 372.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 13
columns = 2
[node name="Label2" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_right = 94.0
offset_bottom = 26.0
size_flags_vertical = 0
text = "Name"
[node name="HBoxContainer" type="HBoxContainer" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_left = 107.0
offset_right = 372.0
offset_bottom = 31.0
[node name="EntryName" type="LineEdit" parent="Entries/EntryEditor/VBox/EntrySettings/HBoxContainer"]
unique_name_in_owner = true
offset_right = 87.7656
offset_bottom = 31.0
size_flags_horizontal = 0
placeholder_text = "MyEntry"
expand_to_text_length = true
caret_blink = true
caret_blink_speed = 0.5
[node name="EntryCaseSensitive" type="CheckBox" parent="Entries/EntryEditor/VBox/EntrySettings/HBoxContainer"]
unique_name_in_owner = true
offset_left = 91.0
offset_right = 228.0
offset_bottom = 31.0
text = "Case sensitive"
[node name="Label3" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 35.0
offset_right = 94.0
offset_bottom = 61.0
size_flags_vertical = 0
text = "Alternatives"
[node name="EntryAlternatives" type="LineEdit" parent="Entries/EntryEditor/VBox/EntrySettings"]
unique_name_in_owner = true
offset_left = 107.0
offset_top = 35.0
offset_right = 372.0
offset_bottom = 66.0
size_flags_horizontal = 3
caret_blink = true
caret_blink_speed = 0.5
[node name="Label4" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 70.0
offset_right = 94.0
offset_bottom = 96.0
size_flags_vertical = 0
text = "Title"
[node name="EntryTitle" type="LineEdit" parent="Entries/EntryEditor/VBox/EntrySettings"]
unique_name_in_owner = true
offset_left = 107.0
offset_top = 70.0
offset_right = 372.0
offset_bottom = 101.0
size_flags_horizontal = 3
caret_blink = true
caret_blink_speed = 0.5
[node name="Label5" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 105.0
offset_right = 94.0
offset_bottom = 131.0
size_flags_vertical = 0
text = "Text"
[node name="EntryText" type="TextEdit" parent="Entries/EntryEditor/VBox/EntrySettings"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 100)
offset_left = 107.0
offset_top = 105.0
offset_right = 372.0
offset_bottom = 205.0
focus_next = NodePath("../EntryExtra")
wrap_mode = 1
[node name="Label6" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 209.0
offset_right = 94.0
offset_bottom = 235.0
size_flags_vertical = 0
text = "Extra"
[node name="EntryExtra" type="TextEdit" parent="Entries/EntryEditor/VBox/EntrySettings"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 50)
offset_left = 107.0
offset_top = 209.0
offset_right = 372.0
offset_bottom = 259.0
wrap_mode = 1
[node name="Label8" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 263.0
offset_right = 94.0
offset_bottom = 289.0
size_flags_vertical = 0
text = "Enabled"
[node name="EntryEnabled" type="CheckBox" parent="Entries/EntryEditor/VBox/EntrySettings"]
unique_name_in_owner = true
offset_left = 107.0
offset_top = 263.0
offset_right = 372.0
offset_bottom = 289.0
button_pressed = true
[node name="Label7" type="Label" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_top = 293.0
offset_right = 94.0
offset_bottom = 319.0
size_flags_vertical = 0
text = "Color"
[node name="HBox" type="HBoxContainer" parent="Entries/EntryEditor/VBox/EntrySettings"]
offset_left = 107.0
offset_top = 293.0
offset_right = 372.0
offset_bottom = 319.0
[node name="EntryCustomColor" type="CheckBox" parent="Entries/EntryEditor/VBox/EntrySettings/HBox"]
unique_name_in_owner = true
offset_right = 24.0
offset_bottom = 26.0
[node name="EntryColor" type="ColorPickerButton" parent="Entries/EntryEditor/VBox/EntrySettings/HBox"]
unique_name_in_owner = true
offset_left = 28.0
offset_right = 265.0
offset_bottom = 26.0
size_flags_horizontal = 3
[connection signal="pressed" from="Entries/Settings/FileList/HBox/LoadGlossaryFile" to="." method="_on_load_glossary_file_pressed"]
[connection signal="pressed" from="Entries/Settings/FileList/HBox/AddGlossaryFile" to="." method="_on_add_glossary_file_pressed"]
[connection signal="pressed" from="Entries/Settings/FileList/HBox/DeleteGlossaryFile" to="." method="_on_delete_glossary_file_pressed"]
[connection signal="pressed" from="Entries/EntryList/HBox/AddGlossaryEntry" to="." method="_on_add_glossary_entry_pressed"]
[connection signal="pressed" from="Entries/EntryList/HBox/DeleteGlossaryEntry" to="." method="_on_delete_glossary_entry_pressed"]
[connection signal="text_changed" from="Entries/EntryEditor/VBox/EntrySettings/HBoxContainer/EntryName" to="." method="_on_entry_name_text_changed"]
[connection signal="toggled" from="Entries/EntryEditor/VBox/EntrySettings/HBoxContainer/EntryCaseSensitive" to="." method="_on_entry_case_sensitive_toggled"]
[connection signal="text_changed" from="Entries/EntryEditor/VBox/EntrySettings/EntryAlternatives" to="." method="_on_entry_alternatives_text_changed"]
[connection signal="text_changed" from="Entries/EntryEditor/VBox/EntrySettings/EntryTitle" to="." method="_on_entry_title_text_changed"]
[connection signal="text_changed" from="Entries/EntryEditor/VBox/EntrySettings/EntryText" to="." method="_on_entry_text_text_changed"]
[connection signal="text_changed" from="Entries/EntryEditor/VBox/EntrySettings/EntryExtra" to="." method="_on_entry_extra_text_changed"]
[connection signal="toggled" from="Entries/EntryEditor/VBox/EntrySettings/EntryEnabled" to="." method="_on_entry_enabled_toggled"]
[connection signal="toggled" from="Entries/EntryEditor/VBox/EntrySettings/HBox/EntryCustomColor" to="." method="_on_entry_custom_color_toggled"]
[connection signal="color_changed" from="Entries/EntryEditor/VBox/EntrySettings/HBox/EntryColor" to="." method="_on_entry_color_color_changed"]

View file

@ -0,0 +1,76 @@
extends DialogicSubsystem
## Subsystem that handles glossaries.
## List of glossary resources that are used.
var glossaries := []
## If false, no parsing will be done.
var enabled := true
####################################################################################################
## STATE
####################################################################################################
func clear_game_state() -> void:
glossaries = []
for path in DialogicUtil.get_project_setting('dialogic/glossary/glossary_files', []):
add_glossary(path)
####################################################################################################
## MAIN METHODS
####################################################################################################
func parse_glossary(text:String) -> String:
if !enabled: return text
var def_case_sensitive :bool = DialogicUtil.get_project_setting('dialogic/glossary/default_case_sensitive', true)
var def_color : Color= DialogicUtil.get_project_setting('dialogic/glossary/default_color', Color.POWDER_BLUE)
var regex := RegEx.new()
for glossary in glossaries:
if !glossary.enabled:
continue
for entry in glossary.entries.keys():
if !glossary.entries[entry].get('enabled', true):
continue
var pattern :String = '(?<=\\W|^)(?<word>'+glossary.entries[entry].get('regopts', entry)+')(?!])(?=\\W|$)'
if glossary.entries[entry].get('case_sensitive', def_case_sensitive):
regex.compile(pattern)
else:
regex.compile('(?i)'+pattern)
text = regex.sub(text,
'[url=' + entry + ']' +
'[color=' + glossary.entries[entry].get('color', def_color).to_html() + ']${word}[/color]' +
'[/url]', true
)
return text
func add_glossary(path:String) -> void:
if FileAccess.file_exists(path):
var x = load(path)
if x is DialogicGlossary:
glossaries.append(x)
for entry in x.entries.keys():
var regopts :String = entry
for i in x.entries[entry].get('alternatives', []):
regopts += '|'+i
x.entries[entry]['regopts'] = regopts
else:
printerr('[Dialogic] The glossary file "' + path + '" is missing. Make sure it exists.')
func get_entry(name:String, parse_variables:bool = true) -> Dictionary:
for glossary in glossaries:
if name in glossary.entries:
var info:Dictionary = glossary.entries[name].duplicate()
if parse_variables and Dialogic.has_subsystem('VAR'):
for key in info.keys():
if typeof(info[key]) == TYPE_STRING:
info[key] = Dialogic.VAR.parse_variables(info[key])
return info
return {}
func set_entry(name: String, value: Dictionary) -> bool:
return false

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.9645 2.62927L12.8459 5.22444C12.6271 5.04546 12.3835 4.89134 12.1151 4.76208C11.8565 4.62287 11.5881 4.55327 11.3097 4.55327C11.0909 4.55327 10.8821 4.59802 10.6832 4.68751C10.4943 4.77699 10.3203 4.89631 10.1612 5.04546C10.0021 5.19461 9.86293 5.36364 9.74361 5.55256C9.6243 5.73154 9.52486 5.91052 9.44532 6.08949L9.14702 6.93964C9.35583 7.37714 9.55966 7.79475 9.75853 8.19248C9.92756 8.53055 10.1016 8.87359 10.2805 9.2216C10.4595 9.55966 10.6087 9.82813 10.728 10.027C10.9169 10.3153 11.1058 10.6136 11.2948 10.9219C11.4837 11.2202 11.6925 11.4936 11.9212 11.7422C12.0206 11.8516 12.1399 11.9261 12.2791 11.9659C12.4283 11.9957 12.5625 12.0107 12.6818 12.0107C12.8707 12.0107 13.0497 11.9858 13.2188 11.9361C13.3878 11.8864 13.5469 11.8217 13.696 11.7422L14.0689 12.2195C13.9297 12.4382 13.7607 12.657 13.5618 12.8757C13.3629 13.0945 13.1442 13.2933 12.9055 13.4723C12.6769 13.6513 12.4283 13.7955 12.1598 13.9048C11.9013 14.0242 11.6378 14.0838 11.3693 14.0838C11.1307 14.0838 10.9169 14.044 10.728 13.9645C10.549 13.8949 10.3849 13.8004 10.2358 13.6811C10.0867 13.5519 9.94745 13.4027 9.81819 13.2337C9.68893 13.0646 9.55469 12.8906 9.41549 12.7117C9.30611 12.5426 9.1868 12.3388 9.05753 12.1001C8.93822 11.8516 8.81393 11.598 8.68466 11.3395C8.5554 11.081 8.42614 10.8324 8.29688 10.5938C8.16762 10.3452 8.04333 10.1364 7.92401 9.96734C7.8743 10.1364 7.82955 10.3054 7.78978 10.4744C7.75001 10.6136 7.70526 10.7578 7.65555 10.907C7.60583 11.0561 7.56109 11.1705 7.52131 11.25C7.37216 11.5682 7.18324 11.8963 6.95455 12.2344C6.72586 12.5724 6.46236 12.8807 6.16407 13.1591C5.87572 13.4276 5.55753 13.6513 5.20952 13.8303C4.86151 13.9993 4.49361 14.0838 4.10583 14.0838C3.75782 14.0838 3.41975 14.0142 3.09162 13.875C2.7635 13.7457 2.46023 13.5717 2.18182 13.353L3.12145 10.8771C3.43964 11.076 3.78765 11.255 4.16549 11.4141C4.54333 11.5632 4.92117 11.6378 5.29901 11.6378C5.41833 11.6378 5.54262 11.6278 5.67188 11.608C5.80114 11.5781 5.92543 11.5384 6.04475 11.4886C6.17401 11.429 6.28836 11.3594 6.38779 11.2798C6.48722 11.1903 6.5618 11.0859 6.61151 10.9666C6.68111 10.8374 6.75569 10.6683 6.83523 10.4595C6.91478 10.2507 6.99432 10.0419 7.07387 9.8331C7.16336 9.59447 7.25285 9.34091 7.34234 9.07245L4.53836 4.5831C4.4091 4.43395 4.25001 4.31464 4.06109 4.22515C3.88211 4.12572 3.69319 4.076 3.49432 4.076C3.32529 4.076 3.1662 4.1108 3.01705 4.1804C2.8679 4.24006 2.72373 4.32458 2.58452 4.43395L2.18182 3.91194C2.32103 3.70313 2.48509 3.4993 2.67401 3.30043C2.87287 3.09162 3.08665 2.90768 3.31535 2.74859C3.54404 2.57955 3.78765 2.44532 4.04617 2.34589C4.30469 2.23651 4.56819 2.18182 4.83665 2.18182C5.16478 2.18182 5.46805 2.26634 5.74645 2.43537C6.02486 2.59447 6.28339 2.79333 6.52202 3.03197C6.76066 3.2706 6.97941 3.52912 7.17827 3.80753C7.37714 4.08594 7.55611 4.34447 7.7152 4.5831C7.79475 4.69248 7.87927 4.83168 7.96876 5.00072C8.06819 5.15981 8.16265 5.3189 8.25214 5.47799C8.36151 5.65697 8.46591 5.85086 8.56535 6.05966C8.66478 5.82103 8.76918 5.58239 8.87856 5.34376C8.96805 5.14489 9.05753 4.94106 9.14702 4.73225C9.24645 4.5135 9.33594 4.32458 9.41549 4.16549C9.56464 3.88708 9.73367 3.62856 9.92259 3.38992C10.1115 3.15128 10.3203 2.94248 10.549 2.7635C10.7876 2.58452 11.0462 2.44532 11.3246 2.34589C11.603 2.23651 11.9063 2.18182 12.2344 2.18182C12.5426 2.18182 12.8409 2.2216 13.1293 2.30114C13.4176 2.38069 13.696 2.49006 13.9645 2.62927Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dlwtdexd63bxi"
path="res://.godot/imported/definition.svg-22368503ac4b1eb0eb4b2dc0b2354e38.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/History/definition.svg"
dest_files=["res://.godot/imported/definition.svg-22368503ac4b1eb0eb4b2dc0b2354e38.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,77 @@
@tool
class_name DialogicHistoryEvent
extends DialogicEvent
## Event that allows clearing, pausing and resuming of history functionality.
enum ActionTypes {Clear, Pause, Resume}
### Settings
## The type of action: Clear, Pause or Resume
var action_type := ActionTypes.Pause
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
match action_type:
ActionTypes.Clear:
dialogic.History.full_history = []
ActionTypes.Pause:
dialogic.History.full_history_enabled = false
ActionTypes.Resume:
dialogic.History.full_history_enabled = true
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "History"
set_default_color('Color6')
event_category = Category.AudioVisual
event_sorting_index = 0
expand_by_default = false
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "history"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"action" : {"property": "action_type", "default": ActionTypes.Pause,
"suggestions": func(): return {"Clear":{'value':'0'}, "Pause":{'value':'1'}, "Resume":{'value':'2'}}},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('action_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'Pause History',
'value': ActionTypes.Pause,
},
{
'label': 'Resume History',
'value': ActionTypes.Resume,
},
{
'label': 'Clear History',
'value': ActionTypes.Clear,
},
]
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2dft055rbthc"
path="res://.godot/imported/icon.png-0f163f3ae73119c52d1c0fa16d2c9e6e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/History/icon.png"
dest_files=["res://.godot/imported/icon.png-0f163f3ae73119c52d1c0fa16d2c9e6e.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

View file

@ -0,0 +1,13 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_history.gd')]
func _get_subsystems() -> Array:
return [{'name':'History', 'script':this_folder.path_join('subsystem_history.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_history.tscn')]

View file

@ -0,0 +1,35 @@
@tool
extends HBoxContainer
func refresh():
%HistoryToggle.button_pressed = DialogicUtil.get_project_setting('dialogic/history/history_system', true)
%FullHistory.button_pressed = DialogicUtil.get_project_setting('dialogic/history/full_history', true)
%HistoryLength.value = DialogicUtil.get_project_setting('dialogic/history/full_history_length', 50)
%TextHistory.button_pressed = DialogicUtil.get_project_setting('dialogic/history/text_history', true)
%FullText.button_pressed = DialogicUtil.get_project_setting('dialogic/history/full_history_option_save_text', false)
func _on_history_toggle_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/history/history_system', button_pressed)
ProjectSettings.save()
func _on_full_history_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/history/full_history', button_pressed)
ProjectSettings.save()
func _on_history_length_value_changed(value):
ProjectSettings.set_setting('dialogic/history/full_history_length', value)
ProjectSettings.save()
func _on_text_history_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/history/text_history', button_pressed)
ProjectSettings.save()
func _on_full_text_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/history/full_history_option_save_text', button_pressed)
ProjectSettings.save()

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,91 @@
extends DialogicSubsystem
## Subsystem that manages history storing.
signal history_text_already_read()
## Set from the settings at runtime. If false, no history will be saved.
var enabled: bool = true
## If true, all events will be stored with additional information.
## Usefull if you want to be able to undo, or jump to certain points.
## (Change from the settings).
var full_history_enabled: bool = true
var full_history_length: int = 50
var full_history_option_save_text: bool = false
## Stores the content of the full_history
var full_history: Array = []
## If true, all text and choices text will be saved to the history. (Change from the settings).
var text_read_history_enabled: bool = true
## Stores the content of the text_read_history
var text_read_history: Dictionary = {}
####################################################################################################
## INITIALIZE
####################################################################################################
func _ready() -> void:
enabled = DialogicUtil.get_project_setting('dialogic/history/history_system', true)
full_history_enabled = DialogicUtil.get_project_setting('dialogic/history/full_history', true)
full_history_length = DialogicUtil.get_project_setting('dialogic/history/full_history_length', 50)
full_history_option_save_text = DialogicUtil.get_project_setting('dialogic/history/full_history_option_save_text', false)
text_read_history_enabled = DialogicUtil.get_project_setting('dialogic/history/text_history', true)
####################################################################################################
## STATE
####################################################################################################
# nothing implemented right now
####################################################################################################
## MAIN METHODS
####################################################################################################
func add_event_to_history(current_timeline:String, current_index:int, current_event:DialogicEvent) -> void:
if full_history_enabled:
var event_dict: Dictionary = {}
event_dict['timeline'] = current_timeline
event_dict['index'] = current_index
event_dict['event_object'] = current_event
event_dict['event_type'] = current_event.event_name
#A few more specific types of checks need to happen here to capture previous values
if current_event.event_name == "Text" and full_history_option_save_text:
event_dict['text_event_character'] = current_event.character.get_character_name()
event_dict['text_event_portrait'] = current_event.portrait
event_dict['text_event_text'] = current_event.text
full_history.push_front(event_dict)
if full_history.size() > full_history_length:
var dropped = full_history.pop_back()
if text_read_history_enabled:
if current_event.event_name == "Text" or current_event.event_name == "Choice":
var line = str(current_index)+ "**" + current_timeline
if line in text_read_history:
emit_signal("history_text_already_read")
else:
text_read_history[line] = true
func strip_events_from_full_history() -> void:
for i in full_history.size():
full_history[i].erase('event_object')
func rebuild_all_history_events() -> void:
var temp_timelines: Dictionary = {}
for i in full_history.size():
if full_history[i]['timeline'] in temp_timelines:
full_history[i]['event_object'] = temp_timelines[full_history[i]['timeline']].events[full_history[i]['index']]
else:
var loaded_timeline = Dialogic.preload_timeline(full_history[i]['timeline'])
temp_timelines[full_history[i]['timeline']] = loaded_timeline
full_history[i]['event_object'] = loaded_timeline.events[full_history[i]['index']]

View file

@ -0,0 +1,108 @@
@tool
class_name DialogicJumpEvent
extends DialogicEvent
## Event that allows starting another timeline. Also can jump to a label in that or the current timeline.
### Settings
## The timeline to jump to, if null then it's the current one. This setting should be a dialogic timeline resource.
var timeline :DialogicTimeline = null
## If not empty, the event will try to find a Label event with this set as name. Empty by default..
var label_name : String = ""
## If true when the timeline this event jumps to finishes, dialogic will continue this one from here.
var return_after: bool = false
### Helpers
## Path to the timeline. Mainly used by the editor.
var _timeline_file: String = ""
## Helper that indicates if [timeline] has been loaded from [_timeline_file].
var _timeline_loaded: bool = false
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
if timeline: # TODO: Question, why is this always done, even if return_after is false?
dialogic.push_to_jump_stack()
if timeline and timeline != dialogic.current_timeline:
dialogic.start_timeline(timeline, label_name)
elif _timeline_file != "":
if _timeline_file.contains("res://"):
dialogic.start_timeline(_timeline_file, label_name)
else:
dialogic.start_timeline(Dialogic.find_timeline(_timeline_file), label_name)
dialogic.jump_to_label(label_name)
# TODO: Question what this is supposed to do. Can't see it being used anywhere. Is it for use from outside?
func load_timeline() -> void:
if timeline == null:
if _timeline_file != "":
timeline = Dialogic.preload_timeline(_timeline_file)
_timeline_loaded = true
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Jump"
set_default_color('Color2')
event_category = Category.Timeline
event_sorting_index = 0
expand_by_default = false
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_jump.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "jump"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"timeline" : {"property": "_timeline_file", "default": null,
"suggestions": get_timeline_suggestions},
"label" : {"property": "label_name", "default": ""},
"return_after" : {"property": "return_after", "default": false,
"suggestions": func(): return {'True':{'value':'true'}, 'False':{'value':'false'}}}
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('_timeline_file', ValueType.ComplexPicker, 'to', '', {
'file_extension': '.dtl',
'suggestions_func': get_timeline_suggestions,
'editor_icon': ["TripleBar", "EditorIcons"],
'empty_text': '(this timeline)'
})
add_header_edit("label_name", ValueType.SinglelineText, "at", '', {'placeholder':'the beginning'})
add_body_edit("return_after", ValueType.Bool, "Return to this spot after completed?")
func get_timeline_suggestions(filter:String= "") -> Dictionary:
var suggestions := {}
var resources := DialogicUtil.list_resources_of_type('.dtl')
suggestions['(this timeline)'] = {'value':'', 'editor_icon':['GuiRadioUnchecked', 'EditorIcons']}
for resource in Engine.get_meta('dialogic_timeline_directory').keys():
suggestions[resource] = {'value': resource, 'tooltip':Engine.get_meta('dialogic_timeline_directory')[resource], 'editor_icon': ["TripleBar", "EditorIcons"]}
return suggestions

View file

@ -0,0 +1,59 @@
@tool
class_name DialogicLabelEvent
extends DialogicEvent
## Event that is used as an anchor. You can use the DialogicJumpEvent to jump to this point.
### Settings
## Used to identify the label. Duplicate names in a timeline will mean it always chooses the first.
var name: String = ""
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
# This event is mainly implemented in the DialogicGameHandlers jump_to_label() method.
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Label"
set_default_color('Color2')
event_category = Category.Timeline
event_sorting_index = 1
continue_at_end = true
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_label.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "label"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"name" : {"property": "name", "default": ""},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('name', ValueType.SinglelineText, '')

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