diff --git a/Assets/Characters/Friendly/Tellik/tellick.gd b/Assets/Characters/Friendly/Tellik/tellick.gd
index 8bd44dc..fb918e1 100644
--- a/Assets/Characters/Friendly/Tellik/tellick.gd
+++ b/Assets/Characters/Friendly/Tellik/tellick.gd
@@ -1,47 +1,92 @@
extends CharacterBody2D
-const SPEED = 600.0
+var SPEED = 600.0
+const REGSPEED = 600.0
const JUMP_VELOCITY = -1000.0
const MAX_JUMP_DURATION = 0.3
const MIN_JUMP_DURATION = 0.01
const GRAVITY = 980
const FALL_GRAVITY_MULTIPLIER = 2
+var isFacingRight: bool
+
+# jumping vars
var jump_timer = 0.0
var is_jumping = false
+# crouching vars
+var is_crouching = false
+var can_stand_up = true
+var crouch_speed = 100
+
+# dialouge vars
+@export var is_talking = false
+
@onready var actionableFinder: Area2D = $Marker2D/ActionableFinder
+func _input(event: InputEvent) -> void:
+ if event.is_action_pressed("crouch"):
+ is_crouching = true
+ update_crouch_state()
+ elif(event.is_action_released("crouch")):
+ is_crouching = false
+ update_crouch_state()
+
+func update_crouch_state() -> void:
+ if is_crouching:
+ #slow speed
+ SPEED = crouch_speed
+ #shorten hitbox
+ $CollisionShape2D.scale.y = .5
+ elif(can_stand_up):
+ SPEED = REGSPEED # resotre normal speed
+ $CollisionShape2D.scale.y = 1 # restore collision
+
+
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_just_pressed("ui_accept"):
var actionables = actionableFinder.get_overlapping_areas()
if actionables.size() > 0:
- actionables[0].action()
+ is_talking = true
+ actionables[0].action(is_talking)
return
func _physics_process(delta):
# Apply gravity
if not is_on_floor():
var gravity_multiplier = FALL_GRAVITY_MULTIPLIER if velocity.y > 0 else 2
velocity.y += GRAVITY * gravity_multiplier * delta
+ if !is_talking:
+ # Handle Jump
+ if Input.is_action_just_pressed("jump") and is_on_floor():
+ start_jump()
+
+ if is_jumping:
+ if Input.is_action_pressed("jump"):
+ continue_jump(delta)
+ else:
+ end_jump()
- # Handle Jump
- if Input.is_action_just_pressed("jump") and is_on_floor():
- start_jump()
-
- if is_jumping:
- if Input.is_action_pressed("jump"):
- continue_jump(delta)
+ # Handle horizontal movement
+ var direction = Input.get_axis("move_left", "move_right")
+
+ if direction < 0:
+ isFacingRight = false
+ elif direction > 0:
+ isFacingRight = true
+
+ if isFacingRight:
+ $Marker2D.scale.x = 1
+ $AnimatedSprite2D.scale.x = 1 * 0.2
else:
- end_jump()
+ $Marker2D.scale.x = -1
+ $AnimatedSprite2D.scale.x = -1 * 0.2
+
+ if direction:
+ velocity.x = direction * SPEED
+ else:
+ velocity.x = move_toward(velocity.x, 0, SPEED)
- # Handle horizontal movement
- var direction = Input.get_axis("move_left", "move_right")
- if direction:
- velocity.x = direction * SPEED
- else:
- velocity.x = move_toward(velocity.x, 0, SPEED)
-
- move_and_slide()
+ move_and_slide()
func start_jump():
velocity.y = JUMP_VELOCITY
diff --git a/Dialouges/actionable.tscn b/Dialouges/actionable.tscn
index 68faf32..49633b8 100644
--- a/Dialouges/actionable.tscn
+++ b/Dialouges/actionable.tscn
@@ -5,3 +5,4 @@
[node name="Actionable" type="Area2D"]
collision_layer = 4
script = ExtResource("1_prwwk")
+dialouge_start = null
diff --git a/addons/dialogic-main/LICENSE b/addons/dialogic-main/LICENSE
new file mode 100644
index 0000000..f001429
--- /dev/null
+++ b/addons/dialogic-main/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 - present Emilio Coppola
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/addons/dialogic-main/README.md b/addons/dialogic-main/README.md
new file mode 100644
index 0000000..a83e6f9
--- /dev/null
+++ b/addons/dialogic-main/README.md
@@ -0,0 +1,78 @@
+
+
+
+
+
Dialogic 2
+
+
+ Create Dialogs, Visual Novels, RPGs, and manage Characters with Godot to create your Game!
+
+
+
+
+
+
+
+
+
+
+## Table of Contents
+- [Version](#version)
+- [Installation](#installation)
+- [Documentation](#documentation)
+- [Testing](#testing)
+- [Credits](#credits)
+- [License](#license)
+
+## Version
+
+Dialogic 2 **requires at least Godot 4.2**.
+
+[If you are looking for the Godot 3.x version (Dialogic 1.x) you can find it here.](https://github.com/dialogic-godot/dialogic-1)
+
+## Installation
+Follow the installation instructions on our [Getting Started](https://docs.dialogic.pro/getting-started.html#1-installation--activation) documentation.
+
+Dialogic comes with an auto-updater so you can install future versions right from within the plugin.
+
+## Documentation
+You can find the official documentation of Dialogic here: [Dialogic Documentation](https://docs.dialogic.pro/)
+
+There is a Class Reference as well: [Class Reference](https://docs.dialogic.pro/class_index.html)
+
+
+## Connect with us!
+If you need help or want to share your Dialogic projects, take a look at the following options:
+
+- Ask questions, or report bugs on our [Discord](https://discord.gg/DjcDgDaTMe)
+- Report bugs and issues on the [GitHub Issues Page](https://github.com/dialogic-godot/dialogic/issues)
+- Ask questions on [GitHub Discussions](https://github.com/dialogic-godot/dialogic/discussions)
+
+## Testing
+Dialogic uses [Unit Tests](https://en.wikipedia.org/wiki/Unit_testing) to ensure specific parts function as expected. These tests run on every git push and pull request. The framework to do these tests is called [gdUnit4](https://github.com/MikeSchulze/gdUnit4) and our tests reside in the [/Tests/Unit](https://github.com/dialogic-godot/dialogic/tree/main/Tests/Unit) path. We recommend installing the `gdUnit4` add-on from the `AssetLib`, with this add-on, you can run tests locally.
+
+To get started, take a look at the existing files in the path and read the documentation to [create your first test](https://mikeschulze.github.io/gdUnit4/first_steps/firstTest/).
+
+## Interacting with the Source Code
+All methods and variables in the Dialogic 2 source **code prefixed with an underscore (`_`)** are considered *private*, for instance: `_remove_character()`.
+
+While you can use them, they may change in their behavior or change their signature, causing breakage in your code while moving between versions.
+Most private methods are used inside public ones; if you need help, check the documentation.
+
+**Public methods and variables can be found in our [Class Reference](https://docs.dialogic.pro/class_index.html).**
+
+During the Alpha and Beta version stages, code may change at any Dialogic Release to allow drafting a better design.
+Changelogs will accommodate for these changes and inform you on how to update your code.
+
+
+## Credits
+Made by [Jowan-Spooner](https://github.com/Jowan-Spooner) and [Emilio Coppola](https://github.com/coppolaemilio).
+
+Contributors: [CakeVR](https://github.com/CakeVR), [Exelia](https://github.com/exelia-antonov), [zaknafean](https://github.com/zaknafean), [and more!](https://github.com/dialogic-godot/dialogic/graphs/contributors).
+
+Special thanks: [Arnaud](https://github.com/arnaudvergnet), [AnidemDex](https://github.com/AnidemDex), [ellogwen](https://github.com/ellogwen), [Tim Krief](https://github.com/timkrief), [Toen](https://twitter.com/ToenAndreMC), Òscar, [Francisco Presencia](https://francisco.io/), [M7mdKady14](https://github.com/M7mdKady14).
+
+### Thank you to all my [Patreons](https://www.patreon.com/coppolaemilio) and Github sponsors for making this possible!
+
+## License
+This project is licensed under the terms of the [MIT license](https://github.com/dialogic-godot/dialogic/blob/main/LICENSE).
diff --git a/addons/dialogic-main/Tests/Resources/unit_test_character.dch b/addons/dialogic-main/Tests/Resources/unit_test_character.dch
new file mode 100644
index 0000000..c3f3e7c
--- /dev/null
+++ b/addons/dialogic-main/Tests/Resources/unit_test_character.dch
@@ -0,0 +1,19 @@
+{
+"@path": "res://addons/dialogic/Resources/character.gd",
+"@subpath": NodePath(""),
+"_translation_id": "",
+"color": Color(1, 1, 1, 1),
+"custom_info": {
+"sound_mood_default": "",
+"sound_moods": {},
+"style": ""
+},
+"default_portrait": "",
+"description": "",
+"display_name": "unit_test_character",
+"mirror": false,
+"nicknames": [""],
+"offset": Vector2(0, 0),
+"portraits": {},
+"scale": 1.0
+}
\ No newline at end of file
diff --git a/addons/dialogic-main/Tests/Unit/glossary_resource_test.gd b/addons/dialogic-main/Tests/Unit/glossary_resource_test.gd
new file mode 100644
index 0000000..62664f3
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/glossary_resource_test.gd
@@ -0,0 +1,76 @@
+## This test suite tests the DialogicGlossary class.
+extends GdUnitTestSuite
+
+
+const NAME_ENTRY := "Example Name"
+const EXAMPLE_TITLE := "Example Title"
+const ALTERNATIVE_ENTRIES := ["A", "BE", "VERY LONG ENTRY"]
+
+const SAMPLE_ENTRY := {
+ DialogicGlossary.TITLE_PROPERTY: EXAMPLE_TITLE,
+ DialogicGlossary.NAME_PROPERTY: NAME_ENTRY,
+ DialogicGlossary.ALTERNATIVE_PROPERTY: ALTERNATIVE_ENTRIES
+}
+
+
+## We test to add a glossary entry and whether the resulting states of the
+## glossary indicate that the entry was added correctly.
+func test_add_entry() -> void:
+ var glossary: DialogicGlossary = DialogicGlossary.new()
+
+ assert(glossary.try_add_entry(SAMPLE_ENTRY), "Unable to add entry.")
+
+ const NAME_COUNTER := 1
+ var total_entry_count := ALTERNATIVE_ENTRIES.size() + NAME_COUNTER
+
+ assert(glossary.entries.size() == total_entry_count, "Glossary should have 1 entry")
+ assert(not glossary.get_entry(NAME_ENTRY).is_empty(), "Entry index cannot be found via entry name.")
+
+ for alternative: String in ALTERNATIVE_ENTRIES:
+ var assert_error_message := "Entry index cannot be found via alternative name: " + alternative
+ assert(not glossary.get_entry(alternative).is_empty(), assert_error_message)
+
+
+## We test whether an entry's key can be replaced and if the resulting action
+## invalidates the old entry key when accessing the glossary.
+func test_replace_entries() -> void:
+ var glossary: DialogicGlossary = DialogicGlossary.new()
+
+ assert(glossary.try_add_entry(SAMPLE_ENTRY), "Unable to add entry.")
+
+ const NEW_NAME := "NEW NAME"
+
+ glossary.replace_entry_key(NAME_ENTRY, NEW_NAME)
+
+ var entry := glossary.get_entry(NEW_NAME)
+ var error := "Entry expected to be non-empty, was empty."
+ assert(not entry.is_empty(), error)
+
+ var old_entry := glossary.get_entry(NAME_ENTRY)
+ error = "Entry expected to be empty, was an instance."
+ assert(old_entry.is_empty(), error)
+
+
+## We test whether adding and deleting entries work.
+func test_remove_entry() -> void:
+ var glossary: DialogicGlossary = DialogicGlossary.new()
+
+ assert(glossary.try_add_entry(SAMPLE_ENTRY), "Unable to add entry.")
+
+ const NAME_COUNTER := 1
+ var total_entry_count := ALTERNATIVE_ENTRIES.size() + NAME_COUNTER
+
+ assert(glossary.entries.size() == total_entry_count, "Glossary should have " + str(total_entry_count) + " entries.")
+
+ var remove_result: bool = glossary.remove_entry(NAME_ENTRY)
+
+ assert(remove_result, "Removal of entry failed.")
+ assert(glossary.get_entry(NAME_ENTRY).is_empty(), "Entry should not exist.")
+ assert(glossary.entries.size() == 0, "Glossary should have 0 entries but has " + str(glossary.entries.size()) + " entries.")
+
+
+func test_add_duplicates() -> void:
+ var glossary: DialogicGlossary = DialogicGlossary.new()
+
+ assert(glossary.try_add_entry(SAMPLE_ENTRY), "Unable to add entry.")
+ assert(not glossary.try_add_entry(SAMPLE_ENTRY), "Entry should not have been added.")
diff --git a/addons/dialogic-main/Tests/Unit/guess_special_resource_test.gd b/addons/dialogic-main/Tests/Unit/guess_special_resource_test.gd
new file mode 100644
index 0000000..41749c7
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/guess_special_resource_test.gd
@@ -0,0 +1,40 @@
+extends GdUnitTestSuite
+
+## Check if transition animations can be accessed with "in", "out, "in out"
+## as space-delimited prefix.
+func test_fade_in_animation_paths() -> void:
+ const TYPE := "PortraitAnimation"
+ var fade_in_1: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade in").get('path', "")
+ var fade_in_2: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade cross").get('path', "")
+ var fade_in_3: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade out").get('path', "")
+
+ var is_any_fade_in_empty := fade_in_1.is_empty() or fade_in_2.is_empty() or fade_in_3.is_empty()
+ assert(is_any_fade_in_empty == false, "Fade In/Out animations are empty.")
+
+ var are_all_fade_in_equal := fade_in_1 == fade_in_2 and fade_in_2 == fade_in_3
+ assert(are_all_fade_in_equal == true, "Fade In/Out animations returned different paths.")
+
+
+## Test if invalid animation paths will return empty strings.
+func test_invalid_animation_path() -> void:
+ const TYPE := "PortraitAnimation"
+ var invalid_animation_1: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade i").get('path', "")
+ assert(invalid_animation_1.is_empty() == true, "Invalid animation 1's path is not empty.")
+
+
+ var invalid_animation_2: String = DialogicResourceUtil.guess_special_resource(TYPE, "fade").get('path', "")
+ assert(invalid_animation_2.is_empty() == true, "Invalid animation 2's path is not empty.")
+
+
+## Test if invalid types will return empty strings.
+func test_invalid_type_path() -> void:
+ const INVALID_TYPE := "Portait Animation"
+ var invalid_animation: String = DialogicResourceUtil.guess_special_resource(INVALID_TYPE, "fade in").get('path', "")
+ assert(invalid_animation.is_empty() == true, "Invalid animation 1's path is not empty.")
+
+ const VALID_TYPE := "PortraitAnimation"
+ var valid_animation_path: String = DialogicResourceUtil.guess_special_resource(VALID_TYPE, "fade in").get('path', "")
+ assert(valid_animation_path.is_empty() == false, "Valids animation's path is empty.")
+
+ assert(not invalid_animation == valid_animation_path, "Valid and invalid animation paths are equal.")
+
diff --git a/addons/dialogic-main/Tests/Unit/subsystem_history_test.gd b/addons/dialogic-main/Tests/Unit/subsystem_history_test.gd
new file mode 100644
index 0000000..7b99abb
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/subsystem_history_test.gd
@@ -0,0 +1,51 @@
+extends GdUnitTestSuite
+
+var history := Dialogic.History
+
+const EXAMPLE_SEEN_HISTORY: Dictionary = {
+ "res://Dialogic/Timelines/start.dtl1": 1,
+ "res://Dialogic/Timelines/start.dtl2": 2,
+ "res://Dialogic/Timelines/start.dtl3": 3,
+ "res://Dialogic/Timelines/start.dtl4": 4,
+ "res://Dialogic/Timelines/start.dtl5": 5,
+ "res://Dialogic/Timelines/start.dtl7": 7
+}
+
+func test_save_load_visited() -> void:
+ assert(history.visited_event_history_content == {}, "Seen events should have be empty.")
+ history.load_visited_history()
+ assert(history.visited_event_history_content == {}, "Seen events should have be empty after empty load.")
+
+ history.visited_event_history_content = EXAMPLE_SEEN_HISTORY
+ assert(history.visited_event_history_content == EXAMPLE_SEEN_HISTORY, "Seen events should have be identical to test data.")
+
+ history.save_visited_history()
+ var global_data_seen_events: Dictionary = history.get_saved_visited_history()
+
+ assert(global_data_seen_events == EXAMPLE_SEEN_HISTORY, "Global data does not have example data.")
+
+ history.load_visited_history()
+ assert(history.visited_event_history_content == EXAMPLE_SEEN_HISTORY, "Seen events should have be identical to test data after load.")
+
+
+func test_deletion_save_visited() -> void:
+ history.visited_event_history_content = EXAMPLE_SEEN_HISTORY
+ assert(history.visited_event_history_content == EXAMPLE_SEEN_HISTORY, "Seen events should have be identical to test data.")
+
+ history.save_visited_history()
+ var global_data_seen_events: Dictionary = history.get_saved_visited_history()
+ assert(global_data_seen_events == EXAMPLE_SEEN_HISTORY, "Global data does not have example data.")
+
+ history.reset_visited_history(false)
+ var global_data_seen_events_after_reset: Dictionary = history.get_saved_visited_history()
+ assert(history.visited_event_history_content == EXAMPLE_SEEN_HISTORY, "Seen events are gone after global data only reset.")
+ assert(global_data_seen_events_after_reset == {}, "Global data should be empty after reset.")
+
+ history.load_visited_history()
+ assert(history.visited_event_history_content == {}, "Seen events should have be empty after empty load.")
+
+ history.visited_event_history_content = EXAMPLE_SEEN_HISTORY
+ history.reset_visited_history(true)
+ var global_data_seen_events_after_full_reset: Dictionary = history.get_saved_visited_history()
+ assert(global_data_seen_events_after_full_reset == {}, "Seen events in global data should have be empty after full data reset.")
+ assert(history.visited_event_history_content == {}, "Seen events in history should have be empty after full data reset.")
diff --git a/addons/dialogic-main/Tests/Unit/subsystem_text_test.gd b/addons/dialogic-main/Tests/Unit/subsystem_text_test.gd
new file mode 100644
index 0000000..8fd6b1e
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/subsystem_text_test.gd
@@ -0,0 +1,27 @@
+extends GdUnitTestSuite
+
+const VALID_SPEAKER_PATH := "res://Tests/Resources/unit_test_character.dch"
+
+## We ensure that missing a speaker will return null.
+func test_missing_current_speaker() -> void:
+ var null_speaker := DialogicUtil.autoload().Text.get_current_speaker()
+
+ assert(null_speaker == null, "Current speaker is not null.")
+
+
+## We ensure invalid speaker paths return the correct value.
+func test_set_invalid_current_speaker() -> void:
+ DialogicUtil.autoload().current_state_info["speaker"] = "Invalid Speaker Path"
+ var current_speaker := DialogicUtil.autoload().Text.get_current_speaker()
+
+ assert(current_speaker == null, "Invalid speaker must be invalid, but is valid.")
+
+
+## We ensure valid speaker paths return a valid [class DialogicCharacter] and
+## the path is set correctly.
+func test_set_valid_current_speaker() -> void:
+ DialogicUtil.autoload().current_state_info["speaker"] = VALID_SPEAKER_PATH
+ var current_speaker := DialogicUtil.autoload().Text.get_current_speaker()
+
+ assert(not current_speaker == null, "Valid speaker must be valid, but is invalid.")
+ assert(current_speaker.get_path() == VALID_SPEAKER_PATH, "Valid speaker path is not set correctly.")
diff --git a/addons/dialogic-main/Tests/Unit/test_auto_advance.gd b/addons/dialogic-main/Tests/Unit/test_auto_advance.gd
new file mode 100644
index 0000000..1acc978
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/test_auto_advance.gd
@@ -0,0 +1,19 @@
+extends GdUnitTestSuite
+
+## Ensure Auto-Advance is enabled properly using the user input flag.
+func test_enable_auto_advance() -> void:
+ Dialogic.Inputs.auto_advance.enabled_until_user_input = true
+ var is_enabled: bool = Dialogic.Inputs.auto_advance.is_enabled()
+
+ assert(is_enabled == true, "Auto-Advance is not enabled.")
+
+
+## This test was created to ensure a bug was fixed:
+## When the user enabled the Auto-Advance until user input,
+## the Auto-Advance would still run after the user input.
+func test_disable_auto_advance() -> void:
+ Dialogic.Inputs.auto_advance.enabled_until_user_input = true
+ Dialogic.Inputs.handle_input()
+
+ var is_enabled: bool = Dialogic.Inputs.auto_advance.is_enabled()
+ assert(is_enabled == false, "Auto-Advance is still running after input")
diff --git a/addons/dialogic-main/Tests/Unit/test_example.gd b/addons/dialogic-main/Tests/Unit/test_example.gd
new file mode 100644
index 0000000..cc827f1
--- /dev/null
+++ b/addons/dialogic-main/Tests/Unit/test_example.gd
@@ -0,0 +1,9 @@
+class_name GdUnitExampleTest
+extends GdUnitTestSuite
+
+func test_example() -> void:
+ const EXAMPLE_STRING := "Dialogic!"
+
+ assert_str(EXAMPLE_STRING)\
+ .has_length(EXAMPLE_STRING.length())\
+ .starts_with("Dia")
diff --git a/addons/dialogic-main/addons/dialogic/Core/DialogicGameHandler.gd b/addons/dialogic-main/addons/dialogic/Core/DialogicGameHandler.gd
new file mode 100644
index 0000000..43c2557
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Core/DialogicGameHandler.gd
@@ -0,0 +1,433 @@
+class_name DialogicGameHandler
+extends Node
+
+## Class that is used as the Dialogic autoload.
+
+## Autoload script that allows you to interact with all of Dialogic's systems:[br]
+## - Holds all important information about the current state of Dialogic.[br]
+## - Provides access to all the subsystems.[br]
+## - Has methods to start/end timelines.[br]
+
+
+## States indicating different phases of dialog.
+enum States {
+ IDLE, ## Dialogic is awaiting input to advance.
+ REVEALING_TEXT, ## Dialogic is currently revealing text.
+ ANIMATING, ## Some animation is happening.
+ AWAITING_CHOICE, ## Dialogic awaits the selection of a choice
+ WAITING ## Dialogic is currently awaiting something.
+ }
+
+## Flags indicating what to clear when calling [method clear].
+enum ClearFlags {
+ FULL_CLEAR = 0, ## Clears all subsystems
+ KEEP_VARIABLES = 1, ## Clears all subsystems and info except for variables
+ TIMELINE_INFO_ONLY = 2 ## Doesn't clear subsystems but current timeline and index
+ }
+
+## Reference to the currently executed timeline.
+var current_timeline: DialogicTimeline = null
+## Copy of the [member current_timeline]'s events.
+var current_timeline_events: Array = []
+
+## Index of the event the timeline handling is currently at.
+var current_event_idx: int = 0
+## Contains all information that subsystems consider relevant for
+## the current situation
+var current_state_info: Dictionary = {}
+
+## Current state (see [member States] enum).
+var current_state := States.IDLE:
+ get:
+ return current_state
+
+ set(new_state):
+ current_state = new_state
+ state_changed.emit(new_state)
+
+## Emitted when [member current_state] change.
+signal state_changed(new_state:States)
+
+## When `true`, many dialogic processes won't continue until it's `false` again.
+var paused := false:
+ set(value):
+ paused = value
+
+ if paused:
+
+ for subsystem in get_children():
+
+ if subsystem is DialogicSubsystem:
+ (subsystem as DialogicSubsystem).pause()
+
+ dialogic_paused.emit()
+
+ else:
+ for subsystem in get_children():
+
+ if subsystem is DialogicSubsystem:
+ (subsystem as DialogicSubsystem).resume()
+
+ dialogic_resumed.emit()
+
+## Emitted when [member paused] changes to `true`.
+signal dialogic_paused
+## Emitted when [member paused] changes to `false`.
+signal dialogic_resumed
+
+
+## Emitted when the timeline ends.
+## This can be a timeline ending or [method end_timeline] being called.
+signal timeline_ended
+## Emitted when a timeline starts by calling either [method start]
+## or [method start_timeline].
+signal timeline_started
+## Emitted when an event starts being executed.
+## The event may not have finished executing yet.
+signal event_handled(resource: DialogicEvent)
+
+## Emitted when a [class SignalEvent] event was reached.
+signal signal_event(argument: Variant)
+## Emitted when a signal event gets fired from a [class TextEvent] event.
+signal text_signal(argument: String)
+
+
+# Careful, this section is repopulated automatically at certain moments.
+#region SUBSYSTEMS
+
+var Audio := preload("res://addons/dialogic/Modules/Audio/subsystem_audio.gd").new():
+ get: return get_subsystem("Audio")
+
+var Backgrounds := preload("res://addons/dialogic/Modules/Background/subsystem_backgrounds.gd").new():
+ get: return get_subsystem("Backgrounds")
+
+var Portraits := preload("res://addons/dialogic/Modules/Character/subsystem_portraits.gd").new():
+ get: return get_subsystem("Portraits")
+
+var PortraitContainers := preload("res://addons/dialogic/Modules/Character/subsystem_containers.gd").new():
+ get: return get_subsystem("PortraitContainers")
+
+var Choices := preload("res://addons/dialogic/Modules/Choice/subsystem_choices.gd").new():
+ get: return get_subsystem("Choices")
+
+var Expressions := preload("res://addons/dialogic/Modules/Core/subsystem_expression.gd").new():
+ get: return get_subsystem("Expressions")
+
+var Animations := preload("res://addons/dialogic/Modules/Core/subsystem_animation.gd").new():
+ get: return get_subsystem("Animations")
+
+var Inputs := preload("res://addons/dialogic/Modules/Core/subsystem_input.gd").new():
+ get: return get_subsystem("Inputs")
+
+var Glossary := preload("res://addons/dialogic/Modules/Glossary/subsystem_glossary.gd").new():
+ get: return get_subsystem("Glossary")
+
+var History := preload("res://addons/dialogic/Modules/History/subsystem_history.gd").new():
+ get: return get_subsystem("History")
+
+var Jump := preload("res://addons/dialogic/Modules/Jump/subsystem_jump.gd").new():
+ get: return get_subsystem("Jump")
+
+var Save := preload("res://addons/dialogic/Modules/Save/subsystem_save.gd").new():
+ get: return get_subsystem("Save")
+
+var Settings := preload("res://addons/dialogic/Modules/Settings/subsystem_settings.gd").new():
+ get: return get_subsystem("Settings")
+
+var Styles := preload("res://addons/dialogic/Modules/Style/subsystem_styles.gd").new():
+ get: return get_subsystem("Styles")
+
+var Text := preload("res://addons/dialogic/Modules/Text/subsystem_text.gd").new():
+ get: return get_subsystem("Text")
+
+var TextInput := preload("res://addons/dialogic/Modules/TextInput/subsystem_text_input.gd").new():
+ get: return get_subsystem("TextInput")
+
+var VAR := preload("res://addons/dialogic/Modules/Variable/subsystem_variables.gd").new():
+ get: return get_subsystem("VAR")
+
+var Voice := preload("res://addons/dialogic/Modules/Voice/subsystem_voice.gd").new():
+ get: return get_subsystem("Voice")
+
+#endregion
+
+
+## Autoloads are added first, so this happens REALLY early on game startup.
+func _ready() -> void:
+ _collect_subsystems()
+
+ clear()
+
+
+#region TIMELINE & EVENT HANDLING
+################################################################################
+
+## Method to start a timeline AND ensure that a layout scene is present.
+## For argument info, checkout [method start_timeline].
+## -> returns the layout node
+func start(timeline:Variant, label:Variant="") -> Node:
+ # If we don't have a style subsystem, default to just start_timeline()
+ if not has_subsystem('Styles'):
+ printerr("[Dialogic] You called Dialogic.start() but the Styles subsystem is missing!")
+ clear(ClearFlags.KEEP_VARIABLES)
+ start_timeline(timeline, label)
+ return null
+
+ # Otherwise make sure there is a style active.
+ var scene: Node = null
+ if !self.Styles.has_active_layout_node():
+ scene = self.Styles.load_style()
+ else:
+ scene = self.Styles.get_layout_node()
+ scene.show()
+
+ if not scene.is_node_ready():
+ scene.ready.connect(clear.bind(ClearFlags.KEEP_VARIABLES))
+ scene.ready.connect(start_timeline.bind(timeline, label))
+ else:
+ start_timeline(timeline, label)
+
+ return scene
+
+
+## Method to start a timeline without adding a layout scene.
+## @timeline can be either a loaded timeline resource or a path to a timeline file.
+## @label_or_idx can be a label (string) or index (int) to skip to immediatly.
+func start_timeline(timeline:Variant, label_or_idx:Variant = "") -> void:
+ # load the resource if only the path is given
+ if typeof(timeline) == TYPE_STRING:
+ #check the lookup table if it's not a full file name
+ if (timeline as String).contains("res://"):
+ timeline = load((timeline as String))
+ else:
+ timeline = DialogicResourceUtil.get_timeline_resource((timeline as String))
+
+ if timeline == null:
+ printerr("[Dialogic] There was an error loading this timeline. Check the filename, and the timeline for errors")
+ return
+
+ (timeline as DialogicTimeline).process()
+
+ current_timeline = timeline
+ current_timeline_events = current_timeline.events
+ for event in current_timeline_events:
+ event.dialogic = self
+ current_event_idx = -1
+
+ if typeof(label_or_idx) == TYPE_STRING:
+ if label_or_idx:
+ if has_subsystem('Jump'):
+ Jump.jump_to_label((label_or_idx as String))
+ elif typeof(label_or_idx) == TYPE_INT:
+ if label_or_idx >-1:
+ current_event_idx = label_or_idx -1
+
+ timeline_started.emit()
+ handle_next_event()
+
+
+## Preloader function, prepares a timeline and returns an object to hold for later
+## [param timeline_resource] can be either a path (string) or a loaded timeline (resource)
+func preload_timeline(timeline_resource:Variant) -> Variant:
+ # I think ideally this should be on a new thread, will test
+ if typeof(timeline_resource) == TYPE_STRING:
+ timeline_resource = load((timeline_resource as String))
+ if timeline_resource == null:
+ printerr("[Dialogic] There was an error preloading this timeline. Check the filename, and the timeline for errors")
+ return null
+
+ (timeline_resource as DialogicTimeline).process()
+
+ return timeline_resource
+
+
+## Clears and stops the current timeline.
+func end_timeline() -> void:
+ await clear(ClearFlags.TIMELINE_INFO_ONLY)
+ _on_timeline_ended()
+ timeline_ended.emit()
+
+
+## Handles the next event.
+func handle_next_event(_ignore_argument: Variant = "") -> void:
+ handle_event(current_event_idx+1)
+
+
+## Handles the event at the given index [param event_index].
+## You can call this manually, but if another event is still executing, it might have unexpected results.
+func handle_event(event_index:int) -> void:
+ if not current_timeline:
+ return
+
+ _cleanup_previous_event()
+
+ if paused:
+ await dialogic_resumed
+
+ if event_index >= len(current_timeline_events):
+ end_timeline()
+ return
+
+ #actually process the event now, since we didnt earlier at runtime
+ #this needs to happen before we create the copy DialogicEvent variable, so it doesn't throw an error if not ready
+ if current_timeline_events[event_index].event_node_ready == false:
+ current_timeline_events[event_index]._load_from_string(current_timeline_events[event_index].event_node_as_text)
+
+ current_event_idx = event_index
+
+ if not current_timeline_events[event_index].event_finished.is_connected(handle_next_event):
+ current_timeline_events[event_index].event_finished.connect(handle_next_event)
+
+ set_meta('previous_event', current_timeline_events[event_index])
+
+ current_timeline_events[event_index].execute(self)
+ event_handled.emit(current_timeline_events[event_index])
+
+
+## Resets Dialogic's state fully or partially.
+## By using the clear flags from the [member ClearFlags] enum you can specify
+## what info should be kept.
+## For example, at timeline end usually it doesn't clear node or subsystem info.
+func clear(clear_flags := ClearFlags.FULL_CLEAR) -> void:
+ _cleanup_previous_event()
+
+ if !clear_flags & ClearFlags.TIMELINE_INFO_ONLY:
+ for subsystem in get_children():
+ if subsystem is DialogicSubsystem:
+ (subsystem as DialogicSubsystem).clear_game_state(clear_flags)
+
+ var timeline := current_timeline
+
+ current_timeline = null
+ current_event_idx = -1
+ current_timeline_events = []
+ current_state = States.IDLE
+
+ # Resetting variables
+ if timeline:
+ await timeline.clean()
+
+
+## Cleanup after previous event (if any).
+func _cleanup_previous_event():
+ if has_meta('previous_event') and get_meta('previous_event') is DialogicEvent:
+ var event := get_meta('previous_event') as DialogicEvent
+ if event.event_finished.is_connected(handle_next_event):
+ event.event_finished.disconnect(handle_next_event)
+ event._clear_state()
+ remove_meta("previous_event")
+
+#endregion
+
+
+#region SAVING & LOADING
+################################################################################
+
+## Returns a dictionary containing all necessary information to later recreate the same state with load_full_state.
+## The [subsystem Save] subsystem might be more useful for you.
+## However, this can be used to integrate the info into your own save system.
+func get_full_state() -> Dictionary:
+ if current_timeline:
+ current_state_info['current_event_idx'] = current_event_idx
+ current_state_info['current_timeline'] = current_timeline.resource_path
+ else:
+ current_state_info['current_event_idx'] = -1
+ current_state_info['current_timeline'] = null
+
+ for subsystem in get_children():
+ (subsystem as DialogicSubsystem).save_game_state()
+
+ return current_state_info.duplicate(true)
+
+
+## This method tries to load the state from the given [param state_info].
+## Will automatically start a timeline and add a layout if a timeline was running when
+## the dictionary was retrieved with [method get_full_state].
+func load_full_state(state_info:Dictionary) -> void:
+ clear()
+ current_state_info = state_info
+ ## The Style subsystem needs to run first for others to load correctly.
+ var scene: Node = null
+ if has_subsystem('Styles'):
+ get_subsystem('Styles').load_game_state()
+ scene = self.Styles.get_layout_node()
+
+ var load_subsystems := func() -> void:
+ for subsystem in get_children():
+ if subsystem.name == 'Styles':
+ continue
+ (subsystem as DialogicSubsystem).load_game_state()
+
+ if null != scene and not scene.is_node_ready():
+ scene.ready.connect(load_subsystems)
+ else:
+ await get_tree().process_frame
+ load_subsystems.call()
+
+ if current_state_info.get('current_timeline', null):
+ start_timeline(current_state_info.current_timeline, current_state_info.get('current_event_idx', 0))
+ else:
+ end_timeline.call_deferred()
+#endregion
+
+
+#region SUB-SYTSEMS
+################################################################################
+
+func _collect_subsystems() -> void:
+ var subsystem_nodes := [] as Array[DialogicSubsystem]
+ for indexer in DialogicUtil.get_indexers():
+ for subsystem in indexer._get_subsystems():
+ var subsystem_node := add_subsystem(str(subsystem.name), str(subsystem.script))
+ subsystem_nodes.push_back(subsystem_node)
+
+ for subsystem in subsystem_nodes:
+ subsystem.post_install()
+
+
+## Returns `true` if a subystem with the given [param subsystem_name] exists.
+func has_subsystem(subsystem_name:String) -> bool:
+ return has_node(subsystem_name)
+
+
+## Returns the subsystem node of the given [param subsystem_name] or null if it doesn't exist.
+func get_subsystem(subsystem_name:String) -> DialogicSubsystem:
+ return get_node(subsystem_name)
+
+
+## Adds a subsystem node with the given [param subsystem_name] and [param script_path].
+func add_subsystem(subsystem_name:String, script_path:String) -> DialogicSubsystem:
+ var node: Node = Node.new()
+ node.name = subsystem_name
+ node.set_script(load(script_path))
+ node = node as DialogicSubsystem
+ node.dialogic = self
+ add_child(node)
+ return node
+
+
+#endregion
+
+
+#region HELPERS
+################################################################################
+
+## This handles the `Layout End Behaviour` setting that can be changed in the Dialogic settings.
+func _on_timeline_ended() -> void:
+ if self.Styles.has_active_layout_node() and self.Styles.get_layout_node().is_inside_tree():
+ match ProjectSettings.get_setting('dialogic/layout/end_behaviour', 0):
+ 0:
+ self.Styles.get_layout_node().get_parent().remove_child(self.Styles.get_layout_node())
+ self.Styles.get_layout_node().queue_free()
+ 1:
+ @warning_ignore("unsafe_method_access")
+ self.Styles.get_layout_node().hide()
+
+
+func print_debug_moment() -> void:
+ if not current_timeline:
+ return
+
+ printerr("\tAt event ", current_event_idx+1, " (",current_timeline_events[current_event_idx].event_name, ' Event) in timeline "', DialogicResourceUtil.get_unique_identifier(current_timeline.resource_path), '" (',current_timeline.resource_path,').')
+ print("\n")
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Core/DialogicResourceUtil.gd b/addons/dialogic-main/addons/dialogic/Core/DialogicResourceUtil.gd
new file mode 100644
index 0000000..a86356c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Core/DialogicResourceUtil.gd
@@ -0,0 +1,291 @@
+@tool
+class_name DialogicResourceUtil
+
+static var label_cache := {}
+static var event_cache: Array[DialogicEvent] = []
+
+static var special_resources := {}
+
+
+static func update() -> void:
+ update_directory('.dch')
+ update_directory('.dtl')
+ update_label_cache()
+
+
+#region RESOURCE DIRECTORIES
+################################################################################
+
+static func get_directory(extension:String) -> Dictionary:
+ extension = extension.trim_prefix('.')
+ if Engine.has_meta(extension+'_directory'):
+ return Engine.get_meta(extension+'_directory', {})
+
+ var directory: Dictionary = ProjectSettings.get_setting("dialogic/directories/"+extension+'_directory', {})
+ Engine.set_meta(extension+'_directory', directory)
+ return directory
+
+
+static func set_directory(extension:String, directory:Dictionary) -> void:
+ extension = extension.trim_prefix('.')
+ if Engine.is_editor_hint():
+ ProjectSettings.set_setting("dialogic/directories/"+extension+'_directory', directory)
+ ProjectSettings.save()
+ Engine.set_meta(extension+'_directory', directory)
+
+
+static func update_directory(extension:String) -> void:
+ var directory := get_directory(extension)
+
+ for resource in list_resources_of_type(extension):
+ if not resource in directory.values():
+ directory = add_resource_to_directory(resource, directory)
+
+ var keys_to_remove := []
+ for key in directory:
+ if not ResourceLoader.exists(directory[key]):
+ keys_to_remove.append(key)
+ for key in keys_to_remove:
+ directory.erase(key)
+
+ set_directory(extension, directory)
+
+
+static func add_resource_to_directory(file_path:String, directory:Dictionary) -> Dictionary:
+ var suggested_name := file_path.get_file().trim_suffix("."+file_path.get_extension())
+ while suggested_name in directory:
+ suggested_name = file_path.trim_suffix("/"+suggested_name+"."+file_path.get_extension()).get_file().path_join(suggested_name)
+ directory[suggested_name] = file_path
+ return directory
+
+
+## Returns the unique identifier for the given resource path.
+## Returns an empty string if no identifier was found.
+static func get_unique_identifier(file_path:String) -> String:
+ var identifier: String = get_directory(file_path.get_extension()).find_key(file_path)
+ if typeof(identifier) == TYPE_STRING:
+ return identifier
+ return ""
+
+
+## Returns the resource associated with the given unique identifier.
+## The expected extension is needed to use the right directory.
+static func get_resource_from_identifier(identifier:String, extension:String) -> Resource:
+ var path: String = get_directory(extension).get(identifier, '')
+ if ResourceLoader.exists(path):
+ return load(path)
+ return null
+
+
+static func change_unique_identifier(file_path:String, new_identifier:String) -> void:
+ var directory := get_directory(file_path.get_extension())
+ var key: String = directory.find_key(file_path)
+ while key != null:
+ if key == new_identifier:
+ break
+ directory.erase(key)
+ directory[new_identifier] = file_path
+ key = directory.find_key(file_path)
+ set_directory(file_path.get_extension(), directory)
+
+
+static func change_resource_path(old_path:String, new_path:String) -> void:
+ var directory := get_directory(new_path.get_extension())
+ var key: String = directory.find_key(old_path)
+ while key != null:
+ directory[key] = new_path
+ key = directory.find_key(old_path)
+ set_directory(new_path.get_extension(), directory)
+
+
+static func remove_resource(file_path:String) -> void:
+ var directory := get_directory(file_path.get_extension())
+ var key: String = directory.find_key(file_path)
+ while key != null:
+ directory.erase(key)
+ key = directory.find_key(file_path)
+ set_directory(file_path.get_extension(), directory)
+
+
+static func is_identifier_unused(extension:String, identifier:String) -> bool:
+ return not identifier in get_directory(extension)
+
+#endregion
+
+#region LABEL CACHE
+################################################################################
+# The label cache is only for the editor so we don't have to scan all timelines
+# whenever we want to suggest labels. This has no use in game and is not always perfect.
+
+static func get_label_cache() -> Dictionary:
+ if not label_cache.is_empty():
+ return label_cache
+
+ label_cache = DialogicUtil.get_editor_setting('label_ref', {})
+ return label_cache
+
+
+static func set_label_cache(cache:Dictionary) -> void:
+ label_cache = cache
+
+
+static func update_label_cache() -> void:
+ var cache := get_label_cache()
+ var timelines := get_timeline_directory().values()
+ for timeline in cache:
+ if !timeline in timelines:
+ cache.erase(timeline)
+ set_label_cache(cache)
+
+#endregion
+
+#region EVENT CACHE
+################################################################################
+
+## Dialogic keeps a list that has each event once. This allows retrieval of that list.
+static func get_event_cache() -> Array:
+ if not event_cache.is_empty():
+ return event_cache
+
+ event_cache = update_event_cache()
+ return event_cache
+
+
+static func update_event_cache() -> Array:
+ event_cache = []
+ for indexer in DialogicUtil.get_indexers():
+ # build event cache
+ for event in indexer._get_events():
+ if not ResourceLoader.exists(event):
+ continue
+ if not 'event_end_branch.gd' in event and not 'event_text.gd' in event:
+ event_cache.append(load(event).new())
+
+ # Events are checked in order while testing them. EndBranch needs to be first, Text needs to be last
+ event_cache.push_front(DialogicEndBranchEvent.new())
+ event_cache.push_back(DialogicTextEvent.new())
+
+ return event_cache
+
+#endregion
+
+#region SPECIAL RESOURCES
+################################################################################
+
+static func update_special_resources() -> void:
+ special_resources.clear()
+ for indexer in DialogicUtil.get_indexers():
+ var additions := indexer._get_special_resources()
+ for resource_type in additions:
+ if not resource_type in special_resources:
+ special_resources[resource_type] = {}
+ special_resources[resource_type].merge(additions[resource_type])
+
+
+static func list_special_resources(type:String, filter := {}) -> Dictionary:
+ if special_resources.is_empty():
+ update_special_resources()
+ if type in special_resources:
+ if filter.is_empty():
+ return special_resources[type]
+ else:
+ var results := {}
+ for i in special_resources[type]:
+ if match_resource_filter(special_resources[type][i], filter):
+ results[i] = special_resources[type][i]
+ return results
+ return {}
+
+
+static func match_resource_filter(dict:Dictionary, filter:Dictionary) -> bool:
+ for i in filter:
+ if not i in dict:
+ return false
+ if typeof(filter[i]) == TYPE_ARRAY:
+ if not dict[i] in filter[i]:
+ return false
+ else:
+ if not dict[i] == filter[i]:
+ return false
+ return true
+
+
+static func guess_special_resource(type: String, string: String, default := {}, filter := {}, ignores:PackedStringArray=[]) -> Dictionary:
+ if string.is_empty():
+ return default
+
+ if special_resources.is_empty():
+ update_special_resources()
+ var resources := list_special_resources(type, filter)
+ if resources.is_empty():
+ printerr("[Dialogic] No ", type, "s found, but attempted to use one.")
+ return default
+
+ if string.begins_with('res://'):
+ for i in resources.values():
+ if i.path == string:
+ return i
+ printerr("[Dialogic] Unable to find ", type, " at path '", string, "'.")
+ return default
+
+ string = string.to_lower()
+
+ if string in resources:
+ return resources[string]
+
+ if not ignores.is_empty():
+ var regex := RegEx.create_from_string(r" ?\b(" + "|".join(ignores) + r")\b")
+ for name in resources:
+ if regex.sub(name, "") == regex.sub(string, ""):
+ return resources[name]
+
+ ## As a last effort check against the unfiltered list
+ if string in special_resources[type]:
+ push_warning("[Dialogic] Using ", type, " '", string,"' when not supposed to.")
+ return special_resources[type][string]
+
+ printerr("[Dialogic] Unable to identify ", type, " based on string '", string, "'.")
+ return default
+
+#endregion
+
+#region HELPERS
+################################################################################
+
+static func get_character_directory() -> Dictionary:
+ return get_directory('dch')
+
+
+static func get_timeline_directory() -> Dictionary:
+ return get_directory('dtl')
+
+
+static func get_timeline_resource(timeline_identifier:String) -> DialogicTimeline:
+ return get_resource_from_identifier(timeline_identifier, 'dtl')
+
+
+static func get_character_resource(character_identifier:String) -> DialogicCharacter:
+ return get_resource_from_identifier(character_identifier, 'dch')
+
+
+static func list_resources_of_type(extension:String) -> Array:
+ var all_resources := scan_folder('res://', extension)
+ return all_resources
+
+
+static func scan_folder(path:String, extension:String) -> Array:
+ var list: Array = []
+ if DirAccess.dir_exists_absolute(path):
+ var dir := DirAccess.open(path)
+ dir.list_dir_begin()
+ var file_name := dir.get_next()
+ while file_name != "":
+ if dir.current_is_dir() and not file_name.begins_with("."):
+ list += scan_folder(path.path_join(file_name), extension)
+ else:
+ if file_name.ends_with(extension):
+ list.append(path.path_join(file_name))
+ file_name = dir.get_next()
+ return list
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Core/DialogicUtil.gd b/addons/dialogic-main/addons/dialogic/Core/DialogicUtil.gd
new file mode 100644
index 0000000..1b8d6c1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Core/DialogicUtil.gd
@@ -0,0 +1,676 @@
+@tool
+class_name DialogicUtil
+
+## Script that container helper methods for both editor and game execution.
+## Used whenever the same thing is needed in different parts of the plugin.
+
+#region EDITOR
+
+## This method should be used instead of EditorInterface.get_editor_scale(), because if you use that
+## it will run perfectly fine from the editor, but crash when the game is exported.
+static func get_editor_scale() -> float:
+ return get_dialogic_plugin().get_editor_interface().get_editor_scale()
+
+
+## Although this does in fact always return a EditorPlugin node,
+## that class is apparently not present in export and referencing it here creates a crash.
+static func get_dialogic_plugin() -> Node:
+ for child in Engine.get_main_loop().get_root().get_children():
+ if child.get_class() == "EditorNode":
+ return child.get_node('DialogicPlugin')
+ return null
+
+#endregion
+
+
+## Returns the autoload when in-game.
+static func autoload() -> DialogicGameHandler:
+ if Engine.is_editor_hint():
+ return null
+ if not Engine.get_main_loop().root.has_node("Dialogic"):
+ return null
+ return Engine.get_main_loop().root.get_node("Dialogic")
+
+
+#region FILE SYSTEM
+################################################################################
+static func listdir(path: String, files_only:= true, _throw_error:= true, full_file_path:= false, include_imports := false) -> Array:
+ var files: Array = []
+ if path.is_empty(): path = "res://"
+ if DirAccess.dir_exists_absolute(path):
+ var dir := DirAccess.open(path)
+ dir.list_dir_begin()
+ var file_name := dir.get_next()
+ while file_name != "":
+ if not file_name.begins_with("."):
+ if files_only:
+ if not dir.current_is_dir() and (not file_name.ends_with('.import') or include_imports):
+ if full_file_path:
+ files.append(path.path_join(file_name))
+ else:
+ files.append(file_name)
+ else:
+ if full_file_path:
+ files.append(path.path_join(file_name))
+ else:
+ files.append(file_name)
+ file_name = dir.get_next()
+ dir.list_dir_end()
+ return files
+
+
+static func get_module_path(name:String, builtin:=true) -> String:
+ if builtin:
+ return "res://addons/dialogic/Modules".path_join(name)
+ else:
+ return ProjectSettings.get_setting('dialogic/extensions_folder', 'res://addons/dialogic_additions').path_join(name)
+
+
+## This is a private and editor-only function.
+##
+## Populates the [class DialogicGameHandler] with new custom subsystems by
+## directly manipulating the file's content and then importing the file.
+static func _update_autoload_subsystem_access() -> void:
+ if not Engine.is_editor_hint():
+ printerr("[Dialogic] This function is only available in the editor.")
+ return
+
+ var script: Script = load("res://addons/dialogic/Core/DialogicGameHandler.gd")
+ var new_subsystem_access_list := "#region SUBSYSTEMS\n"
+
+ for indexer: DialogicIndexer in get_indexers(true, true):
+
+ for subsystem: Dictionary in indexer._get_subsystems().duplicate(true):
+ new_subsystem_access_list += '\nvar {name} := preload("{script}").new():\n\tget: return get_subsystem("{name}")\n'.format(subsystem)
+
+ new_subsystem_access_list += "\n#endregion"
+ script.source_code = RegEx.create_from_string(r"#region SUBSYSTEMS\n#*\n((?!#endregion)(.*\n))*#endregion").sub(script.source_code, new_subsystem_access_list)
+ ResourceSaver.save(script)
+ Engine.get_singleton("EditorInterface").get_resource_filesystem().reimport_files(["res://addons/dialogic/Core/DialogicGameHandler.gd"])
+
+
+static func get_indexers(include_custom := true, force_reload := false) -> Array[DialogicIndexer]:
+ if Engine.get_main_loop().has_meta('dialogic_indexers') and !force_reload:
+ return Engine.get_main_loop().get_meta('dialogic_indexers')
+
+ var indexers: Array[DialogicIndexer] = []
+
+ for file in listdir(DialogicUtil.get_module_path(''), false):
+ var possible_script: String = DialogicUtil.get_module_path(file).path_join("index.gd")
+ if ResourceLoader.exists(possible_script):
+ indexers.append(load(possible_script).new())
+
+ if include_custom:
+ var extensions_folder: String = ProjectSettings.get_setting('dialogic/extensions_folder', "res://addons/dialogic_additions/")
+ for file in listdir(extensions_folder, false, false):
+ var possible_script: String = extensions_folder.path_join(file + "/index.gd")
+ if ResourceLoader.exists(possible_script):
+ indexers.append(load(possible_script).new())
+
+ Engine.get_main_loop().set_meta('dialogic_indexers', indexers)
+ return indexers
+
+
+
+## Turns a [param file_path] from `some_file.png` to `Some File`.
+static func pretty_name(file_path: String) -> String:
+ var _name := file_path.get_file().trim_suffix("." + file_path.get_extension())
+ _name = _name.replace('_', ' ')
+ _name = _name.capitalize()
+
+ return _name
+
+#endregion
+
+
+#region EDITOR SETTINGS & COLORS
+################################################################################
+
+static func set_editor_setting(setting:String, value:Variant) -> void:
+ var cfg := ConfigFile.new()
+ if FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
+ cfg.load('user://dialogic/editor_settings.cfg')
+
+ cfg.set_value('DES', setting, value)
+
+ if !DirAccess.dir_exists_absolute('user://dialogic'):
+ DirAccess.make_dir_absolute('user://dialogic')
+ cfg.save('user://dialogic/editor_settings.cfg')
+
+
+static func get_editor_setting(setting:String, default:Variant=null) -> Variant:
+ var cfg := ConfigFile.new()
+ if !FileAccess.file_exists('user://dialogic/editor_settings.cfg'):
+ return default
+
+ if !cfg.load('user://dialogic/editor_settings.cfg') == OK:
+ return default
+
+ return cfg.get_value('DES', setting, default)
+
+
+static func get_color_palette(default:bool = false) -> Dictionary:
+ var defaults := {
+ 'Color1': Color('#3b8bf2'), # Blue
+ 'Color2': Color('#00b15f'), # Green
+ 'Color3': Color('#e868e2'), # Pink
+ 'Color4': Color('#9468e8'), # Purple
+ 'Color5': Color('#574fb0'), # DarkPurple
+ 'Color6': Color('#1fa3a3'), # Aquamarine
+ 'Color7': Color('#fa952a'), # Orange
+ 'Color8': Color('#de5c5c'), # Red
+ 'Color9': Color('#7c7c7c'), # Gray
+ }
+ if default:
+ return defaults
+ return get_editor_setting('color_palette', defaults)
+
+
+static func get_color(value:String) -> Color:
+ var colors := get_color_palette()
+ return colors[value]
+
+#endregion
+
+
+#region TIMER PROCESS MODE
+################################################################################
+static func is_physics_timer() -> bool:
+ return ProjectSettings.get_setting('dialogic/timer/process_in_physics', false)
+
+
+static func update_timer_process_callback(timer:Timer) -> void:
+ timer.process_callback = Timer.TIMER_PROCESS_PHYSICS if is_physics_timer() else Timer.TIMER_PROCESS_IDLE
+
+#endregion
+
+
+#region MULTITWEEN
+################################################################################
+static func multitween(tweened_value:Variant, item:Node, property:String, part:String) -> void:
+ var parts: Dictionary = item.get_meta(property+'_parts', {})
+ parts[part] = tweened_value
+
+ if not item.has_meta(property+'_base_value') and not 'base' in parts:
+ item.set_meta(property+'_base_value', item.get(property))
+
+ var final_value: Variant = parts.get('base', item.get_meta(property+'_base_value', item.get(property)))
+
+ for key in parts:
+ if key == 'base':
+ continue
+ else:
+ final_value += parts[key]
+
+ item.set(property, final_value)
+ item.set_meta(property+'_parts', parts)
+
+#endregion
+
+
+#region TRANSLATIONS
+################################################################################
+
+static func get_next_translation_id() -> String:
+ ProjectSettings.set_setting('dialogic/translation/id_counter', ProjectSettings.get_setting('dialogic/translation/id_counter', 16)+1)
+ return '%x' % ProjectSettings.get_setting('dialogic/translation/id_counter', 16)
+
+#endregion
+
+
+#region VARIABLES
+################################################################################
+
+enum VarTypes {ANY, STRING, FLOAT, INT, BOOL}
+
+
+static func get_default_variables() -> Dictionary:
+ return ProjectSettings.get_setting('dialogic/variables', {})
+
+
+# helper that converts a nested variable dictionary into an array with paths
+static func list_variables(dict:Dictionary, path := "", type:=VarTypes.ANY) -> Array:
+ var array := []
+ for key in dict.keys():
+ if typeof(dict[key]) == TYPE_DICTIONARY:
+ array.append_array(list_variables(dict[key], path+key+".", type))
+ else:
+ if type == VarTypes.ANY or get_variable_value_type(dict[key]) == type:
+ array.append(path+key)
+ return array
+
+
+static func get_variable_value_type(value:Variant) -> VarTypes:
+ match typeof(value):
+ TYPE_STRING:
+ return VarTypes.STRING
+ TYPE_FLOAT:
+ return VarTypes.FLOAT
+ TYPE_INT:
+ return VarTypes.INT
+ TYPE_BOOL:
+ return VarTypes.BOOL
+ return VarTypes.ANY
+
+
+static func get_variable_type(path:String, dict:Dictionary={}) -> VarTypes:
+ if dict.is_empty():
+ dict = get_default_variables()
+ return get_variable_value_type(_get_value_in_dictionary(path, dict))
+
+
+## This will set a value in a dictionary (or a sub-dictionary based on the path)
+## e.g. it could set "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
+static func _set_value_in_dictionary(path:String, dictionary:Dictionary, value):
+ if '.' in path:
+ var from := path.split('.')[0]
+ if from in dictionary.keys():
+ dictionary[from] = _set_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], value)
+ else:
+ if path in dictionary.keys():
+ dictionary[path] = value
+ return dictionary
+
+
+## This will get a value in a dictionary (or a sub-dictionary based on the path)
+## e.g. it could get "Something.Something.Something" in {'Something':{'Something':{'Someting':"value"}}}
+static func _get_value_in_dictionary(path:String, dictionary:Dictionary, default= null) -> Variant:
+ if '.' in path:
+ var from := path.split('.')[0]
+ if from in dictionary.keys():
+ return _get_value_in_dictionary(path.trim_prefix(from+"."), dictionary[from], default)
+ else:
+ if path in dictionary.keys():
+ return dictionary[path]
+ return default
+
+#endregion
+
+
+
+#region STYLES
+################################################################################
+
+static func get_default_layout_base() -> PackedScene:
+ return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Base_Default/default_layout_base.tscn"))
+
+
+static func get_fallback_style() -> DialogicStyle:
+ return load(DialogicUtil.get_module_path('DefaultLayoutParts').path_join("Style_VN_Default/default_vn_style.tres"))
+
+
+static func get_default_style() -> DialogicStyle:
+ var default: String = ProjectSettings.get_setting('dialogic/layout/default_style', '')
+ if !ResourceLoader.exists(default):
+ return get_fallback_style()
+ return load(default)
+
+
+static func get_style_by_name(name:String) -> DialogicStyle:
+ if name.is_empty():
+ return get_default_style()
+
+ var styles: Array = ProjectSettings.get_setting('dialogic/layout/style_list', [])
+ for style in styles:
+ if not ResourceLoader.exists(style):
+ continue
+ if load(style).name == name:
+ return load(style)
+
+ return get_default_style()
+#endregion
+
+
+#region SCENE EXPORT OVERRIDES
+################################################################################
+
+static func apply_scene_export_overrides(node:Node, export_overrides:Dictionary, apply := true) -> void:
+ var default_info := get_scene_export_defaults(node)
+ if !node.script:
+ return
+ var property_info: Array[Dictionary] = node.script.get_script_property_list()
+ for i in property_info:
+ if i['usage'] & PROPERTY_USAGE_EDITOR:
+ if i['name'] in export_overrides:
+ if str_to_var(export_overrides[i['name']]) == null and typeof(node.get(i['name'])) == TYPE_STRING:
+ node.set(i['name'], export_overrides[i['name']])
+ else:
+ node.set(i['name'], str_to_var(export_overrides[i['name']]))
+ elif i['name'] in default_info:
+ node.set(i['name'], default_info.get(i['name']))
+ if apply:
+ if node.has_method('apply_export_overrides'):
+ node.apply_export_overrides()
+
+
+static func get_scene_export_defaults(node:Node) -> Dictionary:
+ if !node.script:
+ return {}
+
+ if Engine.get_main_loop().has_meta('dialogic_scene_export_defaults') and \
+ node.script.resource_path in Engine.get_main_loop().get_meta('dialogic_scene_export_defaults'):
+ return Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path]
+
+ if !Engine.get_main_loop().has_meta('dialogic_scene_export_defaults'):
+ Engine.get_main_loop().set_meta('dialogic_scene_export_defaults', {})
+ var defaults := {}
+ var property_info: Array[Dictionary] = node.script.get_script_property_list()
+ for i in property_info:
+ if i['usage'] & PROPERTY_USAGE_EDITOR:
+ defaults[i['name']] = node.get(i['name'])
+ Engine.get_main_loop().get_meta('dialogic_scene_export_defaults')[node.script.resource_path] = defaults
+ return defaults
+
+#endregion
+
+#region MAKE CUSTOM
+
+static func make_file_custom(original_file:String, target_folder:String, new_file_name := "", new_folder_name := "") -> String:
+ if not ResourceLoader.exists(original_file):
+ push_error("[Dialogic] Unable to make file with invalid path custom!")
+ return ""
+
+ if new_folder_name:
+ target_folder = target_folder.path_join(new_folder_name)
+ DirAccess.make_dir_absolute(target_folder)
+
+ if new_file_name.is_empty():
+ new_file_name = "custom_" + original_file.get_file()
+
+ if not new_file_name.ends_with(original_file.get_extension()):
+ new_file_name += "." + original_file.get_extension()
+
+ var target_file := target_folder.path_join(new_file_name)
+
+ customize_file(original_file, target_file)
+
+ get_dialogic_plugin().get_editor_interface().get_resource_filesystem().scan_sources()
+
+ return target_file
+
+
+static func customize_file(original_file:String, target_file:String) -> String:
+ #print("\nCUSTOMIZE FILE")
+ #printt(original_file, "->", target_file)
+
+ DirAccess.copy_absolute(original_file, target_file)
+
+ var file := FileAccess.open(target_file, FileAccess.READ)
+ var file_text := file.get_as_text()
+ file.close()
+
+ # If we are customizing a scene, we check for any resources used in that scene that are in the same folder.
+ # Those will be copied as well and the scene will be modified to point to them.
+ if file_text.begins_with('[gd_'):
+ var base_path: String = original_file.get_base_dir()
+
+ var remove_uuid_regex := r'\[gd_.* (?uid="uid:[^"]*")'
+ var result := RegEx.create_from_string(remove_uuid_regex).search(file_text)
+ if result:
+ file_text = file_text.replace(result.get_string("uid"), "")
+
+ # This regex also removes the UID referencing the original resource
+ var file_regex := r'(uid="[^"]*" )?\Qpath="'+base_path+r'\E(?[^"]*)"'
+ result = RegEx.create_from_string(file_regex).search(file_text)
+ while result:
+ var found_file_name := result.get_string('file')
+ var found_file_path := base_path.path_join(found_file_name)
+ var target_file_path := target_file.get_base_dir().path_join(found_file_name)
+
+ # Files found in this file will ALSO be customized.
+ customize_file(found_file_path, target_file_path)
+
+ file_text = file_text.replace(found_file_path, target_file_path)
+
+ result = RegEx.create_from_string(file_regex).search(file_text)
+
+ file = FileAccess.open(target_file, FileAccess.WRITE)
+ file.store_string(file_text)
+ file.close()
+
+ return target_file
+
+#endregion
+
+#region INSPECTOR FIELDS
+################################################################################
+
+static func setup_script_property_edit_node(property_info: Dictionary, value:Variant, property_changed:Callable) -> Control:
+ var input: Control = null
+ match property_info['type']:
+ TYPE_BOOL:
+ input = CheckBox.new()
+ if value != null:
+ input.button_pressed = value
+ input.toggled.connect(DialogicUtil._on_export_bool_submitted.bind(property_info.name, property_changed))
+ TYPE_COLOR:
+ input = ColorPickerButton.new()
+ if value != null:
+ input.color = value
+ input.color_changed.connect(DialogicUtil._on_export_color_submitted.bind(property_info.name, property_changed))
+ input.custom_minimum_size.x = get_editor_scale() * 50
+ TYPE_INT:
+ if property_info['hint'] & PROPERTY_HINT_ENUM:
+ input = OptionButton.new()
+ for x in property_info['hint_string'].split(','):
+ input.add_item(x.split(':')[0])
+ if value != null:
+ input.select(value)
+ input.item_selected.connect(DialogicUtil._on_export_int_enum_submitted.bind(property_info.name, property_changed))
+ else:
+ input = SpinBox.new()
+ input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
+ if property_info.hint_string == 'int':
+ input.step = 1
+ input.allow_greater = true
+ input.allow_lesser = true
+ elif ',' in property_info.hint_string:
+ input.min_value = int(property_info.hint_string.get_slice(',', 0))
+ input.max_value = int(property_info.hint_string.get_slice(',', 1))
+ if property_info.hint_string.count(',') > 1:
+ input.step = int(property_info.hint_string.get_slice(',', 2))
+ if value != null:
+ input.value = value
+ TYPE_FLOAT:
+ input = SpinBox.new()
+ input.step = 0.01
+ if ',' in property_info.hint_string:
+ input.min_value = float(property_info.hint_string.get_slice(',', 0))
+ input.max_value = float(property_info.hint_string.get_slice(',', 1))
+ if property_info.hint_string.count(',') > 1:
+ input.step = float(property_info.hint_string.get_slice(',', 2))
+ input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_info.name, property_changed))
+ if value != null:
+ input.value = value
+ TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4:
+ var vectorSize: String = type_string(typeof(value))[-1]
+ input = load("res://addons/dialogic/Editor/Events/Fields/field_vector" + vectorSize + ".tscn").instantiate()
+ input.property_name = property_info['name']
+ input.set_value(value)
+ input.value_changed.connect(DialogicUtil._on_export_vector_submitted.bind(property_changed))
+ TYPE_STRING:
+ if property_info['hint'] & PROPERTY_HINT_FILE or property_info['hint'] & PROPERTY_HINT_DIR:
+ input = load("res://addons/dialogic/Editor/Events/Fields/field_file.tscn").instantiate()
+ input.file_filter = property_info['hint_string']
+ input.file_mode = FileDialog.FILE_MODE_OPEN_FILE
+ if property_info['hint'] == PROPERTY_HINT_DIR:
+ input.file_mode = FileDialog.FILE_MODE_OPEN_DIR
+ input.property_name = property_info['name']
+ input.placeholder = "Default"
+ input.hide_reset = true
+ if value != null:
+ input.set_value(value)
+ input.value_changed.connect(DialogicUtil._on_export_file_submitted.bind(property_changed))
+ elif property_info['hint'] & PROPERTY_HINT_ENUM:
+ input = OptionButton.new()
+ var options: PackedStringArray = []
+ for x in property_info['hint_string'].split(','):
+ options.append(x.split(':')[0].strip_edges())
+ input.add_item(options[-1])
+ if value != null:
+ input.select(options.find(value))
+ input.item_selected.connect(DialogicUtil._on_export_string_enum_submitted.bind(property_info.name, options, property_changed))
+ else:
+ input = LineEdit.new()
+ if value != null:
+ input.text = value
+ input.text_submitted.connect(DialogicUtil._on_export_input_text_submitted.bind(property_info.name, property_changed))
+ TYPE_DICTIONARY:
+ input = load("res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn").instantiate()
+ input.property_name = property_info["name"]
+ input.value_changed.connect(_on_export_dict_submitted.bind(property_changed))
+ TYPE_OBJECT:
+ input = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
+ input.hint_text = "Objects/Resources as settings are currently not supported. \nUse @export_file('*.extension') instead and load the resource once needed."
+
+ _:
+ input = LineEdit.new()
+ if value != null:
+ input.text = value
+ input.text_submitted.connect(_on_export_input_text_submitted.bind(property_info.name, property_changed))
+ return input
+
+
+static func _on_export_input_text_submitted(text:String, property_name:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(text))
+
+static func _on_export_bool_submitted(value:bool, property_name:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(value))
+
+static func _on_export_color_submitted(color:Color, property_name:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(color))
+
+static func _on_export_int_enum_submitted(item:int, property_name:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(item))
+
+static func _on_export_number_submitted(value:float, property_name:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(value))
+
+static func _on_export_file_submitted(property_name:String, value:String, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(value))
+
+static func _on_export_string_enum_submitted(value:int, property_name:String, list:PackedStringArray, callable: Callable):
+ callable.call(property_name, var_to_str(list[value]))
+
+static func _on_export_vector_submitted(property_name:String, value:Variant, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(value))
+
+static func _on_export_dict_submitted(property_name:String, value:Variant, callable: Callable) -> void:
+ callable.call(property_name, var_to_str(value))
+
+#endregion
+
+
+#region EVENT DEFAULTS
+################################################################################
+
+static func get_custom_event_defaults(event_name:String) -> Dictionary:
+ if Engine.is_editor_hint():
+ return ProjectSettings.get_setting('dialogic/event_default_overrides', {}).get(event_name, {})
+ else:
+ if !Engine.get_main_loop().has_meta('dialogic_event_defaults'):
+ Engine.get_main_loop().set_meta('dialogic_event_defaults', ProjectSettings.get_setting('dialogic/event_default_overrides', {}))
+ return Engine.get_main_loop().get_meta('dialogic_event_defaults').get(event_name, {})
+
+#endregion
+
+
+#region CONVERSION
+################################################################################
+
+static func str_to_bool(boolstring:String) -> bool:
+ return true if boolstring == "true" else false
+
+
+static func logical_convert(value:Variant) -> Variant:
+ if typeof(value) == TYPE_STRING:
+ if value.is_valid_int():
+ return value.to_int()
+ if value.is_valid_float():
+ return value.to_float()
+ if value == 'true':
+ return true
+ if value == 'false':
+ return false
+ return value
+
+
+## Takes [param source] and builds a dictionary of keys only.
+## The values are `null`.
+static func str_to_hash_set(source: String) -> Dictionary:
+ var dictionary := Dictionary()
+
+ for character in source:
+ dictionary[character] = null
+
+ return dictionary
+
+#endregion
+
+
+static func get_character_suggestions(_search_text:String, current_value:DialogicCharacter = null, allow_none := true, allow_all:= false, editor_node:Node = null) -> Dictionary:
+ var suggestions := {}
+
+ var icon := load("res://addons/dialogic/Editor/Images/Resources/character.svg")
+
+ if allow_none and current_value:
+ suggestions['(No one)'] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
+
+ if allow_all:
+ suggestions['ALL'] = {'value':'--All--', 'tooltip':'All currently joined characters leave', 'editor_icon':["GuiEllipsis", "EditorIcons"]}
+
+ # Get characters in the current timeline and place them at the top of suggestions.
+ if editor_node:
+ var recent_characters := []
+ var timeline_node := editor_node.get_parent().find_parent("Timeline") as DialogicEditor
+ for event_node in timeline_node.find_child("Timeline").get_children():
+ if event_node == editor_node:
+ break
+ if event_node.resource is DialogicCharacterEvent or event_node.resource is DialogicTextEvent:
+ recent_characters.append(event_node.resource.character)
+
+ recent_characters.reverse()
+ for character in recent_characters:
+ if character and not character.get_character_name() in suggestions:
+ suggestions[character.get_character_name()] = {'value': character.get_character_name(), 'tooltip': character.resource_path, 'icon': icon.duplicate()}
+
+ var character_directory := DialogicResourceUtil.get_character_directory()
+ for resource in character_directory.keys():
+ suggestions[resource] = {'value': resource, 'tooltip': character_directory[resource], 'icon': icon}
+
+ return suggestions
+
+
+static func get_portrait_suggestions(search_text:String, character:DialogicCharacter, allow_empty := false, empty_text := "Don't Change") -> Dictionary:
+ var icon := load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
+ var suggestions := {}
+
+ if allow_empty:
+ suggestions[empty_text] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
+
+ if "{" in search_text:
+ suggestions[search_text] = {'value':search_text, 'editor_icon':["Variant", "EditorIcons"]}
+
+ if character != null:
+ for portrait in character.portraits:
+ suggestions[portrait] = {'value':portrait, 'icon':icon}
+
+ return suggestions
+
+
+static func get_portrait_position_suggestions(search_text := "") -> Dictionary:
+ var icon := load(DialogicUtil.get_module_path("Character").path_join('portrait_position.svg'))
+
+ var setting: String = ProjectSettings.get_setting('dialogic/portraits/position_suggestion_names', 'leftmost, left, center, right, rightmost')
+
+ var suggestions := {}
+
+ if not search_text.is_empty():
+ suggestions[search_text] = {'value':search_text.strip_edges(), 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
+
+ for position_id in setting.split(','):
+ suggestions[position_id.strip_edges()] = {'value':position_id.strip_edges(), 'icon':icon}
+ if not search_text.is_empty() and position_id.strip_edges().begins_with(search_text):
+ suggestions.erase(search_text)
+
+ return suggestions
diff --git a/addons/dialogic-main/addons/dialogic/Core/Dialogic_Subsystem.gd b/addons/dialogic-main/addons/dialogic/Core/Dialogic_Subsystem.gd
new file mode 100644
index 0000000..3ff55e6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Core/Dialogic_Subsystem.gd
@@ -0,0 +1,41 @@
+class_name DialogicSubsystem
+extends Node
+
+var dialogic: DialogicGameHandler = null
+
+enum LoadFlags {FULL_LOAD, ONLY_DNODES}
+
+# To be overriden by sub-classes
+# Called once after every subsystem has been added to the tree
+func post_install() -> void:
+ pass
+
+
+# To be overriden by sub-classes
+# Fill in everything that should be cleared (for example before loading a different state)
+func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ pass
+
+
+# To be overriden by sub-classes
+# Fill in everything that should be loaded using the dialogic_game_handler.current_state_info
+# This is called when a save is loaded
+func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
+ pass
+
+
+# To be overriden by sub-classes
+# Fill in everything that should be saved into the dialogic_game_handler.current_state_info
+# This is called when a save is saved
+func save_game_state() -> void:
+ pass
+
+
+# To be overriden by sub-classes
+func pause() -> void:
+ pass
+
+
+# To be overriden by sub-classes
+func resume() -> void:
+ pass
diff --git a/addons/dialogic-main/addons/dialogic/Core/index_class.gd b/addons/dialogic-main/addons/dialogic/Core/index_class.gd
new file mode 100644
index 0000000..30c8fa0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Core/index_class.gd
@@ -0,0 +1,152 @@
+@tool
+class_name DialogicIndexer
+extends RefCounted
+
+## Script that indexes events, subsystems, settings pages and more. [br]
+## Place a script of this type in every folder in "addons/Events". [br]
+## Overwrite the methods to return the contents of that folder.
+
+
+var this_folder: String = get_script().resource_path.get_base_dir()
+
+## Overwrite if this module contains any events. [br]
+## Return an array with all the paths to the event scripts.[br]
+## You can use the [property this_folder].path_join('my_event.gd')
+func _get_events() -> Array:
+ if ResourceLoader.exists(this_folder.path_join('event.gd')):
+ return [this_folder.path_join('event.gd')]
+ return []
+
+
+## Overwrite if this module contains any subsystems.
+## Should return an array of dictionaries each with the following keys: [br]
+## "name" -> name for this subsystem[br]
+## "script" -> array of preview images[br]
+func _get_subsystems() -> Array[Dictionary]:
+ return []
+
+
+func _get_editors() -> Array[String]:
+ return []
+
+
+func _get_settings_pages() -> Array:
+ return []
+
+
+func _get_character_editor_sections() -> Array:
+ return []
+
+
+#region TEXT EFFECTS & MODIFIERS
+
+## Should return array of dictionaries with the following keys:[br]
+## "command" -> the text e.g. "speed"[br]
+## "node_path" or "subsystem" -> whichever contains your effect method[br]
+## "method" -> name of the effect method[br]
+func _get_text_effects() -> Array[Dictionary]:
+ return []
+
+
+## Should return array of dictionaries with the same arguments as _get_text_effects()
+func _get_text_modifiers() -> Array[Dictionary]:
+ return []
+
+#endregion
+
+
+## Return a list of resources, scripts, etc.
+## These can later be retrieved with DialogicResourceUtil.
+## Each dictionary should contain (at least "type" and "path").
+## E.g. {"type":"Animation", "path": "res://..."}
+func _get_special_resources() -> Dictionary:
+ return {}
+
+
+## Return a list of dictionaries, each
+func _get_portrait_scene_presets() -> Array[Dictionary]:
+ return []
+
+
+#region HELPERS
+################################################################################
+
+func list_dir(subdir:='') -> Array:
+ return Array(DirAccess.get_files_at(this_folder.path_join(subdir))).map(func(file):return this_folder.path_join(subdir).path_join(file))
+
+
+func list_special_resources(subdir:='', extension:="") -> Dictionary:
+ var dict := {}
+ for i in list_dir(subdir):
+ if extension.is_empty() or i.ends_with(extension):
+ dict[DialogicUtil.pretty_name(i).to_lower()] = {"path":i}
+ return dict
+
+
+func list_animations(subdir := "") -> Dictionary:
+ var full_animation_list := {}
+ for path in list_dir(subdir):
+ if not path.ends_with(".gd") and not path.ends_with(".gdc"):
+ continue
+ var anim_object: DialogicAnimation = load(path).new()
+ var versions := anim_object._get_named_variations()
+ for version_name in versions:
+ full_animation_list[version_name] = versions[version_name]
+ full_animation_list[version_name]["path"] = path
+ anim_object.queue_free()
+ return full_animation_list
+
+#endregion
+
+
+#region STYLES & LAYOUTS
+################################################################################
+
+func _get_style_presets() -> Array[Dictionary]:
+ return []
+
+
+## Should return an array of dictionaries with the following keys:[br]
+## "path" -> the path to the scene[br]
+## "name" -> name for this layout[br]
+## "description"-> description of this layout. list what features/events are supported[br]
+## "preview_image"-> array of preview images[br]
+func _get_layout_parts() -> Array[Dictionary]:
+ return []
+
+
+## Helper that allows scanning sub directories that might be layout parts or styles
+func scan_for_layout_parts() -> Array[Dictionary]:
+ var dir := DirAccess.open(this_folder)
+ var style_list: Array[Dictionary] = []
+ if !dir:
+ return style_list
+ dir.list_dir_begin()
+ var dir_name := dir.get_next()
+ while dir_name != "":
+ if !dir.current_is_dir() or !dir.file_exists(dir_name.path_join('part_config.cfg')):
+ dir_name = dir.get_next()
+ continue
+ var config := ConfigFile.new()
+ config.load(this_folder.path_join(dir_name).path_join('part_config.cfg'))
+ var default_image_path: String = this_folder.path_join(dir_name).path_join('preview.png')
+ style_list.append(
+ {
+ 'type': config.get_value('style', 'type', 'Unknown type'),
+ 'name': config.get_value('style', 'name', 'Unnamed Layout'),
+ 'path': this_folder.path_join(dir_name).path_join(config.get_value('style', 'scene', '')),
+ 'author': config.get_value('style', 'author', 'Anonymous'),
+ 'description': config.get_value('style', 'description', 'No description'),
+ 'preview_image': [config.get_value('style', 'image', default_image_path)],
+ 'style_path':config.get_value('style', 'style_path', ''),
+ 'icon':this_folder.path_join(dir_name).path_join(config.get_value('style', 'icon', '')),
+ })
+
+ if not style_list[-1].style_path.begins_with('res://'):
+ style_list[-1].style_path = this_folder.path_join(dir_name).path_join(style_list[-1].style_path)
+
+ dir_name = dir.get_next()
+
+ return style_list
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd
new file mode 100644
index 0000000..9ad94cb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd
@@ -0,0 +1,91 @@
+@tool
+extends DialogicCharacterEditorPortraitSection
+
+## Section that allows setting values of exported scene variables
+## for custom portrait scenes
+
+var current_portrait_data := {}
+var last_scene := ""
+
+func _get_title() -> String:
+ return "Settings"
+
+
+func _load_portrait_data(data:Dictionary) -> void:
+ _recheck(data, true)
+
+
+## Recheck section visibility and reload export fields.
+## This allows reacting to changes of the portrait_scene setting.
+func _recheck(data: Dictionary, force:=false):
+ if last_scene == data.get("scene", "") and not force:
+ current_portrait_data = data
+ last_scene = data.get("scene", "")
+ return
+
+ last_scene = data.get("scene", "")
+ current_portrait_data = data
+
+ for child in $Grid.get_children():
+ child.get_parent().remove_child(child)
+ child.queue_free()
+
+ var scene: Variant = null
+
+ if current_portrait_data.get('scene', '').is_empty():
+ if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
+ scene = load(character_editor.def_portrait_path)
+ else:
+ scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
+ else:
+ scene = load(current_portrait_data.get('scene'))
+
+ if not scene:
+ return
+
+ scene = scene.instantiate()
+
+ var skip := false
+ for i in scene.script.get_script_property_list():
+ if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
+ var label := Label.new()
+ label.text = i['name'].capitalize()
+ $Grid.add_child(label)
+
+ var current_value: Variant = scene.get(i['name'])
+ if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
+ current_value = str_to_var(current_portrait_data.export_overrides[i['name']])
+ if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
+ current_value = current_portrait_data['export_overrides'][i['name']]
+
+ var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
+ input.size_flags_horizontal = SIZE_EXPAND_FILL
+ $Grid.add_child(input)
+
+ if i['usage'] & PROPERTY_USAGE_GROUP:
+ if i['name'] == 'Main' or i["name"] == "Private":
+ skip = true
+ continue
+ else:
+ skip = false
+
+ if $Grid.get_child_count():
+ get_parent().get_child(get_index()-1).show()
+ show()
+ else:
+ hide()
+ get_parent().get_child(get_index()-1).hide()
+ get_parent().get_child(get_index()+1).hide()
+
+
+## On any change, save the export override to the portrait items metadata.
+func set_export_override(property_name:String, value:String = "") -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ if !data.has('export_overrides'):
+ data['export_overrides'] = {}
+ if !value.is_empty():
+ data.export_overrides[property_name] = value
+ else:
+ data.export_overrides.erase(property_name)
+ changed.emit()
+ update_preview.emit()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn
new file mode 100644
index 0000000..1392a18
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=2 format=3 uid="uid://cfcs7lb6gqnmd"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd" id="1_isys8"]
+
+[node name="Settings" type="VBoxContainer"]
+custom_minimum_size = Vector2(0, 35)
+offset_right = 367.0
+offset_bottom = 82.0
+script = ExtResource("1_isys8")
+
+[node name="Grid" type="GridContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/h_separation = 10
+columns = 2
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd
new file mode 100644
index 0000000..03f4346
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd
@@ -0,0 +1,44 @@
+@tool
+extends DialogicCharacterEditorPortraitSection
+
+## Tab that allows setting size, offset and mirror of a portrait.
+
+
+func _get_title() -> String:
+ return "Scale, Offset & Mirror"
+
+
+func _load_portrait_data(data:Dictionary) -> void:
+ %IgnoreScale.set_pressed_no_signal(data.get('ignore_char_scale', false))
+ %PortraitScale.value = data.get('scale', 1.0)*100
+ %PortraitOffset.set_value(data.get('offset', Vector2()))
+ %PortraitOffset._load_display_info({'step':1})
+ %PortraitMirror.set_pressed_no_signal(data.get('mirror', false))
+
+
+func _on_portrait_scale_value_changed(value:float) -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ data['scale'] = value/100.0
+ update_preview.emit()
+ changed.emit()
+
+
+func _on_portrait_mirror_toggled(button_pressed:bool)-> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ data['mirror'] = button_pressed
+ update_preview.emit()
+ changed.emit()
+
+
+func _on_ignore_scale_toggled(button_pressed:bool) -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ data['ignore_char_scale'] = button_pressed
+ update_preview.emit()
+ changed.emit()
+
+
+func _on_portrait_offset_value_changed(property:String, value:Vector2) -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ data['offset'] = value
+ update_preview.emit()
+ changed.emit()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn
new file mode 100644
index 0000000..9fa25c2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn
@@ -0,0 +1,64 @@
+[gd_scene load_steps=3 format=3 uid="uid://crke8suvv52c6"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd" id="1_76vf2"]
+[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="2_c8kyi"]
+
+[node name="Layout" type="HFlowContainer"]
+offset_right = 428.0
+offset_bottom = 128.0
+size_flags_horizontal = 3
+script = ExtResource("1_76vf2")
+
+[node name="Label3" type="Label" parent="."]
+layout_mode = 2
+text = "Ignore Main Scale: "
+
+[node name="IgnoreScale" type="CheckBox" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "This portrait will ignore the main scale."
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer"]
+layout_mode = 2
+text = "Scale:"
+
+[node name="PortraitScale" type="SpinBox" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "A scale to be applied on top of the main scale
+(unless ignore main scale is pressed)."
+value = 100.0
+allow_greater = true
+suffix = "%"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label2" type="Label" parent="HBoxContainer2"]
+layout_mode = 2
+text = "Offset:"
+
+[node name="PortraitOffset" parent="HBoxContainer2" instance=ExtResource("2_c8kyi")]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Offset that is applied on top of the main portrait offset."
+
+[node name="MirrorOption" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="MirrorOption"]
+layout_mode = 2
+text = "Mirror:"
+
+[node name="PortraitMirror" type="CheckBox" parent="MirrorOption"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Mirroring that is applied on top of the main portrait mirror."
+
+[connection signal="toggled" from="IgnoreScale" to="." method="_on_ignore_scale_toggled"]
+[connection signal="value_changed" from="HBoxContainer/PortraitScale" to="." method="_on_portrait_scale_value_changed"]
+[connection signal="value_changed" from="HBoxContainer2/PortraitOffset" to="." method="_on_portrait_offset_value_changed"]
+[connection signal="toggled" from="MirrorOption/PortraitMirror" to="." method="_on_portrait_mirror_toggled"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd
new file mode 100644
index 0000000..80a96c8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd
@@ -0,0 +1,101 @@
+@tool
+extends DialogicCharacterEditorPortraitSection
+
+## Tab that allows setting a custom scene for a portrait.
+
+func _get_title() -> String:
+ return "Scene"
+
+func _init() -> void:
+ hint_text = "You can use a custom scene for this portrait."
+
+func _start_opened() -> bool:
+ return true
+
+func _ready() -> void:
+ %ChangeSceneButton.icon = get_theme_icon("Loop", "EditorIcons")
+ %ScenePicker.file_filter = "*.tscn, *.scn; Scenes"
+ %ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons')
+ %ScenePicker.placeholder = 'Default scene'
+
+ %OpenSceneButton.icon = get_theme_icon("ExternalLink", "EditorIcons")
+
+
+func _load_portrait_data(data:Dictionary) -> void:
+ reload_ui(data)
+
+
+func _on_open_scene_button_pressed() -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ if ResourceLoader.exists(data.get("scene", "")):
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(data.get("scene", ""))
+ await get_tree().process_frame
+ EditorInterface.set_main_screen_editor("2D")
+
+
+func _on_change_scene_button_pressed() -> void:
+ %PortraitSceneBrowserWindow.popup_centered_ratio(0.6)
+
+
+func _on_portrait_scene_browser_activate_part(part_info: Dictionary) -> void:
+ %PortraitSceneBrowserWindow.hide()
+ match part_info.type:
+ "General":
+ set_scene_path(part_info.path)
+ "Preset":
+ find_parent("EditorView").godot_file_dialog(
+ create_new_portrait_scene.bind(part_info),
+ '*.tscn,*.scn',
+ EditorFileDialog.FILE_MODE_SAVE_FILE,
+ "Select where to save the new scene",
+ part_info.path.get_file().trim_suffix("."+part_info.path.get_extension())+"_"+character_editor.current_resource.get_character_name().to_lower())
+ "Custom":
+ find_parent("EditorView").godot_file_dialog(
+ set_scene_path,
+ '*.tscn, *.scn',
+ EditorFileDialog.FILE_MODE_OPEN_FILE,
+ "Select custom portrait scene",)
+ "Default":
+ set_scene_path("")
+
+
+func create_new_portrait_scene(target_file: String, info: Dictionary) -> void:
+ var path := make_portrait_preset_custom(target_file, info)
+ set_scene_path(path)
+
+
+func make_portrait_preset_custom(target_file:String, info: Dictionary) -> String:
+ var previous_file: String = info.path
+
+ var result_path := DialogicUtil.make_file_custom(previous_file, target_file.get_base_dir(), target_file.get_file())
+
+ return result_path
+
+
+func set_scene_path(path:String) -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ data['scene'] = path
+ update_preview.emit()
+ changed.emit()
+ reload_ui(data)
+
+
+func reload_ui(data: Dictionary) -> void:
+ var path: String = data.get('scene', '')
+ %OpenSceneButton.hide()
+
+ if path.is_empty():
+ %SceneLabel.text = "Default Portrait Scene"
+ %SceneLabel.tooltip_text = "Can be changed in the settings."
+ %SceneLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
+
+ elif %PortraitSceneBrowser.is_premade_portrait_scene(path):
+ %SceneLabel.text = %PortraitSceneBrowser.portrait_scenes_info[path].name
+ %SceneLabel.tooltip_text = path
+ %SceneLabel.add_theme_color_override("font_color", get_theme_color("accent_color", "Editor"))
+
+ else:
+ %SceneLabel.text = path.get_file()
+ %SceneLabel.tooltip_text = path
+ %SceneLabel.add_theme_color_override("font_color", get_theme_color("property_color_x", "Editor"))
+ %OpenSceneButton.show()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn
new file mode 100644
index 0000000..41a38dd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn
@@ -0,0 +1,72 @@
+[gd_scene load_steps=6 format=3 uid="uid://djq4aasoihexj"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"]
+[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"]
+[ext_resource type="PackedScene" uid="uid://b1wn8r84uh11b" path="res://addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn" id="3_ngvgq"]
+
+[sub_resource type="Image" id="Image_tg5pd"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_f5xt2"]
+image = SubResource("Image_tg5pd")
+
+[node name="Scene" type="GridContainer"]
+offset_right = 298.0
+offset_bottom = 86.0
+size_flags_horizontal = 3
+script = ExtResource("1_ht8lu")
+
+[node name="HBox" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ChangeSceneButton" type="Button" parent="HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Change Scene"
+icon = SubResource("ImageTexture_f5xt2")
+
+[node name="SceneLabel" type="Label" parent="HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 0
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+text = "asdsdasdasd"
+clip_text = true
+
+[node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+file_filter = "*.tscn, *.scn; Scenes"
+placeholder = "Default scene"
+
+[node name="OpenSceneButton" type="Button" parent="HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Open/Edit Scene"
+icon = SubResource("ImageTexture_f5xt2")
+
+[node name="PortraitSceneBrowserWindow" type="Window" parent="."]
+unique_name_in_owner = true
+title = "Portrait Scene Browser"
+position = Vector2i(0, 36)
+visible = false
+wrap_controls = true
+transient = true
+popup_window = true
+
+[node name="PortraitSceneBrowser" parent="PortraitSceneBrowserWindow" instance=ExtResource("3_ngvgq")]
+unique_name_in_owner = true
+
+[connection signal="pressed" from="HBox/ChangeSceneButton" to="." method="_on_change_scene_button_pressed"]
+[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"]
+[connection signal="activate_part" from="PortraitSceneBrowserWindow/PortraitSceneBrowser" to="." method="_on_portrait_scene_browser_activate_part"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd
new file mode 100644
index 0000000..d9ff6e5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd
@@ -0,0 +1,80 @@
+@tool
+extends DialogicCharacterEditorPortraitSection
+
+## Portrait Settings Section that only shows the MAIN settings of a portrait scene.
+
+var current_portrait_data := {}
+var last_scene := ""
+
+func _show_title() -> bool:
+ return false
+
+
+func _load_portrait_data(data:Dictionary) -> void:
+ _recheck(data, true)
+
+
+func _recheck(data:Dictionary, force := false) -> void:
+ get_parent().get_child(get_index()+1).hide()
+ if last_scene == data.get("scene", "") and not force:
+ current_portrait_data = data
+ last_scene = data.get("scene", "")
+ return
+
+ last_scene = data.get("scene", "")
+ current_portrait_data = data
+
+ load_portrait_scene_export_variables()
+
+
+func load_portrait_scene_export_variables() -> void:
+ for child in $Grid.get_children():
+ child.queue_free()
+
+ var scene: Variant = null
+ if current_portrait_data.get('scene', '').is_empty():
+ if ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
+ scene = load(character_editor.def_portrait_path)
+ else:
+ scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
+ else:
+ scene = load(current_portrait_data.get('scene'))
+
+ if not scene:
+ return
+
+ scene = scene.instantiate()
+ var skip := true
+ for i in scene.script.get_script_property_list():
+ if i['usage'] & PROPERTY_USAGE_EDITOR and !skip:
+ var label := Label.new()
+ label.text = i['name'].capitalize()
+ $Grid.add_child(label)
+
+ var current_value: Variant = scene.get(i['name'])
+ if current_portrait_data.has('export_overrides') and current_portrait_data['export_overrides'].has(i['name']):
+ current_value = str_to_var(current_portrait_data['export_overrides'][i['name']])
+ if current_value == null and typeof(scene.get(i['name'])) == TYPE_STRING:
+ current_value = current_portrait_data['export_overrides'][i['name']]
+
+ var input: Node = DialogicUtil.setup_script_property_edit_node(i, current_value, set_export_override)
+ input.size_flags_horizontal = SIZE_EXPAND_FILL
+ $Grid.add_child(input)
+
+ if i['usage'] & PROPERTY_USAGE_GROUP:
+ if i['name'] == 'Main':
+ skip = false
+ else:
+ skip = true
+ continue
+
+func set_export_override(property_name:String, value:String = "") -> void:
+ var data: Dictionary = selected_item.get_metadata(0)
+ if !data.has('export_overrides'):
+ data['export_overrides'] = {}
+ if !value.is_empty():
+ data['export_overrides'][property_name] = value
+ else:
+ data['export_overrides'].erase(property_name)
+ changed.emit()
+ update_preview.emit()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn
new file mode 100644
index 0000000..25b7d7c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn
@@ -0,0 +1,15 @@
+[gd_scene load_steps=2 format=3 uid="uid://ba5w02lm3ewkj"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd" id="1_mttrr"]
+
+[node name="MainExports" type="VBoxContainer"]
+offset_right = 374.0
+offset_bottom = 82.0
+script = ExtResource("1_mttrr")
+
+[node name="Grid" type="GridContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/h_separation = 10
+columns = 2
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd
new file mode 100644
index 0000000..a5fe514
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd
@@ -0,0 +1,53 @@
+@tool
+extends DialogicCharacterEditorMainSection
+
+var min_width := 200
+
+## The general character settings tab
+func _get_title() -> String:
+ return "General"
+
+
+func _start_opened() -> bool:
+ return true
+
+
+func _ready() -> void:
+ # Connecting all necessary signals
+ %ColorPickerButton.custom_minimum_size.x = DialogicUtil.get_editor_scale() * 30
+ %ColorPickerButton.color_changed.connect(character_editor.something_changed)
+ %DisplayNameLineEdit.text_changed.connect(character_editor.something_changed)
+ %NicknameLineEdit.text_changed.connect(character_editor.something_changed)
+ %DescriptionTextEdit.text_changed.connect(character_editor.something_changed)
+ min_width = get_minimum_size().x
+ resized.connect(_on_resized)
+
+func _load_character(resource:DialogicCharacter) -> void:
+ %DisplayNameLineEdit.text = resource.display_name
+ %ColorPickerButton.color = resource.color
+
+ %NicknameLineEdit.text = ""
+ for nickname in resource.nicknames:
+ %NicknameLineEdit.text += nickname +", "
+ %NicknameLineEdit.text = %NicknameLineEdit.text.trim_suffix(', ')
+
+ %DescriptionTextEdit.text = resource.description
+
+
+func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
+ resource.display_name = %DisplayNameLineEdit.text
+ resource.color = %ColorPickerButton.color
+ var nicknames := []
+ for n_name in %NicknameLineEdit.text.split(','):
+ nicknames.append(n_name.strip_edges())
+ resource.nicknames = nicknames
+ resource.description = %DescriptionTextEdit.text
+
+ return resource
+
+
+func _on_resized() -> void:
+ if size.x > min_width+20:
+ self.columns = 2
+ else:
+ self.columns = 1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn
new file mode 100644
index 0000000..60f89a6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn
@@ -0,0 +1,114 @@
+[gd_scene load_steps=5 format=3 uid="uid://bnkck3hocbkk5"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd" id="1_3e1i1"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_cxfqm"]
+
+[sub_resource type="Image" id="Image_yiygw"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_hx3oq"]
+image = SubResource("Image_yiygw")
+
+[node name="General" type="GridContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 7.5
+offset_top = 38.5
+offset_right = -7.5
+offset_bottom = -7.5
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/h_separation = 6
+theme_override_constants/v_separation = 6
+columns = 2
+script = ExtResource("1_3e1i1")
+
+[node name="HBox" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Label2" type="Label" parent="HBox"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Display Name"
+
+[node name="HintTooltip" parent="HBox" instance=ExtResource("2_cxfqm")]
+layout_mode = 2
+tooltip_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
+texture = SubResource("ImageTexture_hx3oq")
+hint_text = "This name will be displayed on the name label. You can use a dialogic variable. E.g. :{Player.name}"
+
+[node name="DisplayName" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="DisplayNameLineEdit" type="LineEdit" parent="DisplayName"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+caret_blink = true
+caret_blink_interval = 0.5
+
+[node name="HintTooltip4" parent="DisplayName" instance=ExtResource("2_cxfqm")]
+layout_mode = 2
+tooltip_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
+texture = SubResource("ImageTexture_hx3oq")
+hint_text = "This color can be used on the name label and for occurences of the characters name in text (autocolor names)."
+
+[node name="ColorPickerButton" type="ColorPickerButton" parent="DisplayName"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(30, 0)
+layout_mode = 2
+color = Color(1, 1, 1, 1)
+edit_alpha = false
+
+[node name="HBox2" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Label3" type="Label" parent="HBox2"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Nicknames"
+
+[node name="HintTooltip2" parent="HBox2" instance=ExtResource("2_cxfqm")]
+layout_mode = 2
+tooltip_text = "If autocolor names is enabled, these will be colored in the characters color as well."
+texture = SubResource("ImageTexture_hx3oq")
+hint_text = "If autocolor names is enabled, these will be colored in the characters color as well."
+
+[node name="NicknameLineEdit" type="LineEdit" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+caret_blink = true
+caret_blink_interval = 0.5
+
+[node name="HBox3" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Label4" type="Label" parent="HBox3"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Description"
+
+[node name="HintTooltip3" parent="HBox3" instance=ExtResource("2_cxfqm")]
+layout_mode = 2
+tooltip_text = "No effect, just for you."
+texture = SubResource("ImageTexture_hx3oq")
+hint_text = "No effect, just for you."
+
+[node name="DescriptionTextEdit" type="TextEdit" parent="."]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 65)
+layout_mode = 2
+size_flags_horizontal = 3
+wrap_mode = 1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd
new file mode 100644
index 0000000..44f4ec3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd
@@ -0,0 +1,77 @@
+@tool
+extends DialogicCharacterEditorMainSection
+
+## The general portrait settings section
+
+var loading := false
+
+func _get_title() -> String:
+ return "Portraits"
+
+
+func _ready() -> void:
+ # Connecting all necessary signals
+ %DefaultPortraitPicker.value_changed.connect(default_portrait_changed)
+ %MainScale.value_changed.connect(main_portrait_settings_update)
+ %MainOffset._load_display_info({'step':1})
+ %MainOffset.value_changed.connect(main_portrait_settings_update)
+ %MainMirror.toggled.connect(main_portrait_settings_update)
+
+ # Setting up Default Portrait Picker
+ %DefaultPortraitPicker.resource_icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
+ %DefaultPortraitPicker.get_suggestions_func = suggest_portraits
+
+
+## Make sure preview get's updated when portrait settings change
+func main_portrait_settings_update(_something=null, _value=null) -> void:
+ if loading:
+ return
+ character_editor.current_resource.scale = %MainScale.value/100.0
+ character_editor.current_resource.offset = %MainOffset.current_value
+ character_editor.current_resource.mirror = %MainMirror.button_pressed
+ character_editor.update_preview()
+ character_editor.something_changed()
+
+
+func default_portrait_changed(property:String, value:String) -> void:
+ character_editor.current_resource.default_portrait = value
+ character_editor.update_default_portrait_star(value)
+
+
+func set_default_portrait(portrait_name:String) -> void:
+ %DefaultPortraitPicker.set_value(portrait_name)
+ default_portrait_changed("", portrait_name)
+
+
+func _load_character(resource:DialogicCharacter) -> void:
+ loading = true
+ %DefaultPortraitPicker.set_value(resource.default_portrait)
+
+ %MainScale.value = 100*resource.scale
+ %MainOffset.set_value(resource.offset)
+ %MainMirror.button_pressed = resource.mirror
+ loading = false
+
+
+func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
+ # Portrait settings
+ if %DefaultPortraitPicker.current_value in resource.portraits.keys():
+ resource.default_portrait = %DefaultPortraitPicker.current_value
+ elif !resource.portraits.is_empty():
+ resource.default_portrait = resource.portraits.keys()[0]
+ else:
+ resource.default_portrait = ""
+
+ resource.scale = %MainScale.value/100.0
+ resource.offset = %MainOffset.current_value
+ resource.mirror = %MainMirror.button_pressed
+ return resource
+
+
+## Get suggestions for DefaultPortraitPicker
+func suggest_portraits(search:String) -> Dictionary:
+ var suggestions := {}
+ for portrait in character_editor.get_updated_portrait_dict().keys():
+ suggestions[portrait] = {'value':portrait}
+ return suggestions
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn
new file mode 100644
index 0000000..0f1874a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn
@@ -0,0 +1,59 @@
+[gd_scene load_steps=4 format=3 uid="uid://cmrgbo8qi145o"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd" id="1_6sxsl"]
+[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="2_birla"]
+[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="3_vcvin"]
+
+[node name="Portraits" type="GridContainer"]
+offset_right = 453.0
+offset_bottom = 141.0
+theme_override_constants/h_separation = 1
+theme_override_constants/v_separation = 6
+columns = 2
+script = ExtResource("1_6sxsl")
+
+[node name="Label5" type="Label" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Default"
+
+[node name="DefaultPortraitPicker" parent="." instance=ExtResource("2_birla")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Select Default Portrait"
+fit_text_length = false
+
+[node name="Label" type="Label" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Main Scale"
+
+[node name="MainScale" type="SpinBox" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 8
+value = 100.0
+allow_greater = true
+alignment = 1
+suffix = "%"
+
+[node name="Label2" type="Label" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Main Offset"
+
+[node name="MainOffset" parent="." instance=ExtResource("3_vcvin")]
+unique_name_in_owner = true
+layout_mode = 2
+alignment = 2
+
+[node name="Label3" type="Label" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Main Mirror"
+
+[node name="MainMirror" type="CheckBox" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 8
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.gd
new file mode 100644
index 0000000..cc635cf
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.gd
@@ -0,0 +1,687 @@
+@tool
+extends DialogicEditor
+
+## Editor for editing character resources.
+
+signal character_loaded(resource_path:String)
+signal portrait_selected()
+
+
+# Current state
+var loading := false
+var current_previewed_scene: Variant = null
+var current_scene_path: String = ""
+
+# References
+var selected_item: TreeItem
+var def_portrait_path: String = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
+
+
+######### EDITOR STUFF and LOADING/SAVING ######################################
+
+#region Resource Logic
+## Method is called once editors manager is ready to accept registers.
+func _register() -> void:
+ ## Makes the editor open this when a .dch file is selected.
+ ## Then _open_resource() is called.
+ editors_manager.register_resource_editor("dch", self)
+
+ ## Add an "add character" button
+ var add_character_button: Button = editors_manager.add_icon_button(
+ load("res://addons/dialogic/Editor/Images/Toolbar/add-character.svg"),
+ 'Add Character',
+ self)
+ add_character_button.pressed.connect(_on_create_character_button_pressed)
+ add_character_button.shortcut = Shortcut.new()
+ add_character_button.shortcut.events.append(InputEventKey.new())
+ add_character_button.shortcut.events[0].keycode = KEY_2
+ add_character_button.shortcut.events[0].ctrl_pressed = true
+
+ ## By default show the no character screen
+ $NoCharacterScreen.show()
+
+
+func _get_title() -> String:
+ return "Character"
+
+
+func _get_icon() -> Texture:
+ return load("res://addons/dialogic/Editor/Images/Resources/character.svg")
+
+
+## Called when a character is opened somehow
+func _open_resource(resource:Resource) -> void:
+ if resource == null:
+ $NoCharacterScreen.show()
+ return
+
+ ## Update resource
+ current_resource = (resource as DialogicCharacter)
+
+ ## Make sure changes in the ui won't trigger saving
+ loading = true
+
+ ## Load other main tabs
+ for child in %MainSettingsSections.get_children():
+ if child is DialogicCharacterEditorMainSection:
+ child._load_character(current_resource)
+
+ ## Clear and then load Portrait section
+ %PortraitSearch.text = ""
+ load_portrait_tree()
+
+ loading = false
+ character_loaded.emit(resource.resource_path)
+
+ %CharacterName.text = DialogicResourceUtil.get_unique_identifier(resource.resource_path)
+
+ $NoCharacterScreen.hide()
+ %PortraitChangeInfo.hide()
+
+
+## Called when the character is opened.
+func _open(extra_info:Variant="") -> void:
+ if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
+ def_portrait_path = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
+ else:
+ def_portrait_path = DialogicUtil.get_module_path('Character').path_join('default_portrait.tscn')
+
+ if current_resource == null:
+ $NoCharacterScreen.show()
+ return
+
+ update_preview(true)
+ %PortraitChangeInfo.hide()
+
+
+func _clear() -> void:
+ current_resource = null
+ current_resource_state = ResourceStates.SAVED
+ $NoCharacterScreen.show()
+
+
+func _save() -> void:
+ if ! visible or not current_resource:
+ return
+
+ ## Portrait list
+ current_resource.portraits = get_updated_portrait_dict()
+
+ ## Main tabs
+ for child in %MainSettingsSections.get_children():
+ if child is DialogicCharacterEditorMainSection:
+ current_resource = child._save_changes(current_resource)
+
+ ResourceSaver.save(current_resource, current_resource.resource_path)
+ current_resource_state = ResourceStates.SAVED
+ DialogicResourceUtil.update_directory('dch')
+
+
+## Saves a new empty character to the given path
+func new_character(path: String) -> void:
+ var resource := DialogicCharacter.new()
+ resource.resource_path = path
+ resource.display_name = path.get_file().trim_suffix("."+path.get_extension())
+ resource.color = Color(1,1,1,1)
+ resource.default_portrait = ""
+ resource.custom_info = {}
+ ResourceSaver.save(resource, path)
+ DialogicResourceUtil.update_directory('dch')
+ editors_manager.edit_resource(resource)
+
+#endregion
+
+
+######### INTERFACE ############################################################
+
+#region Interface
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ DialogicUtil.get_dialogic_plugin().resource_saved.connect(_on_some_resource_saved)
+ # NOTE: This check is required because up to 4.2 this signal is not exposed.
+ if DialogicUtil.get_dialogic_plugin().has_signal("scene_saved"):
+ DialogicUtil.get_dialogic_plugin().scene_saved.connect(_on_some_resource_saved)
+
+ $NoCharacterScreen.color = get_theme_color("dark_color_2", "Editor")
+ $NoCharacterScreen.show()
+ setup_portrait_list_tab()
+
+ _on_fit_preview_toggle_toggled(DialogicUtil.get_editor_setting('character_preview_fit', true))
+ %PreviewLabel.add_theme_color_override("font_color", get_theme_color("readonly_color", "Editor"))
+
+ %PortraitChangeWarning.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+
+ %RealPreviewPivot.texture = get_theme_icon("EditorPivot", "EditorIcons")
+
+ %MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
+
+ set_portrait_settings_position(DialogicUtil.get_editor_setting('portrait_settings_position', true))
+
+ await find_parent('EditorView').ready
+
+ ## Add general tabs
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn").instantiate(), %MainSettingsSections)
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn").instantiate(), %MainSettingsSections)
+
+
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn").instantiate(), %PortraitSettingsSection)
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn").instantiate(), %PortraitSettingsSection)
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn").instantiate(), %PortraitSettingsSection)
+ add_settings_section(load("res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn").instantiate(), %PortraitSettingsSection)
+
+ ## Load custom sections from modules
+ for indexer in DialogicUtil.get_indexers():
+ for path in indexer._get_character_editor_sections():
+ var scene: Control = load(path).instantiate()
+ if scene is DialogicCharacterEditorMainSection:
+ add_settings_section(scene, %MainSettingsSections)
+ elif scene is DialogicCharacterEditorPortraitSection:
+ add_settings_section(scene, %PortraitSettingsSection)
+
+
+## Add a section (a control) either to the given settings section (Main or Portraits)
+## - sets up the title of the section
+## - connects to various signals
+func add_settings_section(edit:Control, parent:Node) -> void:
+ edit.changed.connect(something_changed)
+ edit.character_editor = self
+ if edit.has_signal('update_preview'):
+ edit.update_preview.connect(update_preview)
+
+ var button: Button
+ if edit._show_title():
+ var hbox := HBoxContainer.new()
+ hbox.name = edit._get_title()+"BOX"
+ button = Button.new()
+ button.flat = true
+ button.theme_type_variation = "DialogicSection"
+ button.alignment = HORIZONTAL_ALIGNMENT_LEFT
+ button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
+ button.text = edit._get_title()
+ button.icon_alignment = HORIZONTAL_ALIGNMENT_RIGHT
+ button.pressed.connect(_on_section_button_pressed.bind(button))
+ button.focus_mode = Control.FOCUS_NONE
+ button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
+ button.add_theme_color_override('icon_normal_color', get_theme_color("font_color", "DialogicSection"))
+
+ hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ hbox.add_child(button)
+
+ if !edit.hint_text.is_empty():
+ var hint: Node = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
+ hint.hint_text = edit.hint_text
+ hbox.add_child(hint)
+
+ parent.add_child(hbox)
+ parent.add_child(edit)
+ parent.add_child(HSeparator.new())
+ if button and !edit._start_opened():
+ _on_section_button_pressed(button)
+
+
+func get_settings_section_by_name(name:String, main:=true) -> Node:
+ var parent := %MainSettingsSections
+ if not main:
+ parent = %PortraitSettingsSection
+
+ if parent.has_node(name):
+ return parent.get_node(name)
+ elif parent.has_node(name+"BOX/"+name):
+ return parent.get_node(name+"BOX/"+name)
+ else:
+ return null
+
+
+func _on_section_button_pressed(button:Button) -> void:
+ var section_header := button.get_parent()
+ var section := section_header.get_parent().get_child(section_header.get_index()+1)
+ if section.visible:
+ button.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
+ section.visible = false
+ else:
+ button.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
+ section.visible = true
+
+ if section_header.get_parent().get_child_count() > section_header.get_index()+2 and section_header.get_parent().get_child(section_header.get_index()+2) is Separator:
+ section_header.get_parent().get_child(section_header.get_index()+2).visible = section_header.get_parent().get_child(section_header.get_index()+1).visible
+
+
+func something_changed(fake_argument = "", fake_arg2 = null) -> void:
+ if not loading:
+ current_resource_state = ResourceStates.UNSAVED
+
+
+func _on_main_settings_collapse_toggled(button_pressed:bool) -> void:
+ %MainSettingsTitle.visible = !button_pressed
+ %MainSettingsScroll.visible = !button_pressed
+ if button_pressed:
+ %MainSettings.hide()
+ %MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
+ else:
+ %MainSettings.show()
+ %MainSettingsCollapse.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
+
+
+func _on_switch_portrait_settings_position_pressed() -> void:
+ set_portrait_settings_position(!%RightSection.vertical)
+
+
+func set_portrait_settings_position(is_below:bool) -> void:
+ %RightSection.vertical = is_below
+ DialogicUtil.set_editor_setting('portrait_settings_position', is_below)
+ if is_below:
+ %SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignRightWide", "EditorIcons")
+ else:
+ %SwitchPortraitSettingsPosition.icon = get_theme_icon("ControlAlignBottomWide", "EditorIcons")
+
+#endregion
+
+
+########## PORTRAIT SECTION ####################################################
+
+#region Portrait Section
+func setup_portrait_list_tab() -> void:
+ %PortraitTree.editor = self
+
+ ## Portrait section styling/connections
+ %AddPortraitButton.icon = get_theme_icon("Add", "EditorIcons")
+ %AddPortraitButton.pressed.connect(add_portrait)
+ %AddPortraitGroupButton.icon = load("res://addons/dialogic/Editor/Images/Pieces/add-folder.svg")
+ %AddPortraitGroupButton.pressed.connect(add_portrait_group)
+ %ImportPortraitsButton.icon = get_theme_icon("Load", "EditorIcons")
+ %ImportPortraitsButton.pressed.connect(open_portrait_folder_select)
+ %PortraitSearch.right_icon = get_theme_icon("Search", "EditorIcons")
+ %PortraitSearch.text_changed.connect(filter_portrait_list)
+
+ %PortraitTree.item_selected.connect(load_selected_portrait)
+ %PortraitTree.item_edited.connect(_on_item_edited)
+ %PortraitTree.item_activated.connect(_on_item_activated)
+
+
+func open_portrait_folder_select() -> void:
+ find_parent("EditorView").godot_file_dialog(
+ import_portraits_from_folder, "*.svg, *.png",
+ EditorFileDialog.FILE_MODE_OPEN_DIR)
+
+
+func import_portraits_from_folder(path:String) -> void:
+ var parent: TreeItem = %PortraitTree.get_root()
+
+ if %PortraitTree.get_selected() and %PortraitTree.get_selected() != parent and %PortraitTree.get_selected().get_metadata(0).has('group'):
+ parent = %PortraitTree.get_selected()
+
+ var dir := DirAccess.open(path)
+ dir.list_dir_begin()
+ var file_name: String = dir.get_next()
+ var files := []
+ while file_name != "":
+ if not dir.current_is_dir():
+ var file_lower := file_name.to_lower()
+ if '.svg' in file_lower or '.png' in file_lower:
+ if not '.import' in file_lower:
+ files.append(file_name)
+ file_name = dir.get_next()
+
+ var prefix: String = files[0]
+ for file in files:
+ while true:
+ if file.begins_with(prefix):
+ break
+ if prefix.is_empty():
+ break
+ prefix = prefix.substr(0, len(prefix)-1)
+
+ for file in files:
+ %PortraitTree.add_portrait_item(file.trim_prefix(prefix).trim_suffix('.'+file.get_extension()),
+ {'scene':"",'export_overrides':{'image':var_to_str(path.path_join(file))}, 'scale':1, 'offset':Vector2(), 'mirror':false}, parent)
+
+ ## Handle selection
+ if parent.get_child_count():
+ parent.get_first_child().select(0)
+ else:
+ # Call anyways to clear preview and hide portrait settings section
+ load_selected_portrait()
+
+ something_changed()
+
+
+func add_portrait(portrait_name:String='New portrait', portrait_data:Dictionary={'scene':"", 'export_overrides':{'image':''}, 'scale':1, 'offset':Vector2(), 'mirror':false}) -> void:
+ var parent: TreeItem = %PortraitTree.get_root()
+ if %PortraitTree.get_selected():
+ if %PortraitTree.get_selected().get_metadata(0) and %PortraitTree.get_selected().get_metadata(0).has('group'):
+ parent = %PortraitTree.get_selected()
+ else:
+ parent = %PortraitTree.get_selected().get_parent()
+ var item: TreeItem = %PortraitTree.add_portrait_item(portrait_name, portrait_data, parent)
+ item.set_meta('new', true)
+ item.set_editable(0, true)
+ item.select(0)
+ %PortraitTree.call_deferred('edit_selected')
+ something_changed()
+
+
+func add_portrait_group() -> void:
+ var parent_item: TreeItem = %PortraitTree.get_root()
+ if %PortraitTree.get_selected() and %PortraitTree.get_selected().get_metadata(0).has('group'):
+ parent_item = %PortraitTree.get_selected()
+ var item: TreeItem = %PortraitTree.add_portrait_group("Group", parent_item)
+ item.set_meta('new', true)
+ item.set_editable(0, true)
+ item.select(0)
+ %PortraitTree.call_deferred('edit_selected')
+
+
+func load_portrait_tree() -> void:
+ %PortraitTree.clear_tree()
+ var root: TreeItem = %PortraitTree.create_item()
+
+ for portrait in current_resource.portraits.keys():
+ var portrait_label: String = portrait
+ var parent: TreeItem = %PortraitTree.get_root()
+ if '/' in portrait:
+ parent = %PortraitTree.create_necessary_group_items(portrait)
+ portrait_label = portrait.split('/')[-1]
+
+ %PortraitTree.add_portrait_item(portrait_label, current_resource.portraits[portrait], parent)
+
+ update_default_portrait_star(current_resource.default_portrait)
+
+ if root.get_child_count():
+ root.get_first_child().select(0)
+ while %PortraitTree.get_selected().get_child_count():
+ %PortraitTree.get_selected().get_child(0).select(0)
+ else:
+ # Call anyways to clear preview and hide portrait settings section
+ load_selected_portrait()
+
+
+func filter_portrait_list(filter_term := "") -> void:
+ filter_branch(%PortraitTree.get_root(), filter_term)
+
+
+func filter_branch(parent: TreeItem, filter_term: String) -> bool:
+ var anything_visible := false
+ for item in parent.get_children():
+ if item.get_metadata(0).has('group'):
+ item.visible = filter_branch(item, filter_term)
+ anything_visible = item.visible
+ elif filter_term.is_empty() or filter_term.to_lower() in item.get_text(0).to_lower():
+ item.visible = true
+ anything_visible = true
+ else:
+ item.visible = false
+ return anything_visible
+
+
+## This is used to save the portrait data
+func get_updated_portrait_dict() -> Dictionary:
+ return list_portraits(%PortraitTree.get_root().get_children())
+
+
+func list_portraits(tree_items: Array[TreeItem], dict := {}, path_prefix := "") -> Dictionary:
+ for item in tree_items:
+ if item.get_metadata(0).has('group'):
+ dict = list_portraits(item.get_children(), dict, path_prefix+item.get_text(0)+"/")
+ else:
+ dict[path_prefix +item.get_text(0)] = item.get_metadata(0)
+ return dict
+
+
+func load_selected_portrait() -> void:
+ if selected_item and is_instance_valid(selected_item):
+ selected_item.set_editable(0, false)
+
+ selected_item = %PortraitTree.get_selected()
+
+ if selected_item and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
+ %PortraitSettingsSection.show()
+ var current_portrait_data: Dictionary = selected_item.get_metadata(0)
+ portrait_selected.emit(%PortraitTree.get_full_item_name(selected_item), current_portrait_data)
+
+ update_preview()
+
+ for child in %PortraitSettingsSection.get_children():
+ if child is DialogicCharacterEditorPortraitSection:
+ child.selected_item = selected_item
+ child._load_portrait_data(current_portrait_data)
+
+ else:
+ %PortraitSettingsSection.hide()
+ update_preview()
+
+
+func delete_portrait_item(item: TreeItem) -> void:
+ if item.get_next_visible(true) and item.get_next_visible(true) != item:
+ item.get_next_visible(true).select(0)
+ else:
+ selected_item = null
+ load_selected_portrait()
+ item.free()
+ something_changed()
+
+
+func duplicate_item(item: TreeItem) -> void:
+ var new_item: TreeItem = %PortraitTree.add_portrait_item(item.get_text(0)+'_duplicated', item.get_metadata(0).duplicate(true), item.get_parent())
+ new_item.set_meta('new', true)
+ new_item.select(0)
+
+
+func _input(event: InputEvent) -> void:
+ if !is_visible_in_tree() or (get_viewport().gui_get_focus_owner()!= null and !name+'/' in str(get_viewport().gui_get_focus_owner().get_path())):
+ return
+ if event is InputEventKey and event.pressed:
+ if event.keycode == KEY_F2 and %PortraitTree.get_selected():
+ %PortraitTree.get_selected().set_editable(0, true)
+ %PortraitTree.edit_selected()
+ get_viewport().set_input_as_handled()
+ elif event.keycode == KEY_DELETE and get_viewport().gui_get_focus_owner() is Tree and %PortraitTree.get_selected():
+ delete_portrait_item(%PortraitTree.get_selected())
+ get_viewport().set_input_as_handled()
+
+
+func _on_portrait_right_click_menu_index_pressed(id: int) -> void:
+ # RENAME BUTTON
+ if id == 0:
+ _on_item_activated()
+ # DELETE BUTTON
+ if id == 2:
+ delete_portrait_item(%PortraitTree.get_selected())
+ # DUPLICATE ITEM
+ elif id == 1:
+ duplicate_item(%PortraitTree.get_selected())
+ elif id == 4:
+ get_settings_section_by_name("Portraits").set_default_portrait(%PortraitTree.get_full_item_name(%PortraitTree.get_selected()))
+
+
+## This removes/and adds the DEFAULT star on the portrait list
+func update_default_portrait_star(default_portrait_name: String) -> void:
+ var item_list: Array = %PortraitTree.get_root().get_children()
+ if item_list.is_empty() == false:
+ while true:
+ var item: TreeItem = item_list.pop_back()
+ if item.get_button_by_id(0, 2) != -1:
+ item.erase_button(0, item.get_button_by_id(0, 2))
+ if %PortraitTree.get_full_item_name(item) == default_portrait_name:
+ item.add_button(0, get_theme_icon("Favorites", "EditorIcons"), 2, true, "Default")
+ item_list.append_array(item.get_children())
+ if item_list.is_empty():
+ break
+
+
+func _on_item_edited() -> void:
+ selected_item = %PortraitTree.get_selected()
+ something_changed()
+ if selected_item:
+ if %PreviewLabel.text.trim_prefix('Preview of "').trim_suffix('"') == current_resource.default_portrait:
+ current_resource.default_portrait = %PortraitTree.get_full_item_name(selected_item)
+ selected_item.set_editable(0, false)
+
+ if !selected_item.has_meta('new') and %PortraitTree.get_full_item_name(selected_item) != selected_item.get_meta('previous_name'):
+ report_name_change(selected_item)
+ %PortraitChangeInfo.show()
+ update_preview()
+
+
+func _on_item_activated() -> void:
+ if %PortraitTree.get_selected() == null:
+ return
+ %PortraitTree.get_selected().set_editable(0, true)
+ %PortraitTree.edit_selected()
+
+
+func report_name_change(item: TreeItem) -> void:
+ if item.get_metadata(0).has('group'):
+ for s_item in item.get_children():
+ if s_item.get_metadata(0).has('group') or !s_item.has_meta('new'):
+ report_name_change(s_item)
+ else:
+ if item.get_meta('previous_name') == %PortraitTree.get_full_item_name(item):
+ return
+ editors_manager.reference_manager.add_portrait_ref_change(
+ item.get_meta('previous_name'),
+ %PortraitTree.get_full_item_name(item),
+ [DialogicResourceUtil.get_unique_identifier(current_resource.resource_path)])
+ item.set_meta('previous_name', %PortraitTree.get_full_item_name(item))
+ %PortraitChangeInfo.show()
+
+#endregion
+
+########### PREVIEW ############################################################
+
+#region Preview
+func update_preview(force := false, ignore_settings_reload := false) -> void:
+ %ScenePreviewWarning.hide()
+
+ if selected_item and is_instance_valid(selected_item) and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'):
+ %PreviewLabel.text = 'Preview of "'+%PortraitTree.get_full_item_name(selected_item)+'"'
+
+ var current_portrait_data: Dictionary = selected_item.get_metadata(0)
+
+ if not force and current_previewed_scene != null \
+ and scene_file_path == current_portrait_data.get('scene') \
+ and current_previewed_scene.has_method('_should_do_portrait_update') \
+ and is_instance_valid(current_previewed_scene.get_script()) \
+ and current_previewed_scene._should_do_portrait_update(current_resource, selected_item.get_text(0)):
+ # We keep the same scene.
+ pass
+ else:
+
+ for node in %RealPreviewPivot.get_children():
+ node.queue_free()
+
+ current_previewed_scene = null
+ current_scene_path = ""
+
+ var scene_path := def_portrait_path
+ if not current_portrait_data.get('scene', '').is_empty():
+ scene_path = current_portrait_data.get('scene')
+
+ if ResourceLoader.exists(scene_path):
+ current_previewed_scene = load(scene_path).instantiate()
+ current_scene_path = scene_path
+
+ if not current_previewed_scene == null:
+ %RealPreviewPivot.add_child(current_previewed_scene)
+
+ if not current_previewed_scene == null:
+ var scene: Node = current_previewed_scene
+
+ scene.show_behind_parent = true
+ DialogicUtil.apply_scene_export_overrides(scene, current_portrait_data.get('export_overrides', {}))
+
+ var mirror: bool = current_portrait_data.get('mirror', false) != current_resource.mirror
+ var scale: float = current_portrait_data.get('scale', 1) * current_resource.scale
+
+ if current_portrait_data.get('ignore_char_scale', false):
+ scale = current_portrait_data.get('scale', 1)
+
+ var offset: Vector2 = current_portrait_data.get('offset', Vector2()) + current_resource.offset
+
+ if is_instance_valid(scene.get_script()) and scene.script.is_tool():
+
+ if scene.has_method('_update_portrait'):
+ ## Create a fake duplicate resource that has all the portrait changes applied already
+ var preview_character := current_resource.duplicate()
+ preview_character.portraits = get_updated_portrait_dict()
+ scene._update_portrait(preview_character, %PortraitTree.get_full_item_name(selected_item))
+
+ if scene.has_method('_set_mirror'):
+ scene._set_mirror(mirror)
+
+ if !%FitPreview_Toggle.button_pressed:
+ scene.position = Vector2() + offset
+ scene.scale = Vector2(1,1)*scale
+ else:
+
+ if not scene.get_script() == null and scene.script.is_tool() and scene.has_method('_get_covered_rect'):
+ var rect: Rect2 = scene._get_covered_rect()
+ var available_rect: Rect2 = %FullPreviewAvailableRect.get_rect()
+ scene.scale = Vector2(1,1) * min(available_rect.size.x/rect.size.x, available_rect.size.y/rect.size.y)
+ %RealPreviewPivot.position = (rect.position)*-1*scene.scale
+ %RealPreviewPivot.position.x = %FullPreviewAvailableRect.size.x/2
+ scene.position = Vector2()
+
+ else:
+ %ScenePreviewWarning.show()
+ else:
+ %PreviewLabel.text = 'Nothing to preview'
+
+ if not ignore_settings_reload:
+ for child in %PortraitSettingsSection.get_children():
+ if child is DialogicCharacterEditorPortraitSection:
+ child._recheck(current_portrait_data)
+
+ else:
+ %PreviewLabel.text = 'No portrait to preview.'
+
+ for node in %RealPreviewPivot.get_children():
+ node.queue_free()
+
+ current_previewed_scene = null
+ current_scene_path = ""
+
+
+func _on_some_resource_saved(file:Variant) -> void:
+ if current_previewed_scene == null:
+ return
+
+ if file is Resource and file == current_previewed_scene.script:
+ update_preview(true)
+
+ if typeof(file) == TYPE_STRING and file == current_previewed_scene.get_meta("path", ""):
+ update_preview(true)
+
+
+func _on_full_preview_available_rect_resized() -> void:
+ if %FitPreview_Toggle.button_pressed:
+ update_preview(false, true)
+
+
+func _on_create_character_button_pressed() -> void:
+ editors_manager.show_add_resource_dialog(
+ new_character,
+ '*.dch; DialogicCharacter',
+ 'Create new character',
+ 'character',
+ )
+
+
+func _on_fit_preview_toggle_toggled(button_pressed):
+ %FitPreview_Toggle.set_pressed_no_signal(button_pressed)
+ if button_pressed:
+ %FitPreview_Toggle.icon = get_theme_icon("ScrollContainer", "EditorIcons")
+ %FitPreview_Toggle.tooltip_text = "Real scale"
+ else:
+ %FitPreview_Toggle.tooltip_text = "Fit into preview"
+ %FitPreview_Toggle.icon = get_theme_icon("CenterContainer", "EditorIcons")
+ DialogicUtil.set_editor_setting('character_preview_fit', button_pressed)
+ update_preview(false, true)
+
+#endregion
+
+## Open the reference manager
+func _on_reference_manger_button_pressed() -> void:
+ editors_manager.reference_manager.open()
+ %PortraitChangeInfo.hide()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.tscn
new file mode 100644
index 0000000..cef90fd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor.tscn
@@ -0,0 +1,456 @@
+[gd_scene load_steps=11 format=3 uid="uid://dlskc36c5hrwv"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor.gd" id="2"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_uhhqs"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd" id="2_vad0i"]
+[ext_resource type="Texture2D" uid="uid://babwe22dqjta" path="res://addons/dialogic/Editor/Images/Pieces/add-folder.svg" id="3_v1qnr"]
+
+[sub_resource type="Image" id="Image_s4mcg"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_oab13"]
+image = SubResource("Image_s4mcg")
+
+[sub_resource type="Image" id="Image_fnxud"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_u1a6g"]
+image = SubResource("Image_fnxud")
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_es2rd"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4xgdx"]
+
+[node name="CharacterEditor" type="Control"]
+self_modulate = Color(0, 0, 0, 1)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("2")
+
+[node name="Scroll" type="ScrollContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBox" type="VBoxContainer" parent="Scroll"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.3
+theme_override_constants/separation = 0
+
+[node name="TopSection" type="HBoxContainer" parent="Scroll/VBox"]
+layout_mode = 2
+
+[node name="NameContainer" type="HBoxContainer" parent="Scroll/VBox/TopSection"]
+layout_mode = 2
+
+[node name="CharacterName" type="Label" parent="Scroll/VBox/TopSection/NameContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicTitle"
+text = "My Character"
+
+[node name="NameTooltip" parent="Scroll/VBox/TopSection/NameContainer" instance=ExtResource("2_uhhqs")]
+layout_mode = 2
+tooltip_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
+Use this name in timelines to reference this character."
+texture = SubResource("ImageTexture_oab13")
+hint_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
+Use this name in timelines to reference this character."
+
+[node name="MainSettingsCollapse" type="Button" parent="Scroll/VBox/TopSection"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+toggle_mode = true
+text = "Main Settings"
+icon = SubResource("ImageTexture_u1a6g")
+
+[node name="MainHSplit" type="HSplitContainer" parent="Scroll/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="MainSettings" type="VBoxContainer" parent="Scroll/VBox/MainHSplit"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+
+[node name="MainSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/MainSettings"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_type_variation = &"DialogicSubTitle"
+text = "Main Settings"
+
+[node name="MainSettingsScroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/MainSettings"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_es2rd")
+horizontal_scroll_mode = 0
+
+[node name="MainSettingsSections" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/MainSettings/MainSettingsScroll"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Split" type="HSplitContainer" parent="Scroll/VBox/MainHSplit"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+theme_override_constants/separation = 0
+
+[node name="MarginContainer" type="MarginContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+theme_override_constants/margin_bottom = 10
+
+[node name="PortraitListSection" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_type_variation = &"DialogicPanelA"
+
+[node name="Portraits" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection"]
+layout_mode = 2
+
+[node name="PortraitsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+text = "Portraits"
+
+[node name="PortraitListTools" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
+layout_mode = 2
+
+[node name="AddPortraitButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Add portrait"
+icon = SubResource("ImageTexture_u1a6g")
+
+[node name="AddPortraitGroupButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Add Group"
+icon = ExtResource("3_v1qnr")
+
+[node name="ImportPortraitsButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Import images from folder"
+icon = SubResource("ImageTexture_u1a6g")
+
+[node name="PortraitSearch" type="LineEdit" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitListTools"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+placeholder_text = "Search"
+expand_to_text_length = true
+clear_button_enabled = true
+right_icon = SubResource("ImageTexture_u1a6g")
+caret_blink = true
+caret_blink_interval = 0.5
+
+[node name="PortraitTreePanel" type="PanelContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_4xgdx")
+
+[node name="PortraitTree" type="Tree" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel"]
+unique_name_in_owner = true
+layout_mode = 2
+allow_rmb_select = true
+hide_root = true
+drop_mode_flags = 3
+script = ExtResource("2_vad0i")
+
+[node name="PortraitRightClickMenu" type="PopupMenu" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree"]
+size = Vector2i(118, 100)
+item_count = 5
+item_0/text = "Rename"
+item_0/icon = SubResource("ImageTexture_oab13")
+item_0/id = 2
+item_1/text = "Duplicate"
+item_1/icon = SubResource("ImageTexture_oab13")
+item_1/id = 0
+item_2/text = "Delete"
+item_2/icon = SubResource("ImageTexture_oab13")
+item_2/id = 1
+item_3/text = ""
+item_3/id = 3
+item_3/separator = true
+item_4/text = "Make Default"
+item_4/icon = SubResource("ImageTexture_oab13")
+item_4/id = 4
+
+[node name="PortraitChangeInfo" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="PortraitChangeWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+text = "Some portraits were renamed. Make sure no references broke!"
+autowrap_mode = 3
+
+[node name="ReferenceMangerButton" type="Button" parent="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+text = "Reference
+Manager"
+
+[node name="RightSection2" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.5
+
+[node name="Spacer" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="RightSection" type="SplitContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.5
+vertical = true
+
+[node name="PortraitPreviewSection" type="Panel" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
+unique_name_in_owner = true
+show_behind_parent = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_type_variation = &"DialogicPanelB"
+
+[node name="ClipRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Node2D" type="Node2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect"]
+position = Vector2(13, 17)
+
+[node name="RealPreviewPivot" type="Sprite2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/ClipRect/Node2D"]
+unique_name_in_owner = true
+position = Vector2(326.5, 267)
+texture = SubResource("ImageTexture_u1a6g")
+
+[node name="ScenePreviewWarning" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -143.0
+offset_top = -44.5
+offset_right = 143.0
+offset_bottom = 85.5
+grow_horizontal = 2
+grow_vertical = 2
+text = "Custom scenes can only be viewed in \"Full mode\" if they are in @tool mode and override _get_covered_rect"
+horizontal_alignment = 1
+vertical_alignment = 1
+autowrap_mode = 3
+metadata/_edit_layout_mode = 1
+
+[node name="PreviewReal" type="CenterContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -302.0
+offset_top = -80.0
+offset_right = 302.0
+grow_horizontal = 2
+grow_vertical = 0
+mouse_filter = 2
+metadata/_edit_layout_mode = 1
+
+[node name="Control" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal"]
+layout_mode = 2
+
+[node name="RealSizeRemotePivotTransform" type="RemoteTransform2D" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/PreviewReal/Control"]
+unique_name_in_owner = true
+remote_path = NodePath("../../../ClipRect/Node2D/RealPreviewPivot")
+update_rotation = false
+update_scale = false
+
+[node name="FullPreviewAvailableRect" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 10.0
+offset_top = 28.0
+offset_right = -10.0
+offset_bottom = -16.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+metadata/_edit_layout_mode = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_left = 6.0
+offset_top = 7.0
+offset_right = -6.0
+offset_bottom = 43.0
+grow_horizontal = 2
+mouse_filter = 2
+
+[node name="PreviewLabel" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
+unique_name_in_owner = true
+show_behind_parent = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+text = "No portrait to preview."
+text_overrun_behavior = 1
+
+[node name="FitPreview_Toggle" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 0
+tooltip_text = "Real scale"
+focus_mode = 0
+toggle_mode = true
+button_pressed = true
+icon = SubResource("ImageTexture_u1a6g")
+flat = true
+metadata/_edit_layout_mode = 1
+
+[node name="VBox" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.75
+
+[node name="Hbox" type="HBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
+layout_mode = 2
+
+[node name="PortraitSettingsTitle" type="Label" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+text = "Portrait Settings"
+
+[node name="SwitchPortraitSettingsPosition" type="Button" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox"]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.647059)
+layout_mode = 2
+tooltip_text = "Switch position"
+focus_mode = 0
+icon = SubResource("ImageTexture_u1a6g")
+flat = true
+
+[node name="Scroll" type="ScrollContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox"]
+layout_mode = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.4
+
+[node name="PortraitSettingsSection" type="VBoxContainer" parent="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Scroll"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.3
+
+[node name="Spacer2" type="Control" parent="Scroll/VBox/MainHSplit/Split/RightSection2"]
+custom_minimum_size = Vector2(0, 20)
+layout_mode = 2
+
+[node name="NoCharacterScreen" type="ColorRect" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+color = Color(0, 0, 0, 1)
+
+[node name="CenterContainer" type="CenterContainer" parent="NoCharacterScreen"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="NoCharacterScreen/CenterContainer"]
+custom_minimum_size = Vector2(250, 0)
+layout_mode = 2
+
+[node name="Label" type="Label" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "No character opened.
+Create a character or double-click one in the file system dock."
+horizontal_alignment = 1
+autowrap_mode = 3
+
+[node name="CreateCharacterButton" type="Button" parent="NoCharacterScreen/CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "Create New Character"
+
+[connection signal="toggled" from="Scroll/VBox/TopSection/MainSettingsCollapse" to="." method="_on_main_settings_collapse_toggled"]
+[connection signal="item_mouse_selected" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" to="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree" method="_on_item_mouse_selected"]
+[connection signal="index_pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitTreePanel/PortraitTree/PortraitRightClickMenu" to="." method="_on_portrait_right_click_menu_index_pressed"]
+[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/HBoxContainer/MarginContainer/PortraitListSection/Portraits/PortraitChangeInfo/ReferenceMangerButton" to="." method="_on_reference_manger_button_pressed"]
+[connection signal="resized" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/FullPreviewAvailableRect" to="." method="_on_full_preview_available_rect_resized"]
+[connection signal="toggled" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/PortraitPreviewSection/HBoxContainer/FitPreview_Toggle" to="." method="_on_fit_preview_toggle_toggled"]
+[connection signal="pressed" from="Scroll/VBox/MainHSplit/Split/RightSection2/RightSection/VBox/Hbox/SwitchPortraitSettingsPosition" to="." method="_on_switch_portrait_settings_position_pressed"]
+[connection signal="pressed" from="NoCharacterScreen/CenterContainer/VBoxContainer/CreateCharacterButton" to="." method="_on_create_character_button_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd
new file mode 100644
index 0000000..d329d06
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd
@@ -0,0 +1,41 @@
+@tool
+class_name DialogicCharacterEditorMainSection
+extends Control
+
+## Base class for all character editor main sections. Methods should be overriden.
+
+## Emit this, if something changed
+signal changed
+
+## Reference to the character editor, set when instantiated
+var character_editor: Control
+
+## If not empty, a hint icon is added to the section title.
+var hint_text := ""
+
+
+## Overwrite to set the title of this section
+func _get_title() -> String:
+ return "MainSection"
+
+
+## Overwrite to set the visibility of the section title
+func _show_title() -> bool:
+ return true
+
+
+## Overwrite to set whether this should initially be opened.
+func _start_opened() -> bool:
+ return false
+
+
+## Overwrite to load all the information from the character into this section.
+func _load_character(resource:DialogicCharacter) -> void:
+ pass
+
+
+## Overwrite to save all changes made in this section to the resource.
+## In custom sections you will mostly likely save to the [resource.custom_info]
+## dictionary.
+func _save_changes(resource:DialogicCharacter) -> DialogicCharacter:
+ return resource
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd
new file mode 100644
index 0000000..920f8a5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd
@@ -0,0 +1,48 @@
+@tool
+class_name DialogicCharacterEditorPortraitSection
+extends Control
+
+## Base class for all portrait settings sections. Methods should be overriden.
+## Changes made through fields in such a section should instantly be "saved"
+## to the portrait_items metadata from where they will be saved to the resource.
+
+## Emit this, if something changed
+signal changed
+## Emit this if the preview should reload
+signal update_preview
+
+## Reference to the character editor, set when instantiated
+var character_editor: Control
+## Reference to the selected portrait item.
+## `selected_item.get_metadata(0)` can access the portraits data
+var selected_item: TreeItem = null
+
+## If not empty a hint icon is added to the section title
+var hint_text := ""
+
+
+## Overwrite to set the title of this section
+func _get_title() -> String:
+ return "CustomSection"
+
+
+## Overwrite to set the visibility of the section title
+func _show_title() -> bool:
+ return true
+
+
+## Overwrite to set whether this should initially be opened.
+func _start_opened() -> bool:
+ return false
+
+
+## Overwrite to load all the information from the character into this section.
+func _load_portrait_data(data:Dictionary) -> void:
+ pass
+
+
+## Overwrite to recheck visibility of your section and the content of your fields.
+## This is called whenever the preview is updated so it allows reacting to major
+## changes in other portrait sections.
+func _recheck(data:Dictionary) -> void:
+ pass
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd
new file mode 100644
index 0000000..c6fcf46
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd
@@ -0,0 +1,138 @@
+@tool
+extends Tree
+
+## Tree that displays the portrait list as a hirarchy
+
+var editor := find_parent('Character Editor')
+var current_group_nodes := {}
+
+
+func _ready() -> void:
+ $PortraitRightClickMenu.set_item_icon(0, get_theme_icon('Rename', 'EditorIcons'))
+ $PortraitRightClickMenu.set_item_icon(1, get_theme_icon('Duplicate', 'EditorIcons'))
+ $PortraitRightClickMenu.set_item_icon(2, get_theme_icon('Remove', 'EditorIcons'))
+ $PortraitRightClickMenu.set_item_icon(4, get_theme_icon("Favorites", "EditorIcons"))
+
+
+func clear_tree() -> void:
+ clear()
+ current_group_nodes = {}
+
+
+func add_portrait_item(portrait_name: String, portrait_data: Dictionary, parent_item: TreeItem, previous_name := "") -> TreeItem:
+ var item: TreeItem = %PortraitTree.create_item(parent_item)
+ item.set_text(0, portrait_name)
+ item.set_metadata(0, portrait_data)
+ if previous_name.is_empty():
+ item.set_meta('previous_name', get_full_item_name(item))
+ else:
+ item.set_meta('previous_name', previous_name)
+ if portrait_name == editor.current_resource.default_portrait:
+ item.add_button(0, get_theme_icon('Favorites', 'EditorIcons'), 2, true, 'Default')
+ return item
+
+
+func add_portrait_group(goup_name := "Group", parent_item: TreeItem = get_root(), previous_name := "") -> TreeItem:
+ var item: TreeItem = %PortraitTree.create_item(parent_item)
+ item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
+ item.set_text(0, goup_name)
+ item.set_metadata(0, {'group':true})
+ if previous_name.is_empty():
+ item.set_meta('previous_name', get_full_item_name(item))
+ else:
+ item.set_meta('previous_name', previous_name)
+ return item
+
+
+func get_full_item_name(item: TreeItem) -> String:
+ var item_name := item.get_text(0)
+ while item.get_parent() != get_root() and item != get_root():
+ item_name = item.get_parent().get_text(0)+"/"+item_name
+ item = item.get_parent()
+ return item_name
+
+
+## Will create all not yet existing folders in the given path.
+## Returns the last folder (the parent of the portrait item of this path).
+func create_necessary_group_items(path: String) -> TreeItem:
+ var last_item := get_root()
+ var item_path := ""
+
+ for i in Array(path.split('/')).slice(0, -1):
+ item_path += "/"+i
+ item_path = item_path.trim_prefix('/')
+ if current_group_nodes.has(item_path+"/"+i):
+ last_item = current_group_nodes[item_path+"/"+i]
+ else:
+ var new_item: TreeItem = add_portrait_group(i, last_item)
+ current_group_nodes[item_path+"/"+i] = new_item
+ last_item = new_item
+ return last_item
+
+
+func _on_item_mouse_selected(pos: Vector2, mouse_button_index: int) -> void:
+ if mouse_button_index == MOUSE_BUTTON_RIGHT:
+ $PortraitRightClickMenu.set_item_disabled(1, get_selected().get_metadata(0).has('group'))
+ $PortraitRightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(),Vector2()))
+
+
+################################################################################
+## DRAG AND DROP
+################################################################################
+
+func _get_drag_data(position: Vector2) -> Variant:
+ drop_mode_flags = DROP_MODE_INBETWEEN
+ var preview := Label.new()
+ preview.text = " "+get_selected().get_text(0)
+ preview.add_theme_stylebox_override('normal', get_theme_stylebox("Background", "EditorStyles"))
+ set_drag_preview(preview)
+
+ return get_selected()
+
+
+func _can_drop_data(position: Vector2, data: Variant) -> bool:
+ return data is TreeItem
+
+
+func _drop_data(position: Vector2, item: Variant) -> void:
+ var to_item := get_item_at_position(position)
+ if to_item:
+ var test_item := to_item
+ while true:
+ if test_item == item:
+ return
+ test_item = test_item.get_parent()
+ if test_item == get_root():
+ break
+
+ var drop_section := get_drop_section_at_position(position)
+ var parent := get_root()
+ if to_item:
+ parent = to_item.get_parent()
+
+ if to_item and to_item.get_metadata(0).has('group') and drop_section == 1:
+ parent = to_item
+
+ var new_item := copy_branch_or_item(item, parent)
+
+ if to_item and !to_item.get_metadata(0).has('group') and drop_section == 1:
+ new_item.move_after(to_item)
+
+ if drop_section == -1:
+ new_item.move_before(to_item)
+
+ editor.report_name_change(new_item)
+
+ item.free()
+
+
+func copy_branch_or_item(item: TreeItem, new_parent: TreeItem) -> TreeItem:
+ var new_item: TreeItem = null
+ if item.get_metadata(0).has('group'):
+ new_item = add_portrait_group(item.get_text(0), new_parent, item.get_meta('previous_name'))
+ else:
+ new_item = add_portrait_item(item.get_text(0), item.get_metadata(0), new_parent, item.get_meta('previous_name'))
+
+ for child in item.get_children():
+ copy_branch_or_item(child, new_item)
+ return new_item
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd
new file mode 100644
index 0000000..119f813
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd
@@ -0,0 +1,126 @@
+@tool
+extends Control
+
+var ListItem := load("res://addons/dialogic/Editor/Common/BrowserItem.tscn")
+
+enum Types {ALL, GENERAL, PRESET}
+var current_type := Types.ALL
+var current_info := {}
+
+var portrait_scenes_info := {}
+
+signal activate_part(part_info:Dictionary)
+
+
+func _ready() -> void:
+ collect_portrait_scenes()
+
+ %Search.right_icon = get_theme_icon("Search", "EditorIcons")
+ %CloseButton.icon = get_theme_icon("Close", "EditorIcons")
+
+ get_parent().close_requested.connect(_on_close_button_pressed)
+ get_parent().visibility_changed.connect(func():if get_parent().visible: open())
+
+
+func collect_portrait_scenes() -> void:
+ for indexer in DialogicUtil.get_indexers():
+ for element in indexer._get_portrait_scene_presets():
+ portrait_scenes_info[element.get('path', '')] = element
+
+
+func open() -> void:
+ collect_portrait_scenes()
+ load_parts()
+
+
+func is_premade_portrait_scene(scene_path:String) -> bool:
+ return scene_path in portrait_scenes_info
+
+
+func load_parts() -> void:
+ for i in %PartGrid.get_children():
+ i.queue_free()
+
+ %Search.placeholder_text = "Search for "
+ %Search.text = ""
+ match current_type:
+ Types.GENERAL: %Search.placeholder_text += "general portrait scenes"
+ Types.PRESET: %Search.placeholder_text += "portrait scene presets"
+ Types.ALL: %Search.placeholder_text += "general portrait scenes and presets"
+
+ for info in portrait_scenes_info.values():
+ var type: String = info.get('type', '_')
+ if (current_type == Types.GENERAL and type != "General") or (current_type == Types.PRESET and type != "Preset"):
+ continue
+
+ var item: Node = ListItem.instantiate()
+ item.load_info(info)
+ %PartGrid.add_child(item)
+ item.set_meta('info', info)
+ item.clicked.connect(_on_item_clicked.bind(item, info))
+ item.focused.connect(_on_item_clicked.bind(item, info))
+ item.double_clicked.connect(emit_signal.bind('activate_part', info))
+
+ await get_tree().process_frame
+
+ if %PartGrid.get_child_count() > 0:
+ %PartGrid.get_child(0).clicked.emit()
+ %PartGrid.get_child(0).grab_focus()
+
+
+func _on_item_clicked(item: Node, info:Dictionary) -> void:
+ load_part_info(info)
+
+
+func load_part_info(info:Dictionary) -> void:
+ current_info = info
+ %PartTitle.text = info.get('name', 'Unknown Part')
+ %PartAuthor.text = "by "+info.get('author', 'Anonymus')
+ %PartDescription.text = info.get('description', '')
+
+ if info.get('preview_image', null) and ResourceLoader.exists(info.preview_image[0]):
+ %PreviewImage.texture = load(info.preview_image[0])
+ %PreviewImage.show()
+ else:
+ %PreviewImage.hide()
+
+ match info.type:
+ "General":
+ %ActivateButton.text = "Use this scene"
+ %TypeDescription.text = "This is a general use scene, it can be used directly."
+ "Preset":
+ %ActivateButton.text = "Customize this scene"
+ %TypeDescription.text = "This is a preset you can use for a custom portrait scene. Dialogic will promt you to save a copy of this scene that you can then use and customize."
+ "Default":
+ %ActivateButton.text = "Use default scene"
+ %TypeDescription.text = ""
+ "Custom":
+ %ActivateButton.text = "Select a custom scene"
+ %TypeDescription.text = ""
+
+ if info.get("documentation", ""):
+ %DocumentationButton.show()
+ %DocumentationButton.uri = info.documentation
+ else:
+ %DocumentationButton.hide()
+
+
+func _on_activate_button_pressed() -> void:
+ activate_part.emit(current_info)
+
+
+func _on_close_button_pressed() -> void:
+ get_parent().hide()
+
+
+func _on_search_text_changed(new_text: String) -> void:
+ for item in %PartGrid.get_children():
+ if new_text.is_empty():
+ item.show()
+ continue
+
+ if new_text.to_lower() in item.get_meta('info').name.to_lower():
+ item.show()
+ continue
+
+ item.hide()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn
new file mode 100644
index 0000000..3e4ecc1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn
@@ -0,0 +1,260 @@
+[gd_scene load_steps=11 format=3 uid="uid://b1wn8r84uh11b"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd" id="1_an6nc"]
+
+[sub_resource type="Gradient" id="Gradient_0o1u0"]
+colors = PackedColorArray(0.100572, 0.303996, 0.476999, 1, 0.296448, 0.231485, 0.52887, 1)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_gxpvv"]
+gradient = SubResource("Gradient_0o1u0")
+fill = 2
+fill_from = Vector2(0.478632, 1)
+fill_to = Vector2(0, 0)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_we8bq"]
+content_margin_left = 6.0
+content_margin_top = 3.0
+content_margin_right = 6.0
+content_margin_bottom = 3.0
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(1, 1, 1, 0.615686)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3x0xw"]
+content_margin_left = 6.0
+content_margin_top = 3.0
+content_margin_right = 6.0
+content_margin_bottom = 3.0
+draw_center = false
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+border_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 2.0
+expand_margin_top = 2.0
+expand_margin_right = 2.0
+expand_margin_bottom = 2.0
+
+[sub_resource type="Image" id="Image_lwe0k"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_d2gam"]
+image = SubResource("Image_lwe0k")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lf1ht"]
+bg_color = Color(0.0588235, 0.0313726, 0.0980392, 1)
+border_width_left = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a5iyu"]
+bg_color = Color(1, 1, 1, 1)
+draw_center = false
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+shadow_color = Color(0.992157, 0.992157, 0.992157, 0.101961)
+shadow_size = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_htwsp"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[node name="PortraitSceneBrowser" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_an6nc")
+
+[node name="BGColor" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = SubResource("GradientTexture2D_gxpvv")
+
+[node name="HSplitContainer" type="HSplitContainer" parent="."]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+
+[node name="Margin" type="MarginContainer" parent="HSplitContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 1.5
+theme_override_constants/margin_left = 10
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="VBox" type="VBoxContainer" parent="HSplitContainer/Margin"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="BrowserTitle" type="Label" parent="HSplitContainer/Margin/VBox"]
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+theme_override_font_sizes/font_size = 25
+text = "Dialogic Portrait Scene Browser"
+
+[node name="HBox" type="HBoxContainer" parent="HSplitContainer/Margin/VBox"]
+layout_mode = 2
+
+[node name="Search" type="LineEdit" parent="HSplitContainer/Margin/VBox/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_styles/normal = SubResource("StyleBoxFlat_we8bq")
+theme_override_styles/focus = SubResource("StyleBoxFlat_3x0xw")
+placeholder_text = "Search"
+right_icon = SubResource("ImageTexture_d2gam")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="HSplitContainer/Margin/VBox"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="PartGrid" type="HFlowContainer" parent="HSplitContainer/Margin/VBox/ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Buttons" type="HBoxContainer" parent="HSplitContainer/Margin/VBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="CloseButton" type="Button" parent="HSplitContainer/Margin/VBox/Buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Close"
+icon = SubResource("ImageTexture_d2gam")
+
+[node name="PanelContainer" type="PanelContainer" parent="HSplitContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_lf1ht")
+
+[node name="Control" type="Control" parent="HSplitContainer/PanelContainer"]
+layout_mode = 2
+
+[node name="Panel" type="Panel" parent="HSplitContainer/PanelContainer/Control"]
+layout_mode = 1
+anchors_preset = 9
+anchor_bottom = 1.0
+offset_left = -4.0
+offset_right = 40.0
+offset_bottom = 71.0
+grow_vertical = 2
+rotation = 0.0349066
+theme_override_styles/panel = SubResource("StyleBoxFlat_lf1ht")
+
+[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 5
+theme_override_constants/margin_top = 10
+theme_override_constants/margin_right = 10
+theme_override_constants/margin_bottom = 10
+
+[node name="VBox" type="VBoxContainer" parent="HSplitContainer/PanelContainer/MarginContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="Panel" type="PanelContainer" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_a5iyu")
+
+[node name="Panel" type="PanelContainer" parent="HSplitContainer/PanelContainer/MarginContainer/VBox/Panel"]
+clip_children = 1
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_htwsp")
+
+[node name="PreviewImage" type="TextureRect" parent="HSplitContainer/PanelContainer/MarginContainer/VBox/Panel/Panel"]
+unique_name_in_owner = true
+layout_mode = 2
+expand_mode = 5
+stretch_mode = 6
+
+[node name="HFlowContainer" type="HFlowContainer" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+layout_mode = 2
+
+[node name="PartTitle" type="Label" parent="HSplitContainer/PanelContainer/MarginContainer/VBox/HFlowContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 8
+theme_type_variation = &"DialogicTitle"
+text = "Cool Style Part"
+
+[node name="PartAuthor" type="Label" parent="HSplitContainer/PanelContainer/MarginContainer/VBox/HFlowContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 8
+theme_type_variation = &"DialogicHintText"
+text = "by Jowan"
+
+[node name="PartType" type="Label" parent="HSplitContainer/PanelContainer/MarginContainer/VBox/HFlowContainer"]
+visible = false
+layout_mode = 2
+size_flags_vertical = 8
+theme_type_variation = &"DialogicHintText"
+text = "a style"
+
+[node name="PartDescription" type="Label" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicHintText2"
+text = "A cool textbox layer"
+autowrap_mode = 3
+
+[node name="DocumentationButton" type="LinkButton" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Learn more"
+
+[node name="HSeparator" type="HSeparator" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+layout_mode = 2
+
+[node name="ActivateButton" type="Button" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Use"
+
+[node name="TypeDescription" type="Label" parent="HSplitContainer/PanelContainer/MarginContainer/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicHintText"
+text = "A cool textbox layer"
+autowrap_mode = 3
+
+[connection signal="text_changed" from="HSplitContainer/Margin/VBox/HBox/Search" to="." method="_on_search_text_changed"]
+[connection signal="pressed" from="HSplitContainer/Margin/VBox/Buttons/CloseButton" to="." method="_on_close_button_pressed"]
+[connection signal="pressed" from="HSplitContainer/PanelContainer/MarginContainer/VBox/ActivateButton" to="." method="_on_activate_button_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.gd
new file mode 100644
index 0000000..7bfb3cf
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.gd
@@ -0,0 +1,86 @@
+@tool
+extends Container
+
+signal clicked
+signal middle_clicked
+signal double_clicked
+signal focused
+
+var base_size := 1
+
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ %Name.add_theme_font_override("font", get_theme_font("bold", "EditorFonts"))
+ custom_minimum_size = base_size * Vector2(200, 150) * DialogicUtil.get_editor_scale()
+ %CurrentIcon.texture = get_theme_icon("Favorites", "EditorIcons")
+ if %Image.texture == null:
+ %Image.texture = get_theme_icon("ImportFail", "EditorIcons")
+ %Image.stretch_mode = TextureRect.STRETCH_KEEP_CENTERED
+
+
+func load_info(info:Dictionary) -> void:
+ %Name.text = info.name
+ if not info.has("preview_image"):
+ pass
+ elif info.preview_image[0] == 'custom':
+ await ready
+ %Image.texture = get_theme_icon("CreateNewSceneFrom", "EditorIcons")
+ %Image.stretch_mode = TextureRect.STRETCH_KEEP_CENTERED
+ %Panel.self_modulate = get_theme_color("property_color_z", "Editor")
+ elif info.preview_image[0].ends_with('scn'):
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().get_resource_previewer().queue_resource_preview(info.preview_image[0], self, 'set_scene_preview', null)
+ elif ResourceLoader.exists(info.preview_image[0]):
+ %Image.texture = load(info.preview_image[0])
+ elif info.preview_image[0].is_valid_html_color():
+ %Image.texture = null
+ %Panel.self_modulate = Color(info.preview_image[0])
+
+ if ResourceLoader.exists(info.get('icon', '')):
+ %Icon.get_parent().show()
+ %Icon.texture = load(info.get('icon'))
+ else:
+ %Icon.get_parent().hide()
+
+ tooltip_text = info.description
+
+
+func set_scene_preview(path:String, preview:Texture2D, thumbnail:Texture2D, userdata:Variant) -> void:
+ if preview:
+ %Image.texture = preview
+ else:
+ %Image.texture = get_theme_icon("PackedScene", "EditorIcons")
+
+
+
+func set_current(current:bool):
+ %CurrentIcon.visible = current
+
+
+func _on_mouse_entered() -> void:
+ %HoverBG.show()
+
+
+func _on_mouse_exited() -> void:
+ %HoverBG.hide()
+
+
+func _on_gui_input(event):
+ if event.is_action_pressed('ui_accept') or event.is_action_pressed("ui_select") or (
+ event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT):
+ clicked.emit()
+ if not event is InputEventMouseButton or event.double_click:
+ double_clicked.emit()
+ elif event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_MIDDLE:
+ middle_clicked.emit()
+
+
+func _on_focus_entered() -> void:
+ $FocusFG.show()
+ focused.emit()
+
+
+func _on_focus_exited() -> void:
+ $FocusFG.hide()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.tscn b/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.tscn
new file mode 100644
index 0000000..4abc87b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/BrowserItem.tscn
@@ -0,0 +1,154 @@
+[gd_scene load_steps=6 format=3 uid="uid://ddlxjde1cx035"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/BrowserItem.gd" id="1_s3kf0"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pfw08"]
+bg_color = Color(1, 1, 1, 0.32549)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+expand_margin_left = 4.0
+expand_margin_top = 4.0
+expand_margin_right = 4.0
+expand_margin_bottom = 4.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ab24c"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qnehp"]
+bg_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+shadow_color = Color(0.847059, 0.847059, 0.847059, 0.384314)
+shadow_size = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nxx8t"]
+bg_color = Color(0.435294, 0.435294, 0.435294, 0.211765)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+expand_margin_left = 4.0
+expand_margin_top = 4.0
+expand_margin_right = 4.0
+expand_margin_bottom = 4.0
+
+[node name="BrowserItem" type="MarginContainer"]
+custom_minimum_size = Vector2(200, 150)
+offset_left = 1.0
+offset_top = 1.0
+offset_right = 128.0
+offset_bottom = 102.0
+size_flags_horizontal = 0
+focus_mode = 2
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_top = 4
+theme_override_constants/margin_right = 4
+theme_override_constants/margin_bottom = 4
+script = ExtResource("1_s3kf0")
+
+[node name="HoverBG" type="Panel" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_pfw08")
+
+[node name="VBox" type="VBoxContainer" parent="."]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 0
+alignment = 1
+
+[node name="Panel" type="PanelContainer" parent="VBox"]
+unique_name_in_owner = true
+self_modulate = Color(0.0705882, 0.0705882, 0.0705882, 1)
+clip_children = 2
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_ab24c")
+
+[node name="Image" type="TextureRect" parent="VBox/Panel"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+expand_mode = 1
+stretch_mode = 6
+
+[node name="CurrentIcon" type="TextureRect" parent="VBox/Panel/Image"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -22.0
+offset_top = 5.0
+offset_right = -6.0
+offset_bottom = 21.0
+grow_horizontal = 0
+tooltip_text = "Currently in use"
+stretch_mode = 2
+
+[node name="Panel" type="Panel" parent="VBox/Panel/Image"]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -37.0
+offset_top = -36.0
+offset_right = -7.0
+offset_bottom = -6.0
+grow_horizontal = 0
+grow_vertical = 0
+theme_override_styles/panel = SubResource("StyleBoxFlat_qnehp")
+
+[node name="Icon" type="TextureRect" parent="VBox/Panel/Image/Panel"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 4.0
+offset_top = 4.0
+offset_right = -4.0
+offset_bottom = -4.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+stretch_mode = 5
+
+[node name="Name" type="Label" parent="VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Dialogic Theme"
+horizontal_alignment = 1
+
+[node name="FocusFG" type="Panel" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_nxx8t")
+
+[connection signal="focus_entered" from="." to="." method="_on_focus_entered"]
+[connection signal="focus_exited" from="." to="." method="_on_focus_exited"]
+[connection signal="gui_input" from="." to="." method="_on_gui_input"]
+[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"]
+[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/DCSS.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/DCSS.gd
new file mode 100644
index 0000000..2a1f95a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/DCSS.gd
@@ -0,0 +1,47 @@
+@tool
+class_name DCSS
+
+static func inline(style: Dictionary) -> StyleBoxFlat:
+ var scale: float = DialogicUtil.get_editor_scale()
+ var s := StyleBoxFlat.new()
+ for property in style.keys():
+ match property:
+ 'border-left':
+ s.set('border_width_left', style[property] * scale)
+ 'border-radius':
+ var radius: float = style[property] * scale
+ s.set('corner_radius_top_left', radius)
+ s.set('corner_radius_top_right', radius)
+ s.set('corner_radius_bottom_left', radius)
+ s.set('corner_radius_bottom_right', radius)
+ 'background':
+ if typeof(style[property]) == TYPE_STRING and style[property] == "none":
+ s.set('draw_center', false)
+ else:
+ s.set('bg_color', style[property])
+ 'border':
+ var width: float = style[property] * scale
+ s.set('border_width_left', width)
+ s.set('border_width_right', width)
+ s.set('border_width_top', width)
+ s.set('border_width_bottom', width)
+ 'border-color':
+ s.set('border_color', style[property])
+ 'padding':
+ var value_v: float = 0.0
+ var value_h: float = 0.0
+ if style[property] is int:
+ value_v = style[property] * scale
+ value_h = value_v
+ else:
+ value_v = style[property][0] * scale
+ value_h = style[property][1] * scale
+ s.set('content_margin_top', value_v)
+ s.set('content_margin_bottom', value_v)
+ s.set('content_margin_left', value_h)
+ s.set('content_margin_right', value_h)
+ 'padding-right':
+ s.set('content_margin_right', style[property] * scale)
+ 'padding-left':
+ s.set('content_margin_left', style[property] * scale)
+ return s
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd
new file mode 100644
index 0000000..2262619
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd
@@ -0,0 +1,119 @@
+@tool
+extends PanelContainer
+
+
+enum Modes {EDIT, ADD}
+
+var mode := Modes.EDIT
+var item: TreeItem = null
+
+
+func _ready() -> void:
+ hide()
+ %Character.resource_icon = load("res://addons/dialogic/Editor/Images/Resources/character.svg")
+ %Character.get_suggestions_func = get_character_suggestions
+
+ %WholeWords.icon = get_theme_icon("FontItem", "EditorIcons")
+ %MatchCase.icon = get_theme_icon("MatchCase", "EditorIcons")
+
+func _on_add_pressed() -> void:
+ if visible:
+ if mode == Modes.ADD:
+ hide()
+ return
+ elif mode == Modes.EDIT:
+ save()
+
+ %AddButton.text = "Add"
+ mode = Modes.ADD
+ show()
+ %Type.selected = 0
+ _on_type_item_selected(0)
+ %Where.selected = 2
+ _on_where_item_selected(2)
+ %Old.text = ""
+ %New.text = ""
+
+
+func open_existing(_item:TreeItem, info:Dictionary):
+ mode = Modes.EDIT
+ item = _item
+ show()
+ %AddButton.text = "Update"
+ %Type.selected = info.type
+ _on_type_item_selected(info.type)
+ if !info.character_names.is_empty():
+ %Where.selected = 1
+ %Character.set_value(info.character_names[0])
+ else:
+ %Where.selected = 0
+ _on_where_item_selected(%Where.selected)
+
+ %Old.text = info.what
+ %New.text = info.forwhat
+
+func _on_type_item_selected(index:int) -> void:
+ match index:
+ 0:
+ %Where.select(0)
+ %Where.set_item_disabled(0, false)
+ %Where.set_item_disabled(1, false)
+ %Where.set_item_disabled(2, true)
+ 1:
+ %Where.select(0)
+ %Where.set_item_disabled(0, false)
+ %Where.set_item_disabled(1, false)
+ %Where.set_item_disabled(2, true)
+ 2:
+ %Where.select(1)
+ %Where.set_item_disabled(0, true)
+ %Where.set_item_disabled(1, false)
+ %Where.set_item_disabled(2, true)
+ 3,4:
+ %Where.select(0)
+ %Where.set_item_disabled(0, false)
+ %Where.set_item_disabled(1, true)
+ %Where.set_item_disabled(2, true)
+ %PureTextFlags.visible = index == 0
+ _on_where_item_selected(%Where.selected)
+
+
+func _on_where_item_selected(index:int) -> void:
+ %Character.visible = index == 1
+
+
+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
+ var _character_directory := DialogicResourceUtil.get_character_directory()
+
+ var icon := load("res://addons/dialogic/Editor/Images/Resources/character.svg")
+ suggestions['(No one)'] = {'value':null, 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
+
+ for resource in _character_directory.keys():
+ suggestions[resource] = {
+ 'value' : resource,
+ 'tooltip' : _character_directory[resource],
+ 'icon' : icon.duplicate()}
+ return suggestions
+
+
+func save() -> void:
+ if %Old.text.is_empty() or %New.text.is_empty():
+ return
+ if %Where.selected == 1 and %Character.current_value == null:
+ return
+
+ var previous := {}
+ if mode == Modes.EDIT:
+ previous = item.get_metadata(0)
+ item.get_parent()
+ item.free()
+
+ var ref_manager := find_parent('ReferenceManager')
+ var character_names := []
+ if %Character.current_value != null:
+ character_names = [%Character.current_value]
+ ref_manager.add_ref_change(%Old.text, %New.text, %Type.selected, %Where.selected, character_names, %WholeWords.button_pressed, %MatchCase.button_pressed, previous)
+ hide()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/TitleBgStylebox.tres b/addons/dialogic-main/addons/dialogic/Editor/Common/TitleBgStylebox.tres
new file mode 100644
index 0000000..f08bb2c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/TitleBgStylebox.tres
@@ -0,0 +1,8 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://dmsjhgv22dns8"]
+
+[resource]
+content_margin_left = 5.0
+content_margin_top = 5.0
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0.545098, 0.545098, 0.545098, 0.211765)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/broken_reference_manager.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/broken_reference_manager.gd
new file mode 100644
index 0000000..b0d11cc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/broken_reference_manager.gd
@@ -0,0 +1,363 @@
+@tool
+extends VSplitContainer
+
+## This manager shows a list of changed references and allows searching for them and replacing them.
+
+var reference_changes: Array[Dictionary] = []:
+ set(changes):
+ reference_changes = changes
+ update_indicator()
+
+var search_regexes: Array[Array]
+var finder_thread: Thread
+var progress_mutex: Mutex
+var progress_percent: float = 0.0
+var progress_message: String = ""
+
+
+func _ready() -> void:
+ if owner.get_parent() is SubViewport:
+ return
+
+ %TabA.text = "Broken References"
+ %TabA.icon = get_theme_icon("Unlinked", "EditorIcons")
+
+ owner.get_parent().visibility_changed.connect(func(): if is_visible_in_tree(): open())
+
+ %ReplacementSection.hide()
+
+ %CheckButton.icon = get_theme_icon("Search", "EditorIcons")
+ %Replace.icon = get_theme_icon("ArrowRight", "EditorIcons")
+
+ %State.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+ visibility_changed.connect(func(): if !visible: close())
+ await get_parent().ready
+
+ var tab_button: Control = %TabA
+ var dot := Sprite2D.new()
+ dot.texture = get_theme_icon("GuiGraphNodePort", "EditorIcons")
+ dot.scale = Vector2(0.8, 0.8)
+ dot.z_index = 10
+ dot.position = Vector2(tab_button.size.x, tab_button.size.y*0.25)
+ dot.modulate = get_theme_color("warning_color", "Editor").lightened(0.5)
+
+ tab_button.add_child(dot)
+ update_indicator()
+
+
+func open() -> void:
+ %ReplacementEditPanel.hide()
+ %ReplacementSection.hide()
+ %ChangeTree.clear()
+ %ChangeTree.create_item()
+ %ChangeTree.set_column_expand(0, false)
+ %ChangeTree.set_column_expand(2, false)
+ %ChangeTree.set_column_custom_minimum_width(2, 50)
+ var categories := {null:%ChangeTree.get_root()}
+ for i in reference_changes:
+ var parent: TreeItem = null
+ if !i.get('category', null) in categories:
+ parent = %ChangeTree.create_item()
+ parent.set_text(1, i.category)
+ parent.set_custom_color(1, get_theme_color("disabled_font_color", "Editor"))
+ categories[i.category] = parent
+ else:
+ parent = categories[i.get('category')]
+
+ var item: TreeItem = %ChangeTree.create_item(parent)
+ item.set_text(1, i.what+" -> "+i.forwhat)
+ item.add_button(1, get_theme_icon("Edit", "EditorIcons"), 1, false, 'Edit')
+ item.add_button(1, get_theme_icon("Remove", "EditorIcons"), 0, false, 'Remove Change from List')
+ item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
+ item.set_checked(0, true)
+ item.set_editable(0, true)
+ item.set_metadata(0, i)
+ %CheckButton.disabled = reference_changes.is_empty()
+
+
+func _on_change_tree_button_clicked(item:TreeItem, column:int, id:int, mouse_button_index:int) -> void:
+ if id == 0:
+ reference_changes.erase(item.get_metadata(0))
+ if item.get_parent().get_child_count() == 1:
+ item.get_parent().free()
+ else:
+ item.free()
+ update_indicator()
+ %CheckButton.disabled = reference_changes.is_empty()
+
+ if id == 1:
+ %ReplacementEditPanel.open_existing(item, item.get_metadata(0))
+
+ %ReplacementSection.hide()
+
+
+func _on_change_tree_item_edited() -> void:
+ if !%ChangeTree.get_selected():
+ return
+ %CheckButton.disabled = false
+
+
+func _on_check_button_pressed() -> void:
+ var to_be_checked: Array[Dictionary]= []
+ var item: TreeItem = %ChangeTree.get_root()
+ while item.get_next_visible():
+ item = item.get_next_visible()
+
+ if item.get_child_count():
+ continue
+
+ if item.is_checked(0):
+ to_be_checked.append(item.get_metadata(0))
+ to_be_checked[-1]['item'] = item
+ to_be_checked[-1]['count'] = 0
+
+ open_finder(to_be_checked)
+ %CheckButton.disabled = true
+
+
+func open_finder(replacements:Array[Dictionary]) -> void:
+ %ReplacementSection.show()
+ %Progress.show()
+ %ReferenceTree.hide()
+
+ search_regexes = []
+ for i in replacements:
+ if i.has('character_names') and !i.character_names.is_empty():
+ i['character_regex'] = RegEx.create_from_string("(?m)^(join|update|leave)?\\s*("+str(i.character_names).replace('"', '').replace(', ', '|').trim_suffix(']').trim_prefix('[').replace('/', '\\/')+")(?(1).*|.*:)")
+
+ for regex_string in i.regex:
+ var regex := RegEx.create_from_string(regex_string)
+ search_regexes.append([regex, i])
+
+ finder_thread = Thread.new()
+ progress_mutex = Mutex.new()
+ finder_thread.start(search_timelines.bind(search_regexes))
+
+
+func _process(delta: float) -> void:
+ if finder_thread and finder_thread.is_started():
+ if finder_thread.is_alive():
+ progress_mutex.lock()
+ %State.text = progress_message
+ %Progress.value = progress_percent
+ progress_mutex.unlock()
+ else:
+ var finds: Variant = finder_thread.wait_to_finish()
+ display_search_results(finds)
+
+
+
+func display_search_results(finds:Array[Dictionary]) -> void:
+ %Progress.hide()
+ %ReferenceTree.show()
+ for regex_info in search_regexes:
+ regex_info[1]['item'].set_text(2, str(regex_info[1]['count']))
+
+ update_count_coloring()
+ %State.text = str(len(finds))+ " occurrences found"
+
+ %ReferenceTree.clear()
+ %ReferenceTree.set_column_expand(0, false)
+ %ReferenceTree.create_item()
+
+ var timelines := {}
+ var height := 0
+ for i in finds:
+ var parent: TreeItem = null
+ if !i.timeline in timelines:
+ parent = %ReferenceTree.create_item()
+ parent.set_text(1, i.timeline)
+ parent.set_custom_color(1, get_theme_color("disabled_font_color", "Editor"))
+ timelines[i.timeline] = parent
+ height += %ReferenceTree.get_item_area_rect(parent).size.y+10
+ else:
+ parent = timelines[i.timeline]
+
+ var item: TreeItem = %ReferenceTree.create_item(parent)
+ item.set_text(1, 'Line '+str(i.line_number)+': '+i.line)
+ item.set_tooltip_text(1, i.info.what+' -> '+i.info.forwhat)
+ item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
+ item.set_checked(0, true)
+ item.set_editable(0, true)
+ item.set_metadata(0, i)
+ height += %ReferenceTree.get_item_area_rect(item).size.y+10
+ var change_item: TreeItem = i.info.item
+ change_item.set_meta('found_items', change_item.get_meta('found_items', [])+[item])
+
+ %ReferenceTree.custom_minimum_size.y = min(height, 200)
+
+ %ReferenceTree.visible = !finds.is_empty()
+ %Replace.disabled = finds.is_empty()
+ if finds.is_empty():
+ %State.text = "Nothing found"
+ else:
+ %Replace.grab_focus()
+
+
+func search_timelines(regexes:Array[Array]) -> Array[Dictionary]:
+ var finds: Array[Dictionary] = []
+
+ var timeline_paths := DialogicResourceUtil.list_resources_of_type('.dtl')
+
+ var progress := 0
+ var progress_max: float = len(timeline_paths)*len(regexes)
+
+ for timeline_path:String in timeline_paths:
+
+ var timeline_file := FileAccess.open(timeline_path, FileAccess.READ)
+ var timeline_text: String = timeline_file.get_as_text()
+ var timeline_event: PackedStringArray = timeline_text.split('\n')
+ timeline_file.close()
+
+ for regex_info in regexes:
+ progress += 1
+ progress_mutex.lock()
+ progress_percent = 1/progress_max*progress
+ progress_message = "Searching '"+timeline_path+"' for "+regex_info[1].what+' -> '+regex_info[1].forwhat
+ progress_mutex.unlock()
+ for i in regex_info[0].search_all(timeline_text):
+ if regex_info[1].has('character_regex'):
+ if regex_info[1].character_regex.search(get_line(timeline_text, i.get_start()+1)) == null:
+ continue
+
+ var line_number := timeline_text.count('\n', 0, i.get_start()+1)+1
+ var line := timeline_text.get_slice('\n', line_number-1)
+ finds.append({
+ 'match':i,
+ 'timeline':timeline_path,
+ 'info': regex_info[1],
+ 'line_number': line_number,
+ 'line': line,
+ 'line_start': timeline_text.rfind('\n', i.get_start())
+ })
+ regex_info[1]['count'] += 1
+ return finds
+
+
+func _exit_tree() -> void:
+ # Shutting of
+ if finder_thread and finder_thread.is_alive():
+ finder_thread.wait_to_finish()
+
+
+func get_line(string:String, at_index:int) -> String:
+ return string.substr(max(string.rfind('\n', at_index), 0), string.find('\n', at_index)-string.rfind('\n', at_index))
+
+
+func update_count_coloring() -> void:
+ var item: TreeItem = %ChangeTree.get_root()
+ while item.get_next_visible():
+ item = item.get_next_visible()
+
+ if item.get_child_count():
+ continue
+ if int(item.get_text(2)) > 0:
+ item.set_custom_bg_color(1, get_theme_color("warning_color", "Editor").darkened(0.8))
+ item.set_custom_color(1, get_theme_color("warning_color", "Editor"))
+ item.set_custom_color(2, get_theme_color("warning_color", "Editor"))
+ else:
+ item.set_custom_color(2, get_theme_color("success_color", "Editor"))
+ item.set_custom_color(1, get_theme_color("readonly_font_color", "Editor"))
+ if item.get_button_count(1):
+ item.erase_button(1, 1)
+ item.add_button(1, get_theme_icon("Eraser", "EditorIcons"), -1, true, "This reference was not found anywhere and will be removed from this list.")
+
+
+func _on_replace_pressed() -> void:
+ var to_be_replaced: Array[Dictionary]= []
+ var item: TreeItem = %ReferenceTree.get_root()
+ var affected_timelines: Array[String]= []
+
+ while item.get_next_visible():
+ item = item.get_next_visible()
+
+ if item.get_child_count():
+ continue
+
+ if item.is_checked(0):
+ to_be_replaced.append(item.get_metadata(0))
+ to_be_replaced[-1]['f_item'] = item
+ if !item.get_metadata(0).timeline in affected_timelines:
+ affected_timelines.append(item.get_metadata(0).timeline)
+ replace(affected_timelines, to_be_replaced)
+
+
+func replace(timelines:Array[String], replacement_info:Array[Dictionary]) -> void:
+ var reopen_timeline := ""
+ var timeline_editor: DialogicEditor = find_parent('EditorView').editors_manager.editors['Timeline'].node
+ if timeline_editor.current_resource != null and timeline_editor.current_resource.resource_path in timelines:
+ reopen_timeline = timeline_editor.current_resource.resource_path
+ find_parent('EditorView').editors_manager.clear_editor(timeline_editor)
+
+ replacement_info.sort_custom(func(a,b): return a.match.get_start() < b.match.get_start())
+
+ for timeline_path in timelines:
+ %State.text = "Loading '"+timeline_path+"'"
+
+ var timeline_file := FileAccess.open(timeline_path, FileAccess.READ_WRITE)
+ var timeline_text: String = timeline_file.get_as_text()
+ var timeline_events := timeline_text.split('\n')
+ timeline_file.close()
+
+ var idx := 1
+ var offset_correction := 0
+ for replacement in replacement_info:
+ if replacement.timeline != timeline_path:
+ continue
+
+ %State.text = "Replacing in '"+timeline_path + "' ("+str(idx)+"/"+str(len(replacement_info))+")"
+ var group := 'replace'
+ if not 'replace' in replacement.match.names:
+ group = ''
+
+
+ timeline_text = timeline_text.substr(0, replacement.match.get_start(group) + offset_correction) + \
+ replacement.info.regex_replacement + \
+ timeline_text.substr(replacement.match.get_end(group) + offset_correction)
+ offset_correction += len(replacement.info.regex_replacement)-len(replacement.match.get_string(group))
+
+ replacement.info.count -= 1
+ replacement.info.item.set_text(2, str(replacement.info.count))
+ replacement.f_item.set_custom_bg_color(1, get_theme_color("success_color", "Editor").darkened(0.8))
+
+ timeline_file = FileAccess.open(timeline_path, FileAccess.WRITE)
+ timeline_file.store_string(timeline_text.strip_edges(false, true))
+ timeline_file.close()
+
+ if ResourceLoader.has_cached(timeline_path):
+ var tml := load(timeline_path)
+ tml.from_text(timeline_text)
+
+ if !reopen_timeline.is_empty():
+ find_parent('EditorView').editors_manager.edit_resource(load(reopen_timeline), false, true)
+
+ update_count_coloring()
+
+ %Replace.disabled = true
+ %CheckButton.disabled = false
+ %State.text = "Done Replacing"
+
+
+func update_indicator() -> void:
+ %TabA.get_child(0).visible = !reference_changes.is_empty()
+
+
+func close() -> void:
+ var item: TreeItem = %ChangeTree.get_root()
+ if item:
+ while item.get_next_visible():
+ item = item.get_next_visible()
+
+ if item.get_child_count():
+ continue
+ if item.get_text(2) != "" and int(item.get_text(2)) == 0:
+ reference_changes.erase(item.get_metadata(0))
+ for i in reference_changes:
+ i.item = null
+ DialogicUtil.set_editor_setting('reference_changes', reference_changes)
+ update_indicator()
+ find_parent("ReferenceManager").update_indicator()
+
+
+func _on_add_button_pressed() -> void:
+ %ReplacementEditPanel._on_add_pressed()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.gd
new file mode 100644
index 0000000..702fe98
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.gd
@@ -0,0 +1,12 @@
+@tool
+extends TextureRect
+
+@export_multiline var hint_text := ""
+
+func _ready() -> void:
+ if owner and owner.get_parent() is SubViewport:
+ texture = null
+ return
+ texture = get_theme_icon("NodeInfo", "EditorIcons")
+ modulate = get_theme_color("contrast_color_1", "Editor")
+ tooltip_text = hint_text
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.tscn b/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.tscn
new file mode 100644
index 0000000..3ee10fb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/hint_tooltip_icon.tscn
@@ -0,0 +1,21 @@
+[gd_scene load_steps=4 format=3 uid="uid://dbpkta2tjsqim"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.gd" id="1_x8t45"]
+
+[sub_resource type="Image" id="Image_eiyxd"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_lseut"]
+image = SubResource("Image_eiyxd")
+
+[node name="HintTooltip" type="TextureRect"]
+modulate = Color(0, 0, 0, 1)
+texture = SubResource("ImageTexture_lseut")
+stretch_mode = 3
+script = ExtResource("1_x8t45")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.gd
new file mode 100644
index 0000000..915e594
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.gd
@@ -0,0 +1,38 @@
+@tool
+extends PanelContainer
+
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ add_theme_stylebox_override("panel", get_theme_stylebox("Background", "EditorStyles"))
+ $Tabs/Close.icon = get_theme_icon("Close", "EditorIcons")
+
+ for tab in $Tabs/Tabs.get_children():
+ tab.add_theme_color_override("font_selected_color", get_theme_color("accent_color", "Editor"))
+ tab.add_theme_font_override("font", get_theme_font("main", "EditorFonts"))
+ tab.toggled.connect(tab_changed.bind(tab.get_index()+1))
+
+
+func tab_changed(enabled:bool, index:int) -> void:
+ for child in $Tabs.get_children():
+ if child.get_index() == 0 or child.get_index() == index or child is Button:
+ child.show()
+ if child.get_index() == index:
+ child.open()
+ else:
+ if child.visible:
+ child.close()
+ child.hide()
+ for child in $Tabs/Tabs.get_children():
+ child.set_pressed_no_signal(index-1 == child.get_index())
+
+
+func open() -> void:
+ show()
+ $Tabs/BrokenReferences.update_indicator()
+
+
+func _on_close_pressed() -> void:
+ get_parent()._on_close_requested()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.tscn b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.tscn
new file mode 100644
index 0000000..0bd6073
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager.tscn
@@ -0,0 +1,324 @@
+[gd_scene load_steps=13 format=3 uid="uid://c7lmt5cp7bxcm"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/reference_manager.gd" id="1_3t531"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/broken_reference_manager.gd" id="1_agmg4"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd" id="2_tt4jd"]
+[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3_yomsc"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/unique_identifiers_manager.gd" id="5_wnvbq"]
+
+[sub_resource type="ButtonGroup" id="ButtonGroup_l6uiy"]
+
+[sub_resource type="Image" id="Image_36731"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 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_a0gfq"]
+image = SubResource("Image_36731")
+
+[sub_resource type="Image" id="Image_0rvkq"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_mr0fw"]
+image = SubResource("Image_0rvkq")
+
+[sub_resource type="Image" id="Image_5fmdt"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_lce2m"]
+image = SubResource("Image_5fmdt")
+
+[node name="Manager" type="PanelContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_3t531")
+
+[node name="Tabs" type="VBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Tabs" type="HBoxContainer" parent="Tabs"]
+layout_mode = 2
+alignment = 1
+
+[node name="TabA" type="Button" parent="Tabs/Tabs"]
+unique_name_in_owner = true
+layout_mode = 2
+toggle_mode = true
+button_pressed = true
+text = "Broken References"
+flat = true
+
+[node name="TabB" type="Button" parent="Tabs/Tabs"]
+unique_name_in_owner = true
+layout_mode = 2
+toggle_mode = true
+button_group = SubResource("ButtonGroup_l6uiy")
+text = "Unique Identifiers"
+flat = true
+
+[node name="BrokenReferences" type="VSplitContainer" parent="Tabs"]
+layout_mode = 2
+size_flags_vertical = 3
+script = ExtResource("1_agmg4")
+
+[node name="ChangesList" type="PanelContainer" parent="Tabs/BrokenReferences"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_type_variation = &"DialogicPanelA"
+
+[node name="VBox" type="VBoxContainer" parent="Tabs/BrokenReferences/ChangesList"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Tabs/BrokenReferences/ChangesList/VBox"]
+layout_mode = 2
+
+[node name="SectionTitle" type="Label" parent="Tabs/BrokenReferences/ChangesList/VBox/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 16
+text = "Recent renames"
+
+[node name="AddButton" type="Button" parent="Tabs/BrokenReferences/ChangesList/VBox/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 2
+tooltip_text = "Add custom rename"
+icon = SubResource("ImageTexture_a0gfq")
+
+[node name="ReplacementEditPanel" type="PanelContainer" parent="Tabs/BrokenReferences/ChangesList/VBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+script = ExtResource("2_tt4jd")
+
+[node name="VBox" type="HFlowContainer" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel"]
+layout_mode = 2
+
+[node name="HBoxContainer3" type="HBoxContainer" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox"]
+layout_mode = 2
+
+[node name="Type" type="OptionButton" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer3"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "This decides the regexes for searching. Pure text allows you to enter your own regex into into \"Old\". "
+item_count = 5
+selected = 0
+popup/item_0/text = "Pure Text"
+popup/item_0/id = 0
+popup/item_1/text = "Variable"
+popup/item_1/id = 1
+popup/item_2/text = "Portrait"
+popup/item_2/id = 2
+popup/item_3/text = "Character (Ref)"
+popup/item_3/id = 3
+popup/item_4/text = "Timeline (Ref)"
+popup/item_4/id = 4
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Old" type="LineEdit" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Old"
+
+[node name="Label2" type="Label" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer"]
+layout_mode = 2
+text = "->"
+
+[node name="New" type="LineEdit" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "New"
+
+[node name="PureTextFlags" type="HBoxContainer" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+alignment = 2
+
+[node name="MatchCase" type="Button" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/PureTextFlags"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Match Case"
+toggle_mode = true
+icon = SubResource("ImageTexture_mr0fw")
+
+[node name="WholeWords" type="Button" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/PureTextFlags"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Whole World"
+toggle_mode = true
+icon = SubResource("ImageTexture_mr0fw")
+
+[node name="HBoxContainer4" type="HBoxContainer" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox"]
+layout_mode = 2
+
+[node name="Where" type="OptionButton" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer4"]
+unique_name_in_owner = true
+layout_mode = 2
+item_count = 3
+selected = 0
+fit_to_longest_item = false
+popup/item_0/text = "Everywhere"
+popup/item_0/id = 0
+popup/item_1/text = "Only for Character"
+popup/item_1/id = 1
+popup/item_2/text = "Texts only"
+popup/item_2/id = 2
+
+[node name="Character" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer4" instance=ExtResource("3_yomsc")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="AddButton" type="Button" parent="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Add/Save"
+
+[node name="ChangeTree" type="Tree" parent="Tabs/BrokenReferences/ChangesList/VBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/draw_relationship_lines = 1
+columns = 3
+hide_root = true
+
+[node name="CheckButton" type="Button" parent="Tabs/BrokenReferences/ChangesList/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+tooltip_text = "Search timelines for occurences of these renames"
+text = "Check Selected"
+icon = SubResource("ImageTexture_lce2m")
+
+[node name="ReplacementSection" type="PanelContainer" parent="Tabs/BrokenReferences"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicPanelA"
+
+[node name="FindList" type="VBoxContainer" parent="Tabs/BrokenReferences/ReplacementSection"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="HBox" type="HBoxContainer" parent="Tabs/BrokenReferences/ReplacementSection/FindList"]
+layout_mode = 2
+
+[node name="SectionTitle2" type="Label" parent="Tabs/BrokenReferences/ReplacementSection/FindList/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 16
+text = "Found references"
+
+[node name="State" type="Label" parent="Tabs/BrokenReferences/ReplacementSection/FindList/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+text = "State"
+
+[node name="ReferenceTree" type="Tree" parent="Tabs/BrokenReferences/ReplacementSection/FindList"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/draw_relationship_lines = 1
+columns = 2
+hide_root = true
+
+[node name="Progress" type="ProgressBar" parent="Tabs/BrokenReferences/ReplacementSection/FindList"]
+unique_name_in_owner = true
+layout_mode = 2
+max_value = 1.0
+
+[node name="Replace" type="Button" parent="Tabs/BrokenReferences/ReplacementSection/FindList"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+tooltip_text = "Replace all selected findings (Careful, no undo!)"
+text = "Replace Selected"
+icon = SubResource("ImageTexture_lce2m")
+
+[node name="UniqueIdentifiers" type="PanelContainer" parent="Tabs"]
+visible = false
+layout_mode = 2
+size_flags_vertical = 3
+theme_type_variation = &"DialogicPanelA"
+script = ExtResource("5_wnvbq")
+
+[node name="VBox" type="VBoxContainer" parent="Tabs/UniqueIdentifiers"]
+layout_mode = 2
+
+[node name="Tools" type="HBoxContainer" parent="Tabs/UniqueIdentifiers/VBox"]
+layout_mode = 2
+alignment = 1
+
+[node name="Search" type="LineEdit" parent="Tabs/UniqueIdentifiers/VBox/Tools"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+placeholder_text = "Search"
+
+[node name="IdentifierTable" type="Tree" parent="Tabs/UniqueIdentifiers/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+columns = 2
+column_titles_visible = true
+hide_root = true
+
+[node name="RenameNotification" type="Label" parent="Tabs/UniqueIdentifiers/VBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+text = "You've renamed some identifier(s)! Use the \"Broken References\" tab to check if you have used this identifier (and fix it if so)."
+autowrap_mode = 3
+
+[node name="Close" type="Button" parent="Tabs"]
+layout_mode = 2
+size_flags_horizontal = 4
+text = "Close"
+
+[node name="HelpButton" type="LinkButton" parent="."]
+layout_mode = 2
+size_flags_horizontal = 8
+size_flags_vertical = 0
+text = "Documentation"
+uri = "https://docs.dialogic.pro/reference-manager.html"
+
+[connection signal="pressed" from="Tabs/BrokenReferences/ChangesList/VBox/HBoxContainer/AddButton" to="Tabs/BrokenReferences" method="_on_add_button_pressed"]
+[connection signal="item_selected" from="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer3/Type" to="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel" method="_on_type_item_selected"]
+[connection signal="item_selected" from="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/HBoxContainer4/Where" to="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel" method="_on_where_item_selected"]
+[connection signal="pressed" from="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel/VBox/AddButton" to="Tabs/BrokenReferences/ChangesList/VBox/ReplacementEditPanel" method="save"]
+[connection signal="button_clicked" from="Tabs/BrokenReferences/ChangesList/VBox/ChangeTree" to="Tabs/BrokenReferences" method="_on_change_tree_button_clicked"]
+[connection signal="item_edited" from="Tabs/BrokenReferences/ChangesList/VBox/ChangeTree" to="Tabs/BrokenReferences" method="_on_change_tree_item_edited"]
+[connection signal="pressed" from="Tabs/BrokenReferences/ChangesList/VBox/CheckButton" to="Tabs/BrokenReferences" method="_on_check_button_pressed"]
+[connection signal="pressed" from="Tabs/BrokenReferences/ReplacementSection/FindList/Replace" to="Tabs/BrokenReferences" method="_on_replace_pressed"]
+[connection signal="text_changed" from="Tabs/UniqueIdentifiers/VBox/Tools/Search" to="Tabs/UniqueIdentifiers" method="_on_search_text_changed"]
+[connection signal="button_clicked" from="Tabs/UniqueIdentifiers/VBox/IdentifierTable" to="Tabs/UniqueIdentifiers" method="_on_identifier_table_button_clicked"]
+[connection signal="item_edited" from="Tabs/UniqueIdentifiers/VBox/IdentifierTable" to="Tabs/UniqueIdentifiers" method="_on_identifier_table_item_edited"]
+[connection signal="pressed" from="Tabs/Close" to="." method="_on_close_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager_window.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager_window.gd
new file mode 100644
index 0000000..7bb42ec
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/reference_manager_window.gd
@@ -0,0 +1,191 @@
+@tool
+extends Window
+
+## This window manages communication with the replacement manager it contains.
+## Other scripts can call the add_ref_change() method to register changes directly
+## or use the helpers add_variable_ref_change() and add_portrait_ref_change()
+
+@onready var editors_manager := get_node("../EditorsManager")
+@onready var broken_manager := get_node("Manager/Tabs/BrokenReferences")
+enum Where {EVERYWHERE, BY_CHARACTER, TEXTS_ONLY}
+enum Types {TEXT, VARIABLE, PORTRAIT, CHARACTER_NAME, TIMELINE_NAME}
+
+var icon_button: Button = null
+
+
+func _ready() -> void:
+ if owner.get_parent() is SubViewport:
+ return
+
+ $Manager.theme = owner.get_theme()
+
+ icon_button = editors_manager.add_icon_button(get_theme_icon("Unlinked", "EditorIcons"), 'Reference Manager')
+ icon_button.pressed.connect(open)
+
+ var dot := Sprite2D.new()
+ dot.texture = get_theme_icon("GuiGraphNodePort", "EditorIcons")
+ dot.scale = Vector2(0.8, 0.8)
+ dot.z_index = 10
+ dot.position = Vector2(icon_button.size.x*0.8, icon_button.size.x*0.2)
+ dot.modulate = get_theme_color("warning_color", "Editor").lightened(0.5)
+
+ icon_button.add_child(dot)
+
+ var old_changes: Array = DialogicUtil.get_editor_setting('reference_changes', [])
+ if !old_changes.is_empty():
+ broken_manager.reference_changes = old_changes
+
+ update_indicator()
+
+ hide()
+
+ get_parent().plugin_reference.get_editor_interface().get_file_system_dock().files_moved.connect(_on_file_moved)
+ get_parent().plugin_reference.get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed)
+ get_parent().get_node('ResourceRenameWarning').confirmed.connect(open)
+
+
+func add_ref_change(old_name:String, new_name:String, type:Types, where:=Where.TEXTS_ONLY, character_names:=[],
+ whole_words:=false, case_sensitive:=false, previous:Dictionary = {}) -> void:
+ var regexes := []
+ var category_name := ""
+ match type:
+ Types.TEXT:
+ category_name = "Texts"
+ if '' in old_name:
+ regexes = [old_name]
+ else:
+ regexes = [
+ r'(?%s)' % old_name.replace('/', '\\/')
+ ]
+ if !case_sensitive:
+ regexes[0] = '(?i)'+regexes[0]
+ if whole_words:
+ regexes = ['\\b'+regexes[0]+'\\b']
+
+ Types.VARIABLE:
+ regexes = [
+ r'{(?\s*%s\s*)}' % old_name.replace("/", "\\/"),
+ r'var\s*=\s*"(?\s*%s\s*)"' % old_name.replace("/", "\\/")
+ ]
+ category_name = "Variables"
+
+ Types.PORTRAIT:
+ regexes = [
+ r'(?m)^[^:(\n]*\((?%s)\)' % old_name.replace('/', '\\/'),
+ r'\[\s*portrait\s*=(?\s*%s\s*)\]' % old_name.replace('/', '\\/')
+ ]
+ category_name = "Portraits by "+character_names[0]
+
+ Types.CHARACTER_NAME:
+ # for reference: ((join|leave|update) )?(?NAME)(?!\B)(?(1)|(?!([^:\n]|\\:)*(\n|$)))
+ regexes = [
+ r'((join|leave|update) )?(?%s)(?!\B)(?(1)|(?!([^:\n]|\\:)*(\n|$)))' % old_name
+ ]
+ category_name = "Renamed Character Files"
+
+ Types.TIMELINE_NAME:
+ regexes = [
+ r'timeline ?= ?" ?(?%s) ?"' % old_name
+ ]
+ category_name = "Renamed Timeline Files"
+
+ if where != Where.BY_CHARACTER:
+ character_names = []
+
+ # previous is only given when an existing item is edited
+ # in that case the old one is removed first
+ var idx := len(broken_manager.reference_changes)
+ if previous in broken_manager.reference_changes:
+ idx = broken_manager.reference_changes.find(previous)
+ broken_manager.reference_changes.erase(previous)
+
+ if _check_for_ref_change_cycle(old_name, new_name, category_name):
+ update_indicator()
+ return
+
+ broken_manager.reference_changes.insert(idx,
+ {'what':old_name,
+ 'forwhat':new_name,
+ 'regex': regexes,
+ 'regex_replacement':new_name,
+ 'category':category_name,
+ 'character_names':character_names,
+ 'texts_only':where == Where.TEXTS_ONLY,
+ 'type':type
+ })
+
+ update_indicator()
+
+ if visible:
+ $Manager.open()
+ broken_manager.open()
+
+
+## Checks for reference cycles or chains.
+## E.g. if you first rename a portrait from "happy" to "happy1" and then to "Happy/happy1"
+## This will make sure only a change "happy" -> "Happy/happy1" is remembered
+## This is very important for correct replacement
+func _check_for_ref_change_cycle(old_name:String, new_name:String, category:String) -> bool:
+ for ref in broken_manager.reference_changes:
+ if ref['forwhat'] == old_name and ref['category'] == category:
+ if new_name == ref['what']:
+ broken_manager.reference_changes.erase(ref)
+ else:
+ broken_manager.reference_changes[broken_manager.reference_changes.find(ref)]['forwhat'] = new_name
+ broken_manager.reference_changes[broken_manager.reference_changes.find(ref)]['regex_replacement'] = new_name
+ return true
+ return false
+
+
+## Helper for adding variable ref changes
+func add_variable_ref_change(old_name:String, new_name:String) -> void:
+ add_ref_change(old_name, new_name, Types.VARIABLE, Where.EVERYWHERE)
+
+
+## Helper for adding portrait ref changes
+func add_portrait_ref_change(old_name:String, new_name:String, character_names:PackedStringArray) -> void:
+ add_ref_change(old_name, new_name, Types.PORTRAIT, Where.BY_CHARACTER, character_names)
+
+
+## Helper for adding character name ref changes
+func add_character_name_ref_change(old_name:String, new_name:String) -> void:
+ add_ref_change(old_name, new_name, Types.CHARACTER_NAME, Where.EVERYWHERE)
+
+
+## Helper for adding timeline name ref changes
+func add_timeline_name_ref_change(old_name:String, new_name:String) -> void:
+ add_ref_change(old_name, new_name, Types.TIMELINE_NAME, Where.EVERYWHERE)
+
+
+func open() -> void:
+ DialogicResourceUtil.update_directory('dch')
+ DialogicResourceUtil.update_directory('dtl')
+ popup_centered_ratio(0.5)
+ move_to_foreground()
+ grab_focus()
+
+
+func _on_close_requested() -> void:
+ hide()
+ broken_manager.close()
+
+
+func update_indicator() -> void:
+ icon_button.get_child(0).visible = !broken_manager.reference_changes.is_empty()
+
+
+## FILE MANAGEMENT:
+func _on_file_moved(old_file:String, new_file:String) -> void:
+ if old_file.ends_with('.dch') and new_file.ends_with('.dch'):
+ DialogicResourceUtil.change_resource_path(old_file, new_file)
+ if old_file.get_file() != new_file.get_file():
+ get_parent().get_node('ResourceRenameWarning').popup_centered()
+ elif old_file.ends_with('.dtl') and new_file.ends_with('.dtl'):
+ DialogicResourceUtil.change_resource_path(old_file, new_file)
+ if old_file.get_file() != new_file.get_file():
+ get_parent().get_node('ResourceRenameWarning').popup_centered()
+
+
+func _on_file_removed(file:String) -> void:
+ if file.get_extension() in ['dch', 'dtl']:
+ DialogicResourceUtil.remove_resource(file)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/side_bar.tscn b/addons/dialogic-main/addons/dialogic/Editor/Common/side_bar.tscn
new file mode 100644
index 0000000..72f82cd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/side_bar.tscn
@@ -0,0 +1,175 @@
+[gd_scene load_steps=7 format=3 uid="uid://cwe3r2tbh2og1"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/sidebar.gd" id="1_jnq65"]
+[ext_resource type="Texture2D" uid="uid://bff65e82555qr" path="res://addons/dialogic/Editor/Images/Pieces/close-icon.svg" id="2_54pks"]
+[ext_resource type="Texture2D" uid="uid://dx3o2ild56i76" path="res://addons/dialogic/Editor/Images/Pieces/closed-icon.svg" id="2_ilyps"]
+
+[sub_resource type="Theme" id="Theme_pn0f4"]
+VBoxContainer/constants/separation = 4
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_gxwm6"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_n8rql"]
+
+[node name="SideBar" type="VSplitContainer"]
+custom_minimum_size = Vector2(100, 130)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = SubResource("Theme_pn0f4")
+split_offset = 100
+script = ExtResource("1_jnq65")
+
+[node name="VBoxHidden" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="OpenButton" type="Button" parent="VBoxHidden"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 3
+tooltip_text = "Show Sidebar"
+theme_override_constants/icon_max_width = 20
+icon = ExtResource("2_ilyps")
+flat = true
+icon_alignment = 1
+
+[node name="VBoxPrimary" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="Margin" type="MarginContainer" parent="VBoxPrimary"]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/margin_left = 5
+theme_override_constants/margin_bottom = 5
+
+[node name="VSplitContainer" type="VSplitContainer" parent="VBoxPrimary/Margin"]
+layout_mode = 2
+
+[node name="VBox" type="VBoxContainer" parent="VBoxPrimary/Margin/VSplitContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="Logo" type="TextureRect" parent="VBoxPrimary/Margin/VSplitContainer/VBox"]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.623529)
+texture_filter = 6
+custom_minimum_size = Vector2(0, 25)
+layout_mode = 2
+expand_mode = 3
+stretch_mode = 4
+
+[node name="HBox" type="HBoxContainer" parent="VBoxPrimary/Margin/VSplitContainer/VBox"]
+layout_mode = 2
+
+[node name="CurrentResource" type="LineEdit" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "No resource"
+alignment = 1
+editable = false
+
+[node name="CloseButton" type="Button" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Hide Sidebar"
+text = " "
+icon = ExtResource("2_54pks")
+flat = true
+icon_alignment = 1
+expand_icon = true
+
+[node name="HBoxSearchSort" type="HBoxContainer" parent="VBoxPrimary/Margin/VSplitContainer/VBox"]
+layout_mode = 2
+
+[node name="Search" type="LineEdit" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxSearchSort"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "Filter Resources"
+placeholder_text = "Filter Resources"
+caret_blink = true
+caret_blink_interval = 0.5
+
+[node name="SortOption" type="OptionButton" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxSearchSort"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Sort
+- Default: Sort alphabetically split into Characters & Timeilnes.
+- Folder: Sort alphabetically by parent directory.
+- Path: Display full path relative to res://.
+- None: Sort alphabetically with no categories."
+text_overrun_behavior = 1
+clip_text = true
+selected = 0
+item_count = 4
+popup/item_0/text = "Type"
+popup/item_1/text = "Folder"
+popup/item_1/id = 1
+popup/item_2/text = "Path"
+popup/item_2/id = 2
+popup/item_3/text = "None"
+popup/item_3/id = 3
+
+[node name="ResourceTree" type="Tree" parent="VBoxPrimary/Margin/VSplitContainer/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+allow_rmb_select = true
+hide_root = true
+scroll_horizontal_enabled = false
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxPrimary/Margin/VSplitContainer/VBox"]
+visible = false
+layout_mode = 2
+
+[node name="Label" type="Label" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 1
+text = "Sort Order"
+vertical_alignment = 1
+
+[node name="SortOption" type="OptionButton" parent="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+item_count = 1
+popup/item_0/text = "Alphabetical (All)"
+
+[node name="ContentListSection" type="VBoxContainer" parent="VBoxPrimary/Margin/VSplitContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="ContentList" type="ItemList" parent="VBoxPrimary/Margin/VSplitContainer/ContentListSection"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+tooltip_text = "Label events in your timeline will appear here, allowing you to jump to them."
+theme_override_styles/selected = SubResource("StyleBoxEmpty_gxwm6")
+theme_override_styles/selected_focus = SubResource("StyleBoxEmpty_n8rql")
+allow_reselect = true
+same_column_width = true
+
+[node name="CurrentVersion" type="Button" parent="VBoxPrimary"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Some Version"
+flat = true
+clip_text = true
+
+[node name="RightClickMenu" type="PopupMenu" parent="."]
+unique_name_in_owner = true
+size = Vector2i(164, 100)
+
+[connection signal="gui_input" from="VBoxPrimary/Margin/VSplitContainer/VBox/Logo" to="." method="_on_logo_gui_input"]
+[connection signal="text_changed" from="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxSearchSort/Search" to="." method="_on_search_text_changed"]
+[connection signal="text_submitted" from="VBoxPrimary/Margin/VSplitContainer/VBox/HBoxSearchSort/Search" to="." method="_on_search_text_submitted"]
+[connection signal="pressed" from="VBoxPrimary/CurrentVersion" to="." method="_on_current_version_pressed"]
+[connection signal="id_pressed" from="RightClickMenu" to="." method="_on_right_click_menu_id_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/sidebar.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/sidebar.gd
new file mode 100644
index 0000000..339b618
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/sidebar.gd
@@ -0,0 +1,480 @@
+@tool
+class_name DialogicSidebar extends Control
+
+## Script that handles the editor sidebar.
+
+signal file_activated(file_path)
+
+signal content_item_activated(item_name)
+
+signal show_sidebar(show: bool)
+
+@onready var editors_manager = get_parent().get_parent()
+@onready var resource_tree: Tree = %ResourceTree
+var current_resource_list: Array = []
+enum SortMode {
+ TYPE,
+ FOLDER,
+ PATH,
+ NONE,
+}
+
+
+var sort_mode: SortMode = SortMode.TYPE
+
+
+func _ready() -> void:
+ if owner != null and owner.get_parent() is SubViewport:
+ return
+ if editors_manager is SubViewportContainer:
+ return
+
+ ## CONNECTIONS
+ editors_manager.resource_opened.connect(_on_editors_resource_opened)
+ editors_manager.editor_changed.connect(_on_editors_editor_changed)
+
+ resource_tree.item_activated.connect(_on_resources_tree_item_activated)
+ resource_tree.item_mouse_selected.connect(_on_resources_tree_item_clicked)
+
+
+
+ %ContentList.item_selected.connect(
+ func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx))
+ )
+
+ (%OpenButton as Button).pressed.connect(_show_sidebar)
+ (%CloseButton as Button).pressed.connect(_hide_sidebar)
+
+ var editor_scale := DialogicUtil.get_editor_scale()
+ ## ICONS
+ %Logo.texture = load("res://addons/dialogic/Editor/Images/dialogic-logo.svg")
+ %Logo.custom_minimum_size.y = 30 * editor_scale
+ %Search.right_icon = get_theme_icon("Search", "EditorIcons")
+
+ %ContentList.add_theme_color_override(
+ "font_hovered_color", get_theme_color("warning_color", "Editor")
+ )
+ %ContentList.add_theme_color_override(
+ "font_selected_color", get_theme_color("property_color_z", "Editor")
+ )
+
+ ## MARGINS
+ %VBoxPrimary/Margin.set(
+ "theme_override_constants/margin_left",
+ get_theme_constant("base_margin", "Editor") * editor_scale
+ )
+ %VBoxPrimary/Margin.set(
+ "theme_override_constants/margin_bottom",
+ get_theme_constant("base_margin", "Editor") * editor_scale
+ )
+
+ ## RIGHT CLICK MENU
+ %RightClickMenu.clear()
+ %RightClickMenu.add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Remove From List", 1)
+ %RightClickMenu.add_separator()
+ %RightClickMenu.add_icon_item(get_theme_icon("ActionCopy", "EditorIcons"), "Copy Identifier", 4)
+ %RightClickMenu.add_separator()
+ %RightClickMenu.add_icon_item(
+ get_theme_icon("Filesystem", "EditorIcons"), "Show in FileSystem", 2
+ )
+ %RightClickMenu.add_icon_item(
+ get_theme_icon("ExternalLink", "EditorIcons"), "Open in External Program", 3
+ )
+
+ ## SORT MENU
+ %SortOption.set_item_icon(0, get_theme_icon("AnimationTrackGroup", "EditorIcons"))
+ %SortOption.set_item_icon(1, get_theme_icon("Folder", "EditorIcons"))
+ %SortOption.set_item_icon(2, get_theme_icon("FolderBrowse", "EditorIcons"))
+ %SortOption.set_item_icon(3, get_theme_icon("AnimationTrackList", "EditorIcons"))
+ %SortOption.item_selected.connect(_on_sort_changed)
+
+ await get_tree().process_frame
+ if DialogicUtil.get_editor_setting("sidebar_collapsed", false):
+ _hide_sidebar()
+
+ %SortOption.select(DialogicUtil.get_editor_setting("sidebar_sort_mode", 0))
+ sort_mode = DialogicUtil.get_editor_setting("sidebar_sort_mode", 0)
+ update_resource_list()
+
+
+################################################################################
+## SHOW/HIDE SIDEBAR
+################################################################################
+
+
+func _show_sidebar() -> void:
+ %VBoxPrimary.show()
+ %VBoxHidden.hide()
+ DialogicUtil.set_editor_setting("sidebar_collapsed", false)
+ show_sidebar.emit(true)
+
+
+func _hide_sidebar() -> void:
+ %VBoxPrimary.hide()
+ %VBoxHidden.show()
+ DialogicUtil.set_editor_setting("sidebar_collapsed", true)
+ show_sidebar.emit(false)
+
+
+################################################################################
+## RESOURCE LIST
+################################################################################
+
+
+func _on_editors_resource_opened(resource: Resource) -> void:
+ update_resource_list()
+ pass
+
+
+func _on_editors_editor_changed(previous: DialogicEditor, current: DialogicEditor) -> void:
+ %ContentListSection.visible = current.current_resource is DialogicTimeline
+ update_resource_list()
+
+
+func clean_resource_list(resources_list: Array = []) -> PackedStringArray:
+ return PackedStringArray(resources_list.filter(func(x): return ResourceLoader.exists(x)))
+
+
+func update_resource_list(resources_list: PackedStringArray = []) -> void:
+ var filter: String = %Search.text
+ var current_file := ""
+ if editors_manager.current_editor and editors_manager.current_editor.current_resource:
+ current_file = editors_manager.current_editor.current_resource.resource_path
+
+ var character_directory: Dictionary = DialogicResourceUtil.get_character_directory()
+ var timeline_directory: Dictionary = DialogicResourceUtil.get_timeline_directory()
+ if resources_list.is_empty():
+ resources_list = DialogicUtil.get_editor_setting("last_resources", [])
+ if !current_file in resources_list:
+ resources_list.append(current_file)
+
+ resources_list = clean_resource_list(resources_list)
+
+ %CurrentResource.text = "No Resource"
+ %CurrentResource.add_theme_color_override(
+ "font_uneditable_color", get_theme_color("disabled_font_color", "Editor")
+ )
+
+ resource_tree.clear()
+ var resource_list_items := []
+
+ var get_directory_items := func(directory:Dictionary, filter:String, icon:Texture2D) -> Array:
+ var items := []
+ for item_name in directory:
+ if (directory[item_name] in resources_list) and (filter.is_empty() or filter.to_lower() in item_name.to_lower()):
+ var item := ResourceListItem.new()
+ item.text = item_name
+ item.icon = icon
+ item.metadata = directory[item_name]
+ item.tooltip = directory[item_name]
+ items.append(item)
+ return items
+
+ var character_items: Array = get_directory_items.call(character_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/character.svg"))
+ var timeline_items: Array = get_directory_items.call(timeline_directory, filter, get_theme_icon("TripleBar", "EditorIcons"))
+
+
+ # BUILD TREE
+ var root: TreeItem = resource_tree.create_item()
+
+ if sort_mode == SortMode.TYPE:
+ character_items.sort_custom(_sort_by_item_text)
+ timeline_items.sort_custom(_sort_by_item_text)
+ if character_items.size() > 0:
+ var character_tree := add_folder_item("Characters", root)
+ for item in character_items:
+ add_item(item, character_tree, current_file)
+
+ if timeline_items.size() > 0:
+ var timeline_tree := add_folder_item("Timelines", root)
+ for item in timeline_items:
+ add_item(item, timeline_tree, current_file)
+
+ if sort_mode == SortMode.NONE:
+ var all_items := character_items + timeline_items
+ all_items.sort_custom(_sort_by_item_text)
+ for item in all_items:
+ var tree_item = resource_tree.create_item(root)
+ tree_item.set_text(0, item.text)
+ tree_item.set_icon(0, item.icon)
+ tree_item.set_metadata(0, item.metadata)
+ tree_item.set_tooltip_text(0, item.tooltip)
+ if item.metadata == current_file:
+ %CurrentResource.text = item.metadata.get_file()
+ resource_tree.set_selected(tree_item, 0)
+
+ if sort_mode == SortMode.FOLDER:
+ var all_items := character_items + timeline_items
+ var dirs := {}
+ for item in all_items:
+ var dir := item.get_parent_directory() as String
+ if !dirs.has(dir):
+ dirs[dir] = []
+ dirs[dir].append(item)
+ for dir in dirs:
+ var dir_item = resource_tree.create_item(root)
+ dir_item.set_text(0, dir)
+ dir_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
+ dir_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor"))
+ for item in dirs[dir]:
+ var tree_item = resource_tree.create_item(dir_item)
+ tree_item.set_text(0, item.text)
+ tree_item.set_icon(0, item.icon)
+ tree_item.set_metadata(0, item.metadata)
+ tree_item.set_tooltip_text(0, item.tooltip)
+ if item.metadata == current_file:
+ %CurrentResource.text = item.metadata.get_file()
+ resource_tree.set_selected(tree_item, 0)
+
+ if sort_mode == SortMode.PATH:
+ var all_items := character_items + timeline_items
+ var dirs := {}
+ var regex := RegEx.new()
+ for item in all_items:
+ var path := (item.metadata.get_base_dir() as String).replace("res://", "")
+ regex.compile("(\\w+\\/)?\\w+$")
+ if !dirs.has(path):
+ dirs[path] = []
+ dirs[path].append(item)
+
+ for dir in dirs:
+ var dir_display := regex.search(dir).get_string(0)
+ var dir_item = resource_tree.create_item(root)
+ var dir_color = ProjectSettings.get_setting("file_customization/folder_colors").get("res://" + dir + "/", get_theme_color("base_color", "Editor"))
+ var default_color_used = true;
+ if dir_color as Color != null and Color(dir_color) != get_theme_color("base_color", "Editor"):
+ dir_color = Color(dir_color)
+ dir_color.a = 0.2
+ dir_color = (dir_color as Color).to_html(true)
+ default_color_used = false
+ dir_item.set_text(0, dir_display)
+ dir_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
+ dir_item.set_custom_bg_color(0, dir_color)
+ for item in dirs[dir]:
+ var tree_item = resource_tree.create_item(dir_item)
+ tree_item.set_text(0, item.text)
+ tree_item.set_icon(0, item.icon)
+ tree_item.set_metadata(0, item.metadata)
+ if !default_color_used:
+ dir_color = Color(dir_color)
+ dir_color.a = 0.1
+ dir_color = (dir_color as Color).to_html(true)
+ tree_item.set_custom_bg_color(0, dir_color)
+ tree_item.set_tooltip_text(0, item.tooltip)
+ if item.metadata == current_file:
+ %CurrentResource.text = item.metadata.get_file()
+ resource_tree.set_selected(tree_item, 0)
+
+ if %CurrentResource.text != "No Resource":
+ %CurrentResource.add_theme_color_override(
+ "font_uneditable_color", get_theme_color("font_color", "Editor")
+ )
+
+ DialogicUtil.set_editor_setting("last_resources", resources_list)
+
+
+func add_item(item:ResourceListItem, parent:TreeItem, current_file := "") -> TreeItem:
+ var tree_item := resource_tree.create_item(parent)
+ tree_item.set_text(0, item.text)
+ tree_item.set_icon(0, item.icon)
+ tree_item.set_metadata(0, item.metadata)
+ tree_item.set_tooltip_text(0, item.tooltip)
+ if item.metadata == current_file:
+ %CurrentResource.text = item.metadata.get_file()
+ resource_tree.set_selected(tree_item, 0)
+ return tree_item
+
+
+func add_folder_item(label: String, parent:TreeItem) -> TreeItem:
+ var folder_item := resource_tree.create_item(parent)
+ folder_item.set_text(0, label)
+ folder_item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
+ folder_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor"))
+ return folder_item
+
+
+func _on_resources_tree_item_activated() -> void:
+ if resource_tree.get_selected() == null:
+ return
+ var item := resource_tree.get_selected()
+ if item.get_metadata(0) == null:
+ return
+ edit_resource(item.get_metadata(0))
+
+
+func _on_resources_tree_item_clicked(_pos: Vector2, mouse_button_index: int) -> void:
+ if mouse_button_index == MOUSE_BUTTON_LEFT:
+ var selected_item := resource_tree.get_selected()
+ if selected_item == null:
+ return
+ if selected_item.get_metadata(0) == null:
+ return
+ var resource_item := load(selected_item.get_metadata(0))
+ call_deferred("edit_resource", resource_item)
+ return
+ if mouse_button_index == MOUSE_BUTTON_MIDDLE:
+ remove_item_from_list(resource_tree.get_selected())
+ return
+ if mouse_button_index == MOUSE_BUTTON_RIGHT:
+ if resource_tree.get_selected().get_metadata(0):
+ %RightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2()))
+ (%RightClickMenu as PopupMenu).set_meta("item_clicked", resource_tree.get_selected())
+ return
+
+
+func _on_search_text_changed(new_text: String) -> void:
+ update_resource_list()
+ var tree_root := resource_tree.get_root()
+ var tree_items := tree_root.get_children()
+ if tree_items.size() == 0:
+ return
+ for item in tree_items:
+ if item.get_children().size() > 0:
+ resource_tree.set_selected(item.get_child(0), 0)
+ break
+
+
+func _on_search_text_submitted(new_text: String) -> void:
+ if resource_tree.get_selected() == null:
+ return
+ var item := resource_tree.get_selected()
+ if item.get_metadata(0) == null:
+ return
+ edit_resource(item.get_metadata(0))
+ %Search.clear()
+
+
+func set_unsaved_indicator(saved: bool = true) -> void:
+ if saved and %CurrentResource.text.ends_with("(*)"):
+ %CurrentResource.text = %CurrentResource.text.trim_suffix("(*)")
+ if not saved and not %CurrentResource.text.ends_with("(*)"):
+ %CurrentResource.text = %CurrentResource.text + "(*)"
+
+
+func _on_logo_gui_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
+ editors_manager.open_editor(editors_manager.editors["HomePage"].node)
+
+
+func update_content_list(list: PackedStringArray) -> void:
+ var prev_selected := ""
+ if %ContentList.is_anything_selected():
+ prev_selected = %ContentList.get_item_text(%ContentList.get_selected_items()[0])
+ %ContentList.clear()
+ %ContentList.add_item("~ Top")
+ for i in list:
+ if i.is_empty():
+ continue
+ %ContentList.add_item(i)
+ if i == prev_selected:
+ %ContentList.select(%ContentList.item_count - 1)
+ if list.is_empty():
+ return
+
+ var current_resource: Resource = editors_manager.get_current_editor().current_resource
+
+ var timeline_directory := DialogicResourceUtil.get_timeline_directory()
+ var label_directory := DialogicResourceUtil.get_label_cache()
+ if current_resource != null:
+ for i in timeline_directory:
+ if timeline_directory[i] == current_resource.resource_path:
+ label_directory[i] = list
+
+ # also always store the current timelines labels for easy access
+ label_directory[""] = list
+
+ DialogicResourceUtil.set_label_cache(label_directory)
+
+
+func remove_item_from_list(item: TreeItem) -> void:
+ var new_list := []
+ for entry in DialogicUtil.get_editor_setting("last_resources", []):
+ if entry != item.get_metadata(0):
+ new_list.append(entry)
+ DialogicUtil.set_editor_setting("last_resources", new_list)
+ update_resource_list(new_list)
+
+
+func _on_right_click_menu_id_pressed(id: int) -> void:
+ match id:
+ 1: # REMOVE ITEM FROM LIST
+ remove_item_from_list(%RightClickMenu.get_meta("item_clicked"))
+ 2: # OPEN IN FILESYSTEM
+ EditorInterface.get_file_system_dock().navigate_to_path(
+ %RightClickMenu.get_meta("item_clicked").get_metadata(0)
+ )
+ 3: # OPEN IN EXTERNAL EDITOR
+ OS.shell_open(
+ ProjectSettings.globalize_path(
+ %RightClickMenu.get_meta("item_clicked").get_metadata(0)
+ )
+ )
+ 4: # COPY IDENTIFIER
+ DisplayServer.clipboard_set(
+ DialogicResourceUtil.get_unique_identifier(
+ %RightClickMenu.get_meta("item_clicked").get_metadata(0)
+ )
+ )
+
+
+func _on_sort_changed(idx: int) -> void:
+ if (SortMode as Dictionary).values().has(idx):
+ sort_mode = idx
+ DialogicUtil.set_editor_setting("sidebar_sort_mode", idx)
+ update_resource_list()
+ else:
+ sort_mode = SortMode.TYPE
+ print("Invalid sort mode: ", idx)
+
+
+func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool:
+ return a.text < b.text
+
+
+func edit_resource(resource_item: Variant) -> void:
+ if resource_item is Resource:
+ editors_manager.edit_resource(resource_item)
+ else:
+ editors_manager.edit_resource(load(resource_item))
+
+
+
+
+class ResourceListItem:
+ extends Object
+
+ var text: String
+ var index: int = -1
+ var icon: Texture
+ var metadata: String
+ var tooltip: String
+
+ func _to_string() -> String:
+ return JSON.stringify(
+ {
+ "text": text,
+ "index": index,
+ "icon": icon.resource_path,
+ "metadata": metadata,
+ "tooltip": tooltip,
+ "parent_dir": get_parent_directory()
+ },
+ "\t",
+ false
+ )
+
+ func add_to_item_list(item_list: ItemList, current_file: String) -> void:
+ item_list.add_item(text, icon)
+ item_list.set_item_metadata(item_list.item_count - 1, metadata)
+ item_list.set_item_tooltip(item_list.item_count - 1, tooltip)
+
+ func get_parent_directory() -> String:
+ return (metadata.get_base_dir() as String).split("/")[-1]
+
+ func current_file(sidebar: Control, resource_list: ItemList, current_file: String) -> void:
+ if metadata == current_file:
+ resource_list.select(index)
+ resource_list.set_item_custom_fg_color(
+ index, resource_list.get_theme_color("accent_color", "Editor")
+ )
+ sidebar.find_child("CurrentResource").text = metadata.get_file()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/toolbar.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/toolbar.gd
new file mode 100644
index 0000000..e49c1ea
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/toolbar.gd
@@ -0,0 +1,49 @@
+@tool
+extends HBoxContainer
+
+# Dialogic Editor toolbar. Works together with editors_mangager.
+
+################################################################################
+## EDITOR BUTTONS/LABELS
+################################################################################
+func _ready() -> void:
+ if owner.get_parent() is SubViewport:
+ return
+ %CustomButtons.custom_minimum_size.y = 33 * DialogicUtil.get_editor_scale()
+
+ for child in get_children():
+ if child is Button:
+ child.queue_free()
+
+
+func add_icon_button(icon: Texture, tooltip: String) -> Button:
+ var button := Button.new()
+ button.icon = icon
+ button.tooltip_text = tooltip
+ button.flat = true
+ button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
+ button.add_theme_color_override('icon_hover_color', get_theme_color('warning_color', 'Editor'))
+ button.add_theme_stylebox_override('focus', StyleBoxEmpty.new())
+ add_child(button)
+ move_child(button, -2)
+ return button
+
+
+func add_custom_button(label:String, icon:Texture) -> Button:
+ var button := Button.new()
+ button.text = label
+ button.icon = icon
+# button.flat = true
+
+ button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
+ %CustomButtons.add_child(button)
+# custom_minimum_size.y = button.size.y
+ return button
+
+
+func hide_all_custom_buttons() -> void:
+ for button in %CustomButtons.get_children():
+ button.hide()
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/unique_identifiers_manager.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/unique_identifiers_manager.gd
new file mode 100644
index 0000000..fc7c993
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/unique_identifiers_manager.gd
@@ -0,0 +1,95 @@
+@tool
+extends PanelContainer
+
+
+func _ready() -> void:
+ if owner.get_parent() is SubViewport:
+ return
+
+ %TabB.text = "Unique Identifiers"
+ %TabB.icon = get_theme_icon("CryptoKey", "EditorIcons")
+
+ owner.get_parent().visibility_changed.connect(func(): if is_visible_in_tree(): open())
+
+ %RenameNotification.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+
+
+func open() -> void:
+ fill_table()
+ %RenameNotification.hide()
+
+
+func close() -> void:
+ pass
+
+func fill_table() -> void:
+ var t: Tree = %IdentifierTable
+ t.set_column_expand(1, true)
+ t.clear()
+ t.set_column_title(1, "Identifier")
+ t.set_column_title(0, "Resource Path")
+ t.set_column_title_alignment(0, 0)
+ t.set_column_title_alignment(1, 0)
+ t.create_item()
+
+ for d in [["Characters", 'dch'], ["Timelines", "dtl"]]:
+ var directory := DialogicResourceUtil.get_directory(d[1])
+ var directory_item := t.create_item()
+ directory_item.set_text(0, d[0])
+ directory_item.set_metadata(0, d[1])
+ for key in directory:
+ var item: TreeItem = t.create_item(directory_item)
+ item.set_text(0, directory[key])
+ item.set_text(1, key)
+ item.set_editable(1, true)
+ item.set_metadata(1, key)
+ item.add_button(1, get_theme_icon("Edit", "EditorIcons"), 0, false, "Edit")
+
+
+func _on_identifier_table_item_edited() -> void:
+ var item: TreeItem = %IdentifierTable.get_edited()
+ var new_identifier: String = item.get_text(1)
+
+
+ if new_identifier == item.get_metadata(1):
+ return
+
+ if new_identifier.is_empty() or not DialogicResourceUtil.is_identifier_unused(item.get_parent().get_metadata(0), new_identifier):
+ item.set_text(1, item.get_metadata(1))
+ return
+
+ DialogicResourceUtil.change_unique_identifier(item.get_text(0), new_identifier)
+
+ match item.get_parent().get_metadata(0):
+ 'dch':
+ owner.get_parent().add_character_name_ref_change(item.get_metadata(1), new_identifier)
+ 'dtl':
+ owner.get_parent().add_timeline_name_ref_change(item.get_metadata(1), new_identifier)
+
+ %RenameNotification.show()
+ item.set_metadata(1, new_identifier)
+
+
+func _on_identifier_table_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void:
+ item.select(column)
+ %IdentifierTable.edit_selected(true)
+
+
+func filter_tree(filter:String= "", item:TreeItem = null) -> bool:
+ if item == null:
+ item = %IdentifierTable.get_root()
+
+ var any := false
+ for child in item.get_children():
+ if child.get_child_count() > 0:
+ child.visible = filter_tree(filter, child)
+ if child.visible: any = true
+ else:
+ child.visible = filter.is_empty() or filter.to_lower() in child.get_text(0).to_lower() or filter.to_lower() in child.get_text(1).to_lower()
+ if child.visible: any = true
+
+ return any
+
+
+func _on_search_text_changed(new_text: String) -> void:
+ filter_tree(new_text)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.gd
new file mode 100644
index 0000000..0d5a498
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.gd
@@ -0,0 +1,180 @@
+@tool
+extends Control
+
+var current_info := {}
+@onready var editor_view := find_parent('EditorView')
+
+
+func _ready() -> void:
+ await editor_view.ready
+ theme = editor_view.theme
+
+ %Install.icon = editor_view.get_theme_icon("AssetLib", "EditorIcons")
+ %LoadingIcon.texture = editor_view.get_theme_icon("KeyTrackScale", "EditorIcons")
+ %InstallWarning.modulate = editor_view.get_theme_color("warning_color", "Editor")
+ %CloseButton.icon = editor_view.get_theme_icon("Close", "EditorIcons")
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().get_resource_filesystem().resources_reimported.connect(_on_resources_reimported)
+
+
+func open() -> void:
+ get_parent().popup_centered_ratio(0.5)
+ get_parent().mode = Window.MODE_WINDOWED
+ get_parent().move_to_foreground()
+ get_parent().grab_focus()
+
+
+func load_info(info:Dictionary, update_type:int) -> void:
+ current_info = info
+ if update_type == 2:
+ %State.text = "No Information Available"
+ %UpdateName.text = "Unable to access versions."
+ %UpdateName.add_theme_color_override("font_color", editor_view.get_theme_color("readonly_color", "Editor"))
+ %Content.text = "You are probably not connected to the internet. Fair enough."
+ %ShortInfo.text = "Huh, what happened here?"
+ %ReadFull.hide()
+ %Install.disabled = true
+ return
+
+ # If we are up to date (or beyond):
+ if info.is_empty():
+ info['name'] = "You are in the future, Marty!"
+ info["body"] = "# 😎 You are using the WIP branch!\nSeems like you are using a version that isn't even released yet. Be careful and give us your feedback ;)"
+ info["published_at"] = "????T"
+ info["author"] = {'login':"???"}
+ %State.text = "Where are we Doc?"
+ %UpdateName.add_theme_color_override("font_color", editor_view.get_theme_color("property_color_z", "Editor"))
+ %Install.disabled = true
+
+ elif update_type == 0:
+ %State.text = "Update Available!"
+ %UpdateName.add_theme_color_override("font_color", editor_view.get_theme_color("warning_color", "Editor"))
+ %Install.disabled = false
+ else:
+ %State.text = "You are up to date:"
+ %UpdateName.add_theme_color_override("font_color", editor_view.get_theme_color("success_color", "Editor"))
+ %Install.disabled = true
+
+ %UpdateName.text = info.name
+ %Content.text = markdown_to_bbcode(info.body).get_slice("\n[font_size", 0).strip_edges()
+ %ShortInfo.text = "Published on "+info.published_at.substr(0, info.published_at.find('T'))+" by "+info.author.login
+ if info.has("html_url"):
+ %ReadFull.uri = info.html_url
+ %ReadFull.show()
+ else:
+ %ReadFull.hide()
+ if info.has('reactions'):
+ %Reactions.show()
+ var reactions := {"laugh":"😂", "hooray":"🎉", "confused":"😕", "heart":"❤️", "rocket":"🚀", "eyes":"👀"}
+ for i in reactions:
+ %Reactions.get_node(i.capitalize()).visible = info.reactions[i] > 0
+ %Reactions.get_node(i.capitalize()).text = reactions[i]+" "+str(info.reactions[i]) if info.reactions[i] > 0 else reactions[i]
+ if info.reactions['+1']+info.reactions['-1'] > 0:
+ %Reactions.get_node("Likes").visible = true
+ %Reactions.get_node("Likes").text = "👍 "+str(info.reactions['+1']+info.reactions['-1'])
+ else:
+ %Reactions.get_node("Likes").visible = false
+ else:
+ %Reactions.hide()
+
+func _on_window_close_requested() -> void:
+ get_parent().visible = false
+
+
+func _on_install_pressed() -> void:
+ find_parent('UpdateManager').request_update_download()
+
+ %InfoLabel.text = "Downloading. This can take a moment."
+ %Loading.show()
+ %LoadingIcon.create_tween().set_loops().tween_property(%LoadingIcon, 'rotation', 2*PI, 1).from(0)
+
+
+func _on_refresh_pressed() -> void:
+ find_parent('UpdateManager').request_update_check()
+
+
+func _on_update_manager_downdload_completed(result:int):
+ %Loading.hide()
+ match result:
+ 0: # success
+ %InfoLabel.text = "Installed successfully. Restart needed!"
+ %InfoLabel.modulate = editor_view.get_theme_color("success_color", "Editor")
+ %Restart.show()
+ %Restart.grab_focus()
+ 1: # failure
+ %InfoLabel.text = "Download failed."
+ %InfoLabel.modulate = editor_view.get_theme_color("readonly_color", "Editor")
+
+
+func _on_resources_reimported(resources:Array) -> void:
+ if is_inside_tree():
+ await get_tree().process_frame
+ get_parent().move_to_foreground()
+
+
+func markdown_to_bbcode(text:String) -> String:
+ var font_sizes := {1:20, 2:16, 3:16,4:14, 5:14}
+ var title_regex := RegEx.create_from_string('(^|\n)((?#+)(?.*))\\n')
+ var res := title_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(2), '[font_size='+str(font_sizes[len(res.get_string('level'))])+']'+res.get_string('title').strip_edges()+'[/font_size]')
+ res = title_regex.search(text)
+
+ var link_regex := RegEx.create_from_string('(?[^\\]]*)]\\((?[^)]*)\\)')
+ res = link_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[url='+res.get_string('link')+']'+res.get_string('text').strip_edges()+'[/url]')
+ res = link_regex.search(text)
+
+ var image_regex := RegEx.create_from_string('\\!\\[(?[^\\]]*)]\\((?[^)]*)\\)\n*')
+ res = image_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[url='+res.get_string('link')+']'+res.get_string('text').strip_edges()+'[/url]')
+ res = image_regex.search(text)
+
+ var italics_regex := RegEx.create_from_string('\\*(?[^\\*\\n]*)\\*')
+ res = italics_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[i]'+res.get_string('text').strip_edges()+'[/i]')
+ res = italics_regex.search(text)
+
+ var bullets_regex := RegEx.create_from_string('(?<=\\n)(\\*|-)(?[^\\*\\n]*)\\n')
+ res = bullets_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[ul]'+res.get_string('text').strip_edges()+'[/ul]\n')
+ res = bullets_regex.search(text)
+
+ var small_code_regex := RegEx.create_from_string('(?[^`]+)`')
+ res = small_code_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[code][color='+get_theme_color("accent_color", "Editor").to_html()+']'+res.get_string('text').strip_edges()+'[/color][/code]')
+ res = small_code_regex.search(text)
+
+ var big_code_regex := RegEx.create_from_string('(?[^`]+)```')
+ res = big_code_regex.search(text)
+ while res:
+ text = text.replace(res.get_string(), '[code][bgcolor='+get_theme_color("box_selection_fill_color", "Editor").to_html()+']'+res.get_string('text').strip_edges()+'[/bgcolor][/code]')
+ res = big_code_regex.search(text)
+
+ return text
+
+
+
+func _on_content_meta_clicked(meta:Variant) -> void:
+ OS.shell_open(str(meta))
+
+
+func _on_install_mouse_entered() -> void:
+ if not %Install.disabled:
+ %InstallWarning.show()
+
+
+func _on_install_mouse_exited() -> void:
+ %InstallWarning.hide()
+
+
+func _on_restart_pressed() -> void:
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().restart_editor(true)
+
+
+func _on_close_button_pressed() -> void:
+ get_parent().hide()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.tscn b/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.tscn
new file mode 100644
index 0000000..9c7788f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/update_install_window.tscn
@@ -0,0 +1,308 @@
+[gd_scene load_steps=9 format=3 uid="uid://vv3m5m68fwg7"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/update_install_window.gd" id="1_p1pbx"]
+[ext_resource type="Texture2D" uid="uid://dybg3l5pwetne" path="res://addons/dialogic/Editor/Images/plugin-icon.svg" id="2_20ke0"]
+
+[sub_resource type="Gradient" id="Gradient_lt7uf"]
+colors = PackedColorArray(0.296484, 0.648457, 1, 1, 0.732014, 0.389374, 1, 1)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_nl8ke"]
+gradient = SubResource("Gradient_lt7uf")
+fill_from = Vector2(0.151515, 0.272727)
+fill_to = Vector2(1, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1g1am"]
+content_margin_left = 0.0
+content_margin_top = 15.0
+content_margin_right = 15.0
+content_margin_bottom = 15.0
+bg_color = Color(0.0627451, 0.0627451, 0.0627451, 0.407843)
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+expand_margin_left = 20.0
+expand_margin_right = 20.0
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_j1mw2"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h4v2s"]
+content_margin_left = 5.0
+content_margin_top = 3.0
+content_margin_right = 5.0
+content_margin_bottom = 3.0
+bg_color = Color(0, 0, 0, 0.631373)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_utju1"]
+content_margin_left = 5.0
+content_margin_top = 3.0
+content_margin_right = 5.0
+content_margin_bottom = 3.0
+bg_color = Color(0.0470588, 0.0470588, 0.0470588, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[node name="UpdateInstallWindow" type="ColorRect"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0.207843, 0.129412, 0.372549, 1)
+script = ExtResource("1_p1pbx")
+
+[node name="TextureRect" type="TextureRect" parent="."]
+modulate = Color(0.447059, 0.447059, 0.447059, 1)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = SubResource("GradientTexture2D_nl8ke")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 14.0
+offset_top = 13.0
+offset_right = -14.0
+offset_bottom = -13.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="Control" type="Control" parent="VBoxContainer/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 7
+
+[node name="VBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer2"]
+custom_minimum_size = Vector2(450, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 3.74
+alignment = 1
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/HBoxContainer2/VBox"]
+clip_contents = false
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+alignment = 1
+
+[node name="Panel" type="PanelContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_1g1am")
+
+[node name="VBox" type="VBoxContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel"]
+layout_mode = 2
+theme_override_constants/separation = -8
+
+[node name="State" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+text = "Update Available!"
+
+[node name="UpdateName" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicTitle"
+theme_override_font_sizes/font_size = 25
+text = "Dialogic 2.0 - alpha 9"
+uppercase = true
+
+[node name="ShortInfo" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicHintText2"
+theme_override_font_sizes/font_size = 10
+text = "12/31/23"
+
+[node name="Refresh" type="Button" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel"]
+layout_mode = 2
+size_flags_horizontal = 8
+size_flags_vertical = 0
+text = "Refresh
+"
+flat = true
+
+[node name="Content" type="RichTextLabel" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_font_sizes/normal_font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxEmpty_j1mw2")
+bbcode_enabled = true
+text = "[font_size=25]🎉 New alpha, new stuff![/font_size]
+If you are using dialogic 2 alphas then we've got an exciting update. It's not the beta yet, but we are getting closer! As always if you have questions or feedback it's best to reach out on [url=https://discord.gg/2hHQzkf2pX]emilios discord[/url].
+
+This alpha brings a couple of very useful new features to dialogic as well as some syntax changes and a design overhaul (and many, many bug fixes).
+"
+fit_content = true
+
+[node name="Reactions" type="HBoxContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Likes" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "👍12"
+
+[node name="Hooray" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "🎉12"
+
+[node name="Laugh" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "👀12"
+
+[node name="Heart" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "❤️12"
+
+[node name="Rocket" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "😕12"
+
+[node name="Eyes" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "🚀12"
+
+[node name="Confused" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_h4v2s")
+text = "😂12"
+
+[node name="ReadFull" type="LinkButton" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Reactions"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 10
+text = "Read Full Announcement"
+
+[node name="Control" type="Control" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer"]
+custom_minimum_size = Vector2(0, 20)
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer"]
+layout_mode = 2
+alignment = 2
+
+[node name="InfoLabel" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+horizontal_alignment = 2
+autowrap_mode = 3
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer"]
+self_modulate = Color(0, 0, 0, 1)
+layout_mode = 2
+size_flags_horizontal = 4
+theme_override_styles/panel = SubResource("StyleBoxFlat_h4v2s")
+
+[node name="HBox" type="HBoxContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer"]
+layout_mode = 2
+alignment = 2
+
+[node name="Loading" type="CenterContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox"]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(30, 0)
+layout_mode = 2
+
+[node name="Control" type="Control" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Loading"]
+layout_mode = 2
+
+[node name="LoadingIcon" type="Sprite2D" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Loading/Control"]
+unique_name_in_owner = true
+texture = ExtResource("2_20ke0")
+
+[node name="Restart" type="Button" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_vertical = 4
+text = "Restart Now"
+flat = true
+
+[node name="Install" type="Button" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+text = "Install"
+flat = true
+
+[node name="InstallWarning" type="PanelContainer" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Install"]
+unique_name_in_owner = true
+visible = false
+self_modulate = Color(0, 0, 0, 1)
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -493.0
+offset_top = -92.0
+offset_right = 5.0
+offset_bottom = -8.0
+grow_horizontal = 0
+theme_override_styles/panel = SubResource("StyleBoxFlat_utju1")
+
+[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Install/InstallWarning"]
+layout_mode = 2
+theme_override_font_sizes/font_size = 14
+text = "Be careful. This will delete the addons/dialogic folder and install the new version. Any custom changes in that folder will be lost.
+To be on the save side, use version control!"
+autowrap_mode = 3
+
+[node name="Control2" type="Control" parent="VBoxContainer/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 7
+
+[node name="Close" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="CloseButton" type="Button" parent="VBoxContainer/Close"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Close"
+
+[connection signal="pressed" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Panel/Refresh" to="." method="_on_refresh_pressed"]
+[connection signal="meta_clicked" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/Content" to="." method="_on_content_meta_clicked"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Restart" to="." method="_on_restart_pressed"]
+[connection signal="mouse_entered" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Install" to="." method="_on_install_mouse_entered"]
+[connection signal="mouse_exited" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Install" to="." method="_on_install_mouse_exited"]
+[connection signal="pressed" from="VBoxContainer/HBoxContainer2/VBox/ScrollContainer/VBoxContainer/HBoxContainer/PanelContainer/HBox/Install" to="." method="_on_install_pressed"]
+[connection signal="pressed" from="VBoxContainer/Close/CloseButton" to="." method="_on_close_button_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Common/update_manager.gd b/addons/dialogic-main/addons/dialogic/Editor/Common/update_manager.gd
new file mode 100644
index 0000000..36a116e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Common/update_manager.gd
@@ -0,0 +1,192 @@
+@tool
+extends Node
+
+## Script that checks for new versions and can install them.
+
+signal update_check_completed(result:UpdateCheckResult)
+signal downdload_completed(result:DownloadResult)
+
+enum UpdateCheckResult {UPDATE_AVAILABLE, UP_TO_DATE, NO_ACCESS}
+enum DownloadResult {SUCCESS, FAILURE}
+enum ReleaseState {ALPHA, BETA, STABLE}
+
+const REMOTE_RELEASES_URL := "https://api.github.com/repos/dialogic-godot/dialogic/releases"
+const TEMP_FILE_NAME := "user://temp.zip"
+
+var current_version := ""
+var update_info: Dictionary
+var current_info: Dictionary
+
+var version_indicator: Button
+
+func _ready() -> void:
+ request_update_check()
+
+ setup_version_indicator()
+
+
+
+func get_current_version() -> String:
+ var plugin_cfg := ConfigFile.new()
+ plugin_cfg.load("res://addons/dialogic/plugin.cfg")
+ return plugin_cfg.get_value('plugin', 'version', 'unknown version')
+
+
+func request_update_check() -> void:
+ if $UpdateCheckRequest.get_http_client_status() == HTTPClient.STATUS_DISCONNECTED:
+ $UpdateCheckRequest.request(REMOTE_RELEASES_URL)
+
+
+func _on_UpdateCheck_request_completed(result:int, response_code:int, headers:PackedStringArray, body:PackedByteArray) -> void:
+ if result != HTTPRequest.RESULT_SUCCESS:
+ update_check_completed.emit(UpdateCheckResult.NO_ACCESS)
+ return
+
+ # Work out the next version from the releases information on GitHub
+ var response: Variant = JSON.parse_string(body.get_string_from_utf8())
+ if typeof(response) != TYPE_ARRAY: return
+
+
+ var current_release_info := get_release_tag_info(get_current_version())
+
+ # GitHub releases are in order of creation, not order of version
+ var versions: Array = (response as Array).filter(compare_versions.bind(current_release_info))
+ if versions.size() > 0:
+ update_info = versions[0]
+ update_check_completed.emit(UpdateCheckResult.UPDATE_AVAILABLE)
+ else:
+ update_info = current_info
+ update_check_completed.emit(UpdateCheckResult.UP_TO_DATE)
+
+
+func compare_versions(release, current_release_info:Dictionary) -> bool:
+ var checked_release_info := get_release_tag_info(release.tag_name)
+
+ if checked_release_info.major < current_release_info.major:
+ return false
+
+ if checked_release_info.minor < current_release_info.minor:
+ return false
+
+ if checked_release_info.state < current_release_info.state:
+ return false
+
+ elif checked_release_info.state == current_release_info.state:
+ if checked_release_info.state_version < current_release_info.state_version:
+ return false
+
+ if checked_release_info.state_version == current_release_info.state_version:
+ current_info = release
+ return false
+
+ if checked_release_info.state == ReleaseState.STABLE:
+ if checked_release_info.minor == current_release_info.minor:
+ current_info = release
+ return false
+
+ return true
+
+
+func get_release_tag_info(release_tag:String) -> Dictionary:
+ release_tag = release_tag.strip_edges().trim_prefix('v')
+ release_tag = release_tag.substr(0, release_tag.find('('))
+ release_tag = release_tag.to_lower()
+
+ var regex := RegEx.create_from_string(r"(?\d+\.\d+)(-(?alpha|beta)-)?(?(2)(?\d*)|\.(?\d*))?")
+
+ var result: RegExMatch = regex.search(release_tag)
+ if !result:
+ return {}
+
+ var info: Dictionary = {'tag':release_tag}
+ info['major'] = float(result.get_string('major'))
+ info['minor'] = int(result.get_string('minor'))
+
+ match result.get_string('state'):
+ 'alpha':
+ info['state'] = ReleaseState.ALPHA
+ 'beta':
+ info['state'] = ReleaseState.BETA
+ _:
+ info['state'] = ReleaseState.STABLE
+
+ info['state_version'] = int(result.get_string('stateversion'))
+
+ return info
+
+
+func request_update_download() -> void:
+ # Safeguard the actual dialogue manager repo from accidentally updating itself
+ if DirAccess.dir_exists_absolute("res://test-project/"):
+ prints("[Dialogic] Looks like you are working on the addon. You can't update the addon from within itself.")
+ downdload_completed.emit(DownloadResult.FAILURE)
+ return
+
+ $DownloadRequest.request(update_info.zipball_url)
+
+
+func _on_DownloadRequest_completed(result:int, response_code:int, headers:PackedStringArray, body:PackedByteArray):
+ if result != HTTPRequest.RESULT_SUCCESS:
+ downdload_completed.emit(DownloadResult.FAILURE)
+ return
+
+ # Save the downloaded zip
+ var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
+ zip_file.store_buffer(body)
+ zip_file.close()
+
+ OS.move_to_trash(ProjectSettings.globalize_path("res://addons/dialogic"))
+
+ var zip_reader: ZIPReader = ZIPReader.new()
+ zip_reader.open(TEMP_FILE_NAME)
+ var files: PackedStringArray = zip_reader.get_files()
+
+ var base_path: String = files[0].path_join('addons/')
+ for path in files:
+ if not "dialogic/" in path:
+ continue
+
+ var new_file_path: String = path.replace(base_path, "")
+ if path.ends_with("/"):
+ DirAccess.make_dir_recursive_absolute("res://addons/".path_join(new_file_path))
+ else:
+ var file: FileAccess = FileAccess.open("res://addons/".path_join(new_file_path), FileAccess.WRITE)
+ file.store_buffer(zip_reader.read_file(path))
+
+ zip_reader.close()
+ DirAccess.remove_absolute(TEMP_FILE_NAME)
+
+ downdload_completed.emit(DownloadResult.SUCCESS)
+
+
+###################### SOME UI MANAGEMENT #####################################
+################################################################################
+
+func setup_version_indicator() -> void:
+ version_indicator = %Sidebar.get_node('%CurrentVersion')
+ version_indicator.pressed.connect($Window/UpdateInstallWindow.open)
+ version_indicator.text = get_current_version()
+
+
+func _on_update_check_completed(result:int):
+ var result_color: Color
+ match result:
+ UpdateCheckResult.UPDATE_AVAILABLE:
+ result_color = version_indicator.get_theme_color("warning_color", "Editor")
+ version_indicator.icon = version_indicator.get_theme_icon("StatusWarning", "EditorIcons")
+ $Window/UpdateInstallWindow.load_info(update_info, result)
+ UpdateCheckResult.UP_TO_DATE:
+ result_color = version_indicator.get_theme_color("success_color", "Editor")
+ version_indicator.icon = version_indicator.get_theme_icon("StatusSuccess", "EditorIcons")
+ $Window/UpdateInstallWindow.load_info(current_info, result)
+ UpdateCheckResult.NO_ACCESS:
+ result_color = version_indicator.get_theme_color("success_color", "Editor")
+ version_indicator.icon = version_indicator.get_theme_icon("GuiRadioCheckedDisabled", "EditorIcons")
+ $Window/UpdateInstallWindow.load_info(update_info, result)
+
+ version_indicator.add_theme_color_override('font_color', result_color)
+ version_indicator.add_theme_color_override('font_hover_color', result_color.lightened(0.5))
+ version_indicator.add_theme_color_override('font_pressed_color', result_color)
+ version_indicator.add_theme_color_override('font_focus_color', result_color)
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.gd
new file mode 100644
index 0000000..d43ebe3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.gd
@@ -0,0 +1,85 @@
+@tool
+extends Control
+## A scene shown at the end of events that contain other events
+
+var resource: DialogicEndBranchEvent
+
+# References
+var parent_node: Control = null
+var end_control: Control = null
+
+# Indent
+var indent_size := 22
+var current_indent_level := 1
+
+var selected := false
+
+func _ready() -> void:
+ $Icon.icon = get_theme_icon("GuiSpinboxUpdown", "EditorIcons")
+ $Spacer.custom_minimum_size.x = 90 * DialogicUtil.get_editor_scale()
+ visual_deselect()
+ parent_node_changed()
+
+
+## Called by the visual timeline editor
+func visual_select() -> void:
+ modulate = get_theme_color("highlighted_font_color", "Editor")
+ selected = true
+
+
+## Called by the visual timeline editor
+func visual_deselect() -> void:
+ if !parent_node:return
+ selected = false
+ modulate = parent_node.resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.3)
+
+
+func is_selected() -> bool:
+ return selected
+
+
+## Called by the visual timeline editor
+func highlight() -> void:
+ if !parent_node:return
+ modulate = parent_node.resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.6)
+
+
+## Called by the visual timeline editor
+func unhighlight() -> void:
+ modulate = parent_node.resource.event_color
+
+
+func update_hidden_events_indicator(hidden_events_count:int = 0) -> void:
+ $HiddenEventsLabel.visible = hidden_events_count > 0
+ if hidden_events_count == 1:
+ $HiddenEventsLabel.text = "[1 event hidden]"
+ else:
+ $HiddenEventsLabel.text = "["+str(hidden_events_count)+ " events hidden]"
+
+
+## Called by the visual timeline editor
+func set_indent(indent: int) -> void:
+ $Indent.custom_minimum_size = Vector2(indent_size * indent * DialogicUtil.get_editor_scale(), 0)
+ $Indent.visible = indent != 0
+ current_indent_level = indent
+ queue_redraw()
+
+
+## Called by the visual timeline editor if something was edited on the parent event block
+func parent_node_changed() -> void:
+ if parent_node and end_control and end_control.has_method('refresh'):
+ end_control.refresh()
+
+
+## Called on creation if the parent event provides an end control
+func add_end_control(control:Control) -> void:
+ if !control:
+ return
+ add_child(control)
+ control.size_flags_vertical = SIZE_SHRINK_CENTER
+ if "parent_resource" in control:
+ control.parent_resource = parent_node.resource
+ if control.has_method('refresh'):
+ control.refresh()
+ end_control = control
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.tscn
new file mode 100644
index 0000000..9f4147b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/BranchEnd.tscn
@@ -0,0 +1,48 @@
+[gd_scene load_steps=4 format=3 uid="uid://de13fdeebrkcb"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/BranchEnd.gd" id="1"]
+
+[sub_resource type="Image" id="Image_8jrl8"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_44ap0"]
+image = SubResource("Image_8jrl8")
+
+[node name="EndBranch" type="HBoxContainer"]
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 24.0
+grow_horizontal = 2
+mouse_filter = 0
+script = ExtResource("1")
+
+[node name="Indent" type="Control" parent="."]
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Spacer" type="Control" parent="."]
+custom_minimum_size = Vector2(90, 0)
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="Icon" type="Button" parent="."]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(20, 0)
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "Click and drag"
+focus_mode = 0
+mouse_filter = 1
+icon = SubResource("ImageTexture_44ap0")
+flat = true
+
+[node name="HiddenEventsLabel" type="Label" parent="."]
+visible = false
+layout_mode = 2
+text = "XX Events hidden"
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.gd
new file mode 100644
index 0000000..f5c39d3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.gd
@@ -0,0 +1,419 @@
+@tool
+extends MarginContainer
+
+## Scene that represents an event in the visual timeline editor.
+
+signal content_changed()
+
+## REFERENCES
+var resource: DialogicEvent
+var editor_reference
+# for choice and condition
+var end_node: Node = null:
+ get:
+ return end_node
+ set(node):
+ end_node = node
+ %ToggleChildrenVisibilityButton.visible = true if end_node else false
+
+
+## FLAGS
+var selected := false
+# Whether the body is visible
+var expanded := true
+var body_was_build := false
+var has_any_enabled_body_content := false
+# Whether contained events (e.g. in choices) are visible
+var collapsed := false
+
+
+## CONSTANTS
+const icon_size := 28
+const indent_size := 22
+
+## STATE
+# List that stores visibility conditions
+var field_list := []
+var current_indent_level := 1
+
+
+#region UI AND LOGIC INITIALIZATION
+################################################################################
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ if not resource:
+ printerr("[Dialogic] Event block was added without a resource specified.")
+ return
+
+ initialize_ui()
+ initialize_logic()
+
+
+func initialize_ui() -> void:
+ var _scale := DialogicUtil.get_editor_scale()
+
+ $PanelContainer.self_modulate = get_theme_color("accent_color", "Editor")
+
+ # Warning Icon
+ %Warning.texture = get_theme_icon("NodeWarning", "EditorIcons")
+ %Warning.size = Vector2(16 * _scale, 16 * _scale)
+ %Warning.position = Vector2(-5 * _scale, -10 * _scale)
+
+ # Expand Button
+ %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
+ %ToggleBodyVisibilityButton.set("theme_override_colors/icon_normal_color", get_theme_color("contrast_color_2", "Editor"))
+ %ToggleBodyVisibilityButton.set("theme_override_colors/icon_hover_color", get_theme_color("accent_color", "Editor"))
+ %ToggleBodyVisibilityButton.set("theme_override_colors/icon_pressed_color", get_theme_color("contrast_color_2", "Editor"))
+ %ToggleBodyVisibilityButton.set("theme_override_colors/icon_hover_pressed_color", get_theme_color("accent_color", "Editor"))
+ %ToggleBodyVisibilityButton.add_theme_stylebox_override('hover_pressed', StyleBoxEmpty.new())
+
+ # Icon Panel
+ %IconPanel.tooltip_text = resource.event_name
+ %IconPanel.self_modulate = resource.event_color
+
+ # Event Icon
+ %IconTexture.texture = resource._get_icon()
+
+ %IconPanel.custom_minimum_size = Vector2(icon_size, icon_size) * _scale
+ %IconTexture.custom_minimum_size = %IconPanel.custom_minimum_size
+
+ var custom_style: StyleBoxFlat = %IconPanel.get_theme_stylebox('panel')
+ custom_style.set_corner_radius_all(5 * _scale)
+
+ # Focus Mode
+ set_focus_mode(1) # Allowing this node to grab focus
+
+ # Separation on the header
+ %Header.add_theme_constant_override("custom_constants/separation", 5 * _scale)
+
+ # Collapse Button
+ %ToggleChildrenVisibilityButton.toggled.connect(_on_collapse_toggled)
+ %ToggleChildrenVisibilityButton.icon = get_theme_icon("Collapse", "EditorIcons")
+ %ToggleChildrenVisibilityButton.hide()
+
+ %Body.add_theme_constant_override("margin_left", icon_size * _scale)
+
+ visual_deselect()
+
+
+func initialize_logic() -> void:
+ resized.connect(get_parent().get_parent().queue_redraw)
+
+ resource.ui_update_needed.connect(_on_resource_ui_update_needed)
+ resource.ui_update_warning.connect(set_warning)
+
+ content_changed.connect(recalculate_field_visibility)
+
+ _on_ToggleBodyVisibility_toggled(resource.expand_by_default or resource.created_by_button)
+
+#endregion
+
+
+#region VISUAL METHODS
+################################################################################
+
+func visual_select() -> void:
+ $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres"))
+ selected = true
+ %IconPanel.self_modulate = resource.event_color
+ %IconTexture.modulate = get_theme_color("icon_saturation", "Editor")
+
+
+func visual_deselect() -> void:
+ $PanelContainer.add_theme_stylebox_override('panel', load("res://addons/dialogic/Editor/Events/styles/unselected_stylebox.tres"))
+ selected = false
+ %IconPanel.self_modulate = resource.event_color.lerp(Color.DARK_SLATE_GRAY, 0.1)
+ %IconTexture.modulate = get_theme_color('font_color', 'Label')
+
+
+func is_selected() -> bool:
+ return selected
+
+
+func set_warning(text:String= "") -> void:
+ if !text.is_empty():
+ %Warning.show()
+ %Warning.tooltip_text = text
+ else:
+ %Warning.hide()
+
+
+func set_indent(indent: int) -> void:
+ add_theme_constant_override("margin_left", indent_size * indent * DialogicUtil.get_editor_scale())
+ current_indent_level = indent
+
+#endregion
+
+
+#region EVENT FIELDS
+################################################################################
+
+var FIELD_SCENES := {
+ DialogicEvent.ValueType.MULTILINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn",
+ DialogicEvent.ValueType.SINGLELINE_TEXT: "res://addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn",
+ DialogicEvent.ValueType.FILE: "res://addons/dialogic/Editor/Events/Fields/field_file.tscn",
+ DialogicEvent.ValueType.BOOL: "res://addons/dialogic/Editor/Events/Fields/field_bool_check.tscn",
+ DialogicEvent.ValueType.BOOL_BUTTON: "res://addons/dialogic/Editor/Events/Fields/field_bool_button.tscn",
+ DialogicEvent.ValueType.CONDITION: "res://addons/dialogic/Editor/Events/Fields/field_condition.tscn",
+ DialogicEvent.ValueType.ARRAY: "res://addons/dialogic/Editor/Events/Fields/field_array.tscn",
+ DialogicEvent.ValueType.DICTIONARY: "res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn",
+ DialogicEvent.ValueType.DYNAMIC_OPTIONS: "res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn",
+ DialogicEvent.ValueType.FIXED_OPTIONS : "res://addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn",
+ DialogicEvent.ValueType.NUMBER: "res://addons/dialogic/Editor/Events/Fields/field_number.tscn",
+ DialogicEvent.ValueType.VECTOR2: "res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn",
+ DialogicEvent.ValueType.VECTOR3: "res://addons/dialogic/Editor/Events/Fields/field_vector3.tscn",
+ DialogicEvent.ValueType.VECTOR4: "res://addons/dialogic/Editor/Events/Fields/field_vector4.tscn",
+ DialogicEvent.ValueType.COLOR: "res://addons/dialogic/Editor/Events/Fields/field_color.tscn"
+ }
+
+func build_editor(build_header:bool = true, build_body:bool = false) -> void:
+ var current_body_container: HFlowContainer = null
+
+ if build_body and body_was_build:
+ build_body = false
+
+ if build_body:
+ if body_was_build:
+ return
+ current_body_container = HFlowContainer.new()
+ %BodyContent.add_child(current_body_container)
+ body_was_build = true
+
+ for p in resource.get_event_editor_info():
+ field_list.append({'node':null, 'location':p.location})
+ if p.has('condition'):
+ field_list[-1]['condition'] = p.condition
+
+ if !build_body and p.location == 1:
+ continue
+ elif !build_header and p.location == 0:
+ continue
+
+ ### --------------------------------------------------------------------
+ ### 1. CREATE A NODE OF THE CORRECT TYPE FOR THE PROPERTY
+ var editor_node: Control
+
+ ### LINEBREAK
+ if p.name == "linebreak":
+ field_list.remove_at(field_list.size()-1)
+ if !current_body_container.get_child_count():
+ current_body_container.queue_free()
+ current_body_container = HFlowContainer.new()
+ %BodyContent.add_child(current_body_container)
+ continue
+
+ elif p.field_type in FIELD_SCENES:
+ editor_node = load(FIELD_SCENES[p.field_type]).instantiate()
+
+ elif p.field_type == resource.ValueType.LABEL:
+ editor_node = Label.new()
+ editor_node.text = p.display_info.text
+ editor_node.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ editor_node.set('custom_colors/font_color', Color("#7b7b7b"))
+ editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
+
+ elif p.field_type == resource.ValueType.BUTTON:
+ editor_node = Button.new()
+ editor_node.text = p.display_info.text
+ editor_node.tooltip_text = p.display_info.get('tooltip', '')
+ if typeof(p.display_info.icon) == TYPE_ARRAY:
+ editor_node.icon = callv('get_theme_icon', p.display_info.icon)
+ else:
+ editor_node.icon = p.display_info.icon
+ editor_node.flat = true
+ editor_node.custom_minimum_size.x = 30 * DialogicUtil.get_editor_scale()
+ editor_node.pressed.connect(p.display_info.callable)
+
+ ## CUSTOM
+ elif p.field_type == resource.ValueType.CUSTOM:
+ if p.display_info.has('path'):
+ editor_node = load(p.display_info.path).instantiate()
+
+ ## ELSE
+ else:
+ editor_node = Label.new()
+ editor_node.text = p.name
+ editor_node.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
+
+
+ field_list[-1]['node'] = editor_node
+ ### --------------------------------------------------------------------
+ # Some things need to be called BEFORE the field is added to the tree
+ if editor_node is DialogicVisualEditorField:
+ editor_node.event_resource = resource
+
+ editor_node.property_name = p.name
+ field_list[-1]['property'] = p.name
+
+ editor_node._load_display_info(p.display_info)
+
+ var location: Control = %HeaderContent
+ if p.location == 1:
+ location = current_body_container
+ location.add_child(editor_node)
+
+ # Some things need to be called AFTER the field is added to the tree
+ if editor_node is DialogicVisualEditorField:
+ # Only set the value if the field is visible
+ #
+ # This prevents events with varied value types (event_setting, event_variable)
+ # from injecting incorrect types into hidden fields, which then throw errors
+ # in the console.
+ if p.has('condition') and not p.condition.is_empty():
+ if _evaluate_visibility_condition(p):
+ editor_node._set_value(resource.get(p.name))
+ else:
+ editor_node._set_value(resource.get(p.name))
+
+ editor_node.value_changed.connect(set_property)
+
+ editor_node.tooltip_text = p.display_info.get('tooltip', '')
+
+ # Apply autofocus
+ if resource.created_by_button and p.display_info.get('autofocus', false):
+ editor_node.call_deferred('take_autofocus')
+
+ ### --------------------------------------------------------------------
+ ### 4. ADD LEFT AND RIGHT TEXT
+ var left_label: Label = null
+ var right_label: Label = null
+ if !p.get('left_text', '').is_empty():
+ left_label = Label.new()
+ left_label.text = p.get('left_text')
+ left_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ left_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
+ location.add_child(left_label)
+ location.move_child(left_label, editor_node.get_index())
+ if !p.get('right_text', '').is_empty():
+ right_label = Label.new()
+ right_label.text = p.get('right_text')
+ right_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ right_label.add_theme_color_override('font_color', resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8))
+ location.add_child(right_label)
+ location.move_child(right_label, editor_node.get_index()+1)
+
+ ### --------------------------------------------------------------------
+ ### 5. REGISTER CONDITION
+ if p.has('condition'):
+ field_list[-1]['condition'] = p.condition
+ if left_label:
+ field_list.append({'node': left_label, 'condition':p.condition, 'location':p.location})
+ if right_label:
+ field_list.append({'node': right_label, 'condition':p.condition, 'location':p.location})
+
+
+ if build_body:
+ if current_body_container.get_child_count() == 0:
+ expanded = false
+ %Body.visible = false
+
+ recalculate_field_visibility()
+
+
+func recalculate_field_visibility() -> void:
+ has_any_enabled_body_content = false
+ for p in field_list:
+ if !p.has('condition') or p.condition.is_empty():
+ if p.node != null:
+ p.node.show()
+ if p.location == 1:
+ has_any_enabled_body_content = true
+ else:
+ if _evaluate_visibility_condition(p):
+ if p.node != null:
+ if p.node.visible == false and p.has("property"):
+ p.node._set_value(resource.get(p.property))
+ p.node.show()
+ if p.location == 1:
+ has_any_enabled_body_content = true
+ else:
+ if p.node != null:
+ p.node.hide()
+ %ToggleBodyVisibilityButton.visible = has_any_enabled_body_content
+
+
+func set_property(property_name:String, value:Variant) -> void:
+ resource.set(property_name, value)
+ content_changed.emit()
+ if end_node:
+ end_node.parent_node_changed()
+
+
+func _evaluate_visibility_condition(p: Dictionary) -> bool:
+ var expr := Expression.new()
+ expr.parse(p.condition)
+ var result: bool
+ if expr.execute([], resource):
+ result = true
+ else:
+ result = false
+ if expr.has_execute_failed():
+ printerr("[Dialogic] Failed executing visibility condition for '",p.get('property', 'unnamed'),"': " + expr.get_error_text())
+ return result
+
+
+func _on_resource_ui_update_needed() -> void:
+ for node_info in field_list:
+ if node_info.node and node_info.node.has_method('set_value'):
+ # Only set the value if the field is visible
+ #
+ # This prevents events with varied value types (event_setting, event_variable)
+ # from injecting incorrect types into hidden fields, which then throw errors
+ # in the console.
+ if node_info.has('condition') and not node_info.condition.is_empty():
+ if _evaluate_visibility_condition(node_info):
+ node_info.node.set_value(resource.get(node_info.property))
+ else:
+ node_info.node.set_value(resource.get(node_info.property))
+ recalculate_field_visibility()
+
+
+#region SIGNALS
+################################################################################
+
+func _on_collapse_toggled(toggled:bool) -> void:
+ collapsed = toggled
+ var timeline_editor: Node = find_parent('VisualEditor')
+ if (timeline_editor != null):
+ # @todo select item and clear selection is marked as "private" in TimelineEditor.gd
+ # consider to make it "public" or add a public helper function
+ timeline_editor.indent_events()
+
+
+
+func _on_ToggleBodyVisibility_toggled(button_pressed:bool) -> void:
+ if button_pressed and !body_was_build:
+ build_editor(false, true)
+ %ToggleBodyVisibilityButton.set_pressed_no_signal(button_pressed)
+
+ if button_pressed:
+ %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldDownArrow", "EditorIcons")
+ else:
+ %ToggleBodyVisibilityButton.icon = get_theme_icon("CodeFoldedRightArrow", "EditorIcons")
+
+ expanded = button_pressed
+ %Body.visible = button_pressed
+
+ if find_parent('VisualEditor') != null:
+ find_parent('VisualEditor').indent_events()
+
+
+func _on_EventNode_gui_input(event:InputEvent) -> void:
+ if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
+ grab_focus() # Grab focus to avoid copy pasting text or events
+ if event.double_click:
+ if has_any_enabled_body_content:
+ _on_ToggleBodyVisibility_toggled(!expanded)
+ # For opening the context menu
+ if event is InputEventMouseButton:
+ if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
+ var popup: PopupMenu = get_parent().get_parent().get_node('EventPopupMenu')
+ popup.current_event = self
+ popup.popup_on_parent(Rect2(get_global_mouse_position(),Vector2()))
+ if resource.help_page_path == "":
+ popup.set_item_disabled(2, true)
+ else:
+ popup.set_item_disabled(2, false)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.tscn
new file mode 100644
index 0000000..5bd715b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_block.tscn
@@ -0,0 +1,133 @@
+[gd_scene load_steps=8 format=3 uid="uid://bwaxj1n401fp4"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/EventBlock/event_block.gd" id="1"]
+[ext_resource type="StyleBox" uid="uid://cl75ikyq2is7c" path="res://addons/dialogic/Editor/Events/styles/unselected_stylebox.tres" id="2_axj84"]
+[ext_resource type="Texture2D" uid="uid://dybg3l5pwetne" path="res://addons/dialogic/Editor/Images/plugin-icon.svg" id="6"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_otutu"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="Image" id="Image_mem38"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_rc1wh"]
+image = SubResource("Image_mem38")
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ee4ub"]
+
+[node name="EventNode" type="MarginContainer"]
+anchors_preset = 10
+anchor_right = 1.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+size_flags_vertical = 9
+focus_mode = 1
+script = ExtResource("1")
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+self_modulate = Color(0, 0, 0, 1)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_axj84")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Header" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="IconPanel" type="Panel" parent="PanelContainer/VBoxContainer/Header"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+mouse_filter = 1
+mouse_default_cursor_shape = 6
+theme_override_styles/panel = SubResource("StyleBoxFlat_otutu")
+
+[node name="IconTexture" type="TextureRect" parent="PanelContainer/VBoxContainer/Header/IconPanel"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 0
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+texture = ExtResource("6")
+expand_mode = 1
+stretch_mode = 5
+
+[node name="Warning" type="TextureRect" parent="PanelContainer/VBoxContainer/Header/IconPanel"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 0
+offset_left = -5.5
+offset_top = -11.0
+offset_right = 12.1
+offset_bottom = 6.6
+texture = SubResource("ImageTexture_rc1wh")
+stretch_mode = 5
+
+[node name="HeaderContent" type="HBoxContainer" parent="PanelContainer/VBoxContainer/Header"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="ToggleBodyVisibilityButton" type="Button" parent="PanelContainer/VBoxContainer/Header"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(20, 0)
+layout_mode = 2
+size_flags_horizontal = 0
+tooltip_text = "Fold/Unfold Settings"
+theme_override_styles/normal = SubResource("StyleBoxEmpty_ee4ub")
+theme_override_styles/hover = SubResource("StyleBoxEmpty_ee4ub")
+theme_override_styles/pressed = SubResource("StyleBoxEmpty_ee4ub")
+theme_override_styles/disabled = SubResource("StyleBoxEmpty_ee4ub")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_ee4ub")
+toggle_mode = true
+icon = SubResource("ImageTexture_rc1wh")
+flat = true
+
+[node name="ToggleChildrenVisibilityButton" type="Button" parent="PanelContainer/VBoxContainer/Header"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 10
+tooltip_text = "Collapse Contained Events"
+toggle_mode = true
+icon = SubResource("ImageTexture_rc1wh")
+flat = true
+
+[node name="Body" type="MarginContainer" parent="PanelContainer/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_left = 4
+
+[node name="BodyContent" type="VBoxContainer" parent="PanelContainer/VBoxContainer/Body"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+
+[connection signal="gui_input" from="." to="." method="_on_EventNode_gui_input"]
+[connection signal="toggled" from="PanelContainer/VBoxContainer/Header/ToggleBodyVisibilityButton" to="." method="_on_ToggleBodyVisibility_toggled"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd
new file mode 100644
index 0000000..0b765f6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd
@@ -0,0 +1,22 @@
+@tool
+extends PopupMenu
+
+var current_event: Node = null
+
+func _ready() -> void:
+ clear()
+ add_icon_item(get_theme_icon("Duplicate", "EditorIcons"), "Duplicate")
+ add_separator()
+ add_icon_item(get_theme_icon("Help", "EditorIcons"), "Documentation")
+ add_icon_item(get_theme_icon("CodeHighlighter", "EditorIcons"), "Open Code")
+ add_separator()
+ add_icon_item(get_theme_icon("ArrowUp", "EditorIcons"), "Move up")
+ add_icon_item(get_theme_icon("ArrowDown", "EditorIcons"), "Move down")
+ add_separator()
+ add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Delete")
+
+ var menu_background := StyleBoxFlat.new()
+ menu_background.bg_color = get_parent().get_theme_color("base_color", "Editor")
+ add_theme_stylebox_override('panel', menu_background)
+ add_theme_stylebox_override('hover', get_theme_stylebox("FocusViewport", "EditorStyles"))
+ add_theme_color_override('font_color_hover', get_parent().get_theme_color("accent_color", "Editor"))
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.gd
new file mode 100644
index 0000000..20c7c72
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.gd
@@ -0,0 +1,28 @@
+@tool
+extends PanelContainer
+
+## Event block field part for the Array field.
+
+signal value_changed()
+
+var value_field: Node
+var value_type: int = -1
+
+var current_value: Variant
+
+func _ready() -> void:
+ %FlexValue.value_changed.connect(emit_signal.bind("value_changed"))
+ %Delete.icon = get_theme_icon("Remove", "EditorIcons")
+
+
+func set_value(value:Variant):
+ %FlexValue.set_value(value)
+
+
+func get_value() -> Variant:
+ return %FlexValue.current_value
+
+
+func _on_delete_pressed() -> void:
+ queue_free()
+ value_changed.emit()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.tscn
new file mode 100644
index 0000000..7ec2d25
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/array_part.tscn
@@ -0,0 +1,39 @@
+[gd_scene load_steps=5 format=3 uid="uid://ch4j2lesn1sis"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/array_part.gd" id="1"]
+[ext_resource type="PackedScene" uid="uid://dl08ubinx6ugu" path="res://addons/dialogic/Editor/Events/Fields/field_flex_value.tscn" id="3_s4j7i"]
+
+[sub_resource type="Image" id="Image_dcsrk"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_cpbga"]
+image = SubResource("Image_dcsrk")
+
+[node name="ArrayValue" type="PanelContainer"]
+offset_left = 2.0
+offset_right = 76.0
+offset_bottom = 24.0
+theme_type_variation = &"DialogicEventEditGroup"
+script = ExtResource("1")
+
+[node name="Value" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="FlexValue" parent="Value" instance=ExtResource("3_s4j7i")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Delete" type="Button" parent="Value"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Remove"
+icon = SubResource("ImageTexture_cpbga")
+flat = true
+
+[connection signal="pressed" from="Value/Delete" to="." method="_on_delete_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.gd
new file mode 100644
index 0000000..7c28572
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.gd
@@ -0,0 +1,44 @@
+@tool
+extends PanelContainer
+
+## Event block field part for the Dictionary field.
+
+signal value_changed()
+
+
+func set_key(value:String) -> void:
+ %Key.text = str(value)
+
+
+func get_key() -> String:
+ return %Key.text
+
+
+func set_value(value:Variant) -> void:
+ %FlexValue.set_value(value)
+
+
+func get_value() -> Variant:
+ return %FlexValue.current_value
+
+
+func _ready() -> void:
+ %Delete.icon = get_theme_icon("Remove", "EditorIcons")
+
+
+func focus_key() -> void:
+ %Key.grab_focus()
+
+
+func _on_key_text_changed(new_text: String) -> void:
+ value_changed.emit()
+
+
+func _on_flex_value_value_changed() -> void:
+ value_changed.emit()
+
+
+func _on_delete_pressed() -> void:
+ queue_free()
+ value_changed.emit()
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.tscn
new file mode 100644
index 0000000..dc1f961
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/dictionary_part.tscn
@@ -0,0 +1,54 @@
+[gd_scene load_steps=5 format=3 uid="uid://b27yweami3mxi"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/dictionary_part.gd" id="2_q88pg"]
+[ext_resource type="PackedScene" uid="uid://dl08ubinx6ugu" path="res://addons/dialogic/Editor/Events/Fields/field_flex_value.tscn" id="3_p082d"]
+
+[sub_resource type="Image" id="Image_dcsrk"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_cpbga"]
+image = SubResource("Image_dcsrk")
+
+[node name="DictionaryPart" type="PanelContainer"]
+offset_left = 1.0
+offset_top = -1.0
+offset_right = 131.0
+offset_bottom = 32.0
+theme_type_variation = &"DialogicEventEditGroup"
+script = ExtResource("2_q88pg")
+
+[node name="HBox" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Key" type="LineEdit" parent="HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_type_variation = &"DialogicEventEdit"
+expand_to_text_length = true
+select_all_on_focus = true
+
+[node name="Label" type="Label" parent="HBox"]
+layout_mode = 2
+text = ":"
+
+[node name="FlexValue" parent="HBox" instance=ExtResource("3_p082d")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Delete" type="Button" parent="HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Remove"
+icon = SubResource("ImageTexture_cpbga")
+flat = true
+
+[connection signal="text_changed" from="HBox/Key" to="." method="_on_key_text_changed"]
+[connection signal="value_changed" from="HBox/FlexValue" to="." method="_on_flex_value_value_changed"]
+[connection signal="pressed" from="HBox/Delete" to="." method="_on_delete_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.gd
new file mode 100644
index 0000000..301ade5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.gd
@@ -0,0 +1,48 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for editing arrays.
+
+
+const ArrayValue := "res://addons/dialogic/Editor/Events/Fields/array_part.tscn"
+
+
+func _ready() -> void:
+ %Add.icon = get_theme_icon("Add", "EditorIcons")
+ %Add.pressed.connect(_on_AddButton_pressed)
+
+
+func _set_value(value:Variant) -> void:
+ value = value as Array
+ for child in get_children():
+ if child != %Add:
+ child.queue_free()
+
+ for item in value:
+ var x: Node = load(ArrayValue).instantiate()
+ add_child(x)
+ x.set_value(item)
+ x.value_changed.connect(recalculate_values)
+ move_child(%Add, -1)
+
+
+func _on_value_changed(value:Variant) -> void:
+ value_changed.emit(property_name, value)
+
+
+func recalculate_values() -> void:
+ var arr := []
+ for child in get_children():
+ if child != %Add and !child.is_queued_for_deletion():
+ arr.append(child.get_value())
+ _on_value_changed(arr)
+
+
+func _on_AddButton_pressed() -> void:
+ var x: Control = load(ArrayValue).instantiate()
+ add_child(x)
+ x.set_value("")
+ x.value_changed.connect(recalculate_values)
+ recalculate_values()
+ move_child(%Add, -1)
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.tscn
new file mode 100644
index 0000000..ff5ac6c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_array.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=4 format=3 uid="uid://btmy7ageqpyq1"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_array.gd" id="2"]
+
+[sub_resource type="Image" id="Image_dcsrk"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_cpbga"]
+image = SubResource("Image_dcsrk")
+
+[node name="Field_Array" type="HFlowContainer"]
+offset_right = 329.0
+offset_bottom = 256.0
+size_flags_horizontal = 3
+script = ExtResource("2")
+
+[node name="Add" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Add value"
+icon = SubResource("ImageTexture_cpbga")
+flat = true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.gd
new file mode 100644
index 0000000..1593be3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.gd
@@ -0,0 +1,38 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for boolean values.
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ add_theme_color_override("icon_normal_color", get_theme_color("disabled_font_color", "Editor"))
+ add_theme_color_override("icon_hover_color", get_theme_color("warning_color", "Editor"))
+ add_theme_color_override("icon_pressed_color", get_theme_color("icon_saturation", "Editor"))
+ add_theme_color_override("icon_hover_pressed_color", get_theme_color("warning_color", "Editor"))
+ add_theme_color_override("icon_focus_color", get_theme_color("disabled_font_color", "Editor"))
+ self.toggled.connect(_on_value_changed)
+
+
+func _load_display_info(info:Dictionary) -> void:
+ if info.has('editor_icon'):
+ if not is_inside_tree():
+ await ready
+ self.icon = callv('get_theme_icon', info.editor_icon)
+ else:
+ self.icon = info.get('icon', null)
+
+
+func _set_value(value:Variant) -> void:
+ self.button_pressed = true if value else false
+
+#endregion
+
+
+#region SIGNAL METHODS
+################################################################################
+
+func _on_value_changed(value:bool) -> void:
+ value_changed.emit(property_name, value)
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.tscn
new file mode 100644
index 0000000..e4f7d0a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_button.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=2 format=3 uid="uid://iypxcctv080u"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_bool_button.gd" id="1_t1n1f"]
+
+[node name="Field_BoolButton" type="Button"]
+theme_override_colors/icon_normal_color = Color(0, 0, 0, 1)
+theme_override_colors/icon_pressed_color = Color(0, 0, 0, 1)
+theme_override_colors/icon_hover_color = Color(0, 0, 0, 1)
+theme_override_colors/icon_hover_pressed_color = Color(0, 0, 0, 1)
+theme_override_colors/icon_focus_color = Color(0, 0, 0, 1)
+toggle_mode = true
+flat = true
+script = ExtResource("1_t1n1f")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.gd
new file mode 100644
index 0000000..d01a116
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.gd
@@ -0,0 +1,30 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for boolean values.
+
+#region MAIN METHODS
+################################################################################
+func _ready() -> void:
+ self.toggled.connect(_on_value_changed)
+
+
+func _load_display_info(info:Dictionary) -> void:
+ pass
+
+
+func _set_value(value:Variant) -> void:
+ match DialogicUtil.get_variable_value_type(value):
+ DialogicUtil.VarTypes.STRING:
+ self.button_pressed = value and not value.strip_edges() == "false"
+ _:
+ self.button_pressed = value and true
+#endregion
+
+
+#region SIGNAL METHODS
+################################################################################
+func _on_value_changed(value:bool) -> void:
+ value_changed.emit(property_name, value)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.tscn
new file mode 100644
index 0000000..395caf5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_bool_check.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3 uid="uid://dm5hxmhyyxgq"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_bool_check.gd" id="1_ckmtx"]
+
+[node name="Field_BoolCheck" type="CheckButton"]
+offset_right = 44.0
+offset_bottom = 24.0
+script = ExtResource("1_ckmtx")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.gd
new file mode 100644
index 0000000..707dfc5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.gd
@@ -0,0 +1,30 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for color values.
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ self.color_changed.connect(_on_value_changed)
+
+
+func _load_display_info(info:Dictionary) -> void:
+ self.edit_alpha = info.get("edit_alpha", true)
+
+
+func _set_value(value:Variant) -> void:
+ if value is Color:
+ self.color = Color(value)
+
+#endregion
+
+
+#region SIGNAL METHODS
+################################################################################
+
+func _on_value_changed(value: Color) -> void:
+ value_changed.emit(property_name, value)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.tscn
new file mode 100644
index 0000000..5fd3ff2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_color.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://4e0kjekan5e7"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_color.gd" id="1_l666a"]
+
+[node name="Field_Color" type="ColorPickerButton"]
+custom_minimum_size = Vector2(48, 0)
+offset_right = 64.0
+offset_bottom = 31.0
+theme_type_variation = &"DialogicEventEdit"
+text = " "
+color = Color(1, 1, 1, 1)
+script = ExtResource("1_l666a")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.gd
new file mode 100644
index 0000000..db40270
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.gd
@@ -0,0 +1,267 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for displaying conditions in either a simple or complex way.
+
+var _current_value1: Variant = ""
+var _current_value2: Variant = ""
+
+#region MAIN METHODS
+################################################################################
+
+func _set_value(value:Variant) -> void:
+ var too_complex := is_too_complex(value)
+ %ToggleComplex.disabled = too_complex
+ %ToggleComplex.button_pressed = too_complex
+ %ComplexEditor.visible = too_complex
+ %SimpleEditor.visible = !too_complex
+ %ComplexEditor.text = value
+ if not too_complex:
+ load_simple_editor(value)
+
+
+
+func _autofocus() -> void:
+ %Value1Variable.grab_focus()
+
+#endregion
+
+func _ready() -> void:
+ for i in [%Value1Type, %Value2Type]:
+ i.options = [{
+ 'label': 'String',
+ 'icon': ["String", "EditorIcons"],
+ 'value': 0
+ },{
+ 'label': 'Number',
+ 'icon': ["float", "EditorIcons"],
+ 'value': 1
+ },{
+ 'label': 'Variable',
+ 'icon': load("res://addons/dialogic/Editor/Images/Pieces/variable.svg"),
+ 'value': 2
+ },{
+ 'label': 'Bool',
+ 'icon': ["bool", "EditorIcons"],
+ 'value': 3
+ },{
+ 'label': 'Expression',
+ 'icon': ["Variant", "EditorIcons"],
+ 'value': 4
+ }]
+ i.symbol_only = true
+ i.value_changed.connect(value_type_changed.bind(i.name))
+ i.value_changed.connect(something_changed)
+ i.tooltip_text = "Change type"
+
+
+ for i in [%Value1Variable, %Value2Variable]:
+ i.get_suggestions_func = get_variable_suggestions
+ i.value_changed.connect(something_changed)
+
+ %Value1Number.value_changed.connect(something_changed)
+ %Value2Number.value_changed.connect(something_changed)
+ %Value1Text.value_changed.connect(something_changed)
+ %Value2Text.value_changed.connect(something_changed)
+ %Value1Bool.value_changed.connect(something_changed)
+ %Value2Bool.value_changed.connect(something_changed)
+
+ %ToggleComplex.icon = get_theme_icon("Enum", "EditorIcons")
+
+ %Operator.value_changed.connect(something_changed)
+ %Operator.options = [
+ {'label': '==', 'value': '=='},
+ {'label': '>', 'value': '>'},
+ {'label': '<', 'value': '<'},
+ {'label': '<=', 'value': '<='},
+ {'label': '>=', 'value': '>='},
+ {'label': '!=', 'value': '!='}
+ ]
+
+
+func load_simple_editor(condition_string:String) -> void:
+ var data := complex2simple(condition_string)
+ %Value1Type.set_value(get_value_type(data[0], 2))
+ _current_value1 = data[0]
+ value_type_changed('', get_value_type(data[0], 2), 'Value1')
+ %Operator.set_value(data[1].strip_edges())
+ %Value2Type.set_value(get_value_type(data[2], 0))
+ _current_value2 = data[2]
+ value_type_changed('', get_value_type(data[2], 0), 'Value2')
+
+
+func value_type_changed(property:String, value_type:int, value_name:String) -> void:
+ value_name = value_name.trim_suffix('Type')
+ get_node('%'+value_name+'Variable').hide()
+ get_node('%'+value_name+'Text').hide()
+ get_node('%'+value_name+'Number').hide()
+ get_node('%'+value_name+'Bool').hide()
+ var current_val: Variant = ""
+ if '1' in value_name:
+ current_val = _current_value1
+ else:
+ current_val = _current_value2
+ match value_type:
+ 0:
+ get_node('%'+value_name+'Text').show()
+ get_node('%'+value_name+'Text').set_value(trim_value(current_val, value_type))
+ 1:
+ get_node('%'+value_name+'Number').show()
+ get_node('%'+value_name+'Number').set_value(float(current_val.strip_edges()))
+ 2:
+ get_node('%'+value_name+'Variable').show()
+ get_node('%'+value_name+'Variable').set_value(trim_value(current_val, value_type))
+ 3:
+ get_node('%'+value_name+'Bool').show()
+ get_node('%'+value_name+'Bool').set_value(trim_value(current_val, value_type))
+ 4:
+ get_node('%'+value_name+'Text').show()
+ get_node('%'+value_name+'Text').set_value(str(current_val))
+
+
+func get_value_type(value:String, default:int) -> int:
+ value = value.strip_edges()
+ if value.begins_with('"') and value.ends_with('"') and value.count('"')-value.count('\\"') == 2:
+ return 0
+ elif value.begins_with('{') and value.ends_with('}') and value.count('{') == 1:
+ return 2
+ elif value == "true" or value == "false":
+ return 3
+ else:
+ if value.is_empty():
+ return default
+ if value.is_valid_float():
+ return 1
+ else:
+ return 4
+
+
+func prep_value(value:Variant, value_type:int) -> String:
+ if value != null: value = str(value)
+ else: value = ""
+ value = value.strip_edges()
+ match value_type:
+ 0: return '"'+value.replace('"', '\\"')+'"'
+ 2: return '{'+value+'}'
+ _: return value
+
+
+func trim_value(value:Variant, value_type:int) -> String:
+ value = value.strip_edges()
+ match value_type:
+ 0: return value.trim_prefix('"').trim_suffix('"').replace('\\"', '"')
+ 2: return value.trim_prefix('{').trim_suffix('}')
+ 3:
+ if value == "true" or (value and (typeof(value) != TYPE_STRING or value != "false")):
+ return "true"
+ else:
+ return "false"
+ _: return value
+
+
+func something_changed(fake_arg1=null, fake_arg2 = null):
+ if %ComplexEditor.visible:
+ value_changed.emit(property_name, %ComplexEditor.text)
+ return
+
+
+ match %Value1Type.current_value:
+ 0: _current_value1 = prep_value(%Value1Text.text, %Value1Type.current_value)
+ 1: _current_value1 = str(%Value1Number.get_value())
+ 2: _current_value1 = prep_value(%Value1Variable.current_value, %Value1Type.current_value)
+ 3: _current_value1 = prep_value(%Value1Bool.button_pressed, %Value1Type.current_value)
+ _: _current_value1 = prep_value(%Value1Text.text, %Value1Type.current_value)
+
+ match %Value2Type.current_value:
+ 0: _current_value2 = prep_value(%Value2Text.text, %Value2Type.current_value)
+ 1: _current_value2 = str(%Value2Number.get_value())
+ 2: _current_value2 = prep_value(%Value2Variable.current_value, %Value2Type.current_value)
+ 3: _current_value2 = prep_value(%Value2Bool.button_pressed, %Value2Type.current_value)
+ _: _current_value2 = prep_value(%Value2Text.text, %Value2Type.current_value)
+
+ if event_resource:
+ if not %Operator.text in ['==', '!='] and get_value_type(_current_value2, 0) in [0, 3]:
+ event_resource.ui_update_warning.emit("This operator doesn't work with strings and booleans.")
+ else:
+ event_resource.ui_update_warning.emit("")
+
+ value_changed.emit(property_name, get_simple_condition())
+
+
+func is_too_complex(condition:String) -> bool:
+ if condition.strip_edges().is_empty():
+ return false
+
+ var comparison_count: int = 0
+ for i in ['==', '!=', '<=', '<', '>', '>=']:
+ comparison_count += condition.count(i)
+ if comparison_count == 1:
+ return false
+
+ return true
+
+
+## Combines the info from the simple editor fields into a string condition
+func get_simple_condition() -> String:
+ return _current_value1 +" "+ %Operator.text +" "+ _current_value2
+
+
+func complex2simple(condition:String) -> Array:
+ if is_too_complex(condition) or condition.strip_edges().is_empty():
+ return ['', '==','']
+
+ for i in ['==', '!=', '<=', '<', '>', '>=']:
+ if i in condition:
+ var cond_split := Array(condition.split(i, false))
+ return [cond_split[0], i, cond_split[1]]
+
+ return ['', '==','']
+
+
+func _on_toggle_complex_toggled(button_pressed:bool) -> void:
+ if button_pressed:
+ %ComplexEditor.show()
+ %SimpleEditor.hide()
+ %ComplexEditor.text = get_simple_condition()
+ else:
+ if !is_too_complex(%ComplexEditor.text):
+ %ComplexEditor.hide()
+ %SimpleEditor.show()
+ load_simple_editor(%ComplexEditor.text)
+
+
+func _on_complex_editor_text_changed(new_text:String) -> void:
+ %ToggleComplex.disabled = is_too_complex(%ComplexEditor.text)
+ something_changed()
+
+
+func get_variable_suggestions(filter:String) -> Dictionary:
+ var suggestions := {}
+ var vars: Dictionary = ProjectSettings.get_setting('dialogic/variables', {})
+ for var_path in DialogicUtil.list_variables(vars):
+ suggestions[var_path] = {'value':var_path, 'editor_icon':["ClassList", "EditorIcons"]}
+ return suggestions
+
+
+func _on_value_1_variable_value_changed(property_name: Variant, value: Variant) -> void:
+ var type := DialogicUtil.get_variable_type(value)
+ match type:
+ DialogicUtil.VarTypes.BOOL:
+ if not %Operator.text in ["==", "!="]:
+ %Operator.text = "=="
+ if get_value_type(_current_value2, 3) in [0, 1]:
+ %Value2Type.insert_options()
+ %Value2Type.index_pressed(3)
+ DialogicUtil.VarTypes.STRING:
+ if not %Operator.text in ["==", "!="]:
+ %Operator.text = "=="
+ if get_value_type(_current_value2, 0) in [1, 3]:
+ %Value2Type.insert_options()
+ %Value2Type.index_pressed(0)
+ DialogicUtil.VarTypes.FLOAT, DialogicUtil.VarTypes.INT:
+ if get_value_type(_current_value2, 1) in [0,3]:
+ %Value2Type.insert_options()
+ %Value2Type.index_pressed(1)
+
+ something_changed()
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.tscn
new file mode 100644
index 0000000..8024c5b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_condition.tscn
@@ -0,0 +1,101 @@
+[gd_scene load_steps=9 format=3 uid="uid://ir6334lqtuwt"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_condition.gd" id="1_owjj0"]
+[ext_resource type="PackedScene" uid="uid://d3bhehatwoio" path="res://addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn" id="2_f6v80"]
+[ext_resource type="PackedScene" uid="uid://c0vkcehgjsjy" path="res://addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn" id="3_3kfwc"]
+[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="4_6q3a6"]
+[ext_resource type="PackedScene" uid="uid://dm5hxmhyyxgq" path="res://addons/dialogic/Editor/Events/Fields/field_bool_check.tscn" id="5_1x02a"]
+[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="6_5a2xd"]
+
+[sub_resource type="Image" id="Image_je1w7"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_81s3d"]
+image = SubResource("Image_je1w7")
+
+[node name="Field_Condition" type="HBoxContainer"]
+offset_right = 77.0
+offset_bottom = 31.0
+script = ExtResource("1_owjj0")
+
+[node name="SimpleEditor" type="HBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value1Type" parent="SimpleEditor" instance=ExtResource("2_f6v80")]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Change type"
+text = ""
+
+[node name="Value1Text" parent="SimpleEditor" instance=ExtResource("3_3kfwc")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value1Number" parent="SimpleEditor" instance=ExtResource("4_6q3a6")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value1Bool" parent="SimpleEditor" instance=ExtResource("5_1x02a")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value1Variable" parent="SimpleEditor" instance=ExtResource("6_5a2xd")]
+unique_name_in_owner = true
+layout_mode = 2
+placeholder_text = "Variable"
+
+[node name="Operator" parent="SimpleEditor" instance=ExtResource("2_f6v80")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value2Type" parent="SimpleEditor" instance=ExtResource("2_f6v80")]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Change type"
+text = ""
+
+[node name="Value2Text" parent="SimpleEditor" instance=ExtResource("3_3kfwc")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value2Number" parent="SimpleEditor" instance=ExtResource("4_6q3a6")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Value2Variable" parent="SimpleEditor" instance=ExtResource("6_5a2xd")]
+unique_name_in_owner = true
+layout_mode = 2
+placeholder_text = "Variable"
+
+[node name="Value2Bool" parent="SimpleEditor" instance=ExtResource("5_1x02a")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="ComplexEditor" type="LineEdit" parent="."]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(150, 0)
+layout_mode = 2
+mouse_filter = 1
+theme_type_variation = &"DialogicEventEdit"
+text = "VAR.Player.Health > 20 and VAR.Counter < 3 and randi()%3 == 2"
+placeholder_text = "Enter condition"
+expand_to_text_length = true
+
+[node name="ToggleComplex" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Use complex expression"
+toggle_mode = true
+icon = SubResource("ImageTexture_81s3d")
+
+[connection signal="value_changed" from="SimpleEditor/Value1Variable" to="." method="_on_value_1_variable_value_changed"]
+[connection signal="text_changed" from="ComplexEditor" to="." method="_on_complex_editor_text_changed"]
+[connection signal="toggled" from="ToggleComplex" to="." method="_on_toggle_complex_toggled"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.gd
new file mode 100644
index 0000000..f76ef27
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.gd
@@ -0,0 +1,60 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for editing dictionaries.
+
+const DictionaryValue = "res://addons/dialogic/Editor/Events/Fields/dictionary_part.tscn"
+
+func _ready() -> void:
+ %Add.icon = get_theme_icon("Add", "EditorIcons")
+
+
+func _set_value(value:Variant) -> void:
+ for child in get_children():
+ if child != %Add:
+ child.queue_free()
+
+ var dict: Dictionary
+
+ # attempt to take dictionary values, create a fresh one if not possible
+ if typeof(value) == TYPE_DICTIONARY:
+ dict = value
+ elif typeof(value) == TYPE_STRING:
+ if value.begins_with('{'):
+ var result: Variant = JSON.parse_string(value)
+ if result != null:
+ dict = result as Dictionary
+
+ var keys := dict.keys()
+ var values := dict.values()
+
+ for index in dict.size():
+ var x: Node = load(DictionaryValue).instantiate()
+ add_child(x)
+ x.set_value(values[index])
+ x.set_key(keys[index])
+ x.value_changed.connect(recalculate_values)
+ move_child(%Add, -1)
+
+
+func _on_value_changed(value:Variant) -> void:
+ value_changed.emit(property_name, value)
+
+
+func recalculate_values() -> void:
+ var dict := {}
+ for child in get_children():
+ if child != %Add and !child.is_queued_for_deletion():
+ dict[child.get_key()] = child.get_value()
+ _on_value_changed(dict)
+
+
+func _on_AddButton_pressed() -> void:
+ var x: Control = load(DictionaryValue).instantiate()
+ add_child(x)
+ x.set_key("")
+ x.set_value("")
+ x.value_changed.connect(recalculate_values)
+ x.focus_key()
+ recalculate_values()
+ move_child(%Add, -1)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.tscn
new file mode 100644
index 0000000..c5c73e9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_dictionary.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=4 format=3 uid="uid://c74bnmhefu72w"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_dictionary.gd" id="1_p4kmu"]
+
+[sub_resource type="Image" id="Image_dcsrk"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_cpbga"]
+image = SubResource("Image_dcsrk")
+
+[node name="Field_Dictionary" type="HFlowContainer"]
+size_flags_horizontal = 3
+script = ExtResource("1_p4kmu")
+
+[node name="Add" type="Button" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Add key-value pair"
+icon = SubResource("ImageTexture_cpbga")
+flat = true
+
+[connection signal="pressed" from="Add" to="." method="_on_AddButton_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.gd
new file mode 100644
index 0000000..1227dfa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.gd
@@ -0,0 +1,139 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for selecting a file or directory.
+
+#region VARIABLES
+################################################################################
+
+@export var file_filter := ""
+@export var placeholder := ""
+@export var file_mode: EditorFileDialog.FileMode = EditorFileDialog.FILE_MODE_OPEN_FILE
+var resource_icon: Texture:
+ get:
+ return resource_icon
+ set(new_icon):
+ resource_icon = new_icon
+ %Icon.texture = new_icon
+ if new_icon == null:
+ %Field.theme_type_variation = ""
+ else:
+ %Field.theme_type_variation = "LineEditWithIcon"
+
+var max_width := 200
+var current_value: String
+var hide_reset := false
+
+#endregion
+
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ $FocusStyle.add_theme_stylebox_override('panel', get_theme_stylebox('focus', 'DialogicEventEdit'))
+
+ %OpenButton.icon = get_theme_icon("Folder", "EditorIcons")
+ %OpenButton.button_down.connect(_on_OpenButton_pressed)
+
+ %ClearButton.icon = get_theme_icon("Reload", "EditorIcons")
+ %ClearButton.button_up.connect(clear_path)
+ %ClearButton.visible = !hide_reset
+
+ %Field.set_drag_forwarding(Callable(), self._can_drop_data_fw, self._drop_data_fw)
+ %Field.placeholder_text = placeholder
+
+
+func _load_display_info(info:Dictionary) -> void:
+ file_filter = info.get('file_filter', '')
+ placeholder = info.get('placeholder', '')
+ resource_icon = info.get('icon', null)
+ await ready
+
+ if resource_icon == null and info.has('editor_icon'):
+ resource_icon = callv('get_theme_icon', info.editor_icon)
+
+
+func _set_value(value: Variant) -> void:
+ current_value = value
+ var text: String = value
+
+ if file_mode != EditorFileDialog.FILE_MODE_OPEN_DIR:
+ text = value.get_file()
+ %Field.tooltip_text = value
+
+ if %Field.get_theme_font('font').get_string_size(
+ text, 0, -1,
+ %Field.get_theme_font_size('font_size')).x > max_width:
+ %Field.expand_to_text_length = false
+ %Field.custom_minimum_size.x = max_width
+ %Field.size.x = 0
+ else:
+ %Field.custom_minimum_size.x = 0
+ %Field.expand_to_text_length = true
+
+ if not %Field.text == text:
+ value_changed.emit(property_name, current_value)
+ %Field.text = text
+
+ %ClearButton.visible = not value.is_empty() and not hide_reset
+
+
+#endregion
+
+
+#region BUTTONS
+################################################################################
+
+func _on_OpenButton_pressed() -> void:
+ find_parent('EditorView').godot_file_dialog(_on_file_dialog_selected, file_filter, file_mode, "Open "+ property_name)
+
+
+func _on_file_dialog_selected(path:String) -> void:
+ _set_value(path)
+ value_changed.emit(property_name, path)
+
+
+func clear_path() -> void:
+ _set_value("")
+ value_changed.emit(property_name, "")
+
+#endregion
+
+
+#region DRAG AND DROP
+################################################################################
+
+func _can_drop_data_fw(_at_position: Vector2, data: Variant) -> bool:
+ if typeof(data) == TYPE_DICTIONARY and data.has('files') and len(data.files) == 1:
+
+ if file_filter:
+
+ if '*.'+data.files[0].get_extension() in file_filter:
+ return true
+
+ else: return true
+
+ return false
+
+
+func _drop_data_fw(_at_position: Vector2, data: Variant) -> void:
+ var file: String = data.files[0]
+ _on_file_dialog_selected(file)
+
+#endregion
+
+
+#region VISUALS FOR FOCUS
+################################################################################
+
+func _on_field_focus_entered() -> void:
+ $FocusStyle.show()
+
+
+func _on_field_focus_exited() -> void:
+ $FocusStyle.hide()
+ var field_text: String = %Field.text
+ _on_file_dialog_selected(field_text)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.tscn
new file mode 100644
index 0000000..8b47de7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_file.tscn
@@ -0,0 +1,87 @@
+[gd_scene load_steps=8 format=3 uid="uid://7mvxuaulctcq"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_file.gd" id="1_0grcf"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_tr837"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wq6bt"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6b7on"]
+
+[sub_resource type="Image" id="Image_bpoe1"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_dkuon"]
+image = SubResource("Image_bpoe1")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yv1pn"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(1, 0.365, 0.365, 1)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_detail = 1
+
+[node name="Field_File" type="MarginContainer"]
+offset_right = 314.0
+offset_bottom = 40.0
+theme_type_variation = &"DialogicEventEdit"
+script = ExtResource("1_0grcf")
+
+[node name="BG" type="PanelContainer" parent="."]
+layout_mode = 2
+theme_type_variation = &"DialogicEventEdit"
+
+[node name="HBox" type="HBoxContainer" parent="BG"]
+layout_mode = 2
+alignment = 2
+
+[node name="Icon" type="TextureRect" parent="BG/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+mouse_filter = 2
+
+[node name="Field" type="LineEdit" parent="BG/HBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 1
+theme_override_styles/normal = SubResource("StyleBoxEmpty_tr837")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_wq6bt")
+theme_override_styles/read_only = SubResource("StyleBoxEmpty_6b7on")
+expand_to_text_length = true
+
+[node name="OpenButton" type="Button" parent="BG/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+icon = SubResource("ImageTexture_dkuon")
+flat = true
+
+[node name="ClearButton" type="Button" parent="BG/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+icon = SubResource("ImageTexture_dkuon")
+flat = true
+
+[node name="FocusStyle" type="Panel" parent="."]
+visible = false
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_yv1pn")
+
+[connection signal="focus_entered" from="BG/HBox/Field" to="." method="_on_field_focus_entered"]
+[connection signal="focus_exited" from="BG/HBox/Field" to="." method="_on_field_focus_exited"]
+[connection signal="text_submitted" from="BG/HBox/Field" to="." method="_on_file_dialog_selected"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.gd
new file mode 100644
index 0000000..be7a98d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.gd
@@ -0,0 +1,154 @@
+@tool
+extends HBoxContainer
+
+## Event block field part for a value that can change type.
+
+signal value_changed
+
+var value_field: Node
+var value_type: int = -1
+
+var current_value: Variant
+
+func _ready() -> void:
+ %ValueType.options = [{
+ 'label': 'String',
+ 'icon': ["String", "EditorIcons"],
+ 'value': TYPE_STRING
+ },{
+ 'label': 'Number (int)',
+ 'icon': ["int", "EditorIcons"],
+ 'value': TYPE_INT
+ },{
+ 'label': 'Number (float)',
+ 'icon': ["float", "EditorIcons"],
+ 'value': TYPE_FLOAT
+ },{
+ 'label': 'Boolean',
+ 'icon': ["bool", "EditorIcons"],
+ 'value': TYPE_BOOL
+ },{
+ 'label': 'Expression',
+ 'icon': ["Variant", "EditorIcons"],
+ 'value': TYPE_MAX
+ }
+ ]
+ %ValueType.symbol_only = true
+ %ValueType.value_changed.connect(_on_type_changed.bind())
+ %ValueType.tooltip_text = "Change type"
+
+
+func set_value(value:Variant):
+ change_field_type(deduce_type(value))
+ %ValueType.set_value(deduce_type(value))
+ current_value = value
+ match value_type:
+ TYPE_BOOL:
+ value_field.button_pressed = value
+ TYPE_STRING:
+ value_field.text = value
+ TYPE_FLOAT, TYPE_INT:
+ value_field.set_value(value)
+ TYPE_MAX, _:
+ value_field.text = value.trim_prefix('@')
+
+
+func deduce_type(value:Variant) -> int:
+ if value is String and value.begins_with('@'):
+ return TYPE_MAX
+ else:
+ return typeof(value)
+
+
+func _on_type_changed(prop:String, type:Variant) -> void:
+ if type == value_type:
+ return
+
+ match type:
+ TYPE_BOOL:
+ if typeof(current_value) == TYPE_STRING:
+ current_value = DialogicUtil.str_to_bool(current_value)
+ elif value_type == TYPE_FLOAT or value_type == TYPE_INT:
+ current_value = bool(current_value)
+ else:
+ current_value = true if current_value else false
+ set_value(current_value)
+ TYPE_STRING:
+ current_value = str(current_value).trim_prefix('@')
+ set_value(current_value)
+ TYPE_FLOAT:
+ current_value = float(current_value)
+ set_value(current_value)
+ TYPE_INT:
+ current_value = int(current_value)
+ set_value(current_value)
+ TYPE_MAX,_:
+ current_value = var_to_str(current_value)
+ set_value('@'+current_value)
+
+
+ emit_signal.call_deferred('value_changed')
+
+
+func get_value() -> Variant:
+ return current_value
+
+
+func _on_delete_pressed() -> void:
+ queue_free()
+ value_changed.emit()
+
+
+func change_field_type(type:int) -> void:
+ if type == value_type:
+ return
+
+ value_type = type
+
+ if value_field:
+ value_field.queue_free()
+ match type:
+ TYPE_BOOL:
+ value_field = CheckBox.new()
+ value_field.toggled.connect(_on_bool_toggled)
+ TYPE_STRING:
+ value_field = LineEdit.new()
+ value_field.theme_type_variation = "DialogicEventEdit"
+ value_field.text_changed.connect(_on_str_text_changed)
+ value_field.expand_to_text_length = true
+ TYPE_FLOAT, TYPE_INT:
+ value_field = load("res://addons/dialogic/Editor/Events/Fields/field_number.tscn").instantiate()
+ if type == TYPE_FLOAT:
+ value_field.use_float_mode()
+ else:
+ value_field.use_int_mode()
+ value_field.value_changed.connect(_on_number_value_changed.bind(type == TYPE_INT))
+ TYPE_MAX, _:
+ value_field = LineEdit.new()
+ value_field.expand_to_text_length = true
+ value_field.text_changed.connect(_on_expression_changed)
+ add_child(value_field)
+ move_child(value_field, 1)
+
+
+func _on_bool_toggled(value:bool) -> void:
+ current_value = value
+ value_changed.emit()
+
+
+func _on_str_text_changed(value:String) -> void:
+ current_value = value
+ value_changed.emit()
+
+
+func _on_expression_changed(value:String) -> void:
+ current_value = '@'+value
+ value_changed.emit()
+
+
+func _on_number_value_changed(prop:String, value:float, int := false) -> void:
+ if int:
+ current_value = int(value)
+ else:
+ current_value = value
+ value_changed.emit()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.tscn
new file mode 100644
index 0000000..2d46186
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_flex_value.tscn
@@ -0,0 +1,15 @@
+[gd_scene load_steps=3 format=3 uid="uid://dl08ubinx6ugu"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_flex_value.gd" id="1_m5nnp"]
+[ext_resource type="PackedScene" uid="uid://d3bhehatwoio" path="res://addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn" id="3_h10fc"]
+
+[node name="FlexValue" type="HBoxContainer"]
+offset_right = 65.0
+offset_bottom = 22.0
+script = ExtResource("1_m5nnp")
+
+[node name="ValueType" parent="." instance=ExtResource("3_h10fc")]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Change type"
+text = ""
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.gd
new file mode 100644
index 0000000..30d50eb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.gd
@@ -0,0 +1,199 @@
+@tool
+class_name DialogicVisualEditorFieldNumber
+extends DialogicVisualEditorField
+
+## Event block field for integers and floats. Improved version of the native spinbox.
+
+@export var allow_string: bool = false
+@export var step: float = 0.1
+@export var enforce_step: bool = true
+@export var min: float = -INF
+@export var max: float = INF
+@export var value = 0.0
+@export var prefix: String = ""
+@export var suffix: String = ""
+
+var _is_holding_button: bool = false #For handling incrementing while holding key or click
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ if %Value.text.is_empty():
+ set_value(value)
+
+ update_prefix(prefix)
+ update_suffix(suffix)
+
+
+func _load_display_info(info: Dictionary) -> void:
+ match info.get('mode', 0):
+ 0: #FLOAT
+ use_float_mode(info.get('step', 0.1))
+ 1: #INT
+ use_int_mode(info.get('step', 1))
+ 2: #DECIBLE:
+ use_decibel_mode(info.get('step', step))
+
+ for option in info.keys():
+ match option:
+ 'min': min = info[option]
+ 'max': max = info[option]
+ 'prefix': update_prefix(info[option])
+ 'suffix': update_suffix(info[option])
+ 'step':
+ enforce_step = true
+ step = info[option]
+ 'hide_step_button': %Spin.hide()
+
+
+func _set_value(new_value: Variant) -> void:
+ _on_value_text_submitted(str(new_value), true)
+ %Value.tooltip_text = tooltip_text
+
+
+func _autofocus() -> void:
+ %Value.grab_focus()
+
+
+func get_value() -> float:
+ return value
+
+
+func use_float_mode(value_step: float = 0.1) -> void:
+ step = value_step
+ update_suffix("")
+ enforce_step = false
+
+
+func use_int_mode(value_step: float = 1) -> void:
+ step = value_step
+ update_suffix("")
+ enforce_step = true
+
+
+func use_decibel_mode(value_step: float = step) -> void:
+ max = 6
+ update_suffix("dB")
+ min = -80
+
+#endregion
+
+#region UI FUNCTIONALITY
+################################################################################
+var _stop_button_holding: Callable = func(button: BaseButton) -> void:
+ _is_holding_button = false
+ if button.button_up.get_connections().find(_stop_button_holding):
+ button.button_up.disconnect(_stop_button_holding)
+ if button.focus_exited.get_connections().find(_stop_button_holding):
+ button.focus_exited.disconnect(_stop_button_holding)
+ if button.mouse_exited.get_connections().find(_stop_button_holding):
+ button.mouse_exited.disconnect(_stop_button_holding)
+
+
+func _holding_button(value_direction: int, button: BaseButton) -> void:
+ if _is_holding_button:
+ return
+ if _stop_button_holding.get_bound_arguments_count() > 0:
+ _stop_button_holding.unbind(0)
+
+ _is_holding_button = true
+
+ #Ensure removal of our value changing routine when it shouldn't run anymore
+ button.button_up.connect(_stop_button_holding.bind(button))
+ button.focus_exited.connect(_stop_button_holding.bind(button))
+ button.mouse_exited.connect(_stop_button_holding.bind(button))
+
+ var scene_tree: SceneTree = get_tree()
+ var delay_timer_ms: int = 600
+
+ #Instead of awaiting for the duration, await per-frame so we can catch any changes in _is_holding_button and exit completely
+ while(delay_timer_ms > 0):
+ if _is_holding_button == false:
+ return
+ var pre_time: int = Time.get_ticks_msec()
+ await scene_tree.process_frame
+ delay_timer_ms -= Time.get_ticks_msec() - pre_time
+
+ var change_speed: float = 0.25
+
+ while(_is_holding_button == true):
+ await scene_tree.create_timer(change_speed).timeout
+ change_speed = maxf(0.05, change_speed - 0.01)
+ _on_value_text_submitted(str(value+(step * value_direction)))
+
+
+func update_prefix(to_prefix: String) -> void:
+ prefix = to_prefix
+ %Prefix.visible = to_prefix != null and to_prefix != ""
+ %Prefix.text = prefix
+
+
+func update_suffix(to_suffix: String) -> void:
+ suffix = to_suffix
+ %Suffix.visible = to_suffix != null and to_suffix != ""
+ %Suffix.text = suffix
+
+#endregion
+
+#region SIGNAL METHODS
+################################################################################
+func _on_gui_input(event: InputEvent) -> void:
+ if event.is_action('ui_up') and event.get_action_strength('ui_up') > 0.5:
+ _on_value_text_submitted(str(value+step))
+ elif event.is_action('ui_down') and event.get_action_strength('ui_down') > 0.5:
+ _on_value_text_submitted(str(value-step))
+
+
+func _on_increment_button_down(button: NodePath) -> void:
+ _on_value_text_submitted(str(value+step))
+ _holding_button(1.0, get_node(button) as BaseButton)
+
+
+func _on_decrement_button_down(button: NodePath) -> void:
+ _on_value_text_submitted(str(value-step))
+ _holding_button(-1.0, get_node(button) as BaseButton)
+
+
+func _on_value_text_submitted(new_text: String, no_signal:= false) -> void:
+ if new_text.is_empty() and not allow_string:
+ new_text = "0.0"
+ if new_text.is_valid_float():
+ var temp: float = min(max(new_text.to_float(), min), max)
+ if !enforce_step:
+ value = temp
+ else:
+ value = snapped(temp, step)
+ elif allow_string:
+ value = new_text
+ %Value.text = str(value).pad_decimals(len(str(float(step)-floorf(step)))-2)
+ if not no_signal:
+ value_changed.emit(property_name, value)
+ # Visually disable Up or Down arrow when limit is reached to better indicate a limit has been hit
+ %Spin/Decrement.disabled = value <= min
+ %Spin/Increment.disabled = value >= max
+
+
+# If prefix or suffix was clicked, select the actual value box instead and move the caret to the closest side.
+func _on_sublabel_clicked(event: InputEvent) -> void:
+ if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
+ var mousePos: Vector2 = get_global_mouse_position()
+ mousePos.x -= get_minimum_size().x / 2
+ if mousePos.x > global_position.x:
+ (%Value as LineEdit).caret_column = (%Value as LineEdit).text.length()
+ else:
+ (%Value as LineEdit).caret_column = 0
+ (%Value as LineEdit).grab_focus()
+
+
+func _on_value_focus_exited() -> void:
+ _on_value_text_submitted(%Value.text)
+ $Value_Panel.add_theme_stylebox_override('panel', get_theme_stylebox('panel', 'DialogicEventEdit'))
+
+
+func _on_value_focus_entered() -> void:
+ $Value_Panel.add_theme_stylebox_override('panel', get_theme_stylebox('focus', 'DialogicEventEdit'))
+ %Value.select_all.call_deferred()
+
+#endregion
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.tscn
new file mode 100644
index 0000000..5053a87
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_number.tscn
@@ -0,0 +1,160 @@
+[gd_scene load_steps=9 format=3 uid="uid://kdpp3mibml33"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_number.gd" id="1_0jdnn"]
+[ext_resource type="Texture2D" uid="uid://dh1ycbmw8anqh" path="res://addons/dialogic/Editor/Images/Interactable/increment_icon.svg" id="3_v5cne"]
+[ext_resource type="Texture2D" uid="uid://brjikovneb63n" path="res://addons/dialogic/Editor/Images/Interactable/decrement_icon.svg" id="4_ph52o"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_sj3oj"]
+content_margin_left = 3.0
+content_margin_right = 1.0
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_8yqsu"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_smq50"]
+content_margin_left = 2.0
+content_margin_right = 1.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_increment"]
+content_margin_left = 2.0
+content_margin_top = 6.0
+content_margin_right = 2.0
+content_margin_bottom = 2.0
+bg_color = Color(0.94, 0.94, 0.94, 0)
+border_color = Color(0, 0, 0, 0)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_decrement"]
+content_margin_left = 2.0
+content_margin_top = 2.0
+content_margin_right = 2.0
+content_margin_bottom = 6.0
+bg_color = Color(0.94, 0.94, 0.94, 0)
+border_color = Color(0, 0, 0, 0)
+
+[node name="Field_Number" type="HBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -1102.0
+offset_bottom = -617.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 0
+script = ExtResource("1_0jdnn")
+
+[node name="Value_Panel" type="PanelContainer" parent="."]
+layout_mode = 2
+theme_type_variation = &"DialogicEventEdit"
+
+[node name="Layout" type="HBoxContainer" parent="Value_Panel"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Prefix" type="RichTextLabel" parent="Value_Panel/Layout"]
+unique_name_in_owner = true
+visible = false
+clip_contents = false
+layout_direction = 2
+layout_mode = 2
+size_flags_horizontal = 0
+size_flags_vertical = 4
+mouse_filter = 1
+mouse_default_cursor_shape = 1
+theme_override_colors/default_color = Color(0.54099, 0.540991, 0.54099, 1)
+theme_override_styles/focus = SubResource("StyleBoxEmpty_sj3oj")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_sj3oj")
+bbcode_enabled = true
+fit_content = true
+scroll_active = false
+autowrap_mode = 0
+tab_size = 2
+shortcut_keys_enabled = false
+drag_and_drop_selection_enabled = false
+text_direction = 1
+
+[node name="Value" type="LineEdit" parent="Value_Panel/Layout"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+focus_mode = 1
+theme_override_constants/minimum_character_width = 0
+theme_override_styles/normal = SubResource("StyleBoxEmpty_8yqsu")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_8yqsu")
+theme_override_styles/read_only = SubResource("StyleBoxEmpty_8yqsu")
+text = "0"
+alignment = 1
+expand_to_text_length = true
+virtual_keyboard_type = 3
+
+[node name="Suffix" type="RichTextLabel" parent="Value_Panel/Layout"]
+unique_name_in_owner = true
+visible = false
+clip_contents = false
+layout_direction = 2
+layout_mode = 2
+size_flags_horizontal = 8
+size_flags_vertical = 4
+mouse_default_cursor_shape = 1
+theme_override_colors/default_color = Color(0.435192, 0.435192, 0.435192, 1)
+theme_override_styles/focus = SubResource("StyleBoxEmpty_smq50")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_smq50")
+bbcode_enabled = true
+fit_content = true
+scroll_active = false
+autowrap_mode = 0
+tab_size = 2
+shortcut_keys_enabled = false
+drag_and_drop_selection_enabled = false
+text_direction = 1
+
+[node name="Spin" type="VBoxContainer" parent="Value_Panel/Layout"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = 0
+alignment = 1
+
+[node name="Increment" type="Button" parent="Value_Panel/Layout/Spin"]
+layout_mode = 2
+size_flags_vertical = 3
+auto_translate = false
+focus_neighbor_left = NodePath("../../Value")
+focus_neighbor_top = NodePath(".")
+focus_neighbor_bottom = NodePath("../Decrement")
+theme_override_colors/icon_hover_color = Color(0.412738, 0.550094, 0.760917, 1)
+theme_override_colors/icon_focus_color = Color(0.412738, 0.550094, 0.760917, 1)
+theme_override_styles/normal = SubResource("StyleBoxFlat_increment")
+theme_override_styles/hover = SubResource("StyleBoxFlat_increment")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_increment")
+theme_override_styles/disabled = SubResource("StyleBoxFlat_increment")
+theme_override_styles/focus = SubResource("StyleBoxFlat_increment")
+icon = ExtResource("3_v5cne")
+flat = true
+vertical_icon_alignment = 2
+
+[node name="Decrement" type="Button" parent="Value_Panel/Layout/Spin"]
+layout_mode = 2
+size_flags_vertical = 3
+auto_translate = false
+focus_neighbor_left = NodePath("../../Value")
+focus_neighbor_top = NodePath("../Increment")
+focus_neighbor_bottom = NodePath(".")
+theme_override_colors/icon_hover_color = Color(0.412738, 0.550094, 0.760917, 1)
+theme_override_colors/icon_focus_color = Color(0.412738, 0.550094, 0.760917, 1)
+theme_override_styles/normal = SubResource("StyleBoxFlat_decrement")
+theme_override_styles/hover = SubResource("StyleBoxFlat_decrement")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_decrement")
+theme_override_styles/disabled = SubResource("StyleBoxFlat_decrement")
+theme_override_styles/focus = SubResource("StyleBoxFlat_decrement")
+icon = ExtResource("4_ph52o")
+flat = true
+vertical_icon_alignment = 2
+
+[connection signal="gui_input" from="Value_Panel/Layout/Prefix" to="." method="_on_sublabel_clicked"]
+[connection signal="focus_entered" from="Value_Panel/Layout/Value" to="." method="_on_value_focus_entered"]
+[connection signal="focus_exited" from="Value_Panel/Layout/Value" to="." method="_on_value_focus_exited"]
+[connection signal="gui_input" from="Value_Panel/Layout/Value" to="." method="_on_gui_input"]
+[connection signal="text_submitted" from="Value_Panel/Layout/Value" to="." method="_on_value_text_submitted"]
+[connection signal="gui_input" from="Value_Panel/Layout/Suffix" to="." method="_on_sublabel_clicked"]
+[connection signal="button_down" from="Value_Panel/Layout/Spin/Increment" to="." method="_on_increment_button_down" binds= [NodePath("%Spin/Increment")]]
+[connection signal="gui_input" from="Value_Panel/Layout/Spin/Increment" to="." method="_on_gui_input"]
+[connection signal="button_down" from="Value_Panel/Layout/Spin/Decrement" to="." method="_on_decrement_button_down" binds= [NodePath("%Spin/Decrement")]]
+[connection signal="gui_input" from="Value_Panel/Layout/Spin/Decrement" to="." method="_on_gui_input"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd
new file mode 100644
index 0000000..16a315f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd
@@ -0,0 +1,289 @@
+@tool
+extends DialogicVisualEditorField
+## Event block field for strings. Options are determined by a function.
+
+
+## SETTINGS
+@export var placeholder_text := "Select Resource"
+@export var empty_text := ""
+enum Modes {PURE_STRING, PRETTY_PATH, IDENTIFIER}
+@export var mode := Modes.PURE_STRING
+@export var fit_text_length := true
+var collapse_when_empty := false
+var valid_file_drop_extension := ""
+var get_suggestions_func: Callable
+
+var resource_icon: Texture = null:
+ get:
+ return resource_icon
+ set(new_icon):
+ resource_icon = new_icon
+ %Icon.texture = new_icon
+
+## STATE
+var current_value: String
+var current_selected := 0
+
+## SUGGESTIONS ITEM LIST
+var _v_separation := 0
+var _h_separation := 0
+var _icon_margin := 0
+var _line_height := 24
+var _max_height := 200 * DialogicUtil.get_editor_scale()
+
+
+#region FIELD METHODS
+################################################################################
+
+func _set_value(value:Variant) -> void:
+ if value == null or value.is_empty():
+ %Search.text = empty_text
+ else:
+ match mode:
+ Modes.PRETTY_PATH:
+ %Search.text = DialogicUtil.pretty_name(value)
+ Modes.IDENTIFIER when value.begins_with("res://"):
+ %Search.text = DialogicResourceUtil.get_unique_identifier(value)
+ _:
+ %Search.text = str(value)
+
+ %Search.visible = not collapse_when_empty or value
+ current_value = str(value)
+
+
+
+func _load_display_info(info:Dictionary) -> void:
+ valid_file_drop_extension = info.get('file_extension', '')
+ collapse_when_empty = info.get('collapse_when_empty', false)
+ get_suggestions_func = info.get('suggestions_func', get_suggestions_func)
+ empty_text = info.get('empty_text', '')
+ placeholder_text = info.get('placeholder', 'Select Resource')
+ mode = info.get("mode", 0)
+ resource_icon = info.get('icon', null)
+ %Search.tooltip_text = info.get('tooltip_text', '')
+ await ready
+ if resource_icon == null and info.has('editor_icon'):
+ resource_icon = callv('get_theme_icon', info.editor_icon)
+
+
+func _autofocus() -> void:
+ %Search.grab_focus()
+
+#endregion
+
+
+#region BASIC
+################################################################################
+
+func _ready() -> void:
+ var focus := get_theme_stylebox("focus", "LineEdit")
+ if has_theme_stylebox("focus", "DialogicEventEdit"):
+ focus = get_theme_stylebox('focus', 'DialogicEventEdit')
+ %Focus.add_theme_stylebox_override('panel', focus)
+
+ %Search.text_changed.connect(_on_Search_text_changed)
+ %Search.text_submitted.connect(_on_Search_text_entered)
+ %Search.placeholder_text = placeholder_text
+ %Search.expand_to_text_length = fit_text_length
+
+ %SelectButton.icon = get_theme_icon("Collapse", "EditorIcons")
+
+ %Suggestions.add_theme_stylebox_override('bg', load("res://addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres"))
+ %Suggestions.hide()
+ %Suggestions.item_selected.connect(suggestion_selected)
+ %Suggestions.item_clicked.connect(suggestion_selected)
+ %Suggestions.fixed_icon_size = Vector2i(16, 16) * DialogicUtil.get_editor_scale()
+
+ _v_separation = %Suggestions.get_theme_constant("v_separation")
+ _h_separation = %Suggestions.get_theme_constant("h_separation")
+ _icon_margin = %Suggestions.get_theme_constant("icon_margin")
+
+ if resource_icon == null:
+ self.resource_icon = null
+
+
+func change_to_empty() -> void:
+ value_changed.emit(property_name, "")
+
+#endregion
+
+
+#region SEARCH & SUGGESTION POPUP
+################################################################################
+func _on_Search_text_entered(new_text:String) -> void:
+ if %Suggestions.get_item_count():
+ if %Suggestions.is_anything_selected():
+ suggestion_selected(%Suggestions.get_selected_items()[0])
+ else:
+ suggestion_selected(0)
+ else:
+ change_to_empty()
+
+
+func _on_Search_text_changed(new_text:String, just_update:bool = false) -> void:
+ %Suggestions.clear()
+
+ if new_text == "" and !just_update:
+ change_to_empty()
+ else:
+ %Search.show()
+
+ var suggestions: Dictionary = get_suggestions_func.call(new_text)
+
+ var line_length := 0
+ var idx := 0
+ for element in suggestions:
+ if new_text.is_empty() or new_text.to_lower() in element.to_lower() or new_text.to_lower() in str(suggestions[element].value).to_lower() or new_text.to_lower() in suggestions[element].get('tooltip', '').to_lower():
+ var curr_line_length: int = 0
+ curr_line_length = get_theme_font('font', 'Label').get_string_size(
+ element, HORIZONTAL_ALIGNMENT_LEFT, -1, get_theme_font_size("font_size", 'Label')
+ ).x
+
+ %Suggestions.add_item(element)
+ if suggestions[element].has('icon'):
+ %Suggestions.set_item_icon(idx, suggestions[element].icon)
+ curr_line_length += %Suggestions.fixed_icon_size.x * %Suggestions.get_icon_scale() + _icon_margin * 2 + _h_separation
+ elif suggestions[element].has('editor_icon'):
+ %Suggestions.set_item_icon(idx, get_theme_icon(suggestions[element].editor_icon[0],suggestions[element].editor_icon[1]))
+ curr_line_length += %Suggestions.fixed_icon_size.x * %Suggestions.get_icon_scale() + _icon_margin * 2 + _h_separation
+
+ line_length = max(line_length, curr_line_length)
+
+ %Suggestions.set_item_tooltip(idx, suggestions[element].get('tooltip', ''))
+ %Suggestions.set_item_metadata(idx, suggestions[element].value)
+ idx += 1
+
+ if not %Suggestions.visible:
+ %Suggestions.show()
+ %Suggestions.global_position = $PanelContainer.global_position+Vector2(0,1)*$PanelContainer.size.y
+
+ if %Suggestions.item_count:
+ %Suggestions.select(0)
+ current_selected = 0
+ else:
+ current_selected = -1
+ %Search.grab_focus()
+
+ var total_height: int = 0
+ for item in %Suggestions.item_count:
+ total_height += _line_height * DialogicUtil.get_editor_scale() + _v_separation
+ total_height += _v_separation * 2
+ if total_height > _max_height:
+ line_length += %Suggestions.get_v_scroll_bar().get_minimum_size().x
+
+ %Suggestions.size.x = max(%PanelContainer.size.x, line_length)
+ %Suggestions.size.y = min(total_height, _max_height)
+
+ # Defer setting width to give PanelContainer
+ # time to update it's size
+ await get_tree().process_frame
+ await get_tree().process_frame
+
+ %Suggestions.size.x = max(%PanelContainer.size.x, line_length)
+
+
+func suggestion_selected(index: int, position := Vector2(), button_index := MOUSE_BUTTON_LEFT) -> void:
+ if button_index != MOUSE_BUTTON_LEFT:
+ return
+ if %Suggestions.is_item_disabled(index):
+ return
+
+ %Search.text = %Suggestions.get_item_text(index)
+
+ if %Suggestions.get_item_metadata(index) == null:
+ current_value = ""
+
+ else:
+ current_value = %Suggestions.get_item_metadata(index)
+
+ hide_suggestions()
+
+ grab_focus()
+ value_changed.emit(property_name, current_value)
+
+
+func _input(event:InputEvent) -> void:
+ if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
+ if %Suggestions.visible:
+ if !%Suggestions.get_global_rect().has_point(get_global_mouse_position()) and \
+ !%SelectButton.get_global_rect().has_point(get_global_mouse_position()):
+ hide_suggestions()
+
+
+func hide_suggestions() -> void:
+ %SelectButton.set_pressed_no_signal(false)
+ %Suggestions.hide()
+ if !current_value and collapse_when_empty:
+ %Search.hide()
+
+
+func _on_SelectButton_toggled(button_pressed:bool) -> void:
+ if button_pressed:
+ _on_Search_text_changed('', true)
+ else:
+ hide_suggestions()
+
+
+func _on_focus_entered() -> void:
+ %Search.grab_focus()
+
+
+func _on_search_gui_input(event: InputEvent) -> void:
+ if event is InputEventKey and (event.keycode == KEY_DOWN or event.keycode == KEY_UP) and event.pressed:
+ if !%Suggestions.visible:
+ _on_Search_text_changed('', true)
+ current_selected = -1
+ if event.keycode == KEY_DOWN:
+ current_selected = wrapi(current_selected+1, 0, %Suggestions.item_count)
+ if event.keycode == KEY_UP:
+ current_selected = wrapi(current_selected-1, 0, %Suggestions.item_count)
+ %Suggestions.select(current_selected)
+ %Suggestions.ensure_current_is_visible()
+
+ if Input.is_key_pressed(KEY_CTRL):
+ if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
+ if valid_file_drop_extension in [".dch", ".dtl"] and not current_value.is_empty():
+ EditorInterface.edit_resource(DialogicResourceUtil.get_resource_from_identifier(current_value, valid_file_drop_extension))
+
+ if valid_file_drop_extension in [".dch", ".dtl"] and not current_value.is_empty():
+ %Search.mouse_default_cursor_shape = CURSOR_POINTING_HAND
+ else:
+ %Search.mouse_default_cursor_shape = CURSOR_IBEAM
+
+
+func _on_search_focus_entered() -> void:
+ if %Search.text == "":
+ _on_Search_text_changed("")
+ %Search.call_deferred('select_all')
+ %Focus.show()
+
+
+func _on_search_focus_exited() -> void:
+ %Focus.hide()
+ if !%Suggestions.get_global_rect().has_point(get_global_mouse_position()):
+ hide_suggestions()
+
+#endregion
+
+
+#region DRAG AND DROP
+################################################################################
+
+func _can_drop_data(position:Vector2, data:Variant) -> bool:
+ if typeof(data) == TYPE_DICTIONARY and data.has('files') and len(data.files) == 1:
+ if valid_file_drop_extension:
+ if data.files[0].ends_with(valid_file_drop_extension):
+ return true
+ else:
+ return false
+ return false
+
+
+func _drop_data(position:Vector2, data:Variant) -> void:
+ var path := str(data.files[0])
+ if mode == Modes.IDENTIFIER:
+ path = DialogicResourceUtil.get_unique_identifier(path)
+ _set_value(path)
+ value_changed.emit(property_name, path)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn
new file mode 100644
index 0000000..1f4e220
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn
@@ -0,0 +1,135 @@
+[gd_scene load_steps=7 format=3 uid="uid://dpwhshre1n4t6"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd" id="1_b07gq"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_tmt5n"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_vennf"]
+
+[sub_resource type="Image" id="Image_5e7lo"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_g63da"]
+image = SubResource("Image_5e7lo")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g74jb"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(1, 0.365, 0.365, 1)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_detail = 1
+
+[node name="Field_DynamicStringOptions" type="HBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -2.0
+offset_top = -2.0
+offset_right = -1005.0
+offset_bottom = -622.0
+grow_horizontal = 2
+grow_vertical = 2
+focus_mode = 2
+script = ExtResource("1_b07gq")
+placeholder_text = ""
+
+[node name="PanelContainer" type="MarginContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_left = 0
+theme_override_constants/margin_top = 0
+theme_override_constants/margin_right = 0
+theme_override_constants/margin_bottom = 0
+
+[node name="BG" type="Panel" parent="PanelContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 2
+theme_type_variation = &"DialogicEventEdit"
+metadata/_edit_use_anchors_ = true
+
+[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 2
+theme_override_constants/margin_top = 2
+theme_override_constants/margin_right = 2
+theme_override_constants/margin_bottom = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="Icon" type="TextureRect" parent="PanelContainer/MarginContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 4
+mouse_filter = 2
+stretch_mode = 5
+
+[node name="Search" type="LineEdit" parent="PanelContainer/MarginContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+focus_neighbor_bottom = NodePath("Suggestions")
+focus_mode = 1
+mouse_filter = 1
+theme_override_styles/normal = SubResource("StyleBoxEmpty_tmt5n")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_vennf")
+expand_to_text_length = true
+flat = true
+caret_blink = true
+
+[node name="Suggestions" type="ItemList" parent="PanelContainer/MarginContainer/HBoxContainer/Search"]
+unique_name_in_owner = true
+visible = false
+top_level = true
+custom_minimum_size = Vector2(-1086, 0)
+layout_mode = 0
+offset_left = -5.0
+offset_top = 36.0
+offset_right = 195.0
+offset_bottom = 71.0
+size_flags_vertical = 0
+auto_translate = false
+focus_neighbor_top = NodePath("..")
+max_text_lines = 3
+item_count = 1
+fixed_icon_size = Vector2i(16, 16)
+item_0/text = "Hello"
+
+[node name="SelectButton" type="Button" parent="PanelContainer/MarginContainer/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+focus_mode = 0
+toggle_mode = true
+shortcut_in_tooltip = false
+icon = SubResource("ImageTexture_g63da")
+flat = true
+
+[node name="Focus" type="Panel" parent="PanelContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_g74jb")
+metadata/_edit_use_anchors_ = true
+
+[connection signal="focus_entered" from="." to="." method="_on_focus_entered"]
+[connection signal="focus_entered" from="PanelContainer/MarginContainer/HBoxContainer/Search" to="." method="_on_search_focus_entered"]
+[connection signal="focus_exited" from="PanelContainer/MarginContainer/HBoxContainer/Search" to="." method="_on_search_focus_exited"]
+[connection signal="gui_input" from="PanelContainer/MarginContainer/HBoxContainer/Search" to="." method="_on_search_gui_input"]
+[connection signal="gui_input" from="PanelContainer/MarginContainer/HBoxContainer/Search/Suggestions" to="." method="_on_suggestions_gui_input"]
+[connection signal="toggled" from="PanelContainer/MarginContainer/HBoxContainer/SelectButton" to="." method="_on_SelectButton_toggled"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd
new file mode 100644
index 0000000..3f7efb2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd
@@ -0,0 +1,62 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for constant options. For varying options use ComplexPicker.
+
+var options: Array = []
+
+## if true, only the symbol will be displayed. In the dropdown text will be visible.
+## Useful for making UI simpler
+var symbol_only := false:
+ set(value):
+ symbol_only = value
+ if value: self.text = ""
+
+var current_value: Variant = -1
+
+
+func _ready() -> void:
+ add_theme_color_override("font_disabled_color", get_theme_color("font_color", "MenuButton"))
+ self.about_to_popup.connect(insert_options)
+ call("get_popup").index_pressed.connect(index_pressed)
+
+
+func _load_display_info(info:Dictionary) -> void:
+ options = info.get('options', [])
+ self.disabled = info.get('disabled', false)
+ symbol_only = info.get('symbol_only', false)
+
+
+func _set_value(value:Variant) -> void:
+ for option in options:
+ if option['value'] == value:
+ if typeof(option.get('icon')) == TYPE_ARRAY:
+ option.icon = callv('get_theme_icon', option.get('icon'))
+ if !symbol_only:
+ self.text = option['label']
+ self.icon = option.get('icon', null)
+ current_value = value
+
+
+func get_value() -> Variant:
+ return current_value
+
+
+func insert_options() -> void:
+ call("get_popup").clear()
+
+ var idx := 0
+ for option in options:
+ if typeof(option.get('icon')) == TYPE_ARRAY:
+ option.icon = callv('get_theme_icon', option.get('icon'))
+ call("get_popup").add_icon_item(option.get('icon', null), option['label'])
+ call("get_popup").set_item_metadata(idx, option['value'])
+ idx += 1
+
+
+func index_pressed(idx:int) -> void:
+ current_value = idx
+ if !symbol_only:
+ self.text = call("get_popup").get_item_text(idx)
+ self.icon =call("get_popup").get_item_icon(idx)
+ value_changed.emit(property_name, call("get_popup").get_item_metadata(idx))
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn
new file mode 100644
index 0000000..550160c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=2 format=3 uid="uid://d3bhehatwoio"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_options_fixed.gd" id="1"]
+
+[node name="Field_FixedOptions" type="MenuButton"]
+offset_right = 137.0
+offset_bottom = 43.0
+focus_mode = 2
+theme_type_variation = &"DialogicEventEdit"
+theme_override_colors/font_disabled_color = Color(0.875, 0.875, 0.875, 1)
+text = "Placeholder Text"
+flat = false
+script = ExtResource("1")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd
new file mode 100644
index 0000000..aaeb4c4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd
@@ -0,0 +1,74 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field that allows entering multiline text (mainly text event).
+
+@onready var code_completion_helper: Node = find_parent('EditorsManager').get_node('CodeCompletionHelper')
+
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ self.text_changed.connect(_on_text_changed)
+ self.syntax_highlighter = code_completion_helper.text_syntax_highlighter
+
+
+func _load_display_info(info:Dictionary) -> void:
+ pass
+
+
+func _set_value(value:Variant) -> void:
+ self.text = str(value)
+
+
+func _autofocus() -> void:
+ grab_focus()
+
+#endregion
+
+
+#region SIGNAL METHODS
+################################################################################
+
+func _on_text_changed(value := "") -> void:
+ value_changed.emit(property_name, self.text)
+
+#endregion
+
+
+#region AUTO COMPLETION
+################################################################################
+
+## Called if something was typed
+func _request_code_completion(force:bool):
+ code_completion_helper.request_code_completion(force, self, 0)
+
+
+## Filters the list of all possible options, depending on what was typed
+## Purpose of the different Kinds is explained in [_request_code_completion]
+func _filter_code_completion_candidates(candidates:Array) -> Array:
+ return code_completion_helper.filter_code_completion_candidates(candidates, self)
+
+
+## Called when code completion was activated
+## Inserts the selected item
+func _confirm_code_completion(replace:bool) -> void:
+ code_completion_helper.confirm_code_completion(replace, self)
+
+#endregion
+
+
+#region SYMBOL CLICKING
+################################################################################
+
+## Performs an action (like opening a link) when a valid symbol was clicked
+func _on_symbol_lookup(symbol, line, column):
+ code_completion_helper.symbol_lookup(symbol, line, column)
+
+
+## Called to test if a symbol can be clicked
+func _on_symbol_validate(symbol:String) -> void:
+ code_completion_helper.symbol_validate(symbol, self)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn
new file mode 100644
index 0000000..6ab8fb6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=4 format=3 uid="uid://dyp7m2nvab1aj"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd" id="2_ww6ga"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_text_multiline.gd" id="3_q7600"]
+
+[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_2q5dk"]
+script = ExtResource("2_ww6ga")
+
+[node name="Field_Text_Multiline" type="CodeEdit"]
+offset_right = 413.0
+offset_bottom = 15.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_type_variation = &"DialogicTextEventTextEdit"
+wrap_mode = 1
+scroll_fit_content_height = true
+syntax_highlighter = SubResource("SyntaxHighlighter_2q5dk")
+symbol_lookup_on_click = true
+delimiter_strings = Array[String]([])
+code_completion_enabled = true
+code_completion_prefixes = Array[String](["[", "{"])
+indent_automatic_prefixes = Array[String]([":", "{", "[", ")"])
+auto_brace_completion_enabled = true
+auto_brace_completion_pairs = {
+"[": "]",
+"{": "}"
+}
+script = ExtResource("3_q7600")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd
new file mode 100644
index 0000000..765f862
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd
@@ -0,0 +1,40 @@
+@tool
+extends DialogicVisualEditorField
+
+## Event block field for a single line of text.
+
+
+var placeholder := "":
+ set(value):
+ placeholder = value
+ self.placeholder_text = placeholder
+
+
+#region MAIN METHODS
+################################################################################
+
+func _ready() -> void:
+ self.text_changed.connect(_on_text_changed)
+
+
+func _load_display_info(info:Dictionary) -> void:
+ self.placeholder = info.get('placeholder', '')
+
+
+func _set_value(value:Variant) -> void:
+ self.text = str(value)
+
+
+func _autofocus() -> void:
+ grab_focus()
+
+#endregion
+
+
+#region SIGNAL METHODS
+################################################################################
+
+func _on_text_changed(value := "") -> void:
+ value_changed.emit(property_name, self.text)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn
new file mode 100644
index 0000000..d90d3b0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn
@@ -0,0 +1,10 @@
+[gd_scene load_steps=2 format=3 uid="uid://c0vkcehgjsjy"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_text_singleline.gd" id="1_4vnxv"]
+
+[node name="Field_Text_Singleline" type="LineEdit"]
+offset_right = 1152.0
+offset_bottom = 81.0
+theme_type_variation = &"DialogicEventEdit"
+expand_to_text_length = true
+script = ExtResource("1_4vnxv")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.gd
new file mode 100644
index 0000000..040abf0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.gd
@@ -0,0 +1,26 @@
+@tool
+extends DialogicVisualEditorFieldVector
+## Event block field for a Vector2.
+
+var current_value := Vector2()
+
+
+func _set_value(value: Variant) -> void:
+ current_value = value
+ super(value)
+
+
+func get_value() -> Vector2:
+ return current_value
+
+
+func _on_sub_value_changed(sub_component: String, value: float) -> void:
+ match sub_component:
+ 'X': current_value.x = value
+ 'Y': current_value.y = value
+ _on_value_changed(current_value)
+
+
+func _update_sub_component_text(value: Variant) -> void:
+ $X._on_value_text_submitted(str(value.x), true)
+ $Y._on_value_text_submitted(str(value.y), true)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.tscn
new file mode 100644
index 0000000..bfbc8d6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector2.tscn
@@ -0,0 +1,29 @@
+[gd_scene load_steps=3 format=3 uid="uid://dtimnsj014cu"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.gd" id="1_v6lp0"]
+[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="2_a0b6y"]
+
+[node name="Field_Vector2" type="HBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -1033.0
+offset_bottom = -617.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 2
+script = ExtResource("1_v6lp0")
+
+[node name="X" parent="." instance=ExtResource("2_a0b6y")]
+layout_mode = 2
+step = 0.001
+min = -9999.0
+max = 9999.0
+prefix = ""
+
+[node name="Y" parent="." instance=ExtResource("2_a0b6y")]
+layout_mode = 2
+step = 0.001
+min = -9999.0
+max = 9999.0
+prefix = ""
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.gd
new file mode 100644
index 0000000..c5b9f6d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.gd
@@ -0,0 +1,28 @@
+@tool
+extends DialogicVisualEditorFieldVector
+## Event block field for a Vector3.
+
+var current_value := Vector3()
+
+
+func _set_value(value: Variant) -> void:
+ current_value = value
+ super(value)
+
+
+func get_value() -> Vector3:
+ return current_value
+
+
+func _on_sub_value_changed(sub_component: String, value: float) -> void:
+ match sub_component:
+ 'X': current_value.x = value
+ 'Y': current_value.y = value
+ 'Z': current_value.z = value
+ _on_value_changed(current_value)
+
+
+func _update_sub_component_text(value: Variant) -> void:
+ $X._on_value_text_submitted(str(value.x), true)
+ $Y._on_value_text_submitted(str(value.y), true)
+ $Z._on_value_text_submitted(str(value.z), true)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.tscn
new file mode 100644
index 0000000..019a26b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector3.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://cklkpfrcvopgw"]
+
+[ext_resource type="PackedScene" uid="uid://dtimnsj014cu" path="res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn" id="1_l3y0o"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_vector3.gd" id="2_gktf1"]
+[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="3_k0u0p"]
+
+[node name="Field_Vector3" instance=ExtResource("1_l3y0o")]
+offset_right = -973.0
+script = ExtResource("2_gktf1")
+
+[node name="Z" parent="." index="2" instance=ExtResource("3_k0u0p")]
+layout_mode = 2
+step = 0.001
+min = -9999.0
+max = 9999.0
+affix = "z:"
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.gd
new file mode 100644
index 0000000..3259ce3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.gd
@@ -0,0 +1,30 @@
+@tool
+extends DialogicVisualEditorFieldVector
+## Event block field for a Vector4.
+
+var current_value := Vector4()
+
+
+func _set_value(value: Variant) -> void:
+ current_value = value
+ super(value)
+
+
+func get_value() -> Vector4:
+ return current_value
+
+
+func _on_sub_value_changed(sub_component: String, value: float) -> void:
+ match sub_component:
+ 'X': current_value.x = value
+ 'Y': current_value.y = value
+ 'Z': current_value.z = value
+ 'W': current_value.w = value
+ _on_value_changed(current_value)
+
+
+func _update_sub_component_text(value: Variant) -> void:
+ $X._on_value_text_submitted(str(value.x), true)
+ $Y._on_value_text_submitted(str(value.y), true)
+ $Z._on_value_text_submitted(str(value.z), true)
+ $W._on_value_text_submitted(str(value.w), true)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.tscn b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.tscn
new file mode 100644
index 0000000..182e0f2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector4.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=3 uid="uid://dykss037r2rsc"]
+
+[ext_resource type="PackedScene" uid="uid://cklkpfrcvopgw" path="res://addons/dialogic/Editor/Events/Fields/field_vector3.tscn" id="1_20tvl"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/Fields/field_vector4.gd" id="2_yksrc"]
+[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="3_1jogk"]
+
+[node name="Field_Vector4" instance=ExtResource("1_20tvl")]
+offset_right = -908.0
+script = ExtResource("2_yksrc")
+
+[node name="W" parent="." index="3" instance=ExtResource("3_1jogk")]
+layout_mode = 2
+step = 0.001
+min = -9999.0
+max = 9999.0
+affix = "w:"
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector_base.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector_base.gd
new file mode 100644
index 0000000..5d5d9e9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/Fields/field_vector_base.gd
@@ -0,0 +1,38 @@
+class_name DialogicVisualEditorFieldVector
+extends DialogicVisualEditorField
+## Base type for Vector event blocks
+
+
+func _ready() -> void:
+ for child in get_children():
+ child.tooltip_text = tooltip_text
+ child.property_name = child.name #to identify the name of the changed sub-component
+ child.value_changed.connect(_on_sub_value_changed)
+
+
+func _load_display_info(info: Dictionary) -> void:
+ for child in get_children():
+ if child is DialogicVisualEditorFieldNumber:
+ if info.get('no_prefix', false):
+ child._load_display_info(info)
+ else:
+ var prefixed_info := info.duplicate()
+ prefixed_info.merge({'prefix':child.name.to_lower()})
+ child._load_display_info(prefixed_info)
+
+
+func _set_value(value: Variant) -> void:
+ _update_sub_component_text(value)
+ _on_value_changed(value)
+
+
+func _on_value_changed(value: Variant) -> void:
+ value_changed.emit(property_name, value)
+
+
+func _on_sub_value_changed(sub_component: String, value: float) -> void:
+ pass
+
+
+func _update_sub_component_text(value: Variant) -> void:
+ pass
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/event_field.gd b/addons/dialogic-main/addons/dialogic/Editor/Events/event_field.gd
new file mode 100644
index 0000000..f5db871
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/event_field.gd
@@ -0,0 +1,35 @@
+@tool
+class_name DialogicVisualEditorField
+extends Control
+
+signal value_changed(property_name:String, value:Variant)
+var property_name := ""
+
+var event_resource: DialogicEvent = null
+
+#region OVERWRITES
+################################################################################
+
+## To be overwritten
+func _load_display_info(info:Dictionary) -> void:
+ pass
+
+
+## To be overwritten
+func _set_value(value:Variant) -> void:
+ pass
+
+
+## To be overwritten
+func _autofocus() -> void:
+ pass
+
+#endregion
+
+
+func set_value(value:Variant) -> void:
+ _set_value(value)
+
+
+func take_autofocus() -> void:
+ _autofocus()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/InputFieldsStyle.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/InputFieldsStyle.tres
new file mode 100644
index 0000000..fa952ee
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/InputFieldsStyle.tres
@@ -0,0 +1,50 @@
+[gd_resource type="Theme" load_steps=3 format=3 uid="uid://d3g4i4dshtdpu"]
+
+[sub_resource type="StyleBoxFlat" id="1"]
+content_margin_left = 30.0
+content_margin_top = 5.0
+content_margin_right = 20.0
+content_margin_bottom = 5.0
+bg_color = Color(0.12549, 0.141176, 0.192157, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.0980392, 0.113725, 0.152941, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+
+[sub_resource type="StyleBoxFlat" id="2"]
+content_margin_left = 11.0
+content_margin_top = 5.0
+content_margin_right = 20.0
+content_margin_bottom = 5.0
+bg_color = Color(0.12549, 0.141176, 0.192157, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.0980392, 0.113725, 0.152941, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+
+[resource]
+LineEdit/colors/clear_button_color = Color(0, 0, 0, 1)
+LineEdit/colors/clear_button_color_pressed = Color(0, 0, 0, 1)
+LineEdit/colors/cursor_color = Color(1, 1, 1, 1)
+LineEdit/colors/font_color = Color(1, 1, 1, 1)
+LineEdit/colors/font_color_selected = Color(1, 1, 1, 1)
+LineEdit/colors/font_color_uneditable = Color(1, 1, 1, 1)
+LineEdit/colors/selection_color = Color(1, 1, 1, 0.235294)
+LineEdit/constants/minimum_spaces = 10
+LineEdit/fonts/font = null
+LineEdit/icons/clear = null
+LineEdit/styles/focus = SubResource("1")
+LineEdit/styles/normal = SubResource("2")
+LineEdit/styles/read_only = SubResource("1")
+LineEditWithIcon/base_type = &"LineEdit"
+LineEditWithIcon/styles/normal = SubResource("1")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuHover.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuHover.tres
new file mode 100644
index 0000000..ecf8376
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuHover.tres
@@ -0,0 +1,11 @@
+[gd_resource type="StyleBoxFlat" format=2]
+
+[resource]
+content_margin_left = 25.0
+content_margin_right = 10.0
+content_margin_top = 4.0
+content_margin_bottom = 4.0
+bg_color = Color( 0.466667, 0.466667, 0.466667, 0.141176 )
+border_width_bottom = 2
+corner_radius_top_left = 4
+corner_radius_top_right = 4
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuNormal.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuNormal.tres
new file mode 100644
index 0000000..d14860a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuNormal.tres
@@ -0,0 +1,13 @@
+[gd_resource type="StyleBoxFlat" format=2]
+
+[resource]
+content_margin_left = 25.0
+content_margin_right = 10.0
+content_margin_top = 4.0
+content_margin_bottom = 4.0
+bg_color = Color( 0.180392, 0.180392, 0.180392, 0.219608 )
+draw_center = false
+border_width_bottom = 2
+border_color = Color( 0.8, 0.8, 0.8, 0.286275 )
+corner_radius_top_left = 4
+corner_radius_top_right = 4
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres
new file mode 100644
index 0000000..314544b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://c8k6tbipodsg"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.109804)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SectionPanel.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SectionPanel.tres
new file mode 100644
index 0000000..b886c6e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SectionPanel.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=2]
+
+[resource]
+content_margin_left = 6.0
+content_margin_right = 6.0
+content_margin_top = 5.0
+content_margin_bottom = 4.0
+bg_color = Color( 0.6, 0.6, 0.6, 0 )
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color( 0.2, 0.227451, 0.309804, 1 )
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonHover.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonHover.tres
new file mode 100644
index 0000000..5be19d4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonHover.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=2]
+
+[resource]
+content_margin_left = 3.0
+content_margin_right = 3.0
+content_margin_top = 3.0
+content_margin_bottom = 3.0
+bg_color = Color( 0.2, 0.231373, 0.309804, 0.317647 )
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color( 0.8, 0.8, 0.8, 0.109804 )
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonNormal.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonNormal.tres
new file mode 100644
index 0000000..e5c06b4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/SimpleButtonNormal.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=2]
+
+[resource]
+content_margin_left = 3.0
+content_margin_right = 3.0
+content_margin_top = 3.0
+content_margin_bottom = 3.0
+bg_color = Color( 0.2, 0.231373, 0.309804, 0.235294 )
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color( 0.8, 0.8, 0.8, 0.109804 )
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/TextBackground.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/TextBackground.tres
new file mode 100644
index 0000000..0d74e3d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/TextBackground.tres
@@ -0,0 +1,12 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://cu8otiwksn8ma"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 13.0
+content_margin_bottom = 2.0
+bg_color = Color(1, 1, 1, 0.0784314)
+border_color = Color(0.454902, 0.454902, 0.454902, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres
new file mode 100644
index 0000000..c9312fb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres
@@ -0,0 +1,16 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://obyrr26pqk2p"]
+
+[resource]
+content_margin_left = 3.0
+content_margin_top = 1.0
+content_margin_right = 4.0
+content_margin_bottom = 1.0
+bg_color = Color(0.776471, 0.776471, 0.776471, 0.207843)
+border_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 1.0
+expand_margin_top = 1.0
+expand_margin_bottom = 2.0
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Events/styles/unselected_stylebox.tres b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/unselected_stylebox.tres
new file mode 100644
index 0000000..2fae3b3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Events/styles/unselected_stylebox.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StyleBoxEmpty" format=3 uid="uid://cl75ikyq2is7c"]
+
+[resource]
+content_margin_left = 3.0
+content_margin_top = 1.0
+content_margin_right = 4.0
+content_margin_bottom = 1.0
diff --git a/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.gd b/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.gd
new file mode 100644
index 0000000..15aff25
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.gd
@@ -0,0 +1,86 @@
+@tool
+extends DialogicEditor
+
+## A Main page in the dialogic editor.
+
+var tips: Array = []
+
+
+
+func _get_icon() -> Texture:
+ return load("res://addons/dialogic/Editor/Images/plugin-icon.svg")
+
+
+func _ready() -> void:
+ self_modulate = get_theme_color("font_color", "Editor")
+ self_modulate.a = 0.2
+
+ var edit_scale := DialogicUtil.get_editor_scale()
+ %HomePageBox.custom_minimum_size = Vector2(600, 350)*edit_scale
+ %TopPanel.custom_minimum_size.y = 100*edit_scale
+ %VersionLabel.set('theme_override_font_sizes/font_size', 10 * edit_scale)
+ var plugin_cfg := ConfigFile.new()
+ plugin_cfg.load("res://addons/dialogic/plugin.cfg")
+ %VersionLabel.text = plugin_cfg.get_value('plugin', 'version', 'unknown version')
+
+ %BottomPanel.self_modulate = get_theme_color("dark_color_3", "Editor")
+
+ %RandomTipLabel.add_theme_color_override("font_color", get_theme_color("property_color_z", "Editor"))
+ %RandomTipMoreButton.icon = get_theme_icon("ExternalLink", "EditorIcons")
+
+
+
+func _register() -> void:
+ editors_manager.register_simple_editor(self)
+
+ self.alternative_text = "Welcome to dialogic!"
+
+
+
+func _open(extra_info:Variant="") -> void:
+ if tips.is_empty():
+ var file := FileAccess.open('res://addons/dialogic/Editor/HomePage/tips.txt', FileAccess.READ)
+ tips = file.get_as_text().split('\n')
+ tips = tips.filter(func(item): return !item.is_empty())
+
+ randomize()
+ var tip: String = tips[randi()%len(tips)]
+ var text := tip.get_slice(';',0).strip_edges()
+ var action := tip.get_slice(';',1).strip_edges()
+ if action == text:
+ action = ""
+ show_tip(text, action)
+
+
+func show_tip(text:String='', action:String='') -> void:
+ if text.is_empty():
+ %TipBox.hide()
+ %RandomTipLabel.hide()
+ return
+
+ %TipBox.show()
+ %RandomTipLabel.show()
+ %RandomTip.text = '[i]'+text
+
+ if action.is_empty():
+ %RandomTipMoreButton.hide()
+ return
+
+ %RandomTipMoreButton.show()
+
+ if %RandomTipMoreButton.pressed.is_connected(_on_tip_action):
+ %RandomTipMoreButton.pressed.disconnect(_on_tip_action)
+ %RandomTipMoreButton.pressed.connect(_on_tip_action.bind(action))
+
+
+func _on_tip_action(action:String) -> void:
+ if action.begins_with('https://'):
+ OS.shell_open(action)
+ return
+ elif action.begins_with('editor://'):
+ var editor_name := action.trim_prefix('editor://').get_slice('->',0)
+ var extra_info := action.trim_prefix('editor://').get_slice('->',1)
+ if editor_name in editors_manager.editors:
+ editors_manager.open_editor(editors_manager.editors[editor_name].node, false, extra_info)
+ return
+ print("Tip button doesn't do anything (", action, ")")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.tscn b/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.tscn
new file mode 100644
index 0000000..0272d34
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/HomePage/home_page.tscn
@@ -0,0 +1,373 @@
+[gd_scene load_steps=23 format=3 uid="uid://cqy73hshqqgga"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/HomePage/home_page.gd" id="1_6g38w"]
+[ext_resource type="Texture2D" uid="uid://cvmlp5nxb2rer" path="res://addons/dialogic/Editor/HomePage/icon_bg.png" id="1_ed1g1"]
+[ext_resource type="Texture2D" uid="uid://bt87p6qlso0ya" path="res://addons/dialogic/Editor/Images/dialogic-logo.svg" id="3_3leok"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_imi2d"]
+draw_center = false
+corner_radius_top_left = 15
+corner_radius_top_right = 15
+shadow_color = Color(0.796078, 0.572549, 0.933333, 0.0627451)
+shadow_size = 24
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n2afh"]
+corner_radius_top_left = 15
+corner_radius_top_right = 15
+
+[sub_resource type="Gradient" id="Gradient_lt7uf"]
+colors = PackedColorArray(0.296484, 0.648457, 1, 1, 0.732014, 0.389374, 1, 1)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_2klx3"]
+gradient = SubResource("Gradient_lt7uf")
+fill_from = Vector2(0.151515, 0.272727)
+fill_to = Vector2(1, 1)
+
+[sub_resource type="Gradient" id="Gradient_1gns2"]
+offsets = PackedFloat32Array(0.302013, 0.872483)
+colors = PackedColorArray(0.365323, 0.360806, 0.260695, 0, 0.615686, 0.615686, 0.615686, 0.592157)
+
+[sub_resource type="GradientTexture2D" id="GradientTexture2D_u0aw3"]
+gradient = SubResource("Gradient_1gns2")
+fill = 1
+fill_from = Vector2(0.497835, 0.493506)
+fill_to = Vector2(1, 1)
+
+[sub_resource type="FontVariation" id="FontVariation_vepxx"]
+variation_embolden = 2.0
+
+[sub_resource type="LabelSettings" id="LabelSettings_w8q1h"]
+font = SubResource("FontVariation_vepxx")
+font_size = 40
+outline_size = 14
+outline_color = Color(0.0901961, 0.0901961, 0.0901961, 0.258824)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_p7ka2"]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(1, 1, 1, 1)
+corner_radius_bottom_right = 15
+corner_radius_bottom_left = 15
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_es88k"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ce6uo"]
+content_margin_left = 7.0
+content_margin_top = 7.0
+content_margin_right = 7.0
+content_margin_bottom = 14.0
+bg_color = Color(0.803922, 0.352941, 1, 0.141176)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="FontVariation" id="FontVariation_elu6e"]
+variation_embolden = 1.1
+
+[sub_resource type="FontVariation" id="FontVariation_5kbdj"]
+variation_transform = Transform2D(1, 0.239, 0, 1, 0, 0)
+
+[sub_resource type="FontVariation" id="FontVariation_g0m61"]
+variation_embolden = 1.43
+variation_transform = Transform2D(1, 0.343, 0, 1, 0, 0)
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_a8dvw"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ckyhx"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.470588, 0.196078, 0.6, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l1doy"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.470588, 0.196078, 0.6, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="Image" id="Image_e1dkh"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_sr7s6"]
+image = SubResource("Image_e1dkh")
+
+[node name="HomePage" type="TextureRect"]
+self_modulate = Color(0, 0, 0, 0.2)
+clip_contents = true
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -2.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = ExtResource("1_ed1g1")
+expand_mode = 1
+stretch_mode = 3
+script = ExtResource("1_6g38w")
+
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HomePageBox" type="VBoxContainer" parent="CenterContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(600, 350)
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="TopPanel" type="Panel" parent="CenterContainer/HomePageBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 100)
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_imi2d")
+
+[node name="Header2" type="Panel" parent="CenterContainer/HomePageBox/TopPanel"]
+clip_children = 1
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.4
+theme_override_styles/panel = SubResource("StyleBoxFlat_n2afh")
+
+[node name="BG" type="TextureRect" parent="CenterContainer/HomePageBox/TopPanel/Header2"]
+modulate = Color(0.65098, 0.65098, 0.65098, 1)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.3
+texture = SubResource("GradientTexture2D_2klx3")
+expand_mode = 1
+
+[node name="Vignette" type="TextureRect" parent="CenterContainer/HomePageBox/TopPanel/Header2"]
+modulate = Color(0, 0, 0, 1)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -166.0
+offset_bottom = 166.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = SubResource("GradientTexture2D_u0aw3")
+expand_mode = 1
+
+[node name="Logo" type="TextureRect" parent="CenterContainer/HomePageBox/TopPanel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 19.0
+offset_top = 10.0
+offset_right = -23.0
+offset_bottom = -10.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.3
+texture = ExtResource("3_3leok")
+expand_mode = 1
+stretch_mode = 5
+
+[node name="Label" type="Label" parent="CenterContainer/HomePageBox/TopPanel/Logo"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = 155.0
+offset_top = -37.0
+offset_right = 185.0
+offset_bottom = 21.0
+grow_horizontal = 2
+grow_vertical = 2
+rotation = -0.201447
+text = "2"
+label_settings = SubResource("LabelSettings_w8q1h")
+
+[node name="BottomPanel" type="PanelContainer" parent="CenterContainer/HomePageBox"]
+unique_name_in_owner = true
+self_modulate = Color(0, 0, 0, 1)
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_p7ka2")
+
+[node name="VersionLabel" type="Label" parent="CenterContainer/HomePageBox/BottomPanel"]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.501961)
+layout_mode = 2
+size_flags_vertical = 8
+theme_override_font_sizes/font_size = 10
+text = "2.0-Alpha-15 WIP (Godot 4.2+)"
+horizontal_alignment = 2
+
+[node name="ScrollContainer" type="ScrollContainer" parent="CenterContainer/HomePageBox/BottomPanel"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 50
+
+[node name="CenterContainer" type="VBoxContainer" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_stretch_ratio = 0.4
+
+[node name="Label" type="Label" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+theme_override_constants/line_spacing = 0
+text = "Documentation"
+
+[node name="WikiButton" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Wiki"
+underline = 2
+uri = "https://docs.dialogic.pro/"
+
+[node name="WikiGettingStartedButton" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Getting Started"
+underline = 2
+uri = "https://docs.dialogic.pro/getting-started.html"
+
+[node name="Separator" type="Control" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+
+[node name="Label2" type="Label" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Get in touch"
+
+[node name="BugRequestButton" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Bug / Request"
+underline = 2
+uri = "https://github.com/dialogic-godot/dialogic/issues/new/choose"
+
+[node name="DiscordButton" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Discord"
+underline = 2
+uri = "https://discord.gg/2hHQzkf2pX"
+
+[node name="Website" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Website"
+underline = 2
+uri = "https://dialogic.pro/"
+
+[node name="DonateButton" type="LinkButton" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicLink"
+text = " Donate"
+underline = 2
+uri = "https://www.patreon.com/JowanSpooner"
+
+[node name="CenterContainer2" type="VBoxContainer" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/separation = 15
+
+[node name="Control" type="Control" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2"]
+layout_mode = 2
+
+[node name="WelcomeText" type="RichTextLabel" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2"]
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_es88k")
+bbcode_enabled = true
+text = "[center]Welcome to dialogic, a plugin that lets you easily create stories and dialogs for your game!"
+fit_content = true
+
+[node name="RandomTipSection" type="VBoxContainer" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = -4
+alignment = 1
+
+[node name="RandomTipLabel" type="Label" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2/RandomTipSection"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+text = "Random Tip"
+
+[node name="TipBox" type="PanelContainer" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2/RandomTipSection"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_ce6uo")
+
+[node name="RandomTip" type="RichTextLabel" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2/RandomTipSection/TipBox"]
+unique_name_in_owner = true
+clip_contents = false
+layout_mode = 2
+theme_override_fonts/bold_font = SubResource("FontVariation_elu6e")
+theme_override_fonts/italics_font = SubResource("FontVariation_5kbdj")
+theme_override_fonts/bold_italics_font = SubResource("FontVariation_g0m61")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_a8dvw")
+bbcode_enabled = true
+text = "[i]You can[/i] [b]create custom[/b] events, [i][b]subsystems, text effects and even editors for[/b][i] [code]dialogic!"
+fit_content = true
+
+[node name="RandomTipMoreButton" type="Button" parent="CenterContainer/HomePageBox/BottomPanel/ScrollContainer/HBoxContainer/CenterContainer2/RandomTipSection/TipBox/RandomTip"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -30.0
+offset_top = 1.0
+offset_right = -8.0
+offset_bottom = 23.0
+grow_horizontal = 0
+grow_vertical = 0
+tooltip_text = "Check it out!"
+theme_override_styles/normal = SubResource("StyleBoxFlat_ckyhx")
+theme_override_styles/hover = SubResource("StyleBoxFlat_l1doy")
+icon = SubResource("ImageTexture_sr7s6")
+expand_icon = true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png b/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png
new file mode 100644
index 0000000..77f127d
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png.import b/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png.import
new file mode 100644
index 0000000..f889fd4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvmlp5nxb2rer"
+path="res://.godot/imported/icon_bg.png-525c114c802923e6aaaf9e666048cfd0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/HomePage/icon_bg.png"
+dest_files=["res://.godot/imported/icon_bg.png-525c114c802923e6aaaf9e666048cfd0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/HomePage/tips.txt b/addons/dialogic-main/addons/dialogic/Editor/HomePage/tips.txt
new file mode 100644
index 0000000..4ca5ec7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/HomePage/tips.txt
@@ -0,0 +1,11 @@
+Dialogic variables can be changed from timelines [b]and[/b] scripts! They can be used in conditions and inside of texts!; editor://VariablesEditor
+You can create [b]custom modules[/b] for dialogic, including events, subsystems, text effects, ui layouts and even editors!; editor://Settings->General
+If there are events you never need, you can hide them from the list in the editor!; editor://Settings->Modules
+Did you know that dialogic supports translations? It does!; editor://Settings->Translations
+You can use [b]bbcode effects[/b] in text events! What are they though???; https://docs.godotengine.org/en/latest/tutorials/ui/bbcode_in_richtextlabel.html
+Writing [/i][i] in a text event will pick a random one of the three strings!
+There are a number of cool text effects like [pause=x], [speed=x] and [portrait=x]. Try them out!;
+You can use scenes as portraits! This gives you basically limitless freedom.; https://dialogic-docs.coppolaemilio.com/custom-portraits.html
+You can use scenes as backgrounds. This way they can be animated or whatever you want!
+Dialogic has a built in save and load system! It's pretty powerful!; editor://Settings->Saving
+You can add multiple glossary files, each containing words that can be hovered for information!; editor://GlossaryEditor
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg
new file mode 100644
index 0000000..1437dbc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg.import
new file mode 100644
index 0000000..9830711
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bsx8dtqf3vych"
+path="res://.godot/imported/default.svg-b959bd18b8ef5203f5e20ad593e3fe5b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/default.svg"
+dest_files=["res://.godot/imported/default.svg-b959bd18b8ef5203f5e20ad593e3fe5b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg
new file mode 100644
index 0000000..7fb881f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg
@@ -0,0 +1,10 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg.import
new file mode 100644
index 0000000..47002fa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5laykjsxaxtl"
+path="res://.godot/imported/divide.svg-56c06e734a3b787e6a0fb15506118077.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/divide.svg"
+dest_files=["res://.godot/imported/divide.svg-56c06e734a3b787e6a0fb15506118077.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg
new file mode 100644
index 0000000..9eb7aa9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg.import
new file mode 100644
index 0000000..c7ccc1c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7j220k0ewh35"
+path="res://.godot/imported/join.svg-bb063f8363952d3561f81b2b567bc648.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/join.svg"
+dest_files=["res://.godot/imported/join.svg-bb063f8363952d3561f81b2b567bc648.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg
new file mode 100644
index 0000000..e234619
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg.import
new file mode 100644
index 0000000..ba9d555
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cspjyvye6c0r6"
+path="res://.godot/imported/leave.svg-44bd121015b4b5f035aed68ea6514ddc.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/leave.svg"
+dest_files=["res://.godot/imported/leave.svg-44bd121015b4b5f035aed68ea6514ddc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg
new file mode 100644
index 0000000..eb5d732
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg
@@ -0,0 +1,10 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg.import
new file mode 100644
index 0000000..19cc8d7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dwy14qrkfoeb"
+path="res://.godot/imported/minus.svg-49dcbf6b69afb013b09bee12c2eaee4d.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/minus.svg"
+dest_files=["res://.godot/imported/minus.svg-49dcbf6b69afb013b09bee12c2eaee4d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg
new file mode 100644
index 0000000..d4327d1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg
@@ -0,0 +1,10 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import
new file mode 100644
index 0000000..59b5a03
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ddmapfkunbtg7"
+path="res://.godot/imported/multiply.svg-a440aeabe0711499afab2578a605a0c8.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/multiply.svg"
+dest_files=["res://.godot/imported/multiply.svg-a440aeabe0711499afab2578a605a0c8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg
new file mode 100644
index 0000000..adf5179
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg
@@ -0,0 +1,10 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg.import
new file mode 100644
index 0000000..a01bf29
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cqqtygfbvgtag"
+path="res://.godot/imported/plus.svg-c478250e8c267e7f27d3efadae418395.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/plus.svg"
+dest_files=["res://.godot/imported/plus.svg-c478250e8c267e7f27d3efadae418395.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg
new file mode 100644
index 0000000..16c6a8b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg
@@ -0,0 +1,10 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg.import
new file mode 100644
index 0000000..5bb4bed
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ddcfl67v0r1lw"
+path="res://.godot/imported/set.svg-503d5cdf15df79c2138350d7df04097b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/set.svg"
+dest_files=["res://.godot/imported/set.svg-503d5cdf15df79c2138350d7df04097b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg
new file mode 100644
index 0000000..44f1f3c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg
@@ -0,0 +1,5 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg.import
new file mode 100644
index 0000000..4faef9c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://t1roknwygcf3"
+path="res://.godot/imported/update.svg-fa0c73766cd3030063319f62c5965e30.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Dropdown/update.svg"
+dest_files=["res://.godot/imported/update.svg-fa0c73766cd3030063319f62c5965e30.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg
new file mode 100644
index 0000000..eb97691
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg
@@ -0,0 +1 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import
new file mode 100644
index 0000000..b8a4665
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://brjikovneb63n"
+path="res://.godot/imported/decrement_icon.svg-a2593e736b97b85048722ba5d6e9ccb2.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg"
+dest_files=["res://.godot/imported/decrement_icon.svg-a2593e736b97b85048722ba5d6e9ccb2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg
new file mode 100644
index 0000000..0b72c07
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg
@@ -0,0 +1 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import
new file mode 100644
index 0000000..db6ee21
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dh1ycbmw8anqh"
+path="res://.godot/imported/increment_icon.svg-9e5be0fb4753d7b00fd5c17fde994914.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Interactable/increment_icon.svg"
+dest_files=["res://.godot/imported/increment_icon.svg-9e5be0fb4753d7b00fd5c17fde994914.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg
new file mode 100644
index 0000000..7331b61
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import
new file mode 100644
index 0000000..9c8fcda
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://babwe22dqjta"
+path="res://.godot/imported/add-folder.svg-c35b3552332c1a7a422b065715ba5bd0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/add-folder.svg"
+dest_files=["res://.godot/imported/add-folder.svg-c35b3552332c1a7a422b065715ba5bd0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg
new file mode 100644
index 0000000..3e3f7af
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import
new file mode 100644
index 0000000..3b6b962
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bff65e82555qr"
+path="res://.godot/imported/close-icon.svg-b1d1f631dda99063f4967053094e19e9.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/close-icon.svg"
+dest_files=["res://.godot/imported/close-icon.svg-b1d1f631dda99063f4967053094e19e9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg
new file mode 100644
index 0000000..4032eab
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import
new file mode 100644
index 0000000..52e3ecf
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dx3o2ild56i76"
+path="res://.godot/imported/closed-icon.svg-f1be10bfef346e366b060b9397fc99b3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/closed-icon.svg"
+dest_files=["res://.godot/imported/closed-icon.svg-f1be10bfef346e366b060b9397fc99b3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg
new file mode 100644
index 0000000..3ec3def
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import
new file mode 100644
index 0000000..976e1e3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl03vrbj5wsjk"
+path="res://.godot/imported/expand-icon.svg-8b525f094b41df5f412260b6a8ccc628.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/expand-icon.svg"
+dest_files=["res://.godot/imported/expand-icon.svg-8b525f094b41df5f412260b6a8ccc628.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg
new file mode 100644
index 0000000..c66c422
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import
new file mode 100644
index 0000000..1c0d8e4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://mc7a24bcvjo3"
+path="res://.godot/imported/open-icon.svg-08dd4dde8f810f287bf41bb688e33b60.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/open-icon.svg"
+dest_files=["res://.godot/imported/open-icon.svg-08dd4dde8f810f287bf41bb688e33b60.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg
new file mode 100644
index 0000000..236ca35
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg.import
new file mode 100644
index 0000000..c2f19e4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dih1coellhwm8"
+path="res://.godot/imported/variable.svg-042a5ec751904f8b15060524e354ede4.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable.svg"
+dest_files=["res://.godot/imported/variable.svg-042a5ec751904f8b15060524e354ede4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png
new file mode 100644
index 0000000..779e80f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import
new file mode 100644
index 0000000..a6eda4f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ikdhcat2nq2r"
+path="res://.godot/imported/variable_icon.png-90854a14028305f3300bfd6765cc7898.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/variable_icon.png"
+dest_files=["res://.godot/imported/variable_icon.png-90854a14028305f3300bfd6765cc7898.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg
new file mode 100644
index 0000000..a252bde
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg.import
new file mode 100644
index 0000000..e0a4f64
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d4n3j4lvatwxb"
+path="res://.godot/imported/warning.svg-836a90b63e6b4d95b35d0dba1b7a8387.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Pieces/warning.svg"
+dest_files=["res://.godot/imported/warning.svg-836a90b63e6b4d95b35d0dba1b7a8387.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg
new file mode 100644
index 0000000..8871f5e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg.import
new file mode 100644
index 0000000..fe213ba
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbea0efx0ybu7"
+path="res://.godot/imported/character.svg-bd04c5f56d5a275e0a21a39806dde9fd.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Resources/character.svg"
+dest_files=["res://.godot/imported/character.svg-bd04c5f56d5a275e0a21a39806dde9fd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png
new file mode 100644
index 0000000..5858854
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png.import
new file mode 100644
index 0000000..1570b1e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmwrsq48ywc50"
+path="res://.godot/imported/icon_character.png-c43d5e675349285f569332f852145f72.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Resources/icon_character.png"
+dest_files=["res://.godot/imported/icon_character.png-c43d5e675349285f569332f852145f72.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg
new file mode 100644
index 0000000..006807a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg.import
new file mode 100644
index 0000000..ac17fff
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dfi7fhfc4dbc3"
+path="res://.godot/imported/portrait.svg-39b81dae0192e108147b023ff326faca.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Resources/portrait.svg"
+dest_files=["res://.godot/imported/portrait.svg-39b81dae0192e108147b023ff326faca.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg
new file mode 100644
index 0000000..9a42063
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import
new file mode 100644
index 0000000..1bac6ef
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://my600mb32ydt"
+path="res://.godot/imported/add-character.svg-aa244fe75dc8dd70ac6589132e53fed3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-character.svg"
+dest_files=["res://.godot/imported/add-character.svg-aa244fe75dc8dd70ac6589132e53fed3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg
new file mode 100644
index 0000000..5fa7ac1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg
@@ -0,0 +1,7 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import
new file mode 100644
index 0000000..9c80165
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bymlbr4o2m3jc"
+path="res://.godot/imported/add-timeline.svg-5e560147b6f2a7484900915c0ef508c0.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg"
+dest_files=["res://.godot/imported/add-timeline.svg-5e560147b6f2a7484900915c0ef508c0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png b/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png
new file mode 100644
index 0000000..87ab913
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png.import b/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png.import
new file mode 100644
index 0000000..ca47e11
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbf2dlmbn12h0"
+path="res://.godot/imported/Unknown.png-2c9eda788de8704e5c996a9ce2471354.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/Unknown.png"
+dest_files=["res://.godot/imported/Unknown.png-2c9eda788de8704e5c996a9ce2471354.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg
new file mode 100644
index 0000000..d6e5b69
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg
@@ -0,0 +1,18 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg.import
new file mode 100644
index 0000000..7fcffab
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bt87p6qlso0ya"
+path="res://.godot/imported/dialogic-logo.svg-08293ede49a57b02da2b28ef9cba822f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/dialogic-logo.svg"
+dest_files=["res://.godot/imported/dialogic-logo.svg-08293ede49a57b02da2b28ef9cba822f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg b/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg
new file mode 100644
index 0000000..6f542b6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg.import b/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg.import
new file mode 100644
index 0000000..b1062a1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dybg3l5pwetne"
+path="res://.godot/imported/plugin-icon.svg-dc602692e84007d364f87d7626a4b242.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/plugin-icon.svg"
+dest_files=["res://.godot/imported/plugin-icon.svg-dc602692e84007d364f87d7626a4b242.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png
new file mode 100644
index 0000000..0ef6e85
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png.import b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png.import
new file mode 100644
index 0000000..e71b892
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://41634vnjwsfw"
+path="res://.godot/imported/preview_character.png-6f92c4d4d93077deceb050d601d521bd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/preview_character.png"
+dest_files=["res://.godot/imported/preview_character.png-6f92c4d4d93077deceb050d601d521bd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png
new file mode 100644
index 0000000..1e15626
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png.import b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png.import
new file mode 100644
index 0000000..31fcf25
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dp7np6b4ipm0a"
+path="res://.godot/imported/preview_character_speaker.png-9883312ac7b7f924cb3c3b774b455706.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Editor/Images/preview_character_speaker.png"
+dest_files=["res://.godot/imported/preview_character_speaker.png-9883312ac7b7f924cb3c3b774b455706.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Inspector/inspector_plugin.gd b/addons/dialogic-main/addons/dialogic/Editor/Inspector/inspector_plugin.gd
new file mode 100644
index 0000000..0a40bc3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Inspector/inspector_plugin.gd
@@ -0,0 +1,15 @@
+@tool
+extends EditorInspectorPlugin
+
+
+func _can_handle(object: Object) -> bool:
+ return true
+
+
+func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool:
+ if type == TYPE_OBJECT and hint_type == PROPERTY_HINT_RESOURCE_TYPE:
+ if hint_string == "DialogicTimeline":
+ var editor: EditorProperty = load("res://addons/dialogic/Editor/Inspector/timeline_inspector_field.gd").new()
+ add_property_editor(name, editor)
+ return true
+ return false
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd b/addons/dialogic-main/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd
new file mode 100644
index 0000000..6e617fe
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd
@@ -0,0 +1,82 @@
+@tool
+extends EditorProperty
+
+var field: Control = null
+var button: Button = null
+# An internal value of the property.
+var current_value: DialogicTimeline = null
+# A guard against internal changes when the property is updated.
+var updating = false
+
+
+func _init() -> void:
+ var hbox := HBoxContainer.new()
+ add_child(hbox)
+
+ field = load("res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn").instantiate()
+ hbox.add_child(field)
+ field.placeholder_text = "No Timeline"
+ field.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ field.size_flags_vertical = Control.SIZE_SHRINK_CENTER
+ field.mode = field.Modes.IDENTIFIER
+ field.fit_text_length = false
+ field.valid_file_drop_extension = ".dtl"
+ field.value_changed.connect(_on_field_value_changed)
+ field.get_suggestions_func = get_timeline_suggestions
+
+ button = Button.new()
+ hbox.add_child(button)
+ button.hide()
+ button.pressed.connect(_on_button_pressed, CONNECT_DEFERRED)
+
+
+func _on_field_value_changed(property:String, value:Variant) -> void:
+ # Ignore the signal if the property is currently being updated.
+ if updating:
+ return
+
+ var new_value: DialogicTimeline = null
+ if value:
+ new_value = DialogicResourceUtil.get_timeline_resource(value)
+
+ if current_value != new_value:
+ current_value = new_value
+ if current_value:
+ button.show()
+ else:
+ button.hide()
+ emit_changed(get_edited_property(), current_value)
+
+
+func _update_property() -> void:
+ field.resource_icon = get_theme_icon("TripleBar", "EditorIcons")
+ button.icon = get_theme_icon("ExternalLink", "EditorIcons")
+
+ # Read the current value from the property.
+ var new_value = get_edited_object()[get_edited_property()]
+ if (new_value == current_value):
+ return
+
+ # Update the control with the new value.
+ updating = true
+ current_value = new_value
+ if current_value:
+ field.set_value(DialogicResourceUtil.get_unique_identifier(current_value.resource_path))
+ button.show()
+ else:
+ button.hide()
+ field.set_value("")
+ updating = false
+
+
+func get_timeline_suggestions(filter:String) -> Dictionary:
+ var suggestions := {}
+ var timeline_directory := DialogicResourceUtil.get_timeline_directory()
+ for identifier in timeline_directory.keys():
+ suggestions[identifier] = {'value': identifier, 'tooltip':timeline_directory[identifier], 'editor_icon': ["TripleBar", "EditorIcons"]}
+ return suggestions
+
+
+func _on_button_pressed() -> void:
+ if current_value:
+ EditorInterface.edit_resource(current_value)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd
new file mode 100644
index 0000000..f5f11e3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd
@@ -0,0 +1,13 @@
+@tool
+extends Label
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+ # don't load the label settings when opening as a scene
+ # prevents HUGE diffs
+ if owner.get_parent() is SubViewport:
+ return
+ label_settings = LabelSettings.new()
+ label_settings.font = get_theme_font("doc_italic", "EditorFonts")
+ label_settings.font_size = get_theme_font_size('font_size', 'Label')
+ label_settings.font_color = get_theme_color("accent_color", "Editor")
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/csv_file.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/csv_file.gd
new file mode 100644
index 0000000..3f8fb3a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/csv_file.gd
@@ -0,0 +1,356 @@
+class_name DialogicCsvFile
+extends RefCounted
+## Handles translation of a [class DialogicTimeline] to a CSV file.
+
+var lines: Array[PackedStringArray] = []
+## Dictionary of lines from the original file.
+## Key: String, Value: PackedStringArray
+var old_lines: Dictionary = {}
+
+## The amount of columns the CSV file has after loading it.
+## Used to add trailing commas to new lines.
+var column_count := 0
+
+## Whether this CSV file was able to be loaded a defined
+## file path.
+var is_new_file: bool = false
+
+## The underlying file used to read and write the CSV file.
+var file: FileAccess
+
+## File path used to load the CSV file.
+var used_file_path: String
+
+## The amount of events that were updated in the CSV file.
+var updated_rows: int = 0
+
+## The amount of events that were added to the CSV file.
+var new_rows: int = 0
+
+## Whether this CSV handler should add newlines as a separator between sections.
+## A section may be a new character, new timeline, or new glossary item inside
+## a per-project file.
+var add_separator: bool = false
+
+enum PropertyType {
+ String = 0,
+ Array = 1,
+ Other = 2,
+}
+
+## The translation property used for the glossary item translation.
+const TRANSLATION_ID := DialogicGlossary.TRANSLATION_PROPERTY
+
+## Attempts to load the CSV file from [param file_path].
+## If the file does not exist, a single entry is added to the [member lines]
+## array.
+## The [param separator_enabled] enables adding newlines as a separator to
+## per-project files. This is useful for readability.
+func _init(file_path: String, original_locale: String, separator_enabled: bool) -> void:
+ used_file_path = file_path
+ add_separator = separator_enabled
+
+ # The first entry must be the locale row.
+ # [method collect_lines_from_timeline] will add the other locales, if any.
+ var locale_array_line := PackedStringArray(["keys", original_locale])
+ lines.append(locale_array_line)
+
+ if not ResourceLoader.exists(file_path):
+ is_new_file = true
+
+ # The "keys" and original locale are the only columns in a new file.
+ # For example: "keys, en"
+ column_count = 2
+ return
+
+ file = FileAccess.open(file_path, FileAccess.READ)
+
+ var locale_csv_row := file.get_csv_line()
+ column_count = locale_csv_row.size()
+ var locale_key := locale_csv_row[0]
+
+ old_lines[locale_key] = locale_csv_row
+
+ _read_file_into_lines()
+
+
+## Private function to read the CSV file into the [member lines] array.
+## Cannot be called on a new file.
+func _read_file_into_lines() -> void:
+ while not file.eof_reached():
+ var line := file.get_csv_line()
+ var row_key := line[0]
+
+ old_lines[row_key] = line
+
+
+## Collects names from the given [param characters] and adds them to the
+## [member lines].
+##
+## If this is the character name CSV file, use this method to
+## take previously collected characters from other [class DialogicCsvFile]s.
+func collect_lines_from_characters(characters: Dictionary) -> void:
+ for character: DialogicCharacter in characters.values():
+ # Add row for display names.
+ var name_property := DialogicCharacter.TranslatedProperties.NAME
+ var display_name_key: String = character.get_property_translation_key(name_property)
+ var line_value: String = character.display_name
+ var array_line := PackedStringArray([display_name_key, line_value])
+ lines.append(array_line)
+
+ var nicknames: Array = character.nicknames
+
+ if not nicknames.is_empty():
+ var nick_name_property := DialogicCharacter.TranslatedProperties.NICKNAMES
+ var nickname_string: String = ",".join(nicknames)
+ var nickname_name_line_key: String = character.get_property_translation_key(nick_name_property)
+ var nick_array_line := PackedStringArray([nickname_name_line_key, nickname_string])
+ lines.append(nick_array_line)
+
+ # New character item, if needed, add a separator.
+ if add_separator:
+ _append_empty()
+
+
+## Appends an empty line to the [member lines] array.
+func _append_empty() -> void:
+ var empty_line := PackedStringArray(["", ""])
+ lines.append(empty_line)
+
+
+## Returns the property type for the given [param key].
+func _get_key_type(key: String) -> PropertyType:
+ if key.ends_with(DialogicGlossary.NAME_PROPERTY):
+ return PropertyType.String
+
+ if key.ends_with(DialogicGlossary.ALTERNATIVE_PROPERTY):
+ return PropertyType.Array
+
+ return PropertyType.Other
+
+
+func _process_line_into_array(csv_values: PackedStringArray, property_type: PropertyType) -> Array[String]:
+ const KEY_VALUE_INDEX := 0
+ var values_as_array: Array[String] = []
+
+ for i in csv_values.size():
+
+ if i == KEY_VALUE_INDEX:
+ continue
+
+ var csv_value := csv_values[i]
+
+ if csv_value.is_empty():
+ continue
+
+ match property_type:
+ PropertyType.String:
+ values_as_array = [csv_value]
+
+ PropertyType.Array:
+ var split_values := csv_value.split(",")
+
+ for value in split_values:
+ values_as_array.append(value)
+
+ return values_as_array
+
+
+func _add_keys_to_glossary(glossary: DialogicGlossary, names: Array) -> void:
+ var glossary_prefix_key := glossary._get_glossary_translation_id_prefix()
+ var glossary_translation_id_prefix := _get_glossary_translation_key_prefix(glossary)
+
+ for glossary_line: PackedStringArray in names:
+
+ if glossary_line.is_empty():
+ continue
+
+ var csv_key := glossary_line[0]
+
+ # CSV line separators will be empty.
+ if not csv_key.begins_with(glossary_prefix_key):
+ continue
+
+ var value_type := _get_key_type(csv_key)
+
+ # String and Array are the only valid types.
+ if (value_type == PropertyType.Other
+ or not csv_key.begins_with(glossary_translation_id_prefix)):
+ continue
+
+ var new_line_to_add := _process_line_into_array(glossary_line, value_type)
+
+ for name_to_add: String in new_line_to_add:
+ glossary._translation_keys[name_to_add.strip_edges()] = csv_key
+
+
+
+## Reads all [member lines] and adds them to the given [param glossary]'s
+## internal collection of words-to-translation-key mappings.
+##
+## Populate the CSV's lines with the method [method collect_lines_from_glossary]
+## before.
+func add_translation_keys_to_glossary(glossary: DialogicGlossary) -> void:
+ glossary._translation_keys.clear()
+ _add_keys_to_glossary(glossary, lines)
+ _add_keys_to_glossary(glossary, old_lines.values())
+
+
+## Returns the translation key prefix for the given [param glossary_translation_id].
+## The resulting format will look like this: Glossary/a2/
+## You can use this to find entries in [member lines] that to a glossary.
+func _get_glossary_translation_key_prefix(glossary: DialogicGlossary) -> String:
+ return (
+ DialogicGlossary.RESOURCE_NAME
+ .path_join(glossary._translation_id)
+ )
+
+
+## Returns whether [param value_b] is greater than [param value_a].
+##
+## This method helps to sort glossary entry properties by their importance
+## matching the order in the editor.
+##
+## TODO: Allow Dialogic users to define their own order.
+func _sort_glossary_entry_property_keys(property_key_a: String, property_key_b: String) -> bool:
+ const GLOSSARY_CSV_LINE_ORDER := {
+ DialogicGlossary.NAME_PROPERTY: 0,
+ DialogicGlossary.ALTERNATIVE_PROPERTY: 1,
+ DialogicGlossary.TEXT_PROPERTY: 2,
+ DialogicGlossary.EXTRA_PROPERTY: 3,
+ }
+ const UNKNOWN_PROPERTY_ORDER := 100
+
+ var value_a: int = GLOSSARY_CSV_LINE_ORDER.get(property_key_a, UNKNOWN_PROPERTY_ORDER)
+ var value_b: int = GLOSSARY_CSV_LINE_ORDER.get(property_key_b, UNKNOWN_PROPERTY_ORDER)
+
+ return value_a < value_b
+
+
+## Collects properties from glossary entries from the given [param glossary] and
+## adds them to the [member lines].
+func collect_lines_from_glossary(glossary: DialogicGlossary) -> void:
+
+ for glossary_value: Variant in glossary.entries.values():
+
+ if glossary_value is String:
+ continue
+
+ var glossary_entry: Dictionary = glossary_value
+ var glossary_entry_name: String = glossary_entry[DialogicGlossary.NAME_PROPERTY]
+
+ var _glossary_translation_id := glossary.get_set_glossary_translation_id()
+ var entry_translation_id := glossary.get_set_glossary_entry_translation_id(glossary_entry_name)
+
+ var entry_property_keys := glossary_entry.keys().duplicate()
+ entry_property_keys.sort_custom(_sort_glossary_entry_property_keys)
+
+ var entry_name_property: String = glossary_entry[DialogicGlossary.NAME_PROPERTY]
+
+ for entry_key: String in entry_property_keys:
+ # Ignore private keys.
+ if entry_key.begins_with(DialogicGlossary.PRIVATE_PROPERTY_PREFIX):
+ continue
+
+ var item_value: Variant = glossary_entry[entry_key]
+ var item_value_str := ""
+
+ if item_value is Array:
+ var item_array := item_value as Array
+ # We use a space after the comma to make it easier to read.
+ item_value_str = " ,".join(item_array)
+
+ elif not item_value is String or item_value.is_empty():
+ continue
+
+ else:
+ item_value_str = item_value
+
+ var glossary_csv_key := glossary._get_glossary_translation_key(entry_translation_id, entry_key)
+
+ if (entry_key == DialogicGlossary.NAME_PROPERTY
+ or entry_key == DialogicGlossary.ALTERNATIVE_PROPERTY):
+ glossary.entries[glossary_csv_key] = entry_name_property
+
+ var glossary_line := PackedStringArray([glossary_csv_key, item_value_str])
+
+ lines.append(glossary_line)
+
+ # New glossary item, if needed, add a separator.
+ if add_separator:
+ _append_empty()
+
+
+
+## Collects translatable events from the given [param timeline] and adds
+## them to the [member lines].
+func collect_lines_from_timeline(timeline: DialogicTimeline) -> void:
+ for event: DialogicEvent in timeline.events:
+
+ if event.can_be_translated():
+
+ if event._translation_id.is_empty():
+ event.add_translation_id()
+ event.update_text_version()
+
+ var properties: Array = event._get_translatable_properties()
+
+ for property: String in properties:
+ var line_key: String = event.get_property_translation_key(property)
+ var line_value: String = event._get_property_original_translation(property)
+ var array_line := PackedStringArray([line_key, line_value])
+ lines.append(array_line)
+
+ # End of timeline, if needed, add a separator.
+ if add_separator:
+ _append_empty()
+
+
+## Clears the CSV file on disk and writes the current [member lines] array to it.
+## Uses the [member old_lines] dictionary to update existing translations.
+## If a translation row misses a column, a trailing comma will be added to
+## conform to the CSV file format.
+##
+## If the locale CSV line was collected only, a new file won't be created and
+## already existing translations won't be updated.
+func update_csv_file_on_disk() -> void:
+ # None or locale row only.
+ if lines.size() < 2:
+ print_rich("[color=yellow]No lines for the CSV file, skipping: " + used_file_path)
+
+ return
+
+ # Clear the current CSV file.
+ file = FileAccess.open(used_file_path, FileAccess.WRITE)
+
+ for line in lines:
+ var row_key := line[0]
+
+ # In case there might be translations for this line already,
+ # add them at the end again (orig locale text is replaced).
+ if row_key in old_lines:
+ var old_line: PackedStringArray = old_lines[row_key]
+ var updated_line: PackedStringArray = line + old_line.slice(2)
+
+ var line_columns: int = updated_line.size()
+ var line_columns_to_add := column_count - line_columns
+
+ # Add trailing commas to match the amount of columns.
+ for _i in range(line_columns_to_add):
+ updated_line.append("")
+
+ file.store_csv_line(updated_line)
+ updated_rows += 1
+
+ else:
+ var line_columns: int = line.size()
+ var line_columns_to_add := column_count - line_columns
+
+ # Add trailing commas to match the amount of columns.
+ for _i in range(line_columns_to_add):
+ line.append("")
+
+ file.store_csv_line(line)
+ new_rows += 1
+
+ file.close()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.gd
new file mode 100644
index 0000000..3d87c4a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.gd
@@ -0,0 +1,169 @@
+@tool
+extends DialogicEditor
+
+## Editor that contains all settings
+
+var button_group := ButtonGroup.new()
+var registered_sections: Array[DialogicSettingsPage] = []
+
+
+func _get_title() -> String:
+ return "Settings"
+
+
+func _get_icon() -> Texture:
+ return get_theme_icon("PluginScript", "EditorIcons")
+
+
+func _register() -> void:
+ editors_manager.register_simple_editor(self)
+ self.alternative_text = "Customize dialogic and it's behaviour"
+
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ register_settings_section("res://addons/dialogic/Editor/Settings/settings_general.tscn")
+ register_settings_section("res://addons/dialogic/Editor/Settings/settings_translation.tscn")
+ register_settings_section("res://addons/dialogic/Editor/Settings/settings_modules.tscn")
+
+ for indexer in DialogicUtil.get_indexers():
+ for settings_page in indexer._get_settings_pages():
+ register_settings_section(settings_page)
+
+ add_registered_sections()
+ %SettingsTabs.get_child(0).button_pressed = true
+ %SettingsContent.get_child(0).show()
+
+
+func register_settings_section(path:String) -> void:
+ var section: Control = load(path).instantiate()
+ registered_sections.append(section)
+
+
+func add_registered_sections() -> void:
+ for i in %SettingsTabs.get_children():
+ i.queue_free()
+ for i in %FeatureTabs.get_children():
+ i.queue_free()
+
+ for i in %SettingsContent.get_children():
+ i.queue_free()
+
+
+ registered_sections.sort_custom(section_sort)
+ for section in registered_sections:
+
+ section.name = section._get_title()
+
+ var vbox := VBoxContainer.new()
+ vbox.set_meta('section', section)
+ vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ vbox.name = section.name
+ var hbox := HBoxContainer.new()
+
+ var title := Label.new()
+ title.text = section.name
+ title.theme_type_variation = 'DialogicSectionBig'
+ hbox.add_child(title)
+ vbox.add_child(hbox)
+
+
+ if !section.short_info.is_empty():
+ var tooltip_hint: Control = load("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn").instantiate()
+ tooltip_hint.hint_text = section.short_info
+ hbox.add_child(tooltip_hint)
+
+
+ var scroll := ScrollContainer.new()
+ scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ var inner_vbox := VBoxContainer.new()
+ inner_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ inner_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ scroll.add_child(inner_vbox)
+ var panel := PanelContainer.new()
+ panel.theme_type_variation = "DialogicPanelA"
+ panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ if section.size_flags_vertical == Control.SIZE_EXPAND_FILL:
+ panel.size_flags_vertical = Control.SIZE_EXPAND_FILL
+ inner_vbox.add_child(panel)
+
+
+ var info_section: Control = section._get_info_section()
+ if info_section != null:
+ inner_vbox.add_child(Control.new())
+ inner_vbox.get_child(-1).custom_minimum_size.y = 50
+
+ inner_vbox.add_child(title.duplicate())
+ inner_vbox.get_child(-1).text = "Information"
+ var info_panel := panel.duplicate()
+ info_panel.theme_type_variation = "DialogicPanelDarkA"
+
+ inner_vbox.add_child(info_panel)
+ info_section.get_parent().remove_child(info_section)
+ info_panel.add_child(info_section)
+
+ panel.add_child(section)
+ vbox.add_child(scroll)
+
+
+ var button := Button.new()
+ button.text = " "+section.name
+ button.tooltip_text = section.name
+ button.toggle_mode = true
+ button.button_group = button_group
+ button.expand_icon = true
+ button.alignment = HORIZONTAL_ALIGNMENT_LEFT
+ button.flat = true
+ button.add_theme_color_override('font_pressed_color', get_theme_color("property_color_z", "Editor"))
+ button.add_theme_color_override('font_hover_color', get_theme_color('warning_color', 'Editor'))
+ button.add_theme_color_override('font_focus_color', get_theme_color('warning_color', 'Editor'))
+ button.add_theme_stylebox_override('focus', StyleBoxEmpty.new())
+ button.pressed.connect(open_tab.bind(vbox))
+ if section._is_feature_tab():
+ %FeatureTabs.add_child(button)
+ else:
+ %SettingsTabs.add_child(button)
+
+ vbox.hide()
+# if section.has_method('_get_icon'):
+# icon.texture = section._get_icon()
+ %SettingsContent.add_child(vbox)
+
+
+func open_tab(tab_to_show:Control) -> void:
+ for tab in %SettingsContent.get_children():
+ tab.hide()
+
+ tab_to_show.show()
+
+
+func section_sort(item1:DialogicSettingsPage, item2:DialogicSettingsPage) -> bool:
+ if !item1._is_feature_tab() and item2._is_feature_tab():
+ return true
+ if item1._get_priority() > item2._get_priority():
+ return true
+ return false
+
+
+
+func _open(extra_information:Variant = null) -> void:
+ refresh()
+ if typeof(extra_information) == TYPE_STRING:
+ if %SettingsContent.has_node(extra_information):
+ open_tab(%SettingsContent.get_node(extra_information))
+
+
+func _close() -> void:
+ for child in %SettingsContent.get_children():
+ if child.get_meta('section').has_method('_about_to_close'):
+ child.get_meta('section')._about_to_close()
+
+
+func refresh() -> void:
+ for child in %SettingsContent.get_children():
+ if child.get_meta('section').has_method('_refresh'):
+ child.get_meta('section')._refresh()
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.tscn b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.tscn
new file mode 100644
index 0000000..01d385f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_editor.tscn
@@ -0,0 +1,59 @@
+[gd_scene load_steps=2 format=3 uid="uid://dganirw26brfb"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/settings_editor.gd" id="1"]
+
+[node name="Settings" type="HSplitContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1")
+
+[node name="TabList" type="ScrollContainer" parent="."]
+layout_mode = 2
+horizontal_scroll_mode = 0
+
+[node name="Margin" type="MarginContainer" parent="TabList"]
+layout_mode = 2
+theme_override_constants/margin_top = 3
+
+[node name="VBox" type="VBoxContainer" parent="TabList/Margin"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Title" type="Label" parent="TabList/Margin/VBox"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Settings"
+
+[node name="SettingsTabs" type="VBoxContainer" parent="TabList/Margin/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 0
+
+[node name="Control" type="Control" parent="TabList/Margin/VBox"]
+custom_minimum_size = Vector2(0, 30)
+layout_mode = 2
+
+[node name="Title2" type="Label" parent="TabList/Margin/VBox"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Features"
+
+[node name="FeatureTabs" type="VBoxContainer" parent="TabList/Margin/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 0
+
+[node name="SettingsContent" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.gd
new file mode 100644
index 0000000..07500c4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.gd
@@ -0,0 +1,247 @@
+@tool
+extends DialogicSettingsPage
+
+## Settings tab that holds genreal dialogic settings.
+
+
+func _get_title() -> String:
+ return "General"
+
+
+func _get_priority() -> int:
+ return 99
+
+func _ready() -> void:
+ var s := DCSS.inline({
+ 'padding': 5,
+ 'background': Color(0.545098, 0.545098, 0.545098, 0.211765)
+ })
+ %ExtensionsFolderPicker.resource_icon = get_theme_icon("Folder", "EditorIcons")
+
+ # Signals
+ %ExtensionsFolderPicker.value_changed.connect(_on_ExtensionsFolder_value_changed)
+ %PhysicsTimerButton.toggled.connect(_on_physics_timer_button_toggled)
+
+ # Colors
+ %ResetColorsButton.icon = get_theme_icon("Reload", "EditorIcons")
+ %ResetColorsButton.button_up.connect(_on_reset_colors_button)
+
+ # Extension creator
+ %ExtensionCreator.hide()
+
+
+func _refresh() -> void:
+ %PhysicsTimerButton.button_pressed = DialogicUtil.is_physics_timer()
+ %LayoutNodeEndBehaviour.select(ProjectSettings.get_setting('dialogic/layout/end_behaviour', 0))
+ %ExtensionsFolderPicker.set_value(ProjectSettings.get_setting('dialogic/extensions_folder', 'res://addons/dialogic_additions'))
+
+ update_color_palette()
+
+ %SectionList.clear()
+ %SectionList.create_item()
+ var cached_events := DialogicResourceUtil.get_event_cache()
+ var sections := []
+ var section_order: Array = DialogicUtil.get_editor_setting('event_section_order', ['Main', 'Logic', 'Flow', 'Audio', 'Visuals','Other', 'Helper'])
+ for ev in cached_events:
+ if !ev.event_category in sections:
+ sections.append(ev.event_category)
+ var item: TreeItem = %SectionList.create_item(null)
+ item.set_text(0, ev.event_category)
+ item.add_button(0, get_theme_icon("ArrowUp", "EditorIcons"))
+ item.add_button(0, get_theme_icon("ArrowDown", "EditorIcons"))
+ if ev.event_category in section_order:
+
+ item.move_before(item.get_parent().get_child(min(section_order.find(ev.event_category),item.get_parent().get_child_count()-1)))
+
+ %SectionList.get_root().get_child(0).set_button_disabled(0, 0, true)
+ %SectionList.get_root().get_child(-1).set_button_disabled(0, 1, true)
+
+
+func _on_section_list_button_clicked(item:TreeItem, column, id, mouse_button_index):
+ if id == 0:
+ item.move_before(item.get_parent().get_child(item.get_index()-1))
+ else:
+ item.move_after(item.get_parent().get_child(item.get_index()+1))
+
+ for child in %SectionList.get_root().get_children():
+ child.set_button_disabled(0, 0, false)
+ child.set_button_disabled(0, 1, false)
+
+ %SectionList.get_root().get_child(0).set_button_disabled(0, 0, true)
+ %SectionList.get_root().get_child(-1).set_button_disabled(0, 1, true)
+
+ var sections := []
+ for child in %SectionList.get_root().get_children():
+ sections.append(child.get_text(0))
+
+ DialogicUtil.set_editor_setting('event_section_order', sections)
+ force_event_button_list_reload()
+
+
+func force_event_button_list_reload() -> void:
+ find_parent('EditorsManager').editors['Timeline'].node.get_node('%VisualEditor').load_event_buttons()
+
+
+func update_color_palette() -> void:
+ # Color Palette
+ for child in %Colors.get_children():
+ child.queue_free()
+ for color in DialogicUtil.get_color_palette():
+ var button := ColorPickerButton.new()
+ button.custom_minimum_size = Vector2(50 ,50) * DialogicUtil.get_editor_scale()
+ %Colors.add_child(button)
+ button.color = DialogicUtil.get_color(color)
+ button.color_changed.connect(_on_color_change)
+
+
+func _on_color_change(color:Color) -> void:
+ var new_palette := {}
+ for i in %Colors.get_children():
+ new_palette['Color'+str(i.get_index()+1)] = i.color
+ DialogicUtil.set_editor_setting('color_palette', new_palette)
+
+
+
+func _on_reset_colors_button() -> void:
+ DialogicUtil.set_editor_setting('color_palette', null)
+ update_color_palette()
+
+
+func _on_physics_timer_button_toggled(is_toggled: bool) -> void:
+ ProjectSettings.set_setting('dialogic/timer/process_in_physics', is_toggled)
+ ProjectSettings.save()
+
+
+func _on_ExtensionsFolder_value_changed(property:String, value:String) -> void:
+ if value == null or value.is_empty():
+ value = 'res://addons/dialogic_additions'
+ ProjectSettings.set_setting('dialogic/extensions_folder', value)
+ ProjectSettings.save()
+
+
+func _on_layout_node_end_behaviour_item_selected(index:int) -> void:
+ ProjectSettings.set_setting('dialogic/layout/end_behaviour', index)
+ ProjectSettings.save()
+
+
+################################################################################
+## EXTENSION CREATOR
+################################################################################
+
+func _on_create_extension_button_pressed() -> void:
+ %CreateExtensionButton.hide()
+ %ExtensionCreator.show()
+
+ %NameEdit.text = ""
+ %NameEdit.grab_focus()
+
+
+func _on_submit_extension_button_pressed() -> void:
+ if %NameEdit.text.is_empty():
+ return
+
+ var extensions_folder: String = ProjectSettings.get_setting('dialogic/extensions_folder', 'res://addons/dialogic_additions')
+
+ extensions_folder = extensions_folder.path_join(%NameEdit.text.to_pascal_case())
+ DirAccess.make_dir_recursive_absolute(extensions_folder)
+ var mode: int = %ExtensionMode.selected
+
+ var file: FileAccess
+ var indexer_content := "@tool\nextends DialogicIndexer\n\n"
+ if mode != 1: # don't add event in Subsystem Only mode
+ indexer_content += """func _get_events() -> Array:
+ return [this_folder.path_join('event_"""+%NameEdit.text.to_snake_case()+""".gd')]\n\n"""
+ file = FileAccess.open(extensions_folder.path_join('event_'+%NameEdit.text.to_snake_case()+'.gd'), FileAccess.WRITE)
+ file.store_string(
+
+#region EXTENDED EVENT SCRIPT
+"""@tool
+extends DialogicEvent
+class_name Dialogic"""+%NameEdit.text.to_pascal_case()+"""Event
+
+# Define properties of the event here
+
+func _execute() -> void:
+ # This will execute when the event is reached
+ finish() # called to continue with the next event
+
+
+#region INITIALIZE
+################################################################################
+# Set fixed settings of this event
+func _init() -> void:
+ event_name = \""""+%NameEdit.text.capitalize()+"""\"
+ event_category = "Other"
+
+\n
+#endregion
+
+#region SAVING/LOADING
+################################################################################
+func get_shortcode() -> String:
+ return \""""+%NameEdit.text.to_snake_case()+"""\"
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ #"my_parameter" : {"property": "property", "default": "Default"},
+ }
+
+# You can alternatively overwrite these 3 functions: to_text(), from_text(), is_valid_event()
+#endregion
+
+
+#region EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ pass
+
+#endregion
+""")
+
+#endregion
+ if mode != 0: # don't add subsystem in event only mode
+ indexer_content += """func _get_subsystems() -> Array:
+ return [{'name':'"""+%NameEdit.text.to_pascal_case()+"""', 'script':this_folder.path_join('subsystem_"""+%NameEdit.text.to_snake_case()+""".gd')}]"""
+ file = FileAccess.open(extensions_folder.path_join('subsystem_'+%NameEdit.text.to_snake_case()+'.gd'), FileAccess.WRITE)
+ file.store_string(
+
+# region EXTENDED SUBSYSTEM SCRIPT
+"""extends DialogicSubsystem
+
+## Describe the subsystems purpose here.
+
+
+#region STATE
+####################################################################################################
+
+func clear_game_state(clear_flag:=Dialogic.ClearFlags.FULL_CLEAR) -> void:
+ pass
+
+func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void:
+ pass
+
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+# Add some useful methods here.
+
+#endregion
+""")
+ file = FileAccess.open(extensions_folder.path_join('index.gd'), FileAccess.WRITE)
+ file.store_string(indexer_content)
+
+ %ExtensionCreator.hide()
+ %CreateExtensionButton.show()
+
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
+ force_event_button_list_reload()
+
+
+
+func _on_reload_pressed() -> void:
+ DialogicUtil._update_autoload_subsystem_access()
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.tscn b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.tscn
new file mode 100644
index 0000000..264427b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_general.tscn
@@ -0,0 +1,283 @@
+[gd_scene load_steps=6 format=3 uid="uid://b873ho41sklv8"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/settings_general.gd" id="2"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_kqhx5"]
+[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="3_i7rug"]
+
+[sub_resource type="Image" id="Image_e1gle"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_4wgbv"]
+image = SubResource("Image_e1gle")
+
+[node name="General" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("2")
+
+[node name="PaletteTitle" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="SectionPaletteTitle" type="Label" parent="PaletteTitle"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Color Palette"
+
+[node name="HintTooltip" parent="PaletteTitle" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "These colors are used for the events."
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "These colors are used for the events."
+
+[node name="ResetColorsButton" type="Button" parent="PaletteTitle"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 0
+tooltip_text = "Reset Colors to default"
+icon = SubResource("ImageTexture_4wgbv")
+flat = true
+
+[node name="ScrollContainer" type="ScrollContainer" parent="."]
+layout_mode = 2
+horizontal_scroll_mode = 3
+vertical_scroll_mode = 0
+
+[node name="Colors" type="HBoxContainer" parent="ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="SectionBehaviourTitle" type="Label" parent="HBoxContainer2"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Layout Node Behaviour"
+
+[node name="HintTooltip" parent="HBoxContainer2" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "The layout scene configured in the Layout editor is automatically
+instanced when calling Dialogic.start(). Depending on your game,
+you might want it to be deleted after the dialogue, be hidden
+(as reinstancing often is wasting resources) or kept visible. "
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "The layout scene configured in the Layout editor is automatically
+instanced when calling Dialogic.start(). Depending on your game,
+you might want it to be deleted after the dialogue, be hidden
+(as reinstancing often is wasting resources) or kept visible. "
+
+[node name="HBoxContainer3" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer3"]
+layout_mode = 2
+text = "On timeline end"
+
+[node name="LayoutNodeEndBehaviour" type="OptionButton" parent="HBoxContainer3"]
+unique_name_in_owner = true
+layout_mode = 2
+item_count = 3
+selected = 0
+fit_to_longest_item = false
+popup/item_0/text = "Delete Layout Node"
+popup/item_0/id = 0
+popup/item_1/text = "Hide Layout Node"
+popup/item_1/id = 1
+popup/item_2/text = "Keep Layout Node"
+popup/item_2/id = 2
+
+[node name="HSeparator4" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer6" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer4" type="VBoxContainer" parent="HBoxContainer6"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HBoxContainer5" type="HBoxContainer" parent="HBoxContainer6/HBoxContainer4"]
+layout_mode = 2
+
+[node name="SectionExtensionsTitle" type="Label" parent="HBoxContainer6/HBoxContainer4/HBoxContainer5"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Extensions"
+
+[node name="HintTooltip" parent="HBoxContainer6/HBoxContainer4/HBoxContainer5" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "Configure where dialogic looks for custom modules.
+
+You will have to restart the project to see the change take action."
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "Configure where dialogic looks for custom modules.
+
+You will have to restart the project to see the change take action."
+
+[node name="Reload" type="Button" parent="HBoxContainer6/HBoxContainer4/HBoxContainer5"]
+layout_mode = 2
+text = "Reload"
+flat = true
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer6/HBoxContainer4"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer6/HBoxContainer4/HBoxContainer"]
+layout_mode = 2
+text = "Extensions folder"
+
+[node name="ExtensionsFolderPicker" parent="HBoxContainer6/HBoxContainer4/HBoxContainer" instance=ExtResource("3_i7rug")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder = "res://addons/dialogic_additions/Events"
+file_mode = 2
+resource_icon = SubResource("ImageTexture_4wgbv")
+
+[node name="VSeparator" type="VSeparator" parent="HBoxContainer6"]
+layout_mode = 2
+
+[node name="ExtensionsPanel" type="PanelContainer" parent="HBoxContainer6"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_type_variation = &"DialogicPanelA"
+
+[node name="VBox" type="VBoxContainer" parent="HBoxContainer6/ExtensionsPanel"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HBoxContainer6" type="HBoxContainer" parent="HBoxContainer6/ExtensionsPanel/VBox"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer6/ExtensionsPanel/VBox/HBoxContainer6"]
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+text = "Extension Creator "
+
+[node name="HintTooltip" parent="HBoxContainer6/ExtensionsPanel/VBox/HBoxContainer6" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "Use the Exension Creator to quickly setup custom modules!"
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "Use the Exension Creator to quickly setup custom modules!"
+
+[node name="CreateExtensionButton" type="Button" parent="HBoxContainer6/ExtensionsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Create New Extension"
+
+[node name="ExtensionCreator" type="VBoxContainer" parent="HBoxContainer6/ExtensionsPanel/VBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="ExtensionCreatorOptions" type="GridContainer" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator"]
+layout_mode = 2
+columns = 2
+
+[node name="NameLabel" type="Label" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator/ExtensionCreatorOptions"]
+layout_mode = 2
+text = "Name:"
+
+[node name="NameEdit" type="LineEdit" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator/ExtensionCreatorOptions"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "e.g. \"Print\", \"Item\", \"Door\", \"Quest\""
+
+[node name="ModeLabel" type="Label" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator/ExtensionCreatorOptions"]
+layout_mode = 2
+text = "Setup mode:"
+
+[node name="ExtensionMode" type="OptionButton" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator/ExtensionCreatorOptions"]
+unique_name_in_owner = true
+layout_mode = 2
+item_count = 4
+selected = 0
+popup/item_0/text = "Event only"
+popup/item_0/id = 0
+popup/item_1/text = "Event+Subsystem"
+popup/item_1/id = 1
+popup/item_2/text = "Subsystem only"
+popup/item_2/id = 2
+popup/item_3/text = "Complex"
+popup/item_3/id = 3
+
+[node name="SubmitExtensionButton" type="Button" parent="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Create"
+
+[node name="HSeparator2" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer7" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="TimerTitle" type="Label" parent="HBoxContainer7"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Timer processing"
+
+[node name="HintTooltip" parent="HBoxContainer7" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "Change whether dialogics timers process in physics_process (frame-rate independent) or process."
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "Change whether dialogics timers process in physics_process (frame-rate independent) or process."
+
+[node name="HBoxContainer4" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer4"]
+layout_mode = 2
+text = "Process timers in physics_process"
+
+[node name="PhysicsTimerButton" type="CheckBox" parent="HBoxContainer4"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator5" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="SectionSections" type="Label" parent="HBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Section Order"
+
+[node name="HintTooltip" parent="HBoxContainer" instance=ExtResource("2_kqhx5")]
+layout_mode = 2
+tooltip_text = "You can change the order of the event sections here. "
+texture = SubResource("ImageTexture_4wgbv")
+hint_text = "You can change the order of the event sections here. "
+
+[node name="SectionList" type="Tree" parent="."]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(150, 150)
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/button_margin = 0
+allow_reselect = true
+allow_rmb_select = true
+hide_folding = true
+hide_root = true
+drop_mode_flags = 1
+
+[connection signal="item_selected" from="HBoxContainer3/LayoutNodeEndBehaviour" to="." method="_on_layout_node_end_behaviour_item_selected"]
+[connection signal="pressed" from="HBoxContainer6/HBoxContainer4/HBoxContainer5/Reload" to="." method="_on_reload_pressed"]
+[connection signal="pressed" from="HBoxContainer6/ExtensionsPanel/VBox/CreateExtensionButton" to="." method="_on_create_extension_button_pressed"]
+[connection signal="pressed" from="HBoxContainer6/ExtensionsPanel/VBox/ExtensionCreator/SubmitExtensionButton" to="." method="_on_submit_extension_button_pressed"]
+[connection signal="button_clicked" from="SectionList" to="." method="_on_section_list_button_clicked"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.gd
new file mode 100644
index 0000000..008453e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.gd
@@ -0,0 +1,443 @@
+@tool
+extends DialogicSettingsPage
+
+
+func _get_title() -> String:
+ return "Modules"
+
+func _get_priority() -> int:
+ return 0
+
+func _is_feature_tab() -> bool:
+ return true
+
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+ %Refresh.icon = get_theme_icon("Loop", "EditorIcons")
+ %Search.right_icon = get_theme_icon("Search", "EditorIcons")
+
+ %Filter_Events.icon = get_theme_icon("Favorites", "EditorIcons")
+ %Filter_Subsystems.icon = get_theme_icon("Callable", "EditorIcons")
+ %Filter_Styles.icon = get_theme_icon("PopupMenu", "EditorIcons")
+ %Filter_EffectsAndModifiers.icon = get_theme_icon("RichTextEffect", "EditorIcons")
+ %Filter_Editors.icon = get_theme_icon("ConfirmationDialog", "EditorIcons")
+ %Filter_Settings.icon = get_theme_icon("PluginScript", "EditorIcons")
+ %Collapse.icon = get_theme_icon("CollapseTree", "EditorIcons")
+
+ %EventDefaultsPanel.add_theme_stylebox_override('panel', get_theme_stylebox("Background", "EditorStyles"))
+
+ %ExternalLink.icon = get_theme_icon("Help", "EditorIcons")
+
+
+func _refresh() -> void:
+ %EventDefaultsPanel.hide()
+ load_modules_tree()
+
+
+func _on_refresh_pressed() -> void:
+ DialogicUtil.get_indexers(true, true)
+ DialogicResourceUtil.update_event_cache()
+ load_modules_tree()
+
+
+func filters_updated(fake_arg:Variant) -> void:
+ load_modules_tree()
+
+
+func _on_collapse_toggled(button_pressed:bool) -> void:
+ for item in %Tree.get_root().get_children():
+ item.collapsed = button_pressed
+
+ if button_pressed:
+ %Collapse.icon = get_theme_icon("ExpandTree", "EditorIcons")
+ %Collapse.tooltip_text = "Expand All"
+ else:
+ %Collapse.icon = get_theme_icon("CollapseTree", "EditorIcons")
+ %Collapse.tooltip_text = "Collapse All"
+
+
+func _on_search_text_changed(new_text:String) -> void:
+ for filter in [%Filter_Events, %Filter_Subsystems, %Filter_Editors, %Filter_EffectsAndModifiers, %Filter_Settings, %Filter_Styles]:
+ filter.text = ""
+ filter.set_meta("counter", 0)
+
+ var hidden_events: Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+
+ for child in %Tree.get_root().get_children():
+ if new_text.to_lower() in child.get_text(0).to_lower() or new_text.is_empty():
+ for sub_child in child.get_children():
+ sub_child.visible = sub_child.get_meta('filter_button').button_pressed
+ sub_child.get_meta('filter_button').set_meta('counter', sub_child.get_meta('filter_button').get_meta('counter')+1)
+ sub_child.get_meta('filter_button').text = str(sub_child.get_meta('filter_button').get_meta('counter'))
+ child.visible = true
+ else:
+ for sub_child in child.get_children():
+ sub_child.visible = sub_child.get_meta('filter_button').button_pressed and new_text.to_lower() in sub_child.get_text(0).to_lower()
+
+ if new_text.to_lower() in sub_child.get_text(0).to_lower():
+ sub_child.get_meta('filter_button').set_meta('counter', sub_child.get_meta('filter_button').get_meta('counter')+1)
+ sub_child.get_meta('filter_button').text = str(sub_child.get_meta('filter_button').get_meta('counter'))
+
+ for i in range(child.get_button_count(0)):
+ child.erase_button(0, child.get_button_count(0)-1)
+ var any_visible := false
+ var counter := 0
+ for sub_child in child.get_children():
+ if sub_child.visible:
+ child.add_button(0, sub_child.get_icon(0), counter, false, sub_child.get_text(0))
+ if sub_child.get_metadata(0) and sub_child.get_metadata(0)['type'] == 'Event' and sub_child.get_metadata(0)['hidden']:
+ var color: Color = sub_child.get_icon_modulate(0)
+ color.a = 0.5
+ child.set_button_color(0, counter, color)
+ else:
+ child.set_button_color(0, counter, sub_child.get_icon_modulate(0))
+ counter += 1
+ any_visible = true
+ child.visible = any_visible
+
+
+
+func load_modules_tree() -> void:
+ %Tree.clear()
+ var root: TreeItem = %Tree.create_item()
+ var cached_events := DialogicResourceUtil.get_event_cache()
+ var hidden_events: Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+ var indexers := DialogicUtil.get_indexers()
+ for i in indexers:
+ var module_item: TreeItem = %Tree.create_item(root)
+ module_item.set_text(0, i.get_script().resource_path.trim_suffix('/index.gd').get_file())
+ module_item.set_metadata(0, {'type':'Module'})
+
+ # Events
+ for ev in i._get_events():
+ if not ResourceLoader.exists(ev):
+ continue
+ var event_item: TreeItem = %Tree.create_item(module_item)
+ event_item.set_icon(0, get_theme_icon("Favorites", "EditorIcons"))
+ for cached_event in cached_events:
+ if cached_event.get_script().resource_path == ev:
+ event_item.set_text(0, cached_event.event_name + " Event")
+ event_item.set_icon_modulate(0, cached_event.event_color)
+ var hidden: bool = cached_event.event_name in hidden_events
+ event_item.set_metadata(0, {'type':'Event', 'event':cached_event, 'hidden':hidden})
+ event_item.add_button(0, get_theme_icon("GuiVisibilityVisible", "EditorIcons"), 0, false, "Toggle Event Button Visibility")
+ if hidden:
+ event_item.set_button(0, 0, get_theme_icon("GuiVisibilityHidden", "EditorIcons"))
+ event_item.set_meta('filter_button', %Filter_Events)
+ event_item.visible = %Filter_Events.button_pressed
+
+ # Subsystems
+ for subsys in i._get_subsystems():
+ var subsys_item: TreeItem = %Tree.create_item(module_item)
+ subsys_item.set_icon(0, get_theme_icon("Callable", "EditorIcons"))
+ subsys_item.set_text(0, subsys.name + " Subsystem")
+ subsys_item.set_icon_modulate(0, get_theme_color("readonly_color", "Editor"))
+ subsys_item.set_metadata(0, {'type':'Subsystem', 'info':subsys})
+ subsys_item.set_meta('filter_button', %Filter_Subsystems)
+ subsys_item.visible = %Filter_Subsystems.button_pressed
+
+ # Style scenes
+ for style in i._get_layout_parts():
+ var style_item: TreeItem = %Tree.create_item(module_item)
+ style_item.set_icon(0, get_theme_icon("PopupMenu", "EditorIcons"))
+ style_item.set_text(0, style.name)
+ style_item.set_icon_modulate(0, get_theme_color("property_color_x", "Editor"))
+ style_item.set_metadata(0, {'type':'Style', 'info':style})
+ style_item.set_meta('filter_button', %Filter_Styles)
+ style_item.visible = %Filter_Styles.button_pressed
+
+ # Text Effects
+ for effect in i._get_text_effects():
+ var effect_item: TreeItem = %Tree.create_item(module_item)
+ effect_item.set_icon(0, get_theme_icon("RichTextEffect", "EditorIcons"))
+ effect_item.set_text(0, "Text effect ["+effect.command+"]")
+ effect_item.set_icon_modulate(0, get_theme_color("property_color_z", "Editor"))
+ effect_item.set_metadata(0, {'type':'Effect', 'info':effect})
+ effect_item.set_meta('filter_button', %Filter_EffectsAndModifiers)
+ effect_item.visible = %Filter_EffectsAndModifiers.button_pressed
+
+ # Text Modifiers
+ for mod in i._get_text_modifiers():
+ var mod_item: TreeItem = %Tree.create_item(module_item)
+ mod_item.set_icon(0, get_theme_icon("RichTextEffect", "EditorIcons"))
+ mod_item.set_text(0, mod.method.capitalize())
+ mod_item.set_icon_modulate(0, get_theme_color("property_color_z", "Editor"))
+ mod_item.set_metadata(0, {'type':'Modifier', 'info':mod})
+ mod_item.set_meta('filter_button', %Filter_EffectsAndModifiers)
+ mod_item.visible = %Filter_EffectsAndModifiers.button_pressed
+
+ # Settings
+ for settings in i._get_settings_pages():
+ var settings_item: TreeItem = %Tree.create_item(module_item)
+ settings_item.set_icon(0, get_theme_icon("PluginScript", "EditorIcons"))
+ settings_item.set_text(0, module_item.get_text(0) + " Settings")
+ settings_item.set_icon_modulate(0, get_theme_color("readonly_color", "Editor"))
+ settings_item.set_metadata(0, {'type':'Settings', 'info':settings})
+ settings_item.set_meta('filter_button', %Filter_Settings)
+ settings_item.visible = %Filter_Settings.button_pressed
+
+ # Editors
+ for editor in i._get_editors():
+ var editor_item: TreeItem = %Tree.create_item(module_item)
+ editor_item.set_icon(0, get_theme_icon("ConfirmationDialog", "EditorIcons"))
+ editor_item.set_text(0, editor.get_file().trim_suffix('.tscn').capitalize())
+ editor_item.set_icon_modulate(0, get_theme_color("readonly_color", "Editor"))
+ editor_item.set_metadata(0, {'type':'Editor', 'info':editor})
+ editor_item.set_meta('filter_button', %Filter_Editors)
+ editor_item.visible = %Filter_Editors.button_pressed
+
+ module_item.collapsed = %Collapse.button_pressed
+
+ _on_search_text_changed(%Search.text)
+ if %Tree.get_root().get_child_count(): %Tree.set_selected(%Tree.get_root().get_child(0), 0)
+
+
+func _on_tree_button_clicked(item:TreeItem, column:int, id:int, mouse_button_index:int) -> void:
+ match item.get_metadata(0)['type']:
+ 'Module':
+ item.collapsed = false
+ %Tree.set_selected(item.get_child(id), 0)
+ 'Event':
+ # Visibility item clicked
+ if id == 0:
+ var meta: Dictionary= item.get_metadata(0)
+ if meta['hidden']:
+ item.set_button(0, 0, get_theme_icon("GuiVisibilityVisible", "EditorIcons"))
+ item.get_parent().set_button_color(0, item.get_index(), item.get_icon_modulate(0))
+ if item == %Tree.get_selected():
+ %VisibilityToggle.button_pressed = true
+ else:
+ item.set_button(0, 0, get_theme_icon("GuiVisibilityHidden", "EditorIcons"))
+ var color: Color = item.get_icon_modulate(0)
+ color.a = 0.5
+ item.get_parent().set_button_color(0, item.get_index(), color)
+ if item == %Tree.get_selected():
+ %VisibilityToggle.button_pressed = false
+ meta['hidden'] = !meta['hidden']
+ item.set_metadata(0, meta)
+ change_event_visibility(meta['event'], !meta['hidden'])
+
+
+func _on_tree_item_selected() -> void:
+ var selected_item: TreeItem = %Tree.get_selected()
+
+ var metadata: Variant = selected_item.get_metadata(0)
+
+ %Title.text = selected_item.get_text(0)
+ %EventDefaultsPanel.hide()
+ %Icon.texture = null
+ %ExternalLink.hide()
+ %VisibilityToggle.hide()
+
+ if metadata is Dictionary:
+ match metadata.type:
+ 'Event':
+ %GeneralInfo.text = "Events can be used in timelines and do all kinds of things. They often interact with subsystems and dialogic nodes."
+
+ load_event_settings(metadata.event)
+ if %EventDefaults.get_child_count():
+ %EventDefaultsPanel.show()
+
+ if metadata.event.help_page_path:
+ %ExternalLink.show()
+ %ExternalLink.set_meta('url', metadata.event.help_page_path)
+ %Icon.texture = metadata.event._get_icon()
+ if !metadata.event.disable_editor_button:
+ %VisibilityToggle.show()
+ %VisibilityToggle.button_pressed = !metadata.event.event_name in DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+ if %VisibilityToggle.button_pressed:
+ %VisibilityToggle.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
+ else:
+ %VisibilityToggle.icon = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
+ # -------------------------------------------------
+ 'Subsystem':
+ %GeneralInfo.text = "Subsystems hold specialized functionality. They mostly manage communication between events and dialogic nodes. Often they provide handy methods that can be accessed by the user like this: Dialogic.Subsystem.a_method()."
+ # -------------------------------------------------
+ 'Effect':
+ %GeneralInfo.text = "Text effects can be used in text events. They will be executed once reached and can take a single argument."
+ # -------------------------------------------------
+ 'Modifier':
+ %GeneralInfo.text = "Modifiers can modify text from text events before it is shown."
+ # -------------------------------------------------
+ 'Style':
+ %GeneralInfo.text = "Style presets can be activated and modified in the Styles editor. They provide the design of the dialog interface in your game."
+ # -------------------------------------------------
+ 'Editor':
+ %GeneralInfo.text = "Editors provide a user interface for editing dialogic data."
+ # -------------------------------------------------
+ 'Settings':
+ %GeneralInfo.text = "Settings pages provide settings that are usually used by subsystems, events and dialogic nodes."
+ # -------------------------------------------------
+ '_':
+ %GeneralInfo.text = ""
+
+
+func _on_external_link_pressed() -> void:
+ if %ExternalLink.has_meta('url'):
+ OS.shell_open(%ExternalLink.get_meta('url'))
+
+
+func change_event_visibility(event:DialogicEvent, visibility:bool) -> void:
+ if event:
+ var list: Array= DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+ if visibility:
+ list.erase(event.event_name)
+ else:
+ list.append(event.event_name)
+ DialogicUtil.set_editor_setting('hidden_event_buttons', list)
+ force_event_button_list_update()
+
+
+func _on_visibility_toggle_toggled(button_pressed:bool) -> void:
+ change_event_visibility(%Tree.get_selected().get_metadata(0).event, button_pressed)
+
+ if button_pressed:
+ %VisibilityToggle.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
+ %Tree.get_selected().set_button(0, 0, get_theme_icon("GuiVisibilityVisible", "EditorIcons"))
+ %Tree.get_selected().get_parent().set_button_color(0, %Tree.get_selected().get_index(), %Tree.get_selected().get_icon_modulate(0))
+ else:
+ %VisibilityToggle.icon = get_theme_icon("GuiVisibilityHidden", "EditorIcons")
+ %Tree.get_selected().set_button(0, 0, get_theme_icon("GuiVisibilityHidden", "EditorIcons"))
+ var color: Color = %Tree.get_selected().get_icon_modulate(0)
+ color.a = 0.5
+ %Tree.get_selected().get_parent().set_button_color(0, %Tree.get_selected().get_index(), color)
+
+
+
+func force_event_button_list_update() -> void:
+ find_parent('EditorsManager').editors['Timeline'].node.get_node('%VisualEditor').load_event_buttons()
+
+################################################################################
+## EVENT DEFAULT SETTINGS
+################################################################################
+func load_event_settings(event:DialogicEvent) -> void:
+ for child in %EventDefaults.get_children():
+ child.queue_free()
+
+ var event_default_overrides: Dictionary = ProjectSettings.get_setting('dialogic/event_default_overrides', {})
+
+ var params := event.get_shortcode_parameters()
+ for prop in params:
+ var current_value: Variant = params[prop].default
+ if event_default_overrides.get(event.event_name, {}).has(params[prop].property):
+ current_value = event_default_overrides.get(event.event_name, {}).get(params[prop].property)
+
+ # Label
+ var label := Label.new()
+ label.text = prop.capitalize()
+ %EventDefaults.add_child(label)
+
+ var reset := Button.new()
+ reset.icon = get_theme_icon("Clear", "EditorIcons")
+ reset.flat = true
+
+ %EventDefaults.add_child(reset)
+
+ # Editing field
+ var editor_node: Node = null
+ match typeof(event.get(params[prop].property)):
+ TYPE_STRING:
+ editor_node = LineEdit.new()
+ editor_node.custom_minimum_size.x = 150
+ editor_node.text = str(current_value)
+ editor_node.text_changed.connect(_on_event_default_string_submitted.bind(params[prop].property))
+ TYPE_INT, TYPE_FLOAT:
+ if params[prop].has('suggestions'):
+ editor_node = OptionButton.new()
+ for i in params[prop].suggestions.call():
+ editor_node.add_item(i, int(params[prop].suggestions.call()[i].value))
+ editor_node.select(int(current_value))
+ editor_node.item_selected.connect(_on_event_default_option_selected.bind(editor_node, params[prop].property))
+ else:
+ editor_node = SpinBox.new()
+
+ editor_node.allow_greater = true
+ editor_node.allow_lesser = true
+ if typeof(event.get(params[prop].property)) == TYPE_INT:
+ editor_node.step = 1
+ else:
+ editor_node.step = 0.001
+
+ editor_node.value = float(current_value)
+ editor_node.value_changed.connect(_on_event_default_number_changed.bind(params[prop].property))
+
+ TYPE_VECTOR2:
+ editor_node = load("res://addons/dialogic/Editor/Events/Fields/field_vector2.tscn").instantiate()
+ editor_node.set_value(current_value)
+ editor_node.property_name = params[prop].property
+ editor_node.value_changed.connect(_on_event_default_value_changed)
+
+ TYPE_BOOL:
+ editor_node = CheckBox.new()
+ editor_node.button_pressed = bool(current_value)
+ editor_node.toggled.connect(_on_event_default_bool_toggled.bind(params[prop].property))
+
+ TYPE_ARRAY:
+ editor_node = load("res://addons/dialogic/Editor/Events/Fields/field_array.tscn").instantiate()
+ editor_node.set_value(current_value)
+ editor_node.property_name = params[prop].property
+ editor_node.value_changed.connect(_on_event_default_value_changed)
+
+ TYPE_DICTIONARY:
+ editor_node = load("res://addons/dialogic/Editor/Events/Fields/field_dictionary.tscn").instantiate()
+ editor_node.set_value(current_value)
+ editor_node.property_name = params[prop].property
+ editor_node.value_changed.connect(_on_event_default_value_changed)
+ %EventDefaults.add_child(editor_node)
+ reset.pressed.connect(reset_event_default_override.bind(prop, editor_node, params[prop].default))
+
+
+func set_event_default_override(prop:String, value:Variant) -> void:
+ var event_default_overrides: Dictionary = ProjectSettings.get_setting('dialogic/event_default_overrides', {})
+ var event: DialogicEvent = %Tree.get_selected().get_metadata(0).event
+
+ if not event_default_overrides.has(event.event_name):
+ event_default_overrides[event.event_name] = {}
+
+ event_default_overrides[event.event_name][prop] = value
+
+ ProjectSettings.set_setting('dialogic/event_default_overrides', event_default_overrides)
+
+
+func reset_event_default_override(prop:String, node:Node, default:Variant) -> void:
+ var event_default_overrides: Dictionary = ProjectSettings.get_setting('dialogic/event_default_overrides', {})
+ var event: DialogicEvent = %Tree.get_selected().get_metadata(0).event
+
+ if not event_default_overrides.has(event.event_name):
+ return
+
+ event_default_overrides[event.event_name].erase(prop)
+
+ ProjectSettings.set_setting('dialogic/event_default_overrides', event_default_overrides)
+
+ if node is CheckBox:
+ node.button_pressed = default
+ elif node is LineEdit:
+ node.text = default
+ elif node.has_method('set_value'):
+ node.set_value(default)
+ elif node is ColorPickerButton:
+ node.color = default
+ elif node is OptionButton:
+ node.select(default)
+ elif node is SpinBox:
+ node.value = default
+
+
+func _on_event_default_string_submitted(text:String, prop:String) -> void:
+ set_event_default_override(prop, text)
+
+func _on_event_default_option_selected(index:int, option_button:OptionButton, prop:String) -> void:
+ set_event_default_override(prop, option_button.get_item_id(index))
+
+func _on_event_default_number_changed(value:float, prop:String) -> void:
+ set_event_default_override(prop, value)
+
+func _on_event_default_value_changed(prop:String, value:Variant) -> void:
+ set_event_default_override(prop, value)
+
+func _on_event_default_bool_toggled(value:bool, prop:String) -> void:
+ set_event_default_override(prop, value)
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.tscn b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.tscn
new file mode 100644
index 0000000..e43a96a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_modules.tscn
@@ -0,0 +1,236 @@
+[gd_scene load_steps=7 format=3 uid="uid://o7ljiritpgap"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/settings_modules.gd" id="1_l2hk0"]
+
+[sub_resource type="Image" id="Image_570p8"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_lce2m"]
+image = SubResource("Image_570p8")
+
+[sub_resource type="Image" id="Image_ihhvm"]
+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_137g7"]
+image = SubResource("Image_ihhvm")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_315cl"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(1, 0.365, 0.365, 1)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_detail = 1
+
+[node name="ModuleManagement" type="HSplitContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_bottom = -157.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 0
+script = ExtResource("1_l2hk0")
+short_info = "Here you can manage modules:
+- change event defaults
+- hide events from the event list"
+
+[node name="Overview" type="VBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ScrollContainer" type="ScrollContainer" parent="Overview"]
+layout_mode = 2
+size_flags_horizontal = 3
+follow_focus = true
+horizontal_scroll_mode = 3
+vertical_scroll_mode = 0
+
+[node name="HBox" type="HBoxContainer" parent="Overview/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+alignment = 2
+
+[node name="Filter_Events" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Events"
+toggle_mode = true
+button_pressed = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Filter_Subsystems" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Subsystems"
+toggle_mode = true
+button_pressed = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Filter_EffectsAndModifiers" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Text Effects and Modifiers"
+toggle_mode = true
+button_pressed = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Filter_Styles" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Preset Style Scenes"
+toggle_mode = true
+button_pressed = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Filter_Settings" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Settings Pages"
+toggle_mode = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Filter_Editors" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Include Editors"
+toggle_mode = true
+text = "0"
+flat = true
+icon_alignment = 2
+
+[node name="Search" type="LineEdit" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+placeholder_text = "Search"
+clear_button_enabled = true
+
+[node name="Refresh" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Refresh"
+
+[node name="Collapse" type="Button" parent="Overview/ScrollContainer/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Collapse All"
+toggle_mode = true
+
+[node name="Tree" type="Tree" parent="Overview"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+allow_reselect = true
+hide_root = true
+
+[node name="Scroll" type="ScrollContainer" parent="."]
+show_behind_parent = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+size_flags_stretch_ratio = 0.75
+horizontal_scroll_mode = 3
+vertical_scroll_mode = 0
+
+[node name="Settings" type="VBoxContainer" parent="Scroll"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBox" type="HBoxContainer" parent="Scroll/Settings"]
+layout_mode = 2
+
+[node name="Icon" type="TextureRect" parent="Scroll/Settings/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+expand_mode = 3
+
+[node name="Title" type="Label" parent="Scroll/Settings/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicSubTitle"
+
+[node name="ExternalLink" type="Button" parent="Scroll/Settings/HBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+icon = SubResource("ImageTexture_lce2m")
+flat = true
+
+[node name="VisibilityToggle" type="Button" parent="Scroll/Settings/HBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+toggle_mode = true
+button_pressed = true
+icon = SubResource("ImageTexture_137g7")
+flat = true
+
+[node name="EventDefaultsPanel" type="PanelContainer" parent="Scroll/Settings"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_315cl")
+
+[node name="VBox" type="VBoxContainer" parent="Scroll/Settings/EventDefaultsPanel"]
+layout_mode = 2
+
+[node name="Title" type="Label" parent="Scroll/Settings/EventDefaultsPanel/VBox"]
+layout_mode = 2
+text = "Edit event defaults:"
+
+[node name="EventDefaults" type="GridContainer" parent="Scroll/Settings/EventDefaultsPanel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+columns = 3
+
+[node name="GeneralInfo" type="Label" parent="Scroll/Settings"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicHintText2"
+autowrap_mode = 3
+
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_Events" to="." method="filters_updated"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_Subsystems" to="." method="filters_updated"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_EffectsAndModifiers" to="." method="filters_updated"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_Styles" to="." method="filters_updated"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_Settings" to="." method="filters_updated"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Filter_Editors" to="." method="filters_updated"]
+[connection signal="text_changed" from="Overview/ScrollContainer/HBox/Search" to="." method="_on_search_text_changed"]
+[connection signal="pressed" from="Overview/ScrollContainer/HBox/Refresh" to="." method="_on_refresh_pressed"]
+[connection signal="toggled" from="Overview/ScrollContainer/HBox/Collapse" to="." method="_on_collapse_toggled"]
+[connection signal="button_clicked" from="Overview/Tree" to="." method="_on_tree_button_clicked"]
+[connection signal="item_selected" from="Overview/Tree" to="." method="_on_tree_item_selected"]
+[connection signal="pressed" from="Scroll/Settings/HBox/ExternalLink" to="." method="_on_external_link_pressed"]
+[connection signal="toggled" from="Scroll/Settings/HBox/VisibilityToggle" to="." method="_on_visibility_toggle_toggled"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_page.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_page.gd
new file mode 100644
index 0000000..2b7dd81
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_page.gd
@@ -0,0 +1,35 @@
+@tool
+extends Control
+class_name DialogicSettingsPage
+
+@export_multiline var short_info := ""
+
+## Called to get the title of the page
+func _get_title() -> String:
+ return name
+
+
+## Called to get the ordering of the page
+func _get_priority() -> int:
+ return 0
+
+
+## Called to know whether to put this in the features section
+func _is_feature_tab() -> bool:
+ return false
+
+
+## Called when the settings editor is opened
+func _refresh() -> void:
+ pass
+
+
+## Called before the settings editor closes (another editor is opened)
+## Can be used to safe stuff
+func _about_to_close() -> void:
+ pass
+
+
+## Return a section with information.
+func _get_info_section() -> Control:
+ return null
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.gd b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.gd
new file mode 100644
index 0000000..fe0714e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.gd
@@ -0,0 +1,660 @@
+@tool
+extends DialogicSettingsPage
+
+## Settings tab that allows enabeling and updating translation csv-files.
+
+
+enum TranslationModes {PER_PROJECT, PER_TIMELINE, NONE}
+enum SaveLocationModes {INSIDE_TRANSLATION_FOLDER, NEXT_TO_TIMELINE, NONE}
+
+var loading := false
+@onready var settings_editor: Control = find_parent('Settings')
+
+## The default CSV filename that contains the translations for character
+## properties.
+const DEFAULT_CHARACTER_CSV_NAME := "dialogic_character_translations.csv"
+## The default CSV filename that contains the translations for timelines.
+## Only used when all timelines are supposed to be translated in one file.
+const DEFAULT_TIMELINE_CSV_NAME := "dialogic_timeline_translations.csv"
+
+const DEFAULT_GLOSSARY_CSV_NAME := "dialogic_glossary_translations.csv"
+
+const _USED_LOCALES_SETTING := "dialogic/translation/locales"
+
+## Contains translation changes that were made during the last update.
+
+## Unique locales that will be set after updating the CSV files.
+var _unique_locales := []
+
+func _get_icon() -> Texture2D:
+ return get_theme_icon("Translation", "EditorIcons")
+
+
+func _is_feature_tab() -> bool:
+ return true
+
+
+func _ready() -> void:
+ %TransEnabled.toggled.connect(store_changes)
+ %OrigLocale.get_suggestions_func = get_locales
+ %OrigLocale.resource_icon = get_theme_icon("Translation", "EditorIcons")
+ %OrigLocale.value_changed.connect(store_changes)
+ %TestingLocale.get_suggestions_func = get_locales
+ %TestingLocale.resource_icon = get_theme_icon("Translation", "EditorIcons")
+ %TestingLocale.value_changed.connect(store_changes)
+ %TransFolderPicker.value_changed.connect(store_changes)
+ %AddSeparatorEnabled.toggled.connect(store_changes)
+
+ %SaveLocationMode.item_selected.connect(store_changes)
+ %TransMode.item_selected.connect(store_changes)
+
+ %UpdateCsvFiles.pressed.connect(_on_update_translations_pressed)
+ %UpdateCsvFiles.icon = get_theme_icon("Add", "EditorIcons")
+
+ %CollectTranslations.pressed.connect(collect_translations)
+ %CollectTranslations.icon = get_theme_icon("File", "EditorIcons")
+
+ %TransRemove.pressed.connect(_on_erase_translations_pressed)
+ %TransRemove.icon = get_theme_icon("Remove", "EditorIcons")
+
+ %UpdateConfirmationDialog.add_button("Keep old & Generate new", false, "generate_new")
+
+ %UpdateConfirmationDialog.custom_action.connect(_on_custom_action)
+
+ _verify_translation_file()
+
+
+func _on_custom_action(action: String) -> void:
+ if action == "generate_new":
+ update_csv_files()
+
+
+func _refresh() -> void:
+ loading = true
+
+ %TransEnabled.button_pressed = ProjectSettings.get_setting('dialogic/translation/enabled', false)
+ %TranslationSettings.visible = %TransEnabled.button_pressed
+ %OrigLocale.set_value(ProjectSettings.get_setting('dialogic/translation/original_locale', TranslationServer.get_tool_locale()))
+ %TransMode.select(ProjectSettings.get_setting('dialogic/translation/file_mode', 1))
+ %TransFolderPicker.set_value(ProjectSettings.get_setting('dialogic/translation/translation_folder', ''))
+ %TestingLocale.set_value(ProjectSettings.get_setting('internationalization/locale/test', ''))
+ %AddSeparatorEnabled.button_pressed = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
+
+ _verify_translation_file()
+
+ loading = false
+
+
+func store_changes(_fake_arg: Variant = null, _fake_arg2: Variant = null) -> void:
+ if loading:
+ return
+
+ _verify_translation_file()
+
+ ProjectSettings.set_setting('dialogic/translation/enabled', %TransEnabled.button_pressed)
+ %TranslationSettings.visible = %TransEnabled.button_pressed
+ ProjectSettings.set_setting('dialogic/translation/original_locale', %OrigLocale.current_value)
+ ProjectSettings.set_setting('dialogic/translation/file_mode', %TransMode.selected)
+ ProjectSettings.set_setting('dialogic/translation/translation_folder', %TransFolderPicker.current_value)
+ ProjectSettings.set_setting('internationalization/locale/test', %TestingLocale.current_value)
+ ProjectSettings.set_setting('dialogic/translation/save_mode', %SaveLocationMode.selected)
+ ProjectSettings.set_setting('dialogic/translation/add_separator', %AddSeparatorEnabled.button_pressed)
+ ProjectSettings.save()
+
+
+## Checks whether the translation folder path is required.
+## If it is, disables the "Update CSV files" button and shows a warning.
+##
+## The translation folder path is required when either of the following is true:
+## - The translation mode is set to "Per Project".
+## - The save location mode is set to "Inside Translation Folder".
+func _verify_translation_file() -> void:
+ var translation_folder: String = %TransFolderPicker.current_value
+ var file_mode: TranslationModes = %TransMode.selected
+
+ if file_mode == TranslationModes.PER_PROJECT:
+ %SaveLocationMode.disabled = true
+ else:
+ %SaveLocationMode.disabled = false
+
+ var valid_translation_folder := (!translation_folder.is_empty()
+ and DirAccess.dir_exists_absolute(translation_folder))
+
+ %UpdateCsvFiles.disabled = not valid_translation_folder
+
+ var status_message := ""
+
+ if not valid_translation_folder:
+ status_message += "⛔ Requires valid translation folder to translate character names"
+
+ if file_mode == TranslationModes.PER_PROJECT:
+ status_message += " and the project CSV file."
+ else:
+ status_message += "."
+
+ %StatusMessage.text = status_message
+
+
+func get_locales(_filter: String) -> Dictionary:
+ var suggestions := {}
+ suggestions['Default'] = {'value':'', 'tooltip':"Will use the fallback locale set in the project settings."}
+ suggestions[TranslationServer.get_tool_locale()] = {'value':TranslationServer.get_tool_locale()}
+
+ var used_locales: Array = ProjectSettings.get_setting(_USED_LOCALES_SETTING, TranslationServer.get_all_languages())
+
+ for locale: String in used_locales:
+ var language_name := TranslationServer.get_language_name(locale)
+
+ # Invalid locales return an empty String.
+ if language_name.is_empty():
+ continue
+
+ suggestions[locale] = { 'value': locale, 'tooltip': language_name }
+
+ return suggestions
+
+
+func _on_update_translations_pressed() -> void:
+ var save_mode: SaveLocationModes = %SaveLocationMode.selected
+ var file_mode: TranslationModes = %TransMode.selected
+ var translation_folder: String = %TransFolderPicker.current_value
+
+ var old_save_mode: SaveLocationModes = ProjectSettings.get_setting('dialogic/translation/intern/save_mode', save_mode)
+ var old_file_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/intern/file_mode', file_mode)
+ var old_translation_folder: String = ProjectSettings.get_setting('dialogic/translation/intern/translation_folder', translation_folder)
+
+ if (old_save_mode == save_mode
+ and old_file_mode == file_mode
+ and old_translation_folder == translation_folder):
+ update_csv_files()
+ return
+
+ %UpdateConfirmationDialog.popup_centered()
+
+
+## Used by the dialog to inform that the settings were changed.
+func _delete_and_update() -> void:
+ erase_translations()
+ update_csv_files()
+
+
+## Creates or updates the glossary CSV files.
+func _handle_glossary_translation(
+ csv_data: CsvUpdateData,
+ save_location_mode: SaveLocationModes,
+ translation_mode: TranslationModes,
+ translation_folder_path: String,
+ orig_locale: String) -> void:
+
+ var glossary_csv: DialogicCsvFile = null
+ var glossary_paths: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
+ var add_separator_lines: bool = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
+
+ for glossary_path: String in glossary_paths:
+
+ if glossary_csv == null:
+ var csv_name := ""
+
+ # Get glossary CSV file name.
+ match translation_mode:
+ TranslationModes.PER_PROJECT:
+ csv_name = DEFAULT_GLOSSARY_CSV_NAME
+
+ TranslationModes.PER_TIMELINE:
+ var glossary_name: String = glossary_path.trim_suffix('.tres')
+ var path_parts := glossary_name.split("/")
+ var file_name := path_parts[-1]
+ csv_name = "dialogic_" + file_name + '_translation.csv'
+
+ var glossary_csv_path := ""
+ # Get glossary CSV file path.
+ match save_location_mode:
+ SaveLocationModes.INSIDE_TRANSLATION_FOLDER:
+ glossary_csv_path = translation_folder_path.path_join(csv_name)
+
+ SaveLocationModes.NEXT_TO_TIMELINE:
+ glossary_csv_path = glossary_path.get_base_dir().path_join(csv_name)
+
+ # Create or update glossary CSV file.
+ glossary_csv = DialogicCsvFile.new(glossary_csv_path, orig_locale, add_separator_lines)
+
+ if (glossary_csv.is_new_file):
+ csv_data.new_glossaries += 1
+ else:
+ csv_data.updated_glossaries += 1
+
+ var glossary: DialogicGlossary = load(glossary_path)
+ glossary_csv.collect_lines_from_glossary(glossary)
+ glossary_csv.add_translation_keys_to_glossary(glossary)
+ ResourceSaver.save(glossary)
+
+ #If per-file mode is used, save this csv and begin a new one
+ if translation_mode == TranslationModes.PER_TIMELINE:
+ glossary_csv.update_csv_file_on_disk()
+ glossary_csv = null
+
+ # If a Per-Project glossary is still open, we need to save it.
+ if glossary_csv != null:
+ glossary_csv.update_csv_file_on_disk()
+ glossary_csv = null
+
+
+## Keeps information about the amount of new and updated CSV rows and what
+## resources were populated with translation IDs.
+## The final data can be used to display a status message.
+class CsvUpdateData:
+ var new_events := 0
+ var updated_events := 0
+
+ var new_timelines := 0
+ var updated_timelines := 0
+
+ var new_names := 0
+ var updated_names := 0
+
+ var new_glossaries := 0
+ var updated_glossaries := 0
+
+ var new_glossary_entries := 0
+ var updated_glossary_entries := 0
+
+
+func update_csv_files() -> void:
+ _unique_locales = []
+ var orig_locale: String = ProjectSettings.get_setting('dialogic/translation/original_locale', '').strip_edges()
+ var save_location_mode: SaveLocationModes = ProjectSettings.get_setting('dialogic/translation/save_mode', SaveLocationModes.NEXT_TO_TIMELINE)
+ var translation_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/file_mode', TranslationModes.PER_PROJECT)
+ var translation_folder_path: String = ProjectSettings.get_setting('dialogic/translation/translation_folder', 'res://')
+ var add_separator_lines: bool = ProjectSettings.get_setting('dialogic/translation/add_separator', false)
+
+ var csv_data := CsvUpdateData.new()
+
+ if orig_locale.is_empty():
+ orig_locale = ProjectSettings.get_setting('internationalization/locale/fallback')
+
+ ProjectSettings.set_setting('dialogic/translation/intern/save_mode', save_location_mode)
+ ProjectSettings.set_setting('dialogic/translation/intern/file_mode', translation_mode)
+ ProjectSettings.set_setting('dialogic/translation/intern/translation_folder', translation_folder_path)
+
+ var current_timeline := _close_active_timeline()
+
+ var csv_per_project: DialogicCsvFile = null
+ var per_project_csv_path := translation_folder_path.path_join(DEFAULT_TIMELINE_CSV_NAME)
+
+ if translation_mode == TranslationModes.PER_PROJECT:
+ csv_per_project = DialogicCsvFile.new(per_project_csv_path, orig_locale, add_separator_lines)
+
+ if (csv_per_project.is_new_file):
+ csv_data.new_timelines += 1
+ else:
+ csv_data.updated_timelines += 1
+
+ # Iterate over all timelines.
+ # Create or update CSV files.
+ # Transform the timeline into translatable lines and collect into the CSV file.
+ for timeline_path: String in DialogicResourceUtil.list_resources_of_type('.dtl'):
+ var csv_file: DialogicCsvFile = csv_per_project
+
+ # Swap the CSV file to the Per Timeline one.
+ if translation_mode == TranslationModes.PER_TIMELINE:
+ var per_timeline_path: String = timeline_path.trim_suffix('.dtl')
+ var path_parts := per_timeline_path.split("/")
+ var timeline_name: String = path_parts[-1]
+
+ # Adjust the file path to the translation location mode.
+ if save_location_mode == SaveLocationModes.INSIDE_TRANSLATION_FOLDER:
+ var prefixed_timeline_name := "dialogic_" + timeline_name
+ per_timeline_path = translation_folder_path.path_join(prefixed_timeline_name)
+
+
+ per_timeline_path += '_translation.csv'
+ csv_file = DialogicCsvFile.new(per_timeline_path, orig_locale, false)
+ csv_data.new_timelines += 1
+
+ # Load and process timeline, turn events into resources.
+ var timeline: DialogicTimeline = load(timeline_path)
+
+ if timeline.events.size() == 0:
+ print_rich("[color=yellow]Empty timeline, skipping: " + timeline_path + "[/color]")
+ continue
+
+ timeline.process()
+
+ # Collect timeline into CSV.
+ csv_file.collect_lines_from_timeline(timeline)
+
+ # in case new translation_id's were added, we save the timeline again
+ timeline.set_meta("timeline_not_saved", true)
+ ResourceSaver.save(timeline, timeline_path)
+
+ if translation_mode == TranslationModes.PER_TIMELINE:
+ csv_file.update_csv_file_on_disk()
+
+ csv_data.new_events += csv_file.new_rows
+ csv_data.updated_events += csv_file.updated_rows
+
+ _handle_glossary_translation(
+ csv_data,
+ save_location_mode,
+ translation_mode,
+ translation_folder_path,
+ orig_locale
+ )
+
+ _handle_character_names(
+ csv_data,
+ orig_locale,
+ translation_folder_path,
+ add_separator_lines
+ )
+
+ if translation_mode == TranslationModes.PER_PROJECT:
+ csv_per_project.update_csv_file_on_disk()
+
+ _silently_open_timeline(current_timeline)
+
+ # Trigger reimport.
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
+
+ var status_message := "Events created {new_events} found {updated_events}
+ Names created {new_names} found {updated_names}
+ CSVs created {new_timelines} found {updated_timelines}
+ Glossary created {new_glossaries} found {updated_glossaries}
+ Entries created {new_glossary_entries} found {updated_glossary_entries}"
+
+ var status_message_args := {
+ 'new_events': csv_data.new_events,
+ 'updated_events': csv_data.updated_events,
+ 'new_timelines': csv_data.new_timelines,
+ 'updated_timelines': csv_data.updated_timelines,
+ 'new_glossaries': csv_data.new_glossaries,
+ 'updated_glossaries': csv_data.updated_glossaries,
+ 'new_names': csv_data.new_names,
+ 'updated_names': csv_data.updated_names,
+ 'new_glossary_entries': csv_data.new_glossary_entries,
+ 'updated_glossary_entries': csv_data.updated_glossary_entries,
+ }
+
+ %StatusMessage.text = status_message.format(status_message_args)
+ ProjectSettings.set_setting(_USED_LOCALES_SETTING, _unique_locales)
+
+
+## Iterates over all character resource files and creates or updates CSV files
+## that contain the translations for character properties.
+## This will save each character resource file to disk.
+func _handle_character_names(
+ csv_data: CsvUpdateData,
+ original_locale: String,
+ translation_folder_path: String,
+ add_separator_lines: bool) -> void:
+ var names_csv_path := translation_folder_path.path_join(DEFAULT_CHARACTER_CSV_NAME)
+ var character_name_csv: DialogicCsvFile = DialogicCsvFile.new(names_csv_path,
+ original_locale,
+ add_separator_lines
+ )
+
+ var all_characters := {}
+
+ for character_path: String in DialogicResourceUtil.list_resources_of_type('.dch'):
+ var character: DialogicCharacter = load(character_path)
+
+ if character._translation_id.is_empty():
+ csv_data.new_names += 1
+
+ else:
+ csv_data.updated_names += 1
+
+ var translation_id := character.get_set_translation_id()
+ all_characters[translation_id] = character
+
+ ResourceSaver.save(character)
+
+ character_name_csv.collect_lines_from_characters(all_characters)
+ character_name_csv.update_csv_file_on_disk()
+
+
+func collect_translations() -> void:
+ var translation_files := []
+ var translation_mode: TranslationModes = ProjectSettings.get_setting('dialogic/translation/file_mode', TranslationModes.PER_PROJECT)
+
+ if translation_mode == TranslationModes.PER_TIMELINE:
+
+ for timeline_path: String in DialogicResourceUtil.list_resources_of_type('.translation'):
+
+ for file: String in DialogicUtil.listdir(timeline_path.get_base_dir()):
+ file = timeline_path.get_base_dir().path_join(file)
+
+ if file.ends_with('.translation'):
+
+ if not file in translation_files:
+ translation_files.append(file)
+
+ if translation_mode == TranslationModes.PER_PROJECT:
+ var translation_folder: String = ProjectSettings.get_setting('dialogic/translation/translation_folder', 'res://')
+
+ for file: String in DialogicUtil.listdir(translation_folder):
+ file = translation_folder.path_join(file)
+
+ if file.ends_with('.translation'):
+
+ if not file in translation_files:
+ translation_files.append(file)
+
+ var all_translation_files: Array = ProjectSettings.get_setting('internationalization/locale/translations', [])
+ var orig_file_amount := len(all_translation_files)
+
+ # This array keeps track of valid translation file paths.
+ var found_file_paths := []
+ var removed_translation_files := 0
+
+ for file_path: String in translation_files:
+ # If the file path is not valid, we must clean it up.
+ if ResourceLoader.exists(file_path):
+ found_file_paths.append(file_path)
+ else:
+ removed_translation_files += 1
+ continue
+
+ if not file_path in all_translation_files:
+ all_translation_files.append(file_path)
+
+ var path_without_suffix := file_path.trim_suffix('.translation')
+ var locale_part := path_without_suffix.split(".")[-1]
+ _collect_locale(locale_part)
+
+
+ var valid_translation_files := PackedStringArray(all_translation_files)
+ ProjectSettings.set_setting('internationalization/locale/translations', valid_translation_files)
+ ProjectSettings.save()
+
+ %StatusMessage.text = (
+ "Added translation files: " + str(len(all_translation_files)-orig_file_amount)
+ + "\nRemoved translation files: " + str(removed_translation_files)
+ + "\nTotal translation files: " + str(len(all_translation_files)))
+
+
+func _on_erase_translations_pressed() -> void:
+ %EraseConfirmationDialog.popup_centered()
+
+
+## Deletes translation files generated by [param csv_name].
+## The [param csv_name] may not contain the file extension (.csv).
+##
+## Returns a vector, value 1 is amount of deleted translation files.
+## Value
+func delete_translations_files(translation_files: Array, csv_name: String) -> int:
+ var deleted_files := 0
+
+ for file_path: String in DialogicResourceUtil.list_resources_of_type('.translation'):
+ var base_name: String = file_path.get_basename()
+ var path_parts := base_name.split("/")
+ var translation_name: String = path_parts[-1]
+
+ if translation_name.begins_with(csv_name):
+
+ if OK == DirAccess.remove_absolute(file_path):
+ var project_translation_file_index := translation_files.find(file_path)
+
+ if project_translation_file_index > -1:
+ translation_files.remove_at(project_translation_file_index)
+
+ deleted_files += 1
+ print_rich("[color=green]Deleted translation file: " + file_path + "[/color]")
+ else:
+ print_rich("[color=yellow]Failed to delete translation file: " + file_path + "[/color]")
+
+
+ return deleted_files
+
+
+## Iterates over all timelines and deletes their CSVs and timeline
+## translation IDs.
+## Deletes the Per-Project CSV file and the character name CSV file.
+func erase_translations() -> void:
+ var files: PackedStringArray = ProjectSettings.get_setting('internationalization/locale/translations', [])
+ var translation_files := Array(files)
+ ProjectSettings.set_setting(_USED_LOCALES_SETTING, [])
+
+ var deleted_csv_files := 0
+ var deleted_translation_files := 0
+ var cleaned_timelines := 0
+ var cleaned_characters := 0
+ var cleaned_events := 0
+ var cleaned_glossaries := 0
+
+ var current_timeline := _close_active_timeline()
+
+ # Delete all Dialogic CSV files and their translation files.
+ for csv_path: String in DialogicResourceUtil.list_resources_of_type(".csv"):
+ var csv_path_parts: PackedStringArray = csv_path.split("/")
+ var csv_name: String = csv_path_parts[-1].trim_suffix(".csv")
+
+ # Handle Dialogic CSVs only.
+ if not csv_name.begins_with("dialogic_"):
+ continue
+
+ # Delete the CSV file.
+ if OK == DirAccess.remove_absolute(csv_path):
+ deleted_csv_files += 1
+ print_rich("[color=green]Deleted CSV file: " + csv_path + "[/color]")
+
+ deleted_translation_files += delete_translations_files(translation_files, csv_name)
+ else:
+ print_rich("[color=yellow]Failed to delete CSV file: " + csv_path + "[/color]")
+
+ # Clean timelines.
+ for timeline_path: String in DialogicResourceUtil.list_resources_of_type(".dtl"):
+
+ # Process the timeline.
+ var timeline: DialogicTimeline = load(timeline_path)
+ timeline.process()
+ cleaned_timelines += 1
+
+ # Remove event translation IDs.
+ for event: DialogicEvent in timeline.events:
+
+ if event._translation_id and not event._translation_id.is_empty():
+ event.remove_translation_id()
+ event.update_text_version()
+ cleaned_events += 1
+
+ if "character" in event:
+ # Remove character translation IDs.
+ var character: DialogicCharacter = event.character
+
+ if character != null and not character._translation_id.is_empty():
+ character.remove_translation_id()
+ cleaned_characters += 1
+
+ timeline.set_meta("timeline_not_saved", true)
+ ResourceSaver.save(timeline, timeline_path)
+
+ _erase_glossary_translation_ids()
+ _erase_character_name_translation_ids()
+
+ ProjectSettings.set_setting('dialogic/translation/id_counter', 16)
+ ProjectSettings.set_setting('internationalization/locale/translations', PackedStringArray(translation_files))
+ ProjectSettings.save()
+
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
+
+ var status_message := "Timelines cleaned {cleaned_timelines}
+ Events cleaned {cleaned_events}
+ Characters cleaned {cleaned_characters}
+ Glossaries cleaned {cleaned_glossaries}
+
+ CSVs erased {erased_csv_files}
+ Translations erased {erased_translation_files}"
+
+ var status_message_args := {
+ 'cleaned_timelines': cleaned_timelines,
+ 'cleaned_characters': cleaned_characters,
+ 'cleaned_events': cleaned_events,
+ 'cleaned_glossaries': cleaned_glossaries,
+ 'erased_csv_files': deleted_csv_files,
+ 'erased_translation_files': deleted_translation_files,
+ }
+
+ _silently_open_timeline(current_timeline)
+
+ # Trigger reimport.
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources()
+
+ # Clear the internal settings.
+ ProjectSettings.clear('dialogic/translation/intern/save_mode')
+ ProjectSettings.clear('dialogic/translation/intern/file_mode')
+ ProjectSettings.clear('dialogic/translation/intern/translation_folder')
+
+ _verify_translation_file()
+ %StatusMessage.text = status_message.format(status_message_args)
+
+
+func _erase_glossary_translation_ids() -> void:
+ # Clean glossary.
+ var glossary_paths: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
+
+ for glossary_path: String in glossary_paths:
+ var glossary: DialogicGlossary = load(glossary_path)
+ glossary.remove_translation_id()
+ glossary.remove_entry_translation_ids()
+ glossary.clear_translation_keys()
+ ResourceSaver.save(glossary, glossary_path)
+ print_rich("[color=green]Cleaned up glossary file: " + glossary_path + "[/color]")
+
+
+func _erase_character_name_translation_ids() -> void:
+ for character_path: String in DialogicResourceUtil.list_resources_of_type('.dch'):
+ var character: DialogicCharacter = load(character_path)
+
+ character.remove_translation_id()
+ ResourceSaver.save(character)
+
+
+## Closes the current timeline in the Dialogic Editor and returns the timeline
+## as a resource.
+## If no timeline has been opened, returns null.
+func _close_active_timeline() -> Resource:
+ var timeline_node: DialogicEditor = settings_editor.editors_manager.editors['Timeline']['node']
+ # We will close this timeline to ensure it will properly update.
+ # By saving this reference, we can open it again.
+ var current_timeline := timeline_node.current_resource
+ # Clean the current editor, this will also close the timeline.
+ settings_editor.editors_manager.clear_editor(timeline_node)
+
+ return current_timeline
+
+
+## Opens the timeline resource into the Dialogic Editor.
+## If the timeline is null, does nothing.
+func _silently_open_timeline(timeline_to_open: Resource) -> void:
+ if timeline_to_open != null:
+ settings_editor.editors_manager.edit_resource(timeline_to_open, true, true)
+
+
+## Checks [param locale] for unique locales that have not been added
+## to the [_unique_locales] array yet.
+func _collect_locale(locale: String) -> void:
+ if _unique_locales.has(locale):
+ return
+
+ _unique_locales.append(locale)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.tscn b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.tscn
new file mode 100644
index 0000000..61fca18
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Settings/settings_translation.tscn
@@ -0,0 +1,368 @@
+[gd_scene load_steps=7 format=3 uid="uid://chpb1mj03xjxv"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/settings_translation.gd" id="1_dvmyi"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_k2lou"]
+[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3_dq4j2"]
+[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="4_kvsma"]
+
+[sub_resource type="Image" id="Image_g2hic"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_xbph7"]
+image = SubResource("Image_g2hic")
+
+[node name="Translations" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -101.0
+offset_bottom = 102.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_dvmyi")
+
+[node name="HBox" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Basics" type="VBoxContainer" parent="HBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Title" type="Label" parent="HBox/Basics"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Basics"
+
+[node name="VBox4" type="HBoxContainer" parent="HBox/Basics"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HBox/Basics/VBox4"]
+layout_mode = 2
+text = "Enable translations"
+
+[node name="TransEnabled" type="CheckBox" parent="HBox/Basics/VBox4"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator5" type="VSeparator" parent="HBox"]
+layout_mode = 2
+
+[node name="Testing" type="VBoxContainer" parent="HBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Title2" type="Label" parent="HBox/Testing"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Testing"
+
+[node name="VBox3" type="HBoxContainer" parent="HBox/Testing"]
+layout_mode = 2
+
+[node name="Label3" type="Label" parent="HBox/Testing/VBox3"]
+layout_mode = 2
+text = "Testing locale"
+
+[node name="HintTooltip8" parent="HBox/Testing/VBox3" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Change this locale to test your game in a different language (only in-editor).
+Equivalent of the testing local project setting. "
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Change this locale to test your game in a different language (only in-editor).
+Equivalent of the testing local project setting.
+
+Update dropdown list via \"Collect Translation\"."
+
+[node name="TestingLocale" parent="HBox/Testing/VBox3" instance=ExtResource("3_dq4j2")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator4" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="TranslationSettings" type="HBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="TranslationSettings"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="SettingsTitle" type="Label" parent="TranslationSettings/VBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Settings"
+
+[node name="Grid" type="GridContainer" parent="TranslationSettings/VBoxContainer"]
+layout_mode = 2
+columns = 2
+
+[node name="VBox" type="HBoxContainer" parent="TranslationSettings/VBoxContainer/Grid"]
+layout_mode = 2
+
+[node name="Label3" type="Label" parent="TranslationSettings/VBoxContainer/Grid/VBox"]
+layout_mode = 2
+text = "Default locale"
+
+[node name="HintTooltip" parent="TranslationSettings/VBoxContainer/Grid/VBox" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "The locale of the language your timelines are written in."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "The locale of the language your timelines are written in."
+
+[node name="OrigLocale" parent="TranslationSettings/VBoxContainer/Grid" instance=ExtResource("3_dq4j2")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="TransFile" type="HBoxContainer" parent="TranslationSettings/VBoxContainer/Grid"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="TranslationSettings/VBoxContainer/Grid/TransFile"]
+layout_mode = 2
+text = "Translation folder"
+
+[node name="HintTooltip3" parent="TranslationSettings/VBoxContainer/Grid/TransFile" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Choose a folder to let Dialogic save CSV files in.
+Also used when saving \"Inside Translation Folder\""
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Choose a folder to let Dialogic save CSV files in.
+Also used when saving \"Inside Translation Folder\""
+
+[node name="TransFolderPicker" parent="TranslationSettings/VBoxContainer/Grid" instance=ExtResource("4_kvsma")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+file_mode = 2
+
+[node name="VBox2" type="HBoxContainer" parent="TranslationSettings/VBoxContainer/Grid"]
+layout_mode = 2
+
+[node name="OutputModeLabel" type="Label" parent="TranslationSettings/VBoxContainer/Grid/VBox2"]
+layout_mode = 2
+text = "Output mode"
+
+[node name="OutputModeTooltip" parent="TranslationSettings/VBoxContainer/Grid/VBox2" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Decides how many CSV files will be created.
+
+• \"Per Type\": Uses one CSV file for each type of resource: Timelines, characters, and glossaries.
+For example, 10 timelines will be combined into 1 CSV file.
+
+• \"Per File\": Uses one CSV file for each resource file.
+For example, 10 timelines will result in 10 CSV files.
+
+The \"Per File\" option utilises \"Output location\", in contrast, the \"Per Type\" will always use the Translation folder."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Decides how many CSV files will be created.
+
+• \"Per Type\": Uses one CSV file for each type of resource: Timelines, characters, and glossaries.
+For example, 10 timelines will be combined into 1 CSV file.
+
+• \"Per File\": Uses one CSV file for each resource file.
+For example, 10 timelines will result in 10 CSV files.
+
+The \"Per File\" option utilises \"Output location\", in contrast, the \"Per Type\" will always use the Translation folder."
+
+[node name="TransMode" type="OptionButton" parent="TranslationSettings/VBoxContainer/Grid"]
+unique_name_in_owner = true
+layout_mode = 2
+item_count = 2
+selected = 0
+popup/item_0/text = "Per Type"
+popup/item_0/id = 0
+popup/item_1/text = "Per File"
+popup/item_1/id = 1
+
+[node name="OutputLocation" type="HBoxContainer" parent="TranslationSettings/VBoxContainer/Grid"]
+layout_mode = 2
+
+[node name="OutputLocationLabel" type="Label" parent="TranslationSettings/VBoxContainer/Grid/OutputLocation"]
+layout_mode = 2
+text = "Output location"
+
+[node name="OutputLocationTooltip" parent="TranslationSettings/VBoxContainer/Grid/OutputLocation" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Decides where to save the generated CSV files.
+
+• \"Inside Translation Folder\": Uses the \"Translation folder\".
+
+• \"Next To Timeline\": Places them in the resource type's folder.
+
+This button requires the \"Per File\" Output mode.
+A resource type can be: Timelines, characters, and glossaries."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Decides where to save the generated CSV files.
+
+• \"Inside Translation Folder\": Uses the \"Translation folder\".
+
+• \"Next To Timeline\": Places them in the resource type's folder.
+
+This button requires the \"Per File\" Output mode.
+A resource type can be: Timelines, characters, and glossaries."
+
+[node name="SaveLocationMode" type="OptionButton" parent="TranslationSettings/VBoxContainer/Grid"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+item_count = 2
+selected = 0
+popup/item_0/text = "Inside Translation Folder"
+popup/item_0/id = 0
+popup/item_1/text = "Next to File"
+popup/item_1/id = 1
+
+[node name="Control" type="Control" parent="TranslationSettings/VBoxContainer/Grid"]
+visible = false
+layout_mode = 2
+
+[node name="AddSeparatorHBox" type="HBoxContainer" parent="TranslationSettings/VBoxContainer/Grid"]
+layout_mode = 2
+
+[node name="AddSeparatorLabel" type="Label" parent="TranslationSettings/VBoxContainer/Grid/AddSeparatorHBox"]
+layout_mode = 2
+text = "Add Separator Lines"
+
+[node name="HintAddSeparatorEnabled" parent="TranslationSettings/VBoxContainer/Grid/AddSeparatorHBox" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Adds an empty line into per-project CSVs to differentiate between sections.
+
+For example, when a new glossary item or timeline starts, an empty line will be added."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Adds an empty line into per-project CSVs to differentiate between sections.
+
+For example, when a new glossary item or timeline starts, an empty line will be added."
+
+[node name="AddSeparatorEnabled" type="CheckBox" parent="TranslationSettings/VBoxContainer/Grid"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator6" type="VSeparator" parent="TranslationSettings"]
+layout_mode = 2
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="TranslationSettings"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="TranslationSettings/VBoxContainer2"]
+layout_mode = 2
+
+[node name="Title3" type="Label" parent="TranslationSettings/VBoxContainer2/HBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Actions"
+
+[node name="Actions" type="GridContainer" parent="TranslationSettings/VBoxContainer2"]
+layout_mode = 2
+columns = 2
+
+[node name="UpdateCsvFiles" type="Button" parent="TranslationSettings/VBoxContainer2/Actions"]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+text = "Update CSV files"
+icon = SubResource("ImageTexture_xbph7")
+
+[node name="HintTooltip5" parent="TranslationSettings/VBoxContainer2/Actions" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "This button will scan all timelines and generate or update their CSV files.
+
+A Dialogic CSV file will be prefixed with \"dialogic_\".
+
+This action will be disabled if the \"Translation folder\" is missing or has an invalid path."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "This button will scan all timelines and generate or update their CSV files.
+
+A Dialogic CSV file will be prefixed with \"dialogic_\".
+
+This action will be disabled if the \"Translation folder\" is missing or has an invalid path."
+
+[node name="CollectTranslations" type="Button" parent="TranslationSettings/VBoxContainer2/Actions"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Collect translations"
+icon = SubResource("ImageTexture_xbph7")
+
+[node name="HintTooltip6" parent="TranslationSettings/VBoxContainer2/Actions" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Godot imports CSV files as \".translation\" files.
+This buttons adds them to \"Project Settings -> Localization\".
+"
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Godot imports CSV files as \".translation\" files.
+This buttons adds them to \"Project Settings -> Localization\".
+"
+
+[node name="AspectRatioContainer2" type="AspectRatioContainer" parent="TranslationSettings/VBoxContainer2/Actions"]
+custom_minimum_size = Vector2(0, 31)
+layout_mode = 2
+
+[node name="AspectRatioContainer" type="AspectRatioContainer" parent="TranslationSettings/VBoxContainer2/Actions"]
+custom_minimum_size = Vector2(0, 31)
+layout_mode = 2
+
+[node name="TransRemove" type="Button" parent="TranslationSettings/VBoxContainer2/Actions"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Remove translations"
+icon = SubResource("ImageTexture_xbph7")
+
+[node name="HintTooltip7" parent="TranslationSettings/VBoxContainer2/Actions" instance=ExtResource("2_k2lou")]
+layout_mode = 2
+tooltip_text = "Be very careful with this button!
+
+It will try to delete any \".csv\" and \".translation\" files that are related to Dialogic.
+CSV and translation files prefixed with \"dialogic_\" are treated as Dialogic-related.
+
+Removes translation IDs (eg. #id:33) from timelines and characters."
+texture = SubResource("ImageTexture_xbph7")
+hint_text = "Be very careful with this button!
+
+It will try to delete any \".csv\" and \".translation\" files that are related to Dialogic.
+CSV and translation files prefixed with \"dialogic_\" are treated as Dialogic-related.
+
+Removes translation IDs (eg. #id:33) from timelines and characters."
+
+[node name="StatusMessage" type="Label" parent="TranslationSettings/VBoxContainer2"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "⛔ Requires valid translation folder to translate character names and the project CSV file."
+autowrap_mode = 3
+
+[node name="UpdateConfirmationDialog" type="ConfirmationDialog" parent="."]
+unique_name_in_owner = true
+title = "Please Decide..."
+size = Vector2i(490, 200)
+ok_button_text = "Delete old & Generate new"
+dialog_text = "You have previously generated CSVs and translation files with different Translation Settings!
+
+Please consider to delete the old CSVs and then generate new changes."
+dialog_autowrap = true
+
+[node name="EraseConfirmationDialog" type="ConfirmationDialog" parent="."]
+unique_name_in_owner = true
+position = Vector2i(0, 36)
+size = Vector2i(500, 280)
+min_size = Vector2i(300, 70)
+ok_button_text = "DELETE ALL"
+dialog_text = "You are about to:
+- Delete all CSVs prefixed with \"dialogic_\".
+- Delete the related CSV import files.
+- Delete the related translation files.
+- Remove translation IDs from timelines and characters.
+- Remove all \"dialogic\" prefixed translations from \"Project Settings -> Localization\".
+- Remove the \"_translation_keys\" and \"entries\" starting with \"Glossary/\"."
+dialog_autowrap = true
+
+[node name="AspectRatioContainer" type="AspectRatioContainer" parent="."]
+custom_minimum_size = Vector2(0, 31)
+layout_mode = 2
+
+[connection signal="confirmed" from="UpdateConfirmationDialog" to="." method="_delete_and_update"]
+[connection signal="confirmed" from="EraseConfirmationDialog" to="." method="erase_translations"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Theme/MainTheme.tres b/addons/dialogic-main/addons/dialogic/Editor/Theme/MainTheme.tres
new file mode 100644
index 0000000..bd77e0f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Theme/MainTheme.tres
@@ -0,0 +1,3 @@
+[gd_resource type="Theme" format=3 uid="uid://cqst728xxipcw"]
+
+[resource]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/Theme/PickerTheme.tres b/addons/dialogic-main/addons/dialogic/Editor/Theme/PickerTheme.tres
new file mode 100644
index 0000000..36b1955
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/Theme/PickerTheme.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Theme" format=2]
+
+[resource]
+Button/colors/font_color = Color( 1, 1, 1, 1 )
+Button/colors/font_color_disabled = Color( 0.901961, 0.901961, 0.901961, 0.2 )
+Button/colors/font_color_hover = Color( 0.870588, 0.870588, 0.870588, 1 )
+Button/colors/font_color_pressed = Color( 1, 1, 1, 1 )
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd
new file mode 100644
index 0000000..8a12cd5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd
@@ -0,0 +1,324 @@
+@tool
+extends Node
+
+enum Modes {TEXT_EVENT_ONLY, FULL_HIGHLIGHTING}
+
+var syntax_highlighter: SyntaxHighlighter = load("res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd").new()
+var text_syntax_highlighter: SyntaxHighlighter = load("res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd").new()
+
+
+# These RegEx's are used to deduce information from the current line for auto-completion
+
+# To find the currently typed word and the symbol before
+var completion_word_regex := RegEx.new()
+# To find the shortcode of the current shortcode event (basically the type)
+var completion_shortcode_getter_regex := RegEx.new()
+# To find the parameter name of the current if typing a value
+var completion_shortcode_param_getter_regex := RegEx.new()
+# To find the value of a paramater that is being typed
+var completion_shortcode_value_regex := RegEx.new()
+
+# Stores references to all shortcode events for parameter and value suggestions
+var shortcode_events := {}
+var custom_syntax_events := []
+var text_event: DialogicTextEvent = null
+
+func _ready() -> void:
+ # Compile RegEx's
+ completion_word_regex.compile("(?(\\W)|^)(?\\w*)\\x{FFFF}")
+ completion_shortcode_getter_regex.compile("\\[(?\\w*)")
+ completion_shortcode_param_getter_regex.compile("(?\\w*)\\W*=\\s*\"?(\\w|\\s)*"+String.chr(0xFFFF))
+ completion_shortcode_value_regex.compile(r'(\[|\s)[^\[\s=]*="(?[^"$]*)'+String.chr(0xFFFF))
+
+ text_syntax_highlighter.mode = text_syntax_highlighter.Modes.TEXT_EVENT_ONLY
+
+#region AUTO COMPLETION
+################################################################################
+
+# Helper that gets the current line with a special character where the caret is
+func get_code_completion_line(text:CodeEdit) -> String:
+ return text.get_line(text.get_caret_line()).insert(text.get_caret_column(), String.chr(0xFFFF)).strip_edges()
+
+
+# Helper that gets the currently typed word
+func get_code_completion_word(text:CodeEdit) -> String:
+ var result := completion_word_regex.search(get_code_completion_line(text))
+ return result.get_string('word') if result else ""
+
+# Helper that gets the currently typed parameter
+func get_code_completion_parameter_value(text:CodeEdit) -> String:
+ var result := completion_shortcode_value_regex.search(get_code_completion_line(text))
+ return result.get_string('value') if result else ""
+
+
+# Helper that gets the symbol before the current word
+func get_code_completion_prev_symbol(text:CodeEdit) -> String:
+ var result := completion_word_regex.search(get_code_completion_line(text))
+ return result.get_string('s') if result else ""
+
+
+func get_line_untill_caret(line:String) -> String:
+ return line.substr(0, line.find(String.chr(0xFFFF)))
+
+
+# Called if something was typed
+# Adds all kinds of options depending on the
+# content of the current line, the last word and the symbol that came before
+# Triggers opening of the popup
+func request_code_completion(force:bool, text:CodeEdit, mode:=Modes.FULL_HIGHLIGHTING) -> void:
+ ## TODO remove this once https://github.com/godotengine/godot/issues/38560 is fixed
+ if mode != Modes.FULL_HIGHLIGHTING:
+ return
+
+ # make sure shortcode event references are loaded
+ if mode == Modes.FULL_HIGHLIGHTING:
+ var hidden_events: Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+ if shortcode_events.is_empty():
+ for event in DialogicResourceUtil.get_event_cache():
+ if event.get_shortcode() != 'default_shortcode':
+ shortcode_events[event.get_shortcode()] = event
+
+ else:
+ custom_syntax_events.append(event)
+ if event.event_name in hidden_events:
+ event.set_meta('hidden', true)
+ if event is DialogicTextEvent:
+ text_event = event
+ # this is done to force-load the text effects regex which is used below
+ event.load_text_effects()
+
+ # fill helpers
+ var line := get_code_completion_line(text)
+ var word := get_code_completion_word(text)
+ var symbol := get_code_completion_prev_symbol(text)
+ var line_part := get_line_untill_caret(line)
+
+ ## Note on use of KIND types for options.
+ # These types are mostly useless for us.
+ # However I decidede to assign some special cases for them:
+ # - KIND_PLAIN_TEXT is only shown if the beginnging of the option is already typed
+ # !word.is_empty() and option.begins_with(word)
+ # - KIND_CLASS is only shown if anything from the options is already typed
+ # !word.is_empty() and word in option
+ # - KIND_CONSTANT is shown and checked against the beginning
+ # option.begins_with(word)
+ # - KIND_MEMBER is shown and searched completely
+ # word in option
+
+ ## Note on VALUE key
+ # The value key is used to store a potential closing string for the completion.
+ # The completion will check if the string is already present and add it otherwise.
+
+ # Shortcode event suggestions
+ if mode == Modes.FULL_HIGHLIGHTING and syntax_highlighter.line_is_shortcode_event(text.get_caret_line()):
+ if symbol == '[':
+ # suggest shortcodes if a shortcode event has just begun
+ var shortcodes := shortcode_events.keys()
+ shortcodes.sort()
+ for shortcode in shortcodes:
+ if shortcode_events[shortcode].get_meta('hidden', false):
+ continue
+ if shortcode_events[shortcode].get_shortcode_parameters().is_empty():
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, shortcode, shortcode, shortcode_events[shortcode].event_color.lerp(syntax_highlighter.normal_color, 0.3), shortcode_events[shortcode]._get_icon())
+ else:
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, shortcode, shortcode+" ", shortcode_events[shortcode].event_color.lerp(syntax_highlighter.normal_color, 0.3), shortcode_events[shortcode]._get_icon())
+ else:
+ var full_event_text: String = syntax_highlighter.get_full_event(text.get_caret_line())
+ var current_shortcode := completion_shortcode_getter_regex.search(full_event_text)
+ if !current_shortcode:
+ text.update_code_completion_options(false)
+ return
+
+ var code := current_shortcode.get_string('code')
+ if !code in shortcode_events.keys():
+ text.update_code_completion_options(false)
+ return
+
+ # suggest parameters
+ if symbol == ' ' and line.count('"')%2 == 0:
+ var parameters: Array = shortcode_events[code].get_shortcode_parameters().keys()
+ for param in parameters:
+ if !param+'=' in full_event_text:
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, param, param+'="' , shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("MemberProperty", "EditorIcons"))
+
+ # suggest values
+ elif symbol == '=' or symbol == '"':
+ var current_parameter_gex := completion_shortcode_param_getter_regex.search(line)
+ if !current_parameter_gex:
+ text.update_code_completion_options(false)
+ return
+
+ var current_parameter := current_parameter_gex.get_string('param')
+ if !shortcode_events[code].get_shortcode_parameters().has(current_parameter):
+ text.update_code_completion_options(false)
+ return
+ if !shortcode_events[code].get_shortcode_parameters()[current_parameter].has('suggestions'):
+ if typeof(shortcode_events[code].get_shortcode_parameters()[current_parameter].default) == TYPE_BOOL:
+ suggest_bool(text, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3))
+ elif len(word) > 0:
+ text.add_code_completion_option(CodeEdit.KIND_VARIABLE, word, word, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("GuiScrollArrowRight", "EditorIcons"), '" ')
+ text.update_code_completion_options(true)
+ return
+
+ var suggestions: Dictionary = shortcode_events[code].get_shortcode_parameters()[current_parameter]['suggestions'].call()
+ suggest_custom_suggestions(suggestions, text, shortcode_events[code].event_color.lerp(syntax_highlighter.normal_color, 0.3))
+
+ # Force update and showing of the popup
+ text.update_code_completion_options(true)
+ return
+
+
+ for event in custom_syntax_events:
+ if mode == Modes.TEXT_EVENT_ONLY and !event is DialogicTextEvent:
+ continue
+
+ if ! ' ' in line_part:
+ event._get_start_code_completion(self, text)
+
+ if event.is_valid_event(line):
+ event._get_code_completion(self, text, line, word, symbol)
+ break
+
+ # Force update and showing of the popup
+ text.update_code_completion_options(true)
+
+
+
+# Helper that adds all characters as options
+func suggest_characters(text:CodeEdit, type := CodeEdit.KIND_MEMBER, text_event_start:=false) -> void:
+ for character in DialogicResourceUtil.get_character_directory():
+ var result: String = character
+ if " " in character:
+ result = '"'+character+'"'
+ if text_event_start and load(DialogicResourceUtil.get_character_directory()[character]).portraits.is_empty():
+ result += ':'
+ text.add_code_completion_option(type, character, result, syntax_highlighter.character_name_color, load("res://addons/dialogic/Editor/Images/Resources/character.svg"))
+
+
+# Helper that adds all timelines as options
+func suggest_timelines(text:CodeEdit, type := CodeEdit.KIND_MEMBER, color:=Color()) -> void:
+ for timeline in DialogicResourceUtil.get_timeline_directory():
+ text.add_code_completion_option(type, timeline, timeline+'/', color, text.get_theme_icon("TripleBar", "EditorIcons"))
+
+
+func suggest_labels(text:CodeEdit, timeline:String='', end:='', color:=Color()) -> void:
+ if timeline in DialogicResourceUtil.get_label_cache():
+ for i in DialogicResourceUtil.get_label_cache()[timeline]:
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+end, color, load("res://addons/dialogic/Modules/Jump/icon_label.png"))
+
+
+# Helper that adds all portraits of a given character as options
+func suggest_portraits(text:CodeEdit, character_name:String, end_check:=')') -> void:
+ if !character_name in DialogicResourceUtil.get_character_directory():
+ return
+ var character_resource: DialogicCharacter = load(DialogicResourceUtil.get_character_directory()[character_name])
+ for portrait in character_resource.portraits:
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, portrait, portrait, syntax_highlighter.character_portrait_color, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), end_check)
+ if character_resource.portraits.is_empty():
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, 'Has no portraits!', '', syntax_highlighter.character_portrait_color, load("res://addons/dialogic/Editor/Images/Pieces/warning.svg"))
+
+
+# Helper that adds all variable paths as options
+func suggest_variables(text:CodeEdit):
+ for variable in DialogicUtil.list_variables(ProjectSettings.get_setting('dialogic/variables')):
+ text.add_code_completion_option(CodeEdit.KIND_MEMBER, variable, variable, syntax_highlighter.variable_color, text.get_theme_icon("MemberProperty", "EditorIcons"), '}')
+
+
+# Helper that adds true and false as options
+func suggest_bool(text:CodeEdit, color:Color):
+ text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'true', 'true', color, text.get_theme_icon("GuiChecked", "EditorIcons"), '" ')
+ text.add_code_completion_option(CodeEdit.KIND_VARIABLE, 'false', 'false', color, text.get_theme_icon("GuiUnchecked", "EditorIcons"), '" ')
+
+
+func suggest_custom_suggestions(suggestions:Dictionary, text:CodeEdit, color:Color) -> void:
+ for key in suggestions.keys():
+ if suggestions[key].has('text_alt'):
+ text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, suggestions[key].text_alt[0], color, suggestions[key].get('icon', null), '" ')
+ else:
+ text.add_code_completion_option(CodeEdit.KIND_VARIABLE, key, str(suggestions[key].value), color, suggestions[key].get('icon', null), '" ')
+
+
+# Filters the list of all possible options, depending on what was typed
+# Purpose of the different Kinds is explained in [_request_code_completion]
+func filter_code_completion_candidates(candidates:Array, text:CodeEdit) -> Array:
+ var valid_candidates := []
+ var current_word := get_code_completion_word(text)
+ for candidate in candidates:
+ if candidate.kind == text.KIND_PLAIN_TEXT:
+ if !current_word.is_empty() and candidate.insert_text.begins_with(current_word):
+ valid_candidates.append(candidate)
+ elif candidate.kind == text.KIND_MEMBER:
+ if current_word.is_empty() or current_word.to_lower() in candidate.insert_text.to_lower():
+ valid_candidates.append(candidate)
+ elif candidate.kind == text.KIND_VARIABLE:
+ var current_param_value := get_code_completion_parameter_value(text)
+ if current_param_value.is_empty() or current_param_value.to_lower() in candidate.insert_text.to_lower():
+ valid_candidates.append(candidate)
+ elif candidate.kind == text.KIND_CONSTANT:
+ if current_word.is_empty() or candidate.insert_text.begins_with(current_word):
+ valid_candidates.append(candidate)
+ elif candidate.kind == text.KIND_CLASS:
+ if !current_word.is_empty() and current_word.to_lower() in candidate.insert_text.to_lower():
+ valid_candidates.append(candidate)
+ return valid_candidates
+
+
+# Called when code completion was activated
+# Inserts the selected item
+func confirm_code_completion(replace:bool, text:CodeEdit) -> void:
+ # Note: I decided to ALWAYS use replace mode, as dialogic is supposed to be beginner friendly
+
+ var code_completion := text.get_code_completion_option(text.get_code_completion_selected_index())
+
+ var word := get_code_completion_word(text)
+ if code_completion.kind == CodeEdit.KIND_VARIABLE:
+ word = get_code_completion_parameter_value(text)
+
+ text.remove_text(text.get_caret_line(), text.get_caret_column()-len(word), text.get_caret_line(), text.get_caret_column())
+
+ # Something has changed between 4.2 and 4.3
+ # Probably about how carets are reset when text is removed or idk.
+ # To keep compatibility with 4.2 for at least a while this should do the trick:
+ # TODO: Remove once compatibility for 4.2 is dropped.
+ if Engine.get_version_info().hex >= 0x040300:
+ text.set_caret_column(text.get_caret_column())
+ else:
+ text.set_caret_column(text.get_caret_column()-len(word))
+
+ text.insert_text_at_caret(code_completion.insert_text)
+
+ if code_completion.has('default_value') and typeof(code_completion['default_value']) == TYPE_STRING:
+ var next_letter := text.get_line(text.get_caret_line()).substr(text.get_caret_column(), len(code_completion['default_value']))
+ if next_letter == code_completion['default_value'] or next_letter[0] == code_completion['default_value'][0]:
+ text.set_caret_column(text.get_caret_column()+1)
+ else:
+ text.insert_text_at_caret(code_completion['default_value'])
+
+
+#endregion
+
+#region SYMBOL CLICKING
+################################################################################
+
+# Performs an action (like opening a link) when a valid symbol was clicked
+func symbol_lookup(symbol:String, line:int, column:int) -> void:
+ if symbol in shortcode_events.keys():
+ if !shortcode_events[symbol].help_page_path.is_empty():
+ OS.shell_open(shortcode_events[symbol].help_page_path)
+ if symbol in DialogicResourceUtil.get_character_directory():
+ EditorInterface.edit_resource(DialogicResourceUtil.get_resource_from_identifier(symbol, 'dch'))
+ if symbol in DialogicResourceUtil.get_timeline_directory():
+ EditorInterface.edit_resource(DialogicResourceUtil.get_resource_from_identifier(symbol, 'dtl'))
+
+
+# Called to test if a symbol can be clicked
+func symbol_validate(symbol:String, text:CodeEdit) -> void:
+ if symbol in shortcode_events.keys():
+ if !shortcode_events[symbol].help_page_path.is_empty():
+ text.set_symbol_lookup_word_as_valid(true)
+ if symbol in DialogicResourceUtil.get_character_directory():
+ text.set_symbol_lookup_word_as_valid(true)
+ if symbol in DialogicResourceUtil.get_timeline_directory():
+ text.set_symbol_lookup_word_as_valid(true)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd
new file mode 100644
index 0000000..400dbe2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd
@@ -0,0 +1,201 @@
+@tool
+extends SyntaxHighlighter
+
+## Syntax highlighter for the dialogic text timeline editor and text events in the visual editor.
+
+enum Modes {TEXT_EVENT_ONLY, FULL_HIGHLIGHTING}
+var mode := Modes.FULL_HIGHLIGHTING
+
+
+## RegEx's
+var word_regex := RegEx.new()
+var region_regex := RegEx.new()
+var number_regex := RegEx.create_from_string(r"(\d|\.)+")
+var shortcode_regex := RegEx.create_from_string(r"\W*\[(?\w*)(?[^\]]*)?")
+var shortcode_param_regex := RegEx.create_from_string(r'((?[^\s=]*)\s*=\s*"(?([^=]|\\=)*)(? void:
+ update_colors()
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().get_base_control().theme_changed.connect(update_colors)
+
+
+func update_colors() -> void:
+ if not DialogicUtil.get_dialogic_plugin():
+ return
+ var editor_settings: EditorSettings = DialogicUtil.get_dialogic_plugin().get_editor_interface().get_editor_settings()
+ normal_color = editor_settings.get('text_editor/theme/highlighting/text_color')
+ translation_id_color = editor_settings.get('text_editor/theme/highlighting/comment_color')
+
+ code_flow_color = editor_settings.get("text_editor/theme/highlighting/control_flow_keyword_color")
+ boolean_operator_color = code_flow_color.lightened(0.5)
+ variable_color = editor_settings.get('text_editor/theme/highlighting/engine_type_color')
+ string_color = editor_settings.get('text_editor/theme/highlighting/string_color')
+ character_name_color = editor_settings.get('text_editor/theme/highlighting/symbol_color').lerp(normal_color, 0.3)
+ character_portrait_color = character_name_color.lerp(normal_color, 0.5)
+
+
+func _get_line_syntax_highlighting(line:int) -> Dictionary:
+ var str_line := get_text_edit().get_line(line)
+
+ if shortcode_events.is_empty():
+ for event in DialogicResourceUtil.get_event_cache():
+ if event.get_shortcode() != 'default_shortcode':
+ shortcode_events[event.get_shortcode()] = event
+ else:
+ custom_syntax_events.append(event)
+ if event is DialogicTextEvent:
+ text_event = event
+ text_event.load_text_effects()
+
+ var dict := {}
+ dict[0] = {'color':normal_color}
+
+ dict = color_translation_id(dict, str_line)
+
+ if mode == Modes.FULL_HIGHLIGHTING:
+ if line_is_shortcode_event(line):
+ var full_event := get_full_event(line)
+ var result := shortcode_regex.search(full_event)
+ if result:
+ if result.get_string('id') in shortcode_events:
+ if full_event.begins_with(str_line):
+ dict[result.get_start('id')] = {"color":shortcode_events[result.get_string('id')].event_color.lerp(normal_color, 0.4)}
+ dict[result.get_end('id')] = {"color":normal_color}
+
+ if result.get_string('args'):
+ color_shortcode_content(dict, str_line, result.get_start('args'), result.get_end('args'), shortcode_events[result.get_string('id')].event_color)
+ else:
+ color_shortcode_content(dict, str_line, 0, 0, shortcode_events[result.get_string('id')].event_color)
+ return fix_dict(dict)
+
+ else:
+ for event in custom_syntax_events:
+ if event.is_valid_event(str_line.strip_edges()):
+ dict = event._get_syntax_highlighting(self, dict, str_line)
+ return fix_dict(dict)
+
+ else:
+ dict = text_event._get_syntax_highlighting(self, dict, str_line)
+ return fix_dict(dict)
+
+
+func line_is_shortcode_event(line_idx:int) -> bool:
+ var str_line := get_text_edit().get_line(line_idx)
+ if text_event.text_effects_regex.search(str_line.get_slice(' ', 0)):
+ return false
+
+ if str_line.strip_edges().begins_with("["):
+ return true
+
+ if line_idx > 0 and get_text_edit().get_line(line_idx-1).ends_with('\\'):
+ return line_is_shortcode_event(line_idx-1)
+
+ return false
+
+
+func get_full_event(line_idx:int) -> String:
+ var str_line := get_text_edit().get_line(line_idx)
+ var offset := 1
+ # Add previous lines
+ while get_text_edit().get_line(line_idx-offset).ends_with('\\'):
+ str_line = get_text_edit().get_line(line_idx-offset).trim_suffix('\\')+"\n"+str_line
+ offset += 1
+
+ # This is commented out, as it is not needed right now.
+ # However without it, this isn't actually the full event.
+ # Might need to be included some day.
+ #offset = 0
+ ## Add following lines
+ #while get_text_edit().get_line(line_idx+offset).ends_with('\\'):
+ #str_line = str_line.trim_suffix('\\')+"\n"+get_text_edit().get_line(line_idx+offset)
+ #offset += 1
+
+ return str_line
+
+func fix_dict(dict:Dictionary) -> Dictionary:
+ var d := {}
+ var k := dict.keys()
+ k.sort()
+ for i in k:
+ d[i] = dict[i]
+ return d
+
+
+func color_condition(dict:Dictionary, line:String, from:int = 0, to:int = 0) -> Dictionary:
+ dict = color_word(dict, code_flow_color, line, 'or', from, to)
+ dict = color_word(dict, code_flow_color, line, 'and', from, to)
+ dict = color_word(dict, code_flow_color, line, '==', from, to)
+ dict = color_word(dict, code_flow_color, line, '!=', from, to)
+ if !">=" in line:
+ dict = color_word(dict, code_flow_color, line, '>', from, to)
+ else:
+ dict = color_word(dict, code_flow_color, line, '>=', from, to)
+ if !"<=" in line:
+ dict = color_word(dict, code_flow_color, line, '<', from, to)
+ else:
+ dict = color_word(dict, code_flow_color, line, '<=', from, to)
+ dict = color_region(dict, variable_color, line, '{', '}', from, to)
+ dict = color_region(dict, string_color, line, '"', '"', from, to)
+
+
+ return dict
+
+
+func color_translation_id(dict:Dictionary, line:String) -> Dictionary:
+ dict = color_region(dict, translation_id_color, line, '#id:', '')
+ return dict
+
+
+func color_word(dict:Dictionary, color:Color, line:String, word:String, from:int= 0, to:int = 0) -> Dictionary:
+ word_regex.compile("\\W(?"+word+")\\W")
+ if to <= from:
+ to = len(line)-1
+ for i in word_regex.search_all(line.substr(from, to-from+2)):
+ dict[i.get_start('word')+from] = {'color':color}
+ dict[i.get_end('word')+from] = {'color':normal_color}
+ return dict
+
+
+func color_region(dict:Dictionary, color:Color, line:String, start:String, end:String, from:int = 0, to:int = 0, base_color:Color=normal_color) -> Dictionary:
+ if start in "()[].":
+ start = "\\"+start
+ if end in "()[].":
+ end = "\\"+end
+
+ if end.is_empty():
+ region_regex.compile("(? Dictionary:
+ if to <= from:
+ to = len(line)-1
+ var args_result := shortcode_param_regex.search_all(line.substr(from, to-from+2))
+ for x in args_result:
+ dict[x.get_start()+from] = {"color":base_color.lerp(normal_color, 0.5)}
+ dict[x.get_start('value')+from-1] = {"color":base_color.lerp(normal_color, 0.7)}
+ dict[x.get_end()+from] = {"color":normal_color}
+ return dict
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd
new file mode 100644
index 0000000..4d12adc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd
@@ -0,0 +1,306 @@
+@tool
+extends CodeEdit
+
+## Sub-Editor that allows editing timelines in a text format.
+
+@onready var timeline_editor := get_parent().get_parent()
+@onready var code_completion_helper: Node= find_parent('EditorsManager').get_node('CodeCompletionHelper')
+
+var label_regex := RegEx.create_from_string('label +(?[^\n]+)')
+
+func _ready() -> void:
+ await find_parent('EditorView').ready
+ syntax_highlighter = code_completion_helper.syntax_highlighter
+ timeline_editor.editors_manager.sidebar.content_item_activated.connect(_on_content_item_clicked)
+
+
+func _on_text_editor_text_changed() -> void:
+ timeline_editor.current_resource_state = DialogicEditor.ResourceStates.UNSAVED
+ request_code_completion(true)
+ $UpdateTimer.start()
+
+
+func clear_timeline() -> void:
+ text = ''
+ update_content_list()
+
+
+func load_timeline(timeline:DialogicTimeline) -> void:
+ clear_timeline()
+
+ text = timeline.as_text()
+
+ timeline_editor.current_resource.set_meta("timeline_not_saved", false)
+ clear_undo_history()
+
+ await get_tree().process_frame
+ update_content_list()
+
+
+func save_timeline() -> void:
+ if !timeline_editor.current_resource:
+ return
+
+ var text_array: Array = text_timeline_to_array(text)
+
+ timeline_editor.current_resource.events = text_array
+ timeline_editor.current_resource.events_processed = false
+ ResourceSaver.save(timeline_editor.current_resource, timeline_editor.current_resource.resource_path)
+
+ timeline_editor.current_resource.set_meta("timeline_not_saved", false)
+ timeline_editor.current_resource_state = DialogicEditor.ResourceStates.SAVED
+ DialogicResourceUtil.update_directory('dtl')
+
+
+func text_timeline_to_array(text:String) -> Array:
+ # Parse the lines down into an array
+ var events := []
+
+ var lines := text.split('\n', true)
+ var idx := -1
+
+ while idx < len(lines)-1:
+ idx += 1
+ var line: String = lines[idx]
+ var line_stripped: String = line.strip_edges(true, true)
+ events.append(line)
+
+ return events
+
+
+################################################################################
+## HELPFUL EDITOR FUNCTIONALITY
+################################################################################
+
+func _gui_input(event):
+ if not event is InputEventKey: return
+ if not event.is_pressed(): return
+ match event.as_text():
+ "Ctrl+K":
+ toggle_comment()
+ "Alt+Up":
+ move_line(-1)
+ "Alt+Down":
+ move_line(1)
+ "Ctrl+Shift+D":
+ duplicate_line()
+ _:
+ return
+ get_viewport().set_input_as_handled()
+
+# Toggle the selected lines as comments
+func toggle_comment() -> void:
+ var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
+ var selection := Rect2i(
+ Vector2i(get_selection_origin_line(), get_selection_origin_column()),
+ Vector2i(get_caret_line(), get_caret_column()))
+ var from: int = cursor.y
+ var to: int = cursor.y
+ if has_selection():
+ from = get_selection_from_line()
+ to = get_selection_to_line()
+
+ var lines: PackedStringArray = text.split("\n")
+ var will_comment: bool = false
+ for i in range(from, to+1):
+ if not lines[i].begins_with("#"):
+ will_comment = true
+
+ for i in range(from, to + 1):
+ if will_comment:
+ lines[i] = "#" + lines[i]
+ else:
+ lines[i] = lines[i].trim_prefix("#")
+
+ text = "\n".join(lines)
+ if will_comment:
+ cursor.x += 1
+ selection.position.y += 1
+ selection.size.y += 1
+ else:
+ cursor.x -= 1
+ selection.position.y -= 1
+ selection.size.y -= 1
+ select(selection.position.x, selection.position.y, selection.size.x, selection.size.y)
+ text_changed.emit()
+
+
+# Move the selected lines up or down
+func move_line(offset: int) -> void:
+ offset = clamp(offset, -1, 1)
+
+ var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
+ var reselect: bool = false
+ var from: int = cursor.y
+ var to: int = cursor.y
+ if has_selection():
+ reselect = true
+ from = get_selection_from_line()
+ to = get_selection_to_line()
+
+ var lines := text.split("\n")
+
+ if from + offset < 0 or to + offset >= lines.size(): return
+
+ var target_from_index: int = from - 1 if offset == -1 else to + 1
+ var target_to_index: int = to if offset == -1 else from
+ var line_to_move: String = lines[target_from_index]
+ lines.remove_at(target_from_index)
+ lines.insert(target_to_index, line_to_move)
+
+ text = "\n".join(lines)
+
+ cursor.y += offset
+ from += offset
+ to += offset
+ if reselect:
+ select(from, 0, to, get_line_width(to))
+ set_caret_line(cursor.y)
+ set_caret_column(cursor.x)
+ text_changed.emit()
+
+
+func duplicate_line() -> void:
+ var cursor: Vector2 = Vector2(get_caret_column(), get_caret_line())
+ var from: int = cursor.y
+ var to: int = cursor.y+1
+ if has_selection():
+ from = get_selection_from_line()
+ to = get_selection_to_line()+1
+
+ var lines := text.split("\n")
+ var lines_to_dupl: PackedStringArray = lines.slice(from, to)
+
+ text = "\n".join(lines.slice(0, from)+lines_to_dupl+lines.slice(from))
+
+ set_caret_line(cursor.y+to-from)
+ set_caret_column(cursor.x)
+ text_changed.emit()
+
+
+# Allows dragging files into the editor
+func _can_drop_data(at_position:Vector2, data:Variant) -> bool:
+ if typeof(data) == TYPE_DICTIONARY and 'files' in data.keys() and len(data.files) == 1:
+ return true
+ return false
+
+
+# Allows dragging files into the editor
+func _drop_data(at_position:Vector2, data:Variant) -> void:
+ if typeof(data) == TYPE_DICTIONARY and 'files' in data.keys() and len(data.files) == 1:
+ set_caret_column(get_line_column_at_pos(at_position).x)
+ set_caret_line(get_line_column_at_pos(at_position).y)
+ var result: String = data.files[0]
+ if get_line(get_caret_line())[get_caret_column()-1] != '"':
+ result = '"'+result
+ if get_line(get_caret_line())[get_caret_column()] != '"':
+ result = result+'"'
+
+ insert_text_at_caret(result)
+
+
+func _on_update_timer_timeout() -> void:
+ update_content_list()
+
+
+func update_content_list() -> void:
+ var labels: PackedStringArray = []
+ for i in label_regex.search_all(text):
+ labels.append(i.get_string('name'))
+ timeline_editor.editors_manager.sidebar.update_content_list(labels)
+
+
+func _on_content_item_clicked(label:String) -> void:
+ if label == "~ Top":
+ set_caret_line(0)
+ set_caret_column(0)
+ adjust_viewport_to_caret()
+ return
+
+ for i in label_regex.search_all(text):
+ if i.get_string('name') == label:
+ set_caret_column(0)
+ set_caret_line(text.count('\n', 0, i.get_start()+1))
+ center_viewport_to_caret()
+ return
+
+
+func _search_timeline(search_text:String) -> bool:
+ set_search_text(search_text)
+ queue_redraw()
+ set_meta("current_search", search_text)
+
+ return search(search_text, 0, 0, 0).y != -1
+
+
+func _search_navigate_down() -> void:
+ search_navigate(false)
+
+
+func _search_navigate_up() -> void:
+ search_navigate(true)
+
+
+func search_navigate(navigate_up := false) -> void:
+ if not has_meta("current_search"):
+ return
+ var pos: Vector2i
+ var search_from_line := 0
+ var search_from_column := 0
+ if has_selection():
+ if navigate_up:
+ search_from_line = get_selection_from_line()
+ search_from_column = get_selection_from_column()-1
+ if search_from_column == -1:
+ if search_from_line == 0:
+ search_from_line = get_line_count()
+ else:
+ search_from_line -= 1
+ search_from_column = max(get_line(search_from_line).length()-1,0)
+ else:
+ search_from_line = get_selection_to_line()
+ search_from_column = get_selection_to_column()
+ else:
+ search_from_line = get_caret_line()
+ search_from_column = get_caret_column()
+
+ pos = search(get_meta("current_search"), 4 if navigate_up else 0, search_from_line, search_from_column)
+ select(pos.y, pos.x, pos.y, pos.x+len(get_meta("current_search")))
+ set_caret_line(pos.y)
+ center_viewport_to_caret()
+ queue_redraw()
+
+
+################################################################################
+## AUTO COMPLETION
+################################################################################
+
+# Called if something was typed
+func _request_code_completion(force:bool):
+ code_completion_helper.request_code_completion(force, self)
+
+
+# Filters the list of all possible options, depending on what was typed
+# Purpose of the different Kinds is explained in [_request_code_completion]
+func _filter_code_completion_candidates(candidates:Array) -> Array:
+ return code_completion_helper.filter_code_completion_candidates(candidates, self)
+
+
+# Called when code completion was activated
+# Inserts the selected item
+func _confirm_code_completion(replace:bool) -> void:
+ code_completion_helper.confirm_code_completion(replace, self)
+
+
+################################################################################
+## SYMBOL CLICKING
+################################################################################
+
+# Performs an action (like opening a link) when a valid symbol was clicked
+func _on_symbol_lookup(symbol, line, column):
+ code_completion_helper.symbol_lookup(symbol, line, column)
+
+
+# Called to test if a symbol can be clicked
+func _on_symbol_validate(symbol:String) -> void:
+ code_completion_helper.symbol_validate(symbol, self)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn
new file mode 100644
index 0000000..1ba6230
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn
@@ -0,0 +1,32 @@
+[gd_scene load_steps=2 format=3 uid="uid://defdeav8rli6o"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd" id="1_1kbx2"]
+
+[node name="TimelineTextEditor" type="CodeEdit"]
+offset_top = 592.0
+offset_right = 1024.0
+offset_bottom = 600.0
+theme_override_constants/line_spacing = 10
+wrap_mode = 1
+highlight_current_line = true
+draw_tabs = true
+minimap_draw = true
+caret_blink = true
+line_folding = true
+gutters_draw_line_numbers = true
+gutters_draw_fold_gutter = true
+code_completion_enabled = true
+code_completion_prefixes = Array[String](["[", "{"])
+indent_automatic = true
+auto_brace_completion_enabled = true
+auto_brace_completion_highlight_matching = true
+script = ExtResource("1_1kbx2")
+
+[node name="UpdateTimer" type="Timer" parent="."]
+one_shot = true
+
+[connection signal="code_completion_requested" from="." to="." method="_on_code_completion_requested"]
+[connection signal="symbol_lookup" from="." to="." method="_on_symbol_lookup"]
+[connection signal="symbol_validate" from="." to="." method="_on_symbol_validate"]
+[connection signal="text_changed" from="." to="." method="_on_text_editor_text_changed"]
+[connection signal="timeout" from="UpdateTimer" to="." method="_on_update_timer_timeout"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd
new file mode 100644
index 0000000..7318bd4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd
@@ -0,0 +1,63 @@
+@tool
+extends Button
+
+@export var visible_name := ""
+@export var event_id := ""
+@export var event_icon: Texture:
+ get:
+ return event_icon
+ set(texture):
+ event_icon = texture
+ icon = event_icon
+@export var event_sorting_index: int = 0
+@export var resource: DialogicEvent
+@export var dialogic_color_name := ""
+
+
+func _ready() -> void:
+ tooltip_text = visible_name
+
+ custom_minimum_size = Vector2(get_theme_font("font", "Label").get_string_size(text).x+35,30) * DialogicUtil.get_editor_scale()
+
+ add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
+ add_theme_color_override("font_color_hover", get_theme_color("accent_color", "Editor"))
+ apply_base_button_style()
+
+
+func apply_base_button_style() -> void:
+ var nstyle: StyleBoxFlat = get_parent().get_theme_stylebox('normal', 'Button').duplicate()
+ nstyle.border_width_left = 5 * DialogicUtil.get_editor_scale()
+ add_theme_stylebox_override('normal', nstyle)
+ var hstyle: StyleBoxFlat = get_parent().get_theme_stylebox('hover', 'Button').duplicate()
+ hstyle.border_width_left = 5 * DialogicUtil.get_editor_scale()
+ add_theme_stylebox_override('hover', hstyle)
+ set_color(resource.event_color)
+
+
+func set_color(color:Color) -> void:
+ var style := get_theme_stylebox('normal', 'Button')
+ style.border_color = color
+ add_theme_stylebox_override('normal', style)
+ style = get_theme_stylebox('hover', 'Button')
+ style.border_color = color
+ add_theme_stylebox_override('hover', style)
+
+
+func toggle_name(on:= false) -> void:
+ if !on:
+ text = ""
+ custom_minimum_size = Vector2(40, 40) * DialogicUtil.get_editor_scale()
+ var style := get_theme_stylebox('normal', 'Button')
+ style.bg_color = style.border_color.darkened(0.2)
+ add_theme_stylebox_override('normal', style)
+ style = get_theme_stylebox('hover', 'Button')
+ style.bg_color = style.border_color
+ add_theme_stylebox_override('hover', style)
+ else:
+ text = visible_name
+ custom_minimum_size = Vector2(get_theme_font("font", 'Label').get_string_size(text).x+35,30) * DialogicUtil.get_editor_scale()
+ apply_base_button_style()
+
+
+func _on_button_down() -> void:
+ find_parent('VisualEditor').get_node('%TimelineArea').start_dragging(1, resource)
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn
new file mode 100644
index 0000000..2aad5ef
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=4 format=3 uid="uid://depcrpeh3f4rv"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd" id="1_s43sc"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qx31r"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.1, 0.1, 0.1, 0.6)
+border_width_left = 3
+border_color = Color(0.231373, 0.545098, 0.94902, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+corner_detail = 5
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n1o16"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.225, 0.225, 0.225, 0.6)
+border_width_left = 3
+border_color = Color(0.231373, 0.545098, 0.94902, 1)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+corner_detail = 5
+
+[node name="AddEventButton" type="Button"]
+custom_minimum_size = Vector2(44, 30)
+offset_right = 97.0
+offset_bottom = 42.0
+tooltip_text = "S"
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_styles/normal = SubResource("StyleBoxFlat_qx31r")
+theme_override_styles/hover = SubResource("StyleBoxFlat_n1o16")
+alignment = 0
+expand_icon = true
+script = ExtResource("1_s43sc")
+visible_name = "S"
+
+[connection signal="button_down" from="." to="." method="_on_button_down"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd
new file mode 100644
index 0000000..0baa46b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd
@@ -0,0 +1,199 @@
+@tool
+extends ScrollContainer
+
+# Script of the TimelineArea (that contains the event blocks).
+# Manages the drawing of the event lines and event dragging.
+
+
+enum DragTypes {NOTHING, NEW_EVENT, EXISTING_EVENTS}
+
+var drag_type: DragTypes = DragTypes.NOTHING
+var drag_data: Variant
+var drag_to_position := 0
+var dragging := false
+
+
+signal drag_completed(type, index, data)
+signal drag_canceled()
+
+
+func _ready() -> void:
+ resized.connect(add_extra_scroll_area_to_timeline)
+ %Timeline.child_entered_tree.connect(add_extra_scroll_area_to_timeline)
+
+ # This prevents the view to turn black if you are editing this scene in Godot
+ if find_parent('EditorView'):
+ %TimelineArea.get_theme_color("background_color", "CodeEdit")
+
+
+#region EVENT DRAGGING
+################################################################################
+
+func start_dragging(type:DragTypes, data:Variant) -> void:
+ dragging = true
+ drag_type = type
+ drag_data = data
+
+
+func _input(event:InputEvent) -> void:
+ if !dragging:
+ return
+ if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
+ if !event.is_pressed():
+ finish_dragging()
+
+
+func _process(delta:float) -> void:
+ if !dragging:
+ return
+
+ for child in %Timeline.get_children():
+ if (child.global_position.y < get_global_mouse_position().y) and \
+ (child.global_position.y+child.size.y > get_global_mouse_position().y):
+
+ if get_global_mouse_position().y > child.global_position.y+(child.size.y/2.0):
+ drag_to_position = child.get_index()+1
+ queue_redraw()
+ else:
+ drag_to_position = child.get_index()
+ queue_redraw()
+
+
+func finish_dragging() -> void:
+ dragging = false
+ if get_global_rect().has_point(get_global_mouse_position()):
+ drag_completed.emit(drag_type, drag_to_position, drag_data)
+ else:
+ drag_canceled.emit()
+ queue_redraw()
+
+#endregion
+
+
+#region LINE DRAWING
+################################################################################
+
+func _draw() -> void:
+ var line_width := 5 * DialogicUtil.get_editor_scale()
+ var horizontal_line_length := 100 * DialogicUtil.get_editor_scale()
+ var color_multiplier := Color(1,1,1,0.25)
+ var selected_color_multiplier := Color(1,1,1,1)
+
+
+ ## Draw Event Lines
+ for idx in range($Timeline.get_child_count()):
+ var block: Control = $Timeline.get_child(idx)
+
+ if not "resource" in block:
+ continue
+
+ if not block.visible:
+ continue
+
+ if block.resource is DialogicEndBranchEvent:
+ continue
+
+ if not (block.has_any_enabled_body_content or block.resource.can_contain_events):
+ continue
+
+ var icon_panel_height: int = block.get_node('%IconPanel').size.y
+ var rect_position: Vector2 = block.get_node('%IconPanel').global_position+Vector2(0,1)*block.get_node('%IconPanel').size+Vector2(0,-4)
+ var color: Color = block.resource.event_color
+
+ if block.is_selected() or block.end_node and block.end_node.is_selected():
+ color *= selected_color_multiplier
+ else:
+ color *= color_multiplier
+
+ if block.expanded and not block.resource.can_contain_events:
+ draw_rect(Rect2(rect_position-global_position+Vector2(line_width, 0), Vector2(line_width, block.size.y-block.get_node('%IconPanel').size.y)), color)
+
+ ## If the indentation has not changed, nothing else happens
+ if idx >= $Timeline.get_child_count()-1 or block.current_indent_level >= $Timeline.get_child(idx+1).current_indent_level:
+ continue
+
+ ## Draw connection between opening and end branch events
+ if block.resource.can_contain_events:
+ var end_node: Node = block.end_node
+
+ if end_node != null:
+ var v_length: float = end_node.global_position.y+end_node.size.y/2-rect_position.y
+ #var rect_size := Vector2(line_width, )
+ var offset := Vector2(line_width, 0)
+
+ # Draw vertical line
+ draw_rect(Rect2(rect_position-global_position+offset, Vector2(line_width, v_length)), color)
+ # Draw horizonal line (on END BRANCH event)
+ draw_rect(Rect2(
+ rect_position.x+line_width-global_position.x+offset.x,
+ rect_position.y+v_length-line_width-global_position.y,
+ horizontal_line_length-offset.x,
+ line_width),
+ color)
+
+ if block.resource.wants_to_group:
+ var group_color: Color = block.resource.event_color*color_multiplier
+ var group_starter := true
+ if idx != 0:
+ var block_above := $Timeline.get_child(idx-1)
+ if block_above.resource.event_name == block.resource.event_name:
+ group_starter = false
+ if block_above.resource is DialogicEndBranchEvent and block_above.parent_node.resource.event_name == block.resource.event_name:
+ group_starter = false
+
+ ## Draw small horizontal line on any event in group
+ draw_rect(Rect2(
+ rect_position.x-global_position.x-line_width,
+ rect_position.y-global_position.y-icon_panel_height/2,
+ line_width,
+ line_width),
+ group_color)
+
+ if group_starter:
+ ## Find the last event in the group (or that events END BRANCH)
+ var sub_idx := idx
+ var group_end_idx := idx
+ while sub_idx < $Timeline.get_child_count()-1:
+ sub_idx += 1
+ if $Timeline.get_child(sub_idx).current_indent_level == block.current_indent_level-1:
+ group_end_idx = sub_idx-1
+ break
+
+ var end_node := $Timeline.get_child(group_end_idx)
+
+ var offset := Vector2(-2*line_width, -icon_panel_height/2)
+ var v_length: float = end_node.global_position.y - rect_position.y + icon_panel_height
+
+ ## Draw vertical line
+ draw_rect(Rect2(
+ rect_position.x - global_position.x + offset.x,
+ rect_position.y - global_position.y + offset.y,
+ line_width,
+ v_length),
+ group_color)
+
+
+ ## Draw line that indicates the dragging position
+ if dragging and get_global_rect().has_point(get_global_mouse_position()):
+ var height: int = 0
+ if drag_to_position == %Timeline.get_child_count():
+ height = %Timeline.get_child(-1).global_position.y+%Timeline.get_child(-1).size.y-global_position.y-(line_width/2.0)
+ else:
+ height = %Timeline.get_child(drag_to_position).global_position.y-global_position.y-(line_width/2.0)
+
+ draw_line(Vector2(0, height), Vector2(size.x*0.9, height), get_theme_color("accent_color", "Editor"), line_width*.3)
+
+#endregion
+
+
+#region SPACE BELOW
+################################################################################
+
+func add_extra_scroll_area_to_timeline(fake_arg:Variant=null) -> void:
+ if %Timeline.get_children().size() > 4:
+ %Timeline.custom_minimum_size.y = 0
+ %Timeline.size.y = 0
+ if %Timeline.size.y + 200 > %TimelineArea.size.y:
+ %Timeline.custom_minimum_size = Vector2(0, %Timeline.size.y + 200)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd
new file mode 100644
index 0000000..bd350cb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd
@@ -0,0 +1,1279 @@
+@tool
+extends Container
+
+## Visual mode of the timeline editor.
+
+
+################## EDITOR NODES ################################################
+################################################################################
+var TimelineUndoRedo := UndoRedo.new()
+@onready var timeline_editor := get_parent().get_parent()
+var event_node
+var sidebar_collapsed := false
+
+################## SIGNALS #####################################################
+################################################################################
+signal selection_updated
+signal batch_loaded
+signal timeline_loaded
+
+
+################## TIMELINE LOADING ############################################
+################################################################################
+var _batches := []
+var _building_timeline := false
+var _timeline_changed_while_loading := false
+var _initialized := false
+
+################## TIMELINE EVENT MANAGEMENT ###################################
+################################################################################
+var selected_items: Array = []
+var drag_allowed := false
+
+
+#region CREATE/SAVE/LOAD
+################################################################################
+
+func something_changed() -> void:
+ timeline_editor.current_resource_state = DialogicEditor.ResourceStates.UNSAVED
+
+
+func save_timeline() -> void:
+ if !is_inside_tree():
+ return
+
+ # return if resource is unchanged
+ if timeline_editor.current_resource_state != DialogicEditor.ResourceStates.UNSAVED:
+ return
+
+ # create a list of text versions of all the events with the right indent
+ var new_events := []
+ var indent := 0
+ for event in %Timeline.get_children():
+ if 'event_name' in event.resource:
+ event.resource.update_text_version()
+ new_events.append(event.resource)
+
+ if !timeline_editor.current_resource:
+ return
+
+ timeline_editor.current_resource.events = new_events
+ timeline_editor.current_resource.events_processed = true
+ var error: int = ResourceSaver.save(timeline_editor.current_resource, timeline_editor.current_resource.resource_path)
+
+ if error != OK:
+ print('[Dialogic] Saving error: ', error)
+
+ timeline_editor.current_resource.set_meta("unsaved", false)
+ timeline_editor.current_resource_state = DialogicEditor.ResourceStates.SAVED
+ DialogicResourceUtil.update_directory('dtl')
+
+
+func _notification(what:int) -> void:
+ if what == NOTIFICATION_WM_CLOSE_REQUEST:
+ save_timeline()
+
+
+func load_timeline(resource:DialogicTimeline) -> void:
+ if _building_timeline:
+ _timeline_changed_while_loading = true
+ await batch_loaded
+ _timeline_changed_while_loading = false
+ _building_timeline = false
+
+ clear_timeline_nodes()
+
+ if timeline_editor.current_resource.events.size() == 0:
+ pass
+ else:
+ await timeline_editor.current_resource.process()
+
+ if timeline_editor.current_resource.events.size() == 0:
+ return
+
+ var data := resource.events
+ var page := 1
+ var batch_size := 10
+ _batches = []
+ _building_timeline = true
+ while batch_events(data, batch_size, page).size() != 0:
+ _batches.append(batch_events(data, batch_size, page))
+ page += 1
+ batch_loaded.emit()
+ # Reset the scroll position
+ %TimelineArea.scroll_vertical = 0
+
+
+func batch_events(array: Array, size: int, batch_number: int) -> Array:
+ return array.slice((batch_number - 1) * size, batch_number * size)
+
+
+# a list of all events like choice and condition events (so they get connected to their end events)
+var opener_events_stack := []
+
+func load_batch(data:Array) -> void:
+ var current_batch: Array = _batches.pop_front()
+ if current_batch:
+ for i in current_batch:
+ if i is DialogicEndBranchEvent:
+ create_end_branch_event(%Timeline.get_child_count(), opener_events_stack.pop_back())
+ else:
+ var piece := add_event_node(i, %Timeline.get_child_count())
+ if i.can_contain_events:
+ opener_events_stack.push_back(piece)
+ batch_loaded.emit()
+
+
+func _on_batch_loaded() -> void:
+ if _timeline_changed_while_loading:
+ return
+ if _batches.size() > 0:
+ indent_events()
+ await get_tree().process_frame
+ load_batch(_batches)
+ return
+
+ if opener_events_stack:
+
+ for ev in opener_events_stack:
+ create_end_branch_event(%Timeline.get_child_count(), ev)
+
+ opener_events_stack = []
+ indent_events()
+ update_content_list()
+ _building_timeline = false
+
+
+func clear_timeline_nodes() -> void:
+ deselect_all_items()
+ for event in %Timeline.get_children():
+ event.free()
+#endregion
+
+
+#region SETUP
+################################################################################
+
+func _ready() -> void:
+ event_node = load("res://addons/dialogic/Editor/Events/EventBlock/event_block.tscn")
+
+ batch_loaded.connect(_on_batch_loaded)
+
+ await find_parent('EditorView').ready
+ timeline_editor.editors_manager.sidebar.content_item_activated.connect(_on_content_item_clicked)
+ %Timeline.child_order_changed.connect(update_content_list)
+
+ var editor_scale := DialogicUtil.get_editor_scale()
+ %RightSidebar.size.x = DialogicUtil.get_editor_setting("dialogic/editor/right_sidebar_width", 200 * editor_scale)
+ $View.split_offset = -DialogicUtil.get_editor_setting("dialogic/editor/right_sidebar_width", 200 * editor_scale)
+ sidebar_collapsed = DialogicUtil.get_editor_setting("dialogic/editor/right_sidebar_collapsed", false)
+
+ load_event_buttons()
+ _on_right_sidebar_resized()
+ _initialized = true
+
+
+func load_event_buttons() -> void:
+ sidebar_collapsed = DialogicUtil.get_editor_setting("dialogic/editor/right_sidebar_collapsed", false)
+
+ # Clear previous event buttons
+ for child in %RightSidebar.get_child(0).get_children():
+
+ if child is FlowContainer:
+
+ for button in child.get_children():
+ button.queue_free()
+
+
+ for child in %RightSidebar.get_child(0).get_children():
+ child.get_parent().remove_child(child)
+ child.queue_free()
+
+ # Event buttons
+ var button_scene := load("res://addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn")
+
+ var scripts := DialogicResourceUtil.get_event_cache()
+ var hidden_buttons :Array = DialogicUtil.get_editor_setting('hidden_event_buttons', [])
+ var sections := {}
+
+ for event_script in scripts:
+ var event_resource: Variant
+
+ if typeof(event_script) == TYPE_STRING:
+ event_resource = load(event_script).new()
+ else:
+ event_resource = event_script
+
+ if event_resource.disable_editor_button == true:
+ continue
+
+ if event_resource.event_name in hidden_buttons:
+ continue
+
+ var button: Button = button_scene.instantiate()
+ button.resource = event_resource
+ button.visible_name = event_resource.event_name
+ button.event_icon = event_resource._get_icon()
+ button.set_color(event_resource.event_color)
+ button.dialogic_color_name = event_resource.dialogic_color_name
+ button.event_sorting_index = event_resource.event_sorting_index
+
+ button.button_up.connect(_add_event_button_pressed.bind(event_resource))
+
+ if !event_resource.event_category in sections:
+ var section := VBoxContainer.new()
+ section.name = event_resource.event_category
+
+ var section_header := HBoxContainer.new()
+ section_header.add_child(Label.new())
+ section_header.get_child(0).text = event_resource.event_category
+ section_header.get_child(0).size_flags_horizontal = SIZE_SHRINK_BEGIN
+ section_header.get_child(0).theme_type_variation = "DialogicSection"
+ section_header.add_child(HSeparator.new())
+ section_header.get_child(1).size_flags_horizontal = SIZE_EXPAND_FILL
+ section.add_child(section_header)
+
+ var button_container := FlowContainer.new()
+ section.add_child(button_container)
+
+ sections[event_resource.event_category] = button_container
+ %RightSidebar.get_child(0).add_child(section, true)
+
+ sections[event_resource.event_category].add_child(button)
+ button.toggle_name(!sidebar_collapsed)
+
+ # Sort event button
+ while event_resource.event_sorting_index < sections[event_resource.event_category].get_child(max(0, button.get_index()-1)).resource.event_sorting_index:
+ sections[event_resource.event_category].move_child(button, button.get_index()-1)
+
+ # Sort event sections
+ var sections_order: Array = DialogicUtil.get_editor_setting('event_section_order',
+ ['Main', 'Flow', 'Logic', 'Audio', 'Visual','Other', 'Helper'])
+
+ sections_order.reverse()
+ for section_name in sections_order:
+ if %RightSidebar.get_child(0).has_node(section_name):
+ %RightSidebar.get_child(0).move_child(%RightSidebar.get_child(0).get_node(section_name), 0)
+
+ # Resize RightSidebar
+ %RightSidebar.custom_minimum_size.x = 50 * DialogicUtil.get_editor_scale()
+
+ _on_right_sidebar_resized()
+#endregion
+
+
+#region CONTENT LIST
+################################################################################
+
+func _on_content_item_clicked(label:String) -> void:
+ if label == "~ Top":
+ %TimelineArea.scroll_vertical = 0
+ return
+
+ for event in %Timeline.get_children():
+ if 'event_name' in event.resource and event.resource is DialogicLabelEvent:
+ if event.resource.name == label:
+ scroll_to_piece(event.get_index())
+ return
+
+
+func update_content_list() -> void:
+ if not is_inside_tree():
+ return
+
+ var labels: PackedStringArray = []
+
+ for event in %Timeline.get_children():
+
+ if 'event_name' in event.resource and event.resource is DialogicLabelEvent:
+ labels.append(event.resource.name)
+
+ timeline_editor.editors_manager.sidebar.update_content_list(labels)
+
+
+#endregion
+
+
+#region DRAG & DROP + DRAGGING EVENTS
+#################################################################################
+
+# SIGNAL handles input on the events mainly for selection and moving events
+func _on_event_block_gui_input(event: InputEvent, item: Node) -> void:
+ if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
+ if event.is_pressed():
+ if len(selected_items) > 1 and item in selected_items and !Input.is_key_pressed(KEY_CTRL):
+ pass
+ elif not _is_item_selected(item) and not len(selected_items) > 1:
+ select_item(item)
+ elif len(selected_items) > 1 or Input.is_key_pressed(KEY_CTRL):
+ select_item(item)
+
+ drag_allowed = true
+
+ if len(selected_items) > 0 and event is InputEventMouseMotion:
+ if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
+ if !%TimelineArea.dragging and !get_viewport().gui_is_dragging() and drag_allowed:
+ sort_selection()
+ %TimelineArea.start_dragging(%TimelineArea.DragTypes.EXISTING_EVENTS, selected_items)
+
+
+## Activated by TimelineArea drag_completed
+func _on_timeline_area_drag_completed(type:int, index:int, data:Variant) -> void:
+ if type == %TimelineArea.DragTypes.NEW_EVENT:
+ var resource: DialogicEvent = data.duplicate()
+ resource._load_custom_defaults()
+
+ add_event_undoable(resource, index)
+
+ elif type == %TimelineArea.DragTypes.EXISTING_EVENTS:
+ if not (len(data) == 1 and data[0].get_index()+1 == index):
+ move_blocks_to_index(data, index)
+
+ await get_tree().process_frame
+ something_changed()
+ scroll_to_piece(index)
+ indent_events()
+#endregion
+
+
+#region CREATING THE TIMELINE
+################################################################################
+# Adding an event to the timeline
+func add_event_node(event_resource:DialogicEvent, at_index:int = -1, auto_select: bool = false, indent: bool = false) -> Control:
+ if event_resource is DialogicEndBranchEvent:
+ return create_end_branch_event(at_index, %Timeline.get_child(0))
+
+ if event_resource['event_node_ready'] == false:
+ if event_resource['event_node_as_text'] != "":
+ event_resource._load_from_string(event_resource['event_node_as_text'])
+
+ var block: Control = event_node.instantiate()
+ block.resource = event_resource
+ event_resource.editor_node = block
+ event_resource._enter_visual_editor(timeline_editor)
+ block.content_changed.connect(something_changed)
+
+ if event_resource.event_name == "Label":
+ block.content_changed.connect(update_content_list)
+ if at_index == -1:
+ if len(selected_items) != 0:
+ selected_items[0].add_sibling(block)
+ else:
+ %Timeline.add_child(block)
+ else:
+ %Timeline.add_child(block)
+ %Timeline.move_child(block, at_index)
+
+ block.gui_input.connect(_on_event_block_gui_input.bind(block))
+
+ # Building editing part
+ block.build_editor(true, event_resource.expand_by_default)
+
+ if auto_select:
+ select_item(block, false)
+
+ # Indent on create
+ if indent:
+ indent_events()
+
+ return block
+
+
+func create_end_branch_event(at_index:int, parent_node:Node) -> Node:
+ var end_branch_event: Control = load("res://addons/dialogic/Editor/Events/BranchEnd.tscn").instantiate()
+ end_branch_event.resource = DialogicEndBranchEvent.new()
+ end_branch_event.gui_input.connect(_on_event_block_gui_input.bind(end_branch_event))
+ parent_node.end_node = end_branch_event
+ end_branch_event.parent_node = parent_node
+ end_branch_event.add_end_control(parent_node.resource.get_end_branch_control())
+ %Timeline.add_child(end_branch_event)
+ %Timeline.move_child(end_branch_event, at_index)
+ return end_branch_event
+
+
+# combination of the above that establishes the correct connection between the event and it's end branch
+func add_event_with_end_branch(resource, at_index:int=-1, auto_select:bool = false, indent:bool = false) -> void:
+ var event := add_event_node(resource, at_index, auto_select, indent)
+ create_end_branch_event(at_index+1, event)
+
+
+## Adds an event (either single nodes or with end branches) to the timeline with UndoRedo support
+func add_event_undoable(event_resource: DialogicEvent, at_index: int = -1) -> void:
+ TimelineUndoRedo.create_action("[D] Add "+event_resource.event_name+" event.")
+ if event_resource.can_contain_events:
+ TimelineUndoRedo.add_do_method(add_event_with_end_branch.bind(event_resource, at_index, true, true))
+ TimelineUndoRedo.add_undo_method(delete_events_at_index.bind(at_index, 2))
+ else:
+ TimelineUndoRedo.add_do_method(add_event_node.bind(event_resource, at_index, true, true))
+ TimelineUndoRedo.add_undo_method(delete_events_at_index.bind(at_index, 1))
+ TimelineUndoRedo.commit_action()
+#endregion
+
+
+#region DELETING, COPY, PASTE
+################################################################################
+
+## Lists the given events (as text) based on their indexes.
+## This is used to store info for undo/redo.
+## Based on the action you might want to include END_BRANCHES or not (see EndBranchMode)
+func get_events_indexed(events:Array) -> Dictionary:
+ var indexed_dict := {}
+ for event in events:
+ # do not collect selected end branches (e.g. on delete, copy, etc.)
+ if event.resource is DialogicEndBranchEvent:
+ continue
+
+ indexed_dict[event.get_index()] = event.resource.to_text()
+
+ # store an end branch if it is selected or connected to a selected event
+ if 'end_node' in event and event.end_node:
+ event = event.end_node
+ indexed_dict[event.get_index()] = event.resource.to_text()
+ elif event.resource is DialogicEndBranchEvent:
+ if event.parent_node in events: # add local index
+ indexed_dict[event.get_index()] += str(events.find(event.parent_node))
+ else: # add global index
+ indexed_dict[event.get_index()] += '#'+str(event.parent_node.get_index())
+ return indexed_dict
+
+
+## Returns an indexed dictionary of [amount] events at [index]
+func get_events_at_index_indexed(index:int, amount:int) -> Dictionary:
+ var events := []
+
+ for i in range(amount):
+ events.append(%Timeline.get_child(index+i))
+
+ return get_events_indexed(events)
+
+
+## Selects events based on an indexed dictionary
+func select_events_indexed(indexed_events:Dictionary) -> void:
+ selected_items = []
+ for event_index in indexed_events.keys():
+ selected_items.append(%Timeline.get_child(event_index))
+
+
+## Adds events based on an indexed dictionary
+func add_events_indexed(indexed_events:Dictionary) -> void:
+ # sort the dictionaries indexes just in case
+ var indexes := indexed_events.keys()
+ indexes.sort()
+
+ var events := []
+ for event_idx in indexes:
+ # first get a new resource from the text version
+ var event_resource :DialogicEvent
+ for i in DialogicResourceUtil.get_event_cache():
+ if i._test_event_string(indexed_events[event_idx]):
+ event_resource = i.duplicate()
+ break
+
+ event_resource.from_text(indexed_events[event_idx])
+
+ # now create the visual block.
+ deselect_all_items()
+ if event_resource is DialogicEndBranchEvent:
+ var idx: String = indexed_events[event_idx].trim_prefix('<>')
+ if idx.begins_with('#'): # a global index
+ events.append(create_end_branch_event(%Timeline.get_child_count(), %Timeline.get_child(int(idx.trim_prefix('#')))))
+ else: # a local index (index in the added events list)
+ events.append(create_end_branch_event(%Timeline.get_child_count(), events[int(idx)]))
+ %Timeline.move_child(events[-1], event_idx)
+ else:
+ events.append(add_event_node(event_resource))
+ %Timeline.move_child(events[-1], event_idx)
+
+ selected_items = events
+ visual_update_selection()
+ indent_events()
+ something_changed()
+
+
+## Deletes events based on an indexed dictionary
+func delete_events_indexed(indexed_events:Dictionary) -> void:
+ if indexed_events.is_empty():
+ return
+
+ var idx_shift := 0
+ for idx in indexed_events:
+ if 'end_node' in %Timeline.get_child(idx-idx_shift) and %Timeline.get_child(idx-idx_shift).end_node != null and is_instance_valid(%Timeline.get_child(idx-idx_shift).end_node):
+ %Timeline.get_child(idx-idx_shift).end_node.parent_node = null
+ if %Timeline.get_child(idx-idx_shift) != null and is_instance_valid(%Timeline.get_child(idx-idx_shift)):
+ if %Timeline.get_child(idx-idx_shift) in selected_items:
+ selected_items.erase(%Timeline.get_child(idx-idx_shift))
+ %Timeline.get_child(idx-idx_shift).queue_free()
+ %Timeline.get_child(idx-idx_shift).get_parent().remove_child(%Timeline.get_child(idx-idx_shift))
+ idx_shift += 1
+
+ indent_events()
+ something_changed()
+
+
+func delete_selected_events() -> void:
+ # try to find which item to select afterwards
+ var next_node := %Timeline.get_child(mini(%Timeline.get_child_count() - 1, selected_items[-1].get_index() + 1))
+ if _is_item_selected(next_node):
+ next_node = null
+
+ delete_events_indexed(get_events_indexed(selected_items))
+
+ # select next
+ if next_node != null:
+ select_item(next_node, false)
+ elif %Timeline.get_child_count() > 0:
+ next_node = %Timeline.get_child(max(0, %Timeline.get_child_count() - 1))
+ select_item(next_node, false)
+ else:
+ deselect_all_items()
+
+
+func cut_events_indexed(indexed_events:Dictionary) -> void:
+ select_events_indexed(indexed_events)
+ copy_selected_events()
+ delete_events_indexed(indexed_events)
+
+
+func copy_selected_events() -> void:
+ if len(selected_items) == 0:
+ return
+
+ var event_copy_array := []
+ for item in selected_items:
+ event_copy_array.append(item.resource.to_text())
+ if item.resource is DialogicEndBranchEvent:
+ if item.parent_node in selected_items: # add local index
+ event_copy_array[-1] += str(selected_items.find(item.parent_node))
+ else: # add global index
+ event_copy_array[-1] += '#'+str(item.parent_node.get_index())
+ DisplayServer.clipboard_set(var_to_str({
+ "events":event_copy_array,
+ "project_name": ProjectSettings.get_setting("application/config/name")
+ }))
+
+
+func get_clipboard_data() -> Array:
+ var clipboard_parse: Variant = str_to_var(DisplayServer.clipboard_get())
+
+ if clipboard_parse is Dictionary:
+ if clipboard_parse.has("project_name"):
+ if clipboard_parse.project_name != ProjectSettings.get_setting("application/config/name"):
+ print("[Dialogic] Be careful when copying from another project!")
+ if clipboard_parse.has('events'):
+ return clipboard_parse.events
+ return []
+
+
+func add_events_at_index(event_list:Array, at_index:int) -> void:
+ var new_indexed_events := {}
+
+ for i in range(len(event_list)):
+ new_indexed_events[at_index+i] = event_list[i]
+
+ add_events_indexed(new_indexed_events)
+
+
+func delete_events_at_index(at_index:int, amount:int = 1)-> void:
+ var new_indexed_events := {}
+ # delete_events_indexed actually only needs the keys, so we give trash as values
+ for i in range(amount):
+ new_indexed_events[at_index+i] = ""
+ delete_events_indexed(new_indexed_events)
+ indent_events()
+
+#endregion
+
+
+#region BLOCK SELECTION
+################################################################################
+
+func _is_item_selected(item: Node) -> bool:
+ return item in selected_items
+
+
+func select_item(item: Node, multi_possible:bool = true) -> void:
+ if item == null:
+ return
+
+ if Input.is_key_pressed(KEY_CTRL) and multi_possible:
+ # deselect the item if it is selected
+ if _is_item_selected(item):
+ selected_items.erase(item)
+ else:
+ selected_items.append(item)
+ elif Input.is_key_pressed(KEY_SHIFT) and multi_possible:
+ if len(selected_items) == 0:
+ selected_items = [item]
+ else:
+ var index: int = selected_items[-1].get_index()
+ var goal_idx := item.get_index()
+ while true:
+ if index < goal_idx: index += 1
+ else: index -= 1
+ if not %Timeline.get_child(index) in selected_items:
+ selected_items.append(%Timeline.get_child(index))
+
+ if index == goal_idx:
+ break
+ else:
+ if len(selected_items) == 1:
+ if _is_item_selected(item):
+ selected_items.erase(item)
+ else:
+ selected_items = [item]
+ else:
+ selected_items = [item]
+
+ sort_selection()
+ visual_update_selection()
+
+
+# checks all the events and sets their styles (selected/deselected)
+func visual_update_selection() -> void:
+ for item in %Timeline.get_children():
+ item.visual_deselect()
+ if 'end_node' in item and item.end_node != null:
+ item.end_node.unhighlight()
+ for item in selected_items:
+ item.visual_select()
+ if 'end_node' in item and item.end_node != null:
+ item.end_node.highlight()
+ %TimelineArea.queue_redraw()
+
+
+## Sorts the selection using 'custom_sort_selection'
+func sort_selection() -> void:
+ selected_items.sort_custom(custom_sort_selection)
+
+
+## Compares two event blocks based on their position in the timeline
+func custom_sort_selection(item1, item2) -> bool:
+ return item1.get_index() < item2.get_index()
+
+
+func select_all_items() -> void:
+ selected_items = []
+ for event in %Timeline.get_children():
+ selected_items.append(event)
+ visual_update_selection()
+
+
+func deselect_all_items() -> void:
+ selected_items = []
+ visual_update_selection()
+#endregion
+
+
+#region CREATING NEW EVENTS USING THE BUTTONS
+################################################################################
+
+# Event Creation signal for buttons
+# If force_resource is true, the event will be added with the actual resource
+func _add_event_button_pressed(event_resource:DialogicEvent, force_resource := false):
+ if %TimelineArea.get_global_rect().has_point(get_global_mouse_position()) and !force_resource:
+ return
+
+ var at_index := -1
+ if selected_items:
+ at_index = selected_items[-1].get_index()+1
+ else:
+ at_index = %Timeline.get_child_count()
+
+ var resource: DialogicEvent = null
+ if force_resource:
+ resource = event_resource
+ else:
+ resource = event_resource.duplicate()
+ resource._load_custom_defaults()
+
+ resource.created_by_button = true
+
+ add_event_undoable(resource, at_index)
+
+ resource.created_by_button = false
+
+ something_changed()
+ scroll_to_piece(at_index)
+ indent_events()
+#endregion
+
+
+#region BLOCK GETTERS
+################################################################################
+
+func get_block_above(block:Node) -> Node:
+ if block.get_index() > 0:
+ return %Timeline.get_child(block.get_index() - 1)
+ return null
+
+
+func get_block_below(block:Node) -> Node:
+ if block.get_index() < %Timeline.get_child_count() - 1:
+ return %Timeline.get_child(block.get_index() + 1)
+ return null
+#endregion
+
+
+#region BLOCK MOVEMENT
+################################################################################
+
+
+func move_blocks_to_index(blocks:Array, index:int):
+ # the amount of events that were BEFORE the new index (thus shifting the index)
+ var index_shift := 0
+ for event in blocks:
+ if event.resource is DialogicEndBranchEvent:
+ if !event.parent_node in blocks:
+ if index <= event.parent_node.get_index():
+ return
+ if "end_node" in event and event.end_node:
+ if !event.end_node in blocks:
+ if event.end_node.get_index() == event.get_index()+1:
+ blocks.append(event.end_node)
+ else:
+ return
+ index_shift += int(event.get_index() < index)
+
+ var do_indexes := {}
+ var undo_indexes := {}
+
+ var event_count := 0
+ for event in blocks:
+ do_indexes[event.get_index()] = index + event_count
+ undo_indexes[index -index_shift+event_count] = event.get_index()+index_shift*int(index < event.get_index())#+int((index -index_shift+event_count) < event.get_index())
+ event_count += 1
+
+ # complex check to avoid tangling conditions & choices
+ for idx in do_indexes:
+ var event := %Timeline.get_child(idx)
+ if !event.resource is DialogicEndBranchEvent and !event.resource.can_contain_events:
+ continue
+
+ if event.resource is DialogicEndBranchEvent:
+ if !event.parent_node or event.parent_node.get_index() in do_indexes:
+ continue
+ elif event.resource.can_contain_events:
+ if !event.end_node or event.end_node.get_index() in do_indexes:
+ continue
+
+ var check_from := 0
+ var check_to := 0
+
+ if event.resource is DialogicEndBranchEvent:
+ check_from = event.parent_node.get_index()+1
+ check_to = index
+ else:
+ check_from = index
+ check_to = event.end_node.get_index()
+
+ for c_idx in range(check_from, check_to):
+ if c_idx in do_indexes:
+ continue
+ var c_event := %Timeline.get_child(c_idx)
+ if c_event.resource is DialogicEndBranchEvent and c_event.parent_node.get_index() < check_from:
+ return
+ if c_event.resource.can_contain_events and c_event.end_node.get_index() > check_to:
+ return
+
+ TimelineUndoRedo.create_action('[D] Move events.')
+ TimelineUndoRedo.add_do_method(move_events_by_indexes.bind(do_indexes))
+ TimelineUndoRedo.add_undo_method(move_events_by_indexes.bind(undo_indexes))
+ TimelineUndoRedo.commit_action()
+
+
+func move_events_by_indexes(index_dict:Dictionary) -> void:
+ var sorted_indexes := index_dict.keys()
+ sorted_indexes.sort()
+
+ var evts := {}
+ var count := 0
+ for idx in sorted_indexes:
+ evts[idx] =%Timeline.get_child(idx-count)
+ %Timeline.remove_child(%Timeline.get_child(idx-count))
+ count += 1
+ if idx < index_dict[idx]:
+ index_dict[idx] -= len(sorted_indexes.filter(func(x):return x<=index_dict[idx]-count-1))
+
+ for idx in sorted_indexes:
+ %Timeline.add_child(evts[idx])
+ %Timeline.move_child(evts[idx], index_dict[idx])
+
+ indent_events()
+ visual_update_selection()
+ something_changed()
+
+
+func offset_blocks_by_index(blocks:Array, offset:int):
+ var do_indexes := {}
+ var undo_indexes := {}
+
+ for event in blocks:
+ if event.resource is DialogicEndBranchEvent:
+ if !event.parent_node in blocks:
+ if event.get_index()+offset+int(offset>0) <= event.parent_node.get_index():
+ continue
+ if "end_node" in event and event.end_node:
+ if !event.end_node in blocks:
+ if event.get_index()+offset+int(offset>0) > event.end_node.get_index():
+ if event.end_node.get_index() == event.get_index()+1:
+ blocks.append(event.end_node)
+ else:
+ return
+ do_indexes[event.get_index()] = event.get_index()+offset+int(offset>0)
+ undo_indexes[event.get_index()+offset] = event.get_index()+int(offset<0)
+
+
+ TimelineUndoRedo.create_action("[D] Move events.")
+ TimelineUndoRedo.add_do_method(move_events_by_indexes.bind(do_indexes))
+ TimelineUndoRedo.add_undo_method(move_events_by_indexes.bind(undo_indexes))
+
+ TimelineUndoRedo.commit_action()
+#endregion
+
+
+#region VISIBILITY/VISUALS
+################################################################################
+
+func scroll_to_piece(piece_index:int) -> void:
+ await get_tree().process_frame
+ var height: float = %Timeline.get_child(min(piece_index, %Timeline.get_child_count()-1)).position.y
+ if height < %TimelineArea.scroll_vertical or height > %TimelineArea.scroll_vertical+%TimelineArea.size.y:
+ %TimelineArea.scroll_vertical = height
+
+
+func indent_events() -> void:
+ var indent: int = 0
+ var event_list: Array = %Timeline.get_children()
+
+ if event_list.size() < 2:
+ return
+
+ var currently_hidden := false
+ var hidden_count := 0
+ var hidden_until: Control = null
+
+ # will be applied to the indent after the current event
+ var delayed_indent: int = 0
+
+ for block in event_list:
+ if (not "resource" in block):
+ continue
+
+ if (not currently_hidden) and block.resource.can_contain_events and block.end_node and block.collapsed:
+ currently_hidden = true
+ hidden_until = block.end_node
+ hidden_count = 0
+ elif currently_hidden and block == hidden_until:
+ block.update_hidden_events_indicator(hidden_count)
+ currently_hidden = false
+ hidden_until = null
+ elif currently_hidden:
+ block.hide()
+ hidden_count += 1
+ else:
+ block.show()
+ if block.resource is DialogicEndBranchEvent:
+ block.update_hidden_events_indicator(0)
+
+ delayed_indent = 0
+
+ if block.resource.can_contain_events:
+ delayed_indent = 1
+
+ if block.resource.wants_to_group:
+ indent += 1
+
+ elif block.resource is DialogicEndBranchEvent:
+ block.parent_node_changed()
+ delayed_indent -= 1
+ if block.parent_node.resource.wants_to_group:
+ delayed_indent -= 1
+
+ if indent >= 0:
+ block.set_indent(indent)
+ else:
+ block.set_indent(0)
+ indent += delayed_indent
+
+ await get_tree().process_frame
+ await get_tree().process_frame
+ %TimelineArea.queue_redraw()
+
+
+#region SPECIAL BLOCK OPERATIONS
+################################################################################
+
+func _on_event_popup_menu_index_pressed(index:int) -> void:
+ var item: Control = %EventPopupMenu.current_event
+ if index == 0:
+ if not item in selected_items:
+ selected_items = [item]
+ duplicate_selected()
+ elif index == 2:
+ if not item.resource.help_page_path.is_empty():
+ OS.shell_open(item.resource.help_page_path)
+ elif index == 3:
+ find_parent('EditorView').plugin_reference.get_editor_interface().set_main_screen_editor('Script')
+ find_parent('EditorView').plugin_reference.get_editor_interface().edit_script(item.resource.get_script(), 1, 1)
+ elif index == 5 or index == 6:
+ if index == 5:
+ offset_blocks_by_index(selected_items, -1)
+ else:
+ offset_blocks_by_index(selected_items, +1)
+
+ elif index == 8:
+ var events_indexed : Dictionary
+ if item in selected_items:
+ events_indexed = get_events_indexed(selected_items)
+ else:
+ events_indexed = get_events_indexed([item])
+ TimelineUndoRedo.create_action("[D] Deleting 1 event.")
+ TimelineUndoRedo.add_do_method(delete_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.add_undo_method(add_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.commit_action()
+ indent_events()
+
+
+func _on_right_sidebar_resized() -> void:
+ var _scale := DialogicUtil.get_editor_scale()
+
+ if %RightSidebar.size.x < 160 * _scale and (not sidebar_collapsed or not _initialized):
+ sidebar_collapsed = true
+
+ for section in %RightSidebar.get_node('EventContainer').get_children():
+
+ for con in section.get_children():
+
+ if con.get_child_count() == 0:
+ continue
+
+ if con.get_child(0) is Label:
+ con.get_child(0).hide()
+
+ elif con.get_child(0) is Button:
+
+ for button in con.get_children():
+ button.toggle_name(false)
+
+
+ elif %RightSidebar.size.x > 160 * _scale and (sidebar_collapsed or not _initialized):
+ sidebar_collapsed = false
+
+ for section in %RightSidebar.get_node('EventContainer').get_children():
+
+ for con in section.get_children():
+
+ if con.get_child_count() == 0:
+ continue
+
+ if con.get_child(0) is Label:
+ con.get_child(0).show()
+
+ elif con.get_child(0) is Button:
+ for button in con.get_children():
+ button.toggle_name(true)
+
+ if _initialized:
+ DialogicUtil.set_editor_setting("dialogic/editor/right_sidebar_width", %RightSidebar.size.x)
+ DialogicUtil.set_editor_setting("dialogic/editor/right_sidebar_collapsed", sidebar_collapsed)
+
+#endregion
+
+
+#region SHORTCUTS
+################################################################################
+
+func duplicate_selected() -> void:
+ if len(selected_items) > 0:
+ var events := get_events_indexed(selected_items).values()
+ var at_index: int = selected_items[-1].get_index()+1
+ TimelineUndoRedo.create_action("[D] Duplicate "+str(len(events))+" event(s).")
+ TimelineUndoRedo.add_do_method(add_events_at_index.bind(events, at_index))
+ TimelineUndoRedo.add_undo_method(delete_events_at_index.bind(at_index, len(events)))
+ TimelineUndoRedo.commit_action()
+
+
+func _input(event:InputEvent) -> void:
+ if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed == false:
+ drag_allowed = false
+
+ # we protect this with is_visible_in_tree to not
+ # invoke a shortcut by accident
+ if !((event is InputEventKey or !event is InputEventWithModifiers) and is_visible_in_tree()):
+ return
+
+
+ if "pressed" in event:
+ if !event.pressed:
+ return
+
+
+ ## Some shortcuts should always work
+ match event.as_text():
+ "Ctrl+T": # Add text event
+ _add_event_button_pressed(DialogicTextEvent.new(), true)
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+Shift+T", "Ctrl+Alt+T", "Ctrl+Option+T": # Add text event with current or previous character
+ get_viewport().set_input_as_handled()
+ var ev := DialogicTextEvent.new()
+ ev.character = get_previous_character(event.as_text() == "Ctrl+Alt+T" or event.as_text() == "Ctrl+Option+T")
+ _add_event_button_pressed(ev, true)
+
+ "Ctrl+E": # Add character join event
+ _add_event_button_pressed(DialogicCharacterEvent.new(), true)
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+Shift+E": # Add character update event
+ var ev := DialogicCharacterEvent.new()
+ ev.action = DialogicCharacterEvent.Actions.UPDATE
+ _add_event_button_pressed(ev, true)
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+Alt+E", "Ctrl+Option+E": # Add character leave event
+ var ev := DialogicCharacterEvent.new()
+ ev.action = DialogicCharacterEvent.Actions.LEAVE
+ _add_event_button_pressed(ev, true)
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+J": # Add jump event
+ _add_event_button_pressed(DialogicJumpEvent.new(), true)
+ get_viewport().set_input_as_handled()
+ "Ctrl+L": # Add label event
+ _add_event_button_pressed(DialogicLabelEvent.new(), true)
+ get_viewport().set_input_as_handled()
+
+ ## Some shortcuts should be disabled when writing text.
+ var focus_owner: Control = get_viewport().gui_get_focus_owner()
+ if focus_owner is TextEdit or focus_owner is LineEdit or (focus_owner is Button and focus_owner.get_parent_control().name == "Spin"):
+ return
+
+ match event.as_text():
+ "Ctrl+Z": # UNDO
+ TimelineUndoRedo.undo()
+ indent_events()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+Shift+Z", "Ctrl+Y": # REDO
+ TimelineUndoRedo.redo()
+ indent_events()
+ get_viewport().set_input_as_handled()
+
+ "Up": #select previous
+ if (len(selected_items) == 1):
+ var prev := maxi(0, selected_items[0].get_index() - 1)
+ var prev_node := %Timeline.get_child(prev)
+ if (prev_node != selected_items[0]):
+ selected_items = []
+ select_item(prev_node)
+ get_viewport().set_input_as_handled()
+
+ "Down": #select next
+ if (len(selected_items) == 1):
+ var next := mini(%Timeline.get_child_count() - 1, selected_items[0].get_index() + 1)
+ var next_node := %Timeline.get_child(next)
+ if (next_node != selected_items[0]):
+ selected_items = []
+ select_item(next_node)
+ get_viewport().set_input_as_handled()
+
+ "Delete":
+ if (len(selected_items) != 0):
+ var events_indexed := get_events_indexed(selected_items)
+ TimelineUndoRedo.create_action("[D] Deleting "+str(len(selected_items))+" event(s).")
+ TimelineUndoRedo.add_do_method(delete_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.add_undo_method(add_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.commit_action()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+A": # select all
+ if (len(selected_items) != 0):
+ select_all_items()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+Shift+A": # deselect all
+ if (len(selected_items) != 0):
+ deselect_all_items()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+C":
+ copy_selected_events()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+V":
+ var events_list := get_clipboard_data()
+ var paste_position := -1
+ if selected_items:
+ paste_position = selected_items[-1].get_index()+1
+ else:
+ paste_position = %Timeline.get_child_count()-1
+ if events_list:
+ TimelineUndoRedo.create_action("[D] Pasting "+str(len(events_list))+" event(s).")
+ TimelineUndoRedo.add_do_method(add_events_at_index.bind(events_list, paste_position))
+ TimelineUndoRedo.add_undo_method(delete_events_at_index.bind(paste_position+1, len(events_list)))
+ TimelineUndoRedo.commit_action()
+ get_viewport().set_input_as_handled()
+
+
+ "Ctrl+X":
+ var events_indexed := get_events_indexed(selected_items)
+ TimelineUndoRedo.create_action("[D] Cut "+str(len(selected_items))+" event(s).")
+ TimelineUndoRedo.add_do_method(cut_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.add_undo_method(add_events_indexed.bind(events_indexed))
+ TimelineUndoRedo.commit_action()
+ get_viewport().set_input_as_handled()
+
+ "Ctrl+D":
+ duplicate_selected()
+ get_viewport().set_input_as_handled()
+
+ "Alt+Up", "Option+Up":
+ if len(selected_items) > 0:
+ offset_blocks_by_index(selected_items, -1)
+
+ get_viewport().set_input_as_handled()
+
+ "Alt+Down", "Option+Down":
+ if len(selected_items) > 0:
+ offset_blocks_by_index(selected_items, +1)
+
+ get_viewport().set_input_as_handled()
+
+
+func get_previous_character(double_previous := false) -> DialogicCharacter:
+ var character: DialogicCharacter = null
+ var idx: int = %Timeline.get_child_count()
+ if idx == 0:
+ return null
+ if len(selected_items):
+ idx = selected_items[0].get_index()
+ var one_skipped := false
+ idx += 1
+ for i in range(selected_items[0].get_index()+1):
+ idx -= 1
+ if !('resource' in %Timeline.get_child(idx) and 'character' in %Timeline.get_child(idx).resource):
+ continue
+ if %Timeline.get_child(idx).resource.character == null:
+ continue
+ if double_previous:
+ if %Timeline.get_child(idx).resource.character == character:
+ continue
+ if character != null:
+ if one_skipped:
+ one_skipped = false
+ else:
+ character = %Timeline.get_child(idx).resource.character
+ break
+ character = %Timeline.get_child(idx).resource.character
+ else:
+ character = %Timeline.get_child(idx).resource.character
+ break
+ return character
+
+#endregion
+
+#region SEARCH
+################################################################################
+
+var search_results := {}
+func _search_timeline(search_text:String) -> bool:
+ for event in search_results:
+ if is_instance_valid(search_results[event]):
+ search_results[event].set_search_text("")
+ search_results[event].deselect()
+ search_results[event].queue_redraw()
+ search_results.clear()
+
+ for block in %Timeline.get_children():
+ if block.resource is DialogicTextEvent:
+ var text_field: TextEdit = block.get_node("%BodyContent").find_child("Field_Text_Multiline", true, false)
+ text_field.set_search_text(search_text)
+ if text_field.search(search_text, 0, 0, 0).x != -1:
+ search_results[block] = text_field
+ text_field.queue_redraw()
+ set_meta("current_search", search_text)
+ search_navigate(false)
+ return not search_results.is_empty()
+
+
+func _search_navigate_down() -> void:
+ search_navigate(false)
+
+
+func _search_navigate_up() -> void:
+ search_navigate(true)
+
+
+func search_navigate(navigate_up := false) -> void:
+ var search_text: String = get_meta("current_search", "")
+
+ if search_results.is_empty() or %Timeline.get_child_count() == 0:
+ return
+ if selected_items.is_empty():
+ select_item(%Timeline.get_child(0), false)
+
+ while not selected_items[0] in search_results:
+ select_item(%Timeline.get_child(wrapi(selected_items[0].get_index()+1, 0, %Timeline.get_child_count()-1)), false)
+
+ var event: Node = selected_items[0]
+ var counter := 0
+ while true:
+ counter += 1
+ var field: TextEdit = search_results[event]
+ field.queue_redraw()
+ var result := search_text_field(field, search_text, navigate_up)
+ var current_line := field.get_selection_from_line() if field.has_selection() else -1
+ var current_column := field.get_selection_from_column() if field.has_selection() else -1
+ var next_is_in_this_event := false
+ if result.y == -1:
+ next_is_in_this_event = false
+ elif navigate_up:
+ if current_line == -1:
+ current_line = field.get_line_count()-1
+ current_column = field.get_line(current_line).length()
+ next_is_in_this_event = result.x < current_column or result.y < current_line
+ else:
+ next_is_in_this_event = result.x > current_column or result.y > current_line
+
+ if next_is_in_this_event:
+ if not event in selected_items:
+ select_item(event, false)
+ %TimelineArea.ensure_control_visible(event)
+ event._on_ToggleBodyVisibility_toggled(true)
+ field.select(result.y, result.x, result.y, result.x+len(search_text))
+ break
+
+ else:
+ field.deselect()
+ var index := search_results.keys().find(event)
+ event = search_results.keys()[wrapi(index+(-1 if navigate_up else 1), 0, search_results.size())]
+
+ if counter > 5:
+ print("[Dialogic] Search failed.")
+ break
+
+
+func search_text_field(field:TextEdit, search_text := "", navigate_up:= false) -> Vector2i:
+ var search_from_line: int = 0
+ var search_from_column: int = 0
+ if field.has_selection():
+ if navigate_up:
+ search_from_line = field.get_selection_from_line()
+ search_from_column = field.get_selection_from_column()-1
+ if search_from_column == -1:
+ search_from_line -= 1
+ if search_from_line == -1:
+ return Vector2i(-1, -1)
+ search_from_column = field.get_line(search_from_line).length()-1
+ else:
+ search_from_line = field.get_selection_to_line()
+ search_from_column = field.get_selection_to_column()
+ else:
+ if navigate_up:
+ search_from_line = field.get_line_count()-1
+ search_from_column = field.get_line(search_from_line).length()-1
+
+ var search := field.search(search_text, 4 if navigate_up else 0, search_from_line, search_from_column)
+ return search
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn
new file mode 100644
index 0000000..c3e4ffb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn
@@ -0,0 +1,111 @@
+[gd_scene load_steps=10 format=3 uid="uid://ysqbusmy0qma"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd" id="1_8smxc"]
+[ext_resource type="Theme" uid="uid://cqst728xxipcw" path="res://addons/dialogic/Editor/Theme/MainTheme.tres" id="2_x0fhp"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd" id="3_sap1x"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd" id="4_ugiq6"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_phyjj"]
+content_margin_top = 10.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_plab4"]
+bg_color = Color(0, 0, 0, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dov6v"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(1, 0.365, 0.365, 1)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_detail = 1
+
+[sub_resource type="Image" id="Image_y3447"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_vg181"]
+image = SubResource("Image_y3447")
+
+[node name="TimelineVisualEditor" type="MarginContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 5
+theme_override_constants/margin_right = 5
+theme_override_constants/margin_bottom = 5
+script = ExtResource("1_8smxc")
+
+[node name="View" type="HSplitContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme = ExtResource("2_x0fhp")
+
+[node name="TimelineArea" type="ScrollContainer" parent="View"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_phyjj")
+script = ExtResource("3_sap1x")
+
+[node name="Timeline" type="VBoxContainer" parent="View/TimelineArea"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="EventPopupMenu" type="PopupMenu" parent="View/TimelineArea"]
+unique_name_in_owner = true
+size = Vector2i(165, 124)
+theme_override_styles/panel = SubResource("StyleBoxFlat_plab4")
+theme_override_styles/hover = SubResource("StyleBoxFlat_dov6v")
+item_count = 6
+item_0/text = "Documentation"
+item_0/icon = SubResource("ImageTexture_vg181")
+item_0/id = 0
+item_1/text = ""
+item_1/id = -1
+item_1/separator = true
+item_2/text = "Move up"
+item_2/icon = SubResource("ImageTexture_vg181")
+item_2/id = 2
+item_3/text = "Move down"
+item_3/icon = SubResource("ImageTexture_vg181")
+item_3/id = 3
+item_4/text = ""
+item_4/id = -1
+item_4/separator = true
+item_5/text = "Delete"
+item_5/icon = SubResource("ImageTexture_vg181")
+item_5/id = 5
+script = ExtResource("4_ugiq6")
+
+[node name="RightSidebar" type="ScrollContainer" parent="View"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(50, 0)
+layout_mode = 2
+size_flags_stretch_ratio = 0.2
+horizontal_scroll_mode = 0
+
+[node name="EventContainer" type="VBoxContainer" parent="View/RightSidebar"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.2
+
+[connection signal="drag_completed" from="View/TimelineArea" to="." method="_on_timeline_area_drag_completed"]
+[connection signal="index_pressed" from="View/TimelineArea/EventPopupMenu" to="." method="_on_event_popup_menu_index_pressed"]
+[connection signal="resized" from="View/RightSidebar" to="." method="_on_right_sidebar_resized"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd
new file mode 100644
index 0000000..3c1d170
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd
@@ -0,0 +1,44 @@
+extends Control
+
+func _ready() -> void:
+ print("[Dialogic] Testing scene was started.")
+ if not ProjectSettings.get_setting('internationalization/locale/test', "").is_empty():
+ print("Testing locale is: ", ProjectSettings.get_setting('internationalization/locale/test'))
+ $PauseIndictator.hide()
+
+ var scene: Node = DialogicUtil.autoload().Styles.load_style(DialogicUtil.get_editor_setting('current_test_style', ''))
+ if not scene is CanvasLayer:
+ if scene is Control:
+ scene.position = get_viewport_rect().size/2.0
+ if scene is Node2D:
+ scene.position = get_viewport_rect().size/2.0
+
+ randomize()
+ var current_timeline: String = DialogicUtil.get_editor_setting("current_timeline_path", "")
+ if not current_timeline:
+ get_tree().quit()
+ DialogicUtil.autoload().start(current_timeline)
+ DialogicUtil.autoload().timeline_ended.connect(get_tree().quit)
+ DialogicUtil.autoload().signal_event.connect(receive_event_signal)
+ DialogicUtil.autoload().text_signal.connect(receive_text_signal)
+
+func receive_event_signal(argument:String) -> void:
+ print("[Dialogic] Encountered a signal event: ", argument)
+
+func receive_text_signal(argument:String) -> void:
+ print("[Dialogic] Encountered a signal in text: ", argument)
+
+func _input(event:InputEvent) -> void:
+ if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
+ DialogicUtil.autoload().paused = !DialogicUtil.autoload().paused
+ $PauseIndictator.visible = DialogicUtil.autoload().paused
+
+ if (event is InputEventMouseButton
+ and event.is_pressed()
+ and event.button_index == MOUSE_BUTTON_MIDDLE):
+ var auto_skip: DialogicAutoSkip = DialogicUtil.autoload().Inputs.auto_skip
+ var is_auto_skip_enabled := auto_skip.enabled
+
+ auto_skip.disable_on_unread_text = false
+ auto_skip.enabled = not is_auto_skip_enabled
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn
new file mode 100644
index 0000000..8789476
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn
@@ -0,0 +1,25 @@
+[gd_scene load_steps=2 format=3 uid="uid://ud18ke1g2nw4"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd" id="1_bamud"]
+
+[node name="TestTimelineScene" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_bamud")
+
+[node name="PauseIndictator" type="Label" parent="."]
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -65.0
+offset_top = 7.0
+offset_right = -8.0
+offset_bottom = 33.0
+grow_horizontal = 0
+text = "Paused"
+metadata/_edit_layout_mode = 1
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd
new file mode 100644
index 0000000..e5302a7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd
@@ -0,0 +1,238 @@
+@tool
+extends DialogicEditor
+
+## Editor that holds both the visual and the text timeline editors.
+
+# references
+enum EditorMode {VISUAL, TEXT}
+
+var current_editor_mode := EditorMode.VISUAL
+var play_timeline_button: Button = null
+
+
+## Overwrite. Register to the editor manager in here.
+func _register() -> void:
+ resource_unsaved.connect(_on_resource_unsaved)
+ resource_saved.connect(_on_resource_saved)
+
+ # register editor
+ editors_manager.register_resource_editor('dtl', self)
+ # add timeline button
+ var add_timeline_button: Button = editors_manager.add_icon_button(
+ load("res://addons/dialogic/Editor/Images/Toolbar/add-timeline.svg"),
+ "Add Timeline",
+ self)
+ add_timeline_button.pressed.connect(_on_create_timeline_button_pressed)
+ add_timeline_button.shortcut = Shortcut.new()
+ add_timeline_button.shortcut.events.append(InputEventKey.new())
+ add_timeline_button.shortcut.events[0].keycode = KEY_1
+ add_timeline_button.shortcut.events[0].ctrl_pressed = true
+ # play timeline button
+ play_timeline_button = editors_manager.add_custom_button(
+ "Play Timeline",
+ get_theme_icon("PlayScene", "EditorIcons"),
+ self)
+ play_timeline_button.pressed.connect(play_timeline)
+ play_timeline_button.tooltip_text = "Play the current timeline (CTRL+F5)"
+ if OS.get_name() == "macOS":
+ play_timeline_button.tooltip_text = "Play the current timeline (CTRL+B)"
+
+ %VisualEditor.load_event_buttons()
+
+ current_editor_mode = DialogicUtil.get_editor_setting('timeline_editor_mode', 0)
+
+ match current_editor_mode:
+ EditorMode.VISUAL:
+ %VisualEditor.show()
+ %TextEditor.hide()
+ %SwitchEditorMode.text = "Text Editor"
+ EditorMode.TEXT:
+ %VisualEditor.hide()
+ %TextEditor.show()
+ %SwitchEditorMode.text = "Visual Editor"
+
+ $NoTimelineScreen.show()
+ play_timeline_button.disabled = true
+
+
+func _get_title() -> String:
+ return "Timeline"
+
+
+func _get_icon() -> Texture:
+ return get_theme_icon("TripleBar", "EditorIcons")
+
+
+## If this editor supports editing resources, load them here (overwrite in subclass)
+func _open_resource(resource:Resource) -> void:
+ current_resource = resource
+ current_resource_state = ResourceStates.SAVED
+ match current_editor_mode:
+ EditorMode.VISUAL:
+ %VisualEditor.load_timeline(current_resource)
+ EditorMode.TEXT:
+ %TextEditor.load_timeline(current_resource)
+ $NoTimelineScreen.hide()
+ %TimelineName.text = DialogicResourceUtil.get_unique_identifier(current_resource.resource_path)
+ play_timeline_button.disabled = false
+
+
+## If this editor supports editing resources, save them here (overwrite in subclass)
+func _save() -> void:
+ match current_editor_mode:
+ EditorMode.VISUAL:
+ %VisualEditor.save_timeline()
+ EditorMode.TEXT:
+ %TextEditor.save_timeline()
+
+
+func _input(event: InputEvent) -> void:
+ if event is InputEventKey:
+ var keycode := KEY_F5
+ if OS.get_name() == "macOS":
+ keycode = KEY_B
+ if event.keycode == keycode and event.pressed:
+ if Input.is_key_pressed(KEY_CTRL):
+ play_timeline()
+
+ if event.keycode == KEY_F and event.pressed:
+ if Input.is_key_pressed(KEY_CTRL):
+ if is_ancestor_of(get_viewport().gui_get_focus_owner()):
+ search_timeline()
+
+
+## Method to play the current timeline. Connected to the button in the sidebar.
+func play_timeline() -> void:
+ _save()
+
+ var dialogic_plugin := DialogicUtil.get_dialogic_plugin()
+
+ # Save the current opened timeline
+ DialogicUtil.set_editor_setting('current_timeline_path', current_resource.resource_path)
+
+ DialogicUtil.get_dialogic_plugin().get_editor_interface().play_custom_scene("res://addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn")
+
+
+## Method to switch from visual to text editor (and vice versa). Connected to the button in the sidebar.
+func toggle_editor_mode() -> void:
+ match current_editor_mode:
+ EditorMode.VISUAL:
+ current_editor_mode = EditorMode.TEXT
+ %VisualEditor.save_timeline()
+ %VisualEditor.hide()
+ %TextEditor.show()
+ %TextEditor.load_timeline(current_resource)
+ %SwitchEditorMode.text = "Visual Editor"
+ EditorMode.TEXT:
+ current_editor_mode = EditorMode.VISUAL
+ %TextEditor.save_timeline()
+ %TextEditor.hide()
+ %VisualEditor.load_timeline(current_resource)
+ %VisualEditor.show()
+ %SwitchEditorMode.text = "Text Editor"
+ _on_search_text_changed(%Search.text)
+ DialogicUtil.set_editor_setting('timeline_editor_mode', current_editor_mode)
+
+
+func _on_resource_unsaved() -> void:
+ if current_resource:
+ current_resource.set_meta("timeline_not_saved", true)
+
+
+func _on_resource_saved() -> void:
+ if current_resource:
+ current_resource.set_meta("timeline_not_saved", false)
+
+
+func new_timeline(path:String) -> void:
+ _save()
+ var new_timeline := DialogicTimeline.new()
+ new_timeline.resource_path = path
+ new_timeline.set_meta('timeline_not_saved', true)
+ var err := ResourceSaver.save(new_timeline)
+ DialogicResourceUtil.update_directory('dtl')
+ editors_manager.edit_resource(new_timeline)
+
+
+func _ready() -> void:
+ $NoTimelineScreen.add_theme_stylebox_override("panel", get_theme_stylebox("Background", "EditorStyles"))
+
+ # switch editor mode button
+ %SwitchEditorMode.text = "Text editor"
+ %SwitchEditorMode.icon = get_theme_icon("ArrowRight", "EditorIcons")
+ %SwitchEditorMode.pressed.connect(toggle_editor_mode)
+ %SwitchEditorMode.custom_minimum_size.x = 200 * DialogicUtil.get_editor_scale()
+
+ %SearchClose.icon = get_theme_icon("Close", "EditorIcons")
+ %SearchUp.icon = get_theme_icon("MoveUp", "EditorIcons")
+ %SearchDown.icon = get_theme_icon("MoveDown", "EditorIcons")
+
+
+
+func _on_create_timeline_button_pressed() -> void:
+ editors_manager.show_add_resource_dialog(
+ new_timeline,
+ '*.dtl; DialogicTimeline',
+ 'Create new timeline',
+ 'timeline',
+ )
+
+
+func _clear() -> void:
+ current_resource = null
+ current_resource_state = ResourceStates.SAVED
+ match current_editor_mode:
+ EditorMode.VISUAL:
+ %VisualEditor.clear_timeline_nodes()
+ EditorMode.TEXT:
+ %TextEditor.clear_timeline()
+ $NoTimelineScreen.show()
+ play_timeline_button.disabled = true
+
+
+func get_current_editor() -> Node:
+ if current_editor_mode == 1:
+ return %TextEditor
+ return %VisualEditor
+
+#region SEARCH
+
+func search_timeline() -> void:
+ %SearchSection.show()
+ if get_viewport().gui_get_focus_owner() is TextEdit:
+ %Search.text = get_viewport().gui_get_focus_owner().get_selected_text()
+ _on_search_text_changed(%Search.text)
+ else:
+ %Search.text = ""
+ %Search.grab_focus()
+
+
+func _on_close_search_pressed() -> void:
+ %SearchSection.hide()
+ %Search.text = ""
+ _on_search_text_changed('')
+
+
+func _on_search_text_changed(new_text: String) -> void:
+ var editor: Node = null
+ var anything_found: bool = get_current_editor()._search_timeline(new_text)
+ if anything_found or new_text.is_empty():
+ %SearchLabel.hide()
+ %Search.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
+ else:
+ %SearchLabel.show()
+ %SearchLabel.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
+ %Search.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
+ %SearchLabel.text = "No Match"
+
+
+func _on_search_down_pressed() -> void:
+ get_current_editor()._search_navigate_down()
+
+
+func _on_search_up_pressed() -> void:
+ get_current_editor()._search_navigate_up()
+
+#endregion
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn
new file mode 100644
index 0000000..ba77fb4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn
@@ -0,0 +1,163 @@
+[gd_scene load_steps=10 format=3 uid="uid://crce0na84rhfd"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/timeline_editor.gd" id="1_4aceh"]
+[ext_resource type="PackedScene" uid="uid://ysqbusmy0qma" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn" id="2_qs7vc"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_yqd26"]
+[ext_resource type="PackedScene" uid="uid://defdeav8rli6o" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn" id="3_up2bn"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd" id="4_1t6bf"]
+
+[sub_resource type="Image" id="Image_43fqw"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_lvr8x"]
+image = SubResource("Image_43fqw")
+
+[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_7lpql"]
+script = ExtResource("4_1t6bf")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lpeon"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(1, 0.365, 0.365, 1)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_detail = 1
+
+[node name="Timeline" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_4aceh")
+
+[node name="VBox" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HBox" type="HBoxContainer" parent="VBox"]
+layout_mode = 2
+
+[node name="TimelineName" type="Label" parent="VBox/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_type_variation = &"DialogicTitle"
+text = "Cool Name"
+
+[node name="NameTooltip" parent="VBox/HBox" instance=ExtResource("2_yqd26")]
+layout_mode = 2
+tooltip_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
+This is what you should use in a jump event to reference this timeline.
+
+You can also use this name in Dialogic.start()."
+texture = SubResource("ImageTexture_lvr8x")
+hint_text = "This unique identifier is based on the file name. You can change it in the Reference Manager.
+This is what you should use in a jump event to reference this timeline.
+
+You can also use this name in Dialogic.start()."
+
+[node name="SwitchEditorMode" type="Button" parent="VBox/HBox"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(200, 0)
+layout_mode = 2
+size_flags_horizontal = 10
+size_flags_vertical = 4
+tooltip_text = "Switch between Text Editor and Visual Editor"
+text = "Text editor"
+icon = SubResource("ImageTexture_lvr8x")
+
+[node name="VisualEditor" parent="VBox" instance=ExtResource("2_qs7vc")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/margin_left = 0
+theme_override_constants/margin_top = 0
+theme_override_constants/margin_right = 0
+theme_override_constants/margin_bottom = 0
+
+[node name="TextEditor" parent="VBox" instance=ExtResource("3_up2bn")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+syntax_highlighter = SubResource("SyntaxHighlighter_7lpql")
+symbol_lookup_on_click = true
+line_folding = false
+gutters_draw_fold_gutter = false
+
+[node name="SearchSection" type="HBoxContainer" parent="VBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="Search" type="LineEdit" parent="VBox/SearchSection"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Search"
+
+[node name="SearchLabel" type="Label" parent="VBox/SearchSection"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="SearchUp" type="Button" parent="VBox/SearchSection"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="SearchDown" type="Button" parent="VBox/SearchSection"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="SearchClose" type="Button" parent="VBox/SearchSection"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="NoTimelineScreen" type="PanelContainer" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_lpeon")
+
+[node name="CenterContainer" type="CenterContainer" parent="NoTimelineScreen"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="NoTimelineScreen/CenterContainer"]
+custom_minimum_size = Vector2(250, 0)
+layout_mode = 2
+
+[node name="Label" type="Label" parent="NoTimelineScreen/CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "No timeline opened.
+Create a timeline or double-click one in the file system dock."
+horizontal_alignment = 1
+autowrap_mode = 3
+
+[node name="CreateTimelineButton" type="Button" parent="NoTimelineScreen/CenterContainer/VBoxContainer"]
+layout_mode = 2
+text = "Create New Timeline"
+
+[connection signal="text_changed" from="VBox/SearchSection/Search" to="." method="_on_search_text_changed"]
+[connection signal="pressed" from="VBox/SearchSection/SearchUp" to="." method="_on_search_up_pressed"]
+[connection signal="pressed" from="VBox/SearchSection/SearchDown" to="." method="_on_search_down_pressed"]
+[connection signal="pressed" from="VBox/SearchSection/SearchClose" to="." method="_on_close_search_pressed"]
+[connection signal="pressed" from="NoTimelineScreen/CenterContainer/VBoxContainer/CreateTimelineButton" to="." method="_on_create_timeline_button_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/dialogic_editor.gd b/addons/dialogic-main/addons/dialogic/Editor/dialogic_editor.gd
new file mode 100644
index 0000000..353958a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/dialogic_editor.gd
@@ -0,0 +1,66 @@
+@tool
+class_name DialogicEditor
+extends Control
+
+## Base class for all dialogic editors.
+
+# These signals will automatically be emitted if current_resource_state is changed.
+signal resource_saved()
+signal resource_unsaved()
+
+signal opened
+
+var current_resource: Resource
+
+## State of the current resource
+enum ResourceStates {SAVED, UNSAVED}
+var current_resource_state: ResourceStates:
+ set(value):
+ current_resource_state = value
+ if value == ResourceStates.SAVED:
+ resource_saved.emit()
+ else:
+ resource_unsaved.emit()
+
+var editors_manager: Control
+# text displayed on the current resource label on non-resource editors
+var alternative_text: String = ""
+
+## Overwrite. Register to the editor manager in here.
+func _register() -> void:
+ pass
+
+
+## Used on the tab
+func _get_icon() -> Texture:
+ return null
+
+## Used on the tab
+func _get_title() -> String:
+ return ""
+
+
+## If this editor supports editing resources, load them here (overwrite in subclass)
+func _open_resource(_resource:Resource) -> void:
+ pass
+
+
+## If this editor supports editing resources, save them here (overwrite in subclass)
+func _save() -> void:
+ pass
+
+
+## Overwrite. Called when this editor is shown. (show() doesn't have to be called)
+func _open(_extra_info:Variant = null) -> void:
+ pass
+
+
+## Overwrite. Called when another editor is opened. (hide() doesn't have to be called)
+func _close() -> void:
+ pass
+
+
+## Overwrite. Called to clear all current state and resource from the editor.
+## Although rarely used, sometimes you just want NO timeline to be open.
+func _clear() -> void:
+ pass
diff --git a/addons/dialogic-main/addons/dialogic/Editor/editor_main.gd b/addons/dialogic-main/addons/dialogic/Editor/editor_main.gd
new file mode 100644
index 0000000..5994057
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/editor_main.gd
@@ -0,0 +1,336 @@
+@tool
+extends Control
+
+## Editor root node. Most editor functionality is handled by EditorsManager node!
+
+var plugin_reference: EditorPlugin = null
+var editors_manager: Control = null
+
+var editor_file_dialog: EditorFileDialog
+
+@onready var sidebar := %Sidebar as DialogicSidebar
+
+func _ready() -> void:
+ if get_parent() is SubViewport:
+ return
+
+ ## CONNECTIONS
+ sidebar.show_sidebar.connect(_on_sidebar_toggled)
+
+ ## REFERENCES
+ editors_manager = $EditorsManager
+ var button: Button = editors_manager.add_icon_button(
+ get_theme_icon("MakeFloating", "EditorIcons"), "Make floating"
+ )
+ button.pressed.connect(toggle_floating_window)
+
+ # File dialog
+ editor_file_dialog = EditorFileDialog.new()
+ add_child(editor_file_dialog)
+
+ var info_message := Label.new()
+ info_message.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+ editor_file_dialog.get_line_edit().get_parent().add_sibling(info_message)
+ info_message.get_parent().move_child(info_message, info_message.get_index() - 1)
+ editor_file_dialog.set_meta("info_message_label", info_message)
+
+ $SaveConfirmationDialog.add_button("No Saving Please!", true, "nosave")
+ $SaveConfirmationDialog.hide()
+ update_theme_additions()
+ EditorInterface.get_base_control().theme_changed.connect(update_theme_additions)
+
+
+func _on_sidebar_toggled(sidebar_shown: bool) -> void:
+ var h_split := (%HSplit as HSplitContainer)
+ if sidebar_shown:
+ h_split.dragger_visibility = SplitContainer.DRAGGER_VISIBLE
+ h_split.split_offset = 150
+ h_split.collapsed = false
+ else:
+ h_split.dragger_visibility = SplitContainer.DRAGGER_HIDDEN_COLLAPSED
+ h_split.split_offset = 0
+ h_split.collapsed = true
+
+
+func update_theme_additions() -> void:
+ add_theme_stylebox_override(
+ "panel",
+ (
+ DCSS
+ . inline(
+ {
+ "background": get_theme_color("base_color", "Editor"),
+ "padding":
+ [5 * DialogicUtil.get_editor_scale(), 5 * DialogicUtil.get_editor_scale()],
+ }
+ )
+ )
+ )
+ var holder_panel := (
+ DCSS
+ . inline(
+ {
+ "border-radius": 5,
+ #'border': 2,
+ #'border-color': get_theme_color("base_color", "Editor"),
+ "background": get_theme_color("dark_color_2", "Editor"),
+ "padding":
+ [5 * DialogicUtil.get_editor_scale(), 5 * DialogicUtil.get_editor_scale()],
+ }
+ )
+ )
+ holder_panel.border_width_top = 0
+ holder_panel.corner_radius_top_left = 0
+ editors_manager.editors_holder.add_theme_stylebox_override("panel", holder_panel)
+
+ if theme == null:
+ theme = Theme.new()
+ theme.clear()
+
+ theme.set_type_variation("DialogicTitle", "Label")
+ theme.set_font("font", "DialogicTitle", get_theme_font("title", "EditorFonts"))
+ theme.set_color("font_color", "DialogicTitle", get_theme_color("warning_color", "Editor"))
+ theme.set_color(
+ "font_uneditable_color", "DialogicTitle", get_theme_color("warning_color", "Editor")
+ )
+ theme.set_color(
+ "font_selected_color", "DialogicTitle", get_theme_color("warning_color", "Editor")
+ )
+ theme.set_font_size(
+ "font_size", "DialogicTitle", get_theme_font_size("doc_size", "EditorFonts")
+ )
+
+ theme.set_type_variation("DialogicSubTitle", "Label")
+ theme.set_font("font", "DialogicSubTitle", get_theme_font("title", "EditorFonts"))
+ theme.set_font_size(
+ "font_size", "DialogicSubTitle", get_theme_font_size("doc_size", "EditorFonts")
+ )
+ theme.set_color("font_color", "DialogicSubTitle", get_theme_color("accent_color", "Editor"))
+
+ theme.set_type_variation("DialogicPanelA", "PanelContainer")
+ var panel_style := (
+ DCSS
+ . inline(
+ {
+ "border-radius": 10,
+ "background": get_theme_color("base_color", "Editor"),
+ "padding": [5, 5],
+ }
+ )
+ )
+ theme.set_stylebox("panel", "DialogicPanelA", panel_style)
+ theme.set_stylebox("normal", "DialogicPanelA", panel_style)
+
+ var dark_panel := panel_style.duplicate()
+ dark_panel.bg_color = get_theme_color("dark_color_3", "Editor")
+ theme.set_stylebox("panel", "DialogicPanelDarkA", dark_panel)
+
+ var cornerless_panel := panel_style.duplicate()
+ cornerless_panel.corner_radius_top_left = 0
+ theme.set_stylebox("panel", "DialogicPanelA_cornerless", cornerless_panel)
+
+ # panel used for example for portrait previews in character editor
+ theme.set_type_variation("DialogicPanelB", "PanelContainer")
+ var side_panel: StyleBoxFlat = panel_style.duplicate()
+ side_panel.corner_radius_top_left = 0
+ side_panel.corner_radius_bottom_left = 0
+ side_panel.expand_margin_left = get_theme_constant("separation", "SplitContainer")
+ side_panel.bg_color = get_theme_color("dark_color_2", "Editor")
+ side_panel.set_border_width_all(1)
+ side_panel.border_width_left = 0
+ side_panel.border_color = get_theme_color("contrast_color_2", "Editor")
+ theme.set_stylebox("panel", "DialogicPanelB", side_panel)
+
+ theme.set_type_variation("DialogicEventEdit", "Control")
+ var edit_panel := StyleBoxFlat.new()
+ edit_panel.draw_center = true
+ edit_panel.bg_color = get_theme_color("accent_color", "Editor")
+ edit_panel.bg_color.a = 0.05
+ edit_panel.border_width_bottom = 2
+ edit_panel.border_color = get_theme_color("accent_color", "Editor").lerp(
+ get_theme_color("dark_color_2", "Editor"), 0.4
+ )
+ edit_panel.content_margin_left = 5
+ edit_panel.content_margin_right = 5
+ edit_panel.set_corner_radius_all(1)
+ theme.set_stylebox("panel", "DialogicEventEdit", edit_panel)
+ theme.set_stylebox("normal", "DialogicEventEdit", edit_panel)
+
+ var focus_edit := edit_panel.duplicate()
+ focus_edit.border_color = get_theme_color("property_color_z", "Editor")
+ focus_edit.draw_center = false
+ theme.set_stylebox("focus", "DialogicEventEdit", focus_edit)
+
+ var hover_edit := edit_panel.duplicate()
+ hover_edit.border_color = get_theme_color("warning_color", "Editor")
+
+ theme.set_stylebox("hover", "DialogicEventEdit", hover_edit)
+ var disabled_edit := edit_panel.duplicate()
+ disabled_edit.border_color = get_theme_color("property_color", "Editor")
+ theme.set_stylebox("disabled", "DialogicEventEdit", disabled_edit)
+
+ theme.set_type_variation("DialogicHintText", "Label")
+ theme.set_color("font_color", "DialogicHintText", get_theme_color("readonly_color", "Editor"))
+ theme.set_font("font", "DialogicHintText", get_theme_font("doc_italic", "EditorFonts"))
+
+ theme.set_type_variation("DialogicHintText2", "Label")
+ theme.set_color(
+ "font_color", "DialogicHintText2", get_theme_color("property_color_w", "Editor")
+ )
+ theme.set_font("font", "DialogicHintText2", get_theme_font("doc_italic", "EditorFonts"))
+
+ theme.set_type_variation("DialogicSection", "Label")
+ theme.set_font("font", "DialogicSection", get_theme_font("main_msdf", "EditorFonts"))
+ theme.set_color("font_color", "DialogicSection", get_theme_color("property_color_z", "Editor"))
+ theme.set_font_size(
+ "font_size", "DialogicSection", get_theme_font_size("doc_size", "EditorFonts")
+ )
+
+ theme.set_type_variation("DialogicSettingsSection", "DialogicSection")
+ theme.set_font("font", "DialogicSettingsSection", get_theme_font("main_msdf", "EditorFonts"))
+ theme.set_color(
+ "font_color", "DialogicSettingsSection", get_theme_color("property_color_z", "Editor")
+ )
+ theme.set_font_size(
+ "font_size", "DialogicSettingsSection", get_theme_font_size("doc_size", "EditorFonts")
+ )
+
+ theme.set_type_variation("DialogicSectionBig", "DialogicSection")
+ theme.set_color("font_color", "DialogicSectionBig", get_theme_color("accent_color", "Editor"))
+ theme.set_font_size(
+ "font_size", "DialogicSectionBig", get_theme_font_size("doc_title_size", "EditorFonts")
+ )
+
+ theme.set_type_variation("DialogicLink", "LinkButton")
+ theme.set_color("font_hover_color", "DialogicLink", get_theme_color("warning_color", "Editor"))
+
+ theme.set_type_variation("DialogicMegaSeparator", "HSeparator")
+ (
+ theme
+ . set_stylebox(
+ "separator",
+ "DialogicMegaSeparator",
+ (
+ DCSS
+ . inline(
+ {
+ "border-radius": 10,
+ "border": 0,
+ "background": get_theme_color("accent_color", "Editor"),
+ "padding": [5, 5],
+ }
+ )
+ )
+ )
+ )
+ theme.set_constant("separation", "DialogicMegaSeparator", 50)
+
+ theme.set_type_variation("DialogicTextEventTextEdit", "CodeEdit")
+ var editor_settings := plugin_reference.get_editor_interface().get_editor_settings()
+ var text_panel := (
+ DCSS
+ . inline(
+ {
+ "border-radius": 8,
+ "background":
+ editor_settings.get_setting("text_editor/theme/highlighting/background_color").lerp(
+ editor_settings.get_setting("text_editor/theme/highlighting/text_color"), 0.05
+ ),
+ "padding": [8, 8],
+ }
+ )
+ )
+ text_panel.content_margin_bottom = 5
+ text_panel.content_margin_left = 13
+ theme.set_stylebox("normal", "DialogicTextEventTextEdit", text_panel)
+
+ var event_field_group_panel := DCSS.inline({
+ 'border-radius': 8,
+ "border":1,
+ "padding":2,
+ "boder-color": get_theme_color("property_color", "Editor"),
+ "background":"none"})
+ theme.set_type_variation("DialogicEventEditGroup", "PanelContainer")
+ theme.set_stylebox("panel", "DialogicEventEditGroup", event_field_group_panel)
+
+ theme.set_icon('Plugin', 'Dialogic', load("res://addons/dialogic/Editor/Images/plugin-icon.svg"))
+
+
+## Switches from floating window mode to embedded mode based on current mode
+func toggle_floating_window() -> void:
+ if get_parent() is Window:
+ swap_to_embedded_editor()
+ else:
+ swap_to_floating_window()
+
+
+## Removes the main control from it's parent and adds it to a new Window node
+func swap_to_floating_window() -> void:
+ if get_parent() is Window:
+ return
+
+ var parent := get_parent()
+ get_parent().remove_child(self)
+ var window := Window.new()
+ parent.add_child(window)
+ window.add_child(self)
+ window.title = "Dialogic"
+ window.close_requested.connect(swap_to_embedded_editor)
+ window.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
+ window.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
+ window.size = size
+ window.min_size = Vector2(500, 500)
+ set_anchors_preset(Control.PRESET_FULL_RECT)
+ window.disable_3d = true
+ window.wrap_controls = true
+ window.popup_centered()
+ plugin_reference.get_editor_interface().set_main_screen_editor("2D")
+
+
+## Removes the main control from the window node and adds it to it's grandparent
+## which is the original owner.
+func swap_to_embedded_editor() -> void:
+ if not get_parent() is Window:
+ return
+
+ var window := get_parent()
+ get_parent().remove_child(self)
+ plugin_reference.get_editor_interface().set_main_screen_editor("Dialogic")
+ window.get_parent().add_child(self)
+ window.queue_free()
+
+
+func godot_file_dialog(
+ callable: Callable,
+ filter: String,
+ mode := EditorFileDialog.FILE_MODE_OPEN_FILE,
+ window_title := "Save",
+ current_file_name := "New_File",
+ saving_something := false,
+ extra_message: String = ""
+) -> EditorFileDialog:
+ for connection in editor_file_dialog.file_selected.get_connections():
+ editor_file_dialog.file_selected.disconnect(connection.callable)
+ for connection in editor_file_dialog.dir_selected.get_connections():
+ editor_file_dialog.dir_selected.disconnect(connection.callable)
+ editor_file_dialog.file_mode = mode
+ editor_file_dialog.clear_filters()
+ editor_file_dialog.popup_centered_ratio(0.6)
+ editor_file_dialog.add_filter(filter)
+ editor_file_dialog.title = window_title
+ editor_file_dialog.current_file = current_file_name
+ editor_file_dialog.disable_overwrite_warning = !saving_something
+ if extra_message:
+ editor_file_dialog.get_meta("info_message_label").show()
+ editor_file_dialog.get_meta("info_message_label").text = extra_message
+ else:
+ editor_file_dialog.get_meta("info_message_label").hide()
+
+ if mode == EditorFileDialog.FILE_MODE_OPEN_FILE or mode == EditorFileDialog.FILE_MODE_SAVE_FILE:
+ editor_file_dialog.file_selected.connect(callable)
+ elif mode == EditorFileDialog.FILE_MODE_OPEN_DIR:
+ editor_file_dialog.dir_selected.connect(callable)
+ elif mode == EditorFileDialog.FILE_MODE_OPEN_ANY:
+ editor_file_dialog.dir_selected.connect(callable)
+ editor_file_dialog.file_selected.connect(callable)
+ return editor_file_dialog
diff --git a/addons/dialogic-main/addons/dialogic/Editor/editor_main.tscn b/addons/dialogic-main/addons/dialogic/Editor/editor_main.tscn
new file mode 100644
index 0000000..849ad79
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/editor_main.tscn
@@ -0,0 +1,161 @@
+[gd_scene load_steps=18 format=3 uid="uid://de6yhw4r8jqb3"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Editor/editor_main.gd" id="1_x88ov"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/editors_manager.gd" id="2_pe2tl"]
+[ext_resource type="Texture2D" uid="uid://dybg3l5pwetne" path="res://addons/dialogic/Editor/Images/plugin-icon.svg" id="2_scwcl"]
+[ext_resource type="PackedScene" uid="uid://cwe3r2tbh2og1" path="res://addons/dialogic/Editor/Common/side_bar.tscn" id="3_lp6hj"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/toolbar.gd" id="4_6cx8s"]
+[ext_resource type="Texture2D" uid="uid://bbea0efx0ybu7" path="res://addons/dialogic/Editor/Images/Resources/character.svg" id="6_8yp76"]
+[ext_resource type="Texture2D" uid="uid://b5xwnxdb7064n" path="res://addons/dialogic/Modules/Glossary/icon.svg" id="7_45ytg"]
+[ext_resource type="Texture2D" uid="uid://1mccycya6eua" path="res://addons/dialogic/Modules/StyleEditor/styles_icon.svg" id="8_jj1i6"]
+[ext_resource type="Texture2D" uid="uid://ckilxvwc34s84" path="res://addons/dialogic/Modules/Variable/variable.svg" id="9_k4reh"]
+[ext_resource type="PackedScene" uid="uid://c7lmt5cp7bxcm" path="res://addons/dialogic/Editor/Common/reference_manager.tscn" id="10_l1rf8"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/reference_manager_window.gd" id="10_xbkrt"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd" id="11_fyce4"]
+[ext_resource type="Script" path="res://addons/dialogic/Editor/Common/update_manager.gd" id="14_l6b1p"]
+[ext_resource type="PackedScene" uid="uid://vv3m5m68fwg7" path="res://addons/dialogic/Editor/Common/update_install_window.tscn" id="15_cu4xj"]
+
+[sub_resource type="Image" id="Image_uqxml"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_drcn6"]
+image = SubResource("Image_uqxml")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5bs7k"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.1155, 0.132, 0.1595, 1)
+corner_detail = 1
+anti_aliasing = false
+
+[node name="EditorView" type="ScrollContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1_x88ov")
+
+[node name="EditorsManager" type="Control" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("2_pe2tl")
+
+[node name="HSplit" type="HSplitContainer" parent="EditorsManager"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 0
+split_offset = 150
+
+[node name="Sidebar" parent="EditorsManager/HSplit" instance=ExtResource("3_lp6hj")]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(20, 0)
+layout_mode = 2
+split_offset = 0
+
+[node name="VBox" type="VBoxContainer" parent="EditorsManager/HSplit"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Toolbar" type="HBoxContainer" parent="EditorsManager/HSplit/VBox"]
+layout_mode = 2
+size_flags_vertical = 0
+mouse_filter = 2
+alignment = 2
+script = ExtResource("4_6cx8s")
+
+[node name="EditorTabBar" type="TabBar" parent="EditorsManager/HSplit/VBox/Toolbar"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 8
+tab_count = 7
+tab_0/icon = ExtResource("2_scwcl")
+tab_1/title = "Timeline"
+tab_1/icon = SubResource("ImageTexture_drcn6")
+tab_2/title = "Character"
+tab_2/icon = ExtResource("6_8yp76")
+tab_3/title = "Glossary"
+tab_3/icon = ExtResource("7_45ytg")
+tab_4/title = "Layouts"
+tab_4/icon = ExtResource("8_jj1i6")
+tab_5/title = "Variables"
+tab_5/icon = ExtResource("9_k4reh")
+tab_6/title = "Settings"
+tab_6/icon = SubResource("ImageTexture_drcn6")
+
+[node name="CustomButtons" type="HBoxContainer" parent="EditorsManager/HSplit/VBox/Toolbar"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Editors" type="PanelContainer" parent="EditorsManager/HSplit/VBox"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="CodeCompletionHelper" type="Node" parent="EditorsManager"]
+script = ExtResource("11_fyce4")
+
+[node name="SaveConfirmationDialog" type="AcceptDialog" parent="."]
+size = Vector2i(207, 100)
+
+[node name="ResourceRenameWarning" type="AcceptDialog" parent="."]
+title = "Dialogic resource renamed!"
+initial_position = 5
+size = Vector2i(494, 135)
+ok_button_text = "Show Unique Identifiers"
+dialog_text = "You renamed a dialogic resource. This does NOT automatically rename the unique identifier for this resource. Consider checking in the Reference Manager if the identifiers are still the way you want them."
+dialog_autowrap = true
+
+[node name="ReferenceManager" type="Window" parent="."]
+disable_3d = true
+title = "Reference Manager"
+initial_position = 2
+size = Vector2i(858, 442)
+visible = false
+wrap_controls = true
+content_scale_mode = 1
+content_scale_aspect = 4
+script = ExtResource("10_xbkrt")
+
+[node name="Manager" parent="ReferenceManager" instance=ExtResource("10_l1rf8")]
+theme_override_styles/panel = SubResource("StyleBoxFlat_5bs7k")
+
+[node name="UpdateManager" type="Node" parent="."]
+script = ExtResource("14_l6b1p")
+
+[node name="Window" type="Window" parent="UpdateManager"]
+title = "Dialogic Update Checker"
+initial_position = 2
+size = Vector2i(600, 400)
+visible = false
+wrap_controls = true
+
+[node name="UpdateInstallWindow" parent="UpdateManager/Window" instance=ExtResource("15_cu4xj")]
+
+[node name="UpdateCheckRequest" type="HTTPRequest" parent="UpdateManager"]
+timeout = 5.0
+
+[node name="DownloadRequest" type="HTTPRequest" parent="UpdateManager"]
+
+[connection signal="close_requested" from="ReferenceManager" to="ReferenceManager" method="_on_close_requested"]
+[connection signal="downdload_completed" from="UpdateManager" to="UpdateManager/Window/UpdateInstallWindow" method="_on_update_manager_downdload_completed"]
+[connection signal="update_check_completed" from="UpdateManager" to="UpdateManager" method="_on_update_check_completed"]
+[connection signal="close_requested" from="UpdateManager/Window" to="UpdateManager/Window/UpdateInstallWindow" method="_on_window_close_requested"]
+[connection signal="request_completed" from="UpdateManager/UpdateCheckRequest" to="UpdateManager" method="_on_UpdateCheck_request_completed"]
+[connection signal="request_completed" from="UpdateManager/DownloadRequest" to="UpdateManager" method="_on_DownloadRequest_completed"]
diff --git a/addons/dialogic-main/addons/dialogic/Editor/editors_manager.gd b/addons/dialogic-main/addons/dialogic/Editor/editors_manager.gd
new file mode 100644
index 0000000..029a919
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Editor/editors_manager.gd
@@ -0,0 +1,284 @@
+@tool
+extends Control
+
+## Node that manages editors, the toolbar and the sidebar.
+
+signal resource_opened(resource)
+signal editor_changed(previous, current)
+
+### References
+@onready var hsplit := $HSplit
+@onready var sidebar := $HSplit/Sidebar
+@onready var editors_holder := $HSplit/VBox/Editors
+@onready var toolbar := $HSplit/VBox/Toolbar
+@onready var tabbar := $HSplit/VBox/Toolbar/EditorTabBar
+
+var reference_manager: Node:
+ get:
+ return get_node("../ReferenceManager")
+
+## Information on supported resource extensions and registered editors
+var current_editor: DialogicEditor = null
+var previous_editor: DialogicEditor = null
+var editors := {}
+var supported_file_extensions := []
+var used_resources_cache: Array = []
+
+
+################################################################################
+## REGISTERING EDITORS
+################################################################################
+
+## Asks all childs of the editor holder to register
+func _ready() -> void:
+ if owner.get_parent() is SubViewport:
+ return
+
+ tabbar.clear_tabs()
+
+ # Load base editors
+ _add_editor("res://addons/dialogic/Editor/HomePage/home_page.tscn")
+ _add_editor("res://addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn")
+ _add_editor("res://addons/dialogic/Editor/CharacterEditor/character_editor.tscn")
+
+ # Load custom editors
+ for indexer in DialogicUtil.get_indexers():
+ for editor_path in indexer._get_editors():
+ _add_editor(editor_path)
+ _add_editor("res://addons/dialogic/Editor/Settings/settings_editor.tscn")
+
+ tabbar.tab_clicked.connect(_on_editors_tab_changed)
+
+ # Needs to be done here to make sure this node is ready when doing the register calls
+ for editor in editors_holder.get_children():
+ editor.editors_manager = self
+ editor._register()
+
+ DialogicResourceUtil.update()
+
+ await get_parent().ready
+ await get_tree().process_frame
+
+ load_saved_state()
+ used_resources_cache = DialogicUtil.get_editor_setting('last_resources', [])
+ sidebar.update_resource_list(used_resources_cache)
+
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_file_system_dock().files_moved.connect(_on_file_moved)
+ find_parent('EditorView').plugin_reference.get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed)
+
+ hsplit.set("theme_override_constants/separation", get_theme_constant("base_margin", "Editor") * DialogicUtil.get_editor_scale())
+
+
+func _add_editor(path:String) -> void:
+ var editor: DialogicEditor = load(path).instantiate()
+ editors_holder.add_child(editor)
+ editor.hide()
+ tabbar.add_tab(editor._get_title(), editor._get_icon())
+
+
+## Call to register an editor/tab that edits a resource with a custom ending.
+func register_resource_editor(resource_extension:String, editor:DialogicEditor) -> void:
+ editors[editor.name] = {'node':editor, 'buttons':[], 'extension': resource_extension}
+ supported_file_extensions.append(resource_extension)
+ editor.resource_saved.connect(_on_resource_saved.bind(editor))
+ editor.resource_unsaved.connect(_on_resource_unsaved.bind(editor))
+
+
+## Call to register an editor/tab that doesn't edit a resource
+func register_simple_editor(editor:DialogicEditor) -> void:
+ editors[editor.name] = {'node': editor, 'buttons':[]}
+
+
+## Call to add an icon button. These buttons are always visible.
+func add_icon_button(icon:Texture, tooltip:String, editor:DialogicEditor=null) -> Node:
+ var button: Button = toolbar.add_icon_button(icon, tooltip)
+ if editor != null:
+ editors[editor.name]['buttons'].append(button)
+ return button
+
+
+## Call to add a custom action button. Only visible if editor is visible.
+func add_custom_button(label:String, icon:Texture, editor:DialogicEditor) -> Node:
+ var button: Button = toolbar.add_custom_button(label, icon)
+ editors[editor.name]['buttons'].append(button)
+ return button
+
+
+func can_edit_resource(resource:Resource) -> bool:
+ return resource.resource_path.get_extension() in supported_file_extensions
+
+
+################################################################################
+## OPENING/CLOSING
+################################################################################
+
+
+func _on_editors_tab_changed(tab:int) -> void:
+ open_editor(editors_holder.get_child(tab))
+
+
+func edit_resource(resource:Resource, save_previous:bool = true, silent:= false) -> void:
+ if not resource:
+ # The resource doesn't exists, show an error
+ print("[Dialogic] The resource you are trying to edit doesn't exist any more.")
+ return
+
+ if current_editor and save_previous:
+ current_editor._save()
+
+ if !resource.resource_path in used_resources_cache:
+ used_resources_cache.append(resource.resource_path)
+ sidebar.update_resource_list(used_resources_cache)
+
+ ## Open the correct editor
+ var extension: String = resource.resource_path.get_extension()
+ for editor in editors.values():
+ if editor.get('extension', '') == extension:
+ editor['node']._open_resource(resource)
+ if !silent:
+ open_editor(editor['node'], false)
+ if !silent:
+ resource_opened.emit(resource)
+
+
+
+## Only works if there was a different editor opened previously
+func toggle_editor(editor) -> void:
+ if editor.visible:
+ open_editor(previous_editor, true)
+ else:
+ open_editor(editor, true)
+
+
+## Shows the given editor
+func open_editor(editor:DialogicEditor, save_previous: bool = true, extra_info:Variant = null) -> void:
+ if current_editor and save_previous:
+ current_editor._save()
+
+ if current_editor:
+ current_editor._close()
+ current_editor.hide()
+
+ if current_editor != previous_editor:
+ previous_editor = current_editor
+
+ editor._open(extra_info)
+ editor.opened.emit()
+ current_editor = editor
+ editor.show()
+ tabbar.current_tab = editor.get_index()
+
+ if editor.current_resource:
+ var text: String = editor.current_resource.resource_path.get_file()
+ if editor.current_resource_state == DialogicEditor.ResourceStates.UNSAVED:
+ text += "(*)"
+
+ ## This makes custom button editor-specific
+ ## I think it's better without.
+
+ save_current_state()
+ editor_changed.emit(previous_editor, current_editor)
+
+
+## Rarely used to completely clear an editor.
+func clear_editor(editor:DialogicEditor, save:bool = false) -> void:
+ if save:
+ editor._save()
+
+ editor._clear()
+
+## Shows a file selector. Calls [accept_callable] once accepted
+func show_add_resource_dialog(accept_callable:Callable, filter:String = "*", title = "New resource", default_name = "new_character", mode = EditorFileDialog.FILE_MODE_SAVE_FILE) -> void:
+ find_parent('EditorView').godot_file_dialog(
+ _on_add_resource_dialog_accepted.bind(accept_callable),
+ filter,
+ mode,
+ title,
+ default_name,
+ true,
+ "Do not use \"'()!;:/\\*# in character or timeline names!"
+ )
+
+
+func _on_add_resource_dialog_accepted(path:String, callable:Callable) -> void:
+ var file_name: String = path.get_file().trim_suffix('.'+path.get_extension())
+ for i in ['#','&','+',';','(',')','!','*','*','"',"'",'%', '$', ':','.',',']:
+ file_name = file_name.replace(i, '')
+ callable.call(path.trim_suffix(path.get_file()).path_join(file_name)+'.'+path.get_extension())
+
+
+## Called by the plugin.gd script on CTRL+S or Debug Game start
+func save_current_resource() -> void:
+ if current_editor:
+ current_editor._save()
+
+
+## Change the resource state
+func _on_resource_saved(editor:DialogicEditor):
+ sidebar.set_unsaved_indicator(true)
+
+
+## Change the resource state
+func _on_resource_unsaved(editor:DialogicEditor):
+ sidebar.set_unsaved_indicator(false)
+
+
+## Tries opening the last resource
+func load_saved_state() -> void:
+ var current_resources: Dictionary = DialogicUtil.get_editor_setting('current_resources', {})
+ for editor in current_resources.keys():
+ editors[editor]['node']._open_resource(load(current_resources[editor]))
+
+ var current_editor: String = DialogicUtil.get_editor_setting('current_editor', 'HomePage')
+ open_editor(editors[current_editor]['node'])
+
+
+func save_current_state() -> void:
+ DialogicUtil.set_editor_setting('current_editor', current_editor.name)
+ var current_resources: Dictionary = {}
+ for editor in editors.values():
+ if editor['node'].current_resource != null:
+ current_resources[editor['node'].name] = editor['node'].current_resource.resource_path
+ DialogicUtil.set_editor_setting('current_resources', current_resources)
+
+
+func _on_file_moved(old_name:String, new_name:String) -> void:
+ if !old_name.get_extension() in supported_file_extensions:
+ return
+
+ used_resources_cache = DialogicUtil.get_editor_setting('last_resources', [])
+ if old_name in used_resources_cache:
+ used_resources_cache.insert(used_resources_cache.find(old_name), new_name)
+ used_resources_cache.erase(old_name)
+
+ sidebar.update_resource_list(used_resources_cache)
+
+ for editor in editors:
+ if editors[editor].node.current_resource != null and editors[editor].node.current_resource.resource_path == old_name:
+ editors[editor].node.current_resource.take_over_path(new_name)
+ edit_resource(load(new_name), true, true)
+
+ save_current_state()
+
+
+func _on_file_removed(file_name:String) -> void:
+ var current_resources: Dictionary = DialogicUtil.get_editor_setting('current_resources', {})
+ for editor_name in current_resources:
+ if current_resources[editor_name] == file_name:
+ clear_editor(editors[editor_name].node, false)
+ sidebar.update_resource_list()
+ save_current_state()
+
+
+
+################################################################################
+## HELPERS
+################################################################################
+
+
+func get_current_editor() -> DialogicEditor:
+ return current_editor
+
+
+func _exit_tree() -> void:
+ DialogicUtil.set_editor_setting('last_resources', used_resources_cache)
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf
new file mode 100644
index 0000000..d998cf5
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import
new file mode 100644
index 0000000..7014baf
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://cc4xli25271fd"
+path="res://.godot/imported/Roboto-Bold.ttf-06d31b48586230dbad6755c1913dff96.fontdata"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf"
+dest_files=["res://.godot/imported/Roboto-Bold.ttf-06d31b48586230dbad6755c1913dff96.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf
new file mode 100644
index 0000000..5b390ff
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import
new file mode 100644
index 0000000..4c114c3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://b5c0p00x6g6u5"
+path="res://.godot/imported/Roboto-Italic.ttf-5a1c40281cb86d444df1a37fb3030c1e.fontdata"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf"
+dest_files=["res://.godot/imported/Roboto-Italic.ttf-5a1c40281cb86d444df1a37fb3030c1e.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf
new file mode 100644
index 0000000..2b6392f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import
new file mode 100644
index 0000000..9cf6c6b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://vrrmdx83skor"
+path="res://.godot/imported/Roboto-Regular.ttf-60536d53c7113a1e7e3f4a10a4ea30a3.fontdata"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf"
+dest_files=["res://.godot/imported/Roboto-Regular.ttf-60536d53c7113a1e7e3f4a10a4ea30a3.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/already_read_indicator.gd b/addons/dialogic-main/addons/dialogic/Example Assets/already_read_indicator.gd
new file mode 100644
index 0000000..4dbf5d3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/already_read_indicator.gd
@@ -0,0 +1,12 @@
+extends Control
+
+func _ready() -> void:
+ if DialogicUtil.autoload().has_subsystem('History'):
+ DialogicUtil.autoload().History.visited_event.connect(_on_visited_event)
+ DialogicUtil.autoload().History.unvisited_event.connect(_on_not_read_event)
+
+func _on_visited_event() -> void:
+ show()
+
+func _on_not_read_event() -> void:
+ hide()
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png
new file mode 100644
index 0000000..c0ff161
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import
new file mode 100644
index 0000000..c03cab6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3sccqj6l42w6"
+path="res://.godot/imported/BubbleEnd.png-d87c5b5a8bb30ba373f0a549e3497c7a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png"
+dest_files=["res://.godot/imported/BubbleEnd.png-d87c5b5a8bb30ba373f0a549e3497c7a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/new-default-dialog.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/new-default-dialog.png.import
new file mode 100644
index 0000000..7cbe695
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/new-default-dialog.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3mgfla25qml3"
+path="res://.godot/imported/new-default-dialog.png-1bc90907f2427bd6cdb905ff375cdb22.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic/Example Assets/backgrounds/new-default-dialog.png"
+dest_files=["res://.godot/imported/new-default-dialog.png-1bc90907f2427bd6cdb905ff375cdb22.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
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp
new file mode 100644
index 0000000..e1d2a10
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import
new file mode 100644
index 0000000..c91b0ca
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dch8fuekijffp"
+path="res://.godot/imported/rpg_box.webp-1dfd91eef436ce12315234af34971690.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/backgrounds/rpg_box.webp"
+dest_files=["res://.godot/imported/rpg_box.webp-1dfd91eef436ce12315234af34971690.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/default_event.gd b/addons/dialogic-main/addons/dialogic/Example Assets/default_event.gd
new file mode 100644
index 0000000..6ffbc71
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/default_event.gd
@@ -0,0 +1,51 @@
+@tool
+extends DialogicEvent
+
+# DEFINE ALL PROPERTIES OF THE EVENT
+# var MySetting: String = ""
+
+func _execute() -> void:
+ # I have no idea how this event works ;)
+ finish()
+
+
+#region INITIALIZE
+################################################################################
+
+# SET ALL VALUES THAT SHOULD NEVER CHANGE HERE
+func _init() -> void:
+ event_name = "Default"
+ event_color = Color("#ffffff")
+ event_category = "Main"
+ event_sorting_index = 0
+
+#endregion
+
+
+#region SAVING/LOADING
+################################################################################
+func get_shortcode() -> String:
+ return "default_shortcode"
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_name
+ #"arg_name" : "NameOfProperty",
+ }
+
+# You can alternatively overwrite these 3 functions:
+# - to_text(),
+# - from_text(),
+# - is_valid_event()
+
+#endregion
+
+
+#region EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ pass
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png
new file mode 100644
index 0000000..f26ed34
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import
new file mode 100644
index 0000000..86ae0fa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bgn2ci6nu85t5"
+path="res://.godot/imported/next-indicator-dialogic-1.png-c4a79cf26dd6c99a1c9303edd396a02c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png"
+dest_files=["res://.godot/imported/next-indicator-dialogic-1.png-c4a79cf26dd6c99a1c9303edd396a02c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png
new file mode 100644
index 0000000..896d3cf
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import
new file mode 100644
index 0000000..0d120a8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://u0o8qgccs5df"
+path="res://.godot/imported/next-indicator.png-3eb4b8e617dc115079a071ffa727a76b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/next-indicator/next-indicator.png"
+dest_files=["res://.godot/imported/next-indicator.png-3eb4b8e617dc115079a071ffa727a76b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png
new file mode 100644
index 0000000..167c215
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import
new file mode 100644
index 0000000..2bbb65a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://s2jsr1aqiu84"
+path="res://.godot/imported/pl5 blink.png-7a9db7710d9c1844da652e3b597c605c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png"
+dest_files=["res://.godot/imported/pl5 blink.png-7a9db7710d9c1844da652e3b597c605c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png
new file mode 100644
index 0000000..75d7519
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import
new file mode 100644
index 0000000..cd8f80f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bl38l2bv5ny4h"
+path="res://.godot/imported/pl5 doubt.png-b540307bc3464f4877f316e1fc6a1bcd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png"
+dest_files=["res://.godot/imported/pl5 doubt.png-b540307bc3464f4877f316e1fc6a1bcd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png
new file mode 100644
index 0000000..a4eb434
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import
new file mode 100644
index 0000000..34f5c36
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ch4hvrgmq3j7t"
+path="res://.godot/imported/pl5 hate.png-388d1f5b2edc2acf9fc210941b2baa3e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png"
+dest_files=["res://.godot/imported/pl5 hate.png-388d1f5b2edc2acf9fc210941b2baa3e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png
new file mode 100644
index 0000000..4763b82
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import
new file mode 100644
index 0000000..50dfaa7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://1k1be4cqjj4t"
+path="res://.godot/imported/pl5 plot.png-e1a0a115000494e0cdac49ee21b7cee4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png"
+dest_files=["res://.godot/imported/pl5 plot.png-e1a0a115000494e0cdac49ee21b7cee4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png
new file mode 100644
index 0000000..97845b2
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import
new file mode 100644
index 0000000..a9c5cd5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://x7yafneltdfy"
+path="res://.godot/imported/pl5 sad.png-e9fe25ec9497d61a622b26e47089d2b3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png"
+dest_files=["res://.godot/imported/pl5 sad.png-e9fe25ec9497d61a622b26e47089d2b3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png
new file mode 100644
index 0000000..d54b591
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import
new file mode 100644
index 0000000..4a6271d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://675isgfym2tw"
+path="res://.godot/imported/pl5 scoff.png-76e14fb5288feda1c9087fd38abcefa5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png"
+dest_files=["res://.godot/imported/pl5 scoff.png-76e14fb5288feda1c9087fd38abcefa5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png
new file mode 100644
index 0000000..a131cfa
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import
new file mode 100644
index 0000000..227d551
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gaol7fi1ifkx"
+path="res://.godot/imported/pl5 shy.png-317587e287870a58f659a7a07037b49c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png"
+dest_files=["res://.godot/imported/pl5 shy.png-317587e287870a58f659a7a07037b49c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png
new file mode 100644
index 0000000..c780781
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import
new file mode 100644
index 0000000..d6f0dfe
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dpvtdr1itkbd7"
+path="res://.godot/imported/pl5 surprise.png-40a3e6634c031f3cf1912cb589c2490a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png"
+dest_files=["res://.godot/imported/pl5 surprise.png-40a3e6634c031f3cf1912cb589c2490a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png
new file mode 100644
index 0000000..f676f77
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import
new file mode 100644
index 0000000..1fd08d6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bfkpn7mrd786b"
+path="res://.godot/imported/pl5.png-977f403c23ed7d023d7a58ec349e04db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Antonio/pl5.png"
+dest_files=["res://.godot/imported/pl5.png-977f403c23ed7d023d7a58ec349e04db.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd
new file mode 100644
index 0000000..5595991
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd
@@ -0,0 +1,18 @@
+@tool
+extends DialogicPortrait
+
+# If the custom portrait accepts a change, then accept it here
+func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
+ if passed_portrait == "":
+ passed_portrait = passed_character['default_portrait']
+
+ if $Sprite.sprite_frames.has_animation(passed_portrait):
+ $Sprite.play(passed_portrait)
+
+func _on_animated_sprite_2d_animation_finished() -> void:
+ $Sprite.frame = randi()%$Sprite.sprite_frames.get_frame_count($Sprite.animation)
+ $Sprite.play()
+
+
+func _get_covered_rect() -> Rect2:
+ return Rect2($Sprite.position, $Sprite.sprite_frames.get_frame_texture($Sprite.animation, 0).get_size()*$Sprite.scale)
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.tscn b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.tscn
new file mode 100644
index 0000000..bdad81b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.tscn
@@ -0,0 +1,56 @@
+[gd_scene load_steps=5 format=3 uid="uid://cyns86lydp1tl"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd" id="1_63c5k"]
+[ext_resource type="Texture2D" uid="uid://bfkpn7mrd786b" path="res://addons/dialogic/Example Assets/portraits/Antonio/pl5.png" id="2_15o4t"]
+[ext_resource type="Texture2D" uid="uid://s2jsr1aqiu84" path="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png" id="3_qen6e"]
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_yaycq"]
+animations = [{
+"frames": [{
+"duration": 10.0,
+"texture": ExtResource("2_15o4t")
+}, {
+"duration": 1.0,
+"texture": ExtResource("3_qen6e")
+}, {
+"duration": 5.0,
+"texture": ExtResource("2_15o4t")
+}, {
+"duration": 4.0,
+"texture": ExtResource("2_15o4t")
+}, {
+"duration": 1.0,
+"texture": ExtResource("3_qen6e")
+}, {
+"duration": 1.0,
+"texture": ExtResource("2_15o4t")
+}, {
+"duration": 1.0,
+"texture": ExtResource("3_qen6e")
+}, {
+"duration": 5.0,
+"texture": ExtResource("2_15o4t")
+}, {
+"duration": 1.0,
+"texture": ExtResource("3_qen6e")
+}, {
+"duration": 10.0,
+"texture": ExtResource("2_15o4t")
+}],
+"loop": false,
+"name": &"default",
+"speed": 10.0
+}]
+
+[node name="CustomCharacterScene" type="Node2D"]
+position = Vector2(160, 580)
+script = ExtResource("1_63c5k")
+
+[node name="Sprite" type="AnimatedSprite2D" parent="."]
+position = Vector2(-161, -580)
+scale = Vector2(0.751953, 0.751953)
+sprite_frames = SubResource("SpriteFrames_yaycq")
+autoplay = "default"
+centered = false
+
+[connection signal="animation_finished" from="Sprite" to="." method="_on_animated_sprite_2d_animation_finished"]
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd
new file mode 100644
index 0000000..7b1b74a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd
@@ -0,0 +1,70 @@
+@tool
+extends DialogicPortrait
+
+enum Faces {BASED_ON_PORTRAIT_NAME, NEUTRAL, HAPPY, SAD, JOY, SHOCK, ANGRY}
+
+@export var emotion: Faces = Faces.BASED_ON_PORTRAIT_NAME
+@export var portrait_width: int
+@export var portrait_height: int
+@export var alien := true
+
+var does_custom_portrait_change := true
+
+func _ready() -> void:
+ $Alien.hide()
+
+
+# Function to accept and use the extra data, if the custom portrait wants to accept it
+func _set_extra_data(data: String) -> void:
+ if data == "alien":
+ $Alien.show()
+ elif data == "no_alien":
+ $Alien.hide()
+
+
+# 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:
+ for face in $Faces.get_children():
+ face.hide()
+
+ if emotion == Faces.BASED_ON_PORTRAIT_NAME:
+ if 'happy' in passed_portrait.to_lower(): $Faces/Smile.show()
+ elif 'sad' in passed_portrait.to_lower(): $Faces/Frown.show()
+ elif 'joy' in passed_portrait.to_lower(): $Faces/Joy.show()
+ elif 'shock' in passed_portrait.to_lower(): $Faces/Shock.show()
+ elif 'angry' in passed_portrait.to_lower(): $Faces/Anger.show()
+ else: $Faces/Neutral.show()
+
+ else:
+ if emotion == Faces.HAPPY: $Faces/Smile.show()
+ elif emotion == Faces.SAD: $Faces/Frown.show()
+ elif emotion == Faces.JOY: $Faces/Joy.show()
+ elif emotion == Faces.SHOCK: $Faces/Shock.show()
+ elif emotion == Faces.ANGRY: $Faces/Anger.show()
+ else: $Faces/Neutral.show()
+
+ $Alien.visible = alien
+
+
+func _set_mirror(is_mirrored: bool) -> void:
+ if is_mirrored:
+ self.scale.x = -1
+
+ else:
+ self.scale.x = 1
+
+
+## If implemented, this is used by the editor for the "full view" mode
+func _get_covered_rect() -> Rect2:
+ # This will focus on the face.
+ # return Rect2($Faces/Anger.position+$Faces.position, $Faces/Anger.get_rect().size*$Faces/Anger.scale*$Faces.scale)
+ var size: Vector2 = $Body.get_rect().size
+ var scaled_size: Vector2 = size * $Body.scale
+ var position: Vector2 = $Body.position
+
+ return Rect2(position, scaled_size)
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.tscn b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.tscn
new file mode 100644
index 0000000..a780a92
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.tscn
@@ -0,0 +1,67 @@
+[gd_scene load_steps=10 format=3 uid="uid://bgshjju5v2q0i"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd" id="1_fc12l"]
+[ext_resource type="Texture2D" uid="uid://djqit26f4be4f" path="res://addons/dialogic/Example Assets/portraits/Princess/princess_blank.png" id="2_igcyp"]
+[ext_resource type="Texture2D" uid="uid://ndmjrpk41eo4" path="res://addons/dialogic/Example Assets/portraits/Portrait1.png" id="3_6xy1t"]
+[ext_resource type="Texture2D" uid="uid://dokv225cp85ja" path="res://addons/dialogic/Example Assets/portraits/Princess/anger.png" id="3_wdpjk"]
+[ext_resource type="Texture2D" uid="uid://5bruuhj5cqu4" path="res://addons/dialogic/Example Assets/portraits/Princess/frown.png" id="4_pimb3"]
+[ext_resource type="Texture2D" uid="uid://dg7c4umbfsyvs" path="res://addons/dialogic/Example Assets/portraits/Princess/joy.png" id="5_2ekfy"]
+[ext_resource type="Texture2D" uid="uid://bu3631ymfqxi3" path="res://addons/dialogic/Example Assets/portraits/Princess/neutral.png" id="6_5hpoa"]
+[ext_resource type="Texture2D" uid="uid://c5aku2g01k6c6" path="res://addons/dialogic/Example Assets/portraits/Princess/shock.png" id="7_5xil3"]
+[ext_resource type="Texture2D" uid="uid://dsid4ye0q74nl" path="res://addons/dialogic/Example Assets/portraits/Princess/smile.png" id="8_7s6tq"]
+
+[node name="CustomPortraitFaceAtlas" type="Node2D"]
+position = Vector2(301, 598)
+script = ExtResource("1_fc12l")
+
+[node name="Body" type="Sprite2D" parent="."]
+position = Vector2(-182, -465)
+scale = Vector2(0.287561, 0.287561)
+texture = ExtResource("2_igcyp")
+centered = false
+
+[node name="Alien" type="Sprite2D" parent="."]
+visible = false
+position = Vector2(-58, -378)
+rotation = -0.523598
+scale = Vector2(0.84236, 0.875348)
+texture = ExtResource("3_6xy1t")
+
+[node name="Faces" type="Node2D" parent="."]
+position = Vector2(2, -397)
+
+[node name="Anger" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("3_wdpjk")
+centered = false
+
+[node name="Frown" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("4_pimb3")
+centered = false
+
+[node name="Joy" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("5_2ekfy")
+centered = false
+
+[node name="Neutral" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("6_5hpoa")
+centered = false
+
+[node name="Shock" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("7_5xil3")
+centered = false
+
+[node name="Smile" type="Sprite2D" parent="Faces"]
+position = Vector2(-38, -41)
+scale = Vector2(0.290393, 0.288066)
+texture = ExtResource("8_7s6tq")
+centered = false
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png
new file mode 100644
index 0000000..258af79
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import
new file mode 100644
index 0000000..f586e88
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cb3tpn3u3wtis"
+path="res://.godot/imported/pl3 avoid.png-578b29ea2dfb0549d219a472ecd7358e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png"
+dest_files=["res://.godot/imported/pl3 avoid.png-578b29ea2dfb0549d219a472ecd7358e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png
new file mode 100644
index 0000000..0ccfc28
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import
new file mode 100644
index 0000000..c14a9c4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://khlo6wd16qcd"
+path="res://.godot/imported/pl3 blink.png-a3c7085da56ae0dd3997ad9f97044e59.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png"
+dest_files=["res://.godot/imported/pl3 blink.png-a3c7085da56ae0dd3997ad9f97044e59.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png
new file mode 100644
index 0000000..a14954b
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import
new file mode 100644
index 0000000..ad363da
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmshygun2cd0j"
+path="res://.godot/imported/pl3 concept.png-3dec1162b34816ed5aa61ec09c183e57.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png"
+dest_files=["res://.godot/imported/pl3 concept.png-3dec1162b34816ed5aa61ec09c183e57.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png
new file mode 100644
index 0000000..7463f0b
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import
new file mode 100644
index 0000000..996447e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://r23r6iywkw0n"
+path="res://.godot/imported/pl3 confusion.png-19c05ef215d39bb45c2cbc6e5ec13540.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png"
+dest_files=["res://.godot/imported/pl3 confusion.png-19c05ef215d39bb45c2cbc6e5ec13540.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png
new file mode 100644
index 0000000..d34166f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import
new file mode 100644
index 0000000..d400c03
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d0xc8omj2n6h2"
+path="res://.godot/imported/pl3 doubt.png-42af60f4016380c87d334cee7e30918b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png"
+dest_files=["res://.godot/imported/pl3 doubt.png-42af60f4016380c87d334cee7e30918b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png
new file mode 100644
index 0000000..221ab18
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import
new file mode 100644
index 0000000..9dbfcde
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dgteot3g2xw78"
+path="res://.godot/imported/pl3 happy.png-b7f154f672a1ac3b34395f23e674f7ee.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png"
+dest_files=["res://.godot/imported/pl3 happy.png-b7f154f672a1ac3b34395f23e674f7ee.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png
new file mode 100644
index 0000000..f11cbe4
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import
new file mode 100644
index 0000000..717451b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://uqa1ygtex8sj"
+path="res://.godot/imported/pl3 plot.png-4e9fa381db339c7142f819a647194ae3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png"
+dest_files=["res://.godot/imported/pl3 plot.png-4e9fa381db339c7142f819a647194ae3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png
new file mode 100644
index 0000000..d4cae6c
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import
new file mode 100644
index 0000000..1fe1c5f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://be07it6s721a0"
+path="res://.godot/imported/pl3 sad.png-7a7ac6a27ab5f13ec75ed0f19a0d69b0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png"
+dest_files=["res://.godot/imported/pl3 sad.png-7a7ac6a27ab5f13ec75ed0f19a0d69b0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png
new file mode 100644
index 0000000..fa21ed2
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import
new file mode 100644
index 0000000..64a5c32
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bx6037ennf2im"
+path="res://.godot/imported/pl3 shy.png-a9087ad1909796681e30b6fc184f917c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png"
+dest_files=["res://.godot/imported/pl3 shy.png-a9087ad1909796681e30b6fc184f917c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png
new file mode 100644
index 0000000..ab66ecb
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import
new file mode 100644
index 0000000..592a2c2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c8ctomgkvxnta"
+path="res://.godot/imported/pl3 surprise.png-968df71b434e1828a46664e8acecb1ec.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png"
+dest_files=["res://.godot/imported/pl3 surprise.png-968df71b434e1828a46664e8acecb1ec.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png
new file mode 100644
index 0000000..4bac6af
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png.import
new file mode 100644
index 0000000..3d8a401
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ndmjrpk41eo4"
+path="res://.godot/imported/Portrait1.png-48ea7614f1e91241fb18c3265e764d8e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait1.png"
+dest_files=["res://.godot/imported/Portrait1.png-48ea7614f1e91241fb18c3265e764d8e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png
new file mode 100644
index 0000000..9bc4398
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png.import
new file mode 100644
index 0000000..87c67a1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2u3n3yp222uh"
+path="res://.godot/imported/Portrait2.png-cb9eecec7f4236822410562906710a16.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Portrait2.png"
+dest_files=["res://.godot/imported/Portrait2.png-cb9eecec7f4236822410562906710a16.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png
new file mode 100644
index 0000000..e63cfac
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png.import
new file mode 100644
index 0000000..f9211c3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dokv225cp85ja"
+path="res://.godot/imported/anger.png-9e3c461ef1e501793cccc4b4c821fdf5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/anger.png"
+dest_files=["res://.godot/imported/anger.png-9e3c461ef1e501793cccc4b4c821fdf5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png
new file mode 100644
index 0000000..33b45f5
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png.import
new file mode 100644
index 0000000..ea69e12
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://5bruuhj5cqu4"
+path="res://.godot/imported/frown.png-196a97c913f3d53343acbcf406ea74bc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/frown.png"
+dest_files=["res://.godot/imported/frown.png-196a97c913f3d53343acbcf406ea74bc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png
new file mode 100644
index 0000000..8425066
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png.import
new file mode 100644
index 0000000..1c918b8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dg7c4umbfsyvs"
+path="res://.godot/imported/joy.png-1436a00105158793455717679276438c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/joy.png"
+dest_files=["res://.godot/imported/joy.png-1436a00105158793455717679276438c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png
new file mode 100644
index 0000000..cd1441a
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import
new file mode 100644
index 0000000..d6687d3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bu3631ymfqxi3"
+path="res://.godot/imported/neutral.png-9269eabd86ef83d2436b84c4f8a3dadc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/neutral.png"
+dest_files=["res://.godot/imported/neutral.png-9269eabd86ef83d2436b84c4f8a3dadc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png
new file mode 100644
index 0000000..4b40887
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import
new file mode 100644
index 0000000..530a473
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djqit26f4be4f"
+path="res://.godot/imported/princess_blank.png-f5dc7e99b750e7b356a4c7bc86e51084.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png"
+dest_files=["res://.godot/imported/princess_blank.png-f5dc7e99b750e7b356a4c7bc86e51084.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png
new file mode 100644
index 0000000..2e7f306
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png.import
new file mode 100644
index 0000000..0287d92
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5aku2g01k6c6"
+path="res://.godot/imported/shock.png-6f2126369f08f9dabd03b23d27963ed6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/shock.png"
+dest_files=["res://.godot/imported/shock.png-6f2126369f08f9dabd03b23d27963ed6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png
new file mode 100644
index 0000000..45247e4
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png.import
new file mode 100644
index 0000000..d3def9d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dsid4ye0q74nl"
+path="res://.godot/imported/smile.png-349a76ddd18df4af60728c50a94407a6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/Princess/smile.png"
+dest_files=["res://.godot/imported/smile.png-349a76ddd18df4af60728c50a94407a6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png
new file mode 100644
index 0000000..f77329f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import
new file mode 100644
index 0000000..d2516c5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://1es1ixchfied"
+path="res://.godot/imported/base1.png-1481b3fb73bc14d7c07e246a81ddedae.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png"
+dest_files=["res://.godot/imported/base1.png-1481b3fb73bc14d7c07e246a81ddedae.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png
new file mode 100644
index 0000000..015798f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import
new file mode 100644
index 0000000..4463937
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0asqyjv6ea0h"
+path="res://.godot/imported/base2.png-3f4d9fb1b839bb5d974ab53efa8ac3a7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png"
+dest_files=["res://.godot/imported/base2.png-3f4d9fb1b839bb5d974ab53efa8ac3a7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png
new file mode 100644
index 0000000..e7feaa9
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import
new file mode 100644
index 0000000..24191ca
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://vtajb1cdcso"
+path="res://.godot/imported/base3.png-ef0e9f0b1c30207886ef781b23890a05.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png"
+dest_files=["res://.godot/imported/base3.png-ef0e9f0b1c30207886ef781b23890a05.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png
new file mode 100644
index 0000000..035d6b4
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import
new file mode 100644
index 0000000..266a6f9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcm3eo5syiln0"
+path="res://.godot/imported/base4.png-e83451ac6e3d09d60f2695cd091dba07.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png"
+dest_files=["res://.godot/imported/base4.png-e83451ac6e3d09d60f2695cd091dba07.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/LICENSE.txt b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/LICENSE.txt
new file mode 100644
index 0000000..14b8ff5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/LICENSE.txt
@@ -0,0 +1,4 @@
+Copyright (c) 2020 Tim Krief.
+
+Typing sound effects by Tim Krief are licensed under a Creative
+Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License.
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav
new file mode 100644
index 0000000..bcb9c87
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav.import b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav.import
new file mode 100644
index 0000000..09166f8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://b6c1p14bc20p1"
+path="res://.godot/imported/typing1.wav-0c5f13854abb81f116e946bf5ee86d52.sample"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing1.wav"
+dest_files=["res://.godot/imported/typing1.wav-0c5f13854abb81f116e946bf5ee86d52.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav
new file mode 100644
index 0000000..aff48fa
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav.import b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav.import
new file mode 100644
index 0000000..16c1097
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c3uw4nft0de13"
+path="res://.godot/imported/typing2.wav-72432fd2e428028db78e216b890bf4f3.sample"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing2.wav"
+dest_files=["res://.godot/imported/typing2.wav-72432fd2e428028db78e216b890bf4f3.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav
new file mode 100644
index 0000000..91f353b
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav.import b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav.import
new file mode 100644
index 0000000..6366625
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dnboblpkf0fqi"
+path="res://.godot/imported/typing3.wav-7a40c203e2cc4326e69f50d0979a608d.sample"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing3.wav"
+dest_files=["res://.godot/imported/typing3.wav-7a40c203e2cc4326e69f50d0979a608d.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav
new file mode 100644
index 0000000..071ba81
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav.import b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav.import
new file mode 100644
index 0000000..55289f9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://c2viukvbub6v6"
+path="res://.godot/imported/typing4.wav-ac32dd2bf2bb8d8044f96c1ec78a59f2.sample"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing4.wav"
+dest_files=["res://.godot/imported/typing4.wav-ac32dd2bf2bb8d8044f96c1ec78a59f2.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav
new file mode 100644
index 0000000..9143081
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav differ
diff --git a/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav.import b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav.import
new file mode 100644
index 0000000..e088ff7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://dwcre3fjf3cj8"
+path="res://.godot/imported/typing5.wav-1d0d5835fee918fdd6edb28111163f87.sample"
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Example Assets/sound-effects/typing5.wav"
+dest_files=["res://.godot/imported/typing5.wav-1d0d5835fee918fdd6edb28111163f87.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/event_music.gd b/addons/dialogic-main/addons/dialogic/Modules/Audio/event_music.gd
new file mode 100644
index 0000000..c54c5bd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/event_music.gd
@@ -0,0 +1,86 @@
+@tool
+## Event that can change the currently playing background music.
+## This event won't play new music if it's already playing.
+class_name DialogicMusicEvent
+extends DialogicEvent
+
+
+### Settings
+
+## The file to play. If empty, the previous music will be faded out.
+var file_path := ""
+## The length of the fade. If 0 (by default) it's an instant change.
+var fade_length: float = 0
+## The volume the music will be played at.
+var volume: float = 0
+## The audio bus the music will be played at.
+var audio_bus := ""
+## If true, the audio will loop, otherwise only play once.
+var loop := true
+
+
+################################################################################
+## EXECUTE
+################################################################################
+
+func _execute() -> void:
+ if not dialogic.Audio.is_music_playing_resource(file_path):
+ dialogic.Audio.update_music(file_path, volume, audio_bus, fade_length, loop)
+
+ finish()
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Music"
+ set_default_color('Color7')
+ event_category = "Audio"
+ event_sorting_index = 2
+
+
+func _get_icon() -> Resource:
+ return load(self.get_script().get_path().get_base_dir().path_join('icon_music.png'))
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func get_shortcode() -> String:
+ return "music"
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "path" : {"property": "file_path", "default": ""},
+ "fade" : {"property": "fade_length", "default": 0},
+ "volume" : {"property": "volume", "default": 0},
+ "bus" : {"property": "audio_bus", "default": "",
+ "suggestions": get_bus_suggestions},
+ "loop" : {"property": "loop", "default": true},
+ }
+
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('file_path', ValueType.FILE, {
+ 'left_text' : 'Play',
+ 'file_filter' : "*.mp3, *.ogg, *.wav; Supported Audio Files",
+ 'placeholder' : "No music",
+ 'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
+ add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Fade Time:'})
+ add_body_edit('volume', ValueType.NUMBER, {'left_text':'Volume:', 'mode':2}, '!file_path.is_empty()')
+ add_body_edit('audio_bus', ValueType.SINGLELINE_TEXT, {'left_text':'Audio Bus:'}, '!file_path.is_empty()')
+ add_body_edit('loop', ValueType.BOOL, {'left_text':'Loop:'}, '!file_path.is_empty() and not file_path.to_lower().ends_with(".wav")')
+
+
+func get_bus_suggestions() -> Dictionary:
+ var bus_name_list := {}
+ for i in range(AudioServer.bus_count):
+ bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)}
+ return bus_name_list
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/event_sound.gd b/addons/dialogic-main/addons/dialogic/Modules/Audio/event_sound.gd
new file mode 100644
index 0000000..8440061
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/event_sound.gd
@@ -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 := ""
+## The volume to play the sound at.
+var volume: float = 0
+## The bus to play the sound on.
+var audio_bus := ""
+## If true, the sound will loop infinitely. Not recommended (as there is no way to stop it).
+var loop := false
+
+
+################################################################################
+## EXECUTE
+################################################################################
+
+func _execute() -> void:
+ dialogic.Audio.play_sound(file_path, volume, audio_bus, loop)
+ finish()
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Sound"
+ set_default_color('Color7')
+ event_category = "Audio"
+ event_sorting_index = 3
+ help_page_path = "https://dialogic.coppolaemilio.com"
+
+
+func _get_icon() -> Resource:
+ return load(self.get_script().get_path().get_base_dir().path_join('icon_sound.png'))
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func get_shortcode() -> String:
+ return "sound"
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_name
+ "path" : {"property": "file_path", "default": "",},
+ "volume" : {"property": "volume", "default": 0},
+ "bus" : {"property": "audio_bus", "default": "",
+ "suggestions": get_bus_suggestions},
+ "loop" : {"property": "loop", "default": false},
+ }
+
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('file_path', ValueType.FILE,
+ {'left_text' : 'Play',
+ 'file_filter' : '*.mp3, *.ogg, *.wav; Supported Audio Files',
+ 'placeholder' : "Select file",
+ 'editor_icon' : ["AudioStreamPlayer", "EditorIcons"]})
+ add_body_edit('volume', ValueType.NUMBER, {'left_text':'Volume:', 'mode':2}, '!file_path.is_empty()')
+ add_body_edit('audio_bus', ValueType.SINGLELINE_TEXT, {'left_text':'Audio Bus:'}, '!file_path.is_empty()')
+
+
+func get_bus_suggestions() -> Dictionary:
+ var bus_name_list := {}
+ for i in range(AudioServer.bus_count):
+ bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)}
+ return bus_name_list
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png
new file mode 100644
index 0000000..c94600f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png.import b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png.import
new file mode 100644
index 0000000..820a6ae
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://buvpjsvdt4evk"
+path="res://.godot/imported/icon_music.png-11ca769b2b61bc3455c15c6b352a7112.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Audio/icon_music.png"
+dest_files=["res://.godot/imported/icon_music.png-11ca769b2b61bc3455c15c6b352a7112.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png
new file mode 100644
index 0000000..c117397
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png.import b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png.import
new file mode 100644
index 0000000..9916e86
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3ookrkto0yh6"
+path="res://.godot/imported/icon_sound.png-ca2b5b91232ce6a4e7cdd3f55131e611.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Audio/icon_sound.png"
+dest_files=["res://.godot/imported/icon_sound.png-ca2b5b91232ce6a4e7cdd3f55131e611.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Audio/index.gd
new file mode 100644
index 0000000..a75bc3d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/index.gd
@@ -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')}]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Audio/subsystem_audio.gd b/addons/dialogic-main/addons/dialogic/Modules/Audio/subsystem_audio.gd
new file mode 100644
index 0000000..e27fbeb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Audio/subsystem_audio.gd
@@ -0,0 +1,197 @@
+extends DialogicSubsystem
+## Subsystem for managing background music and one-shot sound effects.
+##
+## This subsystem has many different helper methods for managing audio
+## in your timeline.
+## For instance, you can listen to music changes via [signal music_started].
+
+
+## Whenever a new background music is started, this signal is emitted and
+## contains a dictionary with the following keys: [br]
+## [br]
+## Key | Value Type | Value [br]
+## ----------- | ------------- | ----- [br]
+## `path` | [type String] | The path to the audio resource file. [br]
+## `volume` | [type float] | The volume of the audio resource that will be set to the [member base_music_player]. [br]
+## `audio_bus` | [type String] | The audio bus name that the [member base_music_player] will use. [br]
+## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
+signal music_started(info: Dictionary)
+
+
+## Whenever a new sound effect is set, this signal is emitted and contains a
+## dictionary with the following keys: [br]
+## [br]
+## Key | Value Type | Value [br]
+## ----------- | ------------- | ----- [br]
+## `path` | [type String] | The path to the audio resource file. [br]
+## `volume` | [type float] | The volume of the audio resource that will be set to [member base_sound_player]. [br]
+## `audio_bus` | [type String] | The audio bus name that the [member base_sound_player] will use. [br]
+## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
+signal sound_started(info: Dictionary)
+
+
+## Audio player base duplicated to play background music.
+##
+## Background music is long audio.
+var base_music_player := AudioStreamPlayer.new()
+## Reference to the last used music player.
+var current_music_player: AudioStreamPlayer
+## Audio player base, that will be duplicated to play sound effects.
+##
+## Sound effects are short audio.
+var base_sound_player := AudioStreamPlayer.new()
+
+
+#region STATE
+####################################################################################################
+
+## Clears the state on this subsystem and stops all audio.
+##
+## If you want to stop sounds only, use [method stop_all_sounds].
+func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ update_music()
+ stop_all_sounds()
+
+
+## Loads the state on this subsystem from the current state info.
+func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void:
+ if load_flag == LoadFlags.ONLY_DNODES:
+ return
+ var info: Dictionary = dialogic.current_state_info.get("music", {})
+ if info.is_empty() or info.path.is_empty():
+ update_music()
+ else:
+ update_music(info.path, info.volume, info.audio_bus, 0, info.loop)
+
+
+## Pauses playing audio.
+func pause() -> void:
+ for child in get_children():
+ child.stream_paused = true
+
+
+## Resumes playing audio.
+func resume() -> void:
+ for child in get_children():
+ child.stream_paused = false
+
+
+func _on_dialogic_timeline_ended() -> void:
+ if not dialogic.Styles.get_layout_node():
+ clear_game_state()
+ pass
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+func _ready() -> void:
+ dialogic.timeline_ended.connect(_on_dialogic_timeline_ended)
+
+ base_music_player.name = "Music"
+ add_child(base_music_player)
+
+ base_sound_player.name = "Sound"
+ add_child(base_sound_player)
+
+
+## Updates the background music. Will fade out previous music.
+func update_music(path := "", volume := 0.0, audio_bus := "", fade_time := 0.0, loop := true) -> void:
+
+ dialogic.current_state_info['music'] = {'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop}
+ music_started.emit(dialogic.current_state_info['music'])
+
+ var fader: Tween = null
+ if is_instance_valid(current_music_player) and current_music_player.playing or !path.is_empty():
+ fader = create_tween()
+
+ var prev_node: Node = null
+ if is_instance_valid(current_music_player) and current_music_player.playing:
+ prev_node = current_music_player.duplicate()
+ add_child(prev_node)
+ prev_node.play(current_music_player.get_playback_position())
+ fader.tween_method(interpolate_volume_linearly.bind(prev_node), db_to_linear(prev_node.volume_db),0.0,fade_time)
+
+ if path:
+ current_music_player = base_music_player.duplicate()
+ add_child(current_music_player)
+ current_music_player.stream = load(path)
+ current_music_player.volume_db = volume
+ if audio_bus:
+ current_music_player.bus = audio_bus
+ if not current_music_player.stream is AudioStreamWAV:
+ if "loop" in current_music_player.stream:
+ current_music_player.stream.loop = loop
+ elif "loop_mode" in current_music_player.stream:
+ if loop:
+ current_music_player.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
+ else:
+ current_music_player.stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
+
+ current_music_player.play(0)
+ fader.parallel().tween_method(interpolate_volume_linearly.bind(current_music_player), 0.0, db_to_linear(volume),fade_time)
+ else:
+ if is_instance_valid(current_music_player):
+ current_music_player.stop()
+ current_music_player.queue_free()
+
+ if prev_node:
+ fader.tween_callback(prev_node.queue_free)
+
+
+## Whether music is playing.
+func has_music() -> bool:
+ return !dialogic.current_state_info.get('music', {}).get('path', '').is_empty()
+
+
+## Plays a given sound file.
+func play_sound(path: String, volume := 0.0, audio_bus := "", loop := false) -> void:
+ if base_sound_player != null and !path.is_empty():
+ sound_started.emit({'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop})
+
+ var new_sound_node := base_sound_player.duplicate()
+ new_sound_node.name += "Sound"
+ new_sound_node.stream = load(path)
+
+ if "loop" in new_sound_node.stream:
+ new_sound_node.stream.loop = loop
+ elif "loop_mode" in new_sound_node.stream:
+ if loop:
+ new_sound_node.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
+ else:
+ new_sound_node.stream.loop_mode = AudioStreamWAV.LOOP_DISABLED
+
+ new_sound_node.volume_db = volume
+ if audio_bus:
+ new_sound_node.bus = audio_bus
+
+ add_child(new_sound_node)
+ new_sound_node.play()
+ new_sound_node.finished.connect(new_sound_node.queue_free)
+
+
+## Stops all audio.
+func stop_all_sounds() -> void:
+ for node in get_children():
+ if node == base_sound_player:
+ continue
+ if "Sound" in node.name:
+ node.queue_free()
+
+
+## Converts a linear loudness value to decibel and sets that volume to
+## the given [param node].
+func interpolate_volume_linearly(value: float, node: Node) -> void:
+ node.volume_db = linear_to_db(value)
+
+
+## Returns whether the currently playing audio resource is the same as this
+## event's [param resource_path].
+func is_music_playing_resource(resource_path: String) -> bool:
+ var is_playing_resource: bool = (base_music_player.is_playing()
+ and base_music_player.stream.resource_path == resource_path)
+
+ return is_playing_resource
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd
new file mode 100644
index 0000000..ef3c570
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd
@@ -0,0 +1,27 @@
+extends DialogicBackground
+
+## The default background scene.
+## Extend the DialogicBackground class to create your own background scene.
+
+@onready var image_node: TextureRect = $Image
+@onready var color_node: ColorRect = $ColorRect
+
+
+func _ready() -> void:
+ image_node.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ image_node.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
+
+ image_node.anchor_right = 1
+ image_node.anchor_bottom = 1
+
+
+func _update_background(argument:String, _time:float) -> void:
+ if argument.begins_with('res://'):
+ image_node.texture = load(argument)
+ color_node.color = Color.TRANSPARENT
+ elif argument.is_valid_html_color():
+ image_node.texture = null
+ color_node.color = Color(argument, 1)
+ else:
+ image_node.texture = null
+ color_node.color = Color.from_string(argument, Color.TRANSPARENT)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.tscn b/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.tscn
new file mode 100644
index 0000000..e72fdc2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.tscn
@@ -0,0 +1,29 @@
+[gd_scene load_steps=2 format=3 uid="uid://cl6g6ymkhjven"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd" id="1_nkdrp"]
+
+[node name="DefaultBackground" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_nkdrp")
+
+[node name="ColorRect" type="ColorRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Image" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 0
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd
new file mode 100644
index 0000000..80154d9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd
@@ -0,0 +1,7 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_push_shader()
+ shader.set_shader_parameter('final_offset', Vector2.DOWN)
+ tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd
new file mode 100644
index 0000000..778e473
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd
@@ -0,0 +1,7 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_push_shader()
+ shader.set_shader_parameter('final_offset', Vector2.LEFT)
+ tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd
new file mode 100644
index 0000000..a7799eb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd
@@ -0,0 +1,7 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_push_shader()
+ shader.set_shader_parameter('final_offset', Vector2.RIGHT)
+ tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd
new file mode 100644
index 0000000..e75ec92
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd
@@ -0,0 +1,7 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_push_shader()
+ shader.set_shader_parameter('final_offset', Vector2.UP)
+ tween_shader_progress().set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd
new file mode 100644
index 0000000..917d367
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd
@@ -0,0 +1,13 @@
+extends DialogicBackgroundTransition
+
+
+func _fade() -> void:
+ var shader := set_shader()
+ shader.set_shader_parameter("wipe_texture", load(this_folder.path_join("simple_fade.tres")))
+
+ shader.set_shader_parameter("feather", 1)
+
+ shader.set_shader_parameter("previous_background", prev_texture)
+ shader.set_shader_parameter("next_background", next_texture)
+
+ tween_shader_progress()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.tres b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.tres
new file mode 100644
index 0000000..4873e08
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.tres
@@ -0,0 +1,8 @@
+[gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://qak7mr560k0i"]
+
+[sub_resource type="Gradient" id="Gradient_skd6w"]
+offsets = PackedFloat32Array(1)
+colors = PackedColorArray(0.423651, 0.423651, 0.423651, 1)
+
+[resource]
+gradient = SubResource("Gradient_skd6w")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd
new file mode 100644
index 0000000..061f943
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd
@@ -0,0 +1,8 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_swipe_shader()
+ var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
+ texture.fill_from = Vector2.DOWN
+ texture.fill_to = Vector2.RIGHT
+ tween_shader_progress()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd
new file mode 100644
index 0000000..8b55d41
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd
@@ -0,0 +1,10 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_swipe_shader()
+ var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
+
+ texture.fill_from = Vector2.ZERO
+ texture.fill_to = Vector2.RIGHT
+
+ tween_shader_progress()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd
new file mode 100644
index 0000000..5433d59
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd
@@ -0,0 +1,8 @@
+extends "res://addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd"
+
+func _fade() -> void:
+ var shader := setup_swipe_shader()
+ var texture: GradientTexture2D = shader.get_shader_parameter('wipe_texture')
+ texture.fill_from = Vector2.RIGHT
+ texture.fill_to = Vector2.ZERO
+ tween_shader_progress()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd
new file mode 100644
index 0000000..3f4f1f0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd
@@ -0,0 +1,56 @@
+class_name DialogicBackgroundTransition
+extends Node
+
+## Helper
+var this_folder: String = get_script().resource_path.get_base_dir()
+
+
+## Set before _fade() is called, will be the root node of the previous bg scene.
+var prev_scene: Node
+## Set before _fade() is called, will be the viewport texture of the previous bg scene.
+var prev_texture: ViewportTexture
+
+## Set before _fade() is called, will be the root node of the upcoming bg scene.
+var next_scene: Node
+## Set before _fade() is called, will be the viewport texture of the upcoming bg scene.
+var next_texture: ViewportTexture
+
+## Set before _fade() is called, will be the requested time for the fade
+var time: float
+
+## Set before _fade() is called, will be the background holder (TextureRect)
+var bg_holder: DialogicNode_BackgroundHolder
+
+
+signal transition_finished
+
+
+## To be overridden by transitions
+func _fade() -> void:
+ pass
+
+
+func set_shader(path_to_shader:String=DialogicUtil.get_module_path('Background').path_join("Transitions/default_transition_shader.gdshader")) -> ShaderMaterial:
+ if bg_holder:
+ if path_to_shader.is_empty():
+ bg_holder.material = null
+ bg_holder.color = Color.TRANSPARENT
+ return null
+ bg_holder.material = ShaderMaterial.new()
+ bg_holder.material.shader = load(path_to_shader)
+ return bg_holder.material
+ return null
+
+
+func tween_shader_progress(_progress_parameter:="progress") -> PropertyTweener:
+ if !bg_holder:
+ return
+
+ if !bg_holder.material is ShaderMaterial:
+ return
+
+ bg_holder.material.set_shader_parameter("progress", 0.0)
+ var tween := create_tween()
+ var tweener := tween.tween_property(bg_holder, "material:shader_parameter/progress", 1.0, time).from(0.0)
+ tween.tween_callback(emit_signal.bind('transition_finished'))
+ return tweener
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader
new file mode 100644
index 0000000..7fc2cf9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader
@@ -0,0 +1,36 @@
+shader_type canvas_item;
+
+// Indicates how far the transition is (0 start, 1 end).
+uniform float progress : hint_range(0.0, 1.0);
+// The previous background, transparent if there was none.
+uniform sampler2D previous_background : source_color, hint_default_transparent;
+// The next background, transparent if there is none.
+uniform sampler2D next_background : source_color, hint_default_transparent;
+
+// The texture used to determine how far along the progress has to be for bending in the new background.
+uniform sampler2D wipe_texture : source_color;
+// The size of the trailing smear of the transition.
+uniform float feather : hint_range(0.0, 1.0, 0.0001) = 0.1;
+// Determines if the wipe texture should keep it's aspect ratio when scaled to the screen's size.
+uniform bool keep_aspect_ratio = false;
+
+void fragment() {
+ vec2 frag_coord = UV;
+ if(keep_aspect_ratio) {
+ vec2 ratio = (SCREEN_PIXEL_SIZE.x > SCREEN_PIXEL_SIZE.y) // determine how to scale
+ ? vec2(SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x, 1) // fit to width
+ : vec2(1, SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y); // fit to height
+
+ frag_coord *= ratio;
+ frag_coord += ((vec2(1,1) - ratio) / 2.0);
+ }
+
+ // get the blend factor between the previous and next background.
+ float alpha = (texture(wipe_texture, frag_coord).r) - progress;
+ float blend_factor = 1. - smoothstep(0., feather, alpha + (feather * (1. -progress)));
+
+ vec4 old_frag = texture(previous_background, UV);
+ vec4 new_frag = texture(next_background, UV);
+
+ COLOR = mix(old_frag, new_frag, blend_factor);
+}
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader
new file mode 100644
index 0000000..0d29bf1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader
@@ -0,0 +1,17 @@
+shader_type canvas_item;
+
+uniform vec2 final_offset = vec2(0,-1);
+uniform float progress: hint_range(0.0, 1.0);
+uniform sampler2D previous_background: source_color, hint_default_transparent;
+uniform sampler2D next_background: source_color, hint_default_transparent;
+
+
+void fragment() {
+ vec2 uv = UV + final_offset * progress*vec2(-1, -1);
+
+ if (uv.x < 1.0 && uv.x > 0.0 && uv.y < 1.0 && uv.y > 0.0){
+ COLOR = texture(previous_background, uv, 1);
+ } else {
+ COLOR = texture(next_background, uv-final_offset*vec2(-1,-1));
+ }
+}
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd
new file mode 100644
index 0000000..43f1d74
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd
@@ -0,0 +1,9 @@
+extends DialogicBackgroundTransition
+
+func setup_push_shader() -> ShaderMaterial:
+ var shader := set_shader(DialogicUtil.get_module_path('Background').path_join("Transitions/push_transition_shader.gdshader"))
+
+ shader.set_shader_parameter("previous_background", prev_texture)
+ shader.set_shader_parameter("next_background", next_texture)
+
+ return shader
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_gradient.tres b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_gradient.tres
new file mode 100644
index 0000000..8f8a2a5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_gradient.tres
@@ -0,0 +1,7 @@
+[gd_resource type="GradientTexture2D" load_steps=2 format=3 uid="uid://cweb3y3xc4uw0"]
+
+[sub_resource type="Gradient" id="Gradient_skd6w"]
+colors = PackedColorArray(0, 0, 0, 1, 0.991164, 0.991164, 0.991164, 1)
+
+[resource]
+gradient = SubResource("Gradient_skd6w")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd
new file mode 100644
index 0000000..5705fc7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd
@@ -0,0 +1,14 @@
+extends DialogicBackgroundTransition
+
+func setup_swipe_shader() -> ShaderMaterial:
+ var shader := set_shader()
+ shader.set_shader_parameter("wipe_texture", load(
+ DialogicUtil.get_module_path('Background').path_join("Transitions/simple_swipe_gradient.tres")
+ ))
+
+ shader.set_shader_parameter("feather", 0.3)
+
+ shader.set_shader_parameter("previous_background", prev_texture)
+ shader.set_shader_parameter("next_background", next_texture)
+
+ return shader
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/dialogic_background.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/dialogic_background.gd
new file mode 100644
index 0000000..9e84a91
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/dialogic_background.gd
@@ -0,0 +1,38 @@
+extends Node
+class_name DialogicBackground
+
+## This is the base class for dialogic backgrounds.
+## Extend it and override it's methods when you create a custom background.
+## You can take a look at the default background to get an idea of how it's working.
+
+
+## The subviewport container that holds this background. Set when instanced.
+var viewport_container: SubViewportContainer
+## The viewport that holds this background. Set when instanced.
+var viewport: SubViewport
+
+
+## Load the new background in here.
+## The time argument is given for when [_should_do_background_update] returns true
+## (then you have to do a transition in here)
+func _update_background(_argument:String, _time:float) -> void:
+ pass
+
+
+## If a background event with this scene is encountered while this background is used,
+## this decides whether to create a new instance and call fade_out or just call [_update_background] # on this scene. Default is false
+func _should_do_background_update(_argument:String) -> bool:
+ return false
+
+
+## Called by dialogic when first created.
+## If you return false (by default) it will attempt to animate the "modulate" property.
+func _custom_fade_in(_time:float) -> bool:
+ return false
+
+
+## Called by dialogic before removing (done by dialogic).
+## If you return false (by default) it will attempt to animate the "modulate" property.
+func _custom_fade_out(_time:float) -> bool:
+ return false
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/event_background.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/event_background.gd
new file mode 100644
index 0000000..0380e69
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/event_background.gd
@@ -0,0 +1,156 @@
+@tool
+class_name DialogicBackgroundEvent
+extends DialogicEvent
+
+## Event to show scenes in the background and switch between them.
+
+### Settings
+
+## The scene to use. If empty, this will default to the DefaultBackground.gd scene.
+## This scene supports images and fading.
+## If you set it to a scene path, then that scene will be instanced.
+## Learn more about custom backgrounds in the Subsystem_Background.gd docs.
+var scene := ""
+## The argument that is passed to the background scene.
+## For the default scene it's the path to the image to show.
+var argument := ""
+## The time the fade animation will take. Leave at 0 for instant change.
+var fade: float = 0.0
+## Name of the transition to use.
+var transition := ""
+
+## Helpers for visual editor
+enum ArgumentTypes {IMAGE, CUSTOM}
+var _arg_type := ArgumentTypes.IMAGE :
+ get:
+ if argument.begins_with("res://"):
+ return ArgumentTypes.IMAGE
+ else:
+ return _arg_type
+ set(value):
+ if value == ArgumentTypes.CUSTOM:
+ if argument.begins_with("res://"):
+ argument = " "+argument
+ _arg_type = value
+
+enum SceneTypes {DEFAULT, CUSTOM}
+var _scene_type := SceneTypes.DEFAULT :
+ get:
+ if scene.is_empty():
+ return _scene_type
+ else:
+ return SceneTypes.CUSTOM
+ set(value):
+ if value == SceneTypes.DEFAULT:
+ scene = ""
+ _scene_type = value
+
+#region EXECUTION
+################################################################################
+
+func _execute() -> void:
+ var final_fade_duration := fade
+
+ if dialogic.Inputs.auto_skip.enabled:
+ var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
+ final_fade_duration = min(fade, time_per_event)
+
+ dialogic.Backgrounds.update_background(scene, argument, final_fade_duration, transition)
+
+ finish()
+
+#endregion
+
+#region INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Background"
+ set_default_color('Color8')
+ event_category = "Visuals"
+ event_sorting_index = 0
+
+#endregion
+
+#region SAVE & LOAD
+################################################################################
+
+func get_shortcode() -> String:
+ return "background"
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "scene" : {"property": "scene", "default": ""},
+ "arg" : {"property": "argument", "default": ""},
+ "fade" : {"property": "fade", "default": 0},
+ "transition" : {"property": "transition", "default": "",
+ "suggestions": get_transition_suggestions},
+ }
+
+
+#endregion
+
+#region EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('_scene_type', ValueType.FIXED_OPTIONS, {
+ 'left_text' :'Show',
+ 'options': [
+ {
+ 'label': 'Background',
+ 'value': SceneTypes.DEFAULT,
+ 'icon': ["GuiRadioUnchecked", "EditorIcons"]
+ },
+ {
+ 'label': 'Custom Scene',
+ 'value': SceneTypes.CUSTOM,
+ 'icon': ["PackedScene", "EditorIcons"]
+ }
+ ]})
+ add_header_label("with image", "_scene_type == SceneTypes.DEFAULT")
+ add_header_edit("scene", ValueType.FILE,
+ {'file_filter':'*.tscn, *.scn; Scene Files',
+ 'placeholder': "Custom scene",
+ 'editor_icon': ["PackedScene", "EditorIcons"],
+ }, '_scene_type == SceneTypes.CUSTOM')
+ add_header_edit('_arg_type', ValueType.FIXED_OPTIONS, {
+ 'left_text' : 'with',
+ 'options': [
+ {
+ 'label': 'Image',
+ 'value': ArgumentTypes.IMAGE,
+ 'icon': ["Image", "EditorIcons"]
+ },
+ {
+ 'label': 'Custom Argument',
+ 'value': ArgumentTypes.CUSTOM,
+ 'icon': ["String", "EditorIcons"]
+ }
+ ], "symbol_only": true}, "_scene_type == SceneTypes.CUSTOM")
+ add_header_edit('argument', ValueType.FILE,
+ {'file_filter':'*.jpg, *.jpeg, *.png, *.webp, *.tga, *svg, *.bmp, *.dds, *.exr, *.hdr; Supported Image Files',
+ 'placeholder': "No Image",
+ 'editor_icon': ["Image", "EditorIcons"],
+ },
+ '_arg_type == ArgumentTypes.IMAGE or _scene_type == SceneTypes.DEFAULT')
+ add_header_edit('argument', ValueType.SINGLELINE_TEXT, {}, '_arg_type == ArgumentTypes.CUSTOM')
+
+ add_body_edit("transition", ValueType.DYNAMIC_OPTIONS,
+ {'left_text':'Transition:',
+ 'empty_text':'Simple Fade',
+ 'suggestions_func':get_transition_suggestions,
+ 'editor_icon':["PopupMenu", "EditorIcons"]})
+ add_body_edit("fade", ValueType.NUMBER, {'left_text':'Fade time:'})
+
+
+func get_transition_suggestions(_filter:String="") -> Dictionary:
+ var transitions := DialogicResourceUtil.list_special_resources("BackgroundTransition")
+ var suggestions := {}
+ for i in transitions:
+ suggestions[DialogicUtil.pretty_name(i)] = {'value': DialogicUtil.pretty_name(i), 'editor_icon': ["PopupMenu", "EditorIcons"]}
+ return suggestions
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png b/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png
new file mode 100644
index 0000000..d4cf970
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png.import
new file mode 100644
index 0000000..c758138
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://517mp8gfj811"
+path="res://.godot/imported/icon.png-03ce51379144260166615caf778eafb2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Background/icon.png"
+dest_files=["res://.godot/imported/icon.png-03ce51379144260166615caf778eafb2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/index.gd
new file mode 100644
index 0000000..d40f030
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/index.gd
@@ -0,0 +1,13 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_background.gd')]
+
+func _get_subsystems() -> Array:
+ return [{'name':'Backgrounds', 'script':this_folder.path_join('subsystem_backgrounds.gd')}]
+
+
+func _get_special_resources() -> Dictionary:
+ return {&"BackgroundTransition":list_special_resources("Transitions/Defaults", ".gd")}
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/node_background_holder.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/node_background_holder.gd
new file mode 100644
index 0000000..11a2628
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/node_background_holder.gd
@@ -0,0 +1,6 @@
+class_name DialogicNode_BackgroundHolder
+extends ColorRect
+
+
+func _ready() -> void:
+ add_to_group('dialogic_background_holders')
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Background/subsystem_backgrounds.gd b/addons/dialogic-main/addons/dialogic/Modules/Background/subsystem_backgrounds.gd
new file mode 100644
index 0000000..b8f8c4c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Background/subsystem_backgrounds.gd
@@ -0,0 +1,186 @@
+extends DialogicSubsystem
+## Subsystem for managing backgrounds.
+##
+## This subsystem has many different helper methods for managing backgrounds.
+## For instance, you can listen to background changes via
+## [signal background_changed].
+
+
+## Whenever a new background is set, this signal is emitted and contains a
+## dictionary with the following keys: [br]
+## [br]
+## Key | Value Type | Value [br]
+## ----------- | ------------- | ----- [br]
+## `scene` | [type String] | The scene path of the new background. [br]
+## `argument` | [type String] | Information given to the background on its update routine. [br]
+## `fade_time` | [type float] | The time the background may take to transition in. [br]
+## `same_scene`| [type bool] | If the new background uses the same Godot scene. [br]
+signal background_changed(info: Dictionary)
+
+## The default background scene Dialogic will use.
+var default_background_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('DefaultBackgroundScene/default_background.tscn'))
+## The default transition Dialogic will use.
+var default_transition: String = get_script().resource_path.get_base_dir().path_join("Transitions/Defaults/simple_fade.gd")
+
+
+#region STATE
+####################################################################################################
+
+## Empties the current background state.
+func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ update_background()
+
+## Loads the background state from the current state info.
+func load_game_state(_load_flag := LoadFlags.FULL_LOAD) -> void:
+ update_background(dialogic.current_state_info.get('background_scene', ''), dialogic.current_state_info.get('background_argument', ''), 0.0, default_transition, true)
+
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+## Method that adds a given scene as child of the DialogicNode_BackgroundHolder.
+## It will call [_update_background()] on that scene with the given argument [argument].
+## It will call [_fade_in()] on that scene with the given fade time.
+## Will call fade_out on previous backgrounds scene.
+##
+## If the scene is the same as the last background you can bypass another instantiating
+## and use the same scene.
+## To do so implement [_should_do_background_update()] on the custom background scene.
+## Then [_update_background()] will be called directly on that previous scene.
+func update_background(scene := "", argument := "", fade_time := 0.0, transition_path:=default_transition, force := false) -> void:
+ var background_holder: DialogicNode_BackgroundHolder
+ if dialogic.has_subsystem('Styles'):
+ background_holder = dialogic.Styles.get_first_node_in_layout('dialogic_background_holders')
+ else:
+ background_holder = get_tree().get_first_node_in_group('dialogic_background_holders')
+
+ var info := {'scene':scene, 'argument':argument, 'fade_time':fade_time, 'same_scene':false}
+ if background_holder == null:
+ background_changed.emit(info)
+ return
+
+
+ var bg_set := false
+
+ # First try just updating the existing scene.
+ if scene == dialogic.current_state_info.get('background_scene', ''):
+
+ if not force and argument == dialogic.current_state_info.get('background_argument', ''):
+ return
+
+ for old_bg in background_holder.get_children():
+ if !old_bg.has_meta('node') or not old_bg.get_meta('node') is DialogicBackground:
+ continue
+
+ var prev_bg_node: DialogicBackground = old_bg.get_meta('node')
+ if prev_bg_node._should_do_background_update(argument):
+ prev_bg_node._update_background(argument, fade_time)
+ bg_set = true
+ info['same_scene'] = true
+
+ dialogic.current_state_info['background_scene'] = scene
+ dialogic.current_state_info['background_argument'] = argument
+
+ if bg_set:
+ background_changed.emit(info)
+ return
+
+ var old_viewport: SubViewportContainer = null
+ if background_holder.has_meta('current_viewport'):
+ old_viewport = background_holder.get_meta('current_viewport', null)
+
+ var new_viewport: SubViewportContainer
+ if scene.ends_with('.tscn') and ResourceLoader.exists(scene):
+ new_viewport = add_background_node(load(scene), background_holder)
+ elif argument:
+ new_viewport = add_background_node(default_background_scene, background_holder)
+ else:
+ new_viewport = null
+
+ var trans_script: Script = load(DialogicResourceUtil.guess_special_resource("BackgroundTransition", transition_path, {"path":default_transition}).path)
+ var trans_node := Node.new()
+ trans_node.set_script(trans_script)
+ trans_node = (trans_node as DialogicBackgroundTransition)
+ trans_node.bg_holder = background_holder
+ trans_node.time = fade_time
+
+ if old_viewport:
+ trans_node.prev_scene = old_viewport.get_meta('node', null)
+ trans_node.prev_texture = old_viewport.get_child(0).get_texture()
+ old_viewport.get_meta('node')._custom_fade_out(fade_time)
+ old_viewport.hide()
+ # TODO We have to call this again here because of https://github.com/godotengine/godot/issues/23729
+ old_viewport.get_child(0).render_target_update_mode = SubViewport.UPDATE_ALWAYS
+ trans_node.transition_finished.connect(old_viewport.queue_free)
+ if new_viewport:
+ trans_node.next_scene = new_viewport.get_meta('node', null)
+ trans_node.next_texture = new_viewport.get_child(0).get_texture()
+ new_viewport.get_meta('node')._update_background(argument, fade_time)
+ new_viewport.get_meta('node')._custom_fade_in(fade_time)
+ else:
+ background_holder.remove_meta('current_viewport')
+
+ add_child(trans_node)
+ if fade_time == 0:
+ trans_node.transition_finished.emit()
+ _on_transition_finished(background_holder, trans_node)
+ else:
+ trans_node.transition_finished.connect(_on_transition_finished.bind(background_holder, trans_node))
+ # We need to break this connection if the background_holder get's removed during the transition
+ background_holder.tree_exited.connect(trans_node.disconnect.bind("transition_finished", _on_transition_finished))
+ trans_node._fade()
+
+ background_changed.emit(info)
+
+
+func _on_transition_finished(background_node:DialogicNode_BackgroundHolder, transition_node:DialogicBackgroundTransition) -> void:
+ if background_node.has_meta("current_viewport"):
+ if background_node.get_meta("current_viewport").get_meta("node", null) == transition_node.next_scene:
+ background_node.get_meta("current_viewport").show()
+ background_node.material = null
+ background_node.color = Color.TRANSPARENT
+ transition_node.queue_free()
+
+
+## Adds sub-viewport with the given background scene as child to
+## Dialogic scene.
+func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder) -> SubViewportContainer:
+ var v_con := SubViewportContainer.new()
+ var viewport := SubViewport.new()
+ var b_scene := scene.instantiate()
+ if not b_scene is DialogicBackground:
+ printerr("[Dialogic] Given background scene was not of type DialogicBackground! Make sure the scene has a script that extends DialogicBackground.")
+ v_con.queue_free()
+ viewport.queue_free()
+ b_scene.queue_free()
+ return null
+
+ parent.add_child(v_con)
+ v_con.hide()
+ v_con.stretch = true
+ v_con.size = parent.size
+ v_con.set_anchors_preset(Control.PRESET_FULL_RECT)
+
+ v_con.add_child(viewport)
+ viewport.transparent_bg = true
+ viewport.disable_3d = true
+ viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
+ viewport.canvas_item_default_texture_filter = ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter")
+
+ viewport.add_child(b_scene)
+ b_scene.viewport = viewport
+ b_scene.viewport_container = v_con
+
+ parent.set_meta('current_viewport', v_con)
+ v_con.set_meta('node', b_scene)
+
+ return v_con
+
+
+## Whether a background is set.
+func has_background() -> bool:
+ return !dialogic.current_state_info.get('background_scene', '').is_empty() or !dialogic.current_state_info.get('background_argument','').is_empty()
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Call/event_call.gd b/addons/dialogic-main/addons/dialogic/Modules/Call/event_call.gd
new file mode 100644
index 0000000..0f2c05d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Call/event_call.gd
@@ -0,0 +1,263 @@
+@tool
+class_name DialogicCallEvent
+extends DialogicEvent
+
+## Event that allows calling a method in a node or autoload.
+
+### Settings
+
+## The name of the autoload to call the method on.
+var autoload_name := ""
+## The name of the method to call on the given autoload.
+var method := "":
+ set(value):
+ method = value
+ if Engine.is_editor_hint():
+ update_argument_info()
+ check_arguments_and_update_warning()
+## A list of arguments to give to the call.
+var arguments := []:
+ set(value):
+ arguments = value
+ if Engine.is_editor_hint():
+ check_arguments_and_update_warning()
+
+var _current_method_arg_hints := {'a':null, 'm':null, 'info':{}}
+
+################################################################################
+## EXECUTION
+################################################################################
+
+func _execute() -> void:
+ var object: Object = null
+ var obj_path := autoload_name
+ var autoload: Node = dialogic.get_node('/root/'+obj_path.get_slice('.', 0))
+ obj_path = obj_path.trim_prefix(obj_path.get_slice('.', 0)+'.')
+ object = autoload
+ if object:
+ while obj_path:
+ if obj_path.get_slice(".", 0) in object and object.get(obj_path.get_slice(".", 0)) is Object:
+ object = object.get(obj_path.get_slice(".", 0))
+ else:
+ break
+ obj_path = obj_path.trim_prefix(obj_path.get_slice('.', 0)+'.')
+
+ if object == null:
+ printerr("[Dialogic] Call event failed: Unable to find autoload '",autoload_name,"'")
+ finish()
+ return
+
+ if object.has_method(method):
+ var args := []
+ for arg in arguments:
+ if arg is String and arg.begins_with('@'):
+ args.append(dialogic.Expressions.execute_string(arg.trim_prefix('@')))
+ else:
+ args.append(arg)
+ dialogic.current_state = dialogic.States.WAITING
+ await object.callv(method, args)
+ dialogic.current_state = dialogic.States.IDLE
+ else:
+ printerr("[Dialogic] Call event failed: Autoload doesn't have the method '", method,"'.")
+
+ finish()
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Call"
+ set_default_color('Color6')
+ event_category = "Logic"
+ event_sorting_index = 10
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func to_text() -> String:
+ var result := "do "
+ if autoload_name:
+ result += autoload_name
+ if method:
+ result += '.'+method
+ if arguments.is_empty():
+ result += '()'
+ else:
+ result += '('
+ for i in arguments:
+ if i is String and i.begins_with('@'):
+ result += i.trim_prefix('@')
+ else:
+ result += var_to_str(i)
+ result += ', '
+ result = result.trim_suffix(', ')+')'
+ return result
+
+
+func from_text(string:String) -> void:
+ var result := RegEx.create_from_string(r"do (?[^\(]*)\.((?[^.(]*)(\((?.*)\))?)?").search(string.strip_edges())
+ if result:
+ autoload_name = result.get_string('autoload')
+ method = result.get_string('method')
+ if result.get_string('arguments').is_empty():
+ arguments = []
+ else:
+ var arr := []
+ for i in result.get_string('arguments').split(','):
+ i = i.strip_edges()
+ if str_to_var(i) != null:
+ arr.append(str_to_var(i))
+ else:
+ # Mark this as a complex expression
+ arr.append("@"+i)
+ arguments = arr
+
+
+func is_valid_event(string:String) -> bool:
+ if string.strip_edges().begins_with("do"):
+ return true
+ return false
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "autoload" : {"property": "autoload_name", "default": ""},
+ "method" : {"property": "method", "default": ""},
+ "args" : {"property": "arguments", "default": []},
+ }
+
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('autoload_name', ValueType.DYNAMIC_OPTIONS, {'left_text':'On autoload',
+ 'empty_text':'Autoload',
+ 'suggestions_func':get_autoload_suggestions,
+ 'editor_icon':["Node", "EditorIcons"]})
+ add_header_edit('method', ValueType.DYNAMIC_OPTIONS, {'left_text':'call',
+ 'empty_text':'Method',
+ 'suggestions_func':get_method_suggestions,
+ 'editor_icon':["Callable", "EditorIcons"]}, 'autoload_name')
+ add_body_edit('arguments', ValueType.ARRAY, {'left_text':'Arguments:'}, 'not autoload_name.is_empty() and not method.is_empty()')
+
+
+
+func get_autoload_suggestions(filter:String="") -> Dictionary:
+ var suggestions := {}
+
+ for prop in ProjectSettings.get_property_list():
+ if prop.name.begins_with('autoload/'):
+ var autoload: String = prop.name.trim_prefix('autoload/')
+ suggestions[autoload] = {'value': autoload, 'tooltip':autoload, 'editor_icon': ["Node", "EditorIcons"]}
+ if filter.begins_with(autoload):
+ suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
+ return suggestions
+
+
+func get_method_suggestions(filter:String="", temp_autoload:String = "") -> Dictionary:
+ var suggestions := {}
+
+ var script: Script
+ if temp_autoload and ProjectSettings.has_setting('autoload/'+temp_autoload):
+ script = load(ProjectSettings.get_setting('autoload/'+temp_autoload).trim_prefix('*'))
+
+ elif autoload_name and ProjectSettings.has_setting('autoload/'+autoload_name):
+ var loaded_autoload := load(ProjectSettings.get_setting('autoload/'+autoload_name).trim_prefix('*'))
+
+ if loaded_autoload is PackedScene:
+ var packed_scene: PackedScene = loaded_autoload
+ script = packed_scene.instantiate().get_script()
+
+ else:
+ script = loaded_autoload
+
+ if script:
+ for script_method in script.get_script_method_list():
+ if script_method.name.begins_with('@') or script_method.name.begins_with('_'):
+ continue
+ suggestions[script_method.name] = {'value': script_method.name, 'tooltip':script_method.name, 'editor_icon': ["Callable", "EditorIcons"]}
+ if !filter.is_empty():
+ suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]}
+ return suggestions
+
+
+func update_argument_info() -> void:
+ if autoload_name and method and not _current_method_arg_hints.is_empty() and (_current_method_arg_hints.a == autoload_name and _current_method_arg_hints.m == method):
+ if !ResourceLoader.exists(ProjectSettings.get_setting('autoload/'+autoload_name, '').trim_prefix('*')):
+ _current_method_arg_hints = {}
+ return
+ var script: Script = load(ProjectSettings.get_setting('autoload/'+autoload_name, '').trim_prefix('*'))
+ for m in script.get_script_method_list():
+ if m.name == method:
+ _current_method_arg_hints = {'a':autoload_name, 'm':method, 'info':m}
+ break
+
+
+func check_arguments_and_update_warning() -> void:
+ if not _current_method_arg_hints.has("info") or _current_method_arg_hints.info.is_empty():
+ ui_update_warning.emit()
+ return
+
+ var idx := -1
+ for arg in arguments:
+ idx += 1
+ if len(_current_method_arg_hints.info.args) <= idx:
+ continue
+ if _current_method_arg_hints.info.args[idx].type != 0:
+ if _current_method_arg_hints.info.args[idx].type != typeof(arg):
+ if arg is String and arg.begins_with('@'):
+ continue
+ var expected_type: String = ""
+ match _current_method_arg_hints.info.args[idx].type:
+ TYPE_BOOL: expected_type = "bool"
+ TYPE_STRING: expected_type = "string"
+ TYPE_FLOAT: expected_type = "float"
+ TYPE_INT: expected_type = "int"
+ _: expected_type = "something else"
+
+ ui_update_warning.emit('Argument '+ str(idx+1)+ ' ('+_current_method_arg_hints.info.args[idx].name+') has the wrong type (method expects '+expected_type+')!')
+ return
+
+ if len(arguments) < len(_current_method_arg_hints.info.args)-len(_current_method_arg_hints.info.default_args):
+ ui_update_warning.emit("The method is expecting at least "+str(len(_current_method_arg_hints.info.args)-len(_current_method_arg_hints.info.default_args))+ " arguments, but is given only "+str(len(arguments))+".")
+ return
+ elif len(arguments) > len(_current_method_arg_hints.info.args):
+ ui_update_warning.emit("The method is expecting at most "+str(len(_current_method_arg_hints.info.args))+ " arguments, but is given "+str(len(arguments))+".")
+ return
+ ui_update_warning.emit()
+
+####################### CODE COMPLETION ########################################
+################################################################################
+
+func _get_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
+ if line.count(' ') == 1 and not '.' in line:
+ for i in get_autoload_suggestions():
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'.', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), TextNode.get_theme_icon("Node", "EditorIcons"))
+ elif symbol == '.' and not '(' in line:
+ for i in get_method_suggestions('', line.get_slice('.', 0).trim_prefix('do ')):
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'(', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), TextNode.get_theme_icon("Callable", "EditorIcons"))
+
+
+func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'do', 'do ', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), _get_icon())
+
+
+#################### SYNTAX HIGHLIGHTING #######################################
+################################################################################
+
+func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
+ dict[line.find('do')] = {"color":event_color.lerp(Highlighter.normal_color, 0.3)}
+ dict[line.find('do')+2] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
+
+ Highlighter.color_region(dict, Highlighter.normal_color, line, '(', ')')
+ Highlighter.color_region(dict, Highlighter.string_color, line, '"', '"')
+ Highlighter.color_word(dict, Highlighter.boolean_operator_color, line, 'true')
+ Highlighter.color_word(dict, Highlighter.boolean_operator_color, line, 'false')
+ return dict
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png b/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png
new file mode 100644
index 0000000..0bda4de
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png.import
new file mode 100644
index 0000000..ff781e2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Call/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://duvcdvtgy4h4b"
+path="res://.godot/imported/icon.png-c8b4238ef68bc8e6ab753af18e6ff278.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Call/icon.png"
+dest_files=["res://.godot/imported/icon.png-c8b4238ef68bc8e6ab753af18e6ff278.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Call/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Call/index.gd
new file mode 100644
index 0000000..c8817a7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Call/index.gd
@@ -0,0 +1,6 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_call.gd')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd
new file mode 100644
index 0000000..b99e409
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd
@@ -0,0 +1,16 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT)
+ tween.tween_property(node, 'position:y', base_position.y-node.get_viewport().size.y/10, time*0.4).set_trans(Tween.TRANS_EXPO)
+ tween.parallel().tween_property(node, 'scale:y', base_scale.y*1.05, time*0.4).set_trans(Tween.TRANS_EXPO)
+ tween.tween_property(node, 'position:y', base_position.y, time*0.6).set_trans(Tween.TRANS_BOUNCE)
+ tween.parallel().tween_property(node, 'scale:y', base_scale.y, time*0.6).set_trans(Tween.TRANS_BOUNCE)
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "bounce": {"type": AnimationType.ACTION},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd
new file mode 100644
index 0000000..d5484d9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd
@@ -0,0 +1,39 @@
+extends DialogicAnimation
+
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+
+ var end_scale: Vector2 = node.scale
+ var end_modulate_alpha := 1.0
+ var modulation_property := get_modulation_property()
+
+ if is_reversed:
+ end_scale = Vector2(0, 0)
+ end_modulate_alpha = 0.0
+
+ else:
+ node.scale = Vector2(0, 0)
+ var original_modulation: Color = node.get(modulation_property)
+ original_modulation.a = 0.0
+ node.set(modulation_property, original_modulation)
+
+
+ tween.set_ease(Tween.EASE_IN_OUT)
+ tween.set_trans(Tween.TRANS_SINE)
+ tween.set_parallel()
+
+ (tween.tween_property(node, "scale", end_scale, time)
+ .set_trans(Tween.TRANS_SPRING)
+ .set_ease(Tween.EASE_OUT))
+ tween.tween_property(node, modulation_property + ":a", end_modulate_alpha, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "bounce in": {"reversed": false, "type": AnimationType.IN},
+ "bounce out": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd
new file mode 100644
index 0000000..847bda7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd
@@ -0,0 +1,44 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+
+ var start_height: float = base_position.y - node.get_viewport().size.y / 5
+ var end_height := base_position.y
+
+ var start_modulation := 0.0
+ var end_modulation := 1.0
+
+ if is_reversed:
+ end_height = start_height
+ start_height = base_position.y
+ end_modulation = 0.0
+ start_modulation = 1.0
+
+ node.position.y = start_height
+
+ tween.set_ease(Tween.EASE_OUT)
+ tween.set_trans(Tween.TRANS_SINE)
+ tween.set_parallel()
+
+ var end_postion := Vector2(base_position.x, end_height)
+ tween.tween_property(node, "position", end_postion, time)
+
+ var property := get_modulation_property()
+
+ var original_modulation: Color = node.get(property)
+ original_modulation.a = start_modulation
+ node.set(property, original_modulation)
+ var modulation_alpha := property + ":a"
+
+ tween.tween_property(node, modulation_alpha, end_modulation, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "fade in down": {"reversed": false, "type": AnimationType.IN},
+ "fade out up": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd
new file mode 100644
index 0000000..e432ecd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd
@@ -0,0 +1,34 @@
+extends DialogicAnimation
+
+func animate() -> void:
+
+ var modulation_property := get_modulation_property()
+ var end_modulation_alpha := 1.0
+
+ if is_reversed:
+ end_modulation_alpha = 0.0
+
+ else:
+ var original_modulation: Color = node.get(modulation_property)
+ original_modulation.a = 0.0
+ node.set(modulation_property, original_modulation)
+
+ var tween := (node.create_tween() as Tween)
+ if is_reversed:
+ tween.set_ease(Tween.EASE_IN)
+ else:
+ tween.set_ease(Tween.EASE_OUT)
+ tween.set_trans(Tween.TRANS_SINE)
+ tween.tween_property(node, modulation_property + ":a", end_modulation_alpha, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "fade in": {"reversed": false, "type": AnimationType.IN},
+ "fade out": {"reversed": true, "type": AnimationType.OUT},
+ "fade cross": {"type": AnimationType.CROSSFADE},
+ }
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd
new file mode 100644
index 0000000..83a8ba8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd
@@ -0,0 +1,44 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+
+ var start_height: float = base_position.y + node.get_viewport().size.y / 5
+ var end_height := base_position.y
+
+ var start_modulation := 0.0
+ var end_modulation := 1.0
+
+ if is_reversed:
+ end_height = start_height
+ start_height = base_position.y
+ end_modulation = 0.0
+ start_modulation = 1.0
+
+ node.position.y = start_height
+
+ tween.set_ease(Tween.EASE_OUT)
+ tween.set_trans(Tween.TRANS_SINE)
+ tween.set_parallel()
+
+ var end_postion := Vector2(base_position.x, end_height)
+ tween.tween_property(node, "position", end_postion, time)
+
+ var property := get_modulation_property()
+
+ var original_modulation: Color = node.get(property)
+ original_modulation.a = start_modulation
+ node.set(property, original_modulation)
+ var modulation_alpha := property + ":a"
+
+ tween.tween_property(node, modulation_alpha, end_modulation, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "fade in up": {"reversed": false, "type": AnimationType.IN},
+ "fade out down": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd
new file mode 100644
index 0000000..36e8eab
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd
@@ -0,0 +1,13 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.tween_property(node, 'scale', Vector2(1,1)*1.2, time*0.5).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
+ tween.tween_property(node, 'scale', Vector2(1,1), time*0.5).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "heartbeat": {"type": AnimationType.ACTION},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd
new file mode 100644
index 0000000..3044c69
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd
@@ -0,0 +1,12 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ await node.get_tree().process_frame
+ finished.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "instant in": {"reversed": false, "type": AnimationType.IN},
+ "instant out": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd
new file mode 100644
index 0000000..de445fc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd
@@ -0,0 +1,20 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
+ var strength: float = node.get_viewport().size.x/60
+ var bound_multitween := DialogicUtil.multitween.bind(node, "position", "animation_shake_x")
+ tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.2)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.1)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.1)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(-1,0)*strength, time*0.1)
+ tween.tween_method(bound_multitween, Vector2(), Vector2(1, 0)*strength, time*0.2)
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "shake x": {"type": AnimationType.ACTION},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd
new file mode 100644
index 0000000..6a0224c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd
@@ -0,0 +1,23 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
+
+ var strength: float = node.get_viewport().size.y/40
+ tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.2)
+ tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y - strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y + strength, time * 0.1)
+ tween.tween_property(node, 'position:y', base_position.y, time * 0.2)
+
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "shake y": {"type": AnimationType.ACTION},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd
new file mode 100644
index 0000000..af01248
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd
@@ -0,0 +1,26 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
+
+ var target_position := base_position.y
+ var start_position: float = -node.get_viewport().size.y
+
+ if is_reversed:
+ target_position = -node.get_viewport().size.y
+ start_position = base_position.y
+
+ node.position.y = start_position
+
+ tween.tween_property(node, 'position:y', target_position, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "slide in down": {"reversed": false, "type": AnimationType.IN},
+ "slide out up": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd
new file mode 100644
index 0000000..f0d7df3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd
@@ -0,0 +1,26 @@
+extends DialogicAnimation
+
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
+
+ var end_position_x: float = base_position.x
+
+ if is_reversed:
+ end_position_x = -node.get_viewport().size.x / 2
+
+ else:
+ node.position.x = -node.get_viewport().size.x / 5
+
+ tween.tween_property(node, 'position:x', end_position_x, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "slide in left": {"reversed": false, "type": AnimationType.IN},
+ "slide out right": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd
new file mode 100644
index 0000000..3e4a5a4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd
@@ -0,0 +1,27 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
+
+ var viewport_x: float = node.get_viewport().size.x
+
+ var start_position_x: float = viewport_x + viewport_x / 5
+ var end_position_x := base_position.x
+
+ if is_reversed:
+ start_position_x = base_position.x
+ end_position_x = viewport_x + node.get_viewport().size.x / 5
+
+
+ node.position.x = start_position_x
+ tween.tween_property(node, 'position:x', end_position_x, time)
+
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "slide in right": {"reversed": false, "type": AnimationType.IN},
+ "slide out left": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd
new file mode 100644
index 0000000..fcb1e33
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd
@@ -0,0 +1,25 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
+
+ var start_position_y: float = node.get_viewport().size.y * 2
+ var end_position_y := base_position.y
+
+ if is_reversed:
+ start_position_y = base_position.y
+ end_position_y = node.get_viewport().size.y * 2
+
+ node.position.y = start_position_y
+ tween.tween_property(node, 'position:y', end_position_y, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "slide in up": {"reversed": false, "type": AnimationType.IN},
+ "slide out down": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd
new file mode 100644
index 0000000..0d578b1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd
@@ -0,0 +1,25 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var tween := (node.create_tween() as Tween)
+ tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
+
+ var strength: float = 0.01
+
+ tween.set_parallel(true)
+ tween.tween_property(node, 'scale', Vector2(1,1)*(1+strength), time*0.3)
+ tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.2)
+ tween.tween_property(node, 'rotation', strength, time*0.1).set_delay(time*0.3)
+ tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.4)
+ tween.tween_property(node, 'rotation', strength, time*0.1).set_delay(time*0.5)
+ tween.tween_property(node, 'rotation', -strength, time*0.1).set_delay(time*0.6)
+ tween.chain().tween_property(node, 'scale', Vector2(1,1), time*0.3)
+ tween.parallel().tween_property(node, 'rotation', 0.0, time*0.3)
+
+ tween.finished.connect(emit_signal.bind('finished_once'))
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "tada": {"type": AnimationType.ACTION},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd
new file mode 100644
index 0000000..fad8b61
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd
@@ -0,0 +1,36 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var modulate_property := get_modulation_property()
+ var modulate_alpha_property := modulate_property + ":a"
+
+ var end_scale: Vector2 = node.scale
+ var end_modulation_alpha := 1.0
+
+ if is_reversed:
+ end_modulation_alpha = 0.0
+
+ else:
+ node.scale = Vector2(0, 0)
+ node.position.y = base_position.y - node.get_viewport().size.y * 0.5
+
+ var original_modulation: Color = node.get(modulate_property)
+ original_modulation.a = 0.0
+ node.set(modulate_property, original_modulation)
+
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_EXPO)
+ tween.set_parallel(true)
+ tween.tween_property(node, "scale", end_scale, time)
+ tween.tween_property(node, "position", base_position, time)
+ tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "zoom center in": {"reversed": false, "type": AnimationType.IN},
+ "zoom center out": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd
new file mode 100644
index 0000000..21cc338
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd
@@ -0,0 +1,35 @@
+extends DialogicAnimation
+
+func animate() -> void:
+ var modulate_property := get_modulation_property()
+ var modulate_alpha_property := modulate_property + ":a"
+
+ var end_scale: Vector2 = node.scale
+ var end_modulation_alpha := 1.0
+
+ if is_reversed:
+ end_scale = Vector2(0, 0)
+ end_modulation_alpha = 0.0
+
+ else:
+ node.scale = Vector2(0,0)
+
+ var original_modulation: Color = node.get(modulate_property)
+ original_modulation.a = 0.0
+ node.set(modulate_property, original_modulation)
+
+ var tween := (node.create_tween() as Tween)
+ tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO)
+ tween.set_parallel(true)
+ tween.tween_property(node, "scale", end_scale, time)
+ tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time)
+
+ await tween.finished
+ finished_once.emit()
+
+
+func _get_named_variations() -> Dictionary:
+ return {
+ "zoom in": {"reversed": false, "type": AnimationType.IN},
+ "zoom out": {"reversed": true, "type": AnimationType.OUT},
+ }
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd
new file mode 100644
index 0000000..66908e3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd
@@ -0,0 +1,47 @@
+@tool
+class_name DialogicPortraitAnimationUtil
+
+enum AnimationType {ALL=-1, IN=1, OUT=2, ACTION=3, CROSSFADE=4}
+
+
+static func guess_animation(string:String, type := AnimationType.ALL) -> String:
+ var default := {}
+ var filter := {}
+ var ignores := []
+ match type:
+ AnimationType.ALL:
+ pass
+ AnimationType.IN:
+ filter = {"type":AnimationType.IN}
+ ignores = ["in"]
+ AnimationType.OUT:
+ filter = {"type":AnimationType.OUT}
+ ignores = ["out"]
+ AnimationType.ACTION:
+ filter = {"type":AnimationType.ACTION}
+ AnimationType.CROSSFADE:
+ filter = {"type":AnimationType.CROSSFADE}
+ ignores = ["cross"]
+ return DialogicResourceUtil.guess_special_resource(&"PortraitAnimation", string, default, filter, ignores).get("path", "")
+
+
+static func get_portrait_animations_filtered(type := AnimationType.ALL) -> Dictionary:
+ var filter := {"type":type}
+ if type == AnimationType.ALL:
+ filter["type"] = [AnimationType.IN, AnimationType.OUT, AnimationType.ACTION]
+ return DialogicResourceUtil.list_special_resources("PortraitAnimation", filter)
+
+
+static func get_suggestions(_search_text := "", current_value:= "", empty_text := "Default", action := AnimationType.ALL) -> Dictionary:
+ var suggestions := {}
+
+ if empty_text and current_value:
+ suggestions[empty_text] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
+
+ for anim_name in get_portrait_animations_filtered(action):
+ suggestions[DialogicUtil.pretty_name(anim_name)] = {
+ 'value' : DialogicUtil.pretty_name(anim_name),
+ 'editor_icon' : ["Animation", "EditorIcons"]
+ }
+
+ return suggestions
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/class_dialogic_animation.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/class_dialogic_animation.gd
new file mode 100644
index 0000000..9792ab2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/class_dialogic_animation.gd
@@ -0,0 +1,77 @@
+class_name DialogicAnimation
+extends Node
+
+## Class that can be used to animate portraits. Can be extended to create animations.
+
+enum AnimationType {IN=1, OUT=2, ACTION=3, CROSSFADE=4}
+
+signal finished_once
+signal finished
+
+## Set at runtime, will be the node to animate.
+var node: Node
+
+## Set at runtime, will be the length of the animation.
+var time: float
+
+## Set at runtime, will be the base position of the node.
+## Depending on the animation, this might be the start, end or both.
+var base_position: Vector2
+## Set at runtime, will be the base scale of the node.
+var base_scale: Vector2
+
+## Used to repeate the animation for a number of times.
+var repeats: int
+
+## If `true`, the animation will be reversed.
+## This must be implemented by each animation or it will have no effect.
+var is_reversed: bool = false
+
+
+func _ready() -> void:
+ finished_once.connect(finished_one_loop)
+
+
+## To be overridden. Do the actual animating/tweening in here.
+## Use the properties [member node], [member time], [member base_position], etc.
+func animate() -> void:
+ pass
+
+
+## This method controls whether to repeat the animation or not.
+## Animations must call this once they finished an animation.
+func finished_one_loop() -> void:
+ repeats -= 1
+
+ if repeats > 0:
+ animate()
+
+ else:
+ finished.emit()
+
+
+func pause() -> void:
+ if node:
+ node.process_mode = Node.PROCESS_MODE_DISABLED
+
+
+func resume() -> void:
+ if node:
+ node.process_mode = Node.PROCESS_MODE_INHERIT
+
+
+func _get_named_variations() -> Dictionary:
+ return {}
+
+
+## If the animation wants to change the modulation, this method
+## will return the property to change.
+##
+## The [class CanvasGroup] can use `self_modulate` instead of `modulate`
+## to uniformly change the modulation of all children without additively
+## overlaying the modulations.
+func get_modulation_property() -> String:
+ if node is CanvasGroup:
+ return "self_modulate"
+ else:
+ return "modulate"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png b/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png
new file mode 100644
index 0000000..77d3cf3
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import b/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import
new file mode 100644
index 0000000..d20f832
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5tu88x32sjkf"
+path="res://.godot/imported/custom_portrait_thumbnail.png-78c478d77787a80b5846f6ea6aa03006.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png"
+dest_files=["res://.godot/imported/custom_portrait_thumbnail.png-78c478d77787a80b5846f6ea6aa03006.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.gd
new file mode 100644
index 0000000..dcd89b0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.gd
@@ -0,0 +1,16 @@
+@tool
+extends DialogicPortrait
+
+## Default portrait scene.
+## The parent class has a character and portrait variable.
+
+@export_group('Main')
+@export_file var image := ""
+
+
+## Load anything related to the given character and portrait
+func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
+ apply_character_and_portrait(passed_character, passed_portrait)
+
+ apply_texture($Portrait, image)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.tscn b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.tscn
new file mode 100644
index 0000000..be594e7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=3 uid="uid://b32paf0ll6um8"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/default_portrait.gd" id="1_wn77n"]
+
+[node name="DefaultPortrait" type="Node2D"]
+script = ExtResource("1_wn77n")
+
+[node name="Portrait" type="Sprite2D" parent="."]
+centered = false
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png
new file mode 100644
index 0000000..369a111
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import
new file mode 100644
index 0000000..aeffd26
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b0eisk30btlx2"
+path="res://.godot/imported/default_portrait_thumbnail.png-cfec329742af0d17c1264dadc7fc6771.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/default_portrait_thumbnail.png"
+dest_files=["res://.godot/imported/default_portrait_thumbnail.png-cfec329742af0d17c1264dadc7fc6771.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/dialogic_portrait.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/dialogic_portrait.gd
new file mode 100644
index 0000000..b2bf47a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/dialogic_portrait.gd
@@ -0,0 +1,121 @@
+class_name DialogicPortrait
+extends Node
+
+## Default portrait class. Should be extended by custom portraits.
+
+## Stores the character that this scene displays.
+var character: DialogicCharacter
+## Stores the name of the current portrait.
+var portrait: String
+
+#region MAIN OVERRIDES
+################################################################################
+
+## This function can be overridden.
+## If this returns true, it won't instance a new scene, but call
+## [method _update_portrait] on this one.
+## This is only relevant if the next portrait uses the same scene.
+## This allows implementing transitions between portraits that use the same scene.
+func _should_do_portrait_update(_character: DialogicCharacter, _portrait: String) -> bool:
+ return false
+
+
+## If the custom portrait accepts a change, then accept it here
+## You should position your portrait so that the root node is at the pivot point*.
+## For example for a simple sprite this code would work:
+## >>> $Sprite.position = $Sprite.get_rect().size * Vector2(-0.5, -1)
+##
+## * this depends on the portrait containers, but it will most likely be the bottom center (99% of cases)
+func _update_portrait(_passed_character: DialogicCharacter, _passed_portrait: String) -> void:
+ pass
+
+
+## This should be implemented. It is used for sizing in the
+## character editor preview and in portrait containers.
+## Scale and offset will be applied by Dialogic.
+## For example, a simple sprite:
+## >>> return Rect2($Sprite.position, $Sprite.get_rect().size)
+##
+## This will only work as expected if the portrait is positioned so that the
+## root is at the pivot point.
+##
+## If you've used apply_texture this should work automatically.
+func _get_covered_rect() -> Rect2:
+ if has_meta('texture_holder_node') and get_meta('texture_holder_node', null) != null and is_instance_valid(get_meta('texture_holder_node')):
+ var node: Node = get_meta('texture_holder_node')
+ if node is Sprite2D or node is TextureRect:
+ return Rect2(node.position, node.get_rect().size)
+ return Rect2()
+
+
+## If implemented, this is called when the mirror changes
+func _set_mirror(mirror:bool) -> void:
+ if has_meta('texture_holder_node') and get_meta('texture_holder_node', null) != null and is_instance_valid(get_meta('texture_holder_node')):
+ var node: Node = get_meta('texture_holder_node')
+ if node is Sprite2D or node is TextureRect:
+ node.flip_h = mirror
+
+
+## Function to accept and use the extra data, if the custom portrait wants to accept it
+func _set_extra_data(_data: String) -> void:
+ pass
+
+#endregion
+
+#region HIGHLIGHT OVERRIDES
+################################################################################
+
+## Called when this becomes the active speaker
+func _highlight() -> void:
+ pass
+
+
+## Called when this stops being the active speaker
+func _unhighlight() -> void:
+ pass
+#endregion
+
+
+#region HELPERS
+################################################################################
+
+## Helper that quickly setups and checks the character and portrait.
+func apply_character_and_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
+ if passed_portrait == "" or not passed_portrait in passed_character.portraits.keys():
+ passed_portrait = passed_character.default_portrait
+
+ portrait = passed_portrait
+ character = passed_character
+
+
+func apply_texture(node:Node, texture_path:String) -> void:
+ if not character or not character.portraits.has(portrait):
+ return
+
+ if not "texture" in node:
+ return
+
+ node.texture = null
+
+ if not ResourceLoader.exists(texture_path):
+ # This is a leftover from alpha.
+ # Removing this will break any portraits made before alpha-10
+ if ResourceLoader.exists(character.portraits[portrait].get('image', '')):
+ texture_path = character.portraits[portrait].get('image', '')
+ else:
+ return
+
+ node.texture = load(texture_path)
+
+ if node is Sprite2D or node is TextureRect:
+ if node is Sprite2D:
+ node.centered = false
+ node.scale = Vector2.ONE
+ if node is TextureRect:
+ if !is_inside_tree():
+ await ready
+ node.position = node.get_rect().size * Vector2(-0.5, -1)
+
+ set_meta('texture_holder_node', node)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/event_character.gd
new file mode 100644
index 0000000..1294d51
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/event_character.gd
@@ -0,0 +1,575 @@
+@tool
+class_name DialogicCharacterEvent
+extends DialogicEvent
+## Event that allows to manipulate character portraits.
+
+enum Actions {JOIN, LEAVE, UPDATE}
+
+### Settings
+
+## The type of action of this event (JOIN/LEAVE/UPDATE). See [Actions].
+var action := Actions.JOIN
+## The character that will join/leave/update.
+var character: DialogicCharacter = null
+## For Join/Update, this will be the portrait of the character that is shown.
+## Not used on Leave.
+## If empty, the default portrait will be used.
+var portrait := ""
+## The index of the position this character should move to
+var transform := "center"
+
+## Name of the animation script (extending DialogicAnimation).
+## On Join/Leave empty (default) will fallback to the animations set in the settings.
+## On Update empty will mean no animation.
+var animation_name := ""
+## Length of the animation.
+var animation_length: float = 0.5
+## How often the animation is repeated. Only for Update events.
+var animation_repeats: int = 1
+## If true, the events waits for the animation to finish before the next event starts.
+var animation_wait := false
+
+## The fade animation to use. If left empty, the default cross-fade animation AND time will be used.
+var fade_animation := ""
+var fade_length := 0.5
+
+## For Update only. If bigger then 0, the portrait will tween to the
+## new position (if changed) in this time (in seconds).
+var transform_time: float = 0.0
+var transform_ease := Tween.EaseType.EASE_IN_OUT
+var transform_trans := Tween.TransitionType.TRANS_SINE
+
+var ease_options := [
+ {'label': 'In', 'value': Tween.EASE_IN},
+ {'label': 'Out', 'value': Tween.EASE_OUT},
+ {'label': 'In_Out', 'value': Tween.EASE_IN_OUT},
+ {'label': 'Out_In', 'value': Tween.EASE_OUT_IN},
+ ]
+
+var trans_options := [
+ {'label': 'Linear', 'value': Tween.TRANS_LINEAR},
+ {'label': 'Sine', 'value': Tween.TRANS_SINE},
+ {'label': 'Quint', 'value': Tween.TRANS_QUINT},
+ {'label': 'Quart', 'value': Tween.TRANS_QUART},
+ {'label': 'Quad', 'value': Tween.TRANS_QUAD},
+ {'label': 'Expo', 'value': Tween.TRANS_EXPO},
+ {'label': 'Elastic', 'value': Tween.TRANS_ELASTIC},
+ {'label': 'Cubic', 'value': Tween.TRANS_CUBIC},
+ {'label': 'Circ', 'value': Tween.TRANS_CIRC},
+ {'label': 'Bounce', 'value': Tween.TRANS_BOUNCE},
+ {'label': 'Back', 'value': Tween.TRANS_BACK},
+ {'label': 'Spring', 'value': Tween.TRANS_SPRING}
+ ]
+
+## The z_index that the portrait should have.
+var z_index: int = 0
+## If true, the portrait will be set to mirrored.
+var mirrored := false
+## If set, will be passed to the portrait scene.
+var extra_data := ""
+
+
+### Helpers
+
+## Indicators for whether something should be updated (UPDATE mode only)
+var set_portrait := false
+var set_transform := false
+var set_z_index := false
+var set_mirrored := false
+## Used to set the character resource from the unique name identifier and vice versa
+var character_identifier: String:
+ get:
+ if character_identifier == '--All--':
+ return '--All--'
+ if character:
+ var identifier := DialogicResourceUtil.get_unique_identifier(character.resource_path)
+ if not identifier.is_empty():
+ return identifier
+ return character_identifier
+ set(value):
+ character_identifier = value
+ character = DialogicResourceUtil.get_character_resource(value)
+
+var regex := RegEx.create_from_string(r'(?join|update|leave)\s*(")?(?(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?.*)\))?(\s*(?[^\[]*))?(\s*\[(?.*)\])?')
+
+################################################################################
+## EXECUTION
+################################################################################
+
+func _execute() -> void:
+ if not character and not character_identifier == "--All--":
+ finish()
+ return
+
+ # Calculate animation time (can be shortened during skipping)
+ var final_animation_length: float = animation_length
+ var final_position_move_time: float = transform_time
+ if dialogic.Inputs.auto_skip.enabled:
+ var max_time: float = dialogic.Inputs.auto_skip.time_per_event
+ final_animation_length = min(max_time, animation_length)
+ final_position_move_time = min(max_time, transform_time)
+
+
+ # JOIN -------------------------------------
+ if action == Actions.JOIN:
+ if dialogic.has_subsystem('History') and !dialogic.Portraits.is_character_joined(character):
+ var character_name_text := dialogic.Text.get_character_name_parsed(character)
+ dialogic.History.store_simple_history_entry(character_name_text + " joined", event_name, {'character': character_name_text, 'mode':'Join'})
+
+ await dialogic.Portraits.join_character(
+ character, portrait, transform,
+ mirrored, z_index, extra_data,
+ animation_name, final_animation_length, animation_wait)
+
+ # LEAVE -------------------------------------
+ elif action == Actions.LEAVE:
+ if character_identifier == '--All--':
+ if dialogic.has_subsystem('History') and len(dialogic.Portraits.get_joined_characters()):
+ dialogic.History.store_simple_history_entry("Everyone left", event_name, {'character': "All", 'mode':'Leave'})
+
+ await dialogic.Portraits.leave_all_characters(
+ animation_name,
+ final_animation_length,
+ animation_wait
+ )
+
+ elif character:
+ if dialogic.has_subsystem('History') and dialogic.Portraits.is_character_joined(character):
+ var character_name_text := dialogic.Text.get_character_name_parsed(character)
+ dialogic.History.store_simple_history_entry(character_name_text+" left", event_name, {'character': character_name_text, 'mode':'Leave'})
+
+ await dialogic.Portraits.leave_character(
+ character,
+ animation_name,
+ final_animation_length,
+ animation_wait
+ )
+
+ # UPDATE -------------------------------------
+ elif action == Actions.UPDATE:
+ if not character or not dialogic.Portraits.is_character_joined(character):
+ finish()
+ return
+
+ dialogic.Portraits.change_character_extradata(character, extra_data)
+
+ if set_portrait:
+ dialogic.Portraits.change_character_portrait(character, portrait, fade_animation, fade_length)
+
+ if set_mirrored:
+ dialogic.Portraits.change_character_mirror(character, mirrored)
+
+ if set_z_index:
+ dialogic.Portraits.change_character_z_index(character, z_index)
+
+ if set_transform:
+ dialogic.Portraits.move_character(character, transform, final_position_move_time, transform_ease, transform_trans)
+
+ if animation_name:
+ var final_animation_repetitions: int = animation_repeats
+
+ if dialogic.Inputs.auto_skip.enabled:
+ var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
+ var time_for_repetitions: float = time_per_event / animation_repeats
+ final_animation_length = time_for_repetitions
+
+ var animation := dialogic.Portraits.animate_character(
+ character,
+ animation_name,
+ final_animation_length,
+ final_animation_repetitions,
+ )
+
+ if animation_wait:
+ dialogic.current_state = DialogicGameHandler.States.ANIMATING
+ await animation.finished
+ dialogic.current_state = DialogicGameHandler.States.IDLE
+
+
+ finish()
+
+
+#region INITIALIZE
+###############################################################################
+
+func _init() -> void:
+ event_name = "Character"
+ set_default_color('Color2')
+ event_category = "Main"
+ event_sorting_index = 2
+
+
+func _get_icon() -> Resource:
+ return load(self.get_script().get_path().get_base_dir().path_join('icon.svg'))
+
+#endregion
+
+#region SAVING, LOADING, DEFAULTS
+################################################################################
+
+func to_text() -> String:
+ var result_string := ""
+
+ # ACTIONS
+ match action:
+ Actions.JOIN: result_string += "join "
+ Actions.LEAVE: result_string += "leave "
+ Actions.UPDATE: result_string += "update "
+
+ var default_values := DialogicUtil.get_custom_event_defaults(event_name)
+
+ # CHARACTER IDENTIFIER
+ if action == Actions.LEAVE and character_identifier == '--All--':
+ result_string += "--All--"
+ elif character:
+ var name := DialogicResourceUtil.get_unique_identifier(character.resource_path)
+
+ if name.count(" ") > 0:
+ name = '"' + name + '"'
+
+ result_string += name
+
+ # PORTRAIT
+ if portrait.strip_edges() != default_values.get('portrait', ''):
+ if action != Actions.LEAVE and (action != Actions.UPDATE or set_portrait):
+ result_string += " (" + portrait + ")"
+
+ # TRANSFORM
+ if action == Actions.JOIN or (action == Actions.UPDATE and set_transform):
+ result_string += " " + str(transform)
+
+ # SETS:
+ if action == Actions.JOIN or action == Actions.LEAVE:
+ set_mirrored = mirrored != default_values.get("mirrored", false)
+ set_z_index = z_index != default_values.get("z_index", 0)
+
+ var shortcode := store_to_shortcode_parameters()
+
+ if shortcode != "":
+ result_string += " [" + shortcode + "]"
+
+ return result_string
+
+
+func from_text(string:String) -> void:
+ # Load default character
+ character = DialogicResourceUtil.get_character_resource(character_identifier)
+
+ var result := regex.search(string)
+
+ # ACTION
+ match result.get_string('type'):
+ "join": action = Actions.JOIN
+ "leave": action = Actions.LEAVE
+ "update": action = Actions.UPDATE
+
+ # CHARACTER
+ var given_name := result.get_string('name').strip_edges()
+ var given_portrait := result.get_string('portrait').strip_edges()
+ var given_transform := result.get_string('transform').strip_edges()
+
+ if given_name:
+ if action == Actions.LEAVE and given_name == "--All--":
+ character_identifier = '--All--'
+ else:
+ character = DialogicResourceUtil.get_character_resource(given_name)
+
+ # PORTRAIT
+ if given_portrait:
+ portrait = given_portrait.trim_prefix('(').trim_suffix(')')
+ set_portrait = true
+
+ # TRANSFORM
+ if given_transform:
+ transform = given_transform
+ set_transform = true
+
+ # SHORTCODE
+ if not result.get_string('shortcode'):
+ return
+
+ load_from_shortcode_parameters(result.get_string('shortcode'))
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "action" : {"property": "action", "default": 0, "custom_stored":true,
+ "suggestions": func(): return {'Join':
+ {'value':Actions.JOIN},
+ 'Leave':{'value':Actions.LEAVE},
+ 'Update':{'value':Actions.UPDATE}}},
+ "character" : {"property": "character_identifier", "default": "", "custom_stored":true,},
+ "portrait" : {"property": "portrait", "default": "", "custom_stored":true,},
+ "transform" : {"property": "transform", "default": "center", "custom_stored":true,},
+
+ "animation" : {"property": "animation_name", "default": ""},
+ "length" : {"property": "animation_length", "default": 0.5},
+ "wait" : {"property": "animation_wait", "default": false},
+ "repeat" : {"property": "animation_repeats", "default": 1},
+
+ "z_index" : {"property": "z_index", "default": 0},
+ "mirrored" : {"property": "mirrored", "default": false},
+ "fade" : {"property": "fade_animation", "default":""},
+ "fade_length" : {"property": "fade_length", "default":0.5},
+ "move_time" : {"property": "transform_time", "default": 0.0},
+ "move_ease" : {"property": "transform_ease", "default": Tween.EaseType.EASE_IN_OUT,
+ "suggestions": func(): return list_to_suggestions(ease_options)},
+ "move_trans" : {"property": "transform_trans", "default": Tween.TransitionType.TRANS_SINE,
+ "suggestions": func(): return list_to_suggestions(trans_options)},
+ "extra_data" : {"property": "extra_data", "default": ""},
+ }
+
+
+func is_valid_event(string:String) -> bool:
+ if string.begins_with("join") or string.begins_with("leave") or string.begins_with("update"):
+ return true
+ return false
+
+#endregion
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('action', ValueType.FIXED_OPTIONS, {
+ 'options': [
+ {
+ 'label': 'Join',
+ 'value': Actions.JOIN,
+ 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/join.svg")
+ },
+ {
+ 'label': 'Leave',
+ 'value': Actions.LEAVE,
+ 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/leave.svg")
+ },
+ {
+ 'label': 'Update',
+ 'value': Actions.UPDATE,
+ 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/update.svg")
+ }
+ ]
+ })
+ add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS,
+ {'placeholder' : 'Character',
+ 'file_extension' : '.dch',
+ 'mode' : 2,
+ 'suggestions_func' : get_character_suggestions,
+ 'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg"),
+ 'autofocus' : true})
+
+ add_header_edit('set_portrait', ValueType.BOOL_BUTTON,
+ {'icon':load("res://addons/dialogic/Modules/Character/update_portrait.svg"),
+ 'tooltip':'Change Portrait'}, "should_show_portrait_selector() and action == Actions.UPDATE")
+ add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS,
+ {'placeholder' : 'Default',
+ 'collapse_when_empty':true,
+ 'suggestions_func' : get_portrait_suggestions,
+ 'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")},
+ 'should_show_portrait_selector() and (action != Actions.UPDATE or set_portrait)')
+ add_header_edit('set_transform', ValueType.BOOL_BUTTON,
+ {'icon': load("res://addons/dialogic/Modules/Character/update_position.svg"), 'tooltip':'Change Position'}, "character != null and !has_no_portraits() and action == Actions.UPDATE")
+ add_header_label('at position', 'character != null and !has_no_portraits() and action == Actions.JOIN')
+ add_header_label('to position', 'character != null and !has_no_portraits() and action == Actions.UPDATE and set_transform')
+ add_header_edit('transform', ValueType.DYNAMIC_OPTIONS,
+ {'placeholder' : 'center',
+ 'mode' : 0,
+ 'suggestions_func' : get_position_suggestions,
+ 'tooltip' : "You can use a predefined position or a custom transform like 'pos=x0.5y1 size=x0.5y1 rot=10'.\nLearn more about this in the documentation."},
+ 'character != null and !has_no_portraits() and action != %s and (action != Actions.UPDATE or set_transform)' %Actions.LEAVE)
+
+ # Body
+ add_body_edit('fade_animation', ValueType.DYNAMIC_OPTIONS,
+ {'left_text' : 'Fade:',
+ 'suggestions_func' : get_fade_suggestions,
+ 'editor_icon' : ["Animation", "EditorIcons"],
+ 'placeholder' : 'Default',
+ 'enable_pretty_name' : true},
+ 'should_show_fade_options()')
+ add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
+ 'should_show_fade_options() and !fade_animation.is_empty()')
+ add_body_line_break("should_show_fade_options()")
+ add_body_edit('animation_name', ValueType.DYNAMIC_OPTIONS,
+ {'left_text' : 'Animation:',
+ 'suggestions_func' : get_animation_suggestions,
+ 'editor_icon' : ["Animation", "EditorIcons"],
+ 'placeholder' : 'Default',
+ 'enable_pretty_name' : true},
+ 'should_show_animation_options()')
+ add_body_edit('animation_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
+ 'should_show_animation_options() and !animation_name.is_empty()')
+ add_body_edit('animation_wait', ValueType.BOOL, {'left_text':'Await end:'},
+ 'should_show_animation_options() and !animation_name.is_empty()')
+ add_body_edit('animation_repeats', ValueType.NUMBER, {'left_text':'Repeat:', 'mode':1, "min":1},
+ 'should_show_animation_options() and !animation_name.is_empty() and action == %s)' %Actions.UPDATE)
+ add_body_line_break()
+ add_body_edit('transform_time', ValueType.NUMBER, {'left_text':'Movement duration:', "min":0},
+ "should_show_transform_options()")
+ add_body_edit("transform_trans", ValueType.FIXED_OPTIONS, {'options':trans_options, 'left_text':"Trans:"}, 'should_show_transform_options() and transform_time > 0')
+ add_body_edit("transform_ease", ValueType.FIXED_OPTIONS, {'options':ease_options, 'left_text':"Ease:"}, 'should_show_transform_options() and transform_time > 0')
+
+ add_body_edit('set_z_index', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_z_index.svg"), 'tooltip':'Change Z-Index'}, "character != null and action == Actions.UPDATE")
+ add_body_edit('z_index', ValueType.NUMBER, {'left_text':'Z-index:', 'mode':1},
+ 'action != %s and (action != Actions.UPDATE or set_z_index)' %Actions.LEAVE)
+ add_body_edit('set_mirrored', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_mirror.svg"), 'tooltip':'Change Mirroring'}, "character != null and action == Actions.UPDATE")
+ add_body_edit('mirrored', ValueType.BOOL, {'left_text':'Mirrored:'},
+ 'action != %s and (action != Actions.UPDATE or set_mirrored)' %Actions.LEAVE)
+ add_body_edit('extra_data', ValueType.SINGLELINE_TEXT, {'left_text':'Extra Data:'}, 'action != Actions.LEAVE')
+
+
+func should_show_transform_options() -> bool:
+ return action == Actions.UPDATE and set_transform
+
+
+func should_show_animation_options() -> bool:
+ return (character and !character.portraits.is_empty()) or character_identifier == '--All--'
+
+
+func should_show_fade_options() -> bool:
+ return action == Actions.UPDATE and set_portrait and character and not character.portraits.is_empty()
+
+
+func should_show_portrait_selector() -> bool:
+ return character and len(character.portraits) > 1 and action != Actions.LEAVE
+
+
+func has_no_portraits() -> bool:
+ return character and character.portraits.is_empty()
+
+
+func get_character_suggestions(search_text:String) -> Dictionary:
+ return DialogicUtil.get_character_suggestions(search_text, character, false, action == Actions.LEAVE, editor_node)
+
+
+func get_portrait_suggestions(search_text:String) -> Dictionary:
+ var empty_text := "Don't Change"
+ if action == Actions.JOIN:
+ empty_text = "Default portrait"
+ return DialogicUtil.get_portrait_suggestions(search_text, character, true, empty_text)
+
+
+func get_position_suggestions(search_text:String='') -> Dictionary:
+ return DialogicUtil.get_portrait_position_suggestions(search_text)
+
+
+func get_animation_suggestions(search_text:String='') -> Dictionary:
+ var DPAU := DialogicPortraitAnimationUtil
+ match action:
+ Actions.JOIN:
+ return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.IN)
+ Actions.LEAVE:
+ return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.OUT)
+ Actions.UPDATE:
+ return DPAU.get_suggestions(search_text, animation_name, "None", DPAU.AnimationType.ACTION)
+ return {}
+
+
+func get_fade_suggestions(search_text:String='') -> Dictionary:
+ return DialogicPortraitAnimationUtil.get_suggestions(search_text, fade_animation, "Default", DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
+
+
+####################### CODE COMPLETION ########################################
+################################################################################
+
+func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
+ var line_until_caret: String = CodeCompletionHelper.get_line_untill_caret(line)
+ if symbol == ' ' and line_until_caret.count(' ') == 1:
+ CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_MEMBER)
+ if line.begins_with('leave'):
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'All', '--All-- ', event_color, TextNode.get_theme_icon("GuiEllipsis", "EditorIcons"))
+
+ if symbol == '(':
+ var completion_character := regex.search(line).get_string('name')
+ CodeCompletionHelper.suggest_portraits(TextNode, completion_character)
+
+ elif not '[' in line_until_caret and symbol == ' ':
+ if not line.begins_with("leave"):
+ for position in get_position_suggestions():
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, position, position+' ', TextNode.syntax_highlighter.normal_color)
+
+ # Shortcode Part
+ if '[' in line_until_caret:
+ # Suggest Parameters
+ if symbol == '[' or symbol == ' ' and line_until_caret.count('"')%2 == 0:# and (symbol == "[" or (symbol == " " and line_until_caret.rfind('="') < line_until_caret.rfind('"')-1)):
+ suggest_parameter("animation", line, TextNode)
+
+ if "animation=" in line:
+ for param in ["length", "wait"]:
+ suggest_parameter(param, line, TextNode)
+ if line.begins_with('update'):
+ suggest_parameter("repeat", line, TextNode)
+ if line.begins_with("update"):
+ for param in ["move_time", "move_trans", "move_ease"]:
+ suggest_parameter(param, line, TextNode)
+ if not line.begins_with('leave'):
+ for param in ["mirrored", "z_index", "extra_data"]:
+ suggest_parameter(param, line, TextNode)
+
+ # Suggest Values
+ else:
+ var current_param: RegExMatch = CodeCompletionHelper.completion_shortcode_param_getter_regex.search(line)
+ if not current_param:
+ return
+
+ match current_param.get_string("param"):
+ "animation":
+ var animations := {}
+ if line.begins_with('join'):
+ animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.IN)
+ elif line.begins_with('update'):
+ animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.ACTION)
+ elif line.begins_with('leave'):
+ animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.OUT)
+
+ for script: String in animations:
+ TextNode.add_code_completion_option(CodeEdit.KIND_VARIABLE, DialogicUtil.pretty_name(script), DialogicUtil.pretty_name(script), TextNode.syntax_highlighter.normal_color, null, '" ')
+
+ "wait", "mirrored":
+ CodeCompletionHelper.suggest_bool(TextNode, TextNode.syntax_highlighter.normal_color)
+ "move_trans":
+ CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(trans_options), TextNode, TextNode.syntax_highlighter.normal_color)
+ "move_ease":
+ CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(ease_options), TextNode, TextNode.syntax_highlighter.normal_color)
+
+
+func suggest_parameter(parameter:String, line:String, TextNode:TextEdit) -> void:
+ if not parameter + "=" in line:
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, parameter, parameter + '="', TextNode.syntax_highlighter.normal_color)
+
+
+func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'join', 'join ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/join.svg'))
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'leave', 'leave ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/leave.svg'))
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'update', 'update ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/update.svg'))
+
+
+#################### SYNTAX HIGHLIGHTING #######################################
+################################################################################
+
+func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
+ var word := line.get_slice(' ', 0)
+
+ dict[line.find(word)] = {"color":event_color}
+ dict[line.find(word)+len(word)] = {"color":Highlighter.normal_color}
+ var result := regex.search(line)
+ if result.get_string('name'):
+ dict[result.get_start('name')] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
+ dict[result.get_end('name')] = {"color":Highlighter.normal_color}
+ if result.get_string('portrait'):
+ dict[result.get_start('portrait')] = {"color":event_color.lerp(Highlighter.normal_color, 0.6)}
+ dict[result.get_end('portrait')] = {"color":Highlighter.normal_color}
+ if result.get_string('shortcode'):
+ dict = Highlighter.color_shortcode_content(dict, line, result.get_start('shortcode'), result.get_end('shortcode'), event_color)
+ return dict
+
+
+## HELPER
+func list_to_suggestions(list:Array) -> Dictionary:
+ return list.reduce(
+ func(accum, value):
+ accum[value.label] = value
+ accum[value.label]["text_alt"] = [value.label.to_lower()]
+ return accum,
+ {})
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.png.import
new file mode 100644
index 0000000..ec40123
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.png.import
@@ -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
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg
new file mode 100644
index 0000000..6638227
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg.import
new file mode 100644
index 0000000..9ea9fdd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://q6lanmf18ii6"
+path="res://.godot/imported/icon.svg-0a72e51b6e4c45d0b567e7849afdf64b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/icon.svg"
+dest_files=["res://.godot/imported/icon.svg-0a72e51b6e4c45d0b567e7849afdf64b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png b/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png
new file mode 100644
index 0000000..7f01934
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png.import b/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png.import
new file mode 100644
index 0000000..6a199f4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cu6cj55m6dr78"
+path="res://.godot/imported/icon_position.png-5c6af532d706a9bcbecdc4c8c594ae4b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/icon_position.png"
+dest_files=["res://.godot/imported/icon_position.png-5c6af532d706a9bcbecdc4c8c594ae4b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/index.gd
new file mode 100644
index 0000000..805d62a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/index.gd
@@ -0,0 +1,55 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_character.gd')]
+
+
+func _get_subsystems() -> Array:
+ return [{'name':'Portraits', 'script':this_folder.path_join('subsystem_portraits.gd')}, {'name':'PortraitContainers', 'script':this_folder.path_join('subsystem_containers.gd')}]
+
+func _get_settings_pages() -> Array:
+ return [this_folder.path_join('settings_portraits.tscn')]
+
+func _get_text_effects() -> Array[Dictionary]:
+ return [{'command':'portrait', 'subsystem':'Portraits', 'method':'text_effect_portrait', 'arg':true}]
+
+
+func _get_special_resources() -> Dictionary:
+ return {&'PortraitAnimation': list_animations("DefaultAnimations")}
+
+
+func _get_portrait_scene_presets() -> Array[Dictionary]:
+ return [
+ {
+ "path": "",
+ "name": "Default Scene",
+ "description": "The default scene defined in Settings>Portraits.",
+ "author":"Dialogic",
+ "type": "Default",
+ "icon":"",
+ "preview_image":[this_folder.path_join("default_portrait_thumbnail.png")],
+ "documentation":"",
+ },
+ {
+ "path": "CUSTOM",
+ "name": "Custom Scene",
+ "description": "A custom scene. Should extend DialogicPortrait and be in @tool mode.",
+ "author":"Dialogic",
+ "type": "Custom",
+ "icon":"",
+ "preview_image":[this_folder.path_join("custom_portrait_thumbnail.png")],
+ "documentation":"https://docs.dialogic.pro/custom-portraits.html",
+ },
+ {
+ "path": this_folder.path_join("default_portrait.tscn"),
+ "name": "Simple Image Portrait",
+ "description": "Can display images as portraits. Does nothing else.",
+ "author":"Dialogic",
+ "type": "General",
+ "icon":"",
+ "preview_image":[this_folder.path_join("simple_image_portrait_thumbnail.png")],
+ "documentation":"",
+ }
+ ]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/node_portrait_container.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/node_portrait_container.gd
new file mode 100644
index 0000000..5b48398
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/node_portrait_container.gd
@@ -0,0 +1,259 @@
+@tool
+class_name DialogicNode_PortraitContainer
+extends Control
+
+## Node that defines a position for dialogic portraits and how to display portraits at that position.
+
+enum PositionModes {
+ POSITION, ## This container can be joined/moved to with the Character Event
+ SPEAKER, ## This container is joined/left automatically based on the speaker.
+ }
+
+@export var mode := PositionModes.POSITION
+
+@export_subgroup('Mode: Position')
+## The position this node corresponds to.
+@export var container_ids: PackedStringArray = ["1"]
+
+
+@export_subgroup('Mode: Speaker')
+## Can be used to use a different portrait.
+## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
+@export var portrait_prefix := ''
+
+@export_subgroup('Portrait Placement')
+enum SizeModes {
+ KEEP, ## The height and width of the container have no effect, only the origin.
+ FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
+ FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
+ FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
+ }
+## Defines how to affect the scale of the portrait
+@export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT :
+ set(mode):
+ size_mode = mode
+ _update_debug_portrait_transform()
+
+## If true, portraits will be mirrored in this position.
+@export var mirrored := false :
+ set(mirror):
+ mirrored = mirror
+ _update_debug_portrait_scene()
+
+
+@export_group('Origin', 'origin')
+enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
+## The portrait will be placed relative to this point in the container.
+@export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER :
+ set(anchor):
+ origin_anchor = anchor
+ _update_debug_origin()
+
+## An offset to apply to the origin. Rarely useful.
+@export var origin_offset := Vector2() :
+ set(offset):
+ origin_offset = offset
+ _update_debug_origin()
+
+enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
+## Usually you want to rotate or scale around the portrait origin.
+## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
+@export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
+## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
+@export var pivot_value := Vector2()
+
+@export_group('Debug', 'debug')
+## A character that will be displayed in the editor, useful for getting the right size.
+@export var debug_character: DialogicCharacter = null:
+ set(character):
+ debug_character = character
+ _update_debug_portrait_scene()
+@export var debug_character_portrait := "":
+ set(portrait):
+ debug_character_portrait = portrait
+ _update_debug_portrait_scene()
+
+var debug_character_holder_node: Node2D = null
+var debug_character_scene_node: Node = null
+var debug_origin: Sprite2D = null
+var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
+# Used if no debug character is specified
+var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))
+
+var ignore_resize := false
+
+
+func _ready() -> void:
+ match mode:
+ PositionModes.POSITION:
+ add_to_group('dialogic_portrait_con_position')
+ PositionModes.SPEAKER:
+ add_to_group('dialogic_portrait_con_speaker')
+
+ if Engine.is_editor_hint():
+ resized.connect(_update_debug_origin)
+
+ if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
+ default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
+
+ debug_origin = Sprite2D.new()
+ add_child(debug_origin)
+ debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")
+
+ _update_debug_origin()
+ _update_debug_portrait_scene()
+ else:
+ resized.connect(update_portrait_transforms)
+
+
+################################################################################
+## MAIN METHODS
+################################################################################
+
+func update_portrait_transforms() -> void:
+ if ignore_resize:
+ return
+
+ match pivot_mode:
+ PivotModes.AT_ORIGIN:
+ pivot_offset = _get_origin_position()
+ PivotModes.PERCENTAGE:
+ pivot_offset = size*pivot_value
+ PivotModes.PIXELS:
+ pivot_offset = pivot_value
+
+ for child in get_children():
+ DialogicUtil.autoload().Portraits._update_character_transform(child)
+
+
+## Returns a Rect2 with the position as the position and the scale as the size.
+func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
+ var transform := Rect2()
+ transform.position = _get_origin_position()
+
+ # Mode that ignores the containers size
+ if size_mode == SizeModes.KEEP:
+ transform.size = Vector2(1,1) * character_scale
+
+ # Mode that makes sure neither height nor width go out of container
+ elif size_mode == SizeModes.FIT_IGNORE_SCALE:
+ if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
+ transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
+ else:
+ transform.size = Vector2(1,1) * size.y/portrait_rect.size.y
+
+ # Mode that stretches the portrait to fill the whole container
+ elif size_mode == SizeModes.FIT_STRETCH:
+ transform.size = size/portrait_rect.size
+
+ # Mode that size the character so 100% size fills the height
+ elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
+ transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale
+
+ return transform
+
+
+## Returns the current origin position
+func _get_origin_position(rect_size = null) -> Vector2:
+ if rect_size == null:
+ rect_size = size
+ return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset
+
+
+func is_container(id:Variant) -> bool:
+ return str(id) in container_ids
+
+#region DEBUG METHODS
+################################################################################
+### USE THIS TO DEBUG THE POSITIONS
+#func _draw():
+ #draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
+ #draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
+#
+#func _process(delta:float) -> void:
+ #queue_redraw()
+
+
+## Loads the debug_character with the debug_character_portrait
+## Creates a holder node and applies mirror
+func _update_debug_portrait_scene() -> void:
+ if !Engine.is_editor_hint():
+ return
+ if is_instance_valid(debug_character_holder_node):
+ for child in get_children():
+ if child != debug_origin:
+ child.free()
+
+ # Get character
+ var character := _get_debug_character()
+ if not character is DialogicCharacter or character.portraits.is_empty():
+ return
+
+ # Determine portrait
+ var debug_portrait := debug_character_portrait
+ if debug_portrait.is_empty():
+ debug_portrait = character.default_portrait
+ if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
+ if portrait_prefix+debug_portrait in character.portraits:
+ debug_portrait = portrait_prefix+debug_portrait
+ if not debug_portrait in character.portraits:
+ debug_portrait = character.default_portrait
+
+ var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)
+
+ # Determine scene
+ var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene)
+ if portrait_scene_path.is_empty():
+ portrait_scene_path = default_portrait_scene
+
+ debug_character_scene_node = load(portrait_scene_path).instantiate()
+
+ if !is_instance_valid(debug_character_scene_node):
+ return
+
+ # Load portrait
+ DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
+ debug_character_scene_node._update_portrait(character, debug_portrait)
+
+ # Add character node
+ if !is_instance_valid(debug_character_holder_node):
+ debug_character_holder_node = Node2D.new()
+ add_child(debug_character_holder_node)
+ print(debug_character_holder_node)
+
+ # Add portrait node
+ debug_character_holder_node.add_child(debug_character_scene_node)
+ move_child(debug_character_holder_node, 0)
+ debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))
+
+ _update_debug_portrait_transform()
+
+
+## Set's the size and position of the holder and scene node
+## according to the size_mode
+func _update_debug_portrait_transform() -> void:
+ if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
+ return
+ var character := _get_debug_character()
+ var portrait_info := character.get_portrait_info(debug_character_portrait)
+ var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
+ debug_character_holder_node.position = transform.position
+ debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset
+
+ debug_character_holder_node.scale = transform.size
+
+
+## Updates the debug origins position. Also calls _update_debug_portrait_transform()
+func _update_debug_origin() -> void:
+ if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
+ return
+ debug_origin.position = _get_origin_position()
+ _update_debug_portrait_transform()
+
+
+
+## Returns the debug character or the default debug character
+func _get_debug_character() -> DialogicCharacter:
+ return debug_character if debug_character != null else default_debug_character
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg
new file mode 100644
index 0000000..b022121
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg.import
new file mode 100644
index 0000000..e0c9ec0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bn3nq7gw67kye"
+path="res://.godot/imported/portrait_position.svg-7d6630b2aca3d16f624cd8ab41356d15.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/portrait_position.svg"
+dest_files=["res://.godot/imported/portrait_position.svg-7d6630b2aca3d16f624cd8ab41356d15.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/preview_character.tres b/addons/dialogic-main/addons/dialogic/Modules/Character/preview_character.tres
new file mode 100644
index 0000000..cdad7e1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/preview_character.tres
@@ -0,0 +1,32 @@
+[gd_resource type="Resource" script_class="DialogicCharacter" load_steps=2 format=3 uid="uid://dykf1j17ct5mo"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Resources/character.gd" id="1_qsljv"]
+
+[resource]
+script = ExtResource("1_qsljv")
+display_name = "Preview"
+nicknames = [""]
+color = Color(1, 1, 1, 1)
+description = ""
+scale = 1.0
+offset = Vector2(0, 0)
+mirror = false
+default_portrait = "character"
+portraits = {
+"character": {
+"image": "res://addons/dialogic/Editor/Images/preview_character.png",
+"offset": Vector2(0, 0),
+"scene": "res://addons/dialogic/Modules/Character/default_portrait.tscn"
+},
+"speaker": {
+"image": "res://addons/dialogic/Editor/Images/preview_character_speaker.png",
+"offset": Vector2(0, 0),
+"scene": "res://addons/dialogic/Modules/Character/default_portrait.tscn"
+}
+}
+custom_info = {
+"sound_mood_default": "",
+"sound_moods": {},
+"style": ""
+}
+metadata/timeline_not_saved = true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.gd
new file mode 100644
index 0000000..5e13819
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.gd
@@ -0,0 +1,95 @@
+@tool
+extends DialogicSettingsPage
+
+
+const POSITION_SUGGESTION_KEY := 'dialogic/portraits/position_suggestion_names'
+
+const DEFAULT_PORTRAIT_SCENE_KEY := 'dialogic/portraits/default_portrait'
+
+const ANIMATION_JOIN_DEFAULT_KEY := 'dialogic/animations/join_default'
+const ANIMATION_JOIN_DEFAULT_LENGTH_KEY := 'dialogic/animations/join_default_length'
+const ANIMATION_JOIN_DEFAULT_WAIT_KEY := 'dialogic/animations/join_default_wait'
+const ANIMATION_LEAVE_DEFAULT_KEY := 'dialogic/animations/leave_default'
+const ANIMATION_LEAVE_DEFAULT_LENGTH_KEY := 'dialogic/animations/leave_default_length'
+const ANIMATION_LEAVE_DEFAULT_WAIT_KEY := 'dialogic/animations/leave_default_wait'
+const ANIMATION_CROSSFADE_DEFAULT_KEY := 'dialogic/animations/cross_fade_default'
+const ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY:= 'dialogic/animations/cross_fade_default_length'
+
+
+func _ready():
+ %JoinDefault.get_suggestions_func = get_join_animation_suggestions
+ %JoinDefault.mode = 1
+ %LeaveDefault.get_suggestions_func = get_leave_animation_suggestions
+ %LeaveDefault.mode = 1
+ %CrossFadeDefault.get_suggestions_func = get_crossfade_animation_suggestions
+ %CrossFadeDefault.mode = 1
+
+ %PositionSuggestions.text_submitted.connect(save_setting.bind(POSITION_SUGGESTION_KEY))
+ %CustomPortraitScene.value_changed.connect(save_setting_with_name.bind(DEFAULT_PORTRAIT_SCENE_KEY))
+
+ %JoinDefault.value_changed.connect(
+ save_setting_with_name.bind(ANIMATION_JOIN_DEFAULT_KEY))
+ %JoinDefaultLength.value_changed.connect(
+ save_setting.bind(ANIMATION_JOIN_DEFAULT_LENGTH_KEY))
+ %JoinDefaultWait.toggled.connect(
+ save_setting.bind(ANIMATION_JOIN_DEFAULT_WAIT_KEY))
+
+ %LeaveDefault.value_changed.connect(
+ save_setting_with_name.bind(ANIMATION_LEAVE_DEFAULT_KEY))
+ %LeaveDefaultLength.value_changed.connect(
+ save_setting.bind(ANIMATION_LEAVE_DEFAULT_LENGTH_KEY))
+ %LeaveDefaultWait.toggled.connect(
+ save_setting.bind(ANIMATION_LEAVE_DEFAULT_WAIT_KEY))
+
+ %CrossFadeDefault.value_changed.connect(
+ save_setting_with_name.bind(ANIMATION_CROSSFADE_DEFAULT_KEY))
+ %CrossFadeDefaultLength.value_changed.connect(
+ save_setting.bind(ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY))
+
+
+func _refresh():
+ %PositionSuggestions.text = ProjectSettings.get_setting(POSITION_SUGGESTION_KEY, 'leftmost, left, center, right, rightmost')
+
+ %CustomPortraitScene.resource_icon = get_theme_icon(&"PackedScene", &"EditorIcons")
+ %CustomPortraitScene.set_value(ProjectSettings.get_setting(DEFAULT_PORTRAIT_SCENE_KEY, ''))
+
+ # JOIN
+ %JoinDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
+ %JoinDefault.set_value(ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_KEY, "Fade In Up"))
+ %JoinDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_LENGTH_KEY, 0.5))
+ %JoinDefaultWait.button_pressed = ProjectSettings.get_setting(ANIMATION_JOIN_DEFAULT_WAIT_KEY, true)
+
+ # LEAVE
+ %LeaveDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
+ %LeaveDefault.set_value(ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_KEY, "Fade Out Down"))
+ %LeaveDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_LENGTH_KEY, 0.5))
+ %LeaveDefaultWait.button_pressed = ProjectSettings.get_setting(ANIMATION_LEAVE_DEFAULT_WAIT_KEY, true)
+
+ # CROSS FADE
+ %CrossFadeDefault.resource_icon = get_theme_icon(&"Animation", &"EditorIcons")
+ %CrossFadeDefault.set_value(ProjectSettings.get_setting(ANIMATION_CROSSFADE_DEFAULT_KEY, "Fade Cross"))
+ %CrossFadeDefaultLength.set_value(ProjectSettings.get_setting(ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY, 0.5))
+
+
+func save_setting_with_name(property_name:String, value:Variant, settings_key:String) -> void:
+ save_setting(value, settings_key)
+
+
+func save_setting(value:Variant, settings_key:String) -> void:
+ ProjectSettings.set_setting(settings_key, value)
+ ProjectSettings.save()
+
+
+func get_join_animation_suggestions(search_text:String) -> Dictionary:
+ return DialogicPortraitAnimationUtil.get_suggestions(search_text, %JoinDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.IN)
+
+
+func get_leave_animation_suggestions(search_text:String) -> Dictionary:
+ return DialogicPortraitAnimationUtil.get_suggestions(search_text, %LeaveDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.OUT)
+
+
+func get_crossfade_animation_suggestions(search_text:String) -> Dictionary:
+ return DialogicPortraitAnimationUtil.get_suggestions(search_text, %CrossFadeDefault.current_value, "", DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.tscn b/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.tscn
new file mode 100644
index 0000000..5689200
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/settings_portraits.tscn
@@ -0,0 +1,160 @@
+[gd_scene load_steps=5 format=3 uid="uid://cp463rpri5j8a"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/settings_portraits.gd" id="2"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_dce78"]
+[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3"]
+[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="3_m06d8"]
+
+[node name="Portraits" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource("2")
+
+[node name="PositionsTitle" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+
+[node name="Title2" type="Label" parent="PositionsTitle"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Position Suggestions
+"
+
+[node name="HintTooltip" parent="PositionsTitle" instance=ExtResource("2_dce78")]
+layout_mode = 2
+tooltip_text = "You can change the position names that will be suggested in the timeline editor here."
+texture = null
+hint_text = "You can change the position names that will be suggested in the timeline editor here."
+
+[node name="PositionSuggestions" type="LineEdit" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="DefaultSceneTitle" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+
+[node name="Title2" type="Label" parent="DefaultSceneTitle"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Default Portrait Scene
+"
+
+[node name="HintTooltip" parent="DefaultSceneTitle" instance=ExtResource("2_dce78")]
+layout_mode = 2
+tooltip_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified"
+texture = null
+hint_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified"
+
+[node name="DefaultScene" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="DefaultScene"]
+layout_mode = 2
+text = "Scene"
+
+[node name="CustomPortraitScene" parent="DefaultScene" instance=ExtResource("3_m06d8")]
+unique_name_in_owner = true
+layout_mode = 2
+file_filter = "*.tscn, *.scn; PortraitScene"
+placeholder = "Default Scene"
+
+[node name="Animations2" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+
+[node name="Title2" type="Label" parent="Animations2"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Default Animations
+"
+
+[node name="HintTooltip" parent="Animations2" instance=ExtResource("2_dce78")]
+layout_mode = 2
+tooltip_text = "These settings are used for Leave and Join events if no animation is selected.
+
+The Cross-Fade will play if the portrait of a character changes and
+no animation is set."
+texture = null
+hint_text = "These settings are used for Leave and Join events if no animation is selected.
+
+The Cross-Fade will play if the portrait of a character changes and
+no animation is set."
+
+[node name="GridContainer" type="GridContainer" parent="."]
+layout_mode = 2
+columns = 2
+
+[node name="DefaultJoinLabel" type="Label" parent="GridContainer"]
+layout_mode = 2
+text = "Join"
+
+[node name="DefaultIn" type="HBoxContainer" parent="GridContainer"]
+layout_mode = 2
+
+[node name="JoinDefault" parent="GridContainer/DefaultIn" instance=ExtResource("3")]
+unique_name_in_owner = true
+layout_mode = 2
+mode = 1
+
+[node name="JoinDefaultLength" type="SpinBox" parent="GridContainer/DefaultIn"]
+unique_name_in_owner = true
+layout_mode = 2
+step = 0.1
+
+[node name="JoinDefaultWait" type="CheckButton" parent="GridContainer/DefaultIn"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Wait:"
+
+[node name="DefaultOutLabel" type="Label" parent="GridContainer"]
+layout_mode = 2
+text = "Leave"
+
+[node name="DefaultOut" type="HBoxContainer" parent="GridContainer"]
+layout_mode = 2
+
+[node name="LeaveDefault" parent="GridContainer/DefaultOut" instance=ExtResource("3")]
+unique_name_in_owner = true
+layout_mode = 2
+mode = 1
+
+[node name="LeaveDefaultLength" type="SpinBox" parent="GridContainer/DefaultOut"]
+unique_name_in_owner = true
+layout_mode = 2
+step = 0.1
+
+[node name="LeaveDefaultWait" type="CheckButton" parent="GridContainer/DefaultOut"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Wait:"
+
+[node name="CrossFadeLabel" type="Label" parent="GridContainer"]
+layout_mode = 2
+text = "Cross-Fade"
+
+[node name="DefaultCrossFade" type="HBoxContainer" parent="GridContainer"]
+layout_mode = 2
+
+[node name="CrossFadeDefault" parent="GridContainer/DefaultCrossFade" instance=ExtResource("3")]
+unique_name_in_owner = true
+layout_mode = 2
+mode = 1
+
+[node name="CrossFadeDefaultLength" type="SpinBox" parent="GridContainer/DefaultCrossFade"]
+unique_name_in_owner = true
+layout_mode = 2
+step = 0.1
+
+[connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefault" to="." method="_on_JoinDefault_value_changed"]
+[connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefaultLength" to="." method="_on_JoinDefaultLength_value_changed"]
+[connection signal="toggled" from="GridContainer/DefaultIn/JoinDefaultWait" to="." method="_on_JoinDefaultWait_toggled"]
+[connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefault" to="." method="_on_LeaveDefault_value_changed"]
+[connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
+[connection signal="toggled" from="GridContainer/DefaultOut/LeaveDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"]
+[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefault" to="." method="_on_LeaveDefault_value_changed"]
+[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png b/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png
new file mode 100644
index 0000000..656215f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import b/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import
new file mode 100644
index 0000000..e6121ed
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qihj11n7kx3m"
+path="res://.godot/imported/simple_image_portrait_thumbnail.png-703159de15393c48ef0b7c6ff9c1a5f4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png"
+dest_files=["res://.godot/imported/simple_image_portrait_thumbnail.png-703159de15393c48ef0b7c6ff9c1a5f4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_containers.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_containers.gd
new file mode 100644
index 0000000..ba968f1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_containers.gd
@@ -0,0 +1,284 @@
+extends DialogicSubsystem
+
+## Subsystem that manages portrait positions.
+
+signal position_changed(info: Dictionary)
+
+
+var transform_regex := r"(?position|pos|size|siz|rotation|rot)\W*=(?((?!(pos|siz|rot)).)*)"
+
+#region STATE
+####################################################################################################
+
+
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+func get_container(position_id: String) -> DialogicNode_PortraitContainer:
+ for portrait_position:DialogicNode_PortraitContainer in get_tree().get_nodes_in_group(&'dialogic_portrait_con_position'):
+ if portrait_position.is_visible_in_tree() and portrait_position.is_container(position_id):
+ return portrait_position
+ return null
+
+
+func get_containers(position_id: String) -> Array[DialogicNode_PortraitContainer]:
+ return get_tree().get_nodes_in_group(&'dialogic_portrait_con_position').filter(
+ func(node:DialogicNode_PortraitContainer):
+ return node.is_visible_in_tree() and node.is_container(position_id))
+
+
+func get_container_container() -> CanvasItem:
+ var any_portrait_container := get_tree().get_first_node_in_group(&'dialogic_portrait_con_position')
+ if any_portrait_container:
+ return any_portrait_container.get_parent()
+ return null
+
+
+## Creates a new portrait container node.
+## It will copy it's size and most settings from the first p_container in the tree.
+## It will be added as a sibling of the first p_container in the tree.
+func add_container(position_id: String) -> DialogicNode_PortraitContainer:
+ var example_position := get_tree().get_first_node_in_group(&'dialogic_portrait_con_position')
+ if example_position:
+ var new_position := DialogicNode_PortraitContainer.new()
+ example_position.get_parent().add_child(new_position)
+ new_position.name = "Portrait_"+position_id.validate_node_name()
+ copy_container_setup(example_position, new_position)
+ new_position.container_ids = [position_id]
+ position_changed.emit({&'change':'added', &'container_node':new_position, &'position_id':position_id})
+ return new_position
+ return null
+
+
+## Moves the [container] to the [destionation] (using [tween] and [time]).
+## The destination can be a position_id (e.g. "center") or translation, roataion and scale.
+## When moving to a preset container, then some more will be "copied" (e.g. anchors, etc.)
+func move_container(container:DialogicNode_PortraitContainer, destination:String, tween:Tween = null, time:float=1.0) -> void:
+ var target_position: Vector2 = container.position + container._get_origin_position()
+ var target_rotation: float = container.rotation
+ var target_size: Vector2 = container.size
+
+ var destination_container := get_container(destination)
+ if destination_container:
+ container.set_meta("target_container", destination_container)
+ target_position = destination_container.position + destination_container._get_origin_position()
+ target_rotation = destination_container.rotation_degrees
+ target_size = destination_container.size
+ else:
+ var regex := RegEx.create_from_string(transform_regex)
+ for found in regex.search_all(destination):
+ match found.get_string('part'):
+ 'pos', 'position':
+ target_position = str_to_vector(found.get_string("value"), target_position)
+ 'rot', 'rotation':
+ target_rotation = float(found.get_string("value"))
+ 'siz', 'size':
+ target_size = str_to_vector(found.get_string("value"), target_size)
+
+ translate_container(container, target_position, false, tween, time)
+ rotate_container(container, target_rotation, false, tween, time)
+ resize_container(container, target_size, false, tween, time)
+
+ if destination_container:
+ if time:
+ tween.finished.connect(func():
+ if container.has_meta("target_container"):
+ if container.get_meta("target_container") == destination_container:
+ copy_container_setup(destination_container, container)
+ )
+ else:
+ copy_container_setup(destination_container, container)
+
+
+func copy_container_setup(from:DialogicNode_PortraitContainer, to:DialogicNode_PortraitContainer) -> void:
+ to.ignore_resize = true
+ to.layout_mode = from.layout_mode
+ to.anchors_preset = from.anchors_preset
+ to.anchor_bottom = from.anchor_bottom
+ to.anchor_left = from.anchor_left
+ to.anchor_right = from.anchor_right
+ to.anchor_top = from.anchor_top
+ to.offset_bottom = from.offset_bottom
+ to.offset_top = from.offset_top
+ to.offset_right = from.offset_right
+ to.offset_left = from.offset_left
+ to.size_mode = from.size_mode
+ to.origin_anchor = from.origin_anchor
+ to.ignore_resize = false
+ to.update_portrait_transforms()
+
+
+## Translates the given container.
+## The given translation should be the target position of the ORIGIN point, not the container!
+func translate_container(container:DialogicNode_PortraitContainer, translation:Variant, relative := false, tween:Tween=null, time:float=1.0) -> void:
+ if !container.has_meta(&'default_translation'):
+ container.set_meta(&'default_translation', container.position + container._get_origin_position())
+
+ var final_translation: Vector2
+ if typeof(translation) == TYPE_STRING:
+ final_translation = str_to_vector(translation, container.position+container._get_origin_position())
+ elif typeof(translation) == TYPE_VECTOR2:
+ final_translation = translation
+
+ if relative:
+ final_translation += container.position
+ else:
+ final_translation -= container._get_origin_position()
+
+ if tween:
+ tween.tween_method(DialogicUtil.multitween.bind(container, "position", "base"), container.position, final_translation, time)
+ if not tween.finished.is_connected(save_position_container):
+ tween.finished.connect(save_position_container.bind(container))
+ else:
+ container.position = final_translation
+ save_position_container(container)
+ position_changed.emit({&'change':'moved', &'container_node':container})
+
+
+func rotate_container(container:DialogicNode_PortraitContainer, rotation:float, relative := false, tween:Tween=null, time:float=1.0) -> void:
+ if !container.has_meta(&'default_rotation'):
+ container.set_meta(&'default_rotation', container.rotation_degrees)
+
+ var final_rotation := rotation
+
+ if relative:
+ final_rotation += container.rotation_degrees
+
+ container.pivot_offset = container._get_origin_position()
+
+ if tween:
+ tween.tween_property(container, 'rotation_degrees', final_rotation, time)
+ if not tween.finished.is_connected(save_position_container):
+ tween.finished.connect(save_position_container.bind(container))
+ else:
+ container.rotation_degrees = final_rotation
+ save_position_container(container)
+
+ position_changed.emit({&'change':'rotated', &'container_node':container})
+
+
+func resize_container(container: DialogicNode_PortraitContainer, rect_size: Variant, relative := false, tween:Tween=null, time:float=1.0) -> void:
+ if !container.has_meta(&'default_size'):
+ container.set_meta(&'default_size', container.size)
+
+ var final_rect_resize: Vector2
+ if typeof(rect_size) == TYPE_STRING:
+ final_rect_resize = str_to_vector(rect_size, container.size)
+ elif typeof(rect_size) == TYPE_VECTOR2:
+ final_rect_resize = rect_size
+
+ if relative:
+ final_rect_resize += container.rect_size
+
+ var relative_position_change := container._get_origin_position()-container._get_origin_position(final_rect_resize)
+
+ if tween:
+ tween.tween_method(DialogicUtil.multitween.bind(container, "position", "resize_move"), Vector2(), relative_position_change, time)
+ tween.tween_property(container, 'size', final_rect_resize, time)
+ if not tween.finished.is_connected(save_position_container):
+ tween.finished.connect(save_position_container.bind(container))
+ else:
+ container.position = container.position + relative_position_change
+ container.size = final_rect_resize
+ save_position_container(container)
+
+ position_changed.emit({&'change':'resized', &'container_node':container})
+
+
+func save_position_container(container: DialogicNode_PortraitContainer) -> void:
+ if not dialogic.current_state_info.has('portrait_containers'):
+ dialogic.current_state_info['portrait_containers'] = {}
+
+ var info := {
+ "container_ids" : container.container_ids,
+ "position" : container.position,
+ "rotation" : container.rotation_degrees,
+ "size" : container.size,
+ "pivot_mode" : container.pivot_mode,
+ "pivot_value" : container.pivot_value,
+ "origin_anchor" : container.origin_anchor,
+ "anchor_left" : container.anchor_left,
+ "anchor_right" : container.anchor_right,
+ "anchor_top" : container.anchor_top,
+ "anchor_bottom" : container.anchor_bottom,
+ "offset_left" : container.offset_left,
+ "offset_right" : container.offset_right,
+ "offset_top" : container.offset_top,
+ "offset_bottom" : container.offset_bottom,
+ }
+
+ dialogic.current_state_info.portrait_containers[container.container_ids[0]] = info
+
+
+func load_position_container(position_id: String) -> DialogicNode_PortraitContainer:
+ # First check whether the container already exists:
+ var container := get_container(position_id)
+ if container:
+ return container
+
+ if not dialogic.current_state_info.has('portrait_containers') or not dialogic.current_state_info.portrait_containers.has(position_id):
+ return null
+
+ var info: Dictionary = dialogic.current_state_info.portrait_containers[position_id]
+ container = add_container(position_id)
+
+ if not container:
+ return null
+
+ container.container_ids = info.container_ids
+ container.position = info.position
+ container.rotation = info.rotation
+ container.size = info.size
+ container.pivot_mode = info.pivot_mode
+ container.pivot_value = info.pivot_value
+ container.origin_anchor = info.origin_anchor
+ container.anchor_left = info.anchor_left
+ container.anchor_right = info.anchor_right
+ container.anchor_top = info.anchor_top
+ container.anchor_bottom = info.anchor_bottom
+ container.offset_left = info.offset_left
+ container.offset_right = info.offset_right
+ container.offset_top = info.offset_top
+ container.offset_bottom = info.offset_bottom
+
+ return container
+
+
+func str_to_vector(input: String, base_vector:=Vector2()) -> Vector2:
+ var vector_regex := RegEx.create_from_string(r"(?x|y)\s*(?(-|\+)?(\d|\.|)*)(\s*(?%|px))?")
+ var vec := base_vector
+ for i in vector_regex.search_all(input):
+ var value := float(i.get_string(&'number'))
+ match i.get_string(&'type'):
+ 'px':
+ pass # Keep values as they are
+ '%', _:
+ match i.get_string(&'part'):
+ 'x': value *= get_viewport().get_window().size.x
+ 'y': value *= get_viewport().get_window().size.y
+
+ match i.get_string(&'part'):
+ 'x': vec.x = value
+ 'y': vec.y = value
+ return vec
+
+
+func vector_to_str(vec:Vector2) -> String:
+ return "x" + str(vec.x) + "px y" + str(vec.y) + "px"
+
+
+func reset_all_containers(time:= 0.0, tween:Tween = null) -> void:
+ for container in get_tree().get_nodes_in_group(&'dialogic_portrait_con_position'):
+ reset_container(container, time, tween)
+
+
+func reset_container(container:DialogicNode_PortraitContainer, time := 0.0, tween: Tween = null ) -> void:
+ if container.has_meta(&'default_translation'):
+ translate_container(container, vector_to_str(container.get_meta(&'default_translation')), false, tween, time)
+ if container.has_meta(&'default_rotation'):
+ rotate_container(container, container.get_meta(&'default_rotation'), false, tween, time)
+ if container.has_meta(&'default_size'):
+ resize_container(container, vector_to_str(container.get_meta(&'default_size')), false, tween, time)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_portraits.gd
new file mode 100644
index 0000000..b78abd2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/subsystem_portraits.gd
@@ -0,0 +1,701 @@
+extends DialogicSubsystem
+
+## Subsystem that manages portraits and portrait positions.
+
+signal character_joined(info:Dictionary)
+signal character_left(info:Dictionary)
+signal character_portrait_changed(info:Dictionary)
+signal character_moved(info:Dictionary)
+
+## Emitted when a portrait starts animating.
+#signal portrait_animating(character_node: Node, portrait_node: Node, animation_name: String, animation_length: float)
+
+
+## The default portrait scene.
+var default_portrait_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('default_portrait.tscn'))
+
+
+#region STATE
+####################################################################################################
+
+func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ for character in dialogic.current_state_info.get('portraits', {}).keys():
+ remove_character(load(character))
+ dialogic.current_state_info['portraits'] = {}
+
+
+func load_game_state(_load_flag:=LoadFlags.FULL_LOAD) -> void:
+ if not "portraits" in dialogic.current_state_info:
+ dialogic.current_state_info["portraits"] = {}
+
+ # Load Position Portraits
+ var portraits_info: Dictionary = dialogic.current_state_info.portraits.duplicate()
+ dialogic.current_state_info.portraits = {}
+ for character_path in portraits_info:
+ var character_info: Dictionary = portraits_info[character_path]
+ var character: DialogicCharacter = load(character_path)
+ var container := dialogic.PortraitContainers.load_position_container(character.get_character_name())
+ add_character(character, container, character_info.portrait, character_info.position_id)
+ change_character_mirror(character, character_info.get('custom_mirror', false))
+ change_character_z_index(character, character_info.get('z_index', 0))
+ change_character_extradata(character, character_info.get('extra_data', ""))
+
+ # Load Speaker Portrait
+ var speaker: Variant = dialogic.current_state_info.get('speaker', "")
+ if speaker:
+ dialogic.current_state_info['speaker'] = ""
+ change_speaker(load(speaker))
+ dialogic.current_state_info['speaker'] = speaker
+
+
+func pause() -> void:
+ for portrait in dialogic.current_state_info['portraits'].values():
+ if portrait.node.has_meta('animation_node'):
+ portrait.node.get_meta('animation_node').pause()
+
+
+func resume() -> void:
+ for portrait in dialogic.current_state_info['portraits'].values():
+ if portrait.node.has_meta('animation_node'):
+ portrait.node.get_meta('animation_node').resume()
+
+
+func _ready() -> void:
+ if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
+ default_portrait_scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', ''))
+
+
+#region MAIN METHODS
+####################################################################################################
+## The following methods allow manipulating portraits.
+## A portrait is made up of a character node [Node2D] that instances the portrait scene as it's child.
+## The character node is then always the child of a portrait container.
+## - Position (PortraitContainer)
+## ---- character_node (Node2D)
+## --------- portrait_node (e.g. default_portrait.tscn, or a custom portrait)
+##
+## Using these main methods a character can be present multiple times.
+## For a VN style, the "character" methods (next section) provide access based on the character.
+## - (That is what the character event uses)
+
+
+## Creates a new [character node] for the given [character], and add it to the given [portrait container].
+func _create_character_node(character:DialogicCharacter, container:DialogicNode_PortraitContainer) -> Node:
+ if container == null:
+ return null
+ var character_node := Node2D.new()
+ character_node.name = character.get_character_name()
+ character_node.set_meta('character', character)
+ container.add_child(character_node)
+ return character_node
+
+
+## Changes the portrait of a specific [character node].
+func _change_portrait(character_node: Node2D, portrait: String, fade_animation:="", fade_length := 0.5) -> Dictionary:
+ var character: DialogicCharacter = character_node.get_meta('character')
+
+ if portrait.is_empty():
+ portrait = character.default_portrait
+
+ var info := {'character':character, 'portrait':portrait, 'same_scene':false}
+
+ if not portrait in character.portraits.keys():
+ print_debug('[Dialogic] Change to not-existing portrait will be ignored!')
+ return info
+
+ # Path to the scene to use.
+ var scene_path: String = character.portraits[portrait].get('scene', '')
+
+ var portrait_node: Node = null
+ var previous_portrait: Node = null
+ var portrait_count := character_node.get_child_count()
+
+ if portrait_count > 0:
+ previous_portrait = character_node.get_child(-1)
+
+ # Check if the scene is the same as the currently loaded scene.
+ if (not previous_portrait == null and
+ previous_portrait.get_meta('scene', '') == scene_path and
+ # Also check if the scene supports changing to the given portrait.
+ previous_portrait._should_do_portrait_update(character, portrait)):
+ portrait_node = previous_portrait
+ info['same_scene'] = true
+
+ else:
+
+ if ResourceLoader.exists(scene_path):
+ var packed_scene: PackedScene = load(scene_path)
+
+ if packed_scene:
+ portrait_node = packed_scene.instantiate()
+ else:
+ push_error('[Dialogic] Portrait node "' + str(scene_path) + '" for character [' + character.display_name + '] could not be loaded. Your portrait might not show up on the screen. Confirm the path is correct.')
+
+ if !portrait_node:
+ portrait_node = default_portrait_scene.instantiate()
+
+ portrait_node.set_meta('scene', scene_path)
+
+
+ if portrait_node:
+ portrait_node.set_meta('portrait', portrait)
+ character_node.set_meta('portrait', portrait)
+
+ DialogicUtil.apply_scene_export_overrides(portrait_node, character.portraits[portrait].get('export_overrides', {}))
+
+ if portrait_node.has_method('_update_portrait'):
+ portrait_node._update_portrait(character, portrait)
+
+ if not portrait_node.is_inside_tree():
+ character_node.add_child(portrait_node)
+
+ _update_portrait_transform(portrait_node)
+
+ ## Handle Cross-Animating
+ if previous_portrait and previous_portrait != portrait_node:
+ if not fade_animation.is_empty() and fade_length > 0:
+ var fade_out := _animate_node(previous_portrait, fade_animation, fade_length, 1, true)
+ var _fade_in := _animate_node(portrait_node, fade_animation, fade_length, 1, false)
+ fade_out.finished.connect(previous_portrait.queue_free)
+ else:
+ previous_portrait.queue_free()
+
+ return info
+
+
+## Changes the mirroring of the given portrait.
+## Unless @force is false, this will take into consideration the character mirror,
+## portrait mirror and portrait position mirror settings.
+func _change_portrait_mirror(character_node: Node2D, mirrored := false, force := false) -> void:
+ var latest_portrait := character_node.get_child(-1)
+
+ if latest_portrait.has_method('_set_mirror'):
+ var character: DialogicCharacter = character_node.get_meta('character')
+ var current_portrait_info := character.get_portrait_info(character_node.get_meta('portrait'))
+ latest_portrait._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false)))
+
+
+func _change_portrait_extradata(character_node: Node2D, extra_data := "") -> void:
+ var latest_portrait := character_node.get_child(-1)
+
+ if latest_portrait.has_method('_set_extra_data'):
+ latest_portrait._set_extra_data(extra_data)
+
+
+func _update_character_transform(character_node:Node, time := 0.0) -> void:
+ for child in character_node.get_children():
+ _update_portrait_transform(child, time)
+
+
+func _update_portrait_transform(portrait_node: Node, time:float = 0.0) -> void:
+ var character_node: Node = portrait_node.get_parent()
+
+ var character: DialogicCharacter = character_node.get_meta('character')
+ var portrait_info: Dictionary = character.portraits.get(portrait_node.get_meta('portrait'), {})
+
+ # ignore the character scale on custom portraits that have 'ignore_char_scale' set to true
+ var apply_character_scale: bool = not portrait_info.get('ignore_char_scale', false)
+
+ var transform: Rect2 = character_node.get_parent().get_local_portrait_transform(
+ portrait_node._get_covered_rect(),
+ (character.scale * portrait_info.get('scale', 1))*int(apply_character_scale)+portrait_info.get('scale', 1)*int(!apply_character_scale))
+
+ var tween: Tween
+
+ if character_node.has_meta('move_tween'):
+ if character_node.get_meta('move_tween').is_running():
+ time = character_node.get_meta('move_time')-character_node.get_meta('move_tween').get_total_elapsed_time()
+ tween = character_node.get_meta('move_tween')
+ tween.stop()
+ if time == 0:
+ character_node.position = transform.position
+ portrait_node.position = character.offset + portrait_info.get('offset', Vector2())
+ portrait_node.scale = transform.size
+ else:
+ if not tween:
+ tween = character_node.create_tween().set_parallel().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE)
+ character_node.set_meta('move_tween', tween)
+ character_node.set_meta('move_time', time)
+ tween.tween_method(DialogicUtil.multitween.bind(character_node, "position", "base"), character_node.position, transform.position, time)
+ tween.tween_property(portrait_node, 'position',character.offset + portrait_info.get('offset', Vector2()), time)
+ tween.tween_property(portrait_node, 'scale', transform.size, time)
+
+
+## Animates the node with the given animation.
+## Is used both on the character node (most animations) and the portrait nodes (cross-fade animations)
+func _animate_node(node: Node, animation_path: String, length: float, repeats := 1, is_reversed := false) -> DialogicAnimation:
+ if node.has_meta('animation_node') and is_instance_valid(node.get_meta('animation_node')):
+ node.get_meta('animation_node').queue_free()
+
+ var anim_script: Script = load(animation_path)
+ var anim_node := Node.new()
+ anim_node.set_script(anim_script)
+ anim_node = (anim_node as DialogicAnimation)
+ anim_node.node = node
+ anim_node.base_position = node.position
+ anim_node.base_scale = node.scale
+ anim_node.time = length
+ anim_node.repeats = repeats
+ anim_node.is_reversed = is_reversed
+
+ add_child(anim_node)
+ anim_node.animate()
+
+ node.set_meta("animation_path", animation_path)
+ node.set_meta("animation_length", length)
+ node.set_meta("animation_node", anim_node)
+
+ #if not is_silent:
+ #portrait_animating.emit(portrait_node.get_parent(), portrait_node, animation_path, length)
+
+ return anim_node
+
+
+## Moves the given portrait to the given container.
+func _move_character(character_node: Node2D, transform:="", time := 0.0, easing:= Tween.EASE_IN_OUT, trans:= Tween.TRANS_SINE) -> void:
+ var tween := character_node.create_tween().set_ease(easing).set_trans(trans).set_parallel()
+ if time == 0:
+ tween.kill()
+ tween = null
+ var container: DialogicNode_PortraitContainer = character_node.get_parent()
+ dialogic.PortraitContainers.move_container(container, transform, tween, time)
+
+ for portrait_node in character_node.get_children():
+ _update_portrait_transform(portrait_node, time)
+
+
+## Changes the given portraits z_index.
+func _change_portrait_z_index(character_node: Node, z_index:int, update_zindex:= true) -> void:
+ if update_zindex:
+ character_node.get_parent().set_meta('z_index', z_index)
+
+ var sorted_children := character_node.get_parent().get_parent().get_children()
+ sorted_children.sort_custom(z_sort_portrait_containers)
+ var idx := 0
+ for con in sorted_children:
+ con.get_parent().move_child(con, idx)
+ idx += 1
+
+
+## Checks if [para, character] has joined the scene, if so, returns its
+## active [DialogicPortrait] node.
+##
+## The difference between an active and inactive nodes is whether the node is
+## the latest node. [br]
+## If a portrait is fading/animating from portrait A and B, both will exist
+## in the scene, but only the new portrait is active, even if it is not
+## fully visible yet.
+func get_character_portrait(character: DialogicCharacter) -> DialogicPortrait:
+ if is_character_joined(character):
+ var portrait_node: DialogicPortrait = dialogic.current_state_info['portraits'][character.resource_path].node.get_child(-1)
+ return portrait_node
+
+ return null
+
+
+func z_sort_portrait_containers(con1: DialogicNode_PortraitContainer, con2: DialogicNode_PortraitContainer) -> bool:
+ if con1.get_meta('z_index', 0) < con2.get_meta('z_index', 0):
+ return true
+
+ return false
+
+
+## Private method to remove a [param portrait_node].
+func _remove_portrait(portrait_node: Node) -> void:
+ portrait_node.get_parent().remove_child(portrait_node)
+ portrait_node.queue_free()
+
+
+## Gets the default animation length for joining characters
+## If Auto-Skip is enabled, limits the time.
+func _get_join_default_length() -> float:
+ var default_time: float = ProjectSettings.get_setting('dialogic/animations/join_default_length', 0.5)
+
+ if dialogic.Inputs.auto_skip.enabled:
+ default_time = min(default_time, dialogic.Inputs.auto_skip.time_per_event)
+
+ return default_time
+
+
+## Gets the default animation length for leaving characters
+## If Auto-Skip is enabled, limits the time.
+func _get_leave_default_length() -> float:
+ var default_time: float = ProjectSettings.get_setting('dialogic/animations/leave_default_length', 0.5)
+
+ if dialogic.Inputs.auto_skip.enabled:
+ default_time = min(default_time, dialogic.Inputs.auto_skip.time_per_event)
+
+ return default_time
+
+
+## Checks multiple cases to return a valid portrait to use.
+func get_valid_portrait(character:DialogicCharacter, portrait:String) -> String:
+ if character == null:
+ printerr('[Dialogic] Tried to use portrait "', portrait, '" on character.')
+ dialogic.print_debug_moment()
+ return ""
+
+ if "{" in portrait and dialogic.has_subsystem("Expressions"):
+ var test: Variant = dialogic.Expressions.execute_string(portrait)
+ if test:
+ portrait = str(test)
+
+ if not portrait in character.portraits:
+ if not portrait.is_empty():
+ printerr('[Dialogic] Tried to use invalid portrait "', portrait, '" on character "', DialogicResourceUtil.get_unique_identifier(character.resource_path), '". Using default portrait instead.')
+ dialogic.print_debug_moment()
+ portrait = character.default_portrait
+
+ if portrait.is_empty():
+ portrait = character.default_portrait
+
+ return portrait
+
+#endregion
+
+
+#region Character Methods
+####################################################################################################
+## The following methods are used to manage character portraits with the following rules:
+## - a character can only be present once with these methods.
+## Most of them will fail silently if the character isn't joined yet.
+
+
+## Adds a character at a position and sets it's portrait.
+## If the character is already joined it will only update, portrait, position, etc.
+func join_character(character:DialogicCharacter, portrait:String, position_id:String, mirrored:= false, z_index:= 0, extra_data:= "", animation_name:= "", animation_length:= 0.0, animation_wait := false) -> Node:
+ if is_character_joined(character):
+ change_character_portrait(character, portrait)
+
+ if animation_name.is_empty():
+ animation_length = _get_join_default_length()
+
+ if animation_wait:
+ dialogic.current_state = DialogicGameHandler.States.ANIMATING
+ await get_tree().create_timer(animation_length).timeout
+ dialogic.current_state = DialogicGameHandler.States.IDLE
+ move_character(character, position_id, animation_length)
+ change_character_mirror(character, mirrored)
+ return
+
+ var container := dialogic.PortraitContainers.add_container(character.get_character_name())
+ var character_node := add_character(character, container, portrait, position_id)
+ if character_node == null:
+ return null
+
+ dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_id':position_id, 'custom_mirror':mirrored}
+
+ _change_portrait_mirror(character_node, mirrored)
+ _change_portrait_extradata(character_node, extra_data)
+ _change_portrait_z_index(character_node, z_index)
+
+ var info := {'character':character}
+ info.merge(dialogic.current_state_info['portraits'][character.resource_path])
+ character_joined.emit(info)
+
+ if animation_name.is_empty():
+ animation_name = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up")
+ animation_length = _get_join_default_length()
+ animation_wait = ProjectSettings.get_setting('dialogic/animations/join_default_wait', true)
+
+ animation_name = DialogicPortraitAnimationUtil.guess_animation(animation_name, DialogicPortraitAnimationUtil.AnimationType.IN)
+
+ if animation_name and animation_length > 0:
+ var anim: DialogicAnimation = _animate_node(character_node, animation_name, animation_length)
+ if animation_wait:
+ dialogic.current_state = DialogicGameHandler.States.ANIMATING
+ await anim.finished
+ dialogic.current_state = DialogicGameHandler.States.IDLE
+
+ return character_node
+
+
+func add_character(character:DialogicCharacter, container: DialogicNode_PortraitContainer, portrait:String, position_id:String) -> Node:
+ if is_character_joined(character):
+ printerr('[DialogicError] Cannot add a already joined character. If this is intended call _create_character_node manually.')
+ return null
+
+ portrait = get_valid_portrait(character, portrait)
+
+ if portrait.is_empty():
+ return null
+
+ if not character:
+ printerr('[DialogicError] Cannot call add_portrait() with null character.')
+ return null
+
+ var character_node := _create_character_node(character, container)
+
+ if character_node == null:
+ printerr('[Dialogic] Failed to join character to position ', position_id, ". Could not find position container.")
+ return null
+
+
+ dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_id':position_id}
+
+ _move_character(character_node, position_id)
+ _change_portrait(character_node, portrait)
+
+ return character_node
+
+
+## Changes the portrait of a character. Only works with joined characters.
+func change_character_portrait(character: DialogicCharacter, portrait: String, fade_animation:="DEFAULT", fade_length := -1.0) -> void:
+ if not is_character_joined(character):
+ return
+
+ portrait = get_valid_portrait(character, portrait)
+
+ if dialogic.current_state_info.portraits[character.resource_path].portrait == portrait:
+ return
+
+ if fade_animation == "DEFAULT":
+ fade_animation = ProjectSettings.get_setting('dialogic/animations/cross_fade_default', "Fade Cross")
+ fade_length = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5)
+
+ fade_animation = DialogicPortraitAnimationUtil.guess_animation(fade_animation, DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
+
+ var info := _change_portrait(dialogic.current_state_info.portraits[character.resource_path].node, portrait, fade_animation, fade_length)
+ dialogic.current_state_info.portraits[character.resource_path].portrait = info.portrait
+ _change_portrait_mirror(
+ dialogic.current_state_info.portraits[character.resource_path].node,
+ dialogic.current_state_info.portraits[character.resource_path].get('custom_mirror', false)
+ )
+ character_portrait_changed.emit(info)
+
+
+## Changes the mirror of the given character. Only works with joined characters
+func change_character_mirror(character:DialogicCharacter, mirrored:= false, force:= false) -> void:
+ if !is_character_joined(character):
+ return
+
+ _change_portrait_mirror(dialogic.current_state_info.portraits[character.resource_path].node, mirrored, force)
+ dialogic.current_state_info.portraits[character.resource_path]['custom_mirror'] = mirrored
+
+
+## Changes the z_index of a character. Only works with joined characters
+func change_character_z_index(character:DialogicCharacter, z_index:int, update_zindex:= true) -> void:
+ if !is_character_joined(character):
+ return
+
+ _change_portrait_z_index(dialogic.current_state_info.portraits[character.resource_path].node, z_index, update_zindex)
+ if update_zindex:
+ dialogic.current_state_info.portraits[character.resource_path]['z_index'] = z_index
+
+
+## Changes the extra data on the given character. Only works with joined characters
+func change_character_extradata(character:DialogicCharacter, extra_data:="") -> void:
+ if !is_character_joined(character):
+ return
+ _change_portrait_extradata(dialogic.current_state_info.portraits[character.resource_path].node, extra_data)
+ dialogic.current_state_info.portraits[character.resource_path]['extra_data'] = extra_data
+
+
+## Starts the given animation on the given character. Only works with joined characters
+func animate_character(character: DialogicCharacter, animation_path: String, length: float, repeats := 1, is_reversed := false) -> DialogicAnimation:
+ if not is_character_joined(character):
+ return null
+
+ animation_path = DialogicPortraitAnimationUtil.guess_animation(animation_path)
+
+ var character_node: Node = dialogic.current_state_info.portraits[character.resource_path].node
+
+ return _animate_node(character_node, animation_path, length, repeats, is_reversed)
+
+
+## Moves the given character to the given position. Only works with joined characters
+func move_character(character:DialogicCharacter, position_id:String, time:= 0.0, easing:=Tween.EASE_IN_OUT, trans:=Tween.TRANS_SINE) -> void:
+ if !is_character_joined(character):
+ return
+
+ if dialogic.current_state_info.portraits[character.resource_path].position_id == position_id:
+ return
+
+ _move_character(dialogic.current_state_info.portraits[character.resource_path].node, position_id, time, easing, trans)
+ dialogic.current_state_info.portraits[character.resource_path].position_id = position_id
+ character_moved.emit({'character':character, 'position_id':position_id, 'time':time})
+
+
+## Removes a character with a given animation or the default animation.
+func leave_character(character: DialogicCharacter, animation_name:= "", animation_length:= 0.0, animation_wait := false) -> void:
+ if not is_character_joined(character):
+ return
+
+ if animation_name.is_empty():
+ animation_name = ProjectSettings.get_setting('dialogic/animations/leave_default', "Fade Out Down")
+ animation_length = _get_leave_default_length()
+ animation_wait = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true)
+
+ animation_name = DialogicPortraitAnimationUtil.guess_animation(animation_name, DialogicPortraitAnimationUtil.AnimationType.OUT)
+
+ if not animation_name.is_empty():
+ var character_node := get_character_node(character)
+
+ var animation := _animate_node(character_node, animation_name, animation_length, 1, true)
+ if animation_length > 0:
+ if animation_wait:
+ dialogic.current_state = DialogicGameHandler.States.ANIMATING
+ await animation.finished
+ dialogic.current_state = DialogicGameHandler.States.IDLE
+ remove_character(character)
+ else:
+ animation.finished.connect(func(): remove_character(character))
+ else:
+ remove_character(character)
+
+
+## Removes all joined characters with a given animation or the default animation.
+func leave_all_characters(animation_name:="", animation_length:=0.0, animation_wait := false) -> void:
+ for character in get_joined_characters():
+ await leave_character(character, animation_name, animation_length, animation_wait)
+
+
+## Finds the character node for a [param character].
+## Return `null` if the [param character] is not part of the scene.
+func get_character_node(character: DialogicCharacter) -> Node:
+ if is_character_joined(character):
+ if is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].node):
+ return dialogic.current_state_info['portraits'][character.resource_path].node
+ return null
+
+
+## Removes the given characters portrait.
+## Only works with joined characters.
+func remove_character(character: DialogicCharacter) -> void:
+ var character_node := get_character_node(character)
+
+ if is_instance_valid(character_node) and character_node is Node:
+ var container := character_node.get_parent()
+ container.get_parent().remove_child(container)
+ container.queue_free()
+ character_node.queue_free()
+ character_left.emit({'character': character})
+
+ dialogic.current_state_info['portraits'].erase(character.resource_path)
+
+
+func get_current_character() -> DialogicCharacter:
+ if dialogic.current_state_info.get('speaker', null):
+ return load(dialogic.current_state_info.speaker)
+ return null
+
+
+
+## Returns true if the given character is currently joined.
+func is_character_joined(character: DialogicCharacter) -> bool:
+ if character == null or not character.resource_path in dialogic.current_state_info['portraits']:
+ return false
+
+ return true
+
+
+## Returns a list of the joined charcters (as resources)
+func get_joined_characters() -> Array[DialogicCharacter]:
+ var chars: Array[DialogicCharacter] = []
+
+ for char_path: String in dialogic.current_state_info.get('portraits', {}).keys():
+ chars.append(load(char_path))
+
+ return chars
+
+
+## Returns a dictionary with info on a given character.
+## Keys can be [joined, character, node (for the portrait node), position_id]
+## Only joined is included (and false) for not joined characters
+func get_character_info(character:DialogicCharacter) -> Dictionary:
+ if is_character_joined(character):
+ var info: Dictionary = dialogic.current_state_info['portraits'][character.resource_path]
+ info['joined'] = true
+ return info
+ else:
+ return {'joined':false}
+
+#endregion
+
+
+#region SPEAKER PORTRAIT CONTAINERS
+####################################################################################################
+
+## Updates all portrait containers set to SPEAKER.
+func change_speaker(speaker: DialogicCharacter = null, portrait := "") -> void:
+ for container: Node in get_tree().get_nodes_in_group('dialogic_portrait_con_speaker'):
+ var just_joined := true
+ for character_node: Node in container.get_children():
+ if not character_node.get_meta('character') == speaker:
+ var leave_animation: String = ProjectSettings.get_setting('dialogic/animations/leave_default', "Fade Out")
+ leave_animation = DialogicPortraitAnimationUtil.guess_animation(leave_animation, DialogicPortraitAnimationUtil.AnimationType.OUT)
+ var leave_animation_length := _get_leave_default_length()
+
+ if leave_animation and leave_animation_length:
+ var animate_out := _animate_node(character_node, leave_animation, leave_animation_length, 1, true)
+ animate_out.finished.connect(character_node.queue_free)
+ else:
+ character_node.get_parent().remove_child(character_node)
+ character_node.queue_free()
+ else:
+ just_joined = false
+
+ if speaker == null or speaker.portraits.is_empty():
+ continue
+
+ if just_joined:
+ _create_character_node(speaker, container)
+
+ elif portrait.is_empty():
+ continue
+
+ if portrait.is_empty(): portrait = speaker.default_portrait
+
+ var fade_animation: String = ProjectSettings.get_setting('dialogic/animations/cross_fade_default', "Fade Cross")
+ var fade_length: float = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5)
+
+ fade_animation = DialogicPortraitAnimationUtil.guess_animation(fade_animation, DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
+
+ if container.portrait_prefix+portrait in speaker.portraits:
+ portrait = container.portrait_prefix+portrait
+
+ _change_portrait(container.get_child(-1), portrait, fade_animation, fade_length)
+
+ # if the character has no portraits _change_portrait won't actually add a child node
+ if container.get_child(-1).get_child_count() == 0:
+ continue
+
+ if just_joined:
+ # Change speaker is called before the text is changed.
+ # In styles where the speaker is IN the textbox,
+ # this can mean the portrait container isn't sized correctly yet.
+ if not container.is_visible_in_tree():
+ await get_tree().process_frame
+ var join_animation: String = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up")
+ join_animation = DialogicPortraitAnimationUtil.guess_animation(join_animation, DialogicPortraitAnimationUtil.AnimationType.IN)
+ var join_animation_length := _get_join_default_length()
+
+ if join_animation and join_animation_length:
+ _animate_node(container.get_child(-1), join_animation, join_animation_length)
+
+ _change_portrait_mirror(container.get_child(-1))
+
+ if speaker:
+ if speaker.resource_path != dialogic.current_state_info['speaker']:
+ if dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])):
+ dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight()
+
+ if speaker and is_character_joined(speaker):
+ dialogic.current_state_info['portraits'][speaker.resource_path].node.get_child(-1)._highlight()
+
+ elif dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])):
+ dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight()
+
+#endregion
+
+
+#region TEXT EFFECTS
+####################################################################################################
+
+## Called from the [portrait=something] text effect.
+func text_effect_portrait(_text_node:Control, _skipped:bool, argument:String) -> void:
+ if argument:
+ if dialogic.current_state_info.get('speaker', null):
+ change_character_portrait(load(dialogic.current_state_info.speaker), argument)
+ change_speaker(load(dialogic.current_state_info.speaker), argument)
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg
new file mode 100644
index 0000000..561cbc9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg.import
new file mode 100644
index 0000000..075d503
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c735ss4h37y8i"
+path="res://.godot/imported/update_mirror.svg-f408c5d582bc4320ec55291180afbd3e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/update_mirror.svg"
+dest_files=["res://.godot/imported/update_mirror.svg-f408c5d582bc4320ec55291180afbd3e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg
new file mode 100644
index 0000000..b0856b3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg.import
new file mode 100644
index 0000000..c026861
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qx5bntelnslj"
+path="res://.godot/imported/update_portrait.svg-4d9f3b3550c2bd03a5f011882f8b32a9.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/update_portrait.svg"
+dest_files=["res://.godot/imported/update_portrait.svg-4d9f3b3550c2bd03a5f011882f8b32a9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg
new file mode 100644
index 0000000..6e15feb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg
new file mode 100644
index 0000000..6e15feb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import
new file mode 100644
index 0000000..f11c6f1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deu1h3rsp3p8y"
+path="res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-9053f3ca71b94556e93f06feb4afb771.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg"
+dest_files=["res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-9053f3ca71b94556e93f06feb4afb771.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.import
new file mode 100644
index 0000000..ce36404
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rslog4jar4gu"
+path="res://.godot/imported/update_position.svg-f54463965a256444aa84eac1b19ba2cd.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/update_position.svg"
+dest_files=["res://.godot/imported/update_position.svg-f54463965a256444aa84eac1b19ba2cd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg b/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg
new file mode 100644
index 0000000..11cf3fc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg
@@ -0,0 +1,14 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg.import
new file mode 100644
index 0000000..6a3e6aa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5xa3i541igyk"
+path="res://.godot/imported/update_z_index.svg-60b17e1562018d8c0b4c5bce0e3cbdb8.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Character/update_z_index.svg"
+dest_files=["res://.godot/imported/update_z_index.svg-60b17e1562018d8c0b4c5bce0e3cbdb8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/event_choice.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/event_choice.gd
new file mode 100644
index 0000000..7e4364c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/event_choice.gd
@@ -0,0 +1,232 @@
+@tool
+class_name DialogicChoiceEvent
+extends DialogicEvent
+
+## Event that allows adding choices. Needs to go after a text event (or another choices EndBranch).
+
+enum ElseActions {HIDE=0, DISABLE=1, DEFAULT=2}
+
+
+### Settings
+## The text that is displayed on the choice button.
+var text := ""
+## If not empty this condition will determine if this choice is active.
+var condition := ""
+## Determines what happens if [condition] is false. Default will use the action set in the settings.
+var else_action := ElseActions.DEFAULT
+## The text that is displayed if [condition] is false and [else_action] is Disable.
+## If empty [text] will be used for disabled button as well.
+var disabled_text := ""
+## A dictionary that can be filled with arbitrary information
+## This can then be interpreted by a custom choice layer
+var extra_data := {}
+
+
+## UI helper
+var _has_condition := false
+
+#endregion
+
+var regex := RegEx.create_from_string(r'- (?(?>\\\||(?(?=.*\|)[^|]|(?!\[if)[^|]))*)\|?\s*(\[if(?([^\]\[]|\[[^\]]*\])+)\])?\s*(\[(?[^]]*)\])?')
+
+#region EXECUTION
+################################################################################
+
+func _execute() -> void:
+ if dialogic.Choices.is_question(dialogic.current_event_idx):
+ dialogic.Choices.show_current_question(false)
+ dialogic.current_state = dialogic.States.AWAITING_CHOICE
+
+#endregion
+
+
+#region INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Choice"
+ set_default_color('Color3')
+ event_category = "Flow"
+ event_sorting_index = 0
+ can_contain_events = true
+ wants_to_group = true
+
+
+# return a control node that should show on the END BRANCH node
+func get_end_branch_control() -> Control:
+ return load(get_script().resource_path.get_base_dir().path_join('ui_choice_end.tscn')).instantiate()
+#endregion
+
+
+#region SAVING/LOADING
+################################################################################
+
+func to_text() -> String:
+ var result_string := ""
+
+ result_string = "- "+text.strip_edges()
+ var shortcode := store_to_shortcode_parameters()
+ if (condition and _has_condition) or shortcode or extra_data:
+ result_string += " |"
+ if condition and _has_condition:
+ result_string += " [if " + condition + "]"
+
+ if shortcode or extra_data:
+ result_string += " [" + shortcode
+ if extra_data:
+ var extra_data_string := ""
+ for i in extra_data:
+ extra_data_string += " " + i + '="' + value_to_string(extra_data[i]) + '"'
+ if shortcode:
+ result_string += " "
+ result_string += extra_data_string.strip_edges()
+ result_string += "]"
+
+ return result_string
+
+
+func from_text(string:String) -> void:
+ var result := regex.search(string.strip_edges())
+ if result == null:
+ return
+ text = result.get_string('text').strip_edges()
+ condition = result.get_string('condition').strip_edges()
+ _has_condition = not condition.is_empty()
+ if result.get_string('shortcode'):
+ load_from_shortcode_parameters(result.get_string("shortcode"))
+ var shortcode := parse_shortcode_parameters(result.get_string('shortcode'))
+ shortcode.erase("else")
+ shortcode.erase("alt_text")
+ extra_data = shortcode.duplicate()
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ "else" : {"property": "else_action", "default": ElseActions.DEFAULT,
+ "suggestions": func(): return {
+ "Default" :{'value':ElseActions.DEFAULT, 'text_alt':['default']},
+ "Hide" :{'value':ElseActions.HIDE,'text_alt':['hide'] },
+ "Disable" :{'value':ElseActions.DISABLE,'text_alt':['disable']}}},
+ "alt_text" : {"property": "disabled_text", "default": ""},
+ "extra_data" : {"property": "extra_data", "default": {}, "custom_stored":true},
+ }
+
+
+func is_valid_event(string:String) -> bool:
+ if string.strip_edges().begins_with("-"):
+ return true
+ return false
+
+#endregion
+
+#region TRANSLATIONS
+################################################################################
+
+func _get_translatable_properties() -> Array:
+ return ['text', 'disabled_text']
+
+
+func _get_property_original_translation(property:String) -> String:
+ match property:
+ 'text':
+ return text
+ 'disabled_text':
+ return disabled_text
+ return ''
+#endregion
+
+
+#region EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit("text", ValueType.SINGLELINE_TEXT, {'autofocus':true})
+ add_body_edit("", ValueType.LABEL, {"text":"Condition:"})
+ add_body_edit("_has_condition", ValueType.BOOL_BUTTON, {"editor_icon":["Add", "EditorIcons"], "tooltip":"Add Condition"}, "not _has_condition")
+ add_body_edit("condition", ValueType.CONDITION, {}, "_has_condition")
+ add_body_edit("_has_condition", ValueType.BOOL_BUTTON, {"editor_icon":["Remove", "EditorIcons"], "tooltip":"Remove Condition"}, "_has_condition")
+ add_body_edit("else_action", ValueType.FIXED_OPTIONS, {'left_text':'Else:',
+ 'options': [
+ {
+ 'label': 'Default',
+ 'value': ElseActions.DEFAULT,
+ },
+ {
+ 'label': 'Hide',
+ 'value': ElseActions.HIDE,
+ },
+ {
+ 'label': 'Disable',
+ 'value': ElseActions.DISABLE,
+ }
+ ]}, '_has_condition')
+ add_body_edit("disabled_text", ValueType.SINGLELINE_TEXT, {
+ 'left_text':'Disabled text:',
+ 'placeholder':'(Empty for same)'}, 'allow_alt_text()')
+ add_body_line_break()
+ add_body_edit("extra_data", ValueType.DICTIONARY, {"left_text":"Extra Data:"})
+
+
+func allow_alt_text() -> bool:
+ return _has_condition and (
+ else_action == ElseActions.DISABLE or
+ (else_action == ElseActions.DEFAULT and
+ ProjectSettings.get_setting("dialogic/choices/def_false_behaviour", 0) == 1))
+#endregion
+
+
+#region CODE COMPLETION
+################################################################################
+
+func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
+ line = CodeCompletionHelper.get_line_untill_caret(line)
+
+ if !'[if' in line:
+ if symbol == '{':
+ CodeCompletionHelper.suggest_variables(TextNode)
+ return
+
+ if symbol == '[':
+ if !'[if' in line and line.count('[') - line.count(']') == 1:
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'if', 'if ', TextNode.syntax_highlighter.code_flow_color)
+ elif '[if' in line:
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'else', 'else="', TextNode.syntax_highlighter.code_flow_color)
+ if symbol == ' ' and '[else' in line:
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'alt_text', 'alt_text="', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5))
+ elif symbol == '{':
+ CodeCompletionHelper.suggest_variables(TextNode)
+ if (symbol == '=' or symbol == '"') and line.count('[') > 1 and !'" ' in line:
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'default', "default", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'hide', "hide", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
+ TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'disable', "disable", event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.5), null, '"')
+#endregion
+
+
+#region SYNTAX HIGHLIGHTING
+################################################################################
+
+func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
+ var result := regex.search(line)
+
+ dict[0] = {'color':event_color}
+
+ if not result:
+ return dict
+
+ var condition_begin := result.get_start("condition")
+ var condition_end := result.get_end("condition")
+
+ var shortcode_begin := result.get_start("shortcode")
+
+ dict = Highlighter.color_region(dict, event_color.lerp(Highlighter.variable_color, 0.5), line, '{','}', 0, condition_begin, event_color)
+
+ if condition_begin > 0:
+ var from := line.find('[if')
+ dict[from] = {"color":Highlighter.normal_color}
+ dict[from+1] = {"color":Highlighter.code_flow_color}
+ dict[condition_begin] = {"color":Highlighter.normal_color}
+ dict = Highlighter.color_condition(dict, line, condition_begin, condition_end)
+ if shortcode_begin:
+ dict = Highlighter.color_shortcode_content(dict, line, shortcode_begin, 0, event_color)
+ return dict
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg b/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg
new file mode 100644
index 0000000..b69a86d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg.import
new file mode 100644
index 0000000..639985f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://wd4fnxjfmj5m"
+path="res://.godot/imported/icon.svg-7a4535434ff42423c0994da71cad8513.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Choice/icon.svg"
+dest_files=["res://.godot/imported/icon.svg-7a4535434ff42423c0994da71cad8513.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/index.gd
new file mode 100644
index 0000000..23d0ec2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/index.gd
@@ -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')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/node_button_sound.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/node_button_sound.gd
new file mode 100644
index 0000000..3f6cdbe
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/node_button_sound.gd
@@ -0,0 +1,52 @@
+class_name DialogicNode_ButtonSound
+extends AudioStreamPlayer
+
+## Node that is used for playing sound effects on hover/focus/press of sibling DialogicNode_ChoiceButtons.
+
+## Sound to be played if one of the sibling ChoiceButtons is pressed.
+## If sibling ChoiceButton has a sound_pressed set, that is prioritized.
+@export var sound_pressed: AudioStream
+## Sound to be played on hover. See [sound_pressed] for more.
+@export var sound_hover: AudioStream
+## Sound to be played on focus. See [sound_pressed] for more.
+@export var sound_focus: AudioStream
+
+func _ready() -> void:
+ add_to_group('dialogic_button_sound')
+ _connect_all_buttons()
+
+#basic play sound
+func play_sound(sound) -> void:
+ if sound != null:
+ stream = sound
+ play()
+
+func _connect_all_buttons() -> void:
+ for child in get_parent().get_children():
+ if child is DialogicNode_ChoiceButton:
+ child.button_up.connect(_on_pressed.bind(child.sound_pressed))
+ child.mouse_entered.connect(_on_hover.bind(child.sound_hover))
+ child.focus_entered.connect(_on_focus.bind(child.sound_focus))
+
+
+#the custom_sound argument comes from the specifec button and get used
+#if none are found, it uses the above sounds
+
+func _on_pressed(custom_sound) -> void:
+ if custom_sound != null:
+ play_sound(custom_sound)
+ else:
+ play_sound(sound_pressed)
+
+func _on_hover(custom_sound) -> void:
+ if custom_sound != null:
+ play_sound(custom_sound)
+ else:
+ play_sound(sound_hover)
+
+func _on_focus(custom_sound) -> void:
+ if custom_sound != null:
+ play_sound(custom_sound)
+ else:
+ play_sound(sound_focus)
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/node_choice_button.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/node_choice_button.gd
new file mode 100644
index 0000000..8e82436
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/node_choice_button.gd
@@ -0,0 +1,46 @@
+class_name DialogicNode_ChoiceButton
+extends Button
+## The button allows the player to make a choice in the Dialogic system.
+##
+## This class is used in the Choice Layer. [br]
+## You may change the [member text_node] to any [class Node] that has a
+## `text` property. [br]
+## If you don't set the [member text_node], the text will be set on this
+## button instead.
+##
+## Using a different node may allow using rich text effects; they are
+## not supported on buttons at this point.
+
+
+## Used to identify what choices to put on. If you leave it at -1, choices will be distributed automatically.
+@export var choice_index: int = -1
+
+## Can be set to play this sound when pressed. Requires a sibling DialogicNode_ButtonSound node.
+@export var sound_pressed: AudioStream
+## Can be set to play this sound when hovered. Requires a sibling DialogicNode_ButtonSound node.
+@export var sound_hover: AudioStream
+## Can be set to play this sound when focused. Requires a sibling DialogicNode_ButtonSound node.
+@export var sound_focus: AudioStream
+## If set, the text will be set on this node's `text` property instead.
+@export var text_node: Node
+
+
+func _ready() -> void:
+ add_to_group('dialogic_choice_button')
+ shortcut_in_tooltip = false
+ hide()
+
+
+func _load_info(choice_info: Dictionary) -> void:
+ set_choice_text(choice_info.text)
+ visible = choice_info.visible
+ disabled = choice_info.disabled
+
+
+## Called when the text changes.
+func set_choice_text(new_text: String) -> void:
+ if text_node:
+ text_node.text = new_text
+ else:
+ text = new_text
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.gd
new file mode 100644
index 0000000..66e4482
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.gd
@@ -0,0 +1,67 @@
+@tool
+extends DialogicSettingsPage
+
+func _refresh() -> void:
+ %Autofocus.button_pressed = ProjectSettings.get_setting('dialogic/choices/autofocus_first', true)
+ %Delay.value = ProjectSettings.get_setting('dialogic/choices/delay', 0.2)
+ %FalseBehaviour.select(ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', 0))
+ %HotkeyType.select(ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', 0))
+
+ var reveal_delay: float = ProjectSettings.get_setting('dialogic/choices/reveal_delay', 0)
+ var reveal_by_input: bool = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', false)
+ if not reveal_by_input and reveal_delay == 0:
+ _on_appear_mode_item_selected(0)
+ if not reveal_by_input and reveal_delay != 0:
+ _on_appear_mode_item_selected(1)
+ if reveal_by_input and reveal_delay == 0:
+ _on_appear_mode_item_selected(2)
+ if reveal_by_input and reveal_delay != 0:
+ _on_appear_mode_item_selected(3)
+
+ %RevealDelay.value = reveal_delay
+
+func _on_Autofocus_toggled(button_pressed: bool) -> void:
+ ProjectSettings.set_setting('dialogic/choices/autofocus_first', button_pressed)
+ ProjectSettings.save()
+
+
+func _on_FalseBehaviour_item_selected(index) -> void:
+ ProjectSettings.set_setting('dialogic/choices/def_false_behaviour', index)
+ ProjectSettings.save()
+
+
+func _on_HotkeyType_item_selected(index) -> void:
+ ProjectSettings.set_setting('dialogic/choices/hotkey_behaviour', index)
+ ProjectSettings.save()
+
+
+func _on_Delay_value_changed(value) -> void:
+ ProjectSettings.set_setting('dialogic/choices/delay', value)
+ ProjectSettings.save()
+
+
+func _on_reveal_delay_value_changed(value) -> void:
+ ProjectSettings.set_setting('dialogic/choices/reveal_delay', value)
+ ProjectSettings.save()
+
+
+func _on_appear_mode_item_selected(index:int) -> void:
+ %AppearMode.selected = index
+ match index:
+ 0:
+ ProjectSettings.set_setting('dialogic/choices/reveal_delay', 0)
+ ProjectSettings.set_setting('dialogic/choices/reveal_by_input', false)
+ %RevealDelay.hide()
+ 1:
+ ProjectSettings.set_setting('dialogic/choices/reveal_delay', %RevealDelay.value)
+ ProjectSettings.set_setting('dialogic/choices/reveal_by_input', false)
+ %RevealDelay.show()
+ 2:
+ ProjectSettings.set_setting('dialogic/choices/reveal_delay', 0)
+ ProjectSettings.set_setting('dialogic/choices/reveal_by_input', true)
+ %RevealDelay.hide()
+ 3:
+ ProjectSettings.set_setting('dialogic/choices/reveal_delay', %RevealDelay.value)
+ ProjectSettings.set_setting('dialogic/choices/reveal_by_input', true)
+ %RevealDelay.show()
+ ProjectSettings.save()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.tscn b/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.tscn
new file mode 100644
index 0000000..95957ac
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/settings_choices.tscn
@@ -0,0 +1,176 @@
+[gd_scene load_steps=5 format=3 uid="uid://uarvgnbrcltm"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/settings_choices.gd" id="2"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_nxutt"]
+
+[sub_resource type="Image" id="Image_2imc3"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_udy8i"]
+image = SubResource("Image_2imc3")
+
+[node name="Choices" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_bottom = -227.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("2")
+
+[node name="VBoxContainer2" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Title" type="Label" parent="VBoxContainer2"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Behaviour"
+
+[node name="VBoxContainer" type="GridContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+columns = 2
+
+[node name="AutofocusLabel" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="VBoxContainer/AutofocusLabel"]
+layout_mode = 2
+text = "Autofocus first choice"
+
+[node name="Autofocus" type="CheckBox" parent="VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="AppearModeLabel" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="Label2" type="Label" parent="VBoxContainer/AppearModeLabel"]
+layout_mode = 2
+text = "Choices appear"
+
+[node name="HintTooltip" parent="VBoxContainer/AppearModeLabel" instance=ExtResource("2_nxutt")]
+layout_mode = 2
+tooltip_text = "Choices can appear either instantly when the text finished, after a delay, a click or either."
+texture = SubResource("ImageTexture_udy8i")
+hint_text = "Choices can appear either instantly when the text finished, after a delay, a click or either."
+
+[node name="RevealDelayLabel" type="HBoxContainer" parent="VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="AppearMode" type="OptionButton" parent="VBoxContainer/RevealDelayLabel"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+item_count = 4
+selected = 0
+fit_to_longest_item = false
+popup/item_0/text = "Instantly"
+popup/item_0/id = 0
+popup/item_1/text = "After delay"
+popup/item_1/id = 1
+popup/item_2/text = "After another click"
+popup/item_2/id = 2
+popup/item_3/text = "After delay or click"
+popup/item_3/id = 3
+
+[node name="RevealDelay" type="SpinBox" parent="VBoxContainer/RevealDelayLabel"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Delay after which choices will appear (in seconds)."
+step = 0.01
+
+[node name="DelayLabel" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="Label2" type="Label" parent="VBoxContainer/DelayLabel"]
+layout_mode = 2
+text = "Delay before choices can be pressed"
+
+[node name="HintTooltip2" parent="VBoxContainer/DelayLabel" instance=ExtResource("2_nxutt")]
+layout_mode = 2
+tooltip_text = "Adding a small delay before choices can be activated can prevent accidentally choosing an option."
+texture = SubResource("ImageTexture_udy8i")
+hint_text = "Adding a small delay before choices can be activated can prevent accidentally choosing an option."
+
+[node name="Delay" type="SpinBox" parent="VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+step = 0.01
+
+[node name="DefaultFalseBehaviourLabel" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="Label3" type="Label" parent="VBoxContainer/DefaultFalseBehaviourLabel"]
+layout_mode = 2
+text = "Default behaviour for false choices"
+
+[node name="HintTooltip3" parent="VBoxContainer/DefaultFalseBehaviourLabel" instance=ExtResource("2_nxutt")]
+layout_mode = 2
+tooltip_text = "Define the default behaviour (hide or disable) for choices that have a condition that isn't met.
+
+Choices can overwrite this setting individually."
+texture = SubResource("ImageTexture_udy8i")
+hint_text = "Define the default behaviour (hide or disable) for choices that have a condition that isn't met.
+
+Choices can overwrite this setting individually."
+
+[node name="FalseBehaviour" type="OptionButton" parent="VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+item_count = 2
+selected = 0
+popup/item_0/text = "Hide"
+popup/item_0/id = 0
+popup/item_1/text = "Disable"
+popup/item_1/id = 1
+
+[node name="HSeparator" type="HSeparator" parent="."]
+layout_mode = 2
+
+[node name="HotkeySelection" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Title2" type="Label" parent="HotkeySelection"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Choice Hotkeys"
+
+[node name="HintTooltip4" parent="HotkeySelection" instance=ExtResource("2_nxutt")]
+layout_mode = 2
+tooltip_text = "You can add more complex hotkeys (or individual ones) by editing the choice buttons of your layout scene."
+texture = SubResource("ImageTexture_udy8i")
+hint_text = "You can add more complex hotkeys (or individual ones) by editing the choice buttons of your layout scene."
+
+[node name="VBoxContainer3" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Label4" type="Label" parent="VBoxContainer3"]
+layout_mode = 2
+text = "Hotkey type"
+
+[node name="HotkeyType" type="OptionButton" parent="VBoxContainer3"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+item_count = 2
+selected = 0
+popup/item_0/text = "No Hotkeys"
+popup/item_0/id = 0
+popup/item_1/text = "Default (1-9)"
+popup/item_1/id = 1
+
+[connection signal="toggled" from="VBoxContainer/Autofocus" to="." method="_on_Autofocus_toggled"]
+[connection signal="item_selected" from="VBoxContainer/RevealDelayLabel/AppearMode" to="." method="_on_appear_mode_item_selected"]
+[connection signal="value_changed" from="VBoxContainer/RevealDelayLabel/RevealDelay" to="." method="_on_reveal_delay_value_changed"]
+[connection signal="value_changed" from="VBoxContainer/Delay" to="." method="_on_Delay_value_changed"]
+[connection signal="item_selected" from="VBoxContainer/FalseBehaviour" to="." method="_on_FalseBehaviour_item_selected"]
+[connection signal="item_selected" from="VBoxContainer3/HotkeyType" to="." method="_on_HotkeyType_item_selected"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/subsystem_choices.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/subsystem_choices.gd
new file mode 100644
index 0000000..14e61a1
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/subsystem_choices.gd
@@ -0,0 +1,269 @@
+extends DialogicSubsystem
+
+## Subsystem that manages showing and activating of choices.
+
+## Emitted when a choice button was pressed. Info includes the keys 'button_index', 'text', 'event_index'.
+signal choice_selected(info:Dictionary)
+## Emitted when a set of choices is reached and shown.
+## Info includes the keys 'choices' (an array of dictionaries with infos on all the choices).
+signal question_shown(info:Dictionary)
+
+## Contains information on the latest question.
+var last_question_info := {}
+
+## The delay between the text finishing revealing and the choices appearing
+var reveal_delay := 0.0
+## If true the player has to click to reveal choices when they are reached
+var reveal_by_input := false
+## The delay between the choices becoming visible and being clickable. Can prevent accidental selection.
+var block_delay := 0.2
+## If true, the first (top-most) choice will be focused
+var autofocus_first_choice := true
+
+
+enum FalseBehaviour {HIDE=0, DISABLE=1}
+## The behaviour of choices with a false condition and else_action set to DEFAULT.
+var default_false_behaviour := FalseBehaviour.HIDE
+
+enum HotkeyBehaviour {NONE, NUMBERS}
+## Will add some hotkeys to the choices if different then HotkeyBehaviour.NONE.
+var hotkey_behaviour := HotkeyBehaviour.NONE
+
+
+### INTERNALS
+
+## Used to block choices from being clicked for a couple of seconds (if delay is set in settings).
+var _choice_blocker := Timer.new()
+
+#region STATE
+####################################################################################################
+
+func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ hide_all_choices()
+
+
+func _ready() -> void:
+ _choice_blocker.one_shot = true
+ DialogicUtil.update_timer_process_callback(_choice_blocker)
+ add_child(_choice_blocker)
+
+ reveal_delay = float(ProjectSettings.get_setting('dialogic/choices/reveal_delay', reveal_delay))
+ reveal_by_input = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', reveal_by_input)
+ block_delay = ProjectSettings.get_setting('dialogic/choices/delay', block_delay)
+ autofocus_first_choice = ProjectSettings.get_setting('dialogic/choices/autofocus_first', autofocus_first_choice)
+ hotkey_behaviour = ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', hotkey_behaviour)
+ default_false_behaviour = ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', default_false_behaviour)
+
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+## Hides all choice buttons.
+func hide_all_choices() -> void:
+ for node in get_tree().get_nodes_in_group('dialogic_choice_button'):
+ node.hide()
+ if node.is_connected('button_up', _on_choice_selected):
+ node.disconnect('button_up', _on_choice_selected)
+
+
+## Collects information on all the choices of the current question.
+## The result is a dictionary like this:
+## {'choices':
+## [
+## {'event_index':10, 'button_index':1, 'disabled':false, 'text':"My Choice", 'visible':true},
+## {'event_index':15, 'button_index':2, 'disabled':false, 'text':"My Choice2", 'visible':true},
+## ]
+func get_current_question_info() -> Dictionary:
+ var question_info := {'choices':[]}
+
+ var button_idx := 1
+ last_question_info = {'choices':[]}
+
+ for choice_index in get_current_choice_indexes():
+ var event: DialogicEvent = dialogic.current_timeline_events[choice_index]
+
+ if not event is DialogicChoiceEvent:
+ continue
+
+ var choice_event: DialogicChoiceEvent = event
+ var choice_info := {}
+ choice_info['event_index'] = choice_index
+ choice_info['button_index'] = button_idx
+
+ # Check Condition
+ var condition: String = choice_event.condition
+
+ if condition.is_empty() or dialogic.Expressions.execute_condition(choice_event.condition):
+ choice_info['disabled'] = false
+ choice_info['text'] = choice_event.get_property_translated('text')
+ choice_info['visible'] = true
+ button_idx += 1
+ else:
+ choice_info['disabled'] = true
+ if not choice_event.disabled_text.is_empty():
+ choice_info['text'] = choice_event.get_property_translated('disabled_text')
+ else:
+ choice_info['text'] = choice_event.get_property_translated('text')
+
+ var hide := choice_event.else_action == DialogicChoiceEvent.ElseActions.HIDE
+ hide = hide or choice_event.else_action == DialogicChoiceEvent.ElseActions.DEFAULT and default_false_behaviour == DialogicChoiceEvent.ElseActions.HIDE
+ choice_info['visible'] = not hide
+
+ if not hide:
+ button_idx += 1
+
+ choice_info.text = dialogic.Text.parse_text(choice_info.text, true, true, false, true, false, false)
+
+ choice_info.merge(choice_event.extra_data)
+
+ if dialogic.has_subsystem('History'):
+ choice_info['visited_before'] = dialogic.History.has_event_been_visited(choice_index)
+
+ question_info['choices'].append(choice_info)
+
+ return question_info
+
+
+## Lists all current choices and shows buttons.
+func show_current_question(instant:=true) -> void:
+ hide_all_choices()
+ _choice_blocker.stop()
+
+ if !instant and (reveal_delay != 0 or reveal_by_input):
+ if reveal_delay != 0:
+ _choice_blocker.start(reveal_delay)
+ _choice_blocker.timeout.connect(show_current_question)
+ if reveal_by_input:
+ dialogic.Inputs.dialogic_action.connect(show_current_question)
+ return
+
+ if _choice_blocker.timeout.is_connected(show_current_question):
+ _choice_blocker.timeout.disconnect(show_current_question)
+ if dialogic.Inputs.dialogic_action.is_connected(show_current_question):
+ dialogic.Inputs.dialogic_action.disconnect(show_current_question)
+
+ var missing_button := false
+
+ var question_info := get_current_question_info()
+
+ for choice in question_info.choices:
+ var node: DialogicNode_ChoiceButton = get_choice_button_node(choice.button_index)
+
+ if not node:
+ missing_button = true
+ continue
+
+ node._load_info(choice)
+
+ if choice.button_index == 1 and autofocus_first_choice:
+ node.grab_focus()
+
+ match hotkey_behaviour:
+ ## Add 1 to 9 as shortcuts if it's enabled
+ HotkeyBehaviour.NUMBERS:
+ if choice.button_index > 0 or choice.button_index < 10:
+ var shortcut: Shortcut
+ if node.shortcut != null:
+ shortcut = node.shortcut
+ else:
+ shortcut = Shortcut.new()
+
+ var input_key := InputEventKey.new()
+ input_key.keycode = OS.find_keycode_from_string(str(choice.button_index))
+ shortcut.events.append(input_key)
+ node.shortcut = shortcut
+
+ if node.pressed.is_connected(_on_choice_selected):
+ node.pressed.disconnect(_on_choice_selected)
+ node.pressed.connect(_on_choice_selected.bind(choice))
+
+ _choice_blocker.start(block_delay)
+ question_shown.emit(question_info)
+
+ if missing_button:
+ printerr("[Dialogic] The layout you are using doesn't have enough Choice Buttons for the choices you are trying to display.")
+
+
+
+func get_choice_button_node(button_index:int) -> DialogicNode_ChoiceButton:
+ var idx := 1
+ for node: DialogicNode_ChoiceButton in get_tree().get_nodes_in_group('dialogic_choice_button'):
+ if !node.get_parent().is_visible_in_tree():
+ continue
+ if node.choice_index == button_index or (node.choice_index == -1 and idx == button_index):
+ return node
+
+ if node.choice_index > 0:
+ idx = node.choice_index
+ idx += 1
+
+ return null
+
+
+func _on_choice_selected(choice_info := {}) -> void:
+ if dialogic.paused or not _choice_blocker.is_stopped():
+ return
+
+ if dialogic.has_subsystem('History'):
+ var all_choices: Array = dialogic.Choices.last_question_info['choices']
+ if dialogic.has_subsystem('VAR'):
+ dialogic.History.store_simple_history_entry(dialogic.VAR.parse_variables(choice_info.text), "Choice", {'all_choices': all_choices})
+ else:
+ dialogic.History.store_simple_history_entry(choice_info.text, "Choice", {'all_choices': all_choices})
+ if dialogic.has_subsystem("History"):
+ dialogic.History.mark_event_as_visited(choice_info.event_index)
+
+ choice_selected.emit(choice_info)
+ hide_all_choices()
+ dialogic.current_state = dialogic.States.IDLE
+ dialogic.handle_event(choice_info.event_index + 1)
+
+
+
+func get_current_choice_indexes() -> Array:
+ var choices := []
+ var evt_idx := dialogic.current_event_idx
+ var ignore := 0
+ while true:
+ if evt_idx >= len(dialogic.current_timeline_events):
+ break
+ if dialogic.current_timeline_events[evt_idx] is DialogicChoiceEvent:
+ if ignore == 0:
+ choices.append(evt_idx)
+ ignore += 1
+ elif dialogic.current_timeline_events[evt_idx].can_contain_events:
+ ignore += 1
+ else:
+ if ignore == 0:
+ break
+
+ if dialogic.current_timeline_events[evt_idx] is DialogicEndBranchEvent:
+ ignore -= 1
+ evt_idx += 1
+ return choices
+
+#endregion
+
+
+#region HELPERS
+####################################################################################################
+
+func is_question(index:int) -> bool:
+ if dialogic.current_timeline_events[index] is DialogicTextEvent:
+ if len(dialogic.current_timeline_events)-1 != index:
+ if dialogic.current_timeline_events[index+1] is DialogicChoiceEvent:
+ return true
+
+ if dialogic.current_timeline_events[index] is DialogicChoiceEvent:
+ if index != 0 and dialogic.current_timeline_events[index-1] is DialogicEndBranchEvent:
+ if dialogic.current_timeline_events[dialogic.current_timeline_events[index-1].find_opening_index(index-1)] is DialogicChoiceEvent:
+ return false
+ else:
+ return true
+ else:
+ return true
+ return false
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.gd b/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.gd
new file mode 100644
index 0000000..acc5d82
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.gd
@@ -0,0 +1,28 @@
+@tool
+extends HBoxContainer
+
+var parent_resource: DialogicChoiceEvent = null
+
+func refresh() -> void:
+ $AddChoice.icon = get_theme_icon("Add", "EditorIcons")
+
+ if parent_resource is DialogicChoiceEvent:
+ show()
+ if len(parent_resource.text) > 12:
+ $Label.text = "End of choice ("+parent_resource.text.substr(0,12)+"...)"
+ else:
+ $Label.text = "End of choice ("+parent_resource.text+")"
+ else:
+ hide()
+
+
+func _on_add_choice_pressed() -> void:
+ var timeline := find_parent('VisualEditor')
+ if timeline:
+ var resource := DialogicChoiceEvent.new()
+ resource.created_by_button = true
+ timeline.add_event_undoable(resource, get_parent().get_index()+1)
+ timeline.indent_events()
+ timeline.something_changed()
+ # Prevent focusing on future redos
+ resource.created_by_button = false
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.tscn b/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.tscn
new file mode 100644
index 0000000..076cb14
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Choice/ui_choice_end.tscn
@@ -0,0 +1,20 @@
+[gd_scene load_steps=2 format=3 uid="uid://cn0wbb2lk0s22"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/ui_choice_end.gd" id="1_7qd85"]
+
+[node name="Choice_End" type="HBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_bottom = -625.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_7qd85")
+
+[node name="AddChoice" type="Button" parent="."]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="."]
+layout_mode = 2
+
+[connection signal="pressed" from="AddChoice" to="." method="_on_add_choice_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg
new file mode 100644
index 0000000..9bb6889
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg.import
new file mode 100644
index 0000000..03a346a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://brc74uf1yvlfr"
+path="res://.godot/imported/clear_background.svg-ba6c92709c40c50ad9adca3b6dd4f0f9.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_background.svg"
+dest_files=["res://.godot/imported/clear_background.svg-ba6c92709c40c50ad9adca3b6dd4f0f9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg
new file mode 100644
index 0000000..868afba
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg
@@ -0,0 +1,16 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg.import
new file mode 100644
index 0000000..ad5f742
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3b85xt58e8ej"
+path="res://.godot/imported/clear_characters.svg-e753165c774c38d047d5e6714d41b614.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_characters.svg"
+dest_files=["res://.godot/imported/clear_characters.svg-e753165c774c38d047d5e6714d41b614.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg
new file mode 100644
index 0000000..e992063
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg.import
new file mode 100644
index 0000000..7b2d4e4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c70ten8d456b5"
+path="res://.godot/imported/clear_music.svg-9026b2c979b333d6853a516ffc5efe7e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_music.svg"
+dest_files=["res://.godot/imported/clear_music.svg-9026b2c979b333d6853a516ffc5efe7e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg
new file mode 100644
index 0000000..78e5ac7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg.import
new file mode 100644
index 0000000..ec00787
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rs76jb858p8e"
+path="res://.godot/imported/clear_positions.svg-6256ac02449aa6470f0f9ebb8b501b47.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_positions.svg"
+dest_files=["res://.godot/imported/clear_positions.svg-6256ac02449aa6470f0f9ebb8b501b47.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg
new file mode 100644
index 0000000..d9ca782
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg.import
new file mode 100644
index 0000000..d103475
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://clpxmppelspva"
+path="res://.godot/imported/clear_style.svg-00f5c5fa1dfb51c7b4a47fe3262a3001.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_style.svg"
+dest_files=["res://.godot/imported/clear_style.svg-00f5c5fa1dfb51c7b4a47fe3262a3001.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg
new file mode 100644
index 0000000..6383b72
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg.import
new file mode 100644
index 0000000..0c8922f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://co8t6o06m76ed"
+path="res://.godot/imported/clear_textbox.svg-c80ce0311eb72c5cdb0449f683d690f3.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/clear_textbox.svg"
+dest_files=["res://.godot/imported/clear_textbox.svg-c80ce0311eb72c5cdb0449f683d690f3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/event_clear.gd b/addons/dialogic-main/addons/dialogic/Modules/Clear/event_clear.gd
new file mode 100644
index 0000000..c125ae9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/event_clear.gd
@@ -0,0 +1,112 @@
+@tool
+class_name DialogicClearEvent
+extends DialogicEvent
+
+## Event that clears audio & visuals (not variables).
+## Useful to make sure the scene is clear for a completely new thing.
+
+var time := 1.0
+var step_by_step := true
+
+var clear_textbox := true
+var clear_portraits := true
+var clear_style := true
+var clear_music := true
+var clear_portrait_positions := true
+var clear_background := true
+
+################################################################################
+## EXECUTE
+################################################################################
+
+func _execute() -> void:
+ var final_time := time
+
+ if dialogic.Inputs.auto_skip.enabled:
+ var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
+ final_time = min(time, time_per_event)
+
+ if clear_textbox and dialogic.has_subsystem("Text"):
+ dialogic.Text.update_dialog_text('')
+ dialogic.Text.hide_textbox()
+ dialogic.current_state = dialogic.States.IDLE
+ if step_by_step: await dialogic.get_tree().create_timer(final_time).timeout
+
+ if clear_portraits and dialogic.has_subsystem('Portraits') and len(dialogic.Portraits.get_joined_characters()) != 0:
+ if final_time == 0:
+ dialogic.Portraits.leave_all_characters("Instant", final_time, step_by_step)
+ else:
+ dialogic.Portraits.leave_all_characters("", final_time, step_by_step)
+ if step_by_step: await dialogic.get_tree().create_timer(final_time).timeout
+
+ if clear_background and dialogic.has_subsystem('Backgrounds') and dialogic.Backgrounds.has_background():
+ dialogic.Backgrounds.update_background('', '', final_time)
+ if step_by_step: await dialogic.get_tree().create_timer(final_time).timeout
+
+ if clear_music and dialogic.has_subsystem('Audio') and dialogic.Audio.has_music():
+ dialogic.Audio.update_music('', 0.0, "", final_time)
+ if step_by_step: await dialogic.get_tree().create_timer(final_time).timeout
+
+ if clear_style and dialogic.has_subsystem('Styles'):
+ dialogic.Styles.change_style()
+
+ if clear_portrait_positions and dialogic.has_subsystem('Portraits'):
+ dialogic.PortraitContainers.reset_all_containers()
+
+ if not step_by_step:
+ await dialogic.get_tree().create_timer(final_time).timeout
+
+ finish()
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Clear"
+ set_default_color('Color9')
+ event_category = "Other"
+ event_sorting_index = 2
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func get_shortcode() -> String:
+ return "clear"
+
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "time" : {"property": "time", "default": ""},
+ "step" : {"property": "step_by_step", "default": true},
+ "text" : {"property": "clear_textbox", "default": true},
+ "portraits" : {"property": "clear_portraits", "default": true},
+ "music" : {"property": "clear_music", "default": true},
+ "background": {"property": "clear_background", "default": true},
+ "positions" : {"property": "clear_portrait_positions", "default": true},
+ "style" : {"property": "clear_style", "default": true},
+ }
+
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_label('Clear')
+
+ add_body_edit('time', ValueType.NUMBER, {'left_text':'Time:'})
+
+ add_body_edit('step_by_step', ValueType.BOOL, {'left_text':'Step by Step:'}, 'time > 0')
+ add_body_line_break()
+
+ add_body_edit('clear_textbox', ValueType.BOOL_BUTTON, {'left_text':'Clear:', 'icon':load("res://addons/dialogic/Modules/Clear/clear_textbox.svg"), 'tooltip':'Clear Textbox'})
+ add_body_edit('clear_portraits', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Clear/clear_characters.svg"), 'tooltip':'Clear Portraits'})
+ add_body_edit('clear_background', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Clear/clear_background.svg"), 'tooltip':'Clear Background'})
+ add_body_edit('clear_music', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Clear/clear_music.svg"), 'tooltip':'Clear Music'})
+ add_body_edit('clear_style', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Clear/clear_style.svg"), 'tooltip':'Clear Style'})
+ add_body_edit('clear_portrait_positions', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Clear/clear_positions.svg"), 'tooltip':'Clear Portrait Positions'})
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png b/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png
new file mode 100644
index 0000000..d975480
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png.import
new file mode 100644
index 0000000..ced3845
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://p1dnehrtjq1o"
+path="res://.godot/imported/icon.png-e7d9d446f1363a23cef2333974186364.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Clear/icon.png"
+dest_files=["res://.godot/imported/icon.png-e7d9d446f1363a23cef2333974186364.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Clear/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Clear/index.gd
new file mode 100644
index 0000000..4f18d14
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Clear/index.gd
@@ -0,0 +1,6 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_clear.gd')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Comment/event_comment.gd b/addons/dialogic-main/addons/dialogic/Modules/Comment/event_comment.gd
new file mode 100644
index 0000000..ad0b1dd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Comment/event_comment.gd
@@ -0,0 +1,65 @@
+@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 := ""
+
+
+################################################################################
+## EXECUTE
+################################################################################
+
+func _execute() -> void:
+ print("[Dialogic Comment] #", text)
+ finish()
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Comment"
+ set_default_color('Color9')
+ event_category = "Helpers"
+ event_sorting_index = 0
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func to_text() -> String:
+ return "# "+text
+
+
+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() -> void:
+ add_header_edit('text', ValueType.SINGLELINE_TEXT, {'left_text':'#', 'autofocus':true})
+
+
+#################### SYNTAX HIGHLIGHTING #######################################
+################################################################################
+
+func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, _line:String) -> Dictionary:
+ dict[0] = {'color':event_color.lerp(Highlighter.normal_color, 0.3)}
+ return dict
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png b/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png
new file mode 100644
index 0000000..417537c
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png.import
new file mode 100644
index 0000000..eaceb34
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hbx10ru6sxfb"
+path="res://.godot/imported/icon.png-fa7103d6fd3b8f3c9bdc467eda2aa2d8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Comment/icon.png"
+dest_files=["res://.godot/imported/icon.png-fa7103d6fd3b8f3c9bdc467eda2aa2d8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Comment/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Comment/index.gd
new file mode 100644
index 0000000..53ceb32
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Comment/index.gd
@@ -0,0 +1,6 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_comment.gd')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/event_condition.gd b/addons/dialogic-main/addons/dialogic/Modules/Condition/event_condition.gd
new file mode 100644
index 0000000..d56f34a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/event_condition.gd
@@ -0,0 +1,148 @@
+@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 := ""
+
+
+################################################################################
+## EXECUTE
+################################################################################
+
+func _execute() -> void:
+ if condition_type == ConditionTypes.ELSE:
+ finish()
+ return
+
+ if condition.is_empty(): condition = "true"
+
+ var result: bool = dialogic.Expressions.execute_condition(condition)
+ if not result:
+ var idx: int = dialogic.current_event_idx
+ var ignore := 1
+ while true:
+ idx += 1
+ if not dialogic.current_timeline.get_event(idx) or ignore == 0:
+ break
+ elif dialogic.current_timeline.get_event(idx).can_contain_events:
+ ignore += 1
+ elif dialogic.current_timeline.get_event(idx) is DialogicEndBranchEvent:
+ ignore -= 1
+
+ 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 = "Flow"
+ event_sorting_index = 1
+ can_contain_events = 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() in ['if', 'elif', 'else'] or (string.strip_edges().begins_with('if ') or string.strip_edges().begins_with('elif ') or string.strip_edges().begins_with('else')):
+ return true
+ return false
+
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('condition_type', ValueType.FIXED_OPTIONS, {
+ '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)
+
+
+####################### CODE COMPLETION ########################################
+################################################################################
+
+func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
+ if (line.begins_with('if') or line.begins_with('elif')) and symbol == '{':
+ CodeCompletionHelper.suggest_variables(TextNode)
+
+
+func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'if', 'if ', TextNode.syntax_highlighter.code_flow_color)
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'elif', 'elif ', TextNode.syntax_highlighter.code_flow_color)
+ TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'else', 'else:\n ', TextNode.syntax_highlighter.code_flow_color)
+
+
+#################### SYNTAX HIGHLIGHTING #######################################
+################################################################################
+
+
+func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
+ var word := line.get_slice(' ', 0)
+ dict[line.find(word)] = {"color":Highlighter.code_flow_color}
+ dict[line.find(word)+len(word)] = {"color":Highlighter.normal_color}
+ dict = Highlighter.color_condition(dict, line)
+ return dict
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg b/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg
new file mode 100644
index 0000000..805e95d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg.import
new file mode 100644
index 0000000..6979771
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bco4t1ey0fkpm"
+path="res://.godot/imported/icon.svg-ec999e6ccf5550ae3681ab27c522f026.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Condition/icon.svg"
+dest_files=["res://.godot/imported/icon.svg-ec999e6ccf5550ae3681ab27c522f026.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Condition/index.gd
new file mode 100644
index 0000000..c84f897
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/index.gd
@@ -0,0 +1,6 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_condition.gd')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.gd b/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.gd
new file mode 100644
index 0000000..4a5ac1d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.gd
@@ -0,0 +1,51 @@
+@tool
+extends HBoxContainer
+
+var parent_resource: DialogicEvent = null
+
+
+func _ready() -> void:
+ $AddElif.button_up.connect(add_elif)
+ $AddElse.button_up.connect(add_else)
+
+
+func refresh() -> void:
+ 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: DialogicEvent = 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() -> void:
+ var timeline := find_parent('VisualEditor')
+ if timeline:
+ var resource := DialogicConditionEvent.new()
+ resource.condition_type = DialogicConditionEvent.ConditionTypes.ELIF
+ timeline.add_event_undoable(resource, get_parent().get_index()+1)
+ timeline.indent_events()
+ timeline.something_changed()
+
+
+func add_else() -> void:
+ var timeline := find_parent('VisualEditor')
+ if timeline:
+ var resource := DialogicConditionEvent.new()
+ resource.condition_type = DialogicConditionEvent.ConditionTypes.ELSE
+ timeline.add_event_undoable(resource, get_parent().get_index()+1)
+ timeline.indent_events()
+ timeline.something_changed()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.tscn b/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.tscn
new file mode 100644
index 0000000..02ced22
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Condition/ui_condition_end.tscn
@@ -0,0 +1,26 @@
+[gd_scene load_steps=2 format=3]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/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"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/event_end_branch.gd b/addons/dialogic-main/addons/dialogic/Modules/Core/event_end_branch.gd
new file mode 100644
index 0000000..60a53dd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/event_end_branch.gd
@@ -0,0 +1,82 @@
+@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).
+
+
+#region EXECUTE
+################################################################################
+
+func _execute() -> void:
+ dialogic.current_event_idx = find_next_index()-1
+ finish()
+
+
+func find_next_index() -> int:
+ var idx: int = dialogic.current_event_idx
+
+ var ignore: int = 1
+ while true:
+ idx += 1
+ var event: DialogicEvent = 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
+
+
+func find_opening_index(at_index:int) -> int:
+ var idx: int = at_index
+
+ var ignore: int = 1
+ while true:
+ idx -= 1
+ var event: DialogicEvent = dialogic.current_timeline.get_event(idx)
+ if not event:
+ return idx
+ if event is DialogicEndBranchEvent:
+ ignore += 1
+ elif event.can_contain_events:
+ ignore -= 1
+ if ignore == 0:
+ return idx
+
+ return idx
+#endregion
+
+#region INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "End Branch"
+ disable_editor_button = true
+
+#endregion
+
+#region 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 "<>"
+
+
+func from_text(_string:String) -> void:
+ pass
+
+
+func is_valid_event(string:String) -> bool:
+ if string.strip_edges().begins_with("<>"):
+ return true
+ return false
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png b/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png
new file mode 100644
index 0000000..d975480
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png.import
new file mode 100644
index 0000000..38499df
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b0h5mknqmos7r"
+path="res://.godot/imported/icon.png-09b297bc76282f877647b319a2e3acd5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Core/icon.png"
+dest_files=["res://.godot/imported/icon.png-09b297bc76282f877647b319a2e3acd5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Core/index.gd
new file mode 100644
index 0000000..7082d4b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/index.gd
@@ -0,0 +1,27 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_end_branch.gd')]
+
+
+func _get_subsystems() -> Array:
+ return [
+ {'name':'Expressions', 'script':this_folder.path_join('subsystem_expression.gd')},
+ {'name':'Animations', 'script':this_folder.path_join('subsystem_animation.gd')},
+ {'name':'Inputs', 'script':this_folder.path_join('subsystem_input.gd')},
+ ]
+
+
+func _get_text_effects() -> Array[Dictionary]:
+ return [
+ {'command':'aa', 'subsystem':'Inputs', 'method':'effect_autoadvance'},
+ {'command':'ns', 'subsystem':'Inputs', 'method':'effect_noskip'},
+ {'command':'input', 'subsystem':'Inputs', 'method':'effect_input'},
+ ]
+
+func _get_text_modifiers() -> Array[Dictionary]:
+ return [
+ {'subsystem':'Expressions', 'method':"modifier_condition", 'command':'if', 'mode':-1},
+ ]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_animation.gd b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_animation.gd
new file mode 100644
index 0000000..4769134
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_animation.gd
@@ -0,0 +1,42 @@
+extends DialogicSubsystem
+
+## Subsystem that allows entering and leaving an animation state.
+
+signal finished
+signal animation_interrupted
+
+var prev_state: DialogicGameHandler.States = DialogicGameHandler.States.IDLE
+
+var _is_animating := false
+
+#region MAIN METHODS
+####################################################################################################
+
+func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ stop_animation()
+
+
+func is_animating() -> bool:
+ return _is_animating
+
+
+func start_animating() -> void:
+ prev_state = dialogic.current_state
+ dialogic.current_state = dialogic.States.ANIMATING
+ _is_animating = true
+
+
+func animation_finished(_arg := "") -> void:
+ # It can happen that the animation state has already been stopped
+ if not is_animating():
+ return
+ _is_animating = false
+ dialogic.current_state = prev_state as DialogicGameHandler.States
+ finished.emit()
+
+
+func stop_animation() -> void:
+ animation_finished()
+ animation_interrupted.emit()
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_expression.gd b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_expression.gd
new file mode 100644
index 0000000..fd4489a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_expression.gd
@@ -0,0 +1,95 @@
+extends DialogicSubsystem
+
+## Subsystem that allows executing strings (with the Expression class).
+## This is used by conditions and to allow expresions as variables.
+
+
+#region MAIN METHODS
+####################################################################################################
+
+func execute_string(string:String, default: Variant = null, no_warning := false) -> Variant:
+ # Some methods are not supported by the expression class, but very useful.
+ # Thus they are recreated below and secretly added.
+ string = string.replace('range(', 'd_range(')
+ string = string.replace('len(', 'd_len(')
+ string = string.replace('regex(', 'd_regex(')
+
+
+ var regex: RegEx = RegEx.create_from_string('{([^{}]*)}')
+
+ for res in regex.search_all(string):
+ var value: Variant = dialogic.VAR.get_variable(res.get_string())
+ string = string.replace(res.get_string(), var_to_str(value))
+
+ if string.begins_with("{") and string.ends_with('}') and string.count("{") == 1:
+ string = string.trim_prefix("{").trim_suffix("}")
+
+ var expr := Expression.new()
+
+ var autoloads := []
+ var autoload_names := []
+ for c in get_tree().root.get_children():
+ autoloads.append(c)
+ autoload_names.append(c.name)
+
+ if expr.parse(string, autoload_names) != OK:
+ if not no_warning:
+ printerr('[Dialogic] Expression "', string, '" failed to parse.')
+ printerr(' ', expr.get_error_text())
+ dialogic.print_debug_moment()
+ return default
+
+ var result: Variant = expr.execute(autoloads, self)
+ if expr.has_execute_failed():
+ if not no_warning:
+ printerr('[Dialogic] Expression "', string, '" failed to parse.')
+ printerr(' ', expr.get_error_text())
+ dialogic.print_debug_moment()
+ return default
+ return result
+
+
+func execute_condition(condition:String) -> bool:
+ if execute_string(condition, false):
+ return true
+ return false
+
+
+var condition_modifier_regex := RegEx.create_from_string(r"(?(DEFINE)(?([^{}]|\{(?P>nobraces)\})*))\[if *(?\{(?P>nobraces)\})(?(\\\]|\\\/|[^\]\/])*)(\/(?(\\\]|[^\]])*))?\]")
+func modifier_condition(text:String) -> String:
+ for find in condition_modifier_regex.search_all(text):
+ if execute_condition(find.get_string("condition")):
+ text = text.replace(find.get_string(), find.get_string("truetext").strip_edges())
+ else:
+ text = text.replace(find.get_string(), find.get_string("falsetext").strip_edges())
+ return text
+#endregion
+
+
+#region HELPERS
+####################################################################################################
+func d_range(a1, a2=null,a3=null,a4=null) -> Array:
+ if !a2:
+ return range(a1)
+ elif !a3:
+ return range(a1, a2)
+ elif !a4:
+ return range(a1, a2, a3)
+ else:
+ return range(a1, a2, a3, a4)
+
+func d_len(arg:Variant) -> int:
+ return len(arg)
+
+
+# Checks if there is a match in a string based on a regex pattern string.
+func d_regex(input: String, pattern: String, offset: int = 0, end: int = -1) -> bool:
+ var regex: RegEx = RegEx.create_from_string(pattern)
+ regex.compile(pattern)
+ var match := regex.search(input, offset, end)
+ if match:
+ return true
+ else:
+ return false
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_input.gd b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_input.gd
new file mode 100644
index 0000000..1c21f69
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Core/subsystem_input.gd
@@ -0,0 +1,211 @@
+extends DialogicSubsystem
+## Subsystem that handles input, Auto-Advance, and skipping.
+##
+## This subsystem can be accessed via GDScript: `Dialogic.Inputs`.
+
+
+signal dialogic_action_priority
+signal dialogic_action
+
+## Whenever the Auto-Skip timer finishes, this signal is emitted.
+## Configure Auto-Skip settings via [member auto_skip].
+signal autoskip_timer_finished
+
+
+const _SETTING_INPUT_ACTION := "dialogic/text/input_action"
+const _SETTING_INPUT_ACTION_DEFAULT := "dialogic_default_action"
+
+var input_block_timer := Timer.new()
+var _auto_skip_timer_left: float = 0.0
+var action_was_consumed := false
+
+var auto_skip: DialogicAutoSkip = null
+var auto_advance: DialogicAutoAdvance = null
+var manual_advance: DialogicManualAdvance = null
+
+
+#region SUBSYSTEM METHODS
+################################################################################
+
+func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ if not is_node_ready():
+ await ready
+
+ manual_advance.disabled_until_next_event = false
+ manual_advance.system_enabled = true
+
+
+func pause() -> void:
+ auto_advance.autoadvance_timer.paused = true
+ input_block_timer.paused = true
+ set_process(false)
+
+
+func resume() -> void:
+ auto_advance.autoadvance_timer.paused = false
+ input_block_timer.paused = false
+ var is_autoskip_timer_done := _auto_skip_timer_left > 0.0
+ set_process(!is_autoskip_timer_done)
+
+
+func post_install() -> void:
+ dialogic.Settings.connect_to_change('autoadvance_delay_modifier', auto_advance._update_autoadvance_delay_modifier)
+ auto_skip.toggled.connect(_on_autoskip_toggled)
+ auto_skip._init()
+ add_child(input_block_timer)
+ input_block_timer.one_shot = true
+
+
+#endregion
+
+
+#region MAIN METHODS
+################################################################################
+
+func handle_input() -> void:
+ if dialogic.paused or is_input_blocked():
+ return
+
+ if not action_was_consumed:
+ # We want to stop auto-advancing that cancels on user inputs.
+ if (auto_advance.is_enabled()
+ and auto_advance.enabled_until_user_input):
+ auto_advance.enabled_until_user_input = false
+ action_was_consumed = true
+
+ # We want to stop auto-skipping if it's enabled, we are listening
+ # to user inputs, and it's not instant skipping.
+ if (auto_skip.disable_on_user_input
+ and auto_skip.enabled):
+ auto_skip.enabled = false
+ action_was_consumed = true
+
+
+ dialogic_action_priority.emit()
+
+ if action_was_consumed:
+ action_was_consumed = false
+ return
+
+ dialogic_action.emit()
+
+
+## Unhandled Input is used for all NON-Mouse based inputs.
+func _unhandled_input(event:InputEvent) -> void:
+ if is_input_pressed(event, true):
+ if event is InputEventMouse or event is InputEventScreenTouch:
+ return
+ handle_input()
+
+
+## Input is used for all mouse based inputs.
+## If any DialogicInputNode is present this won't do anything (because that node handles MouseInput then).
+func _input(event:InputEvent) -> void:
+ if is_input_pressed(event):
+ if not event is InputEventMouse or get_tree().get_nodes_in_group('dialogic_input').any(func(node):return node.is_visible_in_tree()):
+ return
+
+ handle_input()
+
+
+func is_input_pressed(event: InputEvent, exact := false) -> bool:
+ var action: String = ProjectSettings.get_setting(_SETTING_INPUT_ACTION, _SETTING_INPUT_ACTION_DEFAULT)
+ return (event is InputEventAction and event.action == action) or Input.is_action_just_pressed(action, exact)
+
+
+## This is called from the gui_input of the InputCatcher and DialogText nodes
+func handle_node_gui_input(event:InputEvent) -> void:
+ if Input.is_action_just_pressed(ProjectSettings.get_setting(_SETTING_INPUT_ACTION, _SETTING_INPUT_ACTION_DEFAULT)):
+ if event is InputEventMouseButton and event.pressed:
+ DialogicUtil.autoload().Inputs.handle_input()
+
+
+func is_input_blocked() -> bool:
+ return input_block_timer.time_left > 0.0
+
+
+func block_input(time:=0.1) -> void:
+ if time > 0:
+ input_block_timer.wait_time = max(time, input_block_timer.time_left)
+ input_block_timer.start()
+
+
+func _ready() -> void:
+ auto_skip = DialogicAutoSkip.new()
+ auto_advance = DialogicAutoAdvance.new()
+ manual_advance = DialogicManualAdvance.new()
+
+ # We use the process method to count down the auto-start_autoskip_timer timer.
+ set_process(false)
+
+
+func stop_timers() -> void:
+ auto_advance.autoadvance_timer.stop()
+ input_block_timer.stop()
+ _auto_skip_timer_left = 0.0
+
+#endregion
+
+
+#region AUTO-SKIP
+################################################################################
+
+## This method will advance the timeline based on Auto-Skip settings.
+## The state, whether Auto-Skip is enabled, is ignored.
+func start_autoskip_timer() -> void:
+ _auto_skip_timer_left = auto_skip.time_per_event
+ set_process(true)
+ await autoskip_timer_finished
+
+
+## If Auto-Skip disables, we want to stop the timer.
+func _on_autoskip_toggled(enabled: bool) -> void:
+ if not enabled:
+ _auto_skip_timer_left = 0.0
+
+
+## Handles fine-grained Auto-Skip logic.
+## The [method _process] method allows for a more precise timer than the
+## [Timer] class.
+func _process(delta: float) -> void:
+ if _auto_skip_timer_left > 0:
+ _auto_skip_timer_left -= delta
+
+ if _auto_skip_timer_left <= 0:
+ autoskip_timer_finished.emit()
+
+ else:
+ autoskip_timer_finished.emit()
+ set_process(false)
+
+#endregion
+
+#region TEXT EFFECTS
+################################################################################
+
+
+func effect_input(_text_node:Control, skipped:bool, _argument:String) -> void:
+ if skipped:
+ return
+ dialogic.Text.show_next_indicators()
+ await dialogic.Inputs.dialogic_action_priority
+ dialogic.Text.hide_next_indicators()
+ dialogic.Inputs.action_was_consumed = true
+
+
+func effect_noskip(text_node:Control, skipped:bool, argument:String) -> void:
+ dialogic.Text.set_text_reveal_skippable(false, true)
+ manual_advance.disabled_until_next_event = true
+ effect_autoadvance(text_node, skipped, argument)
+
+
+func effect_autoadvance(_text_node: Control, _skipped:bool, argument:String) -> void:
+ if argument.ends_with('?'):
+ argument = argument.trim_suffix('?')
+ else:
+ auto_advance.enabled_until_next_event = true
+
+ if argument.is_valid_float():
+ auto_advance.override_delay_for_current_event = float(argument)
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd
new file mode 100644
index 0000000..af6878c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd
@@ -0,0 +1,21 @@
+@tool
+extends DialogicLayoutBase
+
+## The default layout base scene.
+
+@export var canvas_layer: int = 1
+@export var follow_viewport: bool = false
+
+@export_subgroup("Global")
+@export var global_bg_color: Color = Color(0, 0, 0, 0.9)
+@export var global_font_color: Color = Color("white")
+@export_file('*.ttf', '*.tres') var global_font: String = ""
+@export var global_font_size: int = 18
+
+
+func _apply_export_overrides() -> void:
+ # apply layer
+ set(&'layer', canvas_layer)
+ set(&'follow_viewport_enabled', follow_viewport)
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.tscn
new file mode 100644
index 0000000..884835e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://cqpb3ie51rwl5"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd" id="1_ifsho"]
+
+[node name="DefaultLayoutBase" type="CanvasLayer"]
+script = ExtResource("1_ifsho")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/part_config.cfg
new file mode 100644
index 0000000..99751e8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/part_config.cfg
@@ -0,0 +1,6 @@
+[style]
+type = "Layout Base"
+name = "Default Layout Base"
+author = "Dialogic"
+description = "A very simple base for layouts."
+scene = "default_layout_base.tscn"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/part_config.cfg
new file mode 100644
index 0000000..7b1adc9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/part_config.cfg
@@ -0,0 +1,6 @@
+[style]
+type = "Layout Base"
+name = "Textbubble Base"
+author = "Dialogic"
+description = "A base scene for the textbubble style. Expects a textbubble layer."
+scene = "text_bubble_base.tscn"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd
new file mode 100644
index 0000000..ed4707a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd
@@ -0,0 +1,94 @@
+@tool
+extends DialogicLayoutBase
+
+## This layout won't do anything on its own
+
+var bubbles: Array = []
+var registered_characters: Dictionary = {}
+
+@export_group("Main")
+@export_range(1, 25, 1) var bubble_count: int = 2
+
+
+func _ready() -> void:
+ if Engine.is_editor_hint():
+ return
+
+ DialogicUtil.autoload().Text.about_to_show_text.connect(_on_dialogic_text_event)
+ $Example/CRT.position = $Example.get_viewport_rect().size/2
+
+ if not has_node('TextBubbleLayer'):
+ return
+
+ if len(bubbles) < bubble_count:
+ add_bubble()
+
+
+func register_character(character:Variant, node:Node):
+ if typeof(character) == TYPE_STRING:
+ var character_string: String = character
+ if character.begins_with("res://"):
+ character = load(character)
+ else:
+ character = DialogicResourceUtil.get_character_resource(character)
+ if not character:
+ printerr("[Dialogic] Textbubble: Tried registering character from invalid string '", character_string, "'.")
+
+ registered_characters[character] = node
+ if len(registered_characters) > len(bubbles) and len(bubbles) < bubble_count:
+ add_bubble()
+
+
+func _get_persistent_info() -> Dictionary:
+ return {"textbubble_registers": registered_characters}
+
+
+func _load_persistent_info(info: Dictionary) -> void:
+ var register_info: Dictionary = info.get("textbubble_registers", {})
+ for character in register_info:
+ if is_instance_valid(register_info[character]):
+ register_character(character, register_info[character])
+
+
+func add_bubble() -> void:
+ if not has_node('TextBubbleLayer'):
+ return
+
+ var new_bubble: Control = get_node("TextBubbleLayer").add_bubble()
+ bubbles.append(new_bubble)
+
+
+func _on_dialogic_text_event(info:Dictionary):
+ var bubble_to_use: Node
+ for bubble in bubbles:
+ if bubble.current_character == info.character:
+ bubble_to_use = bubble
+
+ if bubble_to_use == null:
+ for bubble in bubbles:
+ if bubble.current_character == null:
+ bubble_to_use = bubble
+
+ if bubble_to_use == null:
+ bubble_to_use = bubbles[0]
+
+ var node_to_point_at: Node
+ if info.character in registered_characters:
+ node_to_point_at = registered_characters[info.character]
+ $Example.hide()
+ else:
+ node_to_point_at = $Example/CRT/Marker
+ $Example.show()
+
+ bubble_to_use.current_character = info.character
+ bubble_to_use.node_to_point_at = node_to_point_at
+ bubble_to_use.reset()
+ if has_node('TextBubbleLayer'):
+ get_node("TextBubbleLayer").bubble_apply_overrides(bubble_to_use)
+ bubble_to_use.open()
+
+ ## Now close other bubbles
+ for bubble in bubbles:
+ if bubble != bubble_to_use:
+ bubble.close()
+ bubble.current_character = null
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn
new file mode 100644
index 0000000..27a00fc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn
@@ -0,0 +1,55 @@
+[gd_scene load_steps=3 format=3 uid="uid://syki6k0e6aac"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd" id="1_urqwc"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_70ljh"]
+content_margin_left = 5.0
+content_margin_top = 5.0
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0, 0, 0, 0.654902)
+
+[node name="TextBubbleHolder" type="CanvasLayer"]
+script = ExtResource("1_urqwc")
+
+[node name="Example" type="Control" parent="."]
+visible = false
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Label" type="RichTextLabel" parent="Example"]
+layout_mode = 1
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_left = 12.0
+offset_top = -235.0
+offset_right = 835.0
+offset_bottom = -14.0
+grow_vertical = 0
+theme_override_styles/normal = SubResource("StyleBoxFlat_70ljh")
+bbcode_enabled = true
+text = "This is a fallback bubble, that is not actually connected to any character. In game use the following code to add speech bubbles to a character:
+[color=darkgray]
+var layout = Dialogic.start(timeline_path)
+layout.register_character(character_resource, node)
+[/color]
+- [color=lightblue]character_resource[/color] should be a loaded DialogicCharacter (a .dch file).
+- [color=lightblue]node[/color] should be the 2D or 3D node the bubble should point at.
+ -> E.g. [color=darkgray]layout.register_character(load(\"res://path/to/my/character.dch\"), $BubbleMarker)"
+
+[node name="CRT" type="ColorRect" parent="Example"]
+layout_mode = 0
+offset_left = 504.0
+offset_top = 290.0
+offset_right = 540.0
+offset_bottom = 324.0
+rotation = 0.785397
+color = Color(1, 0.313726, 1, 1)
+
+[node name="Marker" type="Marker2D" parent="Example/CRT"]
+position = Vector2(10.6066, 9.1924)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg
new file mode 100644
index 0000000..3e0b300
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import
new file mode 100644
index 0000000..223ded2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cm8w3lr5o038d"
+path="res://.godot/imported/background_layer_icon.svg-bb4f05c41bc2a42172b5e44318ae3472.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg"
+dest_files=["res://.godot/imported/background_layer_icon.svg-bb4f05c41bc2a42172b5e44318ae3472.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd
new file mode 100644
index 0000000..9162300
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd
@@ -0,0 +1,2 @@
+@tool
+extends DialogicLayoutLayer
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn
new file mode 100644
index 0000000..f1d44c0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn
@@ -0,0 +1,25 @@
+[gd_scene load_steps=3 format=3 uid="uid://c1k5m0w3r40xf"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd" id="1_tu40u"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Background/node_background_holder.gd" id="2_ghan2"]
+
+[node name="BackgroundLayer" type="Control"]
+layout_direction = 2
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_tu40u")
+
+[node name="DialogicNode_BackgroundHolder" type="ColorRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+color = Color(1, 1, 1, 0)
+script = ExtResource("2_ghan2")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/part_config.cfg
new file mode 100644
index 0000000..2dd606a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Full Background"
+author = "Dialogic"
+description = "A simple layer displaying backgrounds."
+scene = "full_background_layer.tscn"
+icon = "background_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png
new file mode 100644
index 0000000..9c42d7d
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import
new file mode 100644
index 0000000..7e79d3c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c3hagnfudj3nk"
+path="res://.godot/imported/preview.png-9b488ad37a3ca34f10b50b008581e4ff.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png"
+dest_files=["res://.godot/imported/preview.png-9b488ad37a3ca34f10b50b008581e4ff.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd
new file mode 100644
index 0000000..60f4e91
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd
@@ -0,0 +1,177 @@
+@tool
+extends DialogicLayoutLayer
+
+## Layer that provides a popup with glossary info,
+## when hovering a glossary entry on a text node.
+
+
+@export_group('Text')
+enum Alignment {LEFT, CENTER, RIGHT}
+@export var title_alignment: Alignment = Alignment.LEFT
+@export var text_alignment: Alignment = Alignment.LEFT
+@export var extra_alignment: Alignment = Alignment.RIGHT
+
+@export_subgroup("Colors")
+enum TextColorModes {GLOBAL, ENTRY, CUSTOM}
+@export var title_color_mode: TextColorModes = TextColorModes.ENTRY
+@export var title_custom_color: Color = Color.WHITE
+@export var text_color_mode: TextColorModes = TextColorModes.ENTRY
+@export var text_custom_color: Color = Color.WHITE
+@export var extra_color_mode: TextColorModes = TextColorModes.ENTRY
+@export var extra_custom_color: Color = Color.WHITE
+
+
+@export_group("Font")
+@export var font_use_global: bool = true
+@export_file('*.ttf', '*.tres') var font_custom: String = ""
+
+@export_subgroup('Sizes')
+@export var font_title_size: int = 18
+@export var font_text_size: int = 17
+@export var font_extra_size: int = 15
+
+
+@export_group("Box")
+@export_subgroup("Color")
+enum ModulateModes {BASE_COLOR_ONLY, ENTRY_COLOR_ON_BOX, GLOBAL_BG_COLOR}
+@export var box_modulate_mode: ModulateModes = ModulateModes.ENTRY_COLOR_ON_BOX
+@export var box_base_modulate: Color = Color.WHITE
+@export_subgroup("Size")
+@export var box_width: int = 200
+
+const MISSING_INDEX := -1
+func get_pointer() -> Control:
+ return $Pointer
+
+
+func get_title() -> Label:
+ return %Title
+
+
+func get_text() -> RichTextLabel:
+ return %Text
+
+
+func get_extra() -> RichTextLabel:
+ return %Extra
+
+
+func get_panel() -> PanelContainer:
+ return %Panel
+
+
+func get_panel_point() -> PanelContainer:
+ return %PanelPoint
+
+
+func _ready() -> void:
+ if Engine.is_editor_hint():
+ return
+
+ get_pointer().hide()
+ var text_system: Node = DialogicUtil.autoload().get(&'Text')
+ var _error: int = 0
+ _error = text_system.connect(&'animation_textbox_hide', get_pointer().hide)
+ _error = text_system.connect(&'meta_hover_started', _on_dialogic_display_dialog_text_meta_hover_started)
+ _error = text_system.connect(&'meta_hover_ended', _on_dialogic_display_dialog_text_meta_hover_ended)
+
+
+## Method that shows the bubble and fills in the info
+func _on_dialogic_display_dialog_text_meta_hover_started(meta: String) -> void:
+ var entry_info := DialogicUtil.autoload().Glossary.get_entry(meta)
+
+ if entry_info.is_empty():
+ return
+
+ get_pointer().show()
+ get_title().text = entry_info.title
+ get_text().text = entry_info.text
+ get_text().text = ['', '[center]', '[right]'][text_alignment] + get_text().text
+ get_extra().text = entry_info.extra
+ get_extra().text = ['', '[center]', '[right]'][extra_alignment] + get_extra().text
+ get_pointer().global_position = get_pointer().get_global_mouse_position()
+
+ if title_color_mode == TextColorModes.ENTRY:
+ get_title().add_theme_color_override(&"font_color", entry_info.color)
+ if text_color_mode == TextColorModes.ENTRY:
+ get_text().add_theme_color_override(&"default_color", entry_info.color)
+ if extra_color_mode == TextColorModes.ENTRY:
+ get_extra().add_theme_color_override(&"default_color", entry_info.color)
+
+ match box_modulate_mode:
+ ModulateModes.ENTRY_COLOR_ON_BOX:
+ get_panel().self_modulate = entry_info.color
+ get_panel_point().self_modulate = entry_info.color
+
+
+## Method that keeps the bubble at mouse position when visible
+func _process(_delta: float) -> void:
+ if Engine.is_editor_hint():
+ return
+
+ var pointer: Control = get_pointer()
+ if pointer.visible:
+ pointer.global_position = pointer.get_global_mouse_position()
+
+
+## Method that hides the bubble
+func _on_dialogic_display_dialog_text_meta_hover_ended(_meta:String) -> void:
+ get_pointer().hide()
+
+
+
+func _apply_export_overrides() -> void:
+ # Apply fonts
+ var font: FontFile
+ var global_font_setting: String = get_global_setting(&"font", '')
+ if font_use_global and ResourceLoader.exists(global_font_setting):
+ font = load(global_font_setting)
+ elif ResourceLoader.exists(font_custom):
+ font = load(font_custom)
+
+ var title: Label = get_title()
+ if font:
+ title.add_theme_font_override(&"font", font)
+ title.horizontal_alignment = title_alignment as HorizontalAlignment
+
+ # Apply font & sizes
+ title.add_theme_font_size_override(&"font_size", font_title_size)
+ var labels: Array[RichTextLabel] = [get_text(), get_extra()]
+ var sizes: PackedInt32Array = [font_text_size, font_extra_size]
+ for i : int in len(labels):
+ if font:
+ labels[i].add_theme_font_override(&'normal_font', font)
+
+ labels[i].add_theme_font_size_override(&"normal_font_size", sizes[i])
+ labels[i].add_theme_font_size_override(&"bold_font_size", sizes[i])
+ labels[i].add_theme_font_size_override(&"italics_font_size", sizes[i])
+ labels[i].add_theme_font_size_override(&"bold_italics_font_size", sizes[i])
+ labels[i].add_theme_font_size_override(&"mono_font_size", sizes[i])
+
+
+ # Apply text colors
+ # this applies Global or Custom colors, entry colors are applied on hover
+ var controls: Array[Control] = [get_title(), get_text(), get_extra()]
+ var settings: Array[StringName] = [&'font_color', &'default_color', &'default_color']
+ var color_modes: Array[TextColorModes] = [title_color_mode, text_color_mode, extra_color_mode]
+ var custom_colors: PackedColorArray = [title_custom_color, text_custom_color, extra_custom_color]
+ for i : int in len(controls):
+ match color_modes[i]:
+ TextColorModes.GLOBAL:
+ controls[i].add_theme_color_override(settings[i], get_global_setting(&'font_color', custom_colors[i]) as Color)
+ TextColorModes.CUSTOM:
+ controls[i].add_theme_color_override(settings[i], custom_colors[i])
+
+ # Apply box size
+ var panel: PanelContainer = get_panel()
+ panel.size.x = box_width
+ panel.position.x = -box_width/2.0
+
+ # Apply box coloring
+ match box_modulate_mode:
+ ModulateModes.BASE_COLOR_ONLY:
+ panel.self_modulate = box_base_modulate
+ get_panel_point().self_modulate = box_base_modulate
+ ModulateModes.GLOBAL_BG_COLOR:
+ panel.self_modulate = get_global_setting(&'bg_color', box_base_modulate)
+ get_panel_point().self_modulate = get_global_setting(&'bg_color', box_base_modulate)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn
new file mode 100644
index 0000000..bb37b7e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn
@@ -0,0 +1,87 @@
+[gd_scene load_steps=3 format=3 uid="uid://dsbwnp5hegnu3"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd" id="1_3nmfj"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_a3cyk"]
+bg_color = Color(0.12549, 0.12549, 0.12549, 1)
+border_width_left = 2
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 2
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 5.0
+expand_margin_top = 5.0
+expand_margin_right = 5.0
+expand_margin_bottom = 5.0
+
+[node name="Glossary" type="Control"]
+layout_mode = 3
+anchors_preset = 0
+mouse_filter = 2
+script = ExtResource("1_3nmfj")
+
+[node name="Pointer" type="Control" parent="."]
+anchors_preset = 0
+
+[node name="Panel" type="PanelContainer" parent="Pointer"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -81.0
+offset_top = -113.0
+offset_right = 86.0
+offset_bottom = -35.0
+grow_horizontal = 2
+grow_vertical = 0
+theme_override_styles/panel = SubResource("StyleBoxFlat_a3cyk")
+metadata/_edit_use_custom_anchors = true
+metadata/_edit_layout_mode = 1
+
+[node name="VBox" type="VBoxContainer" parent="Pointer/Panel"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Title" type="Label" parent="Pointer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="Pointer/Panel/VBox"]
+layout_mode = 2
+
+[node name="Text" type="RichTextLabel" parent="Pointer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+bbcode_enabled = true
+fit_content = true
+
+[node name="Extra" type="RichTextLabel" parent="Pointer/Panel/VBox"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/normal_font_size = 15
+bbcode_enabled = true
+fit_content = true
+
+[node name="Control" type="Control" parent="Pointer/Panel"]
+show_behind_parent = true
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 8
+
+[node name="PanelPoint" type="PanelContainer" parent="Pointer/Panel/Control"]
+unique_name_in_owner = true
+layout_mode = 0
+offset_left = -0.999999
+offset_top = -14.0
+offset_right = 19.0
+offset_bottom = 6.0
+rotation = 0.75799
+size_flags_horizontal = 4
+size_flags_vertical = 8
+theme_override_styles/panel = SubResource("StyleBoxFlat_a3cyk")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/part_config.cfg
new file mode 100644
index 0000000..e7e772b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Popup Glossary"
+author = "Dialogic"
+description = "A popup that that appears when hovering glossary entries."
+scene = "glossary_popup_layer.tscn"
+icon = "popup_glossary_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg
new file mode 100644
index 0000000..bedd4a7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import
new file mode 100644
index 0000000..b95777c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c3q86ma7r6l57"
+path="res://.godot/imported/popup_glossary_layer_icon.svg-c99b40f049399b6abee3b59b9ea01928.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg"
+dest_files=["res://.godot/imported/popup_glossary_layer_icon.svg-c99b40f049399b6abee3b59b9ea01928.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png
new file mode 100644
index 0000000..8dc08bb
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import
new file mode 100644
index 0000000..934ee6e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dydv1g180rqex"
+path="res://.godot/imported/preview.png-e3c8bf1c4c6decf2213bdc60bb3d13db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png"
+dest_files=["res://.godot/imported/preview.png-e3c8bf1c4c6decf2213bdc60bb3d13db.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd
new file mode 100644
index 0000000..83fef50
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd
@@ -0,0 +1,30 @@
+extends Container
+
+func get_text_box() -> RichTextLabel:
+ return %TextBox
+
+
+func get_name_label() -> Label:
+ return %NameLabel
+
+
+func get_icon() -> TextureRect:
+ return %Icon
+
+
+func load_info(text:String, character:String = "", character_color: Color =Color(), icon:Texture= null) -> void:
+ get_text_box().text = text
+ var name_label: Label = get_name_label()
+ if character:
+ name_label.text = character
+ name_label.add_theme_color_override('font_color', character_color)
+ name_label.show()
+ else:
+ name_label.hide()
+
+ var icon_node: TextureRect = get_icon()
+ if icon == null:
+ icon_node.hide()
+ else:
+ icon_node.show()
+ icon_node.texture = icon
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn
new file mode 100644
index 0000000..f1b5983
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=3 format=3 uid="uid://cuoywrmvda1yg"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd" id="1_dgoja"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_upgjp"]
+content_margin_left = 5.0
+content_margin_top = 5.0
+content_margin_right = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0.780392, 0.780392, 0.780392, 0.156863)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[node name="HistoryItem" type="PanelContainer"]
+offset_left = -37.0
+offset_top = 510.0
+offset_right = 1085.0
+offset_bottom = 555.0
+theme_override_styles/panel = SubResource("StyleBoxFlat_upgjp")
+script = ExtResource("1_dgoja")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Icon" type="TextureRect" parent="HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(30, 30)
+layout_mode = 2
+expand_mode = 1
+stretch_mode = 4
+
+[node name="NameLabel" type="Label" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 0
+
+[node name="TextBox" type="RichTextLabel" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+bbcode_enabled = true
+text = "Some tex"
+fit_content = true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg
new file mode 100644
index 0000000..4d21936
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import
new file mode 100644
index 0000000..ed69371
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvq7qbkngeoud"
+path="res://.godot/imported/history_icon.svg-685fe148b401e6c76d1fdec68c677193.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg"
+dest_files=["res://.godot/imported/history_icon.svg-685fe148b401e6c76d1fdec68c677193.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd
new file mode 100644
index 0000000..d420021
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd
@@ -0,0 +1,151 @@
+@tool
+extends DialogicLayoutLayer
+
+## Example scene for viewing the History
+## Implements most of the visual options from 1.x History mode
+
+@export_group('Look')
+@export_subgroup('Font')
+@export var font_use_global_size: bool = true
+@export var font_custom_size: int = 15
+@export var font_use_global_fonts: bool = true
+@export_file('*.ttf', '*.tres') var font_custom_normal: String = ""
+@export_file('*.ttf', '*.tres') var font_custom_bold: String = ""
+@export_file('*.ttf', '*.tres') var font_custom_italics: String = ""
+
+@export_subgroup('Buttons')
+@export var show_open_button: bool = true
+@export var show_close_button: bool = true
+
+@export_group('Settings')
+@export_subgroup('Events')
+@export var show_all_choices: bool = true
+@export var show_join_and_leave: bool = true
+
+@export_subgroup('Behaviour')
+@export var scroll_to_bottom: bool = true
+@export var show_name_colors: bool = true
+@export var name_delimeter: String = ": "
+
+var scroll_to_bottom_flag: bool = false
+
+@export_group('Private')
+@export var HistoryItem: PackedScene = null
+
+var history_item_theme: Theme = null
+
+func get_show_history_button() -> Button:
+ return $ShowHistory
+
+
+func get_hide_history_button() -> Button:
+ return $HideHistory
+
+
+func get_history_box() -> ScrollContainer:
+ return %HistoryBox
+
+
+func get_history_log() -> VBoxContainer:
+ return %HistoryLog
+
+
+func _ready() -> void:
+ if Engine.is_editor_hint():
+ return
+ DialogicUtil.autoload().History.open_requested.connect(_on_show_history_pressed)
+ DialogicUtil.autoload().History.close_requested.connect(_on_hide_history_pressed)
+
+
+func _apply_export_overrides() -> void:
+ var history_subsystem: Node = DialogicUtil.autoload().get(&'History')
+ if history_subsystem != null:
+ get_show_history_button().visible = show_open_button and history_subsystem.get(&'simple_history_enabled')
+ else:
+ set(&'visible', false)
+
+ history_item_theme = Theme.new()
+
+ if font_use_global_size:
+ history_item_theme.default_font_size = get_global_setting(&'font_size', font_custom_size)
+ else:
+ history_item_theme.default_font_size = font_custom_size
+
+ if font_use_global_fonts and ResourceLoader.exists(get_global_setting(&'font', '') as String):
+ history_item_theme.default_font = load(get_global_setting(&'font', '') as String) as Font
+ elif ResourceLoader.exists(font_custom_normal):
+ history_item_theme.default_font = load(font_custom_normal)
+
+ if ResourceLoader.exists(font_custom_bold):
+ history_item_theme.set_font(&'RichtTextLabel', &'bold_font', load(font_custom_bold) as Font)
+ if ResourceLoader.exists(font_custom_italics):
+ history_item_theme.set_font(&'RichtTextLabel', &'italics_font', load(font_custom_italics) as Font)
+
+
+
+func _process(_delta : float) -> void:
+ if Engine.is_editor_hint():
+ return
+ if scroll_to_bottom_flag and get_history_box().visible and get_history_log().get_child_count():
+ await get_tree().process_frame
+ get_history_box().ensure_control_visible(get_history_log().get_children()[-1] as Control)
+ scroll_to_bottom_flag = false
+
+
+func _on_show_history_pressed() -> void:
+ DialogicUtil.autoload().paused = true
+ show_history()
+
+
+func show_history() -> void:
+ for child: Node in get_history_log().get_children():
+ child.queue_free()
+
+ var history_subsystem: Node = DialogicUtil.autoload().get(&'History')
+ for info: Dictionary in history_subsystem.call(&'get_simple_history'):
+ var history_item : Node = HistoryItem.instantiate()
+ history_item.set(&'theme', history_item_theme)
+ match info.event_type:
+ "Text":
+ if info.has('character') and info['character']:
+ if show_name_colors:
+ history_item.call(&'load_info', info['text'], info['character']+name_delimeter, info['character_color'])
+ else:
+ history_item.call(&'load_info', info['text'], info['character']+name_delimeter)
+ else:
+ history_item.call(&'load_info', info['text'])
+ "Character":
+ if !show_join_and_leave:
+ history_item.queue_free()
+ continue
+ history_item.call(&'load_info', '[i]'+info['text'])
+ "Choice":
+ var choices_text: String = ""
+ if show_all_choices:
+ for i : String in info['all_choices']:
+ if i.ends_with('#disabled'):
+ choices_text += "- [i]("+i.trim_suffix('#disabled')+")[/i]\n"
+ elif i == info['text']:
+ choices_text += "-> [b]"+i+"[/b]\n"
+ else:
+ choices_text += "-> "+i+"\n"
+ else:
+ choices_text += "- [b]"+info['text']+"[/b]\n"
+ history_item.call(&'load_info', choices_text)
+
+ get_history_log().add_child(history_item)
+
+ if scroll_to_bottom:
+ scroll_to_bottom_flag = true
+
+ get_show_history_button().hide()
+ get_hide_history_button().visible = show_close_button
+ get_history_box().show()
+
+
+func _on_hide_history_pressed() -> void:
+ DialogicUtil.autoload().paused = false
+ get_history_box().hide()
+ get_hide_history_button().hide()
+ var history_subsystem: Node = DialogicUtil.autoload().get(&'History')
+ get_show_history_button().visible = show_open_button and history_subsystem.get(&'simple_history_enabled')
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn
new file mode 100644
index 0000000..53f7f35
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn
@@ -0,0 +1,93 @@
+[gd_scene load_steps=4 format=3 uid="uid://lx24i8fl6uo"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd" id="1_4mqm3"]
+[ext_resource type="PackedScene" uid="uid://cuoywrmvda1yg" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn" id="2_x1xgk"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1hdvb"]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(0, 0, 0, 0.776471)
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+
+[node name="ExampleHistoryScene" type="Control"]
+layout_direction = 1
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+script = ExtResource("1_4mqm3")
+font_use_global_size = null
+font_custom_size = null
+font_use_global_fonts = null
+font_custom_normal = null
+font_custom_bold = null
+font_custom_italics = null
+HistoryItem = ExtResource("2_x1xgk")
+disabled = null
+
+[node name="HistoryBox" type="ScrollContainer" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 74.0
+offset_top = 65.0
+offset_right = -74.0
+offset_bottom = -57.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_1hdvb")
+horizontal_scroll_mode = 0
+
+[node name="HistoryLog" type="VBoxContainer" parent="HistoryBox"]
+unique_name_in_owner = true
+layout_direction = 1
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="ShowHistory" type="Button" parent="."]
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -73.0
+offset_top = 7.0
+offset_right = -9.0
+offset_bottom = 38.0
+grow_horizontal = 0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "History"
+
+[node name="HideHistory" type="Button" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 1
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -123.0
+offset_top = 58.0
+offset_right = -62.0
+offset_bottom = 89.0
+grow_horizontal = 0
+size_flags_horizontal = 4
+size_flags_vertical = 4
+text = "Return"
+
+[connection signal="pressed" from="ShowHistory" to="." method="_on_show_history_pressed"]
+[connection signal="pressed" from="HideHistory" to="." method="_on_hide_history_pressed"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/part_config.cfg
new file mode 100644
index 0000000..fd104d9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Overlay History"
+author = "Dialogic"
+description = "Provides a history button and overlay."
+scene = "history_layer.tscn"
+icon = "history_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png
new file mode 100644
index 0000000..e22843f
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import
new file mode 100644
index 0000000..fb79b46
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7ep7q40d2amu"
+path="res://.godot/imported/preview.png-b3807e0cc834e554ffa9877732a0d73d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png"
+dest_files=["res://.godot/imported/preview.png-b3807e0cc834e554ffa9877732a0d73d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd
new file mode 100644
index 0000000..e48a1f8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd
@@ -0,0 +1,4 @@
+@tool
+extends DialogicLayoutLayer
+
+## A layer that holds a full-screen input catcher.
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn
new file mode 100644
index 0000000..bae57c5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn
@@ -0,0 +1,24 @@
+[gd_scene load_steps=3 format=3 uid="uid://cn674foxwedqu"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd" id="1_3cmha"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_input.gd" id="2_dxpjw"]
+
+[node name="FullAdvanceInputLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_3cmha")
+
+[node name="DialogicNode_Input" type="Control" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 1
+script = ExtResource("2_dxpjw")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg
new file mode 100644
index 0000000..512ca5b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import
new file mode 100644
index 0000000..20c1f58
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bv38272320783"
+path="res://.godot/imported/input_layer_icon.svg-658ec7f1519aa821c2edfe250f436482.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg"
+dest_files=["res://.godot/imported/input_layer_icon.svg-658ec7f1519aa821c2edfe250f436482.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/part_config.cfg
new file mode 100644
index 0000000..89a7679
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Input Catcher"
+author = "Dialogic"
+description = "A full screen mouse input catcher for advancing dialog."
+scene = "full_advance_input_layer.tscn"
+icon = "input_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png
new file mode 100644
index 0000000..4cb5144
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import
new file mode 100644
index 0000000..9a3213e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6wifoui7fdf1"
+path="res://.godot/imported/preview.png-d5364254eab24877c66e963d1e405864.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png"
+dest_files=["res://.godot/imported/preview.png-d5364254eab24877c66e963d1e405864.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres
new file mode 100644
index 0000000..57300ef
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres
@@ -0,0 +1,13 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://cmpf1qxjh5tuw"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(1, 1, 1, 1)
+skew = Vector2(0.073, 0)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/part_config.cfg
new file mode 100644
index 0000000..e3fc33c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Textbox with Portrait "
+author = "Dialogic"
+description = "A layer with a textbox that also contains a speaker portrait."
+scene = "textbox_with_speaker_portrait.tscn"
+icon = "speaker-textbox-icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png
new file mode 100644
index 0000000..1c61ce8
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import
new file mode 100644
index 0000000..a617b1f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://s5s4lmhfnav2"
+path="res://.godot/imported/preview.png-9deb649ef7103e40149f89be77f79cb6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png"
+dest_files=["res://.godot/imported/preview.png-9deb649ef7103e40149f89be77f79cb6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg
new file mode 100644
index 0000000..02b5b02
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import
new file mode 100644
index 0000000..8bf7797
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c0v3akss47tgy"
+path="res://.godot/imported/speaker-textbox-icon.svg-13eeac1ea97708346fbc7a9ada7f4840.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg"
+dest_files=["res://.godot/imported/speaker-textbox-icon.svg-13eeac1ea97708346fbc7a9ada7f4840.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd
new file mode 100644
index 0000000..61e3714
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd
@@ -0,0 +1,129 @@
+@tool
+extends DialogicLayoutLayer
+
+enum Alignments {LEFT, CENTER, RIGHT}
+enum LimitedAlignments {LEFT=0, RIGHT=1}
+
+@export_group('Text')
+@export_subgroup("Text")
+@export var text_alignment: Alignments = Alignments.LEFT
+@export_subgroup('Size')
+@export var text_use_global_size: bool = true
+@export var text_custom_size: int = 15
+@export_subgroup('Color')
+@export var text_use_global_color: bool = true
+@export var text_custom_color: Color = Color.WHITE
+@export_subgroup('Fonts')
+@export var use_global_fonts: bool = true
+@export_file('*.ttf', '*.tres') var custom_normal_font: String = ""
+@export_file('*.ttf', '*.tres') var custom_bold_font: String = ""
+@export_file('*.ttf', '*.tres') var custom_italic_font: String = ""
+@export_file('*.ttf', '*.tres') var custom_bold_italic_font: String = ""
+
+@export_group('Name Label')
+@export_subgroup("Color")
+enum NameLabelColorModes {GLOBAL_COLOR, CHARACTER_COLOR, CUSTOM_COLOR}
+@export var name_label_color_mode: NameLabelColorModes = NameLabelColorModes.GLOBAL_COLOR
+@export var name_label_custom_color: Color = Color.WHITE
+@export_subgroup("Behaviour")
+@export var name_label_alignment: Alignments = Alignments.LEFT
+@export var name_label_hide_when_no_character: bool = false
+@export_subgroup("Font & Size")
+@export var name_label_use_global_size: bool = true
+@export var name_label_custom_size: int = 15
+@export var name_label_use_global_font: bool = true
+@export_file('*.ttf', '*.tres') var name_label_customfont: String = ""
+
+@export_group('Box')
+@export_subgroup("Box")
+@export_file('*.tres') var box_panel: String = this_folder.path_join("default_stylebox.tres")
+@export var box_modulate_global_color: bool = true
+@export var box_modulate_custom_color: Color = Color(0.47247135639191, 0.31728461384773, 0.16592600941658)
+@export var box_size: Vector2 = Vector2(600, 160)
+@export var box_distance: int = 25
+
+@export_group('Portrait')
+@export_subgroup('Portrait')
+@export var portrait_stretch_factor: float = 0.3
+@export var portrait_position: LimitedAlignments = LimitedAlignments.LEFT
+@export var portrait_bg_modulate: Color = Color(0, 0, 0, 0.5137255191803)
+
+
+## Called by dialogic whenever export overrides might change
+func _apply_export_overrides() -> void:
+ ## FONT SETTINGS
+ var dialog_text: DialogicNode_DialogText = %DialogicNode_DialogText
+ dialog_text.alignment = text_alignment as DialogicNode_DialogText.Alignment
+
+ var text_size: int = text_custom_size
+ if text_use_global_size:
+ text_size = get_global_setting(&'font_size', text_custom_size)
+
+ dialog_text.add_theme_font_size_override(&"normal_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"bold_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"italics_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"bold_italics_font_size", text_size)
+
+
+ var text_color: Color = text_custom_color
+ if text_use_global_color:
+ text_color = get_global_setting(&'font_color', text_custom_color)
+ dialog_text.add_theme_color_override(&"default_color", text_color)
+
+ var normal_font: String = custom_normal_font
+ if use_global_fonts and ResourceLoader.exists(get_global_setting(&'font', '') as String):
+ normal_font = get_global_setting(&'font', '')
+
+ if !normal_font.is_empty():
+ dialog_text.add_theme_font_override(&"normal_font", load(normal_font) as Font)
+ if !custom_bold_font.is_empty():
+ dialog_text.add_theme_font_override(&"bold_font", load(custom_bold_font) as Font)
+ if !custom_italic_font.is_empty():
+ dialog_text.add_theme_font_override(&"italics_font", load(custom_italic_font) as Font)
+ if !custom_bold_italic_font.is_empty():
+ dialog_text.add_theme_font_override(&"bold_italics_font", load(custom_bold_italic_font) as Font)
+
+ ## BOX SETTINGS
+ var panel: PanelContainer = %Panel
+ var portrait_panel: Panel = %PortraitPanel
+ if box_modulate_global_color:
+ panel.self_modulate = get_global_setting(&'bg_color', box_modulate_custom_color)
+ else:
+ panel.self_modulate = box_modulate_custom_color
+ panel.size = box_size
+ panel.position = Vector2(-box_size.x/2, -box_size.y-box_distance)
+ portrait_panel.size_flags_stretch_ratio = portrait_stretch_factor
+
+ var stylebox: StyleBox = load(box_panel)
+ panel.add_theme_stylebox_override(&'panel', stylebox)
+
+ ## PORTRAIT SETTINGS
+ var portrait_background_color: ColorRect = %PortraitBackgroundColor
+ portrait_background_color.color = portrait_bg_modulate
+
+ portrait_panel.get_parent().move_child(portrait_panel, portrait_position)
+
+ ## NAME LABEL SETTINGS
+ var name_label: DialogicNode_NameLabel = %DialogicNode_NameLabel
+ if name_label_use_global_size:
+ name_label.add_theme_font_size_override(&"font_size", get_global_setting(&'font_size', name_label_custom_size) as int)
+ else:
+ name_label.add_theme_font_size_override(&"font_size", name_label_custom_size)
+
+ var name_label_font: String = name_label_customfont
+ if name_label_use_global_font and ResourceLoader.exists(get_global_setting(&'font', '') as String):
+ name_label_font = get_global_setting(&'font', '')
+ if !name_label_font.is_empty():
+ name_label.add_theme_font_override(&'font', load(name_label_font) as Font)
+
+ name_label.use_character_color = false
+ match name_label_color_mode:
+ NameLabelColorModes.GLOBAL_COLOR:
+ name_label.add_theme_color_override(&"font_color", get_global_setting(&'font_color', name_label_custom_color) as Color)
+ NameLabelColorModes.CUSTOM_COLOR:
+ name_label.add_theme_color_override(&"font_color", name_label_custom_color)
+ NameLabelColorModes.CHARACTER_COLOR:
+ name_label.use_character_color = true
+
+ name_label.horizontal_alignment = name_label_alignment as HorizontalAlignment
+ name_label.hide_when_empty = name_label_hide_when_no_character
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn
new file mode 100644
index 0000000..369973c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn
@@ -0,0 +1,124 @@
+[gd_scene load_steps=7 format=3 uid="uid://by6waso0mjpjp"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/node_portrait_container.gd" id="1_4jxq7"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd" id="1_7jt4d"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="2_y0h34"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_11puy"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="5_sr2qw"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dmg1w"]
+bg_color = Color(0.254902, 0.254902, 0.254902, 1)
+skew = Vector2(0.073, 0)
+corner_radius_top_left = 3
+corner_radius_top_right = 3
+corner_radius_bottom_right = 3
+corner_radius_bottom_left = 3
+
+[node name="TextboxWithSpeakerPortrait" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_7jt4d")
+box_panel = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres"
+
+[node name="Anchor" type="Control" parent="."]
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 0
+mouse_filter = 2
+
+[node name="Panel" type="PanelContainer" parent="Anchor"]
+unique_name_in_owner = true
+self_modulate = Color(0.533333, 0.376471, 0.176471, 1)
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -250.0
+offset_top = -200.0
+offset_right = 250.0
+offset_bottom = -50.0
+grow_horizontal = 2
+grow_vertical = 0
+mouse_filter = 2
+
+[node name="HBox" type="HBoxContainer" parent="Anchor/Panel"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 15
+
+[node name="PortraitPanel" type="Panel" parent="Anchor/Panel/HBox"]
+unique_name_in_owner = true
+clip_children = 1
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.3
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_dmg1w")
+
+[node name="PortraitBackgroundColor" type="ColorRect" parent="Anchor/Panel/HBox/PortraitPanel"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -7.0
+offset_top = -3.0
+offset_right = 7.0
+offset_bottom = 3.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+color = Color(0, 0, 0, 0.231373)
+
+[node name="DialogicNode_PortraitContainer" type="Control" parent="Anchor/Panel/HBox/PortraitPanel/PortraitBackgroundColor"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = 4.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_4jxq7")
+mode = 1
+container_ids = PackedStringArray("1")
+debug_character_portrait = "speaker"
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Anchor/Panel/HBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 2
+
+[node name="DialogicNode_NameLabel" type="Label" parent="Anchor/Panel/HBox/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 8
+text = "Name"
+script = ExtResource("2_y0h34")
+
+[node name="DialogicNode_DialogText" type="RichTextLabel" parent="Anchor/Panel/HBox/VBoxContainer" node_paths=PackedStringArray("textbox_root")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_font_sizes/normal_font_size = 6
+bbcode_enabled = true
+text = "Some text"
+scroll_following = true
+visible_characters_behavior = 1
+script = ExtResource("3_11puy")
+textbox_root = NodePath("../../..")
+
+[node name="DialogicNode_TypeSounds" type="AudioStreamPlayer" parent="Anchor/Panel/HBox/VBoxContainer/DialogicNode_DialogText"]
+script = ExtResource("5_sr2qw")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/part_config.cfg
new file mode 100644
index 0000000..b6f8add
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Simple Text Input Box"
+author = "Dialogic"
+description = "A layer with a simple text input box."
+scene = "text_input_layer.tscn"
+icon = "text_input_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png
new file mode 100644
index 0000000..8b591b6
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import
new file mode 100644
index 0000000..87e1afa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bn2b53n51smoo"
+path="res://.godot/imported/preview.png-09839cc2bc590da2f8153bdaaa64d4db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png"
+dest_files=["res://.godot/imported/preview.png-09839cc2bc590da2f8153bdaaa64d4db.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd
new file mode 100644
index 0000000..bd853aa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd
@@ -0,0 +1,14 @@
+@tool
+extends DialogicLayoutLayer
+
+## A layer that contains a text-input node.
+
+
+func _apply_export_overrides() -> void:
+ var layer_theme: Theme = get(&'theme')
+ if layer_theme == null:
+ layer_theme = Theme.new()
+
+ if get_global_setting(&'font', ''):
+ layer_theme.default_font = load(get_global_setting(&'font', '') as String)
+ layer_theme.default_font_size = get_global_setting(&'font_size', 0)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn
new file mode 100644
index 0000000..00aac30
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn
@@ -0,0 +1,76 @@
+[gd_scene load_steps=5 format=3 uid="uid://cvgf4c6gg0tsy"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd" id="1_7ahrn"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/TextInput/node_text_input.gd" id="1_mxdep"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3dpjm"]
+content_margin_left = 15.0
+content_margin_top = 15.0
+content_margin_right = 15.0
+content_margin_bottom = 15.0
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+
+[sub_resource type="Theme" id="Theme_8xwp1"]
+
+[node name="TextInputLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_7ahrn")
+
+[node name="DialogicNode_TextInput" type="Control" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -210.0
+offset_top = -50.0
+offset_right = 210.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_mxdep")
+input_line_edit = NodePath("TextInputPanel/VBoxContainer/InputField")
+text_label = NodePath("TextInputPanel/VBoxContainer/TextLabel")
+confirmation_button = NodePath("TextInputPanel/VBoxContainer/ConfirmationButton")
+metadata/_edit_layout_mode = 1
+
+[node name="TextInputPanel" type="PanelContainer" parent="DialogicNode_TextInput"]
+unique_name_in_owner = true
+self_modulate = Color(0, 0, 0, 0.780392)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_3dpjm")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="DialogicNode_TextInput/TextInputPanel"]
+layout_mode = 2
+
+[node name="TextLabel" type="Label" parent="DialogicNode_TextInput/TextInputPanel/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme = SubResource("Theme_8xwp1")
+text = "Please enter some text:"
+autowrap_mode = 3
+
+[node name="InputField" type="LineEdit" parent="DialogicNode_TextInput/TextInputPanel/VBoxContainer"]
+layout_mode = 2
+
+[node name="ConfirmationButton" type="Button" parent="DialogicNode_TextInput/TextInputPanel/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 8
+text = "Confirm"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg
new file mode 100644
index 0000000..9f8d592
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import
new file mode 100644
index 0000000..e37e1fc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1irtbrawo1jp"
+path="res://.godot/imported/text_input_layer_icon.svg-407b04da3b880940c215bcac3ab4155e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg"
+dest_files=["res://.godot/imported/text_input_layer_icon.svg-407b04da3b880940c215bcac3ab4155e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/part_config.cfg
new file mode 100644
index 0000000..203a685
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Textbubble Layer"
+author = "Dialogic"
+description = "A simple textbubble layer. Expects a textbubble base. Each textbubble provides a name label, dialog text and choices."
+scene = "text_bubble_layer.tscn"
+icon = "text_bubble_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png
new file mode 100644
index 0000000..54b08d6
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import
new file mode 100644
index 0000000..0c843fa
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://x0kxo1qd4jqf"
+path="res://.godot/imported/preview.png-520633dce0bbab60b1681bdf7af02cb2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png"
+dest_files=["res://.godot/imported/preview.png-520633dce0bbab60b1681bdf7af02cb2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader
new file mode 100644
index 0000000..c1e348f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader
@@ -0,0 +1,17 @@
+shader_type canvas_item;
+
+uniform sampler2D deformation_sampler : filter_linear, repeat_enable;
+uniform float radius :hint_range(1.0, 200, 0.01)= 25;
+uniform vec2 box_size = vec2(100, 100);
+uniform float box_padding = 15;
+uniform float wobble_amount : hint_range(0.0, 1.0, 0.01) = 0.2;
+uniform float wobble_speed : hint_range(0.0, 10.0, 0.01) = 1;
+uniform float wobble_detail : hint_range(0.01, 1, 0.01) = 0.5;
+
+void fragment() {
+ float adjusted_radius = min(min(radius, box_size.x/2.0), box_size.y/2.0);
+ vec2 deformation_sample = texture(deformation_sampler, UV*wobble_detail+TIME*wobble_speed*0.05).xy*(vec2(box_padding)/box_size)*0.9;
+ vec2 deformed_UV = UV+((deformation_sample)-vec2(0.5)*vec2(box_padding)/box_size)*wobble_amount;
+ float rounded_box = length(max(abs(deformed_UV*(box_size+vec2(box_padding))-vec2(0.5)*(box_size+vec2(box_padding)))+adjusted_radius-vec2(0.5)*box_size,0))-adjusted_radius;
+ COLOR.a = min(smoothstep(0.0, -1, rounded_box), COLOR.a);
+}
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd
new file mode 100644
index 0000000..669c2e2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd
@@ -0,0 +1,202 @@
+extends Control
+
+@onready var tail: Line2D = ($Group/Tail as Line2D)
+@onready var bubble: Control = ($Group/Background as Control)
+@onready var text: DialogicNode_DialogText = (%DialogText as DialogicNode_DialogText)
+# The choice container is added by the TextBubble layer
+@onready var choice_container: Container = null
+@onready var name_label: Label = (%NameLabel as Label)
+@onready var name_label_box: PanelContainer = (%NameLabelPanel as PanelContainer)
+@onready var name_label_holder: HBoxContainer = $DialogText/NameLabelPositioner
+
+var node_to_point_at: Node = null
+var current_character: DialogicCharacter = null
+
+var max_width := 300
+
+var bubble_rect: Rect2 = Rect2(0.0, 0.0, 2.0, 2.0)
+var base_position := Vector2.ZERO
+
+var base_direction := Vector2(1.0, -1.0).normalized()
+var safe_zone := 50.0
+var padding := Vector2()
+
+var name_label_alignment := HBoxContainer.ALIGNMENT_BEGIN
+var name_label_offset := Vector2()
+var force_choices_on_separate_lines := false
+
+# Sets the padding shader paramter.
+# It's the amount of spacing around the background to allow some wobbeling.
+var bg_padding := 30
+
+
+func _ready() -> void:
+ reset()
+ DialogicUtil.autoload().Choices.question_shown.connect(_on_question_shown)
+
+
+func reset() -> void:
+ scale = Vector2.ZERO
+ modulate.a = 0.0
+
+ tail.points = []
+ bubble_rect = Rect2(0,0,2,2)
+
+ base_position = get_speaker_canvas_position()
+ position = base_position
+
+
+func _process(delta:float) -> void:
+ base_position = get_speaker_canvas_position()
+
+ var center := get_viewport_rect().size / 2.0
+
+ var dist_x := absf(base_position.x - center.x)
+ var dist_y := absf(base_position.y - center.y)
+ var x_e := center.x - bubble_rect.size.x
+ var y_e := center.y - bubble_rect.size.y
+ var influence_x := remap(clamp(dist_x, x_e, center.x), x_e, center.x * 0.8, 0.0, 1.0)
+ var influence_y := remap(clamp(dist_y, y_e, center.y), y_e, center.y * 0.8, 0.0, 1.0)
+ if base_position.x > center.x: influence_x = -influence_x
+ if base_position.y > center.y: influence_y = -influence_y
+ var edge_influence := Vector2(influence_x, influence_y)
+
+ var direction := (base_direction + edge_influence).normalized()
+
+ var p: Vector2 = base_position + direction * (
+ safe_zone + lerp(bubble_rect.size.y, bubble_rect.size.x, abs(direction.x)) * 0.4
+ )
+ p = p.clamp(bubble_rect.size / 2.0, get_viewport_rect().size - bubble_rect.size / 2.0)
+
+ position = position.lerp(p, 5 * delta)
+
+ var point_a: Vector2 = Vector2.ZERO
+ var point_b: Vector2 = (base_position - position) * 0.75
+
+ var offset: Vector2 = Vector2.from_angle(point_a.angle_to_point(point_b)) * bubble_rect.size * abs(direction.x) * 0.4
+
+ point_a += offset
+ point_b += offset * 0.5
+
+ var curve := Curve2D.new()
+ var direction_point := Vector2(0, (point_b.y - point_a.y))
+ curve.add_point(point_a, Vector2.ZERO, direction_point * 0.5)
+ curve.add_point(point_b)
+ tail.points = curve.tessellate(5)
+ tail.width = bubble_rect.size.x * 0.15
+
+
+func open() -> void:
+ set_process(true)
+ show()
+ text.enabled = true
+ var open_tween := create_tween().set_parallel(true)
+ open_tween.tween_property(self, "scale", Vector2.ONE, 0.1).from(Vector2.ZERO)
+ open_tween.tween_property(self, "modulate:a", 1.0, 0.1).from(0.0)
+
+
+func close() -> void:
+ text.enabled = false
+ var close_tween := create_tween().set_parallel(true)
+ close_tween.tween_property(self, "scale", Vector2.ONE * 0.8, 0.2)
+ close_tween.tween_property(self, "modulate:a", 0.0, 0.2)
+ await close_tween.finished
+ hide()
+ set_process(false)
+
+
+func _on_dialog_text_started_revealing_text() -> void:
+ _resize_bubble(get_base_content_size(), true)
+
+
+func _resize_bubble(content_size:Vector2, popup:=false) -> void:
+ var bubble_size: Vector2 = content_size+(padding*2)+Vector2.ONE*bg_padding
+ var half_size: Vector2= (bubble_size / 2.0)
+ bubble.pivot_offset = half_size
+ bubble_rect = Rect2(position, bubble_size * Vector2(1.1, 1.1))
+ bubble.position = -half_size
+ bubble.size = bubble_size
+
+ text.size = content_size
+ text.position = -(content_size/2.0)
+
+ if popup:
+ var t := create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
+ t.tween_property(bubble, "scale", Vector2.ONE, 0.2).from(Vector2.ZERO)
+ else:
+ bubble.scale = Vector2.ONE
+
+ bubble.material.set(&"shader_parameter/box_size", bubble_size)
+ name_label_holder.position = Vector2(0, bubble.position.y - text.position.y - name_label_holder.size.y/2.0)
+ name_label_holder.position += name_label_offset
+ name_label_holder.alignment = name_label_alignment
+ name_label_holder.size.x = text.size.x
+
+
+func _on_question_shown(info:Dictionary) -> void:
+ if !is_visible_in_tree():
+ return
+
+ await get_tree().process_frame
+
+ var content_size := get_base_content_size()
+ content_size.y += choice_container.size.y
+ content_size.x = max(content_size.x, choice_container.size.x)
+ _resize_bubble(content_size)
+
+
+func get_base_content_size() -> Vector2:
+ var font: Font = text.get_theme_font(&"normal_font")
+ return font.get_multiline_string_size(
+ text.get_parsed_text(),
+ HORIZONTAL_ALIGNMENT_LEFT,
+ max_width,
+ text.get_theme_font_size(&"normal_font_size")
+ )
+
+
+func add_choice_container(node:Container, alignment:=FlowContainer.ALIGNMENT_BEGIN) -> void:
+ if choice_container:
+ choice_container.get_parent().remove_child(choice_container)
+ choice_container.queue_free()
+
+ node.name = "ChoiceContainer"
+ choice_container = node
+ node.set_anchors_preset(LayoutPreset.PRESET_BOTTOM_WIDE)
+ node.grow_vertical = Control.GROW_DIRECTION_BEGIN
+ text.add_child(node)
+
+ if node is HFlowContainer:
+ (node as HFlowContainer).alignment = alignment
+
+ for i:int in range(5):
+ choice_container.add_child(DialogicNode_ChoiceButton.new())
+ if node is HFlowContainer:
+ continue
+ match alignment:
+ HBoxContainer.ALIGNMENT_BEGIN:
+ (choice_container.get_child(-1) as Control).size_flags_horizontal = SIZE_SHRINK_BEGIN
+ HBoxContainer.ALIGNMENT_CENTER:
+ (choice_container.get_child(-1) as Control).size_flags_horizontal = SIZE_SHRINK_CENTER
+ HBoxContainer.ALIGNMENT_END:
+ (choice_container.get_child(-1) as Control).size_flags_horizontal = SIZE_SHRINK_END
+
+ for child:Button in choice_container.get_children():
+ var prev := child.get_parent().get_child(wrap(child.get_index()-1, 0, choice_container.get_child_count()-1)).get_path()
+ var next := child.get_parent().get_child(wrap(child.get_index()+1, 0, choice_container.get_child_count()-1)).get_path()
+ child.focus_next = next
+ child.focus_previous = prev
+ child.focus_neighbor_left = prev
+ child.focus_neighbor_top = prev
+ child.focus_neighbor_right = next
+ child.focus_neighbor_bottom = next
+
+
+func get_speaker_canvas_position() -> Vector2:
+ if is_instance_valid(node_to_point_at):
+ if node_to_point_at is Node3D:
+ base_position = get_viewport().get_camera_3d().unproject_position(
+ (node_to_point_at as Node3D).global_position)
+ if node_to_point_at is CanvasItem:
+ base_position = (node_to_point_at as CanvasItem).get_global_transform_with_canvas().origin
+ return base_position
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader
new file mode 100644
index 0000000..60ebcee
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader
@@ -0,0 +1,17 @@
+shader_type canvas_item;
+
+uniform sampler2D deformation_sampler : filter_linear, repeat_enable;
+uniform float radius : hint_range(1.0, 200, 0.01) = 25;
+uniform vec2 box_size = vec2(100, 100);
+uniform float box_padding = 15;
+uniform float wobble_amount : hint_range(0.0, 1.0, 0.01) = 0.2;
+uniform float wobble_speed : hint_range(0.0, 10.0, 0.01) = 1;
+uniform float wobble_detail : hint_range(0.01, 1, 0.01) = 0.5;
+
+void fragment() {
+ float adjusted_radius = min(min(radius, box_size.x/2.0), box_size.y/2.0);
+ vec2 deformation_sample = texture(deformation_sampler, UV*wobble_detail+TIME*wobble_speed*0.05).xy*(vec2(box_padding)/box_size)*0.9;
+ vec2 deformed_UV = UV+((deformation_sample)-vec2(0.5)*vec2(box_padding)/box_size)*wobble_amount;
+ float rounded_box = length(max(abs(deformed_UV*(box_size+vec2(box_padding))-vec2(0.5)*(box_size+vec2(box_padding)))+adjusted_radius-vec2(0.5)*box_size,0))-adjusted_radius;
+ COLOR.a = min(smoothstep(0.0, -1, rounded_box), COLOR.a);
+}
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn
new file mode 100644
index 0000000..6277c5f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn
@@ -0,0 +1,111 @@
+[gd_scene load_steps=11 format=3 uid="uid://dlx7jcvm52tyw"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd" id="1_jdhpk"]
+[ext_resource type="Shader" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader" id="2_1mhvf"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_syv35"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="4_7bm4b"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="6_5gd03"]
+
+[sub_resource type="Curve" id="Curve_0j8nu"]
+_data = [Vector2(0, 1), 0.0, -1.0, 0, 1, Vector2(1, 0), -1.0, 0.0, 1, 0]
+point_count = 2
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_lsfnp"]
+noise_type = 0
+fractal_type = 0
+cellular_jitter = 0.15
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_kr7hw"]
+seamless = true
+noise = SubResource("FastNoiseLite_lsfnp")
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_60xbe"]
+resource_local_to_scene = true
+shader = ExtResource("2_1mhvf")
+shader_parameter/radius = 200.0
+shader_parameter/box_size = Vector2(100, 100)
+shader_parameter/box_padding = 10.0
+shader_parameter/wobble_amount = 0.75
+shader_parameter/wobble_speed = 10.0
+shader_parameter/wobble_detail = 0.51
+shader_parameter/deformation_sampler = SubResource("NoiseTexture2D_kr7hw")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h6ls0"]
+content_margin_left = 5.0
+content_margin_right = 5.0
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+shadow_color = Color(0.152941, 0.152941, 0.152941, 0.12549)
+shadow_size = 5
+
+[node name="TextBubble" type="Control"]
+layout_mode = 3
+anchors_preset = 0
+script = ExtResource("1_jdhpk")
+
+[node name="Group" type="CanvasGroup" parent="."]
+
+[node name="Tail" type="Line2D" parent="Group"]
+unique_name_in_owner = true
+points = PackedVector2Array(-9, 7, -29, 118, -95, 174, -193, 195)
+width = 96.0
+width_curve = SubResource("Curve_0j8nu")
+
+[node name="Background" type="ColorRect" parent="Group"]
+unique_name_in_owner = true
+material = SubResource("ShaderMaterial_60xbe")
+offset_left = -115.0
+offset_top = -69.0
+offset_right = 108.0
+offset_bottom = 83.0
+mouse_filter = 2
+
+[node name="DialogText" type="RichTextLabel" parent="." node_paths=PackedStringArray("textbox_root")]
+unique_name_in_owner = true
+clip_contents = false
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -53.0
+offset_top = -13.0
+offset_right = 53.0
+offset_bottom = 12.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/default_color = Color(0, 0, 0, 1)
+scroll_active = false
+visible_characters_behavior = 1
+script = ExtResource("3_syv35")
+textbox_root = NodePath("..")
+
+[node name="DialogicNode_TypeSounds" type="AudioStreamPlayer" parent="DialogText"]
+script = ExtResource("4_7bm4b")
+
+[node name="NameLabelPositioner" type="HBoxContainer" parent="DialogText"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 23.0
+grow_horizontal = 2
+alignment = 1
+
+[node name="NameLabelPanel" type="PanelContainer" parent="DialogText/NameLabelPositioner"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_h6ls0")
+
+[node name="NameLabel" type="Label" parent="DialogText/NameLabelPositioner/NameLabelPanel" node_paths=PackedStringArray("name_label_root")]
+unique_name_in_owner = true
+layout_mode = 2
+horizontal_alignment = 1
+script = ExtResource("6_5gd03")
+name_label_root = NodePath("..")
+use_character_color = false
+
+[connection signal="started_revealing_text" from="DialogText" to="." method="_on_dialog_text_started_revealing_text"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd
new file mode 100644
index 0000000..2764474
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd
@@ -0,0 +1,185 @@
+@tool
+extends DialogicLayoutLayer
+
+## This layout won't do anything on its own
+
+@export_group("Main")
+@export_subgroup("Text")
+@export var text_size: int = 15
+@export var text_color: Color = Color.BLACK
+@export_file('*.ttf') var normal_font: String = ""
+@export_file('*.ttf') var bold_font: String = ""
+@export_file('*.ttf') var italic_font: String = ""
+@export_file('*.ttf') var bold_italic_font: String = ""
+@export var text_max_width: int = 300
+
+@export_subgroup('Box')
+@export var box_modulate: Color = Color.WHITE
+@export var box_modulate_by_character_color: bool = false
+@export var box_padding: Vector2 = Vector2(10,10)
+@export_range(1, 999) var box_corner_radius: int = 25
+@export_range(0.1, 5) var box_wobble_speed: float = 1
+@export_range(0, 1) var box_wobble_amount: float = 0.5
+@export_range(0, 1) var box_wobble_detail: float = 0.2
+
+@export_subgroup('Behaviour')
+@export var behaviour_distance: int = 50
+@export var behaviour_direction: Vector2 = Vector2(1, -1)
+
+@export_group('Name Label')
+@export_subgroup("Name Label")
+@export var name_label_enabled: bool = true
+@export var name_label_font_size: int = 15
+@export_file('*.ttf') var name_label_font: String = ""
+@export var name_label_use_character_color: bool = true
+@export var name_label_color: Color = Color.BLACK
+@export_subgroup("Name Label Box")
+@export var name_label_box_modulate: Color = Color.WHITE
+@export var name_label_box_modulate_use_character_color: bool = false
+@export var name_label_padding: Vector2 = Vector2(5,0)
+@export var name_label_offset: Vector2 = Vector2(0,0)
+@export var name_label_alignment := HBoxContainer.ALIGNMENT_BEGIN
+
+
+@export_group('Choices')
+@export_subgroup('Choices Text')
+@export var choices_text_size: int = 15
+@export_file('*.ttf') var choices_text_font: String = ""
+@export var choices_text_color: Color = Color.DARK_SLATE_GRAY
+@export var choices_text_color_hover: Color = Color.DARK_MAGENTA
+@export var choices_text_color_focus: Color = Color.DARK_MAGENTA
+@export var choices_text_color_disabled: Color = Color.DARK_GRAY
+
+@export_subgroup('Choices Layout')
+@export var choices_layout_alignment := FlowContainer.ALIGNMENT_END
+@export var choices_layout_force_lines: bool = false
+@export_file('*.tres', "*.res") var choices_base_theme: String = ""
+
+const TextBubble := preload("res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd")
+
+var bubbles: Array[TextBubble] = []
+var fallback_bubble: TextBubble = null
+
+const textbubble_scene: PackedScene = preload("res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn")
+
+
+func add_bubble() -> TextBubble:
+ var new_bubble: TextBubble = textbubble_scene.instantiate()
+ add_child(new_bubble)
+ bubbles.append(new_bubble)
+ return new_bubble
+
+
+## Called by dialogic whenever export overrides might change
+func _apply_export_overrides() -> void:
+ pass
+
+
+
+## Called by the base layer before opening the bubble
+func bubble_apply_overrides(bubble:TextBubble) -> void:
+ ## TEXT FONT AND COLOR
+ var rtl: RichTextLabel = bubble.text
+ rtl.add_theme_font_size_override(&'normal_font', text_size)
+ rtl.add_theme_font_size_override(&"normal_font_size", text_size)
+ rtl.add_theme_font_size_override(&"bold_font_size", text_size)
+ rtl.add_theme_font_size_override(&"italics_font_size", text_size)
+ rtl.add_theme_font_size_override(&"bold_italics_font_size", text_size)
+
+ rtl.add_theme_color_override(&"default_color", text_color)
+
+ if !normal_font.is_empty():
+ rtl.add_theme_font_override(&"normal_font", load(normal_font) as Font)
+ if !bold_font.is_empty():
+ rtl.add_theme_font_override(&"bold_font", load(bold_font) as Font)
+ if !italic_font.is_empty():
+ rtl.add_theme_font_override(&"italitc_font", load(italic_font) as Font)
+ if !bold_italic_font.is_empty():
+ rtl.add_theme_font_override(&"bold_italics_font", load(bold_italic_font) as Font)
+ bubble.set(&'max_width', text_max_width)
+
+
+ ## BOX & TAIL COLOR
+ var tail_and_bg_group := (bubble.get_node("Group") as CanvasGroup)
+ tail_and_bg_group.self_modulate = box_modulate
+ if box_modulate_by_character_color and bubble.current_character != null:
+ tail_and_bg_group.self_modulate = bubble.current_character.color
+
+ var background := (bubble.get_node('%Background') as ColorRect)
+ var bg_material: ShaderMaterial = (background.material as ShaderMaterial)
+ bg_material.set_shader_parameter(&'radius', box_corner_radius)
+ bg_material.set_shader_parameter(&'wobble_amount', box_wobble_amount)
+ bg_material.set_shader_parameter(&'wobble_speed', box_wobble_speed)
+ bg_material.set_shader_parameter(&'wobble_detail', box_wobble_detail)
+
+ bubble.padding = box_padding
+
+
+ ## BEHAVIOUR
+ bubble.safe_zone = behaviour_distance
+ bubble.base_direction = behaviour_direction
+
+
+ ## NAME LABEL SETTINGS
+ var nl: DialogicNode_NameLabel = bubble.name_label
+ nl.add_theme_font_size_override(&"font_size", name_label_font_size)
+
+ if !name_label_font.is_empty():
+ nl.add_theme_font_override(&'font', load(name_label_font) as Font)
+
+
+ if name_label_use_character_color and bubble.current_character:
+ nl.add_theme_color_override(&"font_color", bubble.current_character.color)
+ else:
+ nl.add_theme_color_override(&"font_color", name_label_color)
+
+ var nlp: PanelContainer = bubble.name_label_box
+ nlp.self_modulate = name_label_box_modulate
+ if name_label_box_modulate_use_character_color and bubble.current_character:
+ nlp.self_modulate = bubble.current_character.color
+ nlp.get_theme_stylebox(&'panel').content_margin_left = name_label_padding.x
+ nlp.get_theme_stylebox(&'panel').content_margin_right = name_label_padding.x
+ nlp.get_theme_stylebox(&'panel').content_margin_top = name_label_padding.y
+ nlp.get_theme_stylebox(&'panel').content_margin_bottom = name_label_padding.y
+ bubble.name_label_offset = name_label_offset
+ bubble.name_label_alignment = name_label_alignment
+
+ nlp.get_parent().visible = name_label_enabled
+
+ ## CHOICE SETTINGS
+ if choices_layout_force_lines:
+ bubble.add_choice_container(VBoxContainer.new(), choices_layout_alignment)
+ else:
+ bubble.add_choice_container(HFlowContainer.new(), choices_layout_alignment)
+
+ var choice_theme: Theme = null
+ if choices_base_theme.is_empty() or not ResourceLoader.exists(choices_base_theme):
+ choice_theme = Theme.new()
+ var base_style := StyleBoxFlat.new()
+ base_style.draw_center = false
+ base_style.border_width_bottom = 2
+ base_style.border_color = choices_text_color
+ choice_theme.set_stylebox(&'normal', &'Button', base_style)
+ var focus_style := (base_style.duplicate() as StyleBoxFlat)
+ focus_style.border_color = choices_text_color_focus
+ choice_theme.set_stylebox(&'focus', &'Button', focus_style)
+ var hover_style := (base_style.duplicate() as StyleBoxFlat)
+ hover_style.border_color = choices_text_color_hover
+ choice_theme.set_stylebox(&'hover', &'Button', hover_style)
+ var disabled_style := (base_style.duplicate() as StyleBoxFlat)
+ disabled_style.border_color = choices_text_color_disabled
+ choice_theme.set_stylebox(&'disabled', &'Button', disabled_style)
+ choice_theme.set_stylebox(&'pressed', &'Button', base_style)
+ else:
+ choice_theme = (load(choices_base_theme) as Theme)
+
+ if !choices_text_font.is_empty():
+ choice_theme.default_font = (load(choices_text_font) as Font)
+
+ choice_theme.set_font_size(&'font_size', &'Button', choices_text_size)
+ choice_theme.set_color(&'font_color', &'Button', choices_text_color)
+ choice_theme.set_color(&'font_pressed_color', &'Button', choices_text_color)
+ choice_theme.set_color(&'font_hover_color', &'Button', choices_text_color_hover)
+ choice_theme.set_color(&'font_focus_color', &'Button', choices_text_color_focus)
+ choice_theme.set_color(&'font_disabled_color', &'Button', choices_text_color_disabled)
+ bubble.choice_container.theme = choice_theme
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn
new file mode 100644
index 0000000..5909325
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=3 uid="uid://d2it0xiap3gnt"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd" id="1_b37je"]
+
+[node name="TextBubbleLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 0
+mouse_filter = 2
+script = ExtResource("1_b37je")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg
new file mode 100644
index 0000000..da53086
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import
new file mode 100644
index 0000000..a36316b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dgpyaea4qw8a5"
+path="res://.godot/imported/text_bubble_layer_icon.svg-1fd28851b00f5b373a0f6e85c1d43a95.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg"
+dest_files=["res://.godot/imported/text_bubble_layer_icon.svg-1fd28851b00f5b373a0f6e85c1d43a95.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres
new file mode 100644
index 0000000..8293f0c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres
@@ -0,0 +1,15 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://bu0tsjabpj4rd"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 5.0
+content_margin_right = 10.0
+content_margin_bottom = 5.0
+bg_color = Color(0, 0, 0, 0.956863)
+draw_center = false
+border_width_left = 5
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 5.0
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres
new file mode 100644
index 0000000..b67de21
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres
@@ -0,0 +1,18 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://xs2s6euq5stw"]
+
+[resource]
+content_margin_top = 5.0
+content_margin_bottom = 5.0
+bg_color = Color(0, 0, 0, 0.956863)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
+expand_margin_left = 1.0
+expand_margin_top = 1.0
+expand_margin_right = 1.0
+expand_margin_bottom = 1.0
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres
new file mode 100644
index 0000000..1fe561b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres
@@ -0,0 +1,12 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://wrp8f7ard3uu"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 5.0
+content_margin_right = 10.0
+content_margin_bottom = 5.0
+bg_color = Color(0, 0, 0, 0.941176)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg
new file mode 100644
index 0000000..bc00f8f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import
new file mode 100644
index 0000000..e17931a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://xcxex6r1v6xk"
+path="res://.godot/imported/choices_layer_icon.svg-1decdccfae336e540158a3074780f89c.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg"
+dest_files=["res://.godot/imported/choices_layer_icon.svg-1decdccfae336e540158a3074780f89c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/part_config.cfg
new file mode 100644
index 0000000..8dbc1cd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Centered Choices"
+author = "Dialogic"
+description = "A layer containing simple centered choices."
+scene = "vn_choice_layer.tscn"
+icon = "choices_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png
new file mode 100644
index 0000000..20879db
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import
new file mode 100644
index 0000000..0aed7a7
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://h1qtatxnadhj"
+path="res://.godot/imported/preview.png-d96a319e95e9ba944d347329d3c08505.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png"
+dest_files=["res://.godot/imported/preview.png-d96a319e95e9ba944d347329d3c08505.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd
new file mode 100644
index 0000000..97195f5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd
@@ -0,0 +1,122 @@
+@tool
+extends DialogicLayoutLayer
+
+## A layer that allows showing up to 10 choices.
+## Choices are positioned in the center of the screen.
+
+@export_group("Text")
+@export_subgroup('Font')
+@export var font_use_global: bool = true
+@export_file('*.ttf', '*.tres') var font_custom: String = ""
+@export_subgroup('Size')
+@export var font_size_use_global: bool = true
+@export var font_size_custom: int = 16
+@export_subgroup('Color')
+@export var text_color_use_global: bool = true
+@export var text_color_custom: Color = Color.WHITE
+@export var text_color_pressed: Color = Color.WHITE
+@export var text_color_hovered: Color = Color.GRAY
+@export var text_color_disabled: Color = Color.DARK_GRAY
+@export var text_color_focused: Color = Color.WHITE
+
+@export_group('Boxes')
+@export_subgroup('Panels')
+@export_file('*.tres') var boxes_stylebox_normal: String = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres"
+@export_file('*.tres') var boxes_stylebox_hovered: String = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres"
+@export_file('*.tres') var boxes_stylebox_pressed: String = ""
+@export_file('*.tres') var boxes_stylebox_disabled: String = ""
+@export_file('*.tres') var boxes_stylebox_focused: String = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres"
+@export_subgroup('Modulate')
+@export_subgroup('Size & Position')
+@export var boxes_v_separation: int = 10
+@export var boxes_fill_width: bool = true
+@export var boxes_min_size: Vector2 = Vector2()
+@export var boxes_offset: Vector2 = Vector2()
+
+@export_group('Sounds')
+@export_range(-80, 24, 0.01) var sounds_volume: float = -10
+@export_file("*.wav", "*.ogg", "*.mp3") var sounds_pressed: String = "res://addons/dialogic/Example Assets/sound-effects/typing1.wav"
+@export_file("*.wav", "*.ogg", "*.mp3") var sounds_hover: String = "res://addons/dialogic/Example Assets/sound-effects/typing2.wav"
+@export_file("*.wav", "*.ogg", "*.mp3") var sounds_focus: String = "res://addons/dialogic/Example Assets/sound-effects/typing4.wav"
+
+func get_choices() -> VBoxContainer:
+ return $Choices
+
+
+func get_button_sound() -> DialogicNode_ButtonSound:
+ return %DialogicNode_ButtonSound
+
+
+## Method that applies all exported settings
+func _apply_export_overrides() -> void:
+ # apply text settings
+ var layer_theme: Theme = Theme.new()
+
+ # font
+ if font_use_global and get_global_setting(&'font', false):
+ layer_theme.set_font(&'font', &'Button', load(get_global_setting(&'font', '') as String) as Font)
+ elif ResourceLoader.exists(font_custom):
+ layer_theme.set_font(&'font', &'Button', load(font_custom) as Font)
+
+ # font size
+ if font_size_use_global:
+ layer_theme.set_font_size(&'font_size', &'Button', get_global_setting(&'font_size', font_size_custom) as int)
+ else:
+ layer_theme.set_font_size(&'font_size', &'Button', font_size_custom)
+
+ # font color
+ if text_color_use_global:
+ layer_theme.set_color(&'font_color', &'Button', get_global_setting(&'font_color', text_color_custom) as Color)
+ else:
+ layer_theme.set_color(&'font_color', &'Button', text_color_custom)
+
+ layer_theme.set_color(&'font_pressed_color', &'Button', text_color_pressed)
+ layer_theme.set_color(&'font_hover_color', &'Button', text_color_hovered)
+ layer_theme.set_color(&'font_disabled_color', &'Button', text_color_disabled)
+ layer_theme.set_color(&'font_pressed_color', &'Button', text_color_pressed)
+ layer_theme.set_color(&'font_focus_color', &'Button', text_color_focused)
+
+
+ # apply box settings
+ if ResourceLoader.exists(boxes_stylebox_normal):
+ var style_box: StyleBox = load(boxes_stylebox_normal)
+ layer_theme.set_stylebox(&'normal', &'Button', style_box)
+ layer_theme.set_stylebox(&'hover', &'Button', style_box)
+ layer_theme.set_stylebox(&'pressed', &'Button', style_box)
+ layer_theme.set_stylebox(&'disabled', &'Button', style_box)
+ layer_theme.set_stylebox(&'focus', &'Button', style_box)
+
+ if ResourceLoader.exists(boxes_stylebox_hovered):
+ layer_theme.set_stylebox(&'hover', &'Button', load(boxes_stylebox_hovered) as StyleBox)
+
+ if ResourceLoader.exists(boxes_stylebox_pressed):
+ layer_theme.set_stylebox(&'pressed', &'Button', load(boxes_stylebox_pressed) as StyleBox)
+ if ResourceLoader.exists(boxes_stylebox_disabled):
+ layer_theme.set_stylebox(&'disabled', &'Button', load(boxes_stylebox_disabled) as StyleBox)
+ if ResourceLoader.exists(boxes_stylebox_focused):
+ layer_theme.set_stylebox(&'focus', &'Button', load(boxes_stylebox_focused) as StyleBox)
+
+ get_choices().add_theme_constant_override(&"separation", boxes_v_separation)
+ self.position = boxes_offset
+
+ for child: Node in get_choices().get_children():
+ if not child is DialogicNode_ChoiceButton:
+ continue
+ var choice: DialogicNode_ChoiceButton = child as DialogicNode_ChoiceButton
+
+ if boxes_fill_width:
+ choice.size_flags_horizontal = Control.SIZE_FILL
+ else:
+ choice.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
+
+ choice.custom_minimum_size = boxes_min_size
+
+
+ set(&'theme', layer_theme)
+
+ # apply sound settings
+ var button_sound: DialogicNode_ButtonSound = get_button_sound()
+ button_sound.volume_db = sounds_volume
+ button_sound.sound_pressed = load(sounds_pressed)
+ button_sound.sound_hover = load(sounds_hover)
+ button_sound.sound_focus = load(sounds_focus)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn
new file mode 100644
index 0000000..7c9fb00
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn
@@ -0,0 +1,98 @@
+[gd_scene load_steps=7 format=3 uid="uid://dhk6j6eb6e3q"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd" id="1_kurgw"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/node_choice_button.gd" id="1_w632k"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/node_button_sound.gd" id="2_mgko6"]
+[ext_resource type="AudioStream" uid="uid://b6c1p14bc20p1" path="res://addons/dialogic/Example Assets/sound-effects/typing1.wav" id="3_mql8i"]
+[ext_resource type="AudioStream" uid="uid://c2viukvbub6v6" path="res://addons/dialogic/Example Assets/sound-effects/typing4.wav" id="4_420fr"]
+
+[sub_resource type="AudioStream" id="AudioStream_pe27w"]
+
+[node name="VN_ChoiceLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_kurgw")
+
+[node name="Choices" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -41.0
+offset_top = -47.0
+offset_right = 42.0
+offset_bottom = 47.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+alignment = 1
+metadata/_edit_layout_mode = 1
+
+[node name="DialogicNode_ChoiceButton1" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton2" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton3" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton4" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton5" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton6" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton7" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton8" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton9" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton10" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ChoiceButton11" type="Button" parent="Choices"]
+layout_mode = 2
+text = "Some text"
+script = ExtResource("1_w632k")
+
+[node name="DialogicNode_ButtonSound" type="AudioStreamPlayer" parent="Choices"]
+unique_name_in_owner = true
+script = ExtResource("2_mgko6")
+sound_pressed = ExtResource("3_mql8i")
+sound_hover = ExtResource("4_420fr")
+sound_focus = SubResource("AudioStream_pe27w")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/part_config.cfg
new file mode 100644
index 0000000..fb709b5
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "5 Portraits"
+author = "Dialogic"
+description = "A layer with 5 portrait position containers."
+scene = "vn_portrait_layer.tscn"
+icon = "portrait_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg
new file mode 100644
index 0000000..9e00fc6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import
new file mode 100644
index 0000000..8e7234c
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://fwi64s4gbob2"
+path="res://.godot/imported/portrait_layer_icon.svg-d45bd6267c5f5eeb39913b4d69521257.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg"
+dest_files=["res://.godot/imported/portrait_layer_icon.svg-d45bd6267c5f5eeb39913b4d69521257.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png
new file mode 100644
index 0000000..3c75bd8
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import
new file mode 100644
index 0000000..1572f75
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ypmfci4n2abt"
+path="res://.godot/imported/preview.png-72fc684be4bf9d6bbc7833ccb568ca8c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png"
+dest_files=["res://.godot/imported/preview.png-72fc684be4bf9d6bbc7833ccb568ca8c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd
new file mode 100644
index 0000000..4844cc8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd
@@ -0,0 +1,15 @@
+@tool
+extends DialogicLayoutLayer
+
+## A layer that allows showing 5 portraits, like in a visual novel.
+
+## The canvas layer that the portraits are on.
+@export var portrait_size_mode: DialogicNode_PortraitContainer.SizeModes = DialogicNode_PortraitContainer.SizeModes.FIT_SCALE_HEIGHT
+
+
+func _apply_export_overrides() -> void:
+ # apply portrait size
+ for child: DialogicNode_PortraitContainer in %Portraits.get_children():
+ child.size_mode = portrait_size_mode
+ child.update_portrait_transforms()
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn
new file mode 100644
index 0000000..bb803ee
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn
@@ -0,0 +1,84 @@
+[gd_scene load_steps=3 format=3 uid="uid://cy1y14inwkplb"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd" id="1_1i7em"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/node_portrait_container.gd" id="1_rxdcc"]
+
+[node name="VN_PortraitLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_1i7em")
+
+[node name="Portraits" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+
+[node name="DialogicNode_PortraitContainer1" type="Control" parent="Portraits"]
+layout_mode = 1
+anchor_right = 0.2
+anchor_bottom = 1.0
+offset_right = -1.52588e-05
+grow_vertical = 2
+pivot_offset = Vector2(115.2, 648)
+mouse_filter = 2
+script = ExtResource("1_rxdcc")
+container_ids = PackedStringArray("leftmost", "0")
+metadata/_edit_use_anchors_ = true
+
+[node name="DialogicNode_PortraitContainer2" type="Control" parent="Portraits"]
+layout_mode = 1
+anchor_left = 0.2
+anchor_right = 0.4
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_rxdcc")
+container_ids = PackedStringArray("left", "1")
+metadata/_edit_use_anchors_ = true
+
+[node name="DialogicNode_PortraitContainer3" type="Control" parent="Portraits"]
+layout_mode = 1
+anchor_left = 0.4
+anchor_right = 0.6
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_rxdcc")
+container_ids = PackedStringArray("center", "middle", "2")
+metadata/_edit_use_anchors_ = true
+
+[node name="DialogicNode_PortraitContainer4" type="Control" parent="Portraits"]
+layout_mode = 1
+anchor_left = 0.6
+anchor_right = 0.8
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_rxdcc")
+container_ids = PackedStringArray("right", "3")
+metadata/_edit_use_anchors_ = true
+
+[node name="DialogicNode_PortraitContainer5" type="Control" parent="Portraits"]
+layout_mode = 1
+anchor_left = 0.8
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_rxdcc")
+container_ids = PackedStringArray("rightmost", "4")
+metadata/_edit_use_anchors_ = true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd
new file mode 100644
index 0000000..c03b3ee
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd
@@ -0,0 +1,93 @@
+extends AnimationPlayer
+
+## A custom script/node that adds some animations to the textbox.
+
+# Careful: Sync these with the ones in the root script!
+enum AnimationsIn {NONE, POP_IN, FADE_UP}
+enum AnimationsOut {NONE, POP_OUT, FADE_DOWN}
+enum AnimationsNewText {NONE, WIGGLE}
+
+var animation_in: AnimationsIn
+var animation_out: AnimationsOut
+var animation_new_text: AnimationsNewText
+
+var full_clear := true
+
+
+func get_text_panel() -> PanelContainer:
+ return %DialogTextPanel
+
+
+func get_dialog() -> DialogicNode_DialogText:
+ return %DialogicNode_DialogText
+
+
+func _ready() -> void:
+ var text_system: Node = DialogicUtil.autoload().get(&'Text')
+ text_system.connect(&'animation_textbox_hide', _on_textbox_hide)
+ text_system.connect(&'animation_textbox_show', _on_textbox_show)
+ text_system.connect(&'animation_textbox_new_text', _on_textbox_new_text)
+ text_system.connect(&'about_to_show_text', _on_about_to_show_text)
+ var animation_system: Node = DialogicUtil.autoload().get(&'Animations')
+ animation_system.connect(&'animation_interrupted', _on_animation_interrupted)
+
+
+func _on_textbox_show() -> void:
+ if animation_in == AnimationsIn.NONE:
+ return
+ play('RESET')
+ var animation_system: Node = DialogicUtil.autoload().get(&'Animations')
+ animation_system.call(&'start_animating')
+ get_text_panel().get_parent().get_parent().set(&'modulate', Color.TRANSPARENT)
+ get_dialog().text = ""
+ match animation_in:
+ AnimationsIn.POP_IN:
+ play("textbox_pop")
+ AnimationsIn.FADE_UP:
+ play("textbox_fade_up")
+ if not animation_finished.is_connected(Callable(animation_system, &'animation_finished')):
+ animation_finished.connect(Callable(animation_system, &'animation_finished'), CONNECT_ONE_SHOT)
+
+
+func _on_textbox_hide() -> void:
+ if animation_out == AnimationsOut.NONE:
+ return
+ play('RESET')
+ var animation_system: Node = DialogicUtil.autoload().get(&'Animations')
+ animation_system.call(&'start_animating')
+ match animation_out:
+ AnimationsOut.POP_OUT:
+ play_backwards("textbox_pop")
+ AnimationsOut.FADE_DOWN:
+ play_backwards("textbox_fade_up")
+
+ if not animation_finished.is_connected(Callable(animation_system, &'animation_finished')):
+ animation_finished.connect(Callable(animation_system, &'animation_finished'), CONNECT_ONE_SHOT)
+
+
+func _on_about_to_show_text(info:Dictionary) -> void:
+ full_clear = !info.append
+
+
+func _on_textbox_new_text() -> void:
+ if DialogicUtil.autoload().Inputs.auto_skip.enabled:
+ return
+
+ if animation_new_text == AnimationsNewText.NONE:
+ return
+
+ var animation_system: Node = DialogicUtil.autoload().get(&'Animations')
+ animation_system.call(&'start_animating')
+ if full_clear:
+ get_dialog().text = ""
+ match animation_new_text:
+ AnimationsNewText.WIGGLE:
+ play("new_text")
+
+ if not animation_finished.is_connected(Callable(animation_system, &'animation_finished')):
+ animation_finished.connect(Callable(animation_system, &'animation_finished'), CONNECT_ONE_SHOT)
+
+
+func _on_animation_interrupted() -> void:
+ if is_playing():
+ stop()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd
new file mode 100644
index 0000000..a4384bd
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd
@@ -0,0 +1,13 @@
+extends Range
+
+var enabled: bool = true
+
+func _process(_delta : float) -> void:
+ if !enabled:
+ hide()
+ return
+ if DialogicUtil.autoload().Inputs.auto_advance.get_progress() < 0:
+ hide()
+ else:
+ show()
+ value = DialogicUtil.autoload().Inputs.auto_advance.get_progress()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg
new file mode 100644
index 0000000..ae877a2
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import
new file mode 100644
index 0000000..d5b3ffe
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b0rpqfg4fhebk"
+path="res://.godot/imported/next.svg-23bb6920cc417e55e5677ffe99e585ef.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg"
+dest_files=["res://.godot/imported/next.svg-23bb6920cc417e55e5677ffe99e585ef.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/part_config.cfg
new file mode 100644
index 0000000..e94bc6f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/part_config.cfg
@@ -0,0 +1,7 @@
+[style]
+type = "Layer"
+name = "Visual Novel Textbox"
+author = "Dialogic"
+description = "A textbox in a VN style."
+scene = "vn_textbox_layer.tscn"
+icon = "textbox_layer_icon.svg"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png
new file mode 100644
index 0000000..413d09b
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import
new file mode 100644
index 0000000..b78d58a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dd4pvssu5dlqf"
+path="res://.godot/imported/preview.png-7ff37170169cbfef7b96abc82e9242d2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png"
+dest_files=["res://.godot/imported/preview.png-7ff37170169cbfef7b96abc82e9242d2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg
new file mode 100644
index 0000000..698f5d9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import
new file mode 100644
index 0000000..9ac49e8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cgx0ejya2mtmn"
+path="res://.godot/imported/textbox_layer_icon.svg-ecc0d379d3030b2ce0acdaa94924314b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg"
+dest_files=["res://.godot/imported/textbox_layer_icon.svg-ecc0d379d3030b2ce0acdaa94924314b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.3
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres
new file mode 100644
index 0000000..07489b4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres
@@ -0,0 +1,12 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://dkv1pl1c1dq6"]
+
+[resource]
+content_margin_left = 15.0
+content_margin_top = 15.0
+content_margin_right = 15.0
+content_margin_bottom = 15.0
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd
new file mode 100644
index 0000000..95a4ce3
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd
@@ -0,0 +1,278 @@
+@tool
+extends DialogicLayoutLayer
+## This layer's scene file contains following nodes:
+## - a dialog_text node
+## - a name_label node
+## - a next_indicator node
+## - a type_sound node
+##
+## As well as custom:
+## - animations
+## - auto-advance progress indicator
+##
+## If you want to customize this layer, here is a little rundown of this layer:
+## The Layer Settings are divided into the `@export_group`s below.
+## They get applied in [method _apply_export_overrides].
+## Each `@export_group` has its own method to apply the settings to the scene.
+## If you want to change a specific part inside the scene, you can simply
+## remove or add # (commenting) to the method line.
+
+
+
+enum Alignments {LEFT, CENTER, RIGHT}
+
+enum AnimationsIn {NONE, POP_IN, FADE_UP}
+enum AnimationsOut {NONE, POP_OUT, FADE_DOWN}
+enum AnimationsNewText {NONE, WIGGLE}
+
+@export_group("Text")
+
+@export_subgroup("Alignment & Size")
+@export var text_alignment: Alignments= Alignments.LEFT
+@export var text_use_global_size: bool = true
+@export var text_size: int = 15
+
+@export_subgroup("Color")
+@export var text_use_global_color: bool = true
+@export var text_custom_color: Color = Color.WHITE
+
+@export_subgroup('Font')
+@export var text_use_global_font: bool = true
+@export_file('*.ttf', '*.tres') var normal_font: String = ""
+@export_file('*.ttf', '*.tres') var bold_font: String = ""
+@export_file('*.ttf', '*.tres') var italics_font: String = ""
+@export_file('*.ttf', '*.tres') var bold_italics_font: String = ""
+
+
+@export_group("Box")
+
+@export_subgroup("Panel")
+@export_file("*.tres") var box_panel: String = this_folder.path_join("vn_textbox_default_panel.tres")
+
+@export_subgroup("Color")
+@export var box_color_use_global: bool = true
+@export var box_color_custom: Color = Color.BLACK
+
+@export_subgroup("Size & Position")
+@export var box_size: Vector2 = Vector2(550, 110)
+@export var box_margin_bottom: int = 15
+
+@export_subgroup("Animation")
+@export var box_animation_in: AnimationsIn = AnimationsIn.FADE_UP
+@export var box_animation_out: AnimationsOut = AnimationsOut.FADE_DOWN
+@export var box_animation_new_text: AnimationsNewText = AnimationsNewText.NONE
+
+
+@export_group("Name Label")
+
+@export_subgroup('Color')
+@export var name_label_use_global_color: bool= true
+@export var name_label_use_character_color: bool = true
+@export var name_label_custom_color: Color = Color.WHITE
+
+@export_subgroup('Font')
+@export var name_label_use_global_font: bool = true
+@export_file('*.ttf', '*.tres') var name_label_font: String = ""
+@export var name_label_use_global_font_size: bool = true
+@export var name_label_custom_font_size: int = 15
+
+@export_subgroup('Box')
+@export_file("*.tres") var name_label_box_panel: String = this_folder.path_join("vn_textbox_name_label_panel.tres")
+@export var name_label_box_use_global_color: bool = true
+@export var name_label_box_modulate: Color = box_color_custom
+
+@export_subgroup('Alignment')
+@export var name_label_alignment: Alignments = Alignments.LEFT
+@export var name_label_box_offset: Vector2 = Vector2.ZERO
+
+
+@export_group("Indicators")
+
+@export_subgroup("Next Indicator")
+@export var next_indicator_enabled: bool = true
+@export var next_indicator_show_on_questions: bool = true
+@export var next_indicator_show_on_autoadvance: bool = false
+@export_enum('bounce', 'blink', 'none') var next_indicator_animation: int = 0
+@export_file("*.png","*.svg","*.tres") var next_indicator_texture: String = ''
+@export var next_indicator_size: Vector2 = Vector2(25,25)
+
+@export_subgroup("Autoadvance")
+@export var autoadvance_progressbar: bool = true
+
+
+@export_group('Sounds')
+
+@export_subgroup('Typing Sounds')
+@export var typing_sounds_enabled: bool = true
+@export var typing_sounds_mode: DialogicNode_TypeSounds.Modes = DialogicNode_TypeSounds.Modes.INTERRUPT
+@export_dir var typing_sounds_sounds_folder: String = "res://addons/dialogic/Example Assets/sound-effects/"
+@export_file("*.wav", "*.ogg", "*.mp3") var typing_sounds_end_sound: String = ""
+@export_range(1, 999, 1) var typing_sounds_every_nths_character: int = 1
+@export_range(0.01, 4, 0.01) var typing_sounds_pitch: float = 1.0
+@export_range(0.0, 3.0) var typing_sounds_pitch_variance: float = 0.0
+@export_range(-80, 24, 0.01) var typing_sounds_volume: float = -10
+@export_range(0.0, 10) var typing_sounds_volume_variance: float = 0.0
+@export var typing_sounds_ignore_characters: String = " .,!?"
+
+
+func _apply_export_overrides() -> void:
+ if !is_inside_tree():
+ await ready
+
+ ## FONT SETTINGS
+ _apply_text_settings()
+
+
+ ## BOX SETTINGS
+ _apply_box_settings()
+
+ ## BOX ANIMATIONS
+ _apply_box_animations_settings()
+
+ ## NAME LABEL SETTINGS
+ _apply_name_label_settings()
+
+ ## NEXT INDICATOR SETTINGS
+ _apply_indicator_settings()
+
+ ## OTHER
+ var progress_bar: ProgressBar = %AutoAdvanceProgressbar
+ progress_bar.set(&'enabled', autoadvance_progressbar)
+
+ #### SOUNDS
+
+ ## TYPING SOUNDS
+ _apply_sounds_settings()
+
+
+## Applies all text box settings to the scene.
+## Except the box animations.
+func _apply_box_settings() -> void:
+ var dialog_text_panel: PanelContainer = %DialogTextPanel
+ if ResourceLoader.exists(box_panel):
+ dialog_text_panel.add_theme_stylebox_override(&'panel', load(box_panel) as StyleBox)
+
+ if box_color_use_global:
+ dialog_text_panel.self_modulate = get_global_setting(&'bg_color', box_color_custom)
+ else:
+ dialog_text_panel.self_modulate = box_color_custom
+
+ var sizer: Control = %Sizer
+ sizer.size = box_size
+ sizer.position = box_size * Vector2(-0.5, -1)+Vector2(0, -box_margin_bottom)
+
+
+## Applies box animations settings to the scene.
+func _apply_box_animations_settings() -> void:
+ var animations: AnimationPlayer = %Animations
+ animations.set(&'animation_in', box_animation_in)
+ animations.set(&'animation_out', box_animation_out)
+ animations.set(&'animation_new_text', box_animation_new_text)
+
+
+## Applies all name label settings to the scene.
+func _apply_name_label_settings() -> void:
+ var name_label: DialogicNode_NameLabel = %DialogicNode_NameLabel
+
+ if name_label_use_global_font_size:
+ name_label.add_theme_font_size_override(&"font_size", get_global_setting(&'font_size', name_label_custom_font_size) as int)
+ else:
+ name_label.add_theme_font_size_override(&"font_size", name_label_custom_font_size)
+
+ if name_label_use_global_font and get_global_setting(&'font', false):
+ name_label.add_theme_font_override(&'font', load(get_global_setting(&'font', '') as String) as Font)
+ elif not name_label_font.is_empty():
+ name_label.add_theme_font_override(&'font', load(name_label_font) as Font)
+
+ if name_label_use_global_color:
+ name_label.add_theme_color_override(&"font_color", get_global_setting(&'font_color', name_label_custom_color) as Color)
+ else:
+ name_label.add_theme_color_override(&"font_color", name_label_custom_color)
+
+ name_label.use_character_color = name_label_use_character_color
+
+ var name_label_panel: PanelContainer = %NameLabelPanel
+ if ResourceLoader.exists(name_label_box_panel):
+ name_label_panel.add_theme_stylebox_override(&'panel', load(name_label_box_panel) as StyleBox)
+ else:
+ name_label_panel.add_theme_stylebox_override(&'panel', load(this_folder.path_join("vn_textbox_name_label_panel.tres")) as StyleBox)
+
+ if name_label_box_use_global_color:
+ name_label_panel.self_modulate = get_global_setting(&'bg_color', name_label_box_modulate)
+ else:
+ name_label_panel.self_modulate = name_label_box_modulate
+ var dialog_text_panel: PanelContainer = %DialogTextPanel
+ name_label_panel.position = name_label_box_offset+Vector2(0, -40)
+ name_label_panel.position -= Vector2(
+ dialog_text_panel.get_theme_stylebox(&'panel', &'PanelContainer').content_margin_left,
+ dialog_text_panel.get_theme_stylebox(&'panel', &'PanelContainer').content_margin_top)
+ name_label_panel.anchor_left = name_label_alignment/2.0
+ name_label_panel.anchor_right = name_label_alignment/2.0
+ name_label_panel.grow_horizontal = [1, 2, 0][name_label_alignment]
+
+
+## Applies all text settings to the scene.
+func _apply_text_settings() -> void:
+ var dialog_text: DialogicNode_DialogText = %DialogicNode_DialogText
+ dialog_text.alignment = text_alignment as DialogicNode_DialogText.Alignment
+
+ if text_use_global_size:
+ text_size = get_global_setting(&'font_size', text_size)
+ dialog_text.add_theme_font_size_override(&"normal_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"bold_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"italics_font_size", text_size)
+ dialog_text.add_theme_font_size_override(&"bold_italics_font_size", text_size)
+
+ if text_use_global_color:
+ dialog_text.add_theme_color_override(&"default_color", get_global_setting(&'font_color', text_custom_color) as Color)
+ else:
+ dialog_text.add_theme_color_override(&"default_color", text_custom_color)
+
+ if text_use_global_font and get_global_setting(&'font', false):
+ dialog_text.add_theme_font_override(&"normal_font", load(get_global_setting(&'font', '') as String) as Font)
+ elif !normal_font.is_empty():
+ dialog_text.add_theme_font_override(&"normal_font", load(normal_font) as Font)
+ if !bold_font.is_empty():
+ dialog_text.add_theme_font_override(&"bold_font", load(bold_font) as Font)
+ if !italics_font.is_empty():
+ dialog_text.add_theme_font_override(&"italics_font", load(italics_font) as Font)
+ if !bold_italics_font.is_empty():
+ dialog_text.add_theme_font_override(&"bold_italics_font", load(bold_italics_font) as Font)
+
+
+## Applies all indicator settings to the scene.
+func _apply_indicator_settings() -> void:
+ var next_indicator: DialogicNode_NextIndicator = %NextIndicator
+ next_indicator.enabled = next_indicator_enabled
+
+ if next_indicator_enabled:
+ next_indicator.animation = next_indicator_animation as DialogicNode_NextIndicator.Animations
+ if ResourceLoader.exists(next_indicator_texture):
+ next_indicator.texture = load(next_indicator_texture)
+ next_indicator.show_on_questions = next_indicator_show_on_questions
+ next_indicator.show_on_autoadvance = next_indicator_show_on_autoadvance
+ next_indicator.texture_size = next_indicator_size
+
+
+## Applies all sound settings to the scene.
+func _apply_sounds_settings() -> void:
+ var type_sounds: DialogicNode_TypeSounds = %DialogicNode_TypeSounds
+ type_sounds.enabled = typing_sounds_enabled
+ type_sounds.mode = typing_sounds_mode
+
+ if not typing_sounds_sounds_folder.is_empty():
+ type_sounds.sounds = DialogicNode_TypeSounds.load_sounds_from_path(typing_sounds_sounds_folder)
+ else:
+ type_sounds.sounds.clear()
+
+ if not typing_sounds_end_sound.is_empty():
+ type_sounds.end_sound = load(typing_sounds_end_sound)
+ else:
+ type_sounds.end_sound = null
+
+ type_sounds.play_every_character = typing_sounds_every_nths_character
+ type_sounds.base_pitch = typing_sounds_pitch
+ type_sounds.base_volume = typing_sounds_volume
+ type_sounds.pitch_variance = typing_sounds_pitch_variance
+ type_sounds.volume_variance = typing_sounds_volume_variance
+ type_sounds.ignore_characters = typing_sounds_ignore_characters
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn
new file mode 100644
index 0000000..5521ef8
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn
@@ -0,0 +1,345 @@
+[gd_scene load_steps=17 format=3 uid="uid://bquja8jyk8kbr"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd" id="1_bpydr"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd" id="2_xy7a2"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_4634k"]
+[ext_resource type="StyleBox" uid="uid://dkv1pl1c1dq6" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres" id="3_ssa84"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="4_ma5mw"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_next_indicator.gd" id="5_40a50"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd" id="6_07xym"]
+[ext_resource type="Texture2D" uid="uid://b0rpqfg4fhebk" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg" id="6_uch03"]
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="7_bi7sh"]
+[ext_resource type="StyleBox" uid="uid://m7gyepkysu83" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres" id="9_yg8ig"]
+
+[sub_resource type="Animation" id="Animation_au0a2"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Anchor/AnimationParent:position")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Anchor/AnimationParent:rotation")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [0.0]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Anchor/AnimationParent:scale")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(1, 1)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Anchor/AnimationParent:modulate")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/4/type = "bezier"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("Anchor/AnimationParent/Sizer/DialogTextPanel:rotation")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"handle_modes": PackedInt32Array(0),
+"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
+"times": PackedFloat32Array(0)
+}
+
+[sub_resource type="Animation" id="Animation_6kbwc"]
+resource_name = "new_text"
+length = 0.4
+tracks/0/type = "bezier"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Anchor/AnimationParent/Sizer/DialogTextPanel:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"handle_modes": PackedInt32Array(3, 3, 3, 3, 3),
+"points": PackedFloat32Array(0, -0.025, 0, 0.025, 0, 0.005, -0.025, 0, 0.025, 0, -0.005, -0.025, 0, 0.025, 0, 0.005, -0.025, 0, 0.025, 0, 0, -0.025, 0, 0.025, 0),
+"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4)
+}
+
+[sub_resource type="Animation" id="Animation_g6k55"]
+resource_name = "textbox_fade_up"
+length = 0.7
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Anchor/AnimationParent:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.3, 0.7),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector2(0, 50), Vector2(0, 19.6793), Vector2(0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Anchor/AnimationParent:modulate")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0.1, 0.6),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Anchor/AnimationParent:rotation")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [0.0]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Anchor/AnimationParent:scale")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(1, 1)]
+}
+
+[sub_resource type="Animation" id="Animation_htbgc"]
+resource_name = "textbox_pop"
+length = 0.3
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Anchor/AnimationParent:position")
+tracks/0/interp = 2
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Anchor/AnimationParent:rotation")
+tracks/1/interp = 2
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [-0.0899883, 0.0258223, 0.0]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Anchor/AnimationParent:scale")
+tracks/2/interp = 2
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.2, 0.3),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Vector2(0.793957, 0.778082), Vector2(0.937299, 1.14248), Vector2(1, 1)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("Anchor/AnimationParent:modulate")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 0.3),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_c14kh"]
+_data = {
+"RESET": SubResource("Animation_au0a2"),
+"new_text": SubResource("Animation_6kbwc"),
+"textbox_fade_up": SubResource("Animation_g6k55"),
+"textbox_pop": SubResource("Animation_htbgc")
+}
+
+[sub_resource type="FontVariation" id="FontVariation_v8y64"]
+
+[node name="VN_TextboxLayer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+script = ExtResource("1_bpydr")
+box_panel = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres"
+box_size = Vector2(550, 150)
+name_label_box_panel = "res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres"
+name_label_box_modulate = Color(0, 0, 0, 1)
+
+[node name="Animations" type="AnimationPlayer" parent="."]
+unique_name_in_owner = true
+libraries = {
+"": SubResource("AnimationLibrary_c14kh")
+}
+autoplay = "RESET"
+script = ExtResource("2_xy7a2")
+
+[node name="Anchor" type="Control" parent="."]
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 0
+
+[node name="AnimationParent" type="Control" parent="Anchor"]
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 0
+mouse_filter = 2
+
+[node name="Sizer" type="Control" parent="Anchor/AnimationParent"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -150.0
+offset_top = -50.0
+offset_right = 150.0
+grow_horizontal = 2
+grow_vertical = 0
+mouse_filter = 2
+
+[node name="DialogTextPanel" type="PanelContainer" parent="Anchor/AnimationParent/Sizer"]
+unique_name_in_owner = true
+self_modulate = Color(0.00784314, 0.00784314, 0.00784314, 0.843137)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("3_ssa84")
+metadata/_edit_layout_mode = 1
+
+[node name="DialogicNode_DialogText" type="RichTextLabel" parent="Anchor/AnimationParent/Sizer/DialogTextPanel" node_paths=PackedStringArray("textbox_root")]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 1
+theme_override_colors/default_color = Color(1, 1, 1, 1)
+theme_override_font_sizes/normal_font_size = 15
+theme_override_font_sizes/bold_font_size = 15
+theme_override_font_sizes/italics_font_size = 15
+theme_override_font_sizes/bold_italics_font_size = 15
+bbcode_enabled = true
+text = "Some default text"
+visible_characters_behavior = 1
+script = ExtResource("3_4634k")
+textbox_root = NodePath("..")
+
+[node name="DialogicNode_TypeSounds" type="AudioStreamPlayer" parent="Anchor/AnimationParent/Sizer/DialogTextPanel/DialogicNode_DialogText"]
+unique_name_in_owner = true
+script = ExtResource("4_ma5mw")
+play_every_character = 0
+
+[node name="NextIndicator" type="Control" parent="Anchor/AnimationParent/Sizer/DialogTextPanel"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 8
+size_flags_vertical = 8
+mouse_filter = 2
+script = ExtResource("5_40a50")
+show_on_questions = true
+texture = ExtResource("6_uch03")
+metadata/_edit_layout_mode = 1
+
+[node name="AutoAdvanceProgressbar" type="ProgressBar" parent="Anchor/AnimationParent/Sizer/DialogTextPanel"]
+unique_name_in_owner = true
+modulate = Color(1, 1, 1, 0.188235)
+custom_minimum_size = Vector2(0, 10)
+layout_mode = 2
+size_flags_vertical = 8
+mouse_filter = 2
+max_value = 1.0
+step = 0.001
+value = 0.5
+show_percentage = false
+script = ExtResource("6_07xym")
+
+[node name="NameLabelHolder" type="Control" parent="Anchor/AnimationParent/Sizer/DialogTextPanel"]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="NameLabelPanel" type="PanelContainer" parent="Anchor/AnimationParent/Sizer/DialogTextPanel/NameLabelHolder"]
+unique_name_in_owner = true
+self_modulate = Color(0.00784314, 0.00784314, 0.00784314, 0.843137)
+layout_mode = 1
+offset_top = -50.0
+offset_right = 9.0
+offset_bottom = -25.0
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("9_yg8ig")
+metadata/_edit_layout_mode = 1
+metadata/_edit_use_custom_anchors = true
+metadata/_edit_group_ = true
+
+[node name="DialogicNode_NameLabel" type="Label" parent="Anchor/AnimationParent/Sizer/DialogTextPanel/NameLabelHolder/NameLabelPanel" node_paths=PackedStringArray("name_label_root")]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_colors/font_color = Color(1, 1, 1, 1)
+theme_override_fonts/font = SubResource("FontVariation_v8y64")
+theme_override_font_sizes/font_size = 15
+text = "S"
+script = ExtResource("7_bi7sh")
+name_label_root = NodePath("..")
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres
new file mode 100644
index 0000000..cc88fd9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres
@@ -0,0 +1,12 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://m7gyepkysu83"]
+
+[resource]
+content_margin_left = 10.0
+content_margin_top = 5.0
+content_margin_right = 10.0
+content_margin_bottom = 5.0
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 5
+corner_radius_top_right = 5
+corner_radius_bottom_right = 5
+corner_radius_bottom_left = 5
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/part_config.cfg
new file mode 100644
index 0000000..ceaac42
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/part_config.cfg
@@ -0,0 +1,6 @@
+[style]
+type = "Style"
+name = "Speaker Textbox Style"
+author = "Dialogic"
+description = "A style with a textbox that has a speaker portrait inside of it."
+style_path = "speaker_textbox_style.tres"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png
new file mode 100644
index 0000000..0adc6da
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import
new file mode 100644
index 0000000..d11a4ea
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dmlqow4is4acg"
+path="res://.godot/imported/preview.png-074d498f0e000677d42870a034798778.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png"
+dest_files=["res://.godot/imported/preview.png-074d498f0e000677d42870a034798778.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres
new file mode 100644
index 0000000..9ff8d5b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres
@@ -0,0 +1,54 @@
+[gd_resource type="Resource" script_class="DialogicStyle" load_steps=17 format=3 uid="uid://dgkmuyvy5qc35"]
+
+[ext_resource type="PackedScene" uid="uid://c1k5m0w3r40xf" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn" id="1_sde84"]
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style_layer.gd" id="2_i34tx"]
+[ext_resource type="PackedScene" uid="uid://by6waso0mjpjp" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn" id="3_epko4"]
+[ext_resource type="PackedScene" uid="uid://cn674foxwedqu" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn" id="4_8y2vo"]
+[ext_resource type="PackedScene" uid="uid://dsbwnp5hegnu3" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn" id="5_ll78j"]
+[ext_resource type="PackedScene" uid="uid://dhk6j6eb6e3q" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn" id="6_36eid"]
+[ext_resource type="PackedScene" uid="uid://cvgf4c6gg0tsy" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn" id="7_hx5el"]
+[ext_resource type="PackedScene" uid="uid://lx24i8fl6uo" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn" id="8_00chv"]
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style.gd" id="9_sdr6x"]
+
+[sub_resource type="Resource" id="Resource_35sbo"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("1_sde84")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_x576n"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("4_8y2vo")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_gc1b5"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("3_epko4")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_otikm"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("5_ll78j")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_w8ec6"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("6_36eid")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_qmo1y"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("7_hx5el")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_legp8"]
+script = ExtResource("2_i34tx")
+scene = ExtResource("8_00chv")
+overrides = {}
+
+[resource]
+script = ExtResource("9_sdr6x")
+name = "Speaker Textbox Style"
+base_overrides = {
+"global_bg_color": "Color(0.298039, 0.2, 0.113725, 0.901961)"
+}
+layers = Array[ExtResource("2_i34tx")]([SubResource("Resource_35sbo"), SubResource("Resource_x576n"), SubResource("Resource_gc1b5"), SubResource("Resource_otikm"), SubResource("Resource_w8ec6"), SubResource("Resource_qmo1y"), SubResource("Resource_legp8")])
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/part_config.cfg
new file mode 100644
index 0000000..47b4ec6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/part_config.cfg
@@ -0,0 +1,6 @@
+[style]
+type = "Style"
+name = "Textbubble Style"
+author = "Dialogic"
+description = "A simple text bubble style."
+style_path = "textbubble_style.tres"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png
new file mode 100644
index 0000000..74bc8fd
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import
new file mode 100644
index 0000000..e9982fc
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ryu3i2tv5xg8"
+path="res://.godot/imported/preview.png-dce863864b9eb689be42ffbda780ba2c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png"
+dest_files=["res://.godot/imported/preview.png-dce863864b9eb689be42ffbda780ba2c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres
new file mode 100644
index 0000000..9c94644
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres
@@ -0,0 +1,24 @@
+[gd_resource type="Resource" script_class="DialogicStyle" load_steps=8 format=3 uid="uid://b0sbwssin2kuk"]
+
+[ext_resource type="PackedScene" uid="uid://syki6k0e6aac" path="res://addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn" id="1_a7s28"]
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style.gd" id="1_q3xp1"]
+[ext_resource type="PackedScene" uid="uid://d2it0xiap3gnt" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn" id="2_ctkoo"]
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style_layer.gd" id="3_3a5cc"]
+[ext_resource type="PackedScene" uid="uid://cn674foxwedqu" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn" id="4_rr4hm"]
+
+[sub_resource type="Resource" id="Resource_xt3fr"]
+script = ExtResource("3_3a5cc")
+scene = ExtResource("4_rr4hm")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_inc2n"]
+script = ExtResource("3_3a5cc")
+scene = ExtResource("2_ctkoo")
+overrides = {}
+
+[resource]
+script = ExtResource("1_q3xp1")
+name = "Textbubble Style"
+base_scene = ExtResource("1_a7s28")
+base_overrides = {}
+layers = Array[ExtResource("3_3a5cc")]([SubResource("Resource_xt3fr"), SubResource("Resource_inc2n")])
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres
new file mode 100644
index 0000000..d9200d6
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres
@@ -0,0 +1,58 @@
+[gd_resource type="Resource" script_class="DialogicStyle" load_steps=19 format=3 uid="uid://8t1mr302tmqs"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style.gd" id="1_mvpc0"]
+[ext_resource type="Script" path="res://addons/dialogic/Resources/dialogic_style_layer.gd" id="2_3b8ue"]
+[ext_resource type="PackedScene" uid="uid://c1k5m0w3r40xf" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn" id="2_dtgi6"]
+[ext_resource type="PackedScene" uid="uid://cy1y14inwkplb" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn" id="4_q1t5h"]
+[ext_resource type="PackedScene" uid="uid://bquja8jyk8kbr" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn" id="5_o6sv8"]
+[ext_resource type="PackedScene" uid="uid://cn674foxwedqu" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn" id="6_j6olx"]
+[ext_resource type="PackedScene" uid="uid://dsbwnp5hegnu3" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn" id="7_vw5f4"]
+[ext_resource type="PackedScene" uid="uid://dhk6j6eb6e3q" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn" id="8_tc6v1"]
+[ext_resource type="PackedScene" uid="uid://cvgf4c6gg0tsy" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn" id="9_tufw5"]
+[ext_resource type="PackedScene" uid="uid://lx24i8fl6uo" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn" id="10_8v8jj"]
+
+[sub_resource type="Resource" id="Resource_x8thn"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("2_dtgi6")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_g5yti"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("4_q1t5h")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_eqyxb"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("6_j6olx")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_adxfb"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("5_o6sv8")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_nmutb"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("7_vw5f4")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_dwo52"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("8_tc6v1")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_by0l6"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("9_tufw5")
+overrides = {}
+
+[sub_resource type="Resource" id="Resource_fd6co"]
+script = ExtResource("2_3b8ue")
+scene = ExtResource("10_8v8jj")
+overrides = {}
+
+[resource]
+script = ExtResource("1_mvpc0")
+name = "Visual Novel Style"
+base_overrides = {}
+layers = Array[ExtResource("2_3b8ue")]([SubResource("Resource_x8thn"), SubResource("Resource_g5yti"), SubResource("Resource_eqyxb"), SubResource("Resource_adxfb"), SubResource("Resource_nmutb"), SubResource("Resource_dwo52"), SubResource("Resource_by0l6"), SubResource("Resource_fd6co")])
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/part_config.cfg b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/part_config.cfg
new file mode 100644
index 0000000..92cf452
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/part_config.cfg
@@ -0,0 +1,6 @@
+[style]
+type = "Style"
+name = "Visual Novel Style"
+author = "Dialogic"
+description = "A full visual novel style."
+style_path = "default_vn_style.tres"
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png
new file mode 100644
index 0000000..70ad618
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import
new file mode 100644
index 0000000..880cd48
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0s7elvxjug5l"
+path="res://.godot/imported/preview.png-4cd1aa7045d6e1a899ba900c41696dfa.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png"
+dest_files=["res://.godot/imported/preview.png-4cd1aa7045d6e1a899ba900c41696dfa.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/index.gd b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/index.gd
new file mode 100644
index 0000000..ad5e985
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/DefaultLayoutParts/index.gd
@@ -0,0 +1,5 @@
+extends DialogicIndexer
+
+
+func _get_layout_parts() -> Array[Dictionary]:
+ return scan_for_layout_parts()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/End/event_end.gd b/addons/dialogic-main/addons/dialogic/Modules/End/event_end.gd
new file mode 100644
index 0000000..b853119
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/End/event_end.gd
@@ -0,0 +1,44 @@
+@tool
+class_name DialogicEndTimelineEvent
+extends DialogicEvent
+
+## Event that ends a timeline (even if more events come after).
+
+
+#region EXECUTE
+################################################################################
+
+func _execute() -> void:
+ dialogic.end_timeline()
+
+#endregion
+
+
+#region INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "End"
+ set_default_color('Color4')
+ event_category = "Flow"
+ event_sorting_index = 10
+
+#endregion
+
+
+#region SAVING/LOADING
+################################################################################
+
+func get_shortcode() -> String:
+ return "end_timeline"
+
+#endregion
+
+
+#region EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_label('End Timeline')
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/End/icon.png b/addons/dialogic-main/addons/dialogic/Modules/End/icon.png
new file mode 100644
index 0000000..d975480
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/End/icon.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/End/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/End/icon.png.import
new file mode 100644
index 0000000..92aa2c4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/End/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cstw2y41yacgc"
+path="res://.godot/imported/icon.png-92e4feaa2ba081c6b903820a6cb33f20.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/End/icon.png"
+dest_files=["res://.godot/imported/icon.png-92e4feaa2ba081c6b903820a6cb33f20.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/End/index.gd b/addons/dialogic-main/addons/dialogic/Modules/End/index.gd
new file mode 100644
index 0000000..0997630
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/End/index.gd
@@ -0,0 +1,6 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return [this_folder.path_join('event_end.gd')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg b/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg
new file mode 100644
index 0000000..f1b9f87
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg.import
new file mode 100644
index 0000000..322da00
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cenut3sc5cul0"
+path="res://.godot/imported/add-glossary.svg-1c005836962586a7eda0e628d4cd588b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Glossary/add-glossary.svg"
+dest_files=["res://.godot/imported/add-glossary.svg-1c005836962586a7eda0e628d4cd588b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/event_glossary.gd b/addons/dialogic-main/addons/dialogic/Modules/Glossary/event_glossary.gd
new file mode 100644
index 0000000..d8fce3a
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/event_glossary.gd
@@ -0,0 +1,42 @@
+@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 = "Other"
+ event_sorting_index = 0
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+func get_shortcode() -> String:
+ return "glossary"
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ }
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ pass
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.gd b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.gd
new file mode 100644
index 0000000..403ed6d
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.gd
@@ -0,0 +1,461 @@
+@tool
+extends DialogicEditor
+
+var current_glossary: DialogicGlossary = null
+var current_entry_name := ""
+var current_entry := {}
+
+################################################################################
+## BASICS
+################################################################################
+
+func _get_title() -> String:
+ return "Glossary"
+
+
+func _get_icon() -> Texture:
+ var base_directory: String = self.get_script().get_path().get_base_dir()
+ var icon_path := base_directory + "/icon.svg"
+ return load(icon_path)
+
+
+func _register() -> void:
+ editors_manager.register_simple_editor(self)
+ alternative_text = "Create and edit glossaries."
+
+
+func _ready() -> void:
+ var add_glossary_icon_path: String = self.get_script().get_path().get_base_dir() + "/add-glossary.svg"
+ var add_glossary_icon := load(add_glossary_icon_path)
+ %AddGlossaryFile.icon = add_glossary_icon
+
+ %LoadGlossaryFile.icon = get_theme_icon('Folder', 'EditorIcons')
+ %DeleteGlossaryFile.icon = get_theme_icon('Remove', 'EditorIcons')
+ %DeleteGlossaryEntry.icon = get_theme_icon('Remove', 'EditorIcons')
+
+ %DeleteGlossaryFile.pressed.connect(_on_delete_glossary_file_pressed)
+
+ %AddGlossaryEntry.icon = get_theme_icon('Add', 'EditorIcons')
+ %EntrySearch.right_icon = get_theme_icon('Search', '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'))
+
+ %EntryCaseSensitive.icon = get_theme_icon("MatchCase", "EditorIcons")
+
+ %EntryAlternatives.text_changed.connect(_on_entry_alternatives_text_changed)
+
+
+func set_setting(value: Variant, setting: String) -> void:
+ ProjectSettings.set_setting(setting, value)
+ ProjectSettings.save()
+
+
+func _open(_argument: Variant = null) -> void:
+ %DefaultColor.color = ProjectSettings.get_setting('dialogic/glossary/default_color', Color.POWDER_BLUE)
+ %DefaultCaseSensitive.button_pressed = ProjectSettings.get_setting('dialogic/glossary/default_case_sensitive', true)
+
+ %GlossaryList.clear()
+ var idx := 0
+ for file: String in ProjectSettings.get_setting('dialogic/glossary/glossary_files', []):
+
+ if ResourceLoader.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()
+ var tooltip_item: String = %GlossaryList.get_item_tooltip(idx)
+
+ if ResourceLoader.exists(tooltip_item):
+ var glossary_item := load(tooltip_item)
+
+ if not glossary_item is DialogicGlossary:
+ return
+
+ current_glossary = load(tooltip_item)
+
+ if not current_glossary is DialogicGlossary:
+ return
+
+ var entry_idx := 0
+
+ for entry_key: String in current_glossary.entries.keys():
+ var entry: Variant = current_glossary.entries.get(entry_key)
+
+ if entry is String:
+ continue
+
+ # Older glossary entries may not have the name property and the
+ # alternatives may not be set up as alias entries.
+ if not entry.has(DialogicGlossary.NAME_PROPERTY):
+ entry[DialogicGlossary.NAME_PROPERTY] = entry_key
+ var alternatives_array: Array = entry.get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])
+ var alternatives := ",".join(alternatives_array)
+ _on_entry_alternatives_text_changed(alternatives)
+ ResourceSaver.save(current_glossary)
+
+ %EntryList.add_item(entry.get(DialogicGlossary.NAME_PROPERTY, str(DialogicGlossary.NAME_PROPERTY)), get_theme_icon("Breakpoint", "EditorIcons"))
+ var modulate_color: Color = entry.get('color', %DefaultColor.color)
+ %EntryList.set_item_metadata(entry_idx, entry)
+ %EntryList.set_item_icon_modulate(entry_idx, modulate_color)
+
+ entry_idx += 1
+
+ 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 = ProjectSettings.get_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'))
+
+ var selected_item_index: int = %GlossaryList.item_count - 1
+
+ %GlossaryList.set_item_tooltip(selected_item_index, path)
+ %GlossaryList.select(selected_item_index)
+ _on_GlossaryList_item_selected(selected_item_index)
+
+
+func _on_delete_glossary_file_pressed() -> void:
+ var selected_items: PackedInt32Array = %GlossaryList.get_selected_items()
+
+ if not selected_items.is_empty():
+ var list: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
+ var selected_item_index := selected_items[0]
+ list.remove_at(selected_item_index)
+
+ ProjectSettings.set_setting('dialogic/glossary/glossary_files', list)
+ ProjectSettings.save()
+
+ _open()
+
+
+################################################################################
+## ENTRY LIST
+################################################################################
+func _on_EntryList_item_selected(idx: int) -> void:
+ current_entry_name = %EntryList.get_item_text(idx)
+
+ var entry_info: Dictionary = current_glossary.get_entry(current_entry_name)
+ current_entry = entry_info
+
+ %EntrySettings.show()
+ %EntryName.text = current_entry_name
+ %EntryCaseSensitive.button_pressed = entry_info.get('case_sensitive', %DefaultCaseSensitive.button_pressed)
+
+ var alternative_property: Array = entry_info.get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])
+ var alternatives := ", ".join(alternative_property)
+ %EntryAlternatives.text = alternatives
+
+ %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')
+
+ _check_entry_alternatives(alternatives)
+ _check_entry_name(current_entry_name, current_entry)
+
+func _on_add_glossary_entry_pressed() -> void:
+ if !current_glossary:
+ return
+
+ var entry_count := current_glossary.entries.size() + 1
+ var new_name := "New Entry " + str(entry_count)
+
+ if new_name in current_glossary.entries.keys():
+ var random_hex_number := str(randi() % 0xFFFFFF)
+ new_name = new_name + " " + str(random_hex_number)
+
+ var new_glossary := {}
+ new_glossary[DialogicGlossary.NAME_PROPERTY] = new_name
+
+ if not current_glossary.try_add_entry(new_glossary):
+ print_rich("[color=red]Failed adding '" + new_name + "', exists already.[/color]")
+ return
+
+ ResourceSaver.save(current_glossary)
+
+ %EntryList.add_item(new_name, get_theme_icon("Breakpoint", "EditorIcons"))
+ var item_count: int = %EntryList.item_count - 1
+
+ %EntryList.set_item_metadata(item_count, new_name)
+ %EntryList.set_item_icon_modulate(item_count, %DefaultColor.color)
+ %EntryList.select(item_count)
+
+ _on_EntryList_item_selected(item_count)
+
+ %EntryList.ensure_current_is_visible()
+ %EntryName.grab_focus()
+
+
+func _on_delete_glossary_entry_pressed() -> void:
+ var selected_items: Array = %EntryList.get_selected_items()
+
+ if not selected_items.is_empty():
+ var selected_item_index: int = selected_items[0]
+
+ if not current_glossary == null:
+ current_glossary.remove_entry(current_entry_name)
+ ResourceSaver.save(current_glossary)
+
+ %EntryList.remove_item(selected_item_index)
+ var entries_count: int = %EntryList.item_count
+
+ if entries_count > 0:
+ var previous_item_index := selected_item_index - 1
+ %EntryList.select(previous_item_index)
+
+
+
+func _on_entry_search_text_changed(new_text: String) -> void:
+ if new_text.is_empty() or new_text.to_lower() in %EntryList.get_item_text(%EntryList.get_selected_items()[0]).to_lower():
+ return
+
+ for i: int in %EntryList.item_count:
+
+ if new_text.is_empty() or new_text.to_lower() in %EntryList.get_item_text(i).to_lower():
+ %EntryList.select(i)
+ _on_EntryList_item_selected(i)
+ %EntryList.ensure_current_is_visible()
+
+
+################################################################################
+## ENTRY EDITOR
+################################################################################
+func hide_entry_editor() -> void:
+ %EntrySettings.hide()
+
+
+func _update_alias_entries(old_alias_value_key: String, new_alias_value_key: String) -> void:
+ for entry_key: String in current_glossary.entries.keys():
+
+ var entry_value: Variant = current_glossary.entries.get(entry_key)
+
+ if not entry_value is String:
+ continue
+
+ if not entry_value == old_alias_value_key:
+ continue
+
+ current_glossary.entries[entry_key] = new_alias_value_key
+
+
+## Checks if the [param entry_name] is already used as a key for another entry
+## and returns true if it doesn't.
+## The [param entry] will be used to check if found entry uses the same
+## reference in memory.
+func _check_entry_name(entry_name: String, entry: Dictionary) -> bool:
+ var selected_item: int = %EntryList.get_selected_items()[0]
+ var raised_error: bool = false
+
+ var entry_assigned: Variant = current_glossary.entries.get(entry_name, {})
+
+ # Alternative entry uses the entry name already.
+ if entry_assigned is String:
+ raised_error = true
+
+ if entry_assigned is Dictionary and not entry_assigned.is_empty():
+ var entry_name_assigned: String = entry_assigned.get(DialogicGlossary.NAME_PROPERTY, "")
+
+ # Another entry uses the entry name already.
+ if not entry_name_assigned == entry_name:
+ raised_error = true
+
+ # Not the same memory reference.
+ if not entry == entry_assigned:
+ raised_error = true
+
+ if raised_error:
+ %EntryList.set_item_custom_bg_color(selected_item,
+ get_theme_color("warning_color", "Editor").darkened(0.8))
+ %EntryName.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+ %EntryName.right_icon = get_theme_icon("StatusError", "EditorIcons")
+
+ return false
+
+ else:
+ %EntryName.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
+ %EntryName.add_theme_color_override("caret_color", get_theme_color("font_color", "Editor"))
+ %EntryName.right_icon = null
+ %EntryList.set_item_custom_bg_color(
+ selected_item,
+ Color.TRANSPARENT
+ )
+
+ return true
+
+
+func _on_entry_name_text_changed(new_name: String) -> void:
+ new_name = new_name.strip_edges()
+
+ if current_entry_name != new_name:
+ var selected_item: int = %EntryList.get_selected_items()[0]
+
+ if not _check_entry_name(new_name, current_entry):
+ return
+
+ print_rich("[color=green]Renaming entry '" + current_entry_name + "'' to '" + new_name + "'[/color]")
+
+ _update_alias_entries(current_entry_name, new_name)
+
+ current_glossary.replace_entry_key(current_entry_name, new_name)
+
+ %EntryList.set_item_text(selected_item, new_name)
+ %EntryList.set_item_metadata(selected_item, new_name)
+ ResourceSaver.save(current_glossary)
+ current_entry_name = new_name
+
+
+func _on_entry_case_sensitive_toggled(button_pressed: bool) -> void:
+ current_glossary.get_entry(current_entry_name)['case_sensitive'] = button_pressed
+ ResourceSaver.save(current_glossary)
+
+
+## Checks if the [param new_alternatives] has any alternatives that are already
+## used as a key for another entry and returns true if it doesn't.
+func _can_change_alternative(new_alternatives: String) -> bool:
+ for alternative: String in new_alternatives.split(',', false):
+ var stripped_alternative := alternative.strip_edges()
+
+ var value: Variant = current_glossary.entries.get(stripped_alternative, null)
+
+ if value == null:
+ continue
+
+ if value is String:
+ value = current_glossary.entries.get(value, null)
+
+ var value_name: String = value[DialogicGlossary.NAME_PROPERTY]
+
+ if not current_entry_name == value_name:
+ return false
+
+ return true
+
+
+## Checks if [entry_alternatives] has any alternatives that are already
+## used by any entry and returns true if it doesn't.
+## If false, it will set the alternatives text field to a warning color and
+## set an icon.
+## If true, the alternatives text field will be set to the default color and
+## the icon will be removed.
+func _check_entry_alternatives(entry_alternatives: String) -> bool:
+
+ if not _can_change_alternative(entry_alternatives):
+ %EntryAlternatives.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
+ %EntryAlternatives.right_icon = get_theme_icon("StatusError", "EditorIcons")
+ return false
+
+ else:
+ %EntryAlternatives.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
+ %EntryAlternatives.right_icon = null
+
+ return true
+
+
+## The [param new_alternatives] is a passed as a string of comma separated
+## values form the Dialogic editor.
+##
+## Saves the glossary resource file.
+func _on_entry_alternatives_text_changed(new_alternatives: String) -> void:
+ var current_alternatives: Array = current_glossary.get_entry(current_entry_name).get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])
+
+ if not _check_entry_alternatives(new_alternatives):
+ return
+
+ for current_alternative: String in current_alternatives:
+ current_glossary._remove_entry_alias(current_alternative)
+
+ var alternatives := []
+
+ for new_alternative: String in new_alternatives.split(',', false):
+ var stripped_alternative := new_alternative.strip_edges()
+ alternatives.append(stripped_alternative)
+ current_glossary._add_entry_key_alias(current_entry_name, stripped_alternative)
+
+ current_glossary.get_entry(current_entry_name)[DialogicGlossary.ALTERNATIVE_PROPERTY] = alternatives
+ ResourceSaver.save(current_glossary)
+
+
+func _on_entry_title_text_changed(new_text:String) -> void:
+ current_glossary.get_entry(current_entry_name)['title'] = new_text
+ ResourceSaver.save(current_glossary)
+
+
+func _on_entry_text_text_changed() -> void:
+ current_glossary.get_entry(current_entry_name)['text'] = %EntryText.text
+ ResourceSaver.save(current_glossary)
+
+
+func _on_entry_extra_text_changed() -> void:
+ current_glossary.get_entry(current_entry_name)['extra'] = %EntryExtra.text
+ ResourceSaver.save(current_glossary)
+
+
+func _on_entry_enabled_toggled(button_pressed:bool) -> void:
+ current_glossary.get_entry(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.get_entry(current_entry_name).erase('color')
+ %EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], %DefaultColor.color)
+ else:
+ current_glossary.get_entry(current_entry_name)['color'] = %EntryColor.color
+ %EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], %EntryColor.color)
+
+
+func _on_entry_color_color_changed(color:Color) -> void:
+ current_glossary.get_entry(current_entry_name)['color'] = color
+ %EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], color)
+ ResourceSaver.save(current_glossary)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.tscn b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.tscn
new file mode 100644
index 0000000..75e506b
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_editor.tscn
@@ -0,0 +1,319 @@
+[gd_scene load_steps=5 format=3 uid="uid://due48ce7jiudt"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/Glossary/glossary_editor.gd" id="1_tf3p1"]
+[ext_resource type="Texture2D" uid="uid://cenut3sc5cul0" path="res://addons/dialogic/Modules/Glossary/add-glossary.svg" id="2_0elx7"]
+
+[sub_resource type="Image" id="Image_puu06"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_dfvxn"]
+image = SubResource("Image_puu06")
+
+[node name="GlossaryEditor" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+script = ExtResource("1_tf3p1")
+
+[node name="Entries" type="HSplitContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+split_offset = -200
+
+[node name="Settings" type="VBoxContainer" parent="Entries"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.3
+
+[node name="Label" type="Label" parent="Entries/Settings"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Glossaries"
+
+[node name="Glossaries" type="PanelContainer" parent="Entries/Settings"]
+layout_mode = 2
+size_flags_vertical = 3
+theme_type_variation = &"DialogicPanelA"
+
+[node name="Glossaries" type="VBoxContainer" parent="Entries/Settings/Glossaries"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.69
+
+[node name="HBox" type="HBoxContainer" parent="Entries/Settings/Glossaries/Glossaries"]
+layout_mode = 2
+
+[node name="AddGlossaryFile" type="Button" parent="Entries/Settings/Glossaries/Glossaries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "New Glossary"
+icon = ExtResource("2_0elx7")
+
+[node name="LoadGlossaryFile" type="Button" parent="Entries/Settings/Glossaries/Glossaries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "Import Glossary File"
+icon = SubResource("ImageTexture_dfvxn")
+
+[node name="DeleteGlossaryFile" type="Button" parent="Entries/Settings/Glossaries/Glossaries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "Delete Glossary"
+icon = SubResource("ImageTexture_dfvxn")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="Entries/Settings/Glossaries/Glossaries"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="GlossaryList" type="ItemList" parent="Entries/Settings/Glossaries/Glossaries/ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Label2" type="Label" parent="Entries/Settings"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Defaults"
+
+[node name="Defaults" type="VBoxContainer" parent="Entries/Settings"]
+layout_mode = 2
+
+[node name="DefaultsColor" type="HBoxContainer" parent="Entries/Settings/Defaults"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Entries/Settings/Defaults/DefaultsColor"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Color"
+
+[node name="DefaultColor" type="ColorPickerButton" parent="Entries/Settings/Defaults/DefaultsColor"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(50, 0)
+layout_mode = 2
+size_flags_horizontal = 8
+
+[node name="DefCaseSensitive" type="HBoxContainer" parent="Entries/Settings/Defaults"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="Entries/Settings/Defaults/DefCaseSensitive"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Case sensitive"
+
+[node name="DefaultCaseSensitive" type="CheckBox" parent="Entries/Settings/Defaults/DefCaseSensitive"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HSplit" type="HSplitContainer" parent="Entries"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Entries/HSplit"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Label2" type="Label" parent="Entries/HSplit/VBoxContainer"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Entries"
+
+[node name="Tabs" type="PanelContainer" parent="Entries/HSplit/VBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_type_variation = &"DialogicPanelA"
+
+[node name="Entries" type="VBoxContainer" parent="Entries/HSplit/VBoxContainer/Tabs"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.69
+
+[node name="HBox" type="HBoxContainer" parent="Entries/HSplit/VBoxContainer/Tabs/Entries"]
+layout_mode = 2
+
+[node name="AddGlossaryEntry" type="Button" parent="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "New Glossary Entry"
+icon = SubResource("ImageTexture_dfvxn")
+
+[node name="DeleteGlossaryEntry" type="Button" parent="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "Delete Glossary Entry"
+icon = SubResource("ImageTexture_dfvxn")
+
+[node name="EntrySearch" type="LineEdit" parent="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Search"
+right_icon = SubResource("ImageTexture_dfvxn")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="Entries/HSplit/VBoxContainer/Tabs/Entries"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="EntryList" type="ItemList" parent="Entries/HSplit/VBoxContainer/Tabs/Entries/ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+focus_neighbor_right = NodePath("../../../../EntryEditor/Tabs/Entry Settings/EntrySettings/HBox/EntryName")
+
+[node name="EntryEditor" type="ScrollContainer" parent="Entries/HSplit"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+horizontal_scroll_mode = 0
+metadata/_edit_layout_mode = 1
+
+[node name="VBox" type="VBoxContainer" parent="Entries/HSplit/EntryEditor"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Label" type="Label" parent="Entries/HSplit/EntryEditor/VBox"]
+layout_mode = 2
+theme_type_variation = &"DialogicSection"
+text = "Entry Settings"
+
+[node name="Entry Settings" type="VBoxContainer" parent="Entries/HSplit/EntryEditor/VBox"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="EntrySettings" type="GridContainer" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/h_separation = 13
+columns = 2
+
+[node name="Label2" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Name"
+
+[node name="HBox2" type="HBoxContainer" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+
+[node name="EntryName" type="LineEdit" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox2"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+focus_neighbor_left = NodePath("../../../../../../VBoxContainer/Tabs/Entries/ScrollContainer/EntryList")
+theme_override_colors/caret_color = Color(0, 0, 0, 1)
+placeholder_text = "Enter unique name..."
+caret_blink = true
+
+[node name="EntryCaseSensitive" type="Button" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox2"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Case sensitive"
+toggle_mode = true
+icon = SubResource("ImageTexture_dfvxn")
+flat = true
+
+[node name="Label3" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Alternatives"
+
+[node name="EntryAlternatives" type="LineEdit" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+caret_blink = true
+
+[node name="Label4" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Title"
+
+[node name="EntryTitle" type="LineEdit" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+caret_blink = true
+
+[node name="Label5" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Description"
+
+[node name="EntryText" type="TextEdit" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 100)
+layout_mode = 2
+focus_next = NodePath("../EntryExtra")
+wrap_mode = 1
+
+[node name="Label6" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Extra"
+
+[node name="EntryExtra" type="TextEdit" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+wrap_mode = 1
+
+[node name="Label8" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Enabled"
+
+[node name="EntryEnabled" type="CheckBox" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+unique_name_in_owner = true
+layout_mode = 2
+button_pressed = true
+
+[node name="Label7" type="Label" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+size_flags_vertical = 0
+text = "Color"
+
+[node name="HBox" type="HBoxContainer" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings"]
+layout_mode = 2
+
+[node name="EntryCustomColor" type="CheckBox" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="EntryColor" type="ColorPickerButton" parent="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[connection signal="pressed" from="Entries/Settings/Glossaries/Glossaries/HBox/AddGlossaryFile" to="." method="_on_add_glossary_file_pressed"]
+[connection signal="pressed" from="Entries/Settings/Glossaries/Glossaries/HBox/LoadGlossaryFile" to="." method="_on_load_glossary_file_pressed"]
+[connection signal="pressed" from="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox/AddGlossaryEntry" to="." method="_on_add_glossary_entry_pressed"]
+[connection signal="pressed" from="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox/DeleteGlossaryEntry" to="." method="_on_delete_glossary_entry_pressed"]
+[connection signal="text_changed" from="Entries/HSplit/VBoxContainer/Tabs/Entries/HBox/EntrySearch" to="." method="_on_entry_search_text_changed"]
+[connection signal="text_changed" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox2/EntryName" to="." method="_on_entry_name_text_changed"]
+[connection signal="toggled" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox2/EntryCaseSensitive" to="." method="_on_entry_case_sensitive_toggled"]
+[connection signal="text_changed" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/EntryTitle" to="." method="_on_entry_title_text_changed"]
+[connection signal="text_changed" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/EntryText" to="." method="_on_entry_text_text_changed"]
+[connection signal="text_changed" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/EntryExtra" to="." method="_on_entry_extra_text_changed"]
+[connection signal="toggled" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/EntryEnabled" to="." method="_on_entry_enabled_toggled"]
+[connection signal="toggled" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox/EntryCustomColor" to="." method="_on_entry_custom_color_toggled"]
+[connection signal="color_changed" from="Entries/HSplit/EntryEditor/VBox/Entry Settings/EntrySettings/HBox/EntryColor" to="." method="_on_entry_color_color_changed"]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_resource.gd b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_resource.gd
new file mode 100644
index 0000000..638d036
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/glossary_resource.gd
@@ -0,0 +1,340 @@
+@tool
+## Resource used to store glossary entries. Can be saved to disc and used as a glossary.
+## Add/create glossaries fom the glossaries editor
+class_name DialogicGlossary
+extends Resource
+
+## Stores all entries for the glossary.
+##
+## The value may either be a dictionary, representing an entry, or
+## a string, representing the actual key for the key used.
+## The string key-value pairs are the alias keys, they allow to redirect
+## the actual glossary entry.
+@export var entries := {}
+
+## If false, no entries from this glossary will be shown
+@export var enabled: bool = true
+
+## Refers to the translation type of this resource used for CSV translation files.
+const RESOURCE_NAME := "Glossary"
+## The name of glossary entries, the value is the key in [member entries].
+## This constant is used for CSV translation files.
+const NAME_PROPERTY := "name"
+## Property in a glossary entry. Alternative words for the entry name.
+const ALTERNATIVE_PROPERTY := "alternatives"
+## Property in a glossary entry.
+const TITLE_PROPERTY := "title"
+## Property in a glossary entry.
+const TEXT_PROPERTY := "text"
+## Property in a glossary entry.
+const EXTRA_PROPERTY := "extra"
+## Property in a glossary entry. The translation ID of the entry.
+## May be empty if the entry has not been translated yet.
+const TRANSLATION_PROPERTY := "_translation_id"
+## Property in a glossary entry.
+const REGEX_OPTION_PROPERTY := "regex_options"
+## Prefix used for private properties in entries.
+## Ignored when entries are translated.
+const PRIVATE_PROPERTY_PREFIX := "_"
+
+
+## Private ID assigned when this glossary is translated.
+@export var _translation_id := ""
+
+## Private lookup table used to find the translation ID of a glossary entry.
+## The keys (String) are all translated words that may trigger a glossary entry to
+## be shown.
+## The values (String) are the translation ID.
+@export var _translation_keys := {}
+
+
+
+## Removes an entry and all its aliases (alternative property) from
+## the glossary.
+## [param entry_key] may be an entry name or an alias.
+##
+## Returns true if the entry matching the given [param entry_key] was found.
+func remove_entry(entry_key: String) -> bool:
+ var entry: Dictionary = get_entry(entry_key)
+
+ if entry.is_empty():
+ return false
+
+ var aliases: Array = entry.get(ALTERNATIVE_PROPERTY, [])
+
+ for alias: String in aliases:
+ _remove_entry_alias(alias)
+
+ entries.erase(entry_key)
+
+ return true
+
+
+## This is an internal method.
+## Erases an entry alias key based the given [param entry_key].
+##
+## Returns true if [param entry_key] lead to a value and the value
+## was an alias.
+##
+## This method does not update the entry's alternative property.
+func _remove_entry_alias(entry_key: String) -> bool:
+ var value: Variant = entries.get(entry_key, null)
+
+ if value == null or value is Dictionary:
+ return false
+
+ entries.erase(entry_key)
+
+ return true
+
+
+## Updates the glossary entry's name and related alias keys.
+## The [param old_entry_key] is the old unique name of the entry.
+## The [param new_entry_key] is the new unique name of the entry.
+##
+## This method fails if the [param old_entry_key] does not exist.
+
+## Do not use this to update alternative names.
+## In order to update alternative names, delete all with
+## [method _remove_entry_alias] and then add them again with
+## [method _add_entry_key_alias].
+func replace_entry_key(old_entry_key: String, new_entry_key: String) -> void:
+ var entry := get_entry(old_entry_key)
+
+ if entry == null:
+ return
+
+ entry.name = new_entry_key
+
+ entries.erase(old_entry_key)
+ entries[new_entry_key] = entry
+
+
+## Gets the glossary entry for the given [param entry_key].
+## If there is no matching entry, an empty Dictionary will be returned.
+## Valid glossary entry dictionaries will never be empty.
+func get_entry(entry_key: String) -> Dictionary:
+ var entry: Variant = entries.get(entry_key, {})
+
+ # Handle alias value.
+ if entry is String:
+ entry = entries.get(entry, {})
+
+ return entry
+
+
+## This is an internal method.
+## The [param entry_key] must be valid entry key for an entry.
+## Adds the [param alias] as a valid entry key for that entry.
+##
+## Returns the index of the entry, -1 if the entry does not exist.
+func _add_entry_key_alias(entry_key: String, alias: String) -> bool:
+ var entry := get_entry(entry_key)
+ var alias_entry := get_entry(alias)
+
+ if not entry.is_empty() and alias_entry.is_empty():
+ entries[alias] = entry_key
+ return true
+
+ return false
+
+
+## Adds [param entry] to the glossary if it does not exist.
+## If it does exist, returns false.
+func try_add_entry(entry: Dictionary) -> bool:
+ var entry_key: String = entry[NAME_PROPERTY]
+
+ if entries.has(entry_key):
+ return false
+
+ entries[entry_key] = entry
+
+ for alternative: String in entry.get(ALTERNATIVE_PROPERTY, []):
+ entries[alternative.strip_edges()] = entry_key
+
+ return true
+
+
+## Returns an array of words that can trigger the glossary popup.
+## This method respects whether translation is enabled or not.
+## The words may be: The entry key and the alternative words.
+func _get_word_options(entry_key: String) -> Array:
+ var word_options: Array = []
+
+ var translation_enabled: bool = ProjectSettings.get_setting("dialogic/translation/enabled", false)
+
+ if not translation_enabled:
+ word_options.append(entry_key)
+
+ for alternative: String in get_entry(entry_key).get(ALTERNATIVE_PROPERTY, []):
+ word_options.append(alternative.strip_edges())
+
+ return word_options
+
+ var translation_entry_key_id: String = get_property_translation_key(entry_key, NAME_PROPERTY)
+
+ if translation_entry_key_id.is_empty():
+ return []
+
+ var translated_entry_key := tr(translation_entry_key_id)
+
+ if not translated_entry_key == translation_entry_key_id:
+ word_options.append(translated_entry_key)
+
+ var translation_alternatives_id: String = get_property_translation_key(entry_key, ALTERNATIVE_PROPERTY)
+ var translated_alternatives_str := tr(translation_alternatives_id)
+
+ if not translated_alternatives_str == translation_alternatives_id:
+ var translated_alternatives := translated_alternatives_str.split(",")
+
+ for alternative: String in translated_alternatives:
+ word_options.append(alternative.strip_edges())
+
+ return word_options
+
+
+## Gets the regex option for the given [param entry_key].
+## If the regex option does not exist, it will be generated.
+##
+## A regex option is the accumulation of valid words that can trigger the
+## glossary popup.
+##
+## The [param entry_key] must be valid or an error will occur.
+func get_set_regex_option(entry_key: String) -> String:
+ var entry: Dictionary = get_entry(entry_key)
+
+ var regex_options: Dictionary = entry.get(REGEX_OPTION_PROPERTY, {})
+
+ if regex_options.is_empty():
+ entry[REGEX_OPTION_PROPERTY] = regex_options
+
+ var locale_key: String = TranslationServer.get_locale()
+ var regex_option: String = regex_options.get(locale_key, "")
+
+ if not regex_option.is_empty():
+ return regex_option
+
+ var word_options: Array = _get_word_options(entry_key)
+ regex_option = "|".join(word_options)
+
+ regex_options[locale_key] = regex_option
+
+ return regex_option
+
+
+#region ADD AND CLEAR TRANSLATION KEYS
+
+## This is automatically called, no need to use this.
+func add_translation_id() -> String:
+ _translation_id = DialogicUtil.get_next_translation_id()
+ return _translation_id
+
+
+## Removes the translation ID of this glossary.
+func remove_translation_id() -> void:
+ _translation_id = ""
+
+
+## Removes the translation ID of all glossary entries.
+func remove_entry_translation_ids() -> void:
+ for entry: Variant in entries.values():
+
+ # Ignore aliases.
+ if entry is String:
+ continue
+
+ if entry.has(TRANSLATION_PROPERTY):
+ entry[TRANSLATION_PROPERTY] = ""
+
+
+## Clears the lookup tables using translation keys.
+func clear_translation_keys() -> void:
+ const RESOURCE_NAME_KEY := RESOURCE_NAME + "/"
+
+ for translation_key: String in entries.keys():
+
+ if translation_key.begins_with(RESOURCE_NAME_KEY):
+ entries.erase(translation_key)
+
+ _translation_keys.clear()
+
+#endregion
+
+
+#region GET AND SET TRANSLATION IDS AND KEYS
+
+## Returns a key used to reference this glossary in the translation CSV file.
+##
+## Time complexity: O(1)
+func get_property_translation_key(entry_key: String, property: String) -> String:
+ var entry := get_entry(entry_key)
+
+ if entry == null:
+ return ""
+
+ var entry_translation_key: String = entry.get(TRANSLATION_PROPERTY, "")
+
+ if entry_translation_key.is_empty() or _translation_id.is_empty():
+ return ""
+
+ var glossary_csv_key := (RESOURCE_NAME
+ .path_join(_translation_id)
+ .path_join(entry_translation_key)
+ .path_join(property))
+
+ return glossary_csv_key
+
+
+
+## Returns the translation key prefix for this glossary.
+## The resulting format will look like this: Glossary/a2/
+## This prefix can be used to find translations for this glossary.
+func _get_glossary_translation_id_prefix() -> String:
+ return (
+ DialogicGlossary.RESOURCE_NAME
+ .path_join(_translation_id)
+ )
+
+
+## Returns the translation key for the given [param glossary_translation_id] and
+## [param entry_translation_id].
+##
+## By key, we refer to the uniquely named property per translation entry.
+##
+## The resulting format will look like this: Glossary/a2/b4/name
+func _get_glossary_translation_key(entry_translation_id: String, property: String) -> String:
+ return (
+ DialogicGlossary.RESOURCE_NAME
+ .path_join(_translation_id)
+ .path_join(entry_translation_id)
+ .path_join(property)
+ )
+
+
+## Tries to get the glossary entry's translation ID.
+## If it does not exist, a new one will be generated.
+func get_set_glossary_entry_translation_id(entry_key: String) -> String:
+ var glossary_entry: Dictionary = get_entry(entry_key)
+ var entry_translation_id := ""
+
+ var glossary_translation_id: String = glossary_entry.get(TRANSLATION_PROPERTY, "")
+
+ if glossary_translation_id.is_empty():
+ entry_translation_id = DialogicUtil.get_next_translation_id()
+ glossary_entry[TRANSLATION_PROPERTY] = entry_translation_id
+
+ else:
+ entry_translation_id = glossary_entry[TRANSLATION_PROPERTY]
+
+ return entry_translation_id
+
+
+## Tries to get the glossary's translation ID.
+## If it does not exist, a new one will be generated.
+func get_set_glossary_translation_id() -> String:
+ if _translation_id == null or _translation_id.is_empty():
+ add_translation_id()
+
+ return _translation_id
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.png.import b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.png.import
new file mode 100644
index 0000000..e921f64
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.png.import
@@ -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
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg
new file mode 100644
index 0000000..175e284
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg.import
new file mode 100644
index 0000000..54ca7b9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b5xwnxdb7064n"
+path="res://.godot/imported/icon.svg-460155d4e7ede28c792d9fc7314b74bc.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/Glossary/icon.svg"
+dest_files=["res://.godot/imported/icon.svg-460155d4e7ede28c792d9fc7314b74bc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/index.gd b/addons/dialogic-main/addons/dialogic/Modules/Glossary/index.gd
new file mode 100644
index 0000000..4b0f997
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/index.gd
@@ -0,0 +1,14 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_events() -> Array:
+ return []
+# return [this_folder.path_join('event_glossary.gd')]
+
+func _get_editors() -> Array:
+ return [this_folder.path_join('glossary_editor.tscn')]
+
+func _get_subsystems() -> Array:
+ return [{'name':'Glossary', 'script':this_folder.path_join('subsystem_glossary.gd')}]
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Glossary/subsystem_glossary.gd b/addons/dialogic-main/addons/dialogic/Modules/Glossary/subsystem_glossary.gd
new file mode 100644
index 0000000..d438bc4
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Glossary/subsystem_glossary.gd
@@ -0,0 +1,174 @@
+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
+
+## Any key in this dictionary will overwrite the color for any item with that name.
+var color_overrides := {}
+
+const SETTING_DEFAULT_COLOR := 'dialogic/glossary/default_color'
+
+
+#region STATE
+####################################################################################################
+
+func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ glossaries = []
+
+ for path: String in ProjectSettings.get_setting('dialogic/glossary/glossary_files', []):
+ add_glossary(path)
+
+#endregion
+
+
+#region MAIN METHODS
+####################################################################################################
+
+func parse_glossary(text: String) -> String:
+ if not enabled:
+ return text
+
+ var def_case_sensitive: bool = ProjectSettings.get_setting('dialogic/glossary/default_case_sensitive', true)
+ var def_color: Color = ProjectSettings.get_setting(SETTING_DEFAULT_COLOR, Color.POWDER_BLUE)
+ var regex := RegEx.new()
+
+ for glossary: DialogicGlossary in glossaries:
+
+ if !glossary.enabled:
+ continue
+
+ for entry_value: Variant in glossary.entries.values():
+
+ if not entry_value is Dictionary:
+ continue
+
+ var entry: Dictionary = entry_value
+ var entry_key: String = entry.get(DialogicGlossary.NAME_PROPERTY, "")
+
+ # Older versions of the glossary resource do not have a property
+ # for their name, we must skip these.
+ # They can be updated by opening the resource in the glossary
+ # editor.
+ if entry_key.is_empty():
+ continue
+
+ if not entry.get('enabled', true):
+ continue
+
+ var regex_options := glossary.get_set_regex_option(entry_key)
+
+ if regex_options.is_empty():
+ continue
+
+ var pattern: String = '(?<=\\W|^)(?' + regex_options + ')(?!])(?=\\W|$)'
+
+ if entry.get('case_sensitive', def_case_sensitive):
+ regex.compile(pattern)
+
+ else:
+ regex.compile('(?i)'+pattern)
+
+ var color: String = entry.get('color', def_color).to_html()
+
+ if entry_key in color_overrides:
+ color = color_overrides[entry_key].to_html()
+
+ text = regex.sub(text,
+ '[url=' + entry_key + ']' +
+ '[color=' + color + ']${word}[/color]' +
+ '[/url]',
+ true
+ )
+
+ return text
+
+
+func add_glossary(path:String) -> void:
+ if ResourceLoader.exists(path):
+ var resource: DialogicGlossary = load(path)
+
+ if resource is DialogicGlossary:
+ glossaries.append(resource)
+ else:
+ printerr('[Dialogic] The glossary file "' + path + '" is missing. Make sure it exists.')
+
+
+## Iterates over all glossaries and returns the first one that matches the
+## [param entry_key].
+##
+## Runtime complexity:
+## O(n), where n is the number of glossaries.
+func find_glossary(entry_key: String) -> DialogicGlossary:
+ for glossary: DialogicGlossary in glossaries:
+
+ if glossary.entries.has(entry_key):
+ return glossary
+
+ return null
+
+
+## Returns the first match for a given entry key.
+## If translation is available and enabled, it will be translated
+func get_entry(entry_key: String) -> Dictionary:
+ var glossary: DialogicGlossary = dialogic.Glossary.find_glossary(entry_key)
+
+ var result := {
+ "title": "",
+ "text": "",
+ "extra": "",
+ "color": Color.WHITE,
+ }
+
+ if glossary == null:
+ return {}
+
+ var is_translation_enabled: bool = ProjectSettings.get_setting('dialogic/translation/enabled', false)
+
+ var entry := glossary.get_entry(entry_key)
+
+ if entry.is_empty():
+ return {}
+
+ result.color = entry.get("color")
+ if result.color == null:
+ result.color = ProjectSettings.get_setting(SETTING_DEFAULT_COLOR, Color.POWDER_BLUE)
+
+ if is_translation_enabled and not glossary._translation_id.is_empty():
+ var translation_key: String = glossary._translation_keys.get(entry_key)
+ var last_slash := translation_key.rfind('/')
+
+ if last_slash == -1:
+ return {}
+
+ var tr_base := translation_key.substr(0, last_slash)
+
+ result.title = translate(tr_base, "title", entry)
+ result.text = translate(tr_base, "text", entry)
+ result.extra = translate(tr_base, "extra", entry)
+ else:
+ result.title = entry.get("title", "")
+ result.text = entry.get("text", "")
+ result.extra = entry.get("extra", "")
+
+ ## PARSE TEXTS FOR VARIABLES
+ result.title = dialogic.VAR.parse_variables(result.title)
+ result.text = dialogic.VAR.parse_variables(result.text)
+ result.extra = dialogic.VAR.parse_variables(result.extra)
+
+ return result
+
+
+
+## Tries to translate the property with the given
+func translate(tr_base: String, property: StringName, fallback_entry: Dictionary) -> String:
+ var tr_key := tr_base.path_join(property)
+ var tr_value := tr(tr_key)
+
+ if tr_key == tr_value:
+ tr_value = fallback_entry.get(property, "")
+
+ return tr_value
diff --git a/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png
new file mode 100644
index 0000000..33a594c
Binary files /dev/null and b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png differ
diff --git a/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import
new file mode 100644
index 0000000..d206b20
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dtt0l7qbkknfx"
+path="res://.godot/imported/highlight_portrait_thumbnail.png-5a072ec4a63678946d6da3972b5ece49.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png"
+dest_files=["res://.godot/imported/highlight_portrait_thumbnail.png-5a072ec4a63678946d6da3972b5ece49.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/index.gd b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/index.gd
new file mode 100644
index 0000000..ec9b315
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/index.gd
@@ -0,0 +1,17 @@
+@tool
+extends DialogicIndexer
+
+
+func _get_portrait_scene_presets() -> Array[Dictionary]:
+ return [
+ {
+ "path": this_folder.path_join("simple_highlight_portrait.tscn"),
+ "name": "Simple Highlight Portrait",
+ "description": "A portrait scene that displays a simple image, but changes color and moves to the front when this character is speaking.",
+ "author":"Dialogic",
+ "type": "General",
+ "icon":"",
+ "preview_image":[this_folder.path_join("highlight_portrait_thumbnail.png")],
+ "documentation":"",
+ },
+ ]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd
new file mode 100644
index 0000000..5ed6649
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd
@@ -0,0 +1,30 @@
+@tool
+extends DialogicPortrait
+
+@export_group('Main')
+@export_file var image := ""
+
+var unhighlighted_color := Color.DARK_GRAY
+var _prev_z_index := 0
+
+## Load anything related to the given character and portrait
+func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
+ apply_character_and_portrait(passed_character, passed_portrait)
+
+ apply_texture($Portrait, image)
+
+
+func _ready() -> void:
+ if not Engine.is_editor_hint():
+ self.modulate = unhighlighted_color
+
+
+func _highlight() -> void:
+ create_tween().tween_property(self, 'modulate', Color.WHITE, 0.15)
+ _prev_z_index = DialogicUtil.autoload().Portraits.get_character_info(character).get('z_index', 0)
+ DialogicUtil.autoload().Portraits.change_character_z_index(character, 99)
+
+
+func _unhighlight() -> void:
+ create_tween().tween_property(self, 'modulate', unhighlighted_color, 0.15)
+ DialogicUtil.autoload().Portraits.change_character_z_index(character, _prev_z_index)
diff --git a/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.tscn b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.tscn
new file mode 100644
index 0000000..cf814eb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.tscn
@@ -0,0 +1,9 @@
+[gd_scene load_steps=2 format=3 uid="uid://br18lgpga2y2v"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd" id="1_ceqva"]
+
+[node name="DefaultPortrait" type="Node2D"]
+script = ExtResource("1_ceqva")
+
+[node name="Portrait" type="Sprite2D" parent="."]
+centered = false
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg b/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg
new file mode 100644
index 0000000..236ca35
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg.import b/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg.import
new file mode 100644
index 0000000..966511e
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/definition.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dlwtdexd63bxi"
+path="res://.godot/imported/definition.svg-9680d5589464333c78883fec3809a20e.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/History/definition.svg"
+dest_files=["res://.godot/imported/definition.svg-9680d5589464333c78883fec3809a20e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/event_history.gd b/addons/dialogic-main/addons/dialogic/Modules/History/event_history.gd
new file mode 100644
index 0000000..180d7f0
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/event_history.gd
@@ -0,0 +1,76 @@
+@tool
+class_name DialogicHistoryEvent
+extends DialogicEvent
+
+## Event that allows clearing, pausing and resuming of history functionality.
+
+enum Actions {CLEAR, PAUSE, RESUME}
+
+### Settings
+
+## The type of action: Clear, Pause or Resume
+var action := Actions.PAUSE
+
+
+################################################################################
+## EXECUTION
+################################################################################
+
+func _execute() -> void:
+ match action:
+ Actions.CLEAR:
+ dialogic.History.simple_history_content = []
+ Actions.PAUSE:
+ dialogic.History.simple_history_enabled = false
+ Actions.RESUME:
+ dialogic.History.simple_history_enabled = true
+
+ finish()
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "History"
+ set_default_color('Color9')
+ event_category = "Other"
+ event_sorting_index = 20
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+
+func get_shortcode() -> String:
+ return "history"
+
+func get_shortcode_parameters() -> Dictionary:
+ return {
+ #param_name : property_info
+ "action" : {"property": "action", "default": Actions.PAUSE,
+ "suggestions": func(): return {"Clear":{'value':0, 'text_alt':['clear']}, "Pause":{'value':1, 'text_alt':['pause']}, "Resume":{'value':2, 'text_alt':['resume', 'start']}}},
+ }
+
+################################################################################
+## EDITOR REPRESENTATION
+################################################################################
+
+func build_event_editor() -> void:
+ add_header_edit('action', ValueType.FIXED_OPTIONS, {
+ 'options': [
+ {
+ 'label': 'Pause History',
+ 'value': Actions.PAUSE,
+ },
+ {
+ 'label': 'Resume History',
+ 'value': Actions.RESUME,
+ },
+ {
+ 'label': 'Clear History',
+ 'value': Actions.CLEAR,
+ },
+ ]
+ })
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg b/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg
new file mode 100644
index 0000000..19c9239
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg.import b/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg.import
new file mode 100644
index 0000000..7645211
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/icon.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://1n5bqdv34pmy"
+path="res://.godot/imported/icon.svg-8591cd39fec87d07d2d4eb211b1b473a.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/dialogic-main/addons/dialogic/Modules/History/icon.svg"
+dest_files=["res://.godot/imported/icon.svg-8591cd39fec87d07d2d4eb211b1b473a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/index.gd b/addons/dialogic-main/addons/dialogic/Modules/History/index.gd
new file mode 100644
index 0000000..7791b1f
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/index.gd
@@ -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')]
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.gd b/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.gd
new file mode 100644
index 0000000..97b5cd9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.gd
@@ -0,0 +1,29 @@
+@tool
+extends DialogicSettingsPage
+
+
+func _get_priority() -> int:
+ return -10
+
+
+func _ready() -> void:
+ %SimpleHistoryEnabled.toggled.connect(setting_toggled.bind('dialogic/history/simple_history_enabled'))
+ %SimpleHistorySave.toggled.connect(setting_toggled.bind('dialogic/history/simple_history_save'))
+ %FullHistoryEnabled.toggled.connect(setting_toggled.bind('dialogic/history/full_history_enabled'))
+ %FullHistorySave.toggled.connect(setting_toggled.bind('dialogic/history/full_history_save'))
+ %AlreadyReadHistoryEnabled.toggled.connect(setting_toggled.bind('dialogic/history/visited_event_history_enabled'))
+ %SaveOnAutoSaveToggle.toggled.connect(setting_toggled.bind('dialogic/history/save_on_autosave'))
+ %SaveOnSaveToggle.toggled.connect(setting_toggled.bind('dialogic/history/save_on_save'))
+
+
+func _refresh() -> void:
+ %SimpleHistoryEnabled.button_pressed = ProjectSettings.get_setting('dialogic/history/simple_history_enabled', false)
+ %SimpleHistorySave.button_pressed = ProjectSettings.get_setting('dialogic/history/simple_history_save', false)
+ %FullHistoryEnabled.button_pressed = ProjectSettings.get_setting('dialogic/history/full_history_enabled', false)
+ %FullHistorySave.button_pressed = ProjectSettings.get_setting('dialogic/history/full_history_save', false)
+ %AlreadyReadHistoryEnabled.button_pressed = ProjectSettings.get_setting('dialogic/history/visited_event_history_enabled', false)
+
+
+func setting_toggled(button_pressed: bool, setting: String) -> void:
+ ProjectSettings.set_setting(setting, button_pressed)
+ ProjectSettings.save()
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.tscn b/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.tscn
new file mode 100644
index 0000000..ed55edb
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/settings_history.tscn
@@ -0,0 +1,176 @@
+[gd_scene load_steps=5 format=3 uid="uid://b5yq6xh412ilm"]
+
+[ext_resource type="Script" path="res://addons/dialogic/Modules/History/settings_history.gd" id="1_hbhst"]
+[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_wefye"]
+
+[sub_resource type="Image" id="Image_3clns"]
+data = {
+"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
+"format": "RGBA8",
+"height": 16,
+"mipmaps": false,
+"width": 16
+}
+
+[sub_resource type="ImageTexture" id="ImageTexture_irr0a"]
+image = SubResource("Image_3clns")
+
+[node name="History" type="PanelContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_type_variation = &"DialogicPanelA"
+script = ExtResource("1_hbhst")
+
+[node name="HistoryOptions" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Title3" type="Label" parent="HistoryOptions"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Simple History"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HistoryOptions/HBoxContainer"]
+layout_mode = 2
+text = "Enabled"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainer" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "When enabled, some events (Text, Join, Leave, Choice) will store a log.
+Also, the default layout will feature the log panel option."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "When enabled, some events (Text, Join, Leave, Choice) will store a log.
+Also, the default layout will feature the log panel option."
+
+[node name="SimpleHistoryEnabled" type="CheckBox" parent="HistoryOptions/HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HistoryOptions/HBoxContainer2"]
+layout_mode = 2
+text = "Save and Load"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainer2" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "When enabled, the simple history is included in the savegame."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "When enabled, the simple history is included in the savegame. Also, it is reset on Dialogic.clear(FULL_CLEAR)."
+
+[node name="SimpleHistorySave" type="CheckBox" parent="HistoryOptions/HBoxContainer2"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Title" type="Label" parent="HistoryOptions"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Full History"
+
+[node name="HBoxContainer5" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HistoryOptions/HBoxContainer5"]
+layout_mode = 2
+text = "Enabled"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainer5" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "When enabled, stores a copy of each event."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "When enabled, stores a copy of each event."
+
+[node name="FullHistoryEnabled" type="CheckBox" parent="HistoryOptions/HBoxContainer5"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HBoxContainer6" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="HistoryOptions/HBoxContainer6"]
+layout_mode = 2
+text = "Save and Load"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainer6" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "When enabled, the full history is included in the savegame."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "When enabled, the full history is included in the savegame. Also, it is reset on Dialogic.clear(FULL_CLEAR)."
+
+[node name="FullHistorySave" type="CheckBox" parent="HistoryOptions/HBoxContainer6"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Title2" type="Label" parent="HistoryOptions"]
+layout_mode = 2
+theme_type_variation = &"DialogicSettingsSection"
+text = "Seen Events History"
+
+[node name="HBoxContainer4" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="EnabledLabel" type="Label" parent="HistoryOptions/HBoxContainer4"]
+layout_mode = 2
+text = "Enabled"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainer4" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "Remembers whether events were already met in the timeline.
+When enabled the signals \"Dialogic.History.visited_event\" and \"Dialogic.History.unvisited_event\" are emitted.
+"
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "Remembers whether events were already met in the timeline.
+When enabled the signals \"Dialogic.History.visited_event\" and \"Dialogic.History.unvisited_event\" are emitted.
+"
+
+[node name="AlreadyReadHistoryEnabled" type="CheckBox" parent="HistoryOptions/HBoxContainer4"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HBoxContainerSaveOnAutoSave" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="EnabledLabel" type="Label" parent="HistoryOptions/HBoxContainerSaveOnAutoSave"]
+layout_mode = 2
+text = "Save on Auto-Save signal"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainerSaveOnAutoSave" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "Stores the already-visited history in a global save file when an Auto-Save occurs.
+The Auto-Save is part of the Save settings."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "Stores the already-visited history in a global save file when an Auto-Save occurs.
+The Auto-Save is part of the Save settings."
+
+[node name="SaveOnAutoSaveToggle" type="CheckBox" parent="HistoryOptions/HBoxContainerSaveOnAutoSave"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="HBoxContainerSaveOnSave" type="HBoxContainer" parent="HistoryOptions"]
+layout_mode = 2
+
+[node name="EnabledLabel" type="Label" parent="HistoryOptions/HBoxContainerSaveOnSave"]
+layout_mode = 2
+text = "Save on Save signal"
+
+[node name="HintTooltip" parent="HistoryOptions/HBoxContainerSaveOnSave" instance=ExtResource("2_wefye")]
+layout_mode = 2
+tooltip_text = "Stores the already-visited history in a global save file when a normal Save occurs.
+This can be done via the Dialogic.Save.save method.
+This setting ignores Auto-Saves."
+texture = SubResource("ImageTexture_irr0a")
+hint_text = "Stores the already-visited history in a global save file when a normal Save occurs.
+This can be done via the Dialogic.Save.save method.
+This setting ignores Auto-Saves."
+
+[node name="SaveOnSaveToggle" type="CheckBox" parent="HistoryOptions/HBoxContainerSaveOnSave"]
+unique_name_in_owner = true
+layout_mode = 2
diff --git a/addons/dialogic-main/addons/dialogic/Modules/History/subsystem_history.gd b/addons/dialogic-main/addons/dialogic/Modules/History/subsystem_history.gd
new file mode 100644
index 0000000..d1371ea
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/History/subsystem_history.gd
@@ -0,0 +1,305 @@
+extends DialogicSubsystem
+
+## Subsystem that manages history storing.
+
+signal open_requested
+signal close_requested
+
+
+## Simple history that stores limited information
+## Used for the history display
+var simple_history_enabled := false
+var simple_history_save := false
+var simple_history_content : Array[Dictionary] = []
+signal simple_history_changed
+
+## Whether to keep a history of every Dialogic event encountered.
+var full_event_history_enabled := false
+var full_event_history_save := false
+
+## The full history of all Dialogic events encountered.
+## Requires [member full_event_history_enabled] to be true.
+var full_event_history_content: Array[DialogicEvent] = []
+
+## Emitted if a new event has been inserted into the full event history.
+signal full_event_history_changed
+
+## Read text history
+## Stores which text events and choices have already been visited
+var visited_event_history_enabled := false
+
+## A history of visited Dialogic events.
+var visited_event_history_content := {}
+
+## Whether the last event has been encountered for the first time.
+var _visited_last_event := false
+
+## Emitted if an encountered timeline event has been inserted into the visited
+## event history.
+##
+## This will trigger only once per unique event instance.
+signal visited_event
+
+## Emitted if an encountered timeline event has not been visited before.
+signal unvisited_event
+
+## Used to store [member visited_event_history_content] in the global info file.
+## You can change this to a custom name if you want to use a different key
+## in the global save info file.
+var visited_event_save_key := "visited_event_history_content"
+
+## Whether to automatically save the already-visited history on auto-save.
+var save_visited_history_on_autosave := false:
+ set(value):
+ save_visited_history_on_autosave = value
+ _update_saved_connection(value)
+
+
+## Whether to automatically save the already-visited history on manual save.
+var save_visited_history_on_save := false:
+ set(value):
+ save_visited_history_on_save = value
+ _update_saved_connection(value)
+
+
+## Starts and stops the connection to the [subsystem Save] subsystem's [signal saved] signal.
+func _update_saved_connection(to_connect: bool) -> void:
+ if to_connect:
+ if not DialogicUtil.autoload().Save.saved.is_connected(_on_save):
+ DialogicUtil.autoload().Save.saved.connect(_on_save)
+
+ else:
+ if DialogicUtil.autoload().Save.saved.is_connected(_on_save):
+ DialogicUtil.autoload().Save.saved.disconnect(_on_save)
+
+
+#region INITIALIZE
+####################################################################################################
+
+func _ready() -> void:
+ dialogic.event_handled.connect(store_full_event)
+ dialogic.event_handled.connect(_check_seen)
+
+ simple_history_enabled = ProjectSettings.get_setting('dialogic/history/simple_history_enabled', simple_history_enabled)
+ simple_history_save = ProjectSettings.get_setting('dialogic/history/simple_history_save', simple_history_save)
+ full_event_history_enabled = ProjectSettings.get_setting('dialogic/history/full_history_enabled', full_event_history_enabled)
+ full_event_history_save = ProjectSettings.get_setting('dialogic/history/full_history_save', full_event_history_save)
+ visited_event_history_enabled = ProjectSettings.get_setting('dialogic/history/visited_event_history_enabled', visited_event_history_enabled)
+
+
+
+func _on_save(info: Dictionary) -> void:
+ var is_autosave: bool = info["is_autosave"]
+
+ var save_on_autosave := save_visited_history_on_autosave and is_autosave
+ var save_on_save := save_visited_history_on_save and not is_autosave
+
+ if save_on_save or save_on_autosave:
+ save_visited_history()
+
+
+func post_install() -> void:
+ save_visited_history_on_autosave = ProjectSettings.get_setting('dialogic/history/save_on_autosave', save_visited_history_on_autosave)
+ save_visited_history_on_save = ProjectSettings.get_setting('dialogic/history/save_on_save', save_visited_history_on_save)
+
+
+func clear_game_state(clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
+ if clear_flag == DialogicGameHandler.ClearFlags.FULL_CLEAR:
+ if simple_history_save:
+ simple_history_content = []
+ dialogic.current_state_info.erase("history_simple")
+ if full_event_history_save:
+ full_event_history_content = []
+ dialogic.current_state_info.erase("history_full")
+
+
+func load_game_state(load_flag := LoadFlags.FULL_LOAD) -> void:
+ if load_flag == LoadFlags.FULL_LOAD:
+ if simple_history_save and dialogic.current_state_info.has("history_simple"):
+ simple_history_content.assign(dialogic.current_state_info["history_simple"])
+
+ if full_event_history_save and dialogic.current_state_info.has("history_full"):
+ full_event_history_content = []
+
+ for event_text in dialogic.current_state_info["history_full"]:
+ var event: DialogicEvent
+ for i in DialogicResourceUtil.get_event_cache():
+ if i.is_valid_event(event_text):
+ event = i.duplicate()
+ break
+ event.from_text(event_text)
+ full_event_history_content.append(event)
+
+
+func save_game_state() -> void:
+ if simple_history_save:
+ dialogic.current_state_info["history_simple"] = Array(simple_history_content)
+ else:
+ dialogic.current_state_info.erase("history_simple")
+ if full_event_history_save:
+ dialogic.current_state_info["history_full"] = []
+ for event in full_event_history_content:
+ dialogic.current_state_info["history_full"].append(event.to_text())
+ else:
+ dialogic.current_state_info.erase("history_full")
+
+
+func open_history() -> void:
+ open_requested.emit()
+
+
+func close_history() -> void:
+ close_requested.emit()
+
+#endregion
+
+
+#region SIMPLE HISTORY
+####################################################################################################
+
+func store_simple_history_entry(text:String, event_type:String, extra_info := {}) -> void:
+ if !simple_history_enabled: return
+ extra_info['text'] = text
+ extra_info['event_type'] = event_type
+ simple_history_content.append(extra_info)
+ simple_history_changed.emit()
+
+
+func get_simple_history() -> Array:
+ return simple_history_content
+
+#endregion
+
+
+#region FULL EVENT HISTORY
+####################################################################################################
+
+## Called on each event.
+func store_full_event(event: DialogicEvent) -> void:
+ if !full_event_history_enabled: return
+ full_event_history_content.append(event)
+ full_event_history_changed.emit()
+
+
+#region ALREADY READ HISTORY
+####################################################################################################
+
+## Takes the current timeline event and creates a unique key for it.
+## Uses the timeline resource path as well.
+func _current_event_key() -> String:
+ var resource_path := dialogic.current_timeline.resource_path
+ var event_index := dialogic.current_event_idx
+ var event_key := _get_event_key(event_index, resource_path)
+
+ return event_key
+
+## Composes an event key from the event index and the timeline path.
+## If either of these variables are in an invalid state, the resulting
+## key may be wrong.
+## There are no safety checks in place.
+func _get_event_key(event_index: int, timeline_path: String) -> String:
+ var event_idx := str(event_index)
+ var event_key := timeline_path + event_idx
+
+ return event_key
+
+
+## Called if an event is marked as visited.
+func mark_event_as_visited(event_index := dialogic.current_event_idx, timeline := dialogic.current_timeline) -> void:
+ if !visited_event_history_enabled:
+ return
+
+ var event_key := _get_event_key(event_index, timeline.resource_path)
+
+ visited_event_history_content[event_key] = event_index
+
+
+## Called on each event, but we filter for Text events.
+func _check_seen(event: DialogicEvent) -> void:
+ if !visited_event_history_enabled:
+ return
+
+ # At this point, we only care about Text events.
+ # There may be a more elegant way of filtering events.
+ # Especially since custom events require this event name.
+ if event.event_name != "Text":
+ return
+
+ var event_key := _current_event_key()
+
+ if event_key in visited_event_history_content:
+ visited_event.emit()
+ _visited_last_event = true
+
+ else:
+ unvisited_event.emit()
+ _visited_last_event = false
+
+
+## Whether the last event has been visited for the first time or not.
+## This will return `true` exactly once for each unique timeline event instance.
+func has_last_event_been_visited() -> bool:
+ return _visited_last_event
+
+
+## If called with with no arguments, the method will return whether
+## the last encountered event was visited before.
+##
+## Otherwise, if [param event_index] and [param timeline] are passed,
+## the method will check if the event from that given timeline has been
+## visited yet.
+##
+## If no [param timeline] is passed, the current timeline will be used.
+## If there is no current timeline, `false` will be returned.
+##
+## If no [param event_index] is passed, the current event index will be used.
+func has_event_been_visited(event_index := dialogic.current_event_idx, timeline := dialogic.current_timeline) -> bool:
+ if timeline == null:
+ return false
+
+ var event_key := _get_event_key(event_index, timeline.resource_path)
+ var visited := event_key in visited_event_history_content
+
+ return visited
+
+
+## Saves all seen events to the global info file.
+## This can be useful when the player saves the game.
+## In visual novels, callings this at the end of a route can be useful, as the
+## player may not save the game.
+##
+## Be aware, this won't add any events but completely overwrite the already saved ones.
+##
+## Relies on the [subsystem Save] subsystem.
+func save_visited_history() -> void:
+ DialogicUtil.autoload().Save.set_global_info(visited_event_save_key, visited_event_history_content)
+
+
+## Loads the seen events from the global info save file.
+## Calling this when a game gets loaded may be useful.
+##
+## Relies on the [subsystem Save] subsystem.
+func load_visited_history() -> void:
+ visited_event_history_content = get_saved_visited_history()
+
+
+## Returns the saved already-visited history from the global info save file.
+## If none exist in the global info file, returns an empty dictionary.
+##
+## Relies on the [subsystem Save] subsystem.
+func get_saved_visited_history() -> Dictionary:
+ return DialogicUtil.autoload().Save.get_global_info(visited_event_save_key, {})
+
+
+## Resets the already-visited history in the global info save file.
+## If [param reset_property] is true, it will also reset the already-visited
+## history in the Dialogic Autoload.
+##
+## Relies on the [subsystem Save] subsystem.
+func reset_visited_history(reset_property := true) -> void:
+ DialogicUtil.autoload().Save.set_global_info(visited_event_save_key, {})
+
+ if reset_property:
+ visited_event_history_content = {}
+
+#endregion
diff --git a/addons/dialogic-main/addons/dialogic/Modules/Jump/event_jump.gd b/addons/dialogic-main/addons/dialogic/Modules/Jump/event_jump.gd
new file mode 100644
index 0000000..050dfa9
--- /dev/null
+++ b/addons/dialogic-main/addons/dialogic/Modules/Jump/event_jump.gd
@@ -0,0 +1,157 @@
+@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
+## If not empty, the event will try to find a Label event with this set as name. Empty by default..
+var label_name := ""
+
+
+### Helpers
+
+## Used to set the timeline resource from the unique name identifier and vice versa
+var timeline_identifier := "":
+ get:
+ if timeline:
+ var identifier := DialogicResourceUtil.get_unique_identifier(timeline.resource_path)
+ if not identifier.is_empty():
+ return identifier
+ return timeline_identifier
+ set(value):
+ timeline_identifier = value
+ timeline = DialogicResourceUtil.get_timeline_resource(value)
+
+
+################################################################################
+## EXECUTION
+################################################################################
+
+func _execute() -> void:
+ dialogic.Jump.push_to_jump_stack()
+ if timeline and timeline != dialogic.current_timeline:
+ dialogic.Jump.switched_timeline.emit({'previous_timeline':dialogic.current_timeline, 'timeline':timeline, 'label':label_name})
+ dialogic.start_timeline(timeline, label_name)
+ else:
+ if label_name:
+ dialogic.Jump.jump_to_label(label_name)
+ finish()
+ else:
+ dialogic.start_timeline(dialogic.current_timeline)
+
+
+################################################################################
+## INITIALIZE
+################################################################################
+
+func _init() -> void:
+ event_name = "Jump"
+ set_default_color('Color4')
+ event_category = "Flow"
+ event_sorting_index = 4
+
+
+func _get_icon() -> Resource:
+ return load(self.get_script().get_path().get_base_dir().path_join('icon_jump.png'))
+
+
+################################################################################
+## SAVING/LOADING
+################################################################################
+func to_text() -> String:
+ var result := "jump "
+ if timeline_identifier:
+ result += timeline_identifier+'/'
+ if label_name:
+ result += label_name
+ elif label_name:
+ result += label_name
+ return result
+
+
+func from_text(string:String) -> void:
+ var result := RegEx.create_from_string(r"jump (?.*\/)?(?