diff --git a/.gitignore b/.gitignore index 213ff6b..4fcd04f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ export_credentials.cfg .mono/ data_*/ mono_crash.*.json -addons/ # rust /target diff --git a/godot/addons/Asset_Drawer/AssetDrawerShortcut.tres b/godot/addons/Asset_Drawer/AssetDrawerShortcut.tres new file mode 100644 index 0000000..b05aa41 --- /dev/null +++ b/godot/addons/Asset_Drawer/AssetDrawerShortcut.tres @@ -0,0 +1,7 @@ +[gd_resource type="InputEventKey" format=3 uid="uid://bafyb8y38ahfh"] + +[resource] +device = -1 +ctrl_pressed = true +keycode = 32 +unicode = 32 diff --git a/godot/addons/Asset_Drawer/FileSystem.gd b/godot/addons/Asset_Drawer/FileSystem.gd new file mode 100644 index 0000000..edd080c --- /dev/null +++ b/godot/addons/Asset_Drawer/FileSystem.gd @@ -0,0 +1,110 @@ +@tool +extends EditorPlugin + +# Padding from the bottom when popped out +var padding: int = 20 + +# Padding from the bottom when not popped out +var bottompadding: int = 60 + +# The file system +var FileDock: Object + +# Toggle for when the file system is moved to bottom +var filesBottom: bool = false + +var newSize: Vector2 +var initialLoad: bool = false + +var AssetDrawerShortcut: InputEventKey = InputEventKey.new() +var showing: bool = false + +func _enter_tree() -> void: + # Add tool button to move shelf to editor bottom + add_tool_menu_item("Files to Bottom", Callable(self, "FilesToBottom")) + + # Get our file system + FileDock = self.get_editor_interface().get_file_system_dock() + await get_tree().create_timer(0.1).timeout + FilesToBottom() + + # Prevent file tree from being shrunk on load + await get_tree().create_timer(0.1).timeout + var file_split_container := FileDock.get_child(3) as SplitContainer + file_split_container .split_offset = 175 + + # Get shortcut + AssetDrawerShortcut = preload("res://addons/Asset_Drawer/AssetDrawerShortcut.tres") + +#region show hide filesystem +func _input(event: InputEvent) -> void: + if (AssetDrawerShortcut.is_match(event) && + event.is_pressed() && + !event.is_echo()): + if filesBottom == true: + match showing: + false: + make_bottom_panel_item_visible(FileDock) + showing = true + true: + print("hide") + hide_bottom_panel() + showing = false +#endregion + +func _exit_tree() -> void: + remove_tool_menu_item("Files to Bottom") + FilesToBottom() + + +func _process(delta: float) -> void: + + newSize = FileDock.get_window().size + + # Keeps the file system from being unusable in size + if FileDock.get_window().name == "root" && filesBottom == false: + FileDock.get_child(3).get_child(0).size.y = newSize.y - padding + FileDock.get_child(3).get_child(1).size.y = newSize.y - padding + return + + # Adjust the size of the file system based on how far up + # the drawer has been pulled + if FileDock.get_window().name == "root" && filesBottom == true: + newSize = FileDock.get_parent().size + var editor = get_editor_interface() + var editorsettings = editor.get_editor_settings() + var fontsize: int = editorsettings.get_setting("interface/editor/main_font_size") + var editorscale = EditorInterface.get_editor_scale() + + FileDock.get_child(3).get_child(0).size.y = newSize.y - (fontsize * 2) - (bottompadding * EditorInterface.get_editor_scale()) + FileDock.get_child(3).get_child(1).size.y = newSize.y - (fontsize * 2) - (bottompadding * EditorInterface.get_editor_scale()) + return + + # Keeps our systems sized when popped out + if (FileDock.get_window().name != "root" && filesBottom == false): + FileDock.get_window().min_size.y = 50 + FileDock.get_child(3).get_child(0).size.y = newSize.y - padding + FileDock.get_child(3).get_child(1).size.y = newSize.y - padding + + # Centers window on first pop + if initialLoad == false: + initialLoad = true + var screenSize: Vector2 = DisplayServer.screen_get_size() + FileDock.get_window().position = screenSize/2 + + return + +# Moves the files between the bottom panel and the original dock +func FilesToBottom() -> void: + if filesBottom == true: + remove_control_from_bottom_panel(FileDock) + add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, FileDock) + filesBottom = false + return + + FileDock = self.get_editor_interface().get_file_system_dock() + remove_control_from_docks(FileDock) + add_control_to_bottom_panel(FileDock, "File System") + filesBottom = true + + diff --git a/godot/addons/Asset_Drawer/FileSystem.gd.uid b/godot/addons/Asset_Drawer/FileSystem.gd.uid new file mode 100644 index 0000000..f0fedbe --- /dev/null +++ b/godot/addons/Asset_Drawer/FileSystem.gd.uid @@ -0,0 +1 @@ +uid://b5fge81gtvo4b diff --git a/godot/addons/Asset_Drawer/LICENSE b/godot/addons/Asset_Drawer/LICENSE new file mode 100644 index 0000000..cfbc27e --- /dev/null +++ b/godot/addons/Asset_Drawer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Michael McGuire + +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/godot/addons/Asset_Drawer/plugin.cfg b/godot/addons/Asset_Drawer/plugin.cfg new file mode 100644 index 0000000..7ed9fe6 --- /dev/null +++ b/godot/addons/Asset_Drawer/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Asset Drawer" +description="Converts the File dock to an Asset Drawer at the bottom of the editor." +author="GlitchedCode" +version="" +script="FileSystem.gd" diff --git a/godot/addons/Gedis/core/gedis_core.gd b/godot/addons/Gedis/core/gedis_core.gd new file mode 100644 index 0000000..1dbf591 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_core.gd @@ -0,0 +1,212 @@ +class_name GedisCore + +# Core data buckets +var _store: Dictionary = {} +var _hashes: Dictionary = {} +var _lists: Dictionary = {} +var _sets: Dictionary = {} +var _sorted_sets: Dictionary = {} +var _expiry: Dictionary = {} + +# Pub/Sub registries +var _subscribers: Dictionary[String, Array] = {} +var _psubscribers: Dictionary[String, Array] = {} +var _gedis: Gedis + +func _init(gedis_instance: Gedis) -> void: + _gedis = gedis_instance + +func _now() -> int: + return _gedis._time_source.get_time() + +func _delete_all_types_for_key(key: String) -> void: + _store.erase(key) + _hashes.erase(key) + _lists.erase(key) + _sets.erase(key) + _sorted_sets.erase(key) + _expiry.erase(key) + +func _touch_type(key: String, type_bucket: Dictionary) -> void: + # When a key is used for a new type, remove it from other types. + if not type_bucket.has(key): + _store.erase(key) + _hashes.erase(key) + _lists.erase(key) + _sets.erase(key) + _sorted_sets.erase(key) + +func key_exists(key: String) -> bool: + return _store.has(key) or _hashes.has(key) or _lists.has(key) or _sets.has(key) or _sorted_sets.has(key) + +func _get_all_keys() -> Dictionary[String, bool]: + var all: Dictionary[String, bool] = {} + for k in _store.keys(): + all[str(k)] = true + for k in _hashes.keys(): + all[str(k)] = true + for k in _lists.keys(): + all[str(k)] = true + for k in _sets.keys(): + all[str(k)] = true + for k in _sorted_sets.keys(): + all[str(k)] = true + return all + +func flushall() -> void: + _store.clear() + _hashes.clear() + _lists.clear() + _sets.clear() + _sorted_sets.clear() + _expiry.clear() + _subscribers.clear() + _psubscribers.clear() + + +func dump_all(options: Dictionary = {}) -> Dictionary: + var state := { + "store": _store.duplicate(true), + "hashes": _hashes.duplicate(true), + "lists": _lists.duplicate(true), + "sets": _sets.duplicate(true), + "sorted_sets": _sorted_sets.duplicate(true), + "expiry": _expiry.duplicate(true), + } + + var include: Array = options.get("include", []) + var exclude: Array = options.get("exclude", []) + + if not include.is_empty(): + for bucket_name in state: + var bucket: Dictionary = state[bucket_name] + for key in bucket.keys(): + var keep = false + for prefix in include: + if key.begins_with(prefix): + keep = true + break + if not keep: + bucket.erase(key) + + if not exclude.is_empty(): + for bucket_name in state: + var bucket: Dictionary = state[bucket_name] + for key in bucket.keys(): + for prefix in exclude: + if key.begins_with(prefix): + bucket.erase(key) + break + return state + + +func restore_all(state: Dictionary) -> void: + flushall() + + _store = state.get("store", {}) + _hashes = state.get("hashes", {}) + _lists = state.get("lists", {}) + _sets = state.get("sets", {}) + _sorted_sets = state.get("sorted_sets", {}) + _expiry = state.get("expiry", {}) + + # Discard expired keys + var now: float = _now() + for key in _expiry.keys(): + if _expiry[key] < now: + _delete_all_types_for_key(key) + +func restore_key(key: String, data: Dictionary) -> void: + _delete_all_types_for_key(key) + var value = data["value"] + var type = data["type"] + + if type == "string": + _store[key] = value + elif type == "hash": + _hashes[key] = value + elif type == "list": + _lists[key] = value + elif type == "set": + _sets[key] = value + elif type == "sorted_set": + _sorted_sets[key] = value + + if data.has("expiry"): + _expiry[key] = data["expiry"] + +func rename(key: String, newkey: String) -> int: + if not key_exists(key): + return ERR_DOES_NOT_EXIST + + if key_exists(newkey): + return 0 + + var value + if _store.has(key): + value = _store[key] + _store[newkey] = value + elif _hashes.has(key): + value = _hashes[key] + _hashes[newkey] = value + elif _lists.has(key): + value = _lists[key] + _lists[newkey] = value + elif _sets.has(key): + value = _sets[key] + _sets[newkey] = value + elif _sorted_sets.has(key): + value = _sorted_sets[key] + _sorted_sets[newkey] = value + else: + return ERR_DOES_NOT_EXIST + + if _expiry.has(key): + var expiry_time = _expiry[key] + _expiry[newkey] = expiry_time + + _delete_all_types_for_key(key) + return 1 + +func move(key: String, newkey: String) -> int: + if not key_exists(key): + return ERR_DOES_NOT_EXIST + + if key_exists(newkey): + _delete_all_types_for_key(newkey) + + var value + if _store.has(key): + value = _store[key] + _store[newkey] = value + elif _hashes.has(key): + value = _hashes[key] + _hashes[newkey] = value + elif _lists.has(key): + value = _lists[key] + _lists[newkey] = value + elif _sets.has(key): + value = _sets[key] + _sets[newkey] = value + elif _sorted_sets.has(key): + value = _sorted_sets[key] + _sorted_sets[newkey] = value + else: + return ERR_DOES_NOT_EXIST + + if _expiry.has(key): + var expiry_time = _expiry[key] + _expiry[newkey] = expiry_time + + _delete_all_types_for_key(key) + return 1 + +func ks(key: String) -> String: + return "gedis:keyspace:" + key + +func rks(key: String) -> String: + var prefix = "gedis:keyspace:" + if key.begins_with(prefix): + return key.substr(prefix.length()) + else: + return key diff --git a/godot/addons/Gedis/core/gedis_core.gd.uid b/godot/addons/Gedis/core/gedis_core.gd.uid new file mode 100644 index 0000000..1692014 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_core.gd.uid @@ -0,0 +1 @@ +uid://ccfoujiaf8wpi diff --git a/godot/addons/Gedis/core/gedis_debugger.gd b/godot/addons/Gedis/core/gedis_debugger.gd new file mode 100644 index 0000000..445a300 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_debugger.gd @@ -0,0 +1,162 @@ +extends RefCounted +class_name GedisDebugger + +var _gedis: Gedis + +func _init(gedis: Gedis): + _gedis = gedis + if _gedis and _gedis._pubsub: + _gedis._pubsub.subscribed.connect(_on_subscribed) + _gedis._pubsub.unsubscribed.connect(_on_unsubscribed) + +func _on_subscribed(channel: String, subscriber: Object): + var debugger = Engine.get_singleton("EngineDebugger") + if debugger and debugger.is_active(): + debugger.send_message("gedis:pubsub_event", ["subscribed", channel, str(subscriber)]) + +func _on_unsubscribed(channel: String, subscriber: Object): + var debugger = Engine.get_singleton("EngineDebugger") + if debugger and debugger.is_active(): + debugger.send_message("gedis:pubsub_event", ["unsubscribed", channel, str(subscriber)]) + +static func _ensure_debugger_is_registered(): + if Engine.is_editor_hint(): + return + + if not Gedis._debugger_registered: + if Engine.has_singleton("EngineDebugger"): + var debugger = Engine.get_singleton("EngineDebugger") + debugger.register_message_capture("gedis", Callable(Gedis, "_on_debugger_message")) + if debugger.is_active(): + debugger.send_message("gedis:ping", []) + Gedis._debugger_registered = true + +static func _on_debugger_message(message: String, data: Array) -> bool: + # EngineDebugger will call this with the suffix (the part after "gedis:") + # so message will be e.g. "request_instances" or "request_instance_data". + if not Engine.has_singleton("EngineDebugger"): + return false + + match message: + "request_instances": + var instances_data = [] + for instance_info in Gedis.get_all_instances(): + instances_data.append({ + "id": instance_info["id"], + "name": instance_info["name"] + }) + var debugger = Engine.get_singleton("EngineDebugger") + if debugger and debugger.is_active(): + debugger.send_message("gedis:instances_data", instances_data) + return true + + "request_instance_data": + if data.size() < 2: + return false + var instance_id = data[0] + var command = data[1] + + # Find the target instance in the static registry. + var target_instance = null + for inst in Gedis._instances: + if is_instance_valid(inst) and inst._instance_id == instance_id: + target_instance = inst + break + + if target_instance == null: + return false + + var debugger = Engine.get_singleton("EngineDebugger") + if not debugger or not debugger.is_active(): + return false + + match command: + "snapshot": + var pattern = data[2] if data.size() > 2 else "*" + var snapshot_data = target_instance.snapshot(pattern) + debugger.send_message("gedis:snapshot_data", [snapshot_data]) + return true + "dump": + if data.size() < 3: + return false + var key = data[2] + var key_value_data = target_instance.dump_key(key) + debugger.send_message("gedis:key_value_data", [key_value_data]) + return true + "set": + if data.size() < 4: + return false + var key = data[2] + var value = data[3] + target_instance.set_value(key, value) + var key_value_data = target_instance.dump_key(key) + debugger.send_message("gedis:key_value_data", [key_value_data]) + return true + "pubsub": + var channels = target_instance._pubsub.list_channels() + var channels_data = {} + for channel in channels: + channels_data[channel] = target_instance._pubsub.list_subscribers(channel) + + var patterns = target_instance._pubsub.list_patterns() + var patterns_data = {} + for pattern in patterns: + patterns_data[pattern] = target_instance._pubsub.list_pattern_subscribers(pattern) + + debugger.send_message("gedis:pubsub_data", [channels_data, patterns_data]) + return true + + return false + +# Debugger-like helpers: type/dump/snapshot +func type(key: String) -> String: + if _gedis._expiry._is_expired(key): + return "none" + if _gedis._core._store.has(key): + return "string" + if _gedis._core._hashes.has(key): + return "hash" + if _gedis._core._lists.has(key): + return "list" + if _gedis._core._sets.has(key): + return "set" + if _gedis._core._sorted_sets.has(key): + return "zset" + return "none" + +func dump(key: String) -> Dictionary: + var t = type(key) + if t == "none": + return {} + var d: Dictionary = {} + d["type"] = t + d["ttl"] = _gedis.ttl(key) + match t: + "string": + d["value"] = _gedis._core._store.get(key, null) + "hash": + d["value"] = _gedis._core._hashes.get(key, {}).duplicate(true) + "list": + d["value"] = _gedis._core._lists.get(key, []).duplicate() + "set": + d["value"] = _gedis._core._sets.get(key, {}).keys() + "zset": + var data = _gedis._core._sorted_sets.get(key, {}) + var value = [] + if data.has("sorted_set"): + # The internal representation is [score, member] but for visualization + # it's more intuitive to show [member, score]. + for entry in data.sorted_set: + value.append([entry[1], entry[0]]) + d["value"] = value + _: + d["value"] = null + return d + +func snapshot(pattern: String = "*") -> Dictionary: + var out: Dictionary = {} + for k in _gedis._strings.keys(pattern): + var key_data = dump(str(k)) + key_data["ttl"] = _gedis._expiry.ttl(str(k)) + out[str(k)] = key_data + return out diff --git a/godot/addons/Gedis/core/gedis_debugger.gd.uid b/godot/addons/Gedis/core/gedis_debugger.gd.uid new file mode 100644 index 0000000..0237a05 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_debugger.gd.uid @@ -0,0 +1 @@ +uid://bx77jybsq60mc diff --git a/godot/addons/Gedis/core/gedis_expiry.gd b/godot/addons/Gedis/core/gedis_expiry.gd new file mode 100644 index 0000000..1aff0a6 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_expiry.gd @@ -0,0 +1,98 @@ +extends RefCounted +class_name GedisExpiry + +var _gedis: Gedis + +# Provides an ordered view of expirations for efficient purging. +# The list is sorted by timestamp and stores [timestamp, key] pairs. +var _expiry_list: Array + +func _init(p_gedis: Gedis): + _gedis = p_gedis + _expiry_list = [] + +func _now() -> int: + return _gedis._time_source.get_time() + +func _is_expired(key: String) -> bool: + if _gedis._core._expiry.has(key) and _gedis._core._expiry[key] <= _now(): + var timestamp = _gedis._core._expiry[key] + _gedis._core._delete_all_types_for_key(key) + var entry = [timestamp, key] + var index = _expiry_list.bsearch(entry) + if index < _expiry_list.size() and _expiry_list[index] == entry: + _expiry_list.remove_at(index) + return true + return false + +func _purge_expired() -> void: + var now := _now() + var expired_count := 0 + var ex := _gedis._core._expiry + for item in _expiry_list: + var timestamp: int = item[0] + var key: String = item[1] + + if timestamp <= now: + if ex.has(key) and ex[key] == timestamp: + _gedis._core._delete_all_types_for_key(key) + _gedis.publish("gedis:keyspace:" + key, "expire") + expired_count += 1 + else: + break + + if expired_count > 0: + _expiry_list = _expiry_list.slice(expired_count) + +# ---------------- +# Expiry commands +# ---------------- +func expire(key: String, seconds: int) -> bool: + if not _gedis.exists(key): + return false + + # If the key already has an expiry, remove the old entry from the sorted list. + if _gedis._core._expiry.has(key): + var old_timestamp = _gedis._core._expiry[key] + var old_entry = [old_timestamp, key] + var index = _expiry_list.bsearch(old_entry) + if index < _expiry_list.size() and _expiry_list[index] == old_entry: + _expiry_list.remove_at(index) + + var new_timestamp = _now() + (float(seconds) * 1000.0) + _gedis._core._expiry[key] = new_timestamp + + # Add the new expiry to the sorted list. + var new_entry = [new_timestamp, key] + var insertion_index = _expiry_list.bsearch(new_entry) + _expiry_list.insert(insertion_index, new_entry) + + return true + +# TTL returns: +# -2 if the key does not exist +# -1 if the key exists but has no associated expire +# >= 0 number of seconds to expire +func ttl(key: String) -> int: + if not _gedis.exists(key): + return -2 + if not _gedis._core._expiry.has(key): + return -1 + return max(0, int(ceil((_gedis._core._expiry[key] - _now()) / 1000.0))) + +func persist(key: String) -> bool: + if not _gedis.exists(key): + return false + if _gedis._core._expiry.has(key): + var timestamp = _gedis._core._expiry[key] + _gedis._core._expiry.erase(key) + var entry = [timestamp, key] + var index = _expiry_list.bsearch(entry) + if index < _expiry_list.size() and _expiry_list[index] == entry: + _expiry_list.remove_at(index) + return true + return false + +func setex(key: String, seconds: int, value: Variant) -> void: + _gedis.set_value(key, value) + expire(key, seconds) \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_expiry.gd.uid b/godot/addons/Gedis/core/gedis_expiry.gd.uid new file mode 100644 index 0000000..1d13ef5 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_expiry.gd.uid @@ -0,0 +1 @@ +uid://bscau2tkd4lcx diff --git a/godot/addons/Gedis/core/gedis_hashes.gd b/godot/addons/Gedis/core/gedis_hashes.gd new file mode 100644 index 0000000..9b09022 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_hashes.gd @@ -0,0 +1,132 @@ +extends RefCounted +class_name GedisHashes + +var _gedis + +func _init(gedis): + _gedis = gedis + +# ------ +# Hashes +# ------ +func hset(key: String, field: String, value) -> int: + _gedis._core._touch_type(key, _gedis._core._hashes) + var d: Dictionary = _gedis._core._hashes.get(key, {}) + var existed := int(d.has(field)) + d[field] = value + _gedis._core._hashes[key] = d + _gedis.publish("gedis:keyspace:" + key, "set") + return 1 - existed + +func hget(key: String, field: String, default_value: Variant = null): + if _gedis._expiry._is_expired(key): + return default_value + var d: Dictionary = _gedis._core._hashes.get(key, {}) + return d.get(field, default_value) + +# Gets the values of multiple fields in a hash. +# --- +# @param key: The key of the hash. +# @param fields: An array of fields to get the values of. +# @return: An array of values for the given fields. If a field does not exist, the corresponding value in the array will be null. +func hmget(key: String, fields: Array) -> Array: + if _gedis._expiry._is_expired(key): + return fields.map(func(_field): return null) + var d: Dictionary = _gedis._core._hashes.get(key, {}) + var result: Array = [] + for field in fields: + result.append(d.get(field, null)) + return result + +# Sets multiple fields and their values in a hash. +# --- +# @param key: The key of the hash. +# @param field_value_pairs: A dictionary of field-value pairs to set. +func hmset(key: String, field_value_pairs: Dictionary) -> void: + _gedis._core._touch_type(key, _gedis._core._hashes) + var d: Dictionary = _gedis._core._hashes.get(key, {}) + for field in field_value_pairs: + d[field] = field_value_pairs[field] + _gedis._core._hashes[key] = d + _gedis.publish("gedis:keyspace:" + key, "set") + +func hincrby(key: String, field: String, amount: int) -> Variant: + _gedis._core._touch_type(key, _gedis._core._hashes) + var d: Dictionary = _gedis._core._hashes.get(key, {}) + var value = d.get(field, 0) + if not typeof(value) in [TYPE_INT, TYPE_FLOAT]: + push_error("WRONGTYPE Operation against a key holding the wrong kind of value") + return null + value += amount + d[field] = value + _gedis._core._hashes[key] = d + _gedis.publish("gedis:keyspace:" + key, "set") + return value + +func hincrbyfloat(key: String, field: String, amount: float) -> Variant: + _gedis._core._touch_type(key, _gedis._core._hashes) + var d: Dictionary = _gedis._core._hashes.get(key, {}) + var value = d.get(field, 0.0) + if not typeof(value) in [TYPE_INT, TYPE_FLOAT]: + push_error("WRONGTYPE Operation against a key holding the wrong kind of value") + return null + value += amount + d[field] = value + _gedis._core._hashes[key] = d + _gedis.publish("gedis:keyspace:" + key, "set") + return value + +func hdel(key: String, fields) -> int: + # Accept single field (String) or Array of fields + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._hashes.has(key): + return 0 + var d: Dictionary = _gedis._core._hashes[key] + var removed = 0 + if typeof(fields) == TYPE_ARRAY: + for f in fields: + if d.has(str(f)): + d.erase(str(f)) + removed += 1 + else: + var f = str(fields) + if d.has(f): + d.erase(f) + removed = 1 + if d.is_empty(): + _gedis._core._hashes.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._hashes[key] = d + return removed + +func hgetall(key: String) -> Dictionary: + if _gedis._expiry._is_expired(key): + return {} + return _gedis._core._hashes.get(key, {}).duplicate(true) + +func hexists(key: String, field = null) -> bool: + if _gedis._expiry._is_expired(key): + return false + + if field == null: + return _gedis._core._hashes.has(key) + + var d: Dictionary = _gedis._core._hashes.get(key, {}) + return d.has(field) + +func hkeys(key: String) -> Array: + if _gedis._expiry._is_expired(key): + return [] + return _gedis._core._hashes.get(key, {}).keys() + +func hvals(key: String) -> Array: + if _gedis._expiry._is_expired(key): + return [] + return _gedis._core._hashes.get(key, {}).values() + +func hlen(key: String) -> int: + if _gedis._expiry._is_expired(key): + return 0 + return _gedis._core._hashes.get(key, {}).size() \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_hashes.gd.uid b/godot/addons/Gedis/core/gedis_hashes.gd.uid new file mode 100644 index 0000000..fcf2caa --- /dev/null +++ b/godot/addons/Gedis/core/gedis_hashes.gd.uid @@ -0,0 +1 @@ +uid://dcvde42llr5d4 diff --git a/godot/addons/Gedis/core/gedis_lists.gd b/godot/addons/Gedis/core/gedis_lists.gd new file mode 100644 index 0000000..e336b47 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_lists.gd @@ -0,0 +1,293 @@ +extends RefCounted +class_name GedisLists + +var _gedis + +func _init(gedis): + _gedis = gedis + +# ----- +# Lists +# ----- +func lpush(key: String, value) -> int: + _gedis._core._touch_type(key, _gedis._core._lists) + var a: Array = _gedis._core._lists.get(key, []) + if typeof(value) == TYPE_ARRAY: + a = value + a + else: + a.insert(0, value) + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return a.size() + +func rpush(key: String, value) -> int: + _gedis._core._touch_type(key, _gedis._core._lists) + var a: Array = _gedis._core._lists.get(key, []) + if typeof(value) == TYPE_ARRAY: + a += value + else: + a.append(value) + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return a.size() + +func lpop(key: String): + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._lists.has(key): + return null + var a: Array = _gedis._core._lists[key].duplicate() + if a.is_empty(): + return null + var v = a.pop_front() + if a.is_empty(): + _gedis._core._lists.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return v + +func rpop(key: String): + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._lists.has(key): + return null + var a: Array = _gedis._core._lists[key].duplicate() + if a.is_empty(): + return null + var v = a.pop_back() + if a.is_empty(): + _gedis._core._lists.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return v + +func llen(key: String) -> int: + if _gedis._expiry._is_expired(key): + return 0 + var a: Array = _gedis._core._lists.get(key, []) + return a.size() + +func lexists(key: String) -> bool: + if _gedis._expiry._is_expired(key): + return false + return _gedis._core._lists.has(key) + +func lget(key: String) -> Array: + if _gedis._expiry._is_expired(key): + return [] + return _gedis._core._lists.get(key, []).duplicate() + +func lrange(key: String, start: int, stop: int) -> Array: + if _gedis._expiry._is_expired(key): + return [] + var a: Array = _gedis._core._lists.get(key, []) + var n = a.size() + # normalize negative indices + if start < 0: + start = n + start + if stop < 0: + stop = n + stop + # clamp + start = max(0, start) + stop = min(n - 1, stop) + if start > stop or n == 0: + return [] + var out: Array = [] + for i in range(start, stop + 1): + out.append(a[i]) + return out + +func lindex(key: String, index: int): + if _gedis._expiry._is_expired(key): + return null + var a: Array = _gedis._core._lists.get(key, []) + var n = a.size() + if n == 0: + return null + if index < 0: + index = n + index + if index < 0 or index >= n: + return null + return a[index] + +func lset(key: String, index: int, value) -> bool: + if _gedis._expiry._is_expired(key): + return false + if not _gedis._core._lists.has(key): + return false + var a: Array = _gedis._core._lists[key].duplicate() + var n = a.size() + if index < 0: + index = n + index + if index < 0 or index >= n: + return false + a[index] = value + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return true + +func lrem(key: String, count: int, value) -> int: + # Remove elements equal to value. Behavior similar to Redis. + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._lists.has(key): + return 0 + var a: Array = _gedis._core._lists[key].duplicate() + var removed = 0 + if count == 0: + # remove all + var filtered: Array = [] + for v in a: + if v == value: + removed += 1 + else: + filtered.append(v) + a = filtered + elif count > 0: + var out: Array = [] + for v in a: + if v == value and removed < count: + removed += 1 + continue + out.append(v) + a = out + else: + # count < 0, remove from tail + var rev = a.duplicate() + rev.reverse() + var out2: Array = [] + for v in rev: + if v == value and removed < abs(count): + removed += 1 + continue + out2.append(v) + out2.reverse() + a = out2 + if a.is_empty(): + _gedis._core._lists.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return removed + +func lmove(source: String, destination: String, from: String, to: String): + if _gedis._expiry._is_expired(source): + return null + if not _gedis._core._lists.has(source): + return null + + var source_list: Array = _gedis._core._lists[source] + if source_list.is_empty(): + return null + + var element + if from.to_upper() == "LEFT": + element = source_list.pop_front() + elif from.to_upper() == "RIGHT": + element = source_list.pop_back() + else: + return null + + _gedis._core._touch_type(destination, _gedis._core._lists) + var dest_list: Array = _gedis._core._lists.get(destination, []) + + if to.to_upper() == "LEFT": + dest_list.insert(0, element) + elif to.to_upper() == "RIGHT": + dest_list.append(element) + else: + # Invalid 'to', restore source list and return error + if from.to_upper() == "LEFT": + source_list.insert(0, element) + else: + source_list.append(element) + return null + + if source_list.is_empty(): + _gedis._core._lists.erase(source) + _gedis.publish("gedis:keyspace:" + source, "del") + else: + _gedis._core._lists[source] = source_list + _gedis.publish("gedis:keyspace:" + source, "set") + _gedis._core._lists[destination] = dest_list + _gedis.publish("gedis:keyspace:" + destination, "set") + + return element +# Trims a list to the specified range of indices. +# Removes all elements from the list that are not in the range [start, stop]. +# If start is greater than stop, or start is out of bounds, the list will be emptied. +# --- +# @param key: The key of the list to trim. +# @param start: The starting index of the range. +# @param stop: The ending index of the range. +# @return: Returns true if the list was trimmed, false otherwise. +func ltrim(key: String, start: int, stop: int) -> bool: + if _gedis._expiry._is_expired(key): + return false + if not _gedis._core._lists.has(key): + return false + + var a: Array = _gedis._core._lists[key].duplicate() + var n = a.size() + + if start < 0: + start = n + start + if stop < 0: + stop = n + stop + + start = max(0, start) + stop = min(n - 1, stop) + + if start > stop: + _gedis._core._lists.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + return true + + var trimmed_list: Array = [] + for i in range(start, stop + 1): + trimmed_list.append(a[i]) + + _gedis._core._lists[key] = trimmed_list + _gedis.publish("gedis:keyspace:" + key, "set") + return true + +# Inserts a value into a list before or after a pivot value. +# --- +# @param key: The key of the list. +# @param position: "BEFORE" or "AFTER" the pivot. +# @param pivot: The value to insert the new value next to. +# @param value: The value to insert. +# @return: The new size of the list, or -1 if the pivot was not found. Returns 0 if the key does not exist or the position is invalid. +func linsert(key: String, position: String, pivot: Variant, value: Variant) -> int: + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._lists.has(key): + return 0 + + var a: Array = _gedis._core._lists[key].duplicate() + var n = a.size() + var index = -1 + + for i in range(n): + if a[i] == pivot: + index = i + break + + if index == -1: + return -1 + + if position.to_upper() == "BEFORE": + a.insert(index, value) + elif position.to_upper() == "AFTER": + a.insert(index + 1, value) + else: + return 0 + + _gedis._core._lists[key] = a + _gedis.publish("gedis:keyspace:" + key, "set") + return a.size() \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_lists.gd.uid b/godot/addons/Gedis/core/gedis_lists.gd.uid new file mode 100644 index 0000000..b30e08a --- /dev/null +++ b/godot/addons/Gedis/core/gedis_lists.gd.uid @@ -0,0 +1 @@ +uid://budmyot3vslr3 diff --git a/godot/addons/Gedis/core/gedis_pubsub.gd b/godot/addons/Gedis/core/gedis_pubsub.gd new file mode 100644 index 0000000..ff43b61 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_pubsub.gd @@ -0,0 +1,106 @@ +extends RefCounted +class_name GedisPubSub + +var _gedis: Gedis + +signal pubsub_message(channel, message) +signal psub_message(pattern, channel, message) + +signal subscribed(channel, subscriber) +signal unsubscribed(channel, subscriber) + +func _init(gedis: Gedis): + _gedis = gedis + +# -------- +# Pub/Sub +# -------- +func publish(channel: String, message) -> void: + # Backwards-compatible delivery: + # 1) If subscriber objects registered via subscribe/psubscribe expect direct signals, + # call their 'pubsub_message'/'psub_message' on the subscriber object. + # 2) Emit a single Gedis-level signal so external code can connect to this Gedis instance. + # This avoids emitting the same Gedis signal multiple times (which would cause duplicate callbacks). + # Direct subscribers (back-compat) + if _gedis._core._subscribers.has(channel): + for subscriber in _gedis._core._subscribers[channel]: + if is_instance_valid(subscriber): + # deliver directly to subscriber object if it exposes the signal + if subscriber.has_signal("pubsub_message"): + subscriber.pubsub_message.emit(channel, message) + # Emit a single Gedis-level pubsub notification for all listeners connected to this Gedis instance. + if _gedis._core._subscribers.has(channel) and _gedis._core._subscribers[channel].size() > 0: + pubsub_message.emit(channel, message) + # Pattern subscribers (back-compat + Gedis-level) + for pattern in _gedis._core._psubscribers.keys(): + # Use simple glob matching: convert to RegEx + var rx = _gedis._utils._glob_to_regex(pattern) + if rx.search(channel) != null: + for subscriber in _gedis._core._psubscribers[pattern]: + if is_instance_valid(subscriber): + if subscriber.has_signal("psub_message"): + subscriber.psub_message.emit(pattern, channel, message) + # Emit one Gedis-level pattern message for this matching pattern + psub_message.emit(pattern, channel, message) + +func subscribe(channel: String, subscriber: Object) -> void: + var arr: Array = _gedis._core._subscribers.get(channel, []) + # avoid duplicates + for s in arr: + if s == subscriber: + return + arr.append(subscriber) + _gedis._core._subscribers[channel] = arr + subscribed.emit(channel, subscriber) + +func unsubscribe(channel: String, subscriber: Object) -> void: + if not _gedis._core._subscribers.has(channel): + return + var arr: Array = _gedis._core._subscribers[channel] + for i in range(arr.size()): + if arr[i] == subscriber: + arr.remove_at(i) + unsubscribed.emit(channel, subscriber) + break + if arr.is_empty(): + _gedis._core._subscribers.erase(channel) + else: + _gedis._core._subscribers[channel] = arr + +func psubscribe(pattern: String, subscriber: Object) -> void: + var arr: Array = _gedis._core._psubscribers.get(pattern, []) + for s in arr: + if s == subscriber: + return + arr.append(subscriber) + _gedis._core._psubscribers[pattern] = arr + subscribed.emit(pattern, subscriber) + +func punsubscribe(pattern: String, subscriber: Object) -> void: + if not _gedis._core._psubscribers.has(pattern): + return + var arr: Array = _gedis._core._psubscribers[pattern] + for i in range(arr.size()): + if arr[i] == subscriber: + arr.remove_at(i) + unsubscribed.emit(pattern, subscriber) + break + if arr.is_empty(): + _gedis._core._psubscribers.erase(pattern) + else: + _gedis._core._psubscribers[pattern] = arr + +# ----------- +# Introspection +# ----------- +func list_channels() -> Array: + return _gedis._core._subscribers.keys() + +func list_subscribers(channel: String) -> Array: + return _gedis._core._subscribers.get(channel, []) + +func list_patterns() -> Array: + return _gedis._core._psubscribers.keys() + +func list_pattern_subscribers(pattern: String) -> Array: + return _gedis._core._psubscribers.get(pattern, []) diff --git a/godot/addons/Gedis/core/gedis_pubsub.gd.uid b/godot/addons/Gedis/core/gedis_pubsub.gd.uid new file mode 100644 index 0000000..c65f198 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_pubsub.gd.uid @@ -0,0 +1 @@ +uid://jwecqfjo870q diff --git a/godot/addons/Gedis/core/gedis_sets.gd b/godot/addons/Gedis/core/gedis_sets.gd new file mode 100644 index 0000000..060ce6e --- /dev/null +++ b/godot/addons/Gedis/core/gedis_sets.gd @@ -0,0 +1,216 @@ +extends RefCounted +class_name GedisSets + +var _gedis: Gedis + +func _init(gedis: Gedis): + _gedis = gedis + +# ---- +# Sets +# ---- +func sadd(key: String, member) -> int: + _gedis._core._touch_type(key, _gedis._core._sets) + var s: Dictionary = _gedis._core._sets.get(key, {}) + var existed := int(s.has(member)) + s[member] = true + _gedis._core._sets[key] = s + _gedis.publish("gedis:keyspace:" + key, "set") + return 1 - existed + +func srem(key: String, member) -> int: + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._sets.has(key): + return 0 + var s: Dictionary = _gedis._core._sets[key] + var existed := int(s.has(member)) + s.erase(member) + if s.is_empty(): + _gedis._core._sets.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._sets[key] = s + return existed + +func smembers(key: String) -> Array: + if _gedis._expiry._is_expired(key): + return [] + var s: Dictionary = _gedis._core._sets.get(key, {}) + return s.keys() + +func sismember(key: String, member) -> bool: + if _gedis._expiry._is_expired(key): + return false + var s: Dictionary = _gedis._core._sets.get(key, {}) + return s.has(member) + +func scard(key: String) -> int: + if _gedis._expiry._is_expired(key): + return 0 + return _gedis._core._sets.get(key, {}).size() + +func sexists(key: String) -> bool: + if _gedis._expiry._is_expired(key): + return false + return _gedis._core._sets.has(key) + +func spop(key: String): + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._sets.has(key): + return null + var s: Dictionary = _gedis._core._sets[key] + var keys_arr: Array = s.keys() + if keys_arr.is_empty(): + return null + var idx = randi() % keys_arr.size() + var member = keys_arr[idx] + s.erase(member) + if s.is_empty(): + _gedis._core._sets.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._sets[key] = s + return member + +func smove(source: String, destination: String, member) -> bool: + if _gedis._expiry._is_expired(source): + return false + if not sismember(source, member): + return false + # remove from source + srem(source, member) + # add to destination (creates destination set) + sadd(destination, member) + return true + +# Computes the union of multiple sets. +# --- +# @param keys: An array of set keys. +# @return: An array containing the members of the resulting union set. +func sunion(keys: Array) -> Array: + var result_set := {} + for key in keys: + var members = smembers(key) + for member in members: + result_set[member] = true + return result_set.keys() + +# Computes the intersection of multiple sets. +# --- +# @param keys: An array of set keys. +# @return: An array containing the members of the resulting intersection set. +func sinter(keys: Array) -> Array: + if keys.is_empty(): + return [] + + var result_set := {} + var first_set_members = smembers(keys[0]) + for member in first_set_members: + result_set[member] = true + + for i in range(1, keys.size()): + var next_set_members = smembers(keys[i]) + var current_members = result_set.keys() + for member in current_members: + if not next_set_members.has(member): + result_set.erase(member) + + return result_set.keys() + +# Computes the difference between multiple sets. +# The difference is calculated as the members of the first set minus the members of all subsequent sets. +# --- +# @param keys: An array of set keys. The first key is the set to subtract from. +# @return: An array containing the members of the resulting difference set. +func sdiff(keys: Array) -> Array: + if keys.is_empty(): + return [] + + var result_set := {} + var first_set_members = smembers(keys[0]) + for member in first_set_members: + result_set[member] = true + + for i in range(1, keys.size()): + var next_set_members = smembers(keys[i]) + for member in next_set_members: + if result_set.has(member): + result_set.erase(member) + + return result_set.keys() + +# Computes the union of multiple sets and stores the result in a new set. +# --- +# @param destination: The key to store the resulting union set in. +# @param keys: An array of set keys. +# @return: The number of members in the resulting union set. +func sunionstore(destination: String, keys: Array) -> int: + var result_members = sunion(keys) + _gedis._core._sets.erase(destination) + for member in result_members: + sadd(destination, member) + return result_members.size() + +# Computes the intersection of multiple sets and stores the result in a new set. +# --- +# @param destination: The key to store the resulting intersection set in. +# @param keys: An array of set keys. +# @return: The number of members in the resulting intersection set. +func sinterstore(destination: String, keys: Array) -> int: + var result_members = sinter(keys) + _gedis._core._sets.erase(destination) + for member in result_members: + sadd(destination, member) + return result_members.size() + +# Computes the difference between multiple sets and stores the result in a new set. +# --- +# @param destination: The key to store the resulting difference set in. +# @param keys: An array of set keys. The first key is the set to subtract from. +# @return: The number of members in the resulting difference set. +func sdiffstore(destination: String, keys: Array) -> int: + var result_members = sdiff(keys) + _gedis._core._sets.erase(destination) + for member in result_members: + sadd(destination, member) + return result_members.size() + +# Gets one or more random members from a set. +# --- +# @param key: The key of the set. +# @param count: The number of random members to return. If positive, returns unique members. If negative, allows for repetitions. +# @return: A single random member if count is 1, or an array of random members. Returns null or an empty array if the set is empty. +func srandmember(key: String, count: int = 1): + if _gedis._expiry._is_expired(key): + return null if count == 1 else [] + + var s: Dictionary = _gedis._core._sets.get(key, {}) + var members = s.keys() + if members.is_empty(): + return null if count == 1 else [] + + if count == 1: + return members.pick_random() + + var result = [] + if count > 0: + # Positive count: return unique elements + if count >= members.size(): + members.shuffle() + return members + + var used_indices = {} + while result.size() < count: + var idx = randi() % members.size() + if not used_indices.has(idx): + result.append(members[idx]) + used_indices[idx] = true + else: # count < 0 + # Negative count: repetitions are allowed + var abs_count = abs(count) + for _i in range(abs_count): + result.append(members.pick_random()) + + return result \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_sets.gd.uid b/godot/addons/Gedis/core/gedis_sets.gd.uid new file mode 100644 index 0000000..75e8ba3 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_sets.gd.uid @@ -0,0 +1 @@ +uid://c2v7tr3s0wiv7 diff --git a/godot/addons/Gedis/core/gedis_sorted_sets.gd b/godot/addons/Gedis/core/gedis_sorted_sets.gd new file mode 100644 index 0000000..00addc1 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_sorted_sets.gd @@ -0,0 +1,415 @@ +# This class provides a sorted set data structure, where each member is associated with a score. +# Members are stored in an array, sorted by score, allowing for efficient range queries. A dictionary +# is used for quick lookups of member scores. +# +# Features: +# - Add/update members with scores. +# - Remove members. +# - Retrieve members within a score range. +# - Pop members that are "ready" based on a timestamp score. +# +# The implementation aims to be simple and efficient for common use cases in game development, +# such as managing timed events, priority queues, or leaderboards. + +extends RefCounted +class_name GedisSortedSets + +var _gedis: Gedis + +func _init(gedis: Gedis): + _gedis = gedis + +# Adds a new member to the sorted set with the specified score. +# If the member already exists, its score is updated, and its position in the set is adjusted. +# +# @param member: The member to add or update. +# @param score: The score associated with the member. +func add(key: String, member: String, score: int) -> int: + _gedis._core._touch_type(key, _gedis._core._sorted_sets) + var data: Dictionary = _gedis._core._sorted_sets.get(key, { + "sorted_set": [], + "member_scores": {} + }) + + var new_member = not data.member_scores.has(member) + if not new_member: + remove(key, member) + data = _gedis._core._sorted_sets.get(key, { + "sorted_set": [], + "member_scores": {} + }) + + data.member_scores[member] = score + var entry = [score, member] + var inserted: bool = false + for i in range(data.sorted_set.size()): + if score < data.sorted_set[i][0]: + data.sorted_set.insert(i, entry) + inserted = true + break + if not inserted: + data.sorted_set.append(entry) + + _gedis._core._sorted_sets[key] = data + if new_member: + _gedis.publish("gedis:keyspace:" + key, "set") + return 1 if new_member else 0 + + +# Removes a member from the sorted set. +# +# @param member: The member to remove. +func remove(key: String, member: String) -> int: + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._sorted_sets.has(key): + return 0 + + var data: Dictionary = _gedis._core._sorted_sets[key] + if not data.member_scores.has(member): + return 0 + + var score = data.member_scores[member] + data.member_scores.erase(member) + + for i in range(data.sorted_set.size()): + if data.sorted_set[i][0] == score and data.sorted_set[i][1] == member: + data.sorted_set.remove_at(i) + if data.sorted_set.is_empty(): + _gedis._core._sorted_sets.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._sorted_sets[key] = data + return 1 + return 0 + +func zexists(key: String) -> bool: + if _gedis._expiry._is_expired(key): + return false + return _gedis._core._sorted_sets.has(key) + +func zcard(key: String) -> int: + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._sorted_sets.has(key): + return 0 + return _gedis._core._sorted_sets[key].sorted_set.size() + +# Returns a range of members from the sorted set, ordered by score. +# +# @param start: The starting index of the range. +# @param stop: The ending index of the range. +# @param withscores: Whether to return scores along with members. +# @return: An array of members, or an array of [member, score] pairs if withscores is true. +func zrange(key: String, start, stop, withscores: bool = false) -> Array: + return _zrange(key, start, stop, withscores, false) + +# Returns a range of members from the sorted set, ordered by score in reverse. +# +# @param start: The starting index of the range. +# @param stop: The ending index of the range. +# @param withscores: Whether to return scores along with members. +# @return: An array of members, or an array of [member, score] pairs if withscores is true. +func zrevrange(key: String, start, stop, withscores: bool = false) -> Array: + return _zrange(key, start, stop, withscores, true) + +func _zrange(key: String, start, stop, withscores: bool, reverse: bool) -> Array: + if _gedis._expiry._is_expired(key): + return [] + if not _gedis._core._sorted_sets.has(key): + return [] + + var data: Dictionary = _gedis._core._sorted_sets[key] + var sorted_set: Array = data.sorted_set + if reverse: + sorted_set = sorted_set.duplicate() + sorted_set.reverse() + + var set_len: int = sorted_set.size() + + var start_index = start + if start_index < 0: + start_index = set_len + start_index + + var stop_index = stop + if stop_index < 0: + stop_index = set_len + stop_index + + if start_index < 0: + start_index = 0 + + if stop_index >= set_len: + stop_index = set_len - 1 + + if start_index > stop_index or start_index >= set_len: + return [] + + var result: Array = [] + for i in range(start_index, stop_index + 1): + var entry = sorted_set[i] + var score = entry[0] + var member = entry[1] + if withscores: + result.append([member, score]) + else: + result.append(member) + return result + +# Returns a range of members from the sorted set, ordered by score. +# +# @param min_score: The minimum score of the range. +# @param max_score: The maximum score of the range. +# @param withscores: Whether to return scores along with members. +# @return: An array of members, or an array of [member, score] pairs if withscores is true. +# Returns a range of members from the sorted set, ordered by score. +func zrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array: + if _gedis._expiry._is_expired(key): + return [] + if not _gedis._core._sorted_sets.has(key): + return [] + + var data: Dictionary = _gedis._core._sorted_sets[key] + var result: Array = [] + for entry in data.sorted_set: + var score = entry[0] + var member = entry[1] + if score >= min_score and score <= max_score: + if withscores: + result.append([member, score]) + else: + result.append(member) + return result + +# Returns a range of members from the sorted set, ordered by score in reverse. +# +# @param min_score: The minimum score of the range. +# @param max_score: The maximum score of the range. +# @param withscores: Whether to return scores along with members. +# @return: An array of members, or an array of [member, score] pairs if withscores is true. +# Returns a range of members from the sorted set, ordered by score in reverse. +func zrevrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array: + var result = zrangebyscore(key, min_score, max_score, withscores) + result.reverse() + return result + +# Removes and returns members with scores up to the given 'now' value. +# This is useful for processing items that are due, like in a task scheduler. +# +# @param now: The current time or score to check against. +# @return: An array of members that were popped. +func pop_ready(key: String, now: int) -> Array: + if _gedis._expiry._is_expired(key): + return [] + if not _gedis._core._sorted_sets.has(key): + return [] + + var data: Dictionary = _gedis._core._sorted_sets[key] + var ready_members: Array = [] + var i: int = 0 + while i < data.sorted_set.size(): + var entry = data.sorted_set[i] + if entry[0] <= now: + ready_members.append(entry[1]) + data.member_scores.erase(entry[1]) + data.sorted_set.remove_at(i) + else: + break + + if data.sorted_set.is_empty(): + _gedis._core._sorted_sets.erase(key) + _gedis.publish("gedis:keyspace:" + key, "del") + else: + _gedis._core._sorted_sets[key] = data + return ready_members + + +# Returns the score of a member in the sorted set. +# --- +# @param key: The key of the sorted set. +# @param member: The member to get the score of. +# @return: The score of the member, or null if the member does not exist. +func zscore(key: String, member: String) -> Variant: + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._sorted_sets.has(key): + return null + + var data: Dictionary = _gedis._core._sorted_sets[key] + if not data.member_scores.has(member): + return null + + return data.member_scores[member] + + +# Returns the rank of a member in the sorted set, with scores ordered from low to high. +# The rank is 0-based. +# --- +# @param key: The key of the sorted set. +# @param member: The member to get the rank of. +# @return: The rank of the member, or null if the member does not exist. +func zrank(key: String, member: String) -> Variant: + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._sorted_sets.has(key): + return null + + var data: Dictionary = _gedis._core._sorted_sets[key] + if not data.member_scores.has(member): + return null + + for i in range(data.sorted_set.size()): + if data.sorted_set[i][1] == member: + return i + return null + + +# Returns the rank of a member in the sorted set, with scores ordered from high to low. +# The rank is 0-based. +# --- +# @param key: The key of the sorted set. +# @param member: The member to get the rank of. +# @return: The rank of the member, or null if the member does not exist. +func zrevrank(key: String, member: String) -> Variant: + if _gedis._expiry._is_expired(key): + return null + if not _gedis._core._sorted_sets.has(key): + return null + + var data: Dictionary = _gedis._core._sorted_sets[key] + if not data.member_scores.has(member): + return null + + for i in range(data.sorted_set.size() - 1, -1, -1): + if data.sorted_set[i][1] == member: + return data.sorted_set.size() - 1 - i + return null + + +# Returns the number of members in a sorted set with scores within the given range. +# --- +# @param key: The key of the sorted set. +# @param min_score: The minimum score of the range. +# @param max_score: The maximum score of the range. +# @return: The number of members in the specified score range. +func zcount(key: String, min_score, max_score) -> int: + if _gedis._expiry._is_expired(key): + return 0 + if not _gedis._core._sorted_sets.has(key): + return 0 + + var data: Dictionary = _gedis._core._sorted_sets[key] + var count: int = 0 + for entry in data.sorted_set: + var score = entry[0] + if score >= min_score and score <= max_score: + count += 1 + return count + + +# Increments the score of a member in a sorted set. +# If the member does not exist, it is added with the increment as its score. +# --- +# @param key: The key of the sorted set. +# @param increment: The amount to increment the score by. +# @param member: The member whose score to increment. +# @return: The new score of the member. +func zincrby(key: String, increment, member: String) -> Variant: + _gedis._core._touch_type(key, _gedis._core._sorted_sets) + var current_score = zscore(key, member) + if current_score == null: + current_score = 0 + var new_score = current_score + increment + add(key, member, new_score) + return new_score + +# Computes the union of multiple sorted sets and stores the result in a new sorted set. +# --- +# @param destination: The key to store the resulting sorted set in. +# @param keys: An array of sorted set keys. +# @param aggregate: The aggregation strategy for scores of the same member ("SUM", "MIN", "MAX"). +# @return: The number of members in the resulting sorted set. +func zunionstore(destination: String, keys: Array, aggregate: String = "SUM") -> int: + var temp_scores: Dictionary = {} + for key in keys: + if not _gedis._core._sorted_sets.has(key): + continue + var data: Dictionary = _gedis._core._sorted_sets[key] + for member in data.member_scores: + var score = data.member_scores[member] + if not temp_scores.has(member): + temp_scores[member] = score + else: + match aggregate.to_upper(): + "SUM": + temp_scores[member] += score + "MIN": + temp_scores[member] = min(temp_scores[member], score) + "MAX": + temp_scores[member] = max(temp_scores[member], score) + + if _gedis._core._sorted_sets.has(destination): + _gedis.del([destination]) + + for member in temp_scores: + add(destination, member, temp_scores[member]) + + if not _gedis._core._sorted_sets.has(destination): + return 0 + return _gedis._core._sorted_sets[destination].sorted_set.size() + + +# Computes the intersection of multiple sorted sets and stores the result in a new sorted set. +# --- +# @param destination: The key to store the resulting sorted set in. +# @param keys: An array of sorted set keys. +# @param aggregate: The aggregation strategy for scores of the same member ("SUM", "MIN", "MAX"). +# @return: The number of members in the resulting sorted set. +func zinterstore(destination: String, keys: Array, aggregate: String = "SUM") -> int: + if keys.is_empty(): + return 0 + + var member_sets: Array = [] + for key in keys: + if not _gedis._core._sorted_sets.has(key): + return 0 + var data: Dictionary = _gedis._core._sorted_sets[key] + member_sets.append(data.member_scores.keys()) + + var intersection = member_sets[0] + for i in range(1, member_sets.size()): + var next_set = member_sets[i] + var current_intersection: Array = [] + for member in intersection: + if member in next_set: + current_intersection.append(member) + intersection = current_intersection + + var temp_scores: Dictionary = {} + for member in intersection: + var score_sum = 0 + var score_min = INF + var score_max = - INF + for key in keys: + var data: Dictionary = _gedis._core._sorted_sets[key] + var score = data.member_scores[member] + score_sum += score + score_min = min(score_min, score) + score_max = max(score_max, score) + + match aggregate.to_upper(): + "SUM": + temp_scores[member] = score_sum + "MIN": + temp_scores[member] = score_min + "MAX": + temp_scores[member] = score_max + + if _gedis._core._sorted_sets.has(destination): + _gedis.del([destination]) + + for member in temp_scores: + add(destination, member, temp_scores[member]) + + if not _gedis._core._sorted_sets.has(destination): + return 0 + return _gedis._core._sorted_sets[destination].sorted_set.size() diff --git a/godot/addons/Gedis/core/gedis_sorted_sets.gd.uid b/godot/addons/Gedis/core/gedis_sorted_sets.gd.uid new file mode 100644 index 0000000..39ad72d --- /dev/null +++ b/godot/addons/Gedis/core/gedis_sorted_sets.gd.uid @@ -0,0 +1 @@ +uid://nld5hwedd25b diff --git a/godot/addons/Gedis/core/gedis_strings.gd b/godot/addons/Gedis/core/gedis_strings.gd new file mode 100644 index 0000000..414336c --- /dev/null +++ b/godot/addons/Gedis/core/gedis_strings.gd @@ -0,0 +1,139 @@ +extends RefCounted +class_name GedisStrings + +var _gedis: Gedis + +func _init(gedis: Gedis): + _gedis = gedis + +# ----------------- +# String/number API +# ----------------- +func set_value(key: StringName, value: Variant) -> void: + _gedis._core._touch_type(str(key), _gedis._core._store) + _gedis._core._store[str(key)] = value + _gedis.publish("gedis:keyspace:" + str(key), "set") + +func get_value(key: StringName, default_value: Variant = null) -> Variant: + if _gedis._expiry._is_expired(str(key)): + return default_value + return _gedis._core._store.get(str(key), default_value) + +# del: accept String or Array of keys +func del(keys) -> int: + if typeof(keys) == TYPE_ARRAY: + var count = 0 + for k in keys: + if _gedis._expiry._is_expired(str(k)): + continue + if exists(str(k)): + _gedis.publish("gedis:keyspace:" + str(k), "del") + _gedis._core._delete_all_types_for_key(str(k)) + count += 1 + return count + else: + var k = str(keys) + var existed := int(exists(k)) + if existed > 0: + _gedis.publish("gedis:keyspace:" + k, "del") + _gedis._core._delete_all_types_for_key(k) + return existed + +# exists: if Array -> return number of existing keys, else boolean for single key +func exists(keys) -> Variant: + if typeof(keys) == TYPE_ARRAY: + var cnt = 0 + for k in keys: + if not _gedis._expiry._is_expired(str(k)) and _gedis._core.key_exists(str(k)): + cnt += 1 + return cnt + else: + var k = str(keys) + if _gedis._expiry._is_expired(k): + return false + return _gedis._core.key_exists(k) + +# key_exists: explicit single-key boolean (keeps parity with C++ API) +func key_exists(key: String) -> bool: + return bool(exists(key)) + +func incrby(key: String, amount: int = 1) -> int: + var k := str(key) + var current: int = 0 + if _gedis._expiry._is_expired(k): + current = 0 + else: + var raw = get_value(k, 0) + match typeof(raw): + TYPE_NIL: + current = 0 + TYPE_INT: + current = int(raw) + TYPE_FLOAT: + current = int(raw) + TYPE_STRING: + var s := str(raw).strip_edges() + if s.find(".") != -1: + current = int(float(s)) + else: + # int(s) will raise on invalid strings; rely on Godot to convert or raise as needed. + current = int(s) + _: + current = int(raw) + var v: int = current + int(amount) + # Store as an integer to keep types consistent + _gedis._core._touch_type(k, _gedis._core._store) + _gedis._core._store[k] = v + _gedis.publish("gedis:keyspace:" + k, "set") + return v + +func decrby(key: String, amount: int = 1) -> int: + return incrby(key, -int(amount)) + +func keys(pattern: String = "*") -> Array: + var all: Dictionary = _gedis._core._get_all_keys() + var rx := _gedis._utils._glob_to_regex(pattern) + var out: Array = [] + for k in all.keys(): + if not _gedis._expiry._is_expired(str(k)) and rx.search(str(k)) != null: + out.append(str(k)) + return out + +func mset(dict: Dictionary) -> void: + for k in dict.keys(): + set_value(str(k), dict[k]) + +func mget(keys: Array) -> Array: + var out: Array = [] + for k in keys: + out.append(get_value(str(k), null)) + return out + +func append(key: String, value: String) -> int: + var k := str(key) + var current_value := get_value(k, "") + if typeof(current_value) != TYPE_STRING: + current_value = str(current_value) + var new_value: String = current_value + value + set_value(k, new_value) + return new_value.length() + +func getset(key: String, value: Variant) -> Variant: + var k := str(key) + var old_value = get_value(k) + set_value(k, value) + return old_value + +func strlen(key: String) -> int: + var k := str(key) + var value = get_value(k) + if typeof(value) == TYPE_STRING: + return value.length() + return 0 + +func setnx(key: String, value: Variant) -> int: + var k := str(key) + if not _gedis._expiry._is_expired(k) and key_exists(k): + return 0 + set_value(k, value) + return 1 \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_strings.gd.uid b/godot/addons/Gedis/core/gedis_strings.gd.uid new file mode 100644 index 0000000..f938a71 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_strings.gd.uid @@ -0,0 +1 @@ +uid://dv4qlsimic35p diff --git a/godot/addons/Gedis/core/gedis_utils.gd b/godot/addons/Gedis/core/gedis_utils.gd new file mode 100644 index 0000000..9effbf3 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_utils.gd @@ -0,0 +1,43 @@ +class_name GedisUtils +var _regex_cache := {} + +# ---------------- +# Utility functions +# ---------------- +func _glob_to_regex(glob: String) -> RegEx: + if _regex_cache.has(glob): + return _regex_cache[glob] + + var escaped := "" + for ch in glob: + match ch: + ".": + escaped += "\\." + "*": + escaped += ".*" + "?": + escaped += "." + "+": + escaped += "\\+" + "(": + escaped += "\\(" + ")": + escaped += "\\)" + "[": + escaped += "\\[" + "]": + escaped += "\\]" + "^": + escaped += "\\^" + "$": + escaped += "\\$" + "|": + escaped += "\\|" + "\\": + escaped += "\\\\" + _: + escaped += ch + var r := RegEx.new() + r.compile("^%s$" % escaped) + _regex_cache[glob] = r + return r \ No newline at end of file diff --git a/godot/addons/Gedis/core/gedis_utils.gd.uid b/godot/addons/Gedis/core/gedis_utils.gd.uid new file mode 100644 index 0000000..c9b1af4 --- /dev/null +++ b/godot/addons/Gedis/core/gedis_utils.gd.uid @@ -0,0 +1 @@ +uid://b3eglfuyrjdgp diff --git a/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd b/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd new file mode 100644 index 0000000..a9c9aac --- /dev/null +++ b/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd @@ -0,0 +1,107 @@ +class_name GedisJSONSnapshotBackend extends GedisPersistenceBackend + +func save(data: Dictionary, options: Dictionary) -> int: + var path: String = options.get("path", "") + if path.is_empty(): + push_error("JSON snapshot backend requires a 'path' in options.") + return FAILED + + var file = FileAccess.open(path, FileAccess.WRITE) + if not file: + push_error("Failed to open file for writing: %s" % path) + return FAILED + + # Serialize variants to strings + var serializable_data = {} + for bucket_name in data: + var bucket = data[bucket_name] + var serializable_bucket = {} + for key in bucket: + var value = bucket[key] + if typeof(value) in [TYPE_OBJECT, TYPE_RID, TYPE_CALLABLE, TYPE_SIGNAL]: + serializable_bucket[key] = var_to_str(value) + else: + serializable_bucket[key] = value + serializable_data[bucket_name] = serializable_bucket + + var json_string = JSON.stringify(serializable_data, "\t") + file.store_string(json_string) + file.close() + + return OK + + +func load(options: Dictionary) -> Dictionary: + var path: String = options.get("path", "") + if path.is_empty(): + push_error("JSON snapshot backend requires a 'path' in options.") + return {} + + if not FileAccess.file_exists(path): + return {} # Return empty dict if file doesn't exist, not an error + + var file = FileAccess.open(path, FileAccess.READ) + if not file: + push_error("Failed to open file for reading: %s" % path) + return {} + + var json_string: String = file.get_as_text() + file.close() + + var json = JSON.new() + var error = json.parse(json_string) + if error != OK: + push_error("Failed to parse JSON from file: %s" % path) + return {} + + var loaded_data: Dictionary = json.get_data() + + # Deserialize variants from strings + for bucket_name in loaded_data: + var bucket = loaded_data[bucket_name] + if typeof(bucket) == TYPE_DICTIONARY: + for key in bucket: + var value = bucket[key] + if typeof(value) == TYPE_STRING and value.begins_with("@"): + bucket[key] = str_to_var(value) + elif typeof(bucket) == TYPE_STRING and bucket.begins_with("@"): + loaded_data[bucket_name] = str_to_var(bucket) + + return loaded_data + +func serialize(data: Dictionary) -> String: + var serializable_data = {} + for bucket_name in data: + var bucket = data[bucket_name] + var serializable_bucket = {} + for key in bucket: + var value = bucket[key] + if typeof(value) in [TYPE_OBJECT, TYPE_RID, TYPE_CALLABLE, TYPE_SIGNAL]: + serializable_bucket[key] = var_to_str(value) + else: + serializable_bucket[key] = value + serializable_data[bucket_name] = serializable_bucket + + return JSON.stringify(serializable_data, "\t") + +func deserialize(data: String) -> Dictionary: + var json = JSON.new() + var error = json.parse(data) + if error != OK: + push_error("Failed to parse JSON from string") + return {} + + var loaded_data: Dictionary = json.get_data() + + # Deserialize variants from strings + for bucket_name in loaded_data: + var bucket = loaded_data[bucket_name] + if typeof(bucket) == TYPE_DICTIONARY: + for key in bucket: + var value = bucket[key] + if typeof(value) == TYPE_STRING and value.begins_with("@"): + bucket[key] = str_to_var(value) + elif typeof(bucket) == TYPE_STRING and bucket.begins_with("@"): + loaded_data[bucket_name] = str_to_var(bucket) + + return loaded_data \ No newline at end of file diff --git a/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd.uid b/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd.uid new file mode 100644 index 0000000..7f4cb45 --- /dev/null +++ b/godot/addons/Gedis/core/persistence/gedis_json_snapshot_backend.gd.uid @@ -0,0 +1 @@ +uid://dw5ib42q321e4 diff --git a/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd b/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd new file mode 100644 index 0000000..d2aa0ce --- /dev/null +++ b/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd @@ -0,0 +1,29 @@ +class_name GedisPersistenceBackend + +# Base class for persistence backends. +# All backends should inherit from this class and implement the `save` and `load` methods. + +func save(data: Dictionary, options: Dictionary) -> int: + # This method should be implemented by the child class. + # It should save the data to the persistence layer and return OK or FAILED. + push_error("save() not implemented in the persistence backend.") + return FAILED + + +func load(options: Dictionary) -> Dictionary: + # This method should be implemented by the child class. + # It should load the data from the persistence layer and return it as a Dictionary. + push_error("load() not implemented in the persistence backend.") + return {} + +func serialize(data: Dictionary) -> String: + # This method should be implemented by the child class. + # It should serialize the data to a string. + push_error("serialize() not implemented in the persistence backend.") + return "" + +func deserialize(data: String) -> Dictionary: + # This method should be implemented by the child class. + # It should deserialize the data from a string. + push_error("deserialize() not implemented in the persistence backend.") + return {} \ No newline at end of file diff --git a/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd.uid b/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd.uid new file mode 100644 index 0000000..cb894d2 --- /dev/null +++ b/godot/addons/Gedis/core/persistence/gedis_persistence_backend.gd.uid @@ -0,0 +1 @@ +uid://cqoc0ht0swvnp diff --git a/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd b/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd new file mode 100644 index 0000000..133708d --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd @@ -0,0 +1,15 @@ +class_name GedisProcessDeltaTimeSource +extends GedisTimeSource + +var current_time: int = 0 + +func _init() -> void: + current_time = Time.get_unix_time_from_system() * 1000 + +func tick(delta: float) -> void: + current_time += int(delta * 1000) + instance.purge_expired() + +# Returns the current time as a Unix timestamp with milliseconds. +func get_time() -> int: + return current_time diff --git a/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd.uid b/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd.uid new file mode 100644 index 0000000..e690456 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_process_delta_time_source.gd.uid @@ -0,0 +1 @@ +uid://cepu7wbyednk diff --git a/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd b/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd new file mode 100644 index 0000000..606ff35 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd @@ -0,0 +1,6 @@ +class_name GedisTickTimeSource +extends GedisTimeSource + +# Returns the current time as a Unix timestamp with milliseconds. +func get_time() -> int: + return Time.get_ticks_msec() diff --git a/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd.uid b/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd.uid new file mode 100644 index 0000000..cac8b78 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_tick_time_source.gd.uid @@ -0,0 +1 @@ +uid://dyaygbjbae3jp diff --git a/godot/addons/Gedis/core/time_sources/gedis_time_source.gd b/godot/addons/Gedis/core/time_sources/gedis_time_source.gd new file mode 100644 index 0000000..11b1241 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_time_source.gd @@ -0,0 +1,12 @@ +class_name GedisTimeSource +extends RefCounted + +var instance: Gedis + +# Returns the current time as a Unix timestamp in milliseconds. +func get_time() -> int: + return 0 + +# Increments the time [Optional] +func tick(_delta) -> void: + instance.purge_expired() diff --git a/godot/addons/Gedis/core/time_sources/gedis_time_source.gd.uid b/godot/addons/Gedis/core/time_sources/gedis_time_source.gd.uid new file mode 100644 index 0000000..1ce09bc --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_time_source.gd.uid @@ -0,0 +1 @@ +uid://b36i3x1dhi4b6 diff --git a/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd b/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd new file mode 100644 index 0000000..25c66f6 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd @@ -0,0 +1,6 @@ +class_name GedisUnixTimeSource +extends GedisTimeSource + +# Returns the current time as a Unix timestamp with milliseconds. +func get_time() -> int: + return Time.get_unix_time_from_system() * 1000 diff --git a/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd.uid b/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd.uid new file mode 100644 index 0000000..d31ac38 --- /dev/null +++ b/godot/addons/Gedis/core/time_sources/gedis_unix_time_source.gd.uid @@ -0,0 +1 @@ +uid://bsxup2elcrrr7 diff --git a/godot/addons/Gedis/debugger/gedis_debugger_panel.gd b/godot/addons/Gedis/debugger/gedis_debugger_panel.gd new file mode 100644 index 0000000..0a0a189 --- /dev/null +++ b/godot/addons/Gedis/debugger/gedis_debugger_panel.gd @@ -0,0 +1,22 @@ +@tool +extends VBoxContainer + +@export var pub_sub_tree: Tree +@export var fetch_pubsub_button: Button +@export var instance_selector: OptionButton + +var plugin + +func _ready() -> void: + _populate_pubsub_tree() + fetch_pubsub_button.pressed.connect(_on_fetch_pubsub_pressed) + +func _on_fetch_pubsub_pressed(): + if plugin: + var selected_id = instance_selector.get_item_id(instance_selector.selected) + plugin.send_message_to_game("request_instance_data", [selected_id, "pubsub"]) + +func _populate_pubsub_tree(): + if not pub_sub_tree: + return + pub_sub_tree.clear() diff --git a/godot/addons/Gedis/debugger/gedis_debugger_panel.gd.uid b/godot/addons/Gedis/debugger/gedis_debugger_panel.gd.uid new file mode 100644 index 0000000..75c2f1c --- /dev/null +++ b/godot/addons/Gedis/debugger/gedis_debugger_panel.gd.uid @@ -0,0 +1 @@ +uid://dbrrt18c60tyc diff --git a/godot/addons/Gedis/debugger/gedis_debugger_panel.tscn b/godot/addons/Gedis/debugger/gedis_debugger_panel.tscn new file mode 100644 index 0000000..79d1f2e --- /dev/null +++ b/godot/addons/Gedis/debugger/gedis_debugger_panel.tscn @@ -0,0 +1,102 @@ +[gd_scene load_steps=3 format=3 uid="uid://c6w7w7w7w7w7"] + +[ext_resource type="Script" uid="uid://dbrrt18c60tyc" path="res://addons/Gedis/debugger/gedis_debugger_panel.gd" id="1_z8w8w"] + +[sub_resource type="Theme" id="Theme_1"] +Label/colors/font_color = Color(1, 0.647059, 0, 1) + +[node name="Dashboard" type="VBoxContainer" node_paths=PackedStringArray("pub_sub_tree", "fetch_pubsub_button", "instance_selector")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = -1152.0 +offset_bottom = -648.0 +script = ExtResource("1_z8w8w") +pub_sub_tree = NodePath("TabContainer/PubSub/HSplitContainer/PubSubTree") +fetch_pubsub_button = NodePath("TopPanel/FetchPubSubButton") +instance_selector = NodePath("TopPanel/instance_selector") + +[node name="status_label" type="Label" parent="."] +layout_mode = 2 +theme = SubResource("Theme_1") +text = "Waiting for game connection..." + +[node name="TopPanel" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="instance_selector" type="OptionButton" parent="TopPanel"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="RefreshButton" type="Button" parent="TopPanel"] +layout_mode = 2 +text = "Refresh Instances" + +[node name="FetchKeysButton" type="Button" parent="TopPanel"] +layout_mode = 2 +text = "Fetch Keys" + +[node name="FetchPubSubButton" type="Button" parent="TopPanel"] +layout_mode = 2 +text = "Fetch Pub/Sub" + +[node name="SearchPanel" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="search_box" type="LineEdit" parent="SearchPanel"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Filter keys..." + +[node name="filter_button" type="Button" parent="SearchPanel"] +layout_mode = 2 +text = "Filter" + +[node name="TabContainer" type="TabContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="Keys" type="VBoxContainer" parent="TabContainer"] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="HSplitContainer" type="HSplitContainer" parent="TabContainer/Keys"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="key_list" type="Tree" parent="TabContainer/Keys/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +columns = 3 +column_titles_visible = true + +[node name="key_value_view" type="TextEdit" parent="TabContainer/Keys/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +editable = false + +[node name="PubSub" type="VBoxContainer" parent="TabContainer"] +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="HSplitContainer" type="HSplitContainer" parent="TabContainer/PubSub"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="PubSubTree" type="Tree" parent="TabContainer/PubSub/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +hide_root = true + +[node name="PubSubEventsTree" type="Tree" parent="TabContainer/PubSub/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +columns = 2 +column_titles_visible = true +hide_root = true diff --git a/godot/addons/Gedis/gedis.gd b/godot/addons/Gedis/gedis.gd new file mode 100644 index 0000000..e0dd7b0 --- /dev/null +++ b/godot/addons/Gedis/gedis.gd @@ -0,0 +1,567 @@ +class_name Gedis extends Node + +# Component instances +var _core: GedisCore +var _expiry: GedisExpiry +var _time_source: GedisTimeSource +var _strings: GedisStrings +var _hashes: GedisHashes +var _lists: GedisLists +var _sets: GedisSets +var _sorted_sets: GedisSortedSets +var _pubsub: GedisPubSub +var _debugger_component: GedisDebugger +var _utils: GedisUtils +var _persistence_backends: Dictionary = {} +var _default_persistence_backend: String = "" + +# Instance registry +static var _instances: Array = [] +static var _next_instance_id: int = 0 +var _instance_id: int = -1 +var _instance_name: String = "" +static var _debugger_registered = false + +func _init() -> void: + # assign id and register + _instance_id = _next_instance_id + _next_instance_id += 1 + _instance_name = "Gedis_%d" % _instance_id + _instances.append(self) + + # Instantiate components + _core = GedisCore.new(self) + _utils = GedisUtils.new() + _expiry = GedisExpiry.new(self) + _strings = GedisStrings.new(self) + _hashes = GedisHashes.new(self) + _lists = GedisLists.new(self) + _sets = GedisSets.new(self) + _sorted_sets = GedisSortedSets.new(self) + _pubsub = GedisPubSub.new(self) + _debugger_component = GedisDebugger.new(self) + + _time_source = GedisUnixTimeSource.new() + _time_source.instance = self + + _pubsub.pubsub_message.connect(_on_pubsub_message) + _pubsub.psub_message.connect(_on_psub_message) + + GedisDebugger._ensure_debugger_is_registered() + +func _on_pubsub_message(channel: String, message: Variant) -> void: + pubsub_message.emit(channel, message) + +func _on_psub_message(pattern: String, channel: String, message: Variant) -> void: + psub_message.emit(pattern, channel, message) + +func _exit_tree() -> void: + # unregister instance + for i in range(_instances.size()): + if _instances[i] == self: + _instances.remove_at(i) + break + +func _process(delta: float) -> void: + _time_source.tick(delta) + +# --- Time Source --- +func set_time_source(p_time_source: GedisTimeSource) -> void: + p_time_source.instance = self + _time_source = p_time_source + +func get_time_source() -> GedisTimeSource: + return _time_source + +# --- Public API --- + +signal pubsub_message(channel, message) +signal psub_message(pattern, channel, message) + +## Sets a value for a key +func set_value(key: StringName, value: Variant) -> void: + _strings.set_value(key, value) + +## Sets a key to a value with an expiration time in seconds. +func setex(key: StringName, seconds: int, value: Variant) -> void: + set_value(key, value) + expire(key, seconds) + +## Gets the string value of a key. +func get_value(key: StringName, default_value: Variant = null) -> Variant: + return _strings.get_value(key, default_value) + +## Deletes one or more keys. +func del(keys) -> int: + return _strings.del(keys) + +## Checks if one or more keys exist. +func exists(keys) -> Variant: + return _strings.exists(keys) + +## Checks if a key exists. +func key_exists(key: String) -> bool: + return _strings.key_exists(key) + +## Increments the integer value of a key by a given amount. +func incrby(key: String, amount: int = 1) -> int: + return _strings.incrby(key, amount) + +## Decrements the integer value of a key by a given amount. +func decrby(key: String, amount: int = 1) -> int: + return _strings.decrby(key, amount) + +## Gets all keys matching a pattern. +func keys(pattern: String = "*") -> Array: + return _strings.keys(pattern) + +## Sets multiple keys to multiple values. +func mset(dict: Dictionary) -> void: + _strings.mset(dict) + +## Gets the values of all specified keys. +func mget(keys: Array) -> Array: + return _strings.mget(keys) + +## Appends a value to a key. +func append(key: String, value: String) -> int: + return _strings.append(key, value) + +## Atomically sets a key to a value and returns the old value. +func getset(key: String, value: Variant) -> Variant: + return _strings.getset(key, value) + +## Gets the length of the string value of a key. +func strlen(key: String) -> int: + return _strings.strlen(key) + +## Sets a key to a value, only if the key does not exist. +func setnx(key: String, value: Variant) -> int: + return _strings.setnx(key, value) + +## Renames a key to newkey, only if newkey does not exist. +func rename(key: String, newkey: String) -> int: + return _core.rename(key, newkey) + +## Moves a key to another key. +func move(key: String, newkey: String) -> int: + return _core.move(key, newkey) + +## Returns a random key from the database. +func randomkey() -> String: + var all_keys = _core._get_all_keys().keys() + if all_keys.is_empty(): + return "" + return all_keys.pick_random() + +## Returns the number of keys in the database. +func dbsize() -> int: + return _core._get_all_keys().size() + +## Adds the keyspace prefix to a key +func ks(key: String) -> String: + return _core.ks(key) + +## Removes the keyspace prefix from a key if present, otherwise returns the key unchanged +func rks(key: String) -> String: + return _core.rks(key) + +# Hashes +## Sets the string value of a hash field. +func hset(key: String, field: String, value) -> int: + return _hashes.hset(key, field, value) + +## Gets the value of a hash field. +func hget(key: String, field: String, default_value: Variant = null): + return _hashes.hget(key, field, default_value) + +## Gets the values of all the given hash fields. +func hmget(key: String, fields: Array) -> Array: + return _hashes.hmget(key, fields) + +## Sets multiple hash fields to multiple values. +func hmset(key: String, field_value_pairs: Dictionary) -> void: + _hashes.hmset(key, field_value_pairs) + +## Increments the integer value of a hash field by the given amount. +func hincrby(key: String, field: String, amount: int) -> Variant: + return _hashes.hincrby(key, field, amount) + +## Increments the float value of a hash field by the given amount. +func hincrbyfloat(key: String, field: String, amount: float) -> Variant: + return _hashes.hincrbyfloat(key, field, amount) + +## Deletes one or more hash fields. +func hdel(key: String, fields) -> int: + return _hashes.hdel(key, fields) + +## Gets all the fields and values in a hash. +func hgetall(key: String) -> Dictionary: + return _hashes.hgetall(key) + +## Checks if a hash field exists. +func hexists(key: String, field = null) -> bool: + return _hashes.hexists(key, field) + +## Gets all the fields in a hash. +func hkeys(key: String) -> Array: + return _hashes.hkeys(key) + +## Gets all the values in a hash. +func hvals(key: String) -> Array: + return _hashes.hvals(key) + +## Gets the number of fields in a hash. +func hlen(key: String) -> int: + return _hashes.hlen(key) + +# Lists +## Prepends one or multiple values to a list. +func lpush(key: String, value) -> int: + return _lists.lpush(key, value) + +## Appends one or multiple values to a list. +func rpush(key: String, value) -> int: + return _lists.rpush(key, value) + +## Removes and gets the first element in a list. +func lpop(key: String): + return _lists.lpop(key) + +## Removes and gets the last element in a list. +func rpop(key: String): + return _lists.rpop(key) + +## Gets the length of a list. +func llen(key: String) -> int: + return _lists.llen(key) + +## Checks if a list exists. +func lexists(key: String) -> bool: + return _lists.lexists(key) + +## Gets all elements from a list. +func lget(key: String) -> Array: + return _lists.lget(key) + +## Gets a range of elements from a list. +func lrange(key: String, start: int, stop: int) -> Array: + return _lists.lrange(key, start, stop) + +## Gets an element from a list by index. +func lindex(key: String, index: int): + return _lists.lindex(key, index) + +## Sets the value of an element in a list by index. +func lset(key: String, index: int, value) -> bool: + return _lists.lset(key, index, value) + +## Removes elements from a list. +func lrem(key: String, count: int, value) -> int: + return _lists.lrem(key, count, value) + +## Trims a list to the specified range of indices. +func ltrim(key: String, start: int, stop: int) -> bool: + return _lists.ltrim(key, start, stop) + +## Inserts a value into a list before or after a pivot value. +func linsert(key: String, position: String, pivot, value) -> int: + return _lists.linsert(key, position, pivot, value) + +## Atomically returns and removes the first/last element of the list stored at source and pushes the element at the first/last element of the list stored at destination. +func lmove(source: String, destination: String, from: String, to: String): + return _lists.lmove(source, destination, from, to) + +# Sets +## Adds one or more members to a set. +func sadd(key: String, member) -> int: + return _sets.sadd(key, member) + +## Removes one or more members from a set. +func srem(key: String, member) -> int: + return _sets.srem(key, member) + +## Gets all the members in a set. +func smembers(key: String) -> Array: + return _sets.smembers(key) + +## Checks if a member is in a set. +func sismember(key: String, member) -> bool: + return _sets.sismember(key, member) + +## Gets the number of members in a set. +func scard(key: String) -> int: + return _sets.scard(key) + +## Checks if a set exists. +func sexists(key: String) -> bool: + return _sets.sexists(key) + +## Removes and returns a random member from a set. +func spop(key: String): + return _sets.spop(key) + +## Moves a member from one set to another. +func smove(source: String, destination: String, member) -> bool: + return _sets.smove(source, destination, member) + +## Returns the union of the sets stored at the given keys. +func sunion(keys: Array) -> Array: + return _sets.sunion(keys) + +## Returns the intersection of the sets stored at the given keys. +func sinter(keys: Array) -> Array: + return _sets.sinter(keys) + +## Returns the difference of the sets stored at the given keys. +func sdiff(keys: Array) -> Array: + return _sets.sdiff(keys) + +## Stores the union of the sets at keys in the destination key. +func sunionstore(destination: String, keys: Array) -> int: + return _sets.sunionstore(destination, keys) + +## Stores the intersection of the sets at keys in the destination key. +func sinterstore(destination: String, keys: Array) -> int: + return _sets.sinterstore(destination, keys) + +## Stores the difference of the sets at keys in the destination key. +func sdiffstore(destination: String, keys: Array) -> int: + return _sets.sdiffstore(destination, keys) + +## Returns one or more random members from the set at key. +func srandmember(key: String, count: int = 1) -> Variant: + return _sets.srandmember(key, count) + +# Sorted Sets +## Adds a member with a score to a sorted set. +func zadd(key: String, member: String, score: int): + return _sorted_sets.add(key, member, score) + +## Checks if a sorted set exists. +func zexists(key: String) -> bool: + return _sorted_sets.zexists(key) + +## Gets the number of members in a sorted set. +func zcard(key: String) -> int: + return _sorted_sets.zcard(key) + +## Removes a member from a sorted set. +func zrem(key: String, member: String): + return _sorted_sets.remove(key, member) + +## Gets members from a sorted set within a score range. +func zrange(key: String, start, stop, withscores: bool = false): + return _sorted_sets.zrange(key, start, stop, withscores) + +## Gets members from a sorted set within a score range, in reverse order. +func zrevrange(key: String, start, stop, withscores: bool = false): + return _sorted_sets.zrevrange(key, start, stop, withscores) + +## Removes and returns members with scores up to a certain value. +func zpopready(key: String, now: int): + return _sorted_sets.pop_ready(key, now) + +## Returns the score of member in the sorted set at key. +func zscore(key: String, member: String) -> Variant: + return _sorted_sets.zscore(key, member) + +## Returns the rank of member in the sorted set at key. +func zrank(key: String, member: String) -> Variant: + return _sorted_sets.zrank(key, member) + +## Returns the rank of member in the sorted set at key, with scores ordered from high to low. +func zrevrank(key: String, member: String) -> Variant: + return _sorted_sets.zrevrank(key, member) + +## Returns the number of elements in the sorted set at key with a score between min and max. +func zcount(key: String, min_score, max_score) -> int: + return _sorted_sets.zcount(key, min_score, max_score) + +## Increments the score of member in the sorted set at key by increment. +func zincrby(key: String, increment, member: String) -> Variant: + return _sorted_sets.zincrby(key, increment, member) + +## Returns a range of members in a sorted set, by score. +func zrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array: + return _sorted_sets.zrangebyscore(key, min_score, max_score, withscores) + +## Returns a range of members in a sorted set, by score, in reverse order. +func zrevrangebyscore(key: String, min_score, max_score, withscores: bool = false) -> Array: + return _sorted_sets.zrevrangebyscore(key, min_score, max_score, withscores) + +## Computes the union of sorted sets and stores the result in a new key. +func zunionstore(destination: String, keys: Array, aggregate: String = "SUM") -> int: + return _sorted_sets.zunionstore(destination, keys, aggregate) + +## Computes the intersection of sorted sets and stores the result in a new key. +func zinterstore(destination: String, keys: Array, aggregate: String = "SUM") -> int: + return _sorted_sets.zinterstore(destination, keys, aggregate) + +# Pub/Sub +## Posts a message to a channel. +func publish(channel: String, message) -> void: + _pubsub.publish.call_deferred(channel, message) + +## Subscribes to a channel. +func subscribe(channel: String, subscriber: Object) -> void: + _pubsub.subscribe(channel, subscriber) + +## Unsubscribes from a channel. +func unsubscribe(channel: String, subscriber: Object) -> void: + _pubsub.unsubscribe(channel, subscriber) + +## Subscribes to channels matching a pattern. +func psubscribe(pattern: String, subscriber: Object) -> void: + _pubsub.psubscribe(pattern, subscriber) + +## Unsubscribes from channels matching a pattern. +func punsubscribe(pattern: String, subscriber: Object) -> void: + _pubsub.punsubscribe(pattern, subscriber) + +## Returns a list of all active channels. +func list_channels() -> Array: + return _pubsub.list_channels() + +## Returns a list of subscribers for a given channel. +func list_subscribers(channel: String) -> Array: + return _pubsub.list_subscribers(channel) + +## Returns a list of all active patterns. +func list_patterns() -> Array: + return _pubsub.list_patterns() + +## Returns a list of subscribers for a given pattern. +func list_pattern_subscribers(pattern: String) -> Array: + return _pubsub.list_pattern_subscribers(pattern) + +# Expiry +## Sets a key's time to live in seconds. +func expire(key: String, seconds: int) -> bool: + return _expiry.expire(key, seconds) + +## Gets the remaining time to live of a key. +func ttl(key: String) -> int: + return _expiry.ttl(key) + +## Removes the expiration from a key. +func persist(key: String) -> bool: + return _expiry.persist(key) + +## Purges all expired keys +func purge_expired() -> void: + _expiry._purge_expired() + +# Admin +## Deletes all keys from the database. +func flushall() -> void: + _core.flushall() + +# Persistence +## Registers a new persistence backend. +func register_persistence_backend(name: String, backend: GedisPersistenceBackend) -> void: + _persistence_backends[name] = backend + +## Sets the default persistence backend. +func set_default_persistence_backend(name: String) -> bool: + if _persistence_backends.has(name): + _default_persistence_backend = name + return true + return false + +## Saves the current state to a file using the default persistence backend. +func save(path: String, options: Dictionary = {}) -> int: + if _default_persistence_backend.is_empty(): + register_persistence_backend("json", GedisJSONSnapshotBackend.new()) + set_default_persistence_backend("json") + + var backend: GedisPersistenceBackend = _persistence_backends[_default_persistence_backend] + var dump_options = options.duplicate() + if dump_options.has("path"): + dump_options.erase("path") + + var data = _core.dump_all(dump_options) + + var save_options = {"path": path} + return backend.save(data, save_options) + +## Loads the state from a file using the default persistence backend. +func load(path: String, options: Dictionary = {}) -> int: + if _default_persistence_backend.is_empty(): + register_persistence_backend("json", GedisJSONSnapshotBackend.new()) + set_default_persistence_backend("json") + + var backend: GedisPersistenceBackend = _persistence_backends[_default_persistence_backend] + var load_options = {"path": path} + var data = backend.load(load_options) + + if data.is_empty(): + return FAILED + + _core.restore_all(data) + return OK + +## Dumps the entire dataset to a variable. +func dump_all(options: Dictionary = {}) -> Dictionary: + return _core.dump_all(options) + +## Restores the entire dataset from a variable. +func restore_all(data: Dictionary) -> void: + _core.restore_all(data) + +## Restores a key from a serialized value. +func restore(key: String, data: String, backend: String = "") -> int: + var persistence_backend: GedisPersistenceBackend + if backend.is_empty(): + if _default_persistence_backend.is_empty(): + register_persistence_backend("json", GedisJSONSnapshotBackend.new()) + set_default_persistence_backend("json") + persistence_backend = _persistence_backends[_default_persistence_backend] + elif _persistence_backends.has(backend): + persistence_backend = _persistence_backends[backend] + else: + return FAILED + + var deserialized_data = persistence_backend.deserialize(data) + if deserialized_data.is_empty(): + return FAILED + + _core.restore_key(key, deserialized_data) + return OK + +# Debugger +## Returns the type of the value stored at a key. +func type(key: String) -> String: + return _debugger_component.type(key) + +## Returns a dictionary representation of the value stored at a key. +func dump_key(key: String) -> Dictionary: + return _debugger_component.dump(key) + +## Returns a snapshot of the database for keys matching a pattern. +func snapshot(pattern: String = "*") -> Dictionary: + return _debugger_component.snapshot(pattern) + +# Instance helpers +## Sets the name for this Gedis instance. +func set_instance_name(name: String) -> void: + _instance_name = name + +## Gets the name for this Gedis instance. +func get_instance_name() -> String: + return _instance_name + +## Gets all active Gedis instances. +static func get_all_instances() -> Array: + var result: Array = [] + for inst in _instances: + if is_instance_valid(inst): + var info: Dictionary = {} + info["id"] = inst._instance_id + info["name"] = inst.name if inst.name else inst._instance_name + info["object"] = inst + result.append(info) + return result + +static func _on_debugger_message(message: String, data: Array) -> bool: + return GedisDebugger._on_debugger_message(message, data) diff --git a/godot/addons/Gedis/gedis.gd.uid b/godot/addons/Gedis/gedis.gd.uid new file mode 100644 index 0000000..3f2e080 --- /dev/null +++ b/godot/addons/Gedis/gedis.gd.uid @@ -0,0 +1 @@ +uid://7l48sw6l2lq3 diff --git a/godot/addons/Gedis/icon.png b/godot/addons/Gedis/icon.png new file mode 100644 index 0000000..e579569 Binary files /dev/null and b/godot/addons/Gedis/icon.png differ diff --git a/godot/addons/Gedis/icon.png.import b/godot/addons/Gedis/icon.png.import new file mode 100644 index 0000000..d746fb9 --- /dev/null +++ b/godot/addons/Gedis/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://e8pq1st2w4gh" +path="res://.godot/imported/icon.png-c68ef9c02c787235be987bb01b49c46f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/Gedis/icon.png" +dest_files=["res://.godot/imported/icon.png-c68ef9c02c787235be987bb01b49c46f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/Gedis/plugin.cfg b/godot/addons/Gedis/plugin.cfg new file mode 100644 index 0000000..ea4a265 --- /dev/null +++ b/godot/addons/Gedis/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] +name="Gedis" +description="Redis-like in-memory key-value store for Godot" +author="NodotProject" +version="0.1.9" +script="plugin.gd" +icon="icon.png" diff --git a/godot/addons/Gedis/plugin.gd b/godot/addons/Gedis/plugin.gd new file mode 100644 index 0000000..c504b41 --- /dev/null +++ b/godot/addons/Gedis/plugin.gd @@ -0,0 +1,412 @@ +@tool +extends EditorPlugin + +const GedisDebuggerPanel = preload("res://addons/Gedis/debugger/gedis_debugger_panel.tscn") + +class GedisDebuggerPlugin extends EditorDebuggerPlugin: + var dashboard_tabs = {} # session_id -> dashboard_control + var full_snapshot_data = {} # session_id -> full_snapshot_data + var pending_requests = {} # session_id -> { "command": String, "instance_id": int, "context": any } + var _pubsub_events = {} # session_id -> [] + + func _has_capture(capture): + return capture == "gedis" + + func _capture(message, data, session_id): + if not _pubsub_events.has(session_id): + _pubsub_events[session_id] = [] + # message is the full message string received by the editor (e.g. "gedis:instances_data") + # data is the Array payload sent from the game. Engines and editors may wrap values + # differently (sometimes the payload is passed as a single element array), so normalize. + var parts = message.split(":") + var kind = parts[1] if parts.size() > 1 else "" + + match kind: + "ping": + _request_instances_update(session_id) + return true + "instances_data": + # instances may be either sent as the array itself or wrapped as [instances] + var instances = data + if data.size() == 1 and typeof(data[0]) == TYPE_ARRAY: + instances = data[0] + _update_instances(instances, session_id) + return true + "snapshot_data": + # snapshot is expected to be a Dictionary; it may be wrapped in an Array + var snapshot = data[0] if data.size() > 0 else {} + _update_snapshot_data(snapshot, session_id) + return true + "key_value_data": + # key/value payload may be wrapped similarly + var kv = data[0] if data.size() > 0 else {} + _update_key_value_data(kv, session_id) + return true + "pubsub_data": + var channels_data = data[0] if data.size() > 0 else {} + var patterns_data = data[1] if data.size() > 1 else {} + _update_pubsub_tree(session_id, channels_data, patterns_data) + return true + "pubsub_event": + if not session_id in _pubsub_events: + _pubsub_events[session_id] = [] + _pubsub_events[session_id].append({"message": data[0], "data": [data[1], data[2]]}) + _populate_pubsub_events(session_id) + _fetch_pubsub_for_selected_instance(session_id) # Refresh tree on event + return true + return false + + func _setup_session(session_id): + var dashboard = GedisDebuggerPanel.instantiate() + dashboard.name = "Gedis" + dashboard.plugin = self + + var session = get_session(session_id) + session.started.connect(func(): _on_session_started(session_id)) + session.stopped.connect(func(): _on_session_stopped(session_id)) + session.add_session_tab(dashboard) + + dashboard_tabs[session_id] = dashboard + _pubsub_events[session_id] = [] + + func _on_session_started(session_id): + full_snapshot_data[session_id] = {} + _clear_views(session_id) + _populate_pubsub_events(session_id) + _update_pubsub_tree(session_id, {}, {}) + + var dashboard = dashboard_tabs[session_id] + + var instance_selector = dashboard.find_child("instance_selector", true, false) + if instance_selector: + instance_selector.item_selected.connect(func(index): _on_instance_selected(index, session_id)) + + var refresh_button = dashboard.find_child("RefreshButton", true, false) + if refresh_button: + refresh_button.pressed.connect(func(): _request_instances_update(session_id)) + + var fetch_keys_button = dashboard.find_child("FetchKeysButton", true, false) + if fetch_keys_button: + fetch_keys_button.pressed.connect(func(): _fetch_keys_for_selected_instance(session_id)) + + var filter_button = dashboard.find_child("filter_button", true, false) + if filter_button: + filter_button.pressed.connect(func(): _on_filter_pressed(session_id)) + + var key_list = dashboard.find_child("key_list", true, false) + if key_list: + key_list.item_selected.connect(func(): _on_key_selected(session_id)) + + _request_instances_update(session_id) + + func _on_session_stopped(session_id): + if session_id in dashboard_tabs: + var dashboard = dashboard_tabs[session_id] + var status_label = dashboard.find_child("status_label", true, false) + if status_label: + status_label.text = "Game disconnected" + status_label.add_theme_color_override("font_color", Color.RED) + + func _request_instances_update(session_id): + var session = get_session(session_id) + if session and session.is_active(): + session.send_message("gedis:request_instances", []) + + func _update_instances(instances_data, session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var status_label = dashboard.find_child("status_label", true, false) + var instance_selector = dashboard.find_child("instance_selector", true, false) + + if not instance_selector: + print("Warning: instance_selector not found in dashboard") + return + + instance_selector.clear() + + if instances_data.size() > 0: + status_label.text = "Connected - Found %d Gedis instance(s)" % instances_data.size() + status_label.add_theme_color_override("font_color", Color.GREEN) + + for instance_info in instances_data: + var name = instance_info.get("name", "Gedis_%d" % instance_info.get("id", -1)) + var id = int(instance_info["id"]) if instance_info.has("id") else -1 + instance_selector.add_item(name, id) + + if instance_selector.get_item_count() > 0: + instance_selector.select(0) + # The select() method does not trigger the item_selected signal, so we must call the handler manually. + _fetch_keys_for_selected_instance(session_id) + _fetch_pubsub_for_selected_instance(session_id) + else: + status_label.text = "No Gedis instances found in running game" + status_label.add_theme_color_override("font_color", Color.ORANGE) + _clear_views(session_id) + + func _on_instance_selected(_index, session_id): + _fetch_keys_for_selected_instance(session_id) + _fetch_pubsub_for_selected_instance(session_id) + + func _fetch_keys_for_selected_instance(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var instance_selector = dashboard.find_child("instance_selector", true, false) + if not instance_selector or instance_selector.get_selected() < 0: + return + + var instance_id = instance_selector.get_item_id(instance_selector.get_selected()) + var session = get_session(session_id) + pending_requests[session_id] = {"command": "snapshot", "instance_id": instance_id} + if session and session.is_active(): + session.send_message("gedis:request_instance_data", [instance_id, "snapshot", "*"]) + + func _fetch_pubsub_for_selected_instance(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var instance_selector = dashboard.find_child("instance_selector", true, false) + if not instance_selector or instance_selector.get_selected() < 0: + return + + var instance_id = instance_selector.get_item_id(instance_selector.get_selected()) + var session = get_session(session_id) + pending_requests[session_id] = {"command": "pubsub", "instance_id": instance_id} + if session and session.is_active(): + session.send_message("gedis:request_instance_data", [instance_id, "pubsub"]) + + func _on_filter_pressed(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var search_box = dashboard.find_child("search_box", true, false) + var filter_text = search_box.text if search_box else "" + + _populate_key_list(session_id, filter_text) + + + func _update_snapshot_data(snapshot_data, session_id): + if not session_id in dashboard_tabs: + return + + full_snapshot_data[session_id] = snapshot_data + _populate_key_list(session_id) + + func _populate_key_list(session_id, filter_text = ""): + var dashboard = dashboard_tabs[session_id] + var key_list = dashboard.find_child("key_list", true, false) + + if not key_list: + return + + key_list.clear() + var root = key_list.create_item() + + var data_to_display = full_snapshot_data.get(session_id, {}) + + var regex = RegEx.new() + if not filter_text.is_empty(): + var pattern = filter_text.replace("*", ".*").replace("?", ".") + regex.compile(pattern) + + for redis_key in data_to_display.keys(): + if filter_text.is_empty() or regex.search(redis_key): + var key_info = data_to_display[redis_key] + var item = key_list.create_item(root) + item.set_text(0, redis_key) + item.set_text(1, key_info.get("type", "UNKNOWN")) + var ttl_value = key_info.get("ttl", -1) + if ttl_value == -1: + item.set_text(2, "∞") + elif ttl_value == -2: + item.set_text(2, "EXPIRED") + else: + item.set_text(2, str(ttl_value) + "s") + + func _update_key_value_data(key_value_data, session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var key_value_view = dashboard.find_child("key_value_view", true, false) + + if not key_value_view: + return + + if key_value_data is Dictionary and "value" in key_value_data: + key_value_view.text = var_to_str(key_value_data.value) + else: + key_value_view.text = var_to_str(key_value_data) + + func _on_key_selected(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var key_list = dashboard.find_child("key_list", true, false) + var key_value_view = dashboard.find_child("key_value_view", true, false) + var instance_selector = dashboard.find_child("instance_selector", true, false) + + if not key_list or not key_value_view or not instance_selector: + return + + var selected_item = key_list.get_selected() + if not selected_item: + key_value_view.text = "" + return + + var selected_key = selected_item.get_text(0) + if instance_selector.get_selected() >= 0: + var instance_id = instance_selector.get_item_id(instance_selector.get_selected()) + var session = get_session(session_id) + pending_requests[session_id] = {"command": "dump", "instance_id": instance_id, "key": selected_key} + if session and session.is_active(): + session.send_message("gedis:request_instance_data", [instance_id, "dump", selected_key]) + + key_value_view.editable = false + + + func _on_edit_pressed(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var key_value_view = dashboard.find_child("key_value_view", true, false) + var edit_button = dashboard.find_child("edit_button", true, false) + var save_button = dashboard.find_child("save_button", true, false) + + if not key_value_view or not edit_button or not save_button: + return + + key_value_view.editable = true + save_button.disabled = false + edit_button.disabled = true + + func _on_save_pressed(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var key_list = dashboard.find_child("key_list", true, false) + var key_value_view = dashboard.find_child("key_value_view", true, false) + var instance_selector = dashboard.find_child("instance_selector", true, false) + var edit_button = dashboard.find_child("edit_button", true, false) + var save_button = dashboard.find_child("save_button", true, false) + + if not key_list or not key_value_view or not instance_selector or not edit_button or not save_button: + return + + var selected_item = key_list.get_selected() + if not selected_item or instance_selector.get_selected() < 0: + return + + var instance_id = instance_selector.get_item_id(instance_selector.get_selected()) + var key = selected_item.get_text(0) + var new_value_text = key_value_view.text + + var json = JSON.new() + var error = json.parse(new_value_text) + var new_value + if error == OK: + new_value = json.get_data() + else: + # Fallback for non-JSON strings + new_value = new_value_text + + var session = get_session(session_id) + if session and session.is_active(): + pending_requests[session_id] = {"command": "set", "instance_id": instance_id, "key": key} + session.send_message("gedis:request_instance_data", [instance_id, "set", key, new_value]) + + key_value_view.editable = false + save_button.disabled = true + edit_button.disabled = false + + func _clear_views(session_id): + if not session_id in dashboard_tabs: + return + + var dashboard = dashboard_tabs[session_id] + var key_list = dashboard.find_child("key_list", true, false) + var key_value_view = dashboard.find_child("key_value_view", true, false) + var edit_button = dashboard.find_child("edit_button", true, false) + var save_button = dashboard.find_child("save_button", true, false) + + if key_list: + key_list.clear() + if key_value_view: + key_value_view.text = "" + if edit_button: + edit_button.disabled = true + if save_button: + save_button.disabled = true + + func _populate_pubsub_events(session_id): + var dashboard = dashboard_tabs[session_id] + var events_tree = dashboard.find_child("PubSubEventsTree", true, false) + if not events_tree: + return + events_tree.clear() + var root = events_tree.create_item() + for event in _pubsub_events.get(session_id, []): + var event_item = events_tree.create_item(root) + event_item.set_text(0, event.message) + event_item.set_text(1, JSON.stringify(event.data)) + + func _update_pubsub_tree(session_id, channels_data, patterns_data): + var dashboard = dashboard_tabs[session_id] + var pub_sub_tree = dashboard.find_child("PubSubTree", true, false) + if not pub_sub_tree: + return + + pub_sub_tree.clear() + var root = pub_sub_tree.create_item() + + var channels_item = pub_sub_tree.create_item(root) + channels_item.set_text(0, "Channels") + for channel_name in channels_data: + var subscribers = channels_data[channel_name] + var channel_item = pub_sub_tree.create_item(channels_item) + channel_item.set_text(0, channel_name) + for sub in subscribers: + var sub_item = pub_sub_tree.create_item(channel_item) + sub_item.set_text(0, str(sub)) + + var patterns_item = pub_sub_tree.create_item(root) + patterns_item.set_text(0, "Patterns") + for pattern_name in patterns_data: + var subscribers = patterns_data[pattern_name] + var pattern_item = pub_sub_tree.create_item(patterns_item) + pattern_item.set_text(0, pattern_name) + for sub in subscribers: + var sub_item = pub_sub_tree.create_item(pattern_item) + sub_item.set_text(0, str(sub)) + + func send_message_to_game(message: String, data: Array): + var session_id = get_current_session_id() + if session_id != -1: + var session = get_session(session_id) + if session and session.is_active(): + session.send_message("gedis:" + message, data) + + func get_current_session_id(): + for session_id in dashboard_tabs: + var session = get_session(session_id) + if session and session.is_active(): + return session_id + return -1 + +var debugger_plugin + +func _enter_tree(): + debugger_plugin = GedisDebuggerPlugin.new() + add_debugger_plugin(debugger_plugin) + +func _exit_tree(): + remove_debugger_plugin(debugger_plugin) + debugger_plugin = null diff --git a/godot/addons/Gedis/plugin.gd.uid b/godot/addons/Gedis/plugin.gd.uid new file mode 100644 index 0000000..1d8c476 --- /dev/null +++ b/godot/addons/Gedis/plugin.gd.uid @@ -0,0 +1 @@ +uid://bad4nw3ewpjx6 diff --git a/godot/addons/GedisQueue/GedisQueue.gd b/godot/addons/GedisQueue/GedisQueue.gd new file mode 100644 index 0000000..4a20392 --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueue.gd @@ -0,0 +1,274 @@ +extends Node +class_name GedisQueue + +## Manages job queues and workers. +## +## This class provides a high-level interface for creating and managing job queues +## that are processed by workers. It uses Gedis (a Godot Redis-like in-memory +## data structure server) for storing job information and queue state. + +## Emitted when a job is completed successfully. +signal completed(job: GedisJob, return_value) +## Emitted when a job fails. +signal failed(job: GedisJob, error_message: String) +## Emitted when a job's progress is updated. +signal progress(job: GedisJob, value: float) + +const QUEUE_PREFIX = "gedis_queue:" + +var max_completed_jobs := 0 +var max_failed_jobs := 0 + +const STATUS_WAITING = "waiting" +const STATUS_ACTIVE = "active" +const STATUS_COMPLETED = "completed" +const STATUS_FAILED = "failed" + +var _gedis: Gedis +var _workers: Array[GedisWorker] = [] + +## Sets up the GedisQueue with a Gedis instance. +## +## If no Gedis instance is provided, a new one will be created automatically +## when needed. +## +## @param gedis_instance The Gedis instance to use. +func setup(gedis_instance: Gedis = null): + if gedis_instance: + _gedis = gedis_instance + else: + _gedis = Gedis.new() + _gedis.name = "Gedis" + add_child(_gedis) + +## Adds a new job to a queue. +## +## @param queue_name The name of the queue to add the job to. +## @param job_data A dictionary containing the data for the job. +## @param opts A dictionary of options for the job. +## "add_to_front": (bool) If true, adds the job to the front of the queue. +## @return The newly created GedisJob. +func add(queue_name: String, job_data: Dictionary, opts: Dictionary = {}) -> GedisJob: + _ensure_gedis_instance() + + var job_id = _generate_job_id() + var job_key = _get_job_key(queue_name, job_id) + var job = GedisJob.new(self, queue_name, job_id, job_data) + + var job_hash = { + "id": job_id, + "data": job_data, + "status": STATUS_WAITING, + "progress": 0.0 + } + + for key in job_hash: + _gedis.hset(job_key, key, job_hash[key]) + + if opts.get("add_to_front", false): + _gedis.lpush(_get_queue_key(queue_name, STATUS_WAITING), job_id) + else: + _gedis.rpush(_get_queue_key(queue_name, STATUS_WAITING), job_id) + + _gedis.publish(_get_event_channel(queue_name, "added"), {"job_id": job_id, "data": job_data}) + + return job + +## Retrieves a job from a queue by its ID. +## +## @param queue_name The name of the queue. +## @param job_id The ID of the job to retrieve. +## @return The GedisJob if found, otherwise null. +func get_job(queue_name: String, job_id: String) -> GedisJob: + var job_key = _get_job_key(queue_name, job_id) + var job_hash = _gedis.hgetall(job_key) + + if job_hash.is_empty(): + return null + + var job_data = job_hash.get("data", {}) + var job_status = job_hash.get("status", GedisQueue.STATUS_WAITING) + var job = GedisJob.new(self, queue_name, job_id, job_data, job_status) + return job + +## Retrieves a list of jobs from a queue. +## +## @param queue_name The name of the queue. +## @param types An array of job statuses to retrieve (e.g., ["waiting", "active"]). +## @param start The starting index. +## @param end The ending index. +## @param asc Whether to sort in ascending order (currently unused). +## @return An array of GedisJob objects. +func get_jobs(queue_name: String, types: Array, start: int = 0, end: int = -1, asc: bool = false) -> Array[GedisJob]: + var jobs: Array[GedisJob] = [] + for type in types: + var queue_key = _get_queue_key(queue_name, type) + var job_ids = _gedis.lrange(queue_key, start, end) + for job_id in job_ids: + var job = get_job(queue_name, job_id) + if job: + jobs.append(job) + return jobs + +## Pauses a queue. +## +## When a queue is paused, workers will not process any new jobs from it. +## +## @param queue_name The name of the queue to pause. +func pause(queue_name: String) -> void: + _ensure_gedis_instance() + + var state_key = _get_state_key(queue_name) + _gedis.hset(state_key, "paused", "1") + +## Resumes a paused queue. +## +## @param queue_name The name of the queue to resume. +func resume(queue_name: String) -> void: + _ensure_gedis_instance() + + var state_key = _get_state_key(queue_name) + _gedis.hdel(state_key, "paused") + +## Checks if a queue is paused. +## +## @param queue_name The name of the queue. +## @return True if the queue is paused, otherwise false. +func is_paused(queue_name: String) -> bool: + _ensure_gedis_instance() + + var state_key = _get_state_key(queue_name) + return _gedis.hexists(state_key, "paused") + +## Updates the progress of a job. +## +## @param queue_name The name of the queue. +## @param job_id The ID of the job. +## @param value The new progress value (0.0 to 1.0). +func update_job_progress(queue_name: String, job_id: String, value: float): + _ensure_gedis_instance() + + var job_key = _get_job_key(queue_name, job_id) + _gedis.hset(job_key, "progress", value) + _gedis.publish(_get_event_channel(queue_name, "progress"), {"job_id": job_id, "progress": value}) + progress.emit(get_job(queue_name, job_id), value) + +## Removes a job from a queue. +## +## @param queue_name The name of the queue. +## @param job_id The ID of the job to remove. +func remove_job(queue_name: String, job_id: String): + _ensure_gedis_instance() + + var job_key = _get_job_key(queue_name, job_id) + _gedis.del(job_key) + + # Remove the job ID from all possible status lists + for status in [STATUS_WAITING, STATUS_ACTIVE, STATUS_COMPLETED, STATUS_FAILED]: + var queue_key = _get_queue_key(queue_name, status) + _gedis.lrem(queue_key, 0, job_id) + +func _get_queue_key(queue_name: String, status: String = STATUS_WAITING) -> String: + return "%s%s:%s" % [QUEUE_PREFIX, queue_name, status] + +func _get_job_key(queue_name: String, job_id: String) -> String: + return QUEUE_PREFIX + queue_name + ":job:" + job_id + +func _get_state_key(queue_name: String) -> String: + return QUEUE_PREFIX + queue_name + ":state" + +func _get_event_channel(queue_name: String, event: String) -> String: + return "%s%s:events:%s" % [QUEUE_PREFIX, queue_name, event] + +func _generate_job_id() -> String: + var t = Time.get_unix_time_from_system() + var r = randi() % 1000 + return "%s-%s" % [t, r] + +func _ensure_gedis_instance(): + if not _gedis: + var gedis_instance = Gedis.new() + gedis_instance.name = "Gedis" + add_child(gedis_instance) + setup(gedis_instance) + +## Starts a worker to process jobs from a queue. +## +## @param queue_name The name of the queue to process. +## @param processor A callable that will be executed for each job. +## @return The newly created GedisWorker. +func process(queue_name: String, processor: Callable, p_batch_size: int = 1) -> GedisWorker: + var worker = GedisWorker.new(self, queue_name, processor, p_batch_size) + add_child(worker) + _workers.append(worker) + worker.start() + return worker + +## Closes all workers for a specific queue. +## +## @param queue_name The name of the queue. +func close(queue_name: String) -> void: + var workers_to_remove: Array[GedisWorker] = [] + for worker in _workers: + if worker._queue_name == queue_name: + workers_to_remove.append(worker) + + for worker in workers_to_remove: + worker.close() + _workers.erase(worker) + worker.queue_free() + +func _enter_tree() -> void: + if !_gedis: + var gedis_instance = Gedis.new() + gedis_instance.name = "Gedis" + add_child(gedis_instance) + _gedis = gedis_instance + +func _exit_tree(): + for worker in _workers: + if is_instance_valid(worker): + worker.close() + +## Marks a job as completed. +## +## @param job The job to mark as completed. +## @param return_value The return value of the job. +func _job_completed(job: GedisJob, return_value): + _ensure_gedis_instance() + var job_key = _get_job_key(job.queue_name, job.id) + _gedis.lrem(_get_queue_key(job.queue_name, STATUS_ACTIVE), 1, job.id) + + completed.emit(job, return_value) + _gedis.publish(_get_event_channel(job.queue_name, "completed"), {"job_id": job.id, "return_value": return_value}) + + if max_completed_jobs == 0: + _gedis.del(job_key) + else: + _gedis.hset(job_key, "status", STATUS_COMPLETED) + _gedis.hset(job_key, "returnvalue", return_value) + _gedis.lpush(_get_queue_key(job.queue_name, STATUS_COMPLETED), job.id) + if max_completed_jobs > 0: + _gedis.ltrim(_get_queue_key(job.queue_name, STATUS_COMPLETED), 0, max_completed_jobs - 1) + + +## Marks a job as failed. +## +## @param job The job to mark as failed. +## @param error_message The error message. +func _job_failed(job: GedisJob, error_message: String): + _ensure_gedis_instance() + var job_key = _get_job_key(job.queue_name, job.id) + _gedis.lrem(_get_queue_key(job.queue_name, STATUS_ACTIVE), 1, job.id) + + failed.emit(job, error_message) + _gedis.publish(_get_event_channel(job.queue_name, "failed"), {"job_id": job.id, "error_message": error_message}) + + if max_failed_jobs == 0: + _gedis.del(job_key) + else: + _gedis.hset(job_key, "status", STATUS_FAILED) + _gedis.hset(job_key, "failed_reason", error_message) + _gedis.lpush(_get_queue_key(job.queue_name, STATUS_FAILED), job.id) + if max_failed_jobs > 0: + _gedis.ltrim(_get_queue_key(job.queue_name, STATUS_FAILED), 0, max_failed_jobs - 1) diff --git a/godot/addons/GedisQueue/GedisQueue.gd.uid b/godot/addons/GedisQueue/GedisQueue.gd.uid new file mode 100644 index 0000000..ed26305 --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueue.gd.uid @@ -0,0 +1 @@ +uid://cbcpxfgtw831t diff --git a/godot/addons/GedisQueue/GedisQueueJob.gd b/godot/addons/GedisQueue/GedisQueueJob.gd new file mode 100644 index 0000000..32c6bd1 --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueueJob.gd @@ -0,0 +1,51 @@ +extends RefCounted + +class_name GedisJob + +## Represents a job in a GedisQueue. +## +## This class holds information about a job, such as its ID, data, and status. +## It also provides methods for interacting with the job, such as updating its +## progress and removing it from the queue. + +var id: String +var data: Dictionary +var queue_name: String +var status: String + +var _gedis_queue + +func _init(p_gedis_queue, p_queue_name: String, p_id: String, p_data: Dictionary, p_status: String = GedisQueue.STATUS_WAITING): + _gedis_queue = p_gedis_queue + queue_name = p_queue_name + id = p_id + data = p_data + status = p_status + +## Updates the progress of the job. +## +## @param value The new progress value (0.0 to 1.0). +func progress(value: float) -> void: + _gedis_queue.update_job_progress(queue_name, id, value) + +## Removes the job from the queue. +func remove() -> void: + _gedis_queue.remove_job(queue_name, id) + +## Marks the job as completed. +## +## This should be called by a worker's processor function when the job has +## been successfully processed. +## +## @param return_value An optional value to store as the result of the job. +func complete(return_value: Variant = null) -> void: + _gedis_queue._job_completed(self, return_value) + +## Marks the job as failed. +## +## This should be called by a worker's processor function when the job has +## failed to be processed. +## +## @param error_message The error message to store for the failed job. +func fail(error_message: String) -> void: + _gedis_queue._job_failed(self, error_message) \ No newline at end of file diff --git a/godot/addons/GedisQueue/GedisQueueJob.gd.uid b/godot/addons/GedisQueue/GedisQueueJob.gd.uid new file mode 100644 index 0000000..a8c51f8 --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueueJob.gd.uid @@ -0,0 +1 @@ +uid://3qjcot47lxgw diff --git a/godot/addons/GedisQueue/GedisQueueWorker.gd b/godot/addons/GedisQueue/GedisQueueWorker.gd new file mode 100644 index 0000000..e67b76e --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueueWorker.gd @@ -0,0 +1,106 @@ +extends Node + +class_name GedisWorker + +## Processes jobs from a GedisQueue. +## +## A worker is responsible for fetching jobs from a specific queue and executing +## a processor function for each job. +## +## The processor function receives the job and is responsible for calling +## `job.complete()` or `job.fail()` to finish the job. The worker itself does +## not handle the return value of the processor. + +signal completed(job: GedisJob, return_value) +signal failed(job: GedisJob, error_message: String) +signal progress(job: GedisJob, value: float) +signal _batch_completed + +var _gedis_queue: GedisQueue +var _queue_name: String +var _processor: Callable +var _is_running = false +var _gedis: Gedis +var _jobs_in_progress = {} + +var batch_size = 1 + +func _init(p_gedis_queue: GedisQueue, p_queue_name: String, p_processor: Callable, p_batch_size: int = 1): + _gedis_queue = p_gedis_queue + _gedis = _gedis_queue._gedis + _queue_name = p_queue_name + _processor = p_processor + batch_size = p_batch_size + + _gedis_queue.completed.connect(_on_job_completed) + _gedis_queue.failed.connect(_on_job_failed) + _gedis_queue.progress.connect(func(job: GedisJob, value: float): progress.emit(job, value)) + +## Starts the worker. +func start(): + _is_running = true + _process_jobs() + +## Stops the worker. +func close(): + _is_running = false + +func _on_job_completed(job: GedisJob, return_value): + if _jobs_in_progress.has(job.id): + _jobs_in_progress.erase(job.id) + if _jobs_in_progress.is_empty(): + _batch_completed.emit() + completed.emit(job, return_value) + +func _on_job_failed(job: GedisJob, error_message: String): + if _jobs_in_progress.has(job.id): + _jobs_in_progress.erase(job.id) + if _jobs_in_progress.is_empty(): + _batch_completed.emit() + failed.emit(job, error_message) + +func _process_jobs(): + await get_tree().process_frame + while _is_running: + if _gedis_queue.is_paused(_queue_name): + await get_tree().create_timer(1.0).timeout + continue + + var job_ids = [] + for i in range(batch_size): + var job_id = _gedis.lpop(_gedis_queue._get_queue_key(_queue_name, GedisQueue.STATUS_WAITING)) + if not job_id: + break + job_ids.append(job_id) + + if job_ids.is_empty(): + if Engine.is_editor_hint(): + await get_tree().process_frame + else: + if get_tree(): + await get_tree().process_frame + continue + + for job_id in job_ids: + _gedis.lpush(_gedis_queue._get_queue_key(_queue_name, GedisQueue.STATUS_ACTIVE), job_id) + var job = _gedis_queue.get_job(_queue_name, job_id) + if not job: + push_warning("GedisQueueWorker: Job with id %s not found." % job_id) + continue + + _jobs_in_progress[job_id] = job + var job_key = _gedis_queue._get_job_key(_queue_name, job_id) + _gedis.hset(job_key, "status", GedisQueue.STATUS_ACTIVE) + _gedis_queue._gedis.publish(_gedis_queue._get_event_channel(_queue_name, "active"), {"job_id": job.id}) + _process_job(job) + + if not _jobs_in_progress.is_empty(): + await _batch_completed + +func _process_job(job: GedisJob): + var result = await _processor.call(job) + if result is Object and result.has_method("is_valid"): + result = await result + + if job.status == GedisQueue.STATUS_ACTIVE: + job.complete(result) diff --git a/godot/addons/GedisQueue/GedisQueueWorker.gd.uid b/godot/addons/GedisQueue/GedisQueueWorker.gd.uid new file mode 100644 index 0000000..8095ee3 --- /dev/null +++ b/godot/addons/GedisQueue/GedisQueueWorker.gd.uid @@ -0,0 +1 @@ +uid://ccq10gtvm32oo diff --git a/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd new file mode 100644 index 0000000..3e1795c --- /dev/null +++ b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd @@ -0,0 +1,67 @@ +@tool +extends VBoxContainer + +# UI references will be set from the scene +@export var queues_tree: Tree +@export var jobs_tree: Tree +@export var job_details_text: TextEdit +@export var refresh_button: Button + +var plugin + +func _ready() -> void: + if refresh_button: + refresh_button.pressed.connect(_on_refresh_pressed) + + # Set column titles for the queues tree + queues_tree.set_column_titles_visible(true) + queues_tree.set_column_title(0, "Queue") + queues_tree.set_column_title(1, "Jobs") + + # Set column titles for the jobs tree + jobs_tree.set_column_titles_visible(true) + jobs_tree.set_column_title(0, "ID") + jobs_tree.set_column_title(1, "Queue") + jobs_tree.set_column_title(2, "Status") + +func set_plugin(p) -> void: + plugin = p + +func _on_refresh_pressed() -> void: + if plugin: + # This assumes the main dashboard has an instance selector + var selected_id = get_parent().get_parent().instance_selector.get_item_id(get_parent().get_parent().instance_selector.selected) + var session_id = plugin.get_current_session_id() + if session_id != -1: + plugin._fetch_keys_for_selected_instance(session_id) + +func update_queues(data: Dictionary) -> void: + queues_tree.clear() + var root = queues_tree.create_item() + queues_tree.hide_root = true + + for queue_name in data: + var queue_item = queues_tree.create_item(root) + queue_item.set_text(0, queue_name) + queue_item.set_text(1, str(data[queue_name].size())) + +func update_jobs(jobs: Array) -> void: + jobs_tree.clear() + var root = jobs_tree.create_item() + jobs_tree.hide_root = true + + for job in jobs: + var job_item = jobs_tree.create_item(root) + job_item.set_text(0, job.id) + job_item.set_text(1, job.queue_name) + job_item.set_text(2, job.status) + job_item.set_meta("data", job.data) + +func _on_jobs_tree_item_selected() -> void: + var selected_item = jobs_tree.get_selected() + if selected_item: + job_details_text.text = selected_item.get_meta("data") + +func _on_queues_tree_item_selected() -> void: + # TODO: should filter out jobs not the selected queue + pass diff --git a/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd.uid b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd.uid new file mode 100644 index 0000000..cb05b85 --- /dev/null +++ b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd.uid @@ -0,0 +1 @@ +uid://c5fv5yw1cok24 diff --git a/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.tscn b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.tscn new file mode 100644 index 0000000..7c2b9c7 --- /dev/null +++ b/godot/addons/GedisQueue/debugger/gedis_queue_debugger_panel.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=2 format=3 uid="uid://b5h5h5h5h5h5"] + +[ext_resource type="Script" uid="uid://c5fv5yw1cok24" path="res://addons/GedisQueue/debugger/gedis_queue_debugger_panel.gd" id="1_abcde"] + +[node name="GedisQueueDebuggerPanel" type="VBoxContainer" node_paths=PackedStringArray("queues_tree", "jobs_tree", "job_details_text", "refresh_button")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource("1_abcde") +queues_tree = NodePath("HSplitContainer/QueuesTree") +jobs_tree = NodePath("HSplitContainer/JobsVBox/JobsTree") +job_details_text = NodePath("HSplitContainer/JobsVBox/JobDetailsText") +refresh_button = NodePath("RefreshButton") + +[node name="RefreshButton" type="Button" parent="."] +layout_mode = 2 +text = "Refresh Queues" + +[node name="HSplitContainer" type="HSplitContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="QueuesTree" type="Tree" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.3 +columns = 2 +column_titles_visible = true +select_mode = 1 + +[node name="JobsVBox" type="VBoxContainer" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="JobsTree" type="Tree" parent="HSplitContainer/JobsVBox"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 3 +column_titles_visible = true +select_mode = 1 + +[node name="JobDetailsText" type="TextEdit" parent="HSplitContainer/JobsVBox"] +layout_mode = 2 +size_flags_vertical = 3 +editable = false + +[connection signal="item_selected" from="HSplitContainer/QueuesTree" to="." method="_on_queues_tree_item_selected"] +[connection signal="item_selected" from="HSplitContainer/JobsVBox/JobsTree" to="." method="_on_jobs_tree_item_selected"] diff --git a/godot/addons/GedisQueue/icon.png b/godot/addons/GedisQueue/icon.png new file mode 100644 index 0000000..3829df9 Binary files /dev/null and b/godot/addons/GedisQueue/icon.png differ diff --git a/godot/addons/GedisQueue/icon.png.import b/godot/addons/GedisQueue/icon.png.import new file mode 100644 index 0000000..74e27cc --- /dev/null +++ b/godot/addons/GedisQueue/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://48jxifffcg1y" +path="res://.godot/imported/icon.png-bff46c8d6bc629df6e2505dfe04b11b4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/GedisQueue/icon.png" +dest_files=["res://.godot/imported/icon.png-bff46c8d6bc629df6e2505dfe04b11b4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/GedisQueue/plugin.cfg b/godot/addons/GedisQueue/plugin.cfg new file mode 100644 index 0000000..8968b1b --- /dev/null +++ b/godot/addons/GedisQueue/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="GedisQueue" +description="A BullMQ-like queue system for Godot, backed by Gedis." +author="NodotProject" +version="0.1.9" +script="plugin.gd" \ No newline at end of file diff --git a/godot/addons/GedisQueue/plugin.gd b/godot/addons/GedisQueue/plugin.gd new file mode 100644 index 0000000..8f3c3e6 --- /dev/null +++ b/godot/addons/GedisQueue/plugin.gd @@ -0,0 +1,87 @@ +@tool +extends EditorPlugin + +const GedisQueueDebuggerPanel = preload("res://addons/GedisQueue/debugger/gedis_queue_debugger_panel.tscn") + +var queue_debugger_plugin +var queue_panel +var dashboard + +func _enter_tree(): + queue_debugger_plugin = GedisQueueDebuggerPlugin.new() + add_debugger_plugin(queue_debugger_plugin) + + var timer = Timer.new() + timer.wait_time = 1 + timer.timeout.connect(_on_timer_timeout) + add_child(timer) + timer.start() + +func _on_timer_timeout(): + var editor_node = EditorInterface.get_base_control() + dashboard = editor_node.find_child("Gedis", true, false) + + if dashboard: + var debugger = dashboard.plugin + if debugger: + var tab_container = dashboard.find_child("TabContainer", true, false) + if tab_container: + queue_panel = dashboard.find_child("Queue", true, false) + if !queue_panel: + queue_panel = GedisQueueDebuggerPanel.instantiate() + queue_panel.name = "Queue" + tab_container.add_child(queue_panel) + queue_panel.set_plugin(debugger) + + var session_id = debugger.get_current_session_id() + if session_id != -1: + queue_debugger_plugin.set_queue_panel(session_id, queue_panel) + + +func _exit_tree(): + if queue_debugger_plugin: + remove_debugger_plugin(queue_debugger_plugin) + queue_debugger_plugin = null + if queue_panel: + queue_panel.queue_free() + +class GedisQueueDebuggerPlugin extends EditorDebuggerPlugin: + var queue_panels = {} + + func set_queue_panel(session_id, panel): + queue_panels[session_id] = panel + + func _has_capture(capture): + return capture == "gedis" + + func _capture(message, data, session_id): + var parts = message.split(":") + var kind = parts[1] if parts.size() > 1 else "" + + if session_id in queue_panels: + var queue_panel = queue_panels[session_id] + match kind: + "snapshot_data": + if queue_panel: + var snapshot = data[0] + var queues = {} + var jobs = [] + + for key in snapshot: + var value = snapshot[key] + if "job" in key: + var job_data = value["value"] + var key_parts = key.split(":") + if key_parts.size() > 2: + job_data["queue_name"] = key_parts[1] + jobs.append(job_data) + else: + var queue_name = key.replace("gedis_queue:", "").replace(":waiting", "").replace(":active", "") + if not queue_name in queues: + queues[queue_name] = [] + queues[queue_name].append_array(value["value"]) + + queue_panel.update_queues(queues) + queue_panel.update_jobs(jobs) + return true + return false diff --git a/godot/addons/GedisQueue/plugin.gd.uid b/godot/addons/GedisQueue/plugin.gd.uid new file mode 100644 index 0000000..376e978 --- /dev/null +++ b/godot/addons/GedisQueue/plugin.gd.uid @@ -0,0 +1 @@ +uid://vbxscf2mqmmy diff --git a/godot/addons/PaletteTools/Images/Blank.png b/godot/addons/PaletteTools/Images/Blank.png new file mode 100644 index 0000000..7966a87 Binary files /dev/null and b/godot/addons/PaletteTools/Images/Blank.png differ diff --git a/godot/addons/PaletteTools/Images/Blank.png.import b/godot/addons/PaletteTools/Images/Blank.png.import new file mode 100644 index 0000000..d029e4f --- /dev/null +++ b/godot/addons/PaletteTools/Images/Blank.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cim3uuxhxs3x4" +path="res://.godot/imported/Blank.png-e20d4d6a47b08c47420d456c717ca61f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/Blank.png" +dest_files=["res://.godot/imported/Blank.png-e20d4d6a47b08c47420d456c717ca61f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/Plus.png b/godot/addons/PaletteTools/Images/Plus.png new file mode 100644 index 0000000..6a0cda0 Binary files /dev/null and b/godot/addons/PaletteTools/Images/Plus.png differ diff --git a/godot/addons/PaletteTools/Images/Plus.png.import b/godot/addons/PaletteTools/Images/Plus.png.import new file mode 100644 index 0000000..15ad244 --- /dev/null +++ b/godot/addons/PaletteTools/Images/Plus.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bv0rua34hrcsa" +path="res://.godot/imported/Plus.png-e4e5eebf42f899e6f55e4d0ffe60c50a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/Plus.png" +dest_files=["res://.godot/imported/Plus.png-e4e5eebf42f899e6f55e4d0ffe60c50a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/checked.png b/godot/addons/PaletteTools/Images/checked.png new file mode 100755 index 0000000..1b76d16 Binary files /dev/null and b/godot/addons/PaletteTools/Images/checked.png differ diff --git a/godot/addons/PaletteTools/Images/checked.png.import b/godot/addons/PaletteTools/Images/checked.png.import new file mode 100644 index 0000000..1f81f1e --- /dev/null +++ b/godot/addons/PaletteTools/Images/checked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6f62u2b00rdv" +path="res://.godot/imported/checked.png-d81f80362f72567a0fcdbf6bf355283e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/checked.png" +dest_files=["res://.godot/imported/checked.png-d81f80362f72567a0fcdbf6bf355283e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/checked_4k.png b/godot/addons/PaletteTools/Images/checked_4k.png new file mode 100644 index 0000000..a68a486 Binary files /dev/null and b/godot/addons/PaletteTools/Images/checked_4k.png differ diff --git a/godot/addons/PaletteTools/Images/checked_4k.png.import b/godot/addons/PaletteTools/Images/checked_4k.png.import new file mode 100644 index 0000000..c6134a5 --- /dev/null +++ b/godot/addons/PaletteTools/Images/checked_4k.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blxftvivd7x5x" +path="res://.godot/imported/checked_4k.png-342c0f16a75c36f211dc8d6291dcbfde.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/checked_4k.png" +dest_files=["res://.godot/imported/checked_4k.png-342c0f16a75c36f211dc8d6291dcbfde.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/spyglass.png b/godot/addons/PaletteTools/Images/spyglass.png new file mode 100644 index 0000000..cd33f77 Binary files /dev/null and b/godot/addons/PaletteTools/Images/spyglass.png differ diff --git a/godot/addons/PaletteTools/Images/spyglass.png.import b/godot/addons/PaletteTools/Images/spyglass.png.import new file mode 100644 index 0000000..58b186c --- /dev/null +++ b/godot/addons/PaletteTools/Images/spyglass.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duuedcimehb1f" +path="res://.godot/imported/spyglass.png-c8a5839ba62e6621291233abb45e1ee9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/spyglass.png" +dest_files=["res://.godot/imported/spyglass.png-c8a5839ba62e6621291233abb45e1ee9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/unchecked.png b/godot/addons/PaletteTools/Images/unchecked.png new file mode 100755 index 0000000..c8262c1 Binary files /dev/null and b/godot/addons/PaletteTools/Images/unchecked.png differ diff --git a/godot/addons/PaletteTools/Images/unchecked.png.import b/godot/addons/PaletteTools/Images/unchecked.png.import new file mode 100644 index 0000000..a64f708 --- /dev/null +++ b/godot/addons/PaletteTools/Images/unchecked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://585mb8p87t5x" +path="res://.godot/imported/unchecked.png-5249d27fb8a908d32590159e278c99c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/unchecked.png" +dest_files=["res://.godot/imported/unchecked.png-5249d27fb8a908d32590159e278c99c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/Images/unchecked_4k.png b/godot/addons/PaletteTools/Images/unchecked_4k.png new file mode 100644 index 0000000..0b49623 Binary files /dev/null and b/godot/addons/PaletteTools/Images/unchecked_4k.png differ diff --git a/godot/addons/PaletteTools/Images/unchecked_4k.png.import b/godot/addons/PaletteTools/Images/unchecked_4k.png.import new file mode 100644 index 0000000..cd908a0 --- /dev/null +++ b/godot/addons/PaletteTools/Images/unchecked_4k.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpiotwvx0p7kp" +path="res://.godot/imported/unchecked_4k.png-4470f9e94d7b68e36513e292443cc559.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/Images/unchecked_4k.png" +dest_files=["res://.godot/imported/unchecked_4k.png-4470f9e94d7b68e36513e292443cc559.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/LICENSE.md b/godot/addons/PaletteTools/LICENSE.md new file mode 100644 index 0000000..d8d9374 --- /dev/null +++ b/godot/addons/PaletteTools/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2023 RancidMilk + +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/godot/addons/PaletteTools/README.md b/godot/addons/PaletteTools/README.md new file mode 100644 index 0000000..9d1a56e --- /dev/null +++ b/godot/addons/PaletteTools/README.md @@ -0,0 +1,63 @@ +# Godot 4 Palette Tools + +This is a small addon that allows for easy color palette integration in Godot 4. Quickly Create/Download palettes. Load/Save for use any time. Comes with the ability to quickly switch the editor's color-picker swatches as well as a custom color picker that can switch between palettes on demand. Download option currently supports Lospec palettes. + +## Setup + +### Install Methods + +#### Recommended: + +Open the *AssetLib* tab in the editor and search for Palette Tools. + +#### Manual Method: + +From either the [project repo](https://github.com/RancidMilkGames/GodotPaletteTools) or [asset library website](https://godotengine.org/asset-library/asset) copy the "PaletteTools" folder to your project's "addons" folder. If this is your first time using a Godot plugin/addon, and you don't have an addons folder, make a folder named "addons" in the root of your project. + +### Activate + +Godot will automatically activate the addon if you used the previously mentioned recommended method. If done manually, go to `Project Settings->Plugins` to activate it. + + + + + + + + + + + + + +
+ :warning: Notice +
+
    +
  • Plugin will not be immediately noticeable. It will appear in the right-side dock with Inspector/Node/History. You may need to either expand the dock or use the arrows to access it.
  • +
+
+ +## USE + +* **Custom Palette Picker**: Replaces the default color picker with an extended one that can switch between palettes. For special use cases and not thoroughly tested so by default it's set to off. +* **Saved Palettes**: List of previously saved palettes. To load or delete a palette, select it in the list and then press the corresponding button. +* **Get from Lospec**: Either enter a Lospec palette URL or browse through popular palettes. The plugin will automatically keep track of the palette name, and it's author for referencing or crediting. +* **Palette Preview**: Preview/editor for palettes. Click the plus sign to add a color to the palette. Existing colors can be edited by clicking on them, or removed with the red close button in the top right corner of the button. The save palette button will store the palette. +* **Replace Editor Swatches**: If you have an eligible palette, an option will appear that allows you to save it to the editor's swatches, which can be accessed from both the default color picker and the extended one. + +## Tips + +* You can copy the "res://addons/PaletteTools/color_presets.cfg" file to move saved palettes/settings between projects + +## Future + +I consider this plugin mostly done, but still use it regularly, so if Godot has any breaking changes that affect it, those will probably be addressed. It also might get the occasional new feature. Some current thoughts are: + +- [ ] Theme integration of some sort +- [ ] The ability to mass convert one color in the project to another + +## Notes + +* Tested on Godot 4.2. If you'd like to run it on an earlier version of Godot 4, [this is a link](https://github.com/RancidMilkGames/GodotPaletteTools/tree/47c09b8d6e43a0acc0380a1344a4b2282f95d49b) to the last commit before the 4.2 changes. +* Custom Color picker won't work when editing resources. I'm not sure if this is possible to implement in Godot's current state. diff --git a/godot/addons/PaletteTools/Resources/section_label_settings.tres b/godot/addons/PaletteTools/Resources/section_label_settings.tres new file mode 100644 index 0000000..53e96af --- /dev/null +++ b/godot/addons/PaletteTools/Resources/section_label_settings.tres @@ -0,0 +1,9 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://d21bg0y0svxi3"] + +[resource] +font_size = 24 +outline_size = 4 +outline_color = Color(0, 0, 0, 1) +shadow_size = 6 +shadow_color = Color(0.714494, 0.571648, 0.404247, 1) +shadow_offset = Vector2(0, 0) diff --git a/godot/addons/PaletteTools/Resources/section_label_settings_contrast.tres b/godot/addons/PaletteTools/Resources/section_label_settings_contrast.tres new file mode 100644 index 0000000..012fa67 --- /dev/null +++ b/godot/addons/PaletteTools/Resources/section_label_settings_contrast.tres @@ -0,0 +1,8 @@ +[gd_resource type="LabelSettings" format=3 uid="uid://k60qt3dd7ov2"] + +[resource] +font_size = 50 +font_color = Color(0.545098, 0.333333, 0.501961, 1) +outline_color = Color(0.772549, 0.8, 0.721569, 1) +shadow_color = Color(0.760784, 0.552941, 0.458824, 1) +shadow_offset = Vector2(0, 0) diff --git a/godot/addons/PaletteTools/Scenes/browse_palette_preview.tscn b/godot/addons/PaletteTools/Scenes/browse_palette_preview.tscn new file mode 100644 index 0000000..1100e00 --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/browse_palette_preview.tscn @@ -0,0 +1,83 @@ +[gd_scene load_steps=5 format=3 uid="uid://xj8jb4l74mf"] + +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/browse_palette_preview.gd" id="1_j460a"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g4ruc"] +content_margin_left = 25.0 +content_margin_top = 25.0 +content_margin_right = 25.0 +content_margin_bottom = 25.0 +bg_color = Color(0.435294, 0.403922, 0.462745, 1) +border_width_left = 10 +border_width_top = 10 +border_width_right = 10 +border_width_bottom = 10 +border_color = Color(0, 0, 0, 1) +border_blend = true +corner_radius_top_left = 25 +corner_radius_top_right = 25 +corner_radius_bottom_right = 25 +corner_radius_bottom_left = 25 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qogr7"] +content_margin_left = 7.0 +content_margin_top = 4.0 +content_margin_right = 7.0 +content_margin_bottom = 4.0 +bg_color = Color(0.713726, 0.572549, 0.403922, 1) +border_width_left = 5 +border_width_top = 5 +border_width_right = 5 +border_width_bottom = 5 +border_color = Color(0.00820202, 0.00820203, 0.00820202, 1) +border_blend = true +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="LabelSettings" id="LabelSettings_nkmub"] +font_size = 24 +font_color = Color(0, 0, 0, 1) +outline_size = 4 +outline_color = Color(0.552941, 0.384314, 0.407843, 1) + +[node name="PanelContainer" type="PanelContainer" node_paths=PackedStringArray("info_label", "color_container")] +offset_top = 54.0 +offset_right = 1152.0 +offset_bottom = 54.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_g4ruc") +script = ExtResource("1_j460a") +info_label = NodePath("BrowsePalettePreview/HBoxContainer/Label") +color_container = NodePath("BrowsePalettePreview/MarginContainer/HFlowContainer") + +[node name="BrowsePalettePreview" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 10 + +[node name="HBoxContainer" type="HBoxContainer" parent="BrowsePalettePreview"] +layout_mode = 2 + +[node name="PaletteSelect" type="Button" parent="BrowsePalettePreview/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 0 +mouse_default_cursor_shape = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_qogr7") +text = "Import" + +[node name="Label" type="Label" parent="BrowsePalettePreview/HBoxContainer"] +layout_mode = 2 +text = "Palette name by user" +label_settings = SubResource("LabelSettings_nkmub") + +[node name="MarginContainer" type="MarginContainer" parent="BrowsePalettePreview"] +layout_mode = 2 + +[node name="HFlowContainer" type="HFlowContainer" parent="BrowsePalettePreview/MarginContainer"] +layout_mode = 2 +theme_override_constants/h_separation = 0 +theme_override_constants/v_separation = 0 + +[connection signal="pressed" from="BrowsePalettePreview/HBoxContainer/PaletteSelect" to="." method="_on_palette_select_pressed"] diff --git a/godot/addons/PaletteTools/Scenes/color_sample.tscn b/godot/addons/PaletteTools/Scenes/color_sample.tscn new file mode 100644 index 0000000..2f94e24 --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/color_sample.tscn @@ -0,0 +1,70 @@ +[gd_scene load_steps=4 format=3 uid="uid://157npdwcov77"] + +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/color_sample.gd" id="1_8rxjv"] +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Blank.png" id="1_jr35n"] + +[sub_resource type="LabelSettings" id="LabelSettings_s1xg4"] +font_size = 20 +font_color = Color(0.113725, 0.113725, 0.113725, 1) + +[node name="ColorSample" type="MarginContainer" node_paths=PackedStringArray("color_picker_button", "remove_button", "remove_button_text")] +custom_minimum_size = Vector2(50, 50) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -12.5 +offset_top = -12.5 +offset_right = 12.5 +offset_bottom = 12.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 +script = ExtResource("1_8rxjv") +color_picker_button = NodePath("Color") +remove_button = NodePath("Color/Remove") +remove_button_text = NodePath("Color/Remove/Label") + +[node name="Color" type="ColorPickerButton" parent="."] +custom_minimum_size = Vector2(50, 50) +layout_mode = 2 +mouse_default_cursor_shape = 2 + +[node name="Remove" type="TextureButton" parent="Color"] +self_modulate = Color(0.980392, 0.513726, 0.47451, 1) +custom_minimum_size = Vector2(10, 10) +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -14.0 +offset_top = 4.0 +offset_right = -4.0 +offset_bottom = 14.0 +grow_horizontal = 0 +mouse_default_cursor_shape = 2 +texture_normal = ExtResource("1_jr35n") +ignore_texture_size = true +stretch_mode = 4 + +[node name="Label" type="Label" parent="Color/Remove"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -13.0 +offset_right = 20.0 +offset_bottom = 13.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "X" +label_settings = SubResource("LabelSettings_s1xg4") +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/godot/addons/PaletteTools/Scenes/custom_palette.tscn b/godot/addons/PaletteTools/Scenes/custom_palette.tscn new file mode 100644 index 0000000..564a43d --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/custom_palette.tscn @@ -0,0 +1,20 @@ +[gd_scene format=3 uid="uid://c41c3pd6clxuu"] + +[node name="CustomPalette" type="ColorRect"] +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 +color = Color(1, 1, 1, 0.996078) + +[node name="Button" type="Button" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +flat = true diff --git a/godot/addons/PaletteTools/Scenes/custom_picker.tscn b/godot/addons/PaletteTools/Scenes/custom_picker.tscn new file mode 100644 index 0000000..7e84871 --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/custom_picker.tscn @@ -0,0 +1,140 @@ +[gd_scene load_steps=10 format=3 uid="uid://te84lilqmwp0"] + +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/custom_picker.gd" id="1_oxy04"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_igykn"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.745098, 0.584314, 0.360784, 1) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 +corner_detail = 6 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_etl6w"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.603922, 0.309804, 0.313726, 1) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 +corner_detail = 6 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jt85f"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.772549, 0.8, 0.721569, 1) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 +corner_detail = 6 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1lt0k"] +content_margin_left = 4.0 +content_margin_top = 0.0 +content_margin_right = 4.0 +content_margin_bottom = 0.0 +bg_color = Color(0.407843, 0.67451, 0.662745, 1) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 +corner_detail = 6 + +[sub_resource type="Theme" id="Theme_8u2vs"] +VScrollBar/styles/grabber = SubResource("StyleBoxFlat_igykn") +VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_etl6w") +VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_jt85f") +VScrollBar/styles/scroll = SubResource("StyleBoxFlat_1lt0k") + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jcc3n"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t77tn"] +bg_color = Color(0.839216, 0.839216, 0.839216, 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_ykbik"] +bg_color = Color(0.839216, 0.839216, 0.839216, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[node name="CustomPicker" type="PopupPanel" node_paths=PackedStringArray("color_picker", "saved_palettes", "apply_palette_button")] +disable_3d = true +title = "Palette Tools Color-Picker" +size = Vector2i(1280, 2700) +visible = true +min_size = Vector2i(640, 1350) +theme = SubResource("Theme_8u2vs") +script = ExtResource("1_oxy04") +color_picker = NodePath("ScrollContainer/VBoxContainer/ColorPicker") +saved_palettes = NodePath("ScrollContainer/VBoxContainer/SavedPalettes") +apply_palette_button = NodePath("ScrollContainer/VBoxContainer/HBoxContainer/Button") + +[node name="ScrollContainer" type="ScrollContainer" parent="."] +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 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"] +custom_minimum_size = Vector2(300, 600) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +alignment = 1 + +[node name="ColorPicker" type="ColorPicker" parent="ScrollContainer/VBoxContainer"] +layout_mode = 2 +deferred_mode = true +picker_shape = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Saved Palettes: +" + +[node name="Spacer" type="Control" parent="ScrollContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Button" type="Button" parent="ScrollContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +tooltip_text = "Applies palette to Swatches. Alternatively you can double click a palette or press enter with a palette highlighted" +text = "Apply to Swatches" + +[node name="SavedPalettes" type="ItemList" parent="ScrollContainer/VBoxContainer"] +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_horizontal = 4 +tooltip_text = "Double click on palette or select one and hit enter to apply it." +theme_override_colors/font_selected_color = Color(0.0862745, 0.0862745, 0.0862745, 1) +theme_override_styles/focus = SubResource("StyleBoxEmpty_jcc3n") +theme_override_styles/selected = SubResource("StyleBoxFlat_t77tn") +theme_override_styles/selected_focus = SubResource("StyleBoxFlat_ykbik") +auto_height = true +item_count = 1 +same_column_width = true +item_0/text = "No Palettes" diff --git a/godot/addons/PaletteTools/Scenes/palette_tools.tscn b/godot/addons/PaletteTools/Scenes/palette_tools.tscn new file mode 100644 index 0000000..bca9890 --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/palette_tools.tscn @@ -0,0 +1,647 @@ +[gd_scene load_steps=42 format=3 uid="uid://3m5qn5hayqy3"] + +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/palette_tool.gd" id="1_coeap"] +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/get_from_url.gd" id="2_4p84m"] +[ext_resource type="PackedScene" uid="uid://x40sgfxkrskd" path="res://addons/PaletteTools/Scenes/plus_box.tscn" id="2_85d0j"] +[ext_resource type="PackedScene" uid="uid://157npdwcov77" path="res://addons/PaletteTools/Scenes/color_sample.tscn" id="2_fn217"] +[ext_resource type="Shader" path="res://addons/PaletteTools/Shaders/rainbow_shader.gdshader" id="3_gaxkd"] +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Plus.png" id="3_m16i6"] +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/spyglass.png" id="4_7wo4d"] +[ext_resource type="LabelSettings" uid="uid://k60qt3dd7ov2" path="res://addons/PaletteTools/Resources/section_label_settings_contrast.tres" id="4_qjnhp"] +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/checked_4k.png" id="6_iloov"] +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/browse_popup.gd" id="7_ob3x8"] +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/unchecked_4k.png" id="7_rs6oc"] +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/loading_palettes_screen.gd" id="9_xvc3k"] +[ext_resource type="Script" path="res://addons/PaletteTools/Scripts/alert_popup_panel.gd" id="11_hklnm"] +[ext_resource type="PackedScene" uid="uid://xj8jb4l74mf" path="res://addons/PaletteTools/Scenes/browse_palette_preview.tscn" id="13_a5syi"] + +[sub_resource type="Theme" id="Theme_igl82"] +MarginContainer/constants/margin_bottom = 3 +MarginContainer/constants/margin_left = 3 +MarginContainer/constants/margin_right = 3 +MarginContainer/constants/margin_top = 3 +TextEdit/font_sizes/font_size = 16 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7qvmi"] +bg_color = Color(0.713726, 0.572549, 0.403922, 1) + +[sub_resource type="Theme" id="Theme_8bgoc"] +PanelContainer/styles/panel = SubResource("StyleBoxFlat_7qvmi") + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_g1sn2"] +shader = ExtResource("3_gaxkd") +shader_parameter/strength = 0.25 +shader_parameter/speed = 0.25 +shader_parameter/angle = 0.0 + +[sub_resource type="LabelSettings" id="LabelSettings_yeikm"] +font_size = 80 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_u67lc"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1qaos"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kl4yb"] +bg_color = Color(0.839216, 0.839216, 0.839216, 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_2u8j5"] +bg_color = Color(0.839216, 0.839216, 0.839216, 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_g30ga"] +bg_color = Color(0.929412, 0.882353, 0.619608, 0.823529) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +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_c6ft6"] +bg_color = Color(0.92549, 0.85098, 0.596078, 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_b113t"] +bg_color = Color(0.415686, 0.415686, 0.415686, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_kk1ek"] + +[sub_resource type="Theme" id="Theme_t5g4x"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4b8x1"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.890196, 0.890196, 0.890196, 0.6) +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_inmej"] +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) +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_u8dak"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.0235294, 0.0235294, 0.0235294, 0.6) +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="LabelSettings" id="LabelSettings_rgr51"] +font_size = 40 + +[sub_resource type="LabelSettings" id="LabelSettings_jnb0d"] +font_size = 40 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6d60a"] +content_margin_left = 5.0 +content_margin_right = 5.0 +bg_color = Color(0.890196, 0.337255, 0.341176, 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_y2upq"] +bg_color = Color(0.768627, 0.764706, 0.509804, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.121569, 0.121569, 0.121569, 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_7ta0s"] +bg_color = Color(0.25098, 0.25098, 0.25098, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.933333, 0.772549, 0.509804, 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_ceb4a"] +bg_color = Color(0.333333, 0.333333, 0.333333, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="LabelSettings" id="LabelSettings_ww1rl"] +font_size = 40 +font_color = Color(0.0823529, 0.0823529, 0.0823529, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tdaa4"] +bg_color = Color(0.172549, 0.172549, 0.172549, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="LabelSettings" id="LabelSettings_0flab"] +font_size = 32 + +[sub_resource type="LabelSettings" id="LabelSettings_26hsa"] +font_size = 100 +font_color = Color(0.00820202, 0.00820203, 0.00820202, 1) + +[node name="Palette Tools" type="ScrollContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 + +[node name="Colors" type="PanelContainer" parent="." node_paths=PackedStringArray("http", "url", "color_preview", "editor_swatch_save", "restart_editor", "alert", "p_name_text", "p_author_text", "saved_palettes", "clear_preview_button", "col_pick_popup", "browse_popup_panel", "custom_picker_check_box")] +layout_mode = 2 +size_flags_horizontal = 3 +theme = SubResource("Theme_igl82") +script = ExtResource("1_coeap") +http = NodePath("HTTPRequest") +url = NodePath("Palette/Search/LineEdit") +color_preview = NodePath("Palette/ColorPreview") +editor_swatch_save = NodePath("Palette/Container/VBoxContainer/SaveToEditor") +restart_editor = NodePath("Palette/Container/VBoxContainer/RestartEditor") +alert = NodePath("AlertPopup") +p_name_text = NodePath("Palette/Info/PaletteName/LineEdit") +p_author_text = NodePath("Palette/Info/Author/LineEdit") +saved_palettes = NodePath("Palette/SavedPalettes") +clear_preview_button = NodePath("Palette/HBoxContainer/Clear") +col_pick_popup = NodePath("ColorPickerPopup") +browse_popup_panel = NodePath("BrowsePopup") +add_color_scene = ExtResource("2_85d0j") +color_sample = ExtResource("2_fn217") +custom_picker_check_box = NodePath("Palette/CustomPicker/CustomPickerCheckBox") + +[node name="HTTPRequest" type="HTTPRequest" parent="Colors" node_paths=PackedStringArray("colors")] +download_file = "res://addons/PaletteTools/temp_palette.txt" +script = ExtResource("2_4p84m") +colors = NodePath("..") + +[node name="Palette" type="VBoxContainer" parent="Colors"] +layout_mode = 2 +theme = SubResource("Theme_8bgoc") +theme_override_constants/separation = 5 +alignment = 1 + +[node name="Title" type="Label" parent="Colors/Palette"] +material = SubResource("ShaderMaterial_g1sn2") +layout_mode = 2 +size_flags_horizontal = 4 +text = "Palette Tools" +label_settings = SubResource("LabelSettings_yeikm") +horizontal_alignment = 1 + +[node name="CustomPicker" type="HBoxContainer" parent="Colors/Palette"] +layout_mode = 2 +alignment = 1 + +[node name="CustomPickerCheckBox" type="CheckBox" parent="Colors/Palette/CustomPicker"] +layout_mode = 2 +tooltip_text = "Toggle an extended color picker that can switch palettes" +theme_override_font_sizes/font_size = 30 +theme_override_icons/checked = ExtResource("6_iloov") +theme_override_icons/unchecked = ExtResource("7_rs6oc") +icon_alignment = 1 +expand_icon = true + +[node name="RichTextLabel" type="RichTextLabel" parent="Colors/Palette/CustomPicker"] +layout_mode = 2 +size_flags_vertical = 4 +theme_override_font_sizes/normal_font_size = 40 +theme_override_styles/normal = SubResource("StyleBoxEmpty_u67lc") +bbcode_enabled = true +text = "Custom Palette Picker [color=9a4f50](beta)[/color]" +fit_content = true +scroll_active = false +autowrap_mode = 0 + +[node name="PaletteListTitle" type="Label" parent="Colors/Palette"] +layout_mode = 2 +text = "--- Saved Palettes: ---" +label_settings = ExtResource("4_qjnhp") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SavedPalettes" type="ItemList" parent="Colors/Palette"] +layout_mode = 2 +theme_override_colors/font_selected_color = Color(0.0862745, 0.0862745, 0.0862745, 1) +theme_override_font_sizes/font_size = 30 +theme_override_styles/focus = SubResource("StyleBoxEmpty_1qaos") +theme_override_styles/selected = SubResource("StyleBoxFlat_kl4yb") +theme_override_styles/selected_focus = SubResource("StyleBoxFlat_2u8j5") +auto_height = true +item_count = 1 +same_column_width = true +item_0/text = "No Palettes" + +[node name="IO" type="HBoxContainer" parent="Colors/Palette"] +layout_mode = 2 + +[node name="New" type="TextureButton" parent="Colors/Palette/IO"] +visible = false +self_modulate = Color(0.720467, 0.720468, 0.720467, 1) +texture_filter = 1 +custom_minimum_size = Vector2(80, 0) +layout_mode = 2 +tooltip_text = "Make a new palette. Erases current preview" +mouse_default_cursor_shape = 2 +texture_normal = ExtResource("3_m16i6") +texture_pressed = ExtResource("3_m16i6") +texture_hover = ExtResource("3_m16i6") +ignore_texture_size = true +stretch_mode = 5 + +[node name="Load" type="Button" parent="Colors/Palette/IO"] +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Load selected Saved Palette" +mouse_default_cursor_shape = 2 +theme_override_font_sizes/font_size = 30 +text = "Load" + +[node name="Delete" type="Button" parent="Colors/Palette/IO"] +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Delete selected Saved Palette" +mouse_default_cursor_shape = 2 +theme_override_font_sizes/font_size = 30 +text = "Delete" + +[node name="SearchTitle" type="Label" parent="Colors/Palette"] +layout_mode = 2 +text = "--- Get from Lospec: ---" +label_settings = ExtResource("4_qjnhp") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Search" type="HBoxContainer" parent="Colors/Palette"] +layout_mode = 2 + +[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Search"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "Lospec Palette URL" +expand_to_text_length = true +caret_blink = true +caret_blink_interval = 0.5 + +[node name="Search" type="Button" parent="Colors/Palette/Search"] +texture_filter = 1 +custom_minimum_size = Vector2(80, 4) +layout_mode = 2 +mouse_default_cursor_shape = 2 +theme_override_font_sizes/font_size = 40 +theme_override_styles/normal = SubResource("StyleBoxFlat_g30ga") +theme_override_styles/hover = SubResource("StyleBoxFlat_c6ft6") +theme_override_styles/pressed = SubResource("StyleBoxFlat_b113t") +theme_override_styles/focus = SubResource("StyleBoxEmpty_kk1ek") +icon = ExtResource("4_7wo4d") +icon_alignment = 1 +expand_icon = true + +[node name="BrowsePalettesButton" type="Button" parent="Colors/Palette"] +layout_mode = 2 +tooltip_text = "Browse the most popular Lospec palettes" +mouse_default_cursor_shape = 2 +theme = SubResource("Theme_t5g4x") +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_pressed_color = Color(0.91664, 0.91664, 0.91664, 1) +theme_override_colors/font_hover_color = Color(0.813258, 0.813258, 0.813258, 1) +theme_override_font_sizes/font_size = 30 +theme_override_styles/normal = SubResource("StyleBoxFlat_4b8x1") +theme_override_styles/hover = SubResource("StyleBoxFlat_inmej") +theme_override_styles/pressed = SubResource("StyleBoxFlat_u8dak") +text = "Browse Palettes" + +[node name="PreviewTitle" type="Label" parent="Colors/Palette"] +layout_mode = 2 +text = "--- Palette Preview: ---" +label_settings = ExtResource("4_qjnhp") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Info" type="VBoxContainer" parent="Colors/Palette"] +layout_mode = 2 +alignment = 1 + +[node name="PaletteName" type="HBoxContainer" parent="Colors/Palette/Info"] +layout_mode = 2 + +[node name="Label" type="Label" parent="Colors/Palette/Info/PaletteName"] +layout_mode = 2 +text = "Palette Name:" +label_settings = SubResource("LabelSettings_rgr51") + +[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Info/PaletteName"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "Name" +caret_blink = true +caret_blink_interval = 0.5 + +[node name="Author" type="HBoxContainer" parent="Colors/Palette/Info"] +layout_mode = 2 + +[node name="Label" type="Label" parent="Colors/Palette/Info/Author"] +layout_mode = 2 +text = "Author:" +label_settings = SubResource("LabelSettings_jnb0d") + +[node name="LineEdit" type="LineEdit" parent="Colors/Palette/Info/Author"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "Name" +caret_blink = true +caret_blink_interval = 0.5 + +[node name="ColorPreview" type="HFlowContainer" parent="Colors/Palette"] +layout_mode = 2 +size_flags_vertical = 3 +alignment = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="Colors/Palette"] +layout_mode = 2 +theme_override_constants/separation = 10 +alignment = 1 + +[node name="Save" type="Button" parent="Colors/Palette/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +tooltip_text = "Save the current preview to Saved Palettes" +mouse_default_cursor_shape = 2 +theme_override_font_sizes/font_size = 30 +text = "Save Palette" + +[node name="Clear" type="Button" parent="Colors/Palette/HBoxContainer"] +visible = false +layout_mode = 2 +tooltip_text = "Save the current preview to Saved Palettes" +mouse_default_cursor_shape = 2 +theme_override_colors/font_color = Color(0.125911, 0.125911, 0.125911, 1) +theme_override_font_sizes/font_size = 30 +theme_override_styles/normal = SubResource("StyleBoxFlat_6d60a") +text = "Clear Preview" + +[node name="Container" type="MarginContainer" parent="Colors/Palette"] +layout_mode = 2 + +[node name="ColorRect" type="Panel" parent="Colors/Palette/Container"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_y2upq") + +[node name="VBoxContainer" type="VBoxContainer" parent="Colors/Palette/Container"] +layout_mode = 2 + +[node name="SaveToEditor" type="VBoxContainer" parent="Colors/Palette/Container/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 4 +alignment = 1 + +[node name="Spacer" type="Control" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"] +layout_mode = 2 + +[node name="SaveToEditorButton" type="Button" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"] +layout_mode = 2 +size_flags_horizontal = 4 +tooltip_text = "Replaces the current editor swatches with current palette" +mouse_default_cursor_shape = 2 +theme_override_colors/font_color = Color(0.933333, 0.772549, 0.509804, 1) +theme_override_font_sizes/font_size = 30 +theme_override_styles/normal = SubResource("StyleBoxFlat_7ta0s") +theme_override_styles/hover = SubResource("StyleBoxFlat_ceb4a") +text = "*Save to Editor's Swatches" + +[node name="Label" type="Label" parent="Colors/Palette/Container/VBoxContainer/SaveToEditor"] +layout_mode = 2 +text = "*Note: Editor must be restarted +for changes to take effect" +label_settings = SubResource("LabelSettings_ww1rl") +horizontal_alignment = 1 + +[node name="RestartEditor" type="VBoxContainer" parent="Colors/Palette/Container/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 4 +alignment = 1 + +[node name="RestartEditorButton" type="Button" parent="Colors/Palette/Container/VBoxContainer/RestartEditor"] +layout_mode = 2 +size_flags_horizontal = 4 +tooltip_text = "Replaces the current editor swatches with current palette" +theme_override_colors/font_color = Color(1, 0.435294, 0.364706, 1) +theme_override_font_sizes/font_size = 30 +theme_override_styles/hover = SubResource("StyleBoxFlat_tdaa4") +text = "*Restart To finalize changes?" + +[node name="Spacer" type="Control" parent="Colors/Palette/Container/VBoxContainer/RestartEditor"] +layout_mode = 2 + +[node name="AlertPopup" type="PopupPanel" parent="Colors" node_paths=PackedStringArray("alert_text_label")] +disable_3d = true +title = "Alert" +initial_position = 1 +size = Vector2i(500, 300) +script = ExtResource("11_hklnm") +alert_text_label = NodePath("Control/Label") + +[node name="Control" type="Control" parent="Colors/AlertPopup"] +layout_mode = 3 +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 + +[node name="Label" type="Label" parent="Colors/AlertPopup/Control"] +custom_minimum_size = Vector2(400, 200) +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -0.5 +offset_top = -13.0 +offset_right = 0.5 +offset_bottom = 13.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Alert" +label_settings = SubResource("LabelSettings_0flab") +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 2 + +[node name="CloseButton" type="Button" parent="Colors/AlertPopup/Control"] +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -25.0 +offset_top = -42.0 +offset_right = 25.0 +offset_bottom = -11.0 +grow_horizontal = 2 +grow_vertical = 0 +size_flags_horizontal = 6 +size_flags_vertical = 6 +mouse_default_cursor_shape = 2 +text = "Close" + +[node name="ColorPickerPopup" type="PopupPanel" parent="Colors"] +disable_3d = true +sdf_oversize = 0 +initial_position = 1 +size = Vector2i(306, 514) +unresizable = false + +[node name="ColorPicker" type="ColorPicker" parent="Colors/ColorPickerPopup"] +offset_left = 4.0 +offset_top = 4.0 +offset_right = 302.0 +offset_bottom = 510.0 +picker_shape = 3 +can_add_swatches = false +presets_visible = false + +[node name="BrowsePopup" type="Window" parent="Colors" node_paths=PackedStringArray("colors", "browse_http", "preview_container", "alert_popup", "loading_screen")] +disable_3d = true +title = "Browse Lospec Palettes" +initial_position = 2 +size = Vector2i(1000, 600) +visible = false +wrap_controls = true +script = ExtResource("7_ob3x8") +colors = NodePath("..") +browse_http = NodePath("HTTPRequest") +browse_preview_scene = ExtResource("13_a5syi") +preview_container = NodePath("MarginContainer/VBoxContainer/ScrollContainer/VBoxContainer") +alert_popup = NodePath("../AlertPopup") +loading_screen = NodePath("MarginContainer/Loading") + +[node name="MarginContainer" type="MarginContainer" parent="Colors/BrowsePopup"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 25 +theme_override_constants/margin_top = 25 +theme_override_constants/margin_right = 25 +theme_override_constants/margin_bottom = 25 + +[node name="VBoxContainer" type="VBoxContainer" parent="Colors/BrowsePopup/MarginContainer"] +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="LoadMore" type="Button" parent="Colors/BrowsePopup/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Load More" + +[node name="Loading" type="ColorRect" parent="Colors/BrowsePopup/MarginContainer" node_paths=PackedStringArray("loading_text")] +layout_mode = 2 +color = Color(0.878431, 0.878431, 0.878431, 1) +script = ExtResource("9_xvc3k") +loading_text = NodePath("Label") + +[node name="Label" type="Label" parent="Colors/BrowsePopup/MarginContainer/Loading"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -191.0 +offset_top = -68.5 +offset_right = 191.0 +offset_bottom = 68.5 +grow_horizontal = 2 +grow_vertical = 2 +text = "Loading" +label_settings = SubResource("LabelSettings_26hsa") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HTTPRequest" type="HTTPRequest" parent="Colors/BrowsePopup"] + +[connection signal="request_completed" from="Colors/HTTPRequest" to="Colors/HTTPRequest" method="_on_request_completed"] +[connection signal="toggled" from="Colors/Palette/CustomPicker/CustomPickerCheckBox" to="Colors" method="_on_custom_picker_check_box_toggled"] +[connection signal="item_activated" from="Colors/Palette/SavedPalettes" to="Colors" method="_on_saved_palettes_item_activated"] +[connection signal="pressed" from="Colors/Palette/IO/New" to="Colors" method="_on_new_palette_pressed"] +[connection signal="pressed" from="Colors/Palette/IO/Load" to="Colors" method="_on_load_palette_pressed"] +[connection signal="pressed" from="Colors/Palette/IO/Delete" to="Colors" method="_on_delete_palette_pressed"] +[connection signal="text_submitted" from="Colors/Palette/Search/LineEdit" to="Colors" method="_on_line_edit_text_submitted"] +[connection signal="pressed" from="Colors/Palette/Search/Search" to="Colors" method="_on_search_pressed"] +[connection signal="pressed" from="Colors/Palette/BrowsePalettesButton" to="Colors" method="_on_browse_palettes_button_pressed"] +[connection signal="pressed" from="Colors/Palette/HBoxContainer/Save" to="Colors" method="_on_save_palette_pressed"] +[connection signal="pressed" from="Colors/Palette/HBoxContainer/Clear" to="Colors" method="_on_clear_pressed"] +[connection signal="pressed" from="Colors/Palette/Container/VBoxContainer/SaveToEditor/SaveToEditorButton" to="Colors" method="_on_save_to_editor_button_pressed"] +[connection signal="pressed" from="Colors/Palette/Container/VBoxContainer/RestartEditor/RestartEditorButton" to="Colors" method="_on_restart_editor_pressed"] +[connection signal="close_requested" from="Colors/AlertPopup" to="Colors/AlertPopup" method="_on_close_requested"] +[connection signal="pressed" from="Colors/AlertPopup/Control/CloseButton" to="Colors/AlertPopup" method="_on_close_button_pressed"] +[connection signal="color_changed" from="Colors/ColorPickerPopup/ColorPicker" to="Colors" method="_on_color_picker_color_changed"] +[connection signal="close_requested" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_close_requested"] +[connection signal="focus_exited" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_focus_exited"] +[connection signal="size_changed" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_size_changed"] +[connection signal="visibility_changed" from="Colors/BrowsePopup" to="Colors/BrowsePopup" method="_on_visibility_changed"] +[connection signal="pressed" from="Colors/BrowsePopup/MarginContainer/VBoxContainer/LoadMore" to="Colors/BrowsePopup" method="_on_load_more_pressed"] +[connection signal="visibility_changed" from="Colors/BrowsePopup/MarginContainer/Loading" to="Colors/BrowsePopup/MarginContainer/Loading" method="_on_visibility_changed"] +[connection signal="request_completed" from="Colors/BrowsePopup/HTTPRequest" to="Colors/BrowsePopup" method="_on_http_request_completed"] diff --git a/godot/addons/PaletteTools/Scenes/plus_box.tscn b/godot/addons/PaletteTools/Scenes/plus_box.tscn new file mode 100644 index 0000000..016d841 --- /dev/null +++ b/godot/addons/PaletteTools/Scenes/plus_box.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=2 format=3 uid="uid://x40sgfxkrskd"] + +[ext_resource type="Texture2D" path="res://addons/PaletteTools/Images/Plus.png" id="1_o5f0x"] + +[node name="ColorSample" type="MarginContainer"] +custom_minimum_size = Vector2(50, 50) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -12.5 +offset_top = -12.5 +offset_right = 12.5 +offset_bottom = 12.5 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="Color" type="TextureButton" parent="."] +texture_filter = 1 +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +tooltip_text = "Add color to palette" +mouse_default_cursor_shape = 2 +texture_normal = ExtResource("1_o5f0x") +ignore_texture_size = true +stretch_mode = 5 diff --git a/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd b/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd new file mode 100644 index 0000000..28884f7 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd @@ -0,0 +1,17 @@ +@tool +extends PopupPanel + +@export var alert_text_label: Label + + +func alert(message: String) -> void: + alert_text_label.text = message + show() + + +func _on_close_button_pressed() -> void: + hide() + + +func _on_close_requested() -> void: + hide() diff --git a/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd.uid b/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd.uid new file mode 100644 index 0000000..cead3bf --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/alert_popup_panel.gd.uid @@ -0,0 +1 @@ +uid://cd6rspiirpfy5 diff --git a/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd b/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd new file mode 100644 index 0000000..fe7a9bd --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd @@ -0,0 +1,14 @@ +@tool +extends Control + +const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd") + +@export var info_label: Label +@export var color_container: Control + +var colors: Palette +var palette_obj: Dictionary + + +func _on_palette_select_pressed() -> void: + colors.import_palette_from_browse(palette_obj) diff --git a/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd.uid b/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd.uid new file mode 100644 index 0000000..1f70437 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/browse_palette_preview.gd.uid @@ -0,0 +1 @@ +uid://pagg2ueykteh diff --git a/godot/addons/PaletteTools/Scripts/browse_popup.gd b/godot/addons/PaletteTools/Scripts/browse_popup.gd new file mode 100644 index 0000000..9465453 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/browse_popup.gd @@ -0,0 +1,94 @@ +@tool +extends Window + +const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd") +const Alert := preload("res://addons/PaletteTools/Scripts/alert_popup_panel.gd") +const BrowsePreview := preload("res://addons/PaletteTools/Scripts/browse_palette_preview.gd") + +@export var colors: Palette +@export var browse_http: HTTPRequest +@export var browse_preview_scene: PackedScene +@export var preview_container: Control +@export var alert_popup: Alert +@export var loading_screen: Control + +var current_page: int = 0 +var close_delay: int = 4 + + +func get_palette_list() -> void: + browse_http.request( + "https://lospec.com/palette-list/load?colorNumberFilterType=any&colorNumber=8&page=" + + str(current_page) + + "&tag=&sortingType=downloads" + ) + current_page += 1 + + +func display_preview(palette_obj: Dictionary) -> void: + var prev: BrowsePreview = browse_preview_scene.instantiate() + prev.colors = colors + prev.palette_obj = palette_obj + prev.info_label.text = palette_obj.name + " by: " + palette_obj.author + preview_container.add_child(prev) + for col in palette_obj.colors: + var new_color := ColorRect.new() + new_color.custom_minimum_size = Vector2(75, 75) + new_color.color = Color.from_string(col, Color.WHITE) + prev.color_container.add_child(new_color) + + +func _on_close_button_pressed() -> void: + visible = false + + +func _on_http_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: + var html_text := body.get_string_from_utf8() + var json := JSON.new() + var err := json.parse(html_text) + if err != OK: + alert_popup.alert("Error getting palettes") + return + + loading_screen.visible = false + for pal in json.data.palettes: + var user := "Lospec" + var slug := "" + if pal.has("user"): + user = pal.user.name + if pal.has("slug"): + slug = pal.slug + var palette_obj := { + "name": pal.title, + "author": user, + "colors": pal.colors, + "slug": slug + } + + display_preview(palette_obj) + + +func _on_load_more_pressed() -> void: + get_palette_list() + + +func _on_visibility_changed() -> void: + if visible and current_page == 0: + get_palette_list() + + +func _on_close_requested() -> void: + hide() + + +func _on_focus_exited() -> void: + while close_delay > 0: + close_delay -= 1 + await get_tree().create_timer(1).timeout + if not has_focus(): + hide() + close_delay = 3 + + +func _on_size_changed() -> void: + close_delay = 3 diff --git a/godot/addons/PaletteTools/Scripts/browse_popup.gd.uid b/godot/addons/PaletteTools/Scripts/browse_popup.gd.uid new file mode 100644 index 0000000..7ca0ca7 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/browse_popup.gd.uid @@ -0,0 +1 @@ +uid://cas0s5bfj0txi diff --git a/godot/addons/PaletteTools/Scripts/color_sample.gd b/godot/addons/PaletteTools/Scripts/color_sample.gd new file mode 100644 index 0000000..294b3e6 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/color_sample.gd @@ -0,0 +1,6 @@ +@tool +extends MarginContainer + +@export var color_picker_button: ColorPickerButton +@export var remove_button: TextureButton +@export var remove_button_text: Label diff --git a/godot/addons/PaletteTools/Scripts/color_sample.gd.uid b/godot/addons/PaletteTools/Scripts/color_sample.gd.uid new file mode 100644 index 0000000..bac769f --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/color_sample.gd.uid @@ -0,0 +1 @@ +uid://8epr28t2njss diff --git a/godot/addons/PaletteTools/Scripts/custom_picker.gd b/godot/addons/PaletteTools/Scripts/custom_picker.gd new file mode 100644 index 0000000..9abc924 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/custom_picker.gd @@ -0,0 +1,15 @@ +@tool +extends PopupPanel + +@export var color_picker: ColorPicker +@export var saved_palettes: ItemList +@export var apply_palette_button: Button + + +func _init() -> void: + hide() + min_size = Vector2i(320, 675) + size = Vector2i(320, 675) + if DisplayServer.screen_get_size().x > 2000: + min_size *= 2 + size *= 2 diff --git a/godot/addons/PaletteTools/Scripts/custom_picker.gd.uid b/godot/addons/PaletteTools/Scripts/custom_picker.gd.uid new file mode 100644 index 0000000..87e99d8 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/custom_picker.gd.uid @@ -0,0 +1 @@ +uid://b3jncwvxjir6f diff --git a/godot/addons/PaletteTools/Scripts/custom_property.gd b/godot/addons/PaletteTools/Scripts/custom_property.gd new file mode 100644 index 0000000..9a6b3c6 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/custom_property.gd @@ -0,0 +1,67 @@ +extends EditorProperty + +signal mouse_released + +const CustomPalette := preload("res://addons/PaletteTools/Scripts/custom_picker.gd") + +var property_control: Control +var updating := false +var custom_palette: CustomPalette + + +func _init(cust_palette: CustomPalette, obj: Object, named = null) -> void: + property_control = load("res://addons/PaletteTools/Scenes/custom_palette.tscn").instantiate() + custom_palette = cust_palette + if named and obj[named]: + property_control.color = obj[named] + else: + property_control.color = Color.BLACK + add_child(property_control) + add_focusable(property_control) + property_control.get_child(0).pressed.connect(_on_button_pressed) + resource_selected.connect(resource_prop) + + +func resource_prop(path: String, prop: Resource) -> void: + if prop.get(path): + var p := prop.get(path) + if p is Vector4: + property_control.color = Color(p.x, p.y, p.z, p.w) + else: + property_control.color = p + else: + property_control.color = Color.WHITE + + +func _on_button_pressed() -> void: + if not custom_palette: + push_warning( + 'Error in Palette Tools addon: If the "Palette Tools" addon was just activated, ' + + "please select a different node before trying to edit color properties of this one. " + + "This error happens becuase the inspector was loaded before the addon. If you didn't " + + "just activate the addon or start the Godot editor, you can try turning the addon " + + "on and off. If the problem persists, you may need to disable the addon. If this happens, " + + "please report any relevant info so a fix can be made. Thanks!" + ) + return + custom_palette.popup(Rect2(get_global_mouse_position() - Vector2(size.x, (size.y * 20)), size)) + custom_palette.color_picker.color = get_edited_object()[get_edited_property()] + custom_palette.popup_hide.connect(cust_palette_closed, CONNECT_ONE_SHOT) + custom_palette.color_picker.color_changed.connect(cust_palette_changed) + + +func cust_palette_changed(new_color: Color) -> void: + property_control.color = new_color + + +func cust_palette_closed() -> void: + custom_palette.color_picker.color_changed.disconnect(cust_palette_changed) + property_control.color = custom_palette.color_picker.color + emit_changed(get_edited_property(), property_control.color) + + +func _update_property() -> void: + if get_edited_object() and get_edited_object()[get_edited_property()]: + property_control.color = get_edited_object()[get_edited_property()] + else: + property_control.color = Color.BLACK diff --git a/godot/addons/PaletteTools/Scripts/custom_property.gd.uid b/godot/addons/PaletteTools/Scripts/custom_property.gd.uid new file mode 100644 index 0000000..c10c223 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/custom_property.gd.uid @@ -0,0 +1 @@ +uid://bnpe28125rr05 diff --git a/godot/addons/PaletteTools/Scripts/get_from_url.gd b/godot/addons/PaletteTools/Scripts/get_from_url.gd new file mode 100644 index 0000000..ae740e0 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/get_from_url.gd @@ -0,0 +1,29 @@ +@tool +extends HTTPRequest + +const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd") + +@export var colors: Palette + +var searching := false + + +func get_palette(url: String) -> void: + if searching: + return + searching = true + if url.ends_with("/"): + url = url.left(-1) + var error := request(url + ".json") + if error != OK: + push_error("An error occurred in the HTTP request.") + + +func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: + searching = false + var json := FileAccess.get_file_as_string(download_file) + var json_obj := JSON.parse_string(json) + + colors.preview_colors(json_obj.colors) + colors.p_name_text.text = json_obj.name + colors.p_author_text.text = json_obj.author diff --git a/godot/addons/PaletteTools/Scripts/get_from_url.gd.uid b/godot/addons/PaletteTools/Scripts/get_from_url.gd.uid new file mode 100644 index 0000000..b8b7dcb --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/get_from_url.gd.uid @@ -0,0 +1 @@ +uid://bm2n1k624d6y1 diff --git a/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd b/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd new file mode 100644 index 0000000..b869fc8 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd @@ -0,0 +1,17 @@ +@tool +extends ColorRect + +@export var loading_text: Label + + +func _ready() -> void: + visible = true + + +func _on_visibility_changed() -> void: + while visible: + await get_tree().create_timer(.7).timeout + if loading_text.text.ends_with("..."): + loading_text.text.replace("...", "") + else: + loading_text.text += "." diff --git a/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd.uid b/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd.uid new file mode 100644 index 0000000..033a856 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/loading_palettes_screen.gd.uid @@ -0,0 +1 @@ +uid://on3p67y6kr2h diff --git a/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd b/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd new file mode 100644 index 0000000..a64053f --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd @@ -0,0 +1,73 @@ +extends EditorInspectorPlugin + +const custom_palette_property := preload("res://addons/PaletteTools/Scripts/custom_property.gd") +const CustomColorPicker := preload("res://addons/PaletteTools/Scripts/custom_picker.gd") +const PalettePlugin := preload("res://addons/PaletteTools/palette_tools.gd") +#const custom_palette_picker: PackedScene #= pre + +var my_picker: CustomColorPicker +var my_plugin: PalettePlugin +var saved_palettes: ItemList +var editor_inspector: EditorInspector + + +func _init(plugin: PalettePlugin) -> void: + #custom_palette_picker = load("res://addons/PaletteTools/Scenes/custom_picker.tscn") + my_plugin = plugin + my_picker = load("res://addons/PaletteTools/Scenes/custom_picker.tscn").instantiate() as CustomColorPicker + my_plugin.dock.add_child(my_picker) + + my_plugin.colors.palette_list_updated.connect(load_palettes) + saved_palettes = my_picker.saved_palettes + saved_palettes.item_activated.connect(set_palette) + my_picker.apply_palette_button.pressed.connect(set_palette.bind(-1)) + + editor_inspector = my_plugin.get_editor_interface().get_inspector() + + +func initial_palette(): + var col_picker := my_picker.color_picker as ColorPicker + var editor_settings := my_plugin.get_editor_interface().get_editor_settings() + var preset_settings := editor_settings.get_project_metadata("color_picker", "presets", []) + if preset_settings: + for color: String in preset_settings: + col_picker.add_preset(Color(color)) + + +func _can_handle(object: Object) -> bool: + if object is Resource: + var prop := custom_palette_property.new(my_picker, object) + for property: Dictionary in object.get_property_list(): + if "color" in property.name: + prop.resource_selected.emit(property.name, object) + return false + return true + + +func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: PropertyHint, wide: bool) -> bool: + if type == TYPE_COLOR: + var prop := custom_palette_property.new(my_picker, object, name) + add_property_editor(name, prop) + return true + else: + return false + + +func load_palettes() -> void: + if my_plugin.colors.my_palettes.size() > 0: + saved_palettes.clear() + for p in my_plugin.colors.my_palettes: + saved_palettes.add_item(p.name) + else: + saved_palettes.clear() + saved_palettes.add_item("No Palettes") + + +func set_palette(palette_num: int) -> void: + if palette_num == -1 and saved_palettes.get_selected_items().size() > 0: + palette_num = saved_palettes.get_selected_items()[0] + var col_picker: ColorPicker = my_picker.color_picker + for color: Color in col_picker.get_presets(): + col_picker.erase_preset(color) + for color: Color in my_plugin.colors.my_palettes[palette_num].colors: + col_picker.add_preset(color) diff --git a/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd.uid b/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd.uid new file mode 100644 index 0000000..d63a652 --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/palette_inspector_script.gd.uid @@ -0,0 +1 @@ +uid://dhc2n37pvfjsx diff --git a/godot/addons/PaletteTools/Scripts/palette_tool.gd b/godot/addons/PaletteTools/Scripts/palette_tool.gd new file mode 100644 index 0000000..e130dcd --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/palette_tool.gd @@ -0,0 +1,310 @@ +@tool +extends Control + +const Alert := preload("res://addons/PaletteTools/Scripts/alert_popup_panel.gd") +const URLHTTP := preload("res://addons/PaletteTools/Scripts/get_from_url.gd") +const BrowsePalettes := preload("res://addons/PaletteTools/Scripts/browse_popup.gd") +const PalettePlugin := preload("res://addons/PaletteTools/palette_tools.gd") +const ColorSample := preload("res://addons/PaletteTools/Scripts/color_sample.gd") + +signal palette_list_updated + +@export var http: URLHTTP +@export var url: LineEdit +@export var color_preview: HFlowContainer +@export var editor_swatch_save: Control +@export var restart_editor: Control +@export var alert: Alert +@export var p_name_text: LineEdit +@export var p_author_text: LineEdit +@export var saved_palettes: ItemList +@export var clear_preview_button: Button +@export var col_pick_popup: PopupPanel +@export var browse_popup_panel: BrowsePalettes +@export var add_color_scene: PackedScene +@export var color_sample: PackedScene +@export var custom_picker_check_box: CheckBox +var my_plugin: PalettePlugin +var my_palettes: Array[Dictionary] = [] +var config := ConfigFile.new() +var config_path := "res://addons/PaletteTools/color_presets.cfg" +var editing_color_on: ColorPickerButton +var four_k_plus: bool = false + + +func _ready() -> void: + if DisplayServer.screen_get_size().x > 2000: + four_k_plus = true + load_palettes() + _on_new_palette_pressed() + iterate_for_control_sizing(self) + + +## We want to size the plugin to look good in both 1080p and 4k +func iterate_for_control_sizing(node: Node) -> void: + ##TODO: See if there's anything to do about this if/else abomination + if node is Window: + return + + if node is Label and node.label_settings: + if "Title" in node.name: + if node.name == "Title": + if four_k_plus: + node.label_settings.font_size = 80 + else: + node.label_settings.font_size = 40 + else: + if four_k_plus: + node.label_settings.font_size = 50 + else: + node.label_settings.font_size = 25 + else: + if four_k_plus: + node.label_settings.font_size = 40 + else: + node.label_settings.font_size = 22 + + if node is CheckBox: + if four_k_plus: + node.add_theme_icon_override("checked", load("res://addons/PaletteTools/Images/checked_4k.png")) + node.add_theme_icon_override("unchecked", load("res://addons/PaletteTools/Images/unchecked_4k.png")) + else: + node.add_theme_icon_override("checked", load("res://addons/PaletteTools/Images/checked.png")) + node.add_theme_icon_override("unchecked", load("res://addons/PaletteTools/Images/unchecked.png")) + + if node is RichTextLabel: + if four_k_plus: + node.add_theme_font_size_override("normal_font_size", 40) + else: + node.add_theme_font_size_override("normal_font_size", 20) + + if node is Button or node is TextureButton or node is LineEdit or node is ItemList: + if (node is Button and node.icon) or node is TextureButton: + if four_k_plus: + node.custom_minimum_size = Vector2i(80, node.get_minimum_size().y) + else: + node.custom_minimum_size = Vector2i(40, node.get_minimum_size().y) + node.update_minimum_size() + else: + if four_k_plus: + node.add_theme_font_size_override("font_size", 30) + else: + node.add_theme_font_size_override("font_size", 16) + for c in node.get_children(): + iterate_for_control_sizing(c) + + +func size_color_sample(cs: ColorSample) -> void: + if four_k_plus: + cs.custom_minimum_size = Vector2i(95, 95) + cs.remove_button.custom_minimum_size = Vector2i(30, 30) + cs.remove_button_text.label_settings.font_size = 20 + else: + cs.custom_minimum_size = Vector2i(40, 40) + cs.remove_button.custom_minimum_size = Vector2i(10, 10) + cs.remove_button_text.label_settings.font_size = 10 + cs.update_minimum_size() + cs.remove_button.update_minimum_size() + + +func preview_colors(p_colors: PackedStringArray) -> void: + for c in color_preview.get_children(): + c.queue_free() + + for pc: String in p_colors: + var cs: ColorSample = color_sample.instantiate() + var col_pick_btn := cs.color_picker_button as ColorPickerButton + col_pick_btn.color = Color.from_string(pc, Color.RED) + col_pick_btn.get_picker().presets_visible = false + col_pick_btn.get_picker().picker_shape = ColorPicker.SHAPE_OKHSL_CIRCLE + + if four_k_plus: + cs.remove_button.size = Vector2i(20, 20) + else: + cs.remove_button.size = Vector2i(10, 10) + + cs.remove_button.pressed.connect(remove_color.bind(cs)) + color_preview.add_child(cs) + size_color_sample(cs) + + var add_box := add_color_scene.instantiate() + if four_k_plus: + add_box.custom_minimum_size = Vector2i(95, 95) + else: + add_box.custom_minimum_size = Vector2i(40, 40) + color_preview.add_child(add_box) + add_box.update_minimum_size() + add_box.get_child(0).pressed.connect(add_color_to_palette) + + await get_tree().create_timer(.01).timeout + + if color_preview.get_child_count() > 2: + editor_swatch_save.visible = true + clear_preview_button.visible = true + else: + editor_swatch_save.visible = false + clear_preview_button.visible = false + + +func _on_line_edit_text_submitted(new_text: String) -> void: + if new_text.length() > 0: + http.get_palette(new_text) + else: + alert.alert("Please enter a URL") + + +func load_palettes() -> void: + my_palettes.clear() + config.load(config_path) + var json := JSON.new() + if config.has_section("color_picker"): + for sec in config.get_section_keys("color_picker"): + my_palettes.append(json.parse_string(config.get_value("color_picker", sec))) + + if my_palettes.size() > 0: + saved_palettes.clear() + for p in my_palettes: + saved_palettes.add_item(p.name) + else: + saved_palettes.clear() + saved_palettes.add_item("No Palettes") + + +func save_new_palette() -> void: + if color_preview.get_child_count() < 2: + alert.alert("Please add at least one color to save a palette") + return + if p_name_text.text.length() <= 0: + alert.alert("Please add a name to save a palette") + return + + var temp_pca: Array[String] = [] + for c in color_preview.get_children(): + if c == color_preview.get_child(-1): + continue + var color = (c as ColorSample).color_picker_button.color + temp_pca.append(color.to_html()) + + var json := JSON.new() + var new_pal := json.stringify({ + "name": p_name_text.text, + "author": p_author_text.text, + "colors": temp_pca + }) + config.set_value("color_picker", p_name_text.text, new_pal) + config.save(config_path) + + load_palettes() + palette_list_updated.emit() + + +func _on_save_to_editor_button_pressed() -> void: + restart_editor.visible = true + var settings = my_plugin.get_editor_interface().get_editor_settings() + var temp_pca: Array[String] = [] + for c in color_preview.get_children(): + if c == color_preview.get_child(-1): + continue + var color := c.get_node("Color").color as Color + temp_pca.append(color.to_html()) + + settings.set_project_metadata("color_picker", "presets", temp_pca) + + +func _on_restart_editor_pressed() -> void: + my_plugin.get_editor_interface().restart_editor() + + +func _on_search_pressed() -> void: + if url.text.length() > 0: + http.get_palette(url.text) + else: + alert.alert("Please enter a URL") + + +func _on_save_palette_pressed() -> void: + save_new_palette() + + +func _on_load_palette_pressed() -> void: + if saved_palettes.get_selected_items().size() <= 0: + await get_tree().process_frame + alert.alert("Please select a palette") + return + + if not config.has_section("color_picker"): + alert.alert("No palettes saved") + return + var load_color := JSON.parse_string(config.get_value("color_picker", saved_palettes.get_item_text(saved_palettes.get_selected_items()[0]))) + p_name_text.text = load_color.name + p_author_text.text = load_color.author + preview_colors(load_color.colors) + + +func add_color_to_palette() -> void: + var cs: ColorSample = color_sample.instantiate() as ColorSample + editor_swatch_save.visible = true + clear_preview_button.visible = true + color_preview.add_child(cs) + color_preview.move_child(cs, -2) + size_color_sample(cs) + editing_color_on = cs.color_picker_button as ColorPickerButton + editing_color_on.get_picker().presets_visible = false + editing_color_on.get_picker().picker_shape = ColorPicker.SHAPE_OKHSL_CIRCLE + cs.remove_button.pressed.connect(remove_color.bind(cs)) + cs.color_picker_button.get_popup().popup( + Rect2( + get_global_mouse_position() - Vector2(size.x, size.y * 20), + cs.color_picker_button.get_popup().size + ) + ) + + +func remove_color(obj: Node) -> void: + obj.queue_free() + if color_preview.get_child_count() < 3: + editor_swatch_save.visible = false + clear_preview_button.visible = false + + +func _on_delete_palette_pressed() -> void: + if config.has_section("color_picker"): + config.erase_section_key("color_picker", saved_palettes.get_item_text(saved_palettes.get_selected_items()[0])) + config.save(config_path) + load_palettes() + palette_list_updated.emit() + + +func _on_new_palette_pressed() -> void: + p_name_text.text = "" + p_author_text.text = "" + saved_palettes.deselect_all() + preview_colors([]) + + +func _on_color_picker_color_changed(color: Color) -> void: + editing_color_on.color = color + + +func _on_saved_palettes_item_activated(_index: int) -> void: + _on_load_palette_pressed() + + +func _on_clear_pressed() -> void: + p_name_text.text = "" + p_author_text.text = "" + preview_colors([]) + + +func import_palette_from_browse(palette_obj: Dictionary) -> void: + p_name_text.text = palette_obj.name + p_author_text.text = palette_obj.author + preview_colors(palette_obj.colors) + + +func _on_browse_palettes_button_pressed() -> void: + browse_popup_panel.visible = true + + +func _on_custom_picker_check_box_toggled(toggled_on: bool) -> void: + my_plugin.toggle_custom_picker(toggled_on) diff --git a/godot/addons/PaletteTools/Scripts/palette_tool.gd.uid b/godot/addons/PaletteTools/Scripts/palette_tool.gd.uid new file mode 100644 index 0000000..ca094dd --- /dev/null +++ b/godot/addons/PaletteTools/Scripts/palette_tool.gd.uid @@ -0,0 +1 @@ +uid://blkegljtk4m4l diff --git a/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader b/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader new file mode 100644 index 0000000..8114347 --- /dev/null +++ b/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader @@ -0,0 +1,34 @@ +//Shader is CC0 from https://godotshaders.com/shader/moving-rainbow-gradient/ +// by Exuin + +// HSV to RBG from https://www.rapidtables.com/convert/color/hsv-to-rgb.html +// Rotation matrix from https://en.wikipedia.org/wiki/Rotation_matrix + +shader_type canvas_item; + + +uniform float strength: hint_range(0., 1.) = 0.5; +uniform float speed: hint_range(0., 10.) = 0.5; +uniform float angle: hint_range(0., 360.) = 0.; + +void fragment() { + float hue = UV.x * cos(radians(angle)) - UV.y * sin(radians(angle)); + hue = fract(hue + fract(TIME * speed)); + float x = 1. - abs(mod(hue / (1./ 6.), 2.) - 1.); + vec3 rainbow; + if(hue < 1./6.){ + rainbow = vec3(1., x, 0.); + } else if (hue < 1./3.) { + rainbow = vec3(x, 1., 0); + } else if (hue < 0.5) { + rainbow = vec3(0, 1., x); + } else if (hue < 2./3.) { + rainbow = vec3(0., x, 1.); + } else if (hue < 5./6.) { + rainbow = vec3(x, 0., 1.); + } else { + rainbow = vec3(1., 0., x); + } + vec4 color = texture(TEXTURE, UV); + COLOR = mix(color, vec4(rainbow, color.a), strength); +} \ No newline at end of file diff --git a/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader.uid b/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader.uid new file mode 100644 index 0000000..0251bef --- /dev/null +++ b/godot/addons/PaletteTools/Shaders/rainbow_shader.gdshader.uid @@ -0,0 +1 @@ +uid://d28kki7c38q1s diff --git a/godot/addons/PaletteTools/icon.png b/godot/addons/PaletteTools/icon.png new file mode 100644 index 0000000..be5fdf3 Binary files /dev/null and b/godot/addons/PaletteTools/icon.png differ diff --git a/godot/addons/PaletteTools/icon.png.import b/godot/addons/PaletteTools/icon.png.import new file mode 100644 index 0000000..64c64a9 --- /dev/null +++ b/godot/addons/PaletteTools/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jnbf7y0pgmxt" +path="res://.godot/imported/icon.png-0223c51045c6df14e252616e4752d918.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/PaletteTools/icon.png" +dest_files=["res://.godot/imported/icon.png-0223c51045c6df14e252616e4752d918.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/PaletteTools/palette_tools.gd b/godot/addons/PaletteTools/palette_tools.gd new file mode 100644 index 0000000..731a706 --- /dev/null +++ b/godot/addons/PaletteTools/palette_tools.gd @@ -0,0 +1,99 @@ +@tool +extends EditorPlugin + +const Palette := preload("res://addons/PaletteTools/Scripts/palette_tool.gd") +const CustomPickerPlugin := preload("res://addons/PaletteTools/Scripts/palette_inspector_script.gd") + +var dock: Control +var inspector_palette_plugin: CustomPickerPlugin +var colors: Palette +var config := ConfigFile.new() +var config_path := "res://addons/PaletteTools/color_presets.cfg" +var max_first_load_tries := 100 + + +func _enter_tree() -> void: + ## load configurations if they exist + var err := config.load(config_path) + + ## If configurations don't exist, it's probably the first plugin load. + ## If installed from the asset library, the plugin is automatically activated. + ## We want to wait for textures to be imported before loading the plugin for this reason. + ## Not doing so will procude log errors and require a project restart + ## before the plugin will work. + if err != OK: + ## Bool to break out of a loop from sub loop + var found_import := false + ## Search for first sign that the import has happened + ## Max attempt of 10 seconds(0.1 * 100) + for try in max_first_load_tries: + for file in DirAccess.get_files_at("res://addons/PaletteTools/Images/"): + if ".import" in file: + found_import = true + break + if found_import: + break + await get_tree().create_timer(.1).timeout + if not found_import: + ## Test + ## Plugin will fail to load if this point is somehow reached. + ## Show error, stop load, and disable to prevent error spam before also failing. + push_error( + "Palette Tools failed to locate both configuration settings and imported textures. " + + "Turning the plugin on again or a project restart may fix this if you have not modified the plugin. " + + "Stopping plugin load and deactivating to prevent a less graceful failure. " + + "If you are modifying the plugin and running into this error, " + + "please edit res://addons/PaletteTools/palette_tools.gd to your needs." + ) + abort_load() + return + + dock = load("res://addons/PaletteTools/Scenes/palette_tools.tscn").instantiate() as Control + colors = dock.get_node("Colors") as Palette + inspector_palette_plugin = load("res://addons/PaletteTools/Scripts/palette_inspector_script.gd").new(self) + + colors.my_plugin = self + add_control_to_dock(DOCK_SLOT_RIGHT_UL, dock) + + var json := JSON.new() + if config.has_section("settings"): + var toggled: bool = config.get_value("settings", "custom_palette_on") + if toggled: + toggle_custom_picker(toggled) + colors.custom_picker_check_box.button_pressed = toggled + else: + config.set_value("settings", "custom_palette_on", false) + + inspector_palette_plugin.load_palettes() + inspector_palette_plugin.initial_palette() + + +func toggle_custom_picker(state: bool): + config.set_value("settings", "custom_palette_on", state) + config.save(config_path) + if state: + add_inspector_plugin(inspector_palette_plugin) + else: + remove_inspector_plugin(inspector_palette_plugin) + + +func _exit_tree() -> void: + if dock: + remove_control_from_docks(dock) + dock.free() + + if is_instance_valid(inspector_palette_plugin) and config.get_value("settings", "custom_palette_on"): + remove_inspector_plugin(inspector_palette_plugin) + + +func _get_plugin_icon() -> Texture2D: + return load("res://addons/PaletteTools/icon.png") + + +func _get_plugin_name() -> String: + return "Palette Tools" + + +func abort_load(): + await get_tree().create_timer(.1).timeout + get_editor_interface().set_plugin_enabled("PaletteTools", false) diff --git a/godot/addons/PaletteTools/palette_tools.gd.uid b/godot/addons/PaletteTools/palette_tools.gd.uid new file mode 100644 index 0000000..80835d4 --- /dev/null +++ b/godot/addons/PaletteTools/palette_tools.gd.uid @@ -0,0 +1 @@ +uid://c0f14vlthbxx8 diff --git a/godot/addons/PaletteTools/plugin.cfg b/godot/addons/PaletteTools/plugin.cfg new file mode 100644 index 0000000..332faeb --- /dev/null +++ b/godot/addons/PaletteTools/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Palette Tools" +description="Color palatte plugin" +author="RancidMilk" +version="1.6.2" +script="palette_tools.gd" diff --git a/godot/addons/PaletteTools/temp_palette.txt b/godot/addons/PaletteTools/temp_palette.txt new file mode 100644 index 0000000..e69de29 diff --git a/godot/addons/beehave/LICENSE b/godot/addons/beehave/LICENSE new file mode 100644 index 0000000..caabbff --- /dev/null +++ b/godot/addons/beehave/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 bitbrain + +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/godot/addons/beehave/blackboard.gd b/godot/addons/beehave/blackboard.gd new file mode 100644 index 0000000..c152152 --- /dev/null +++ b/godot/addons/beehave/blackboard.gd @@ -0,0 +1,55 @@ +@icon("icons/blackboard.svg") +class_name Blackboard extends Node + +const DEFAULT = "default" + +## The blackboard is an object that can be used to store and access data between +## multiple nodes of the behavior tree. +@export var blackboard: Dictionary = {}: + set(b): + blackboard = b.duplicate() + _data[DEFAULT] = blackboard + +var _data: Dictionary = {} + + +func _ready(): + blackboard = blackboard.duplicate() + _data[DEFAULT] = blackboard + + +func keys() -> Array[String]: + var keys: Array[String] + keys.assign(_data.keys().duplicate()) + return keys + + +func set_value(key: Variant, value: Variant, blackboard_name: String = DEFAULT) -> void: + if not _data.has(blackboard_name): + _data[blackboard_name] = {} + + _data[blackboard_name][key] = value + + +func get_value( + key: Variant, default_value: Variant = null, blackboard_name: String = DEFAULT +) -> Variant: + if has_value(key, blackboard_name): + return _data[blackboard_name].get(key, default_value) + return default_value + + +func has_value(key: Variant, blackboard_name: String = DEFAULT) -> bool: + return ( + _data.has(blackboard_name) + and _data[blackboard_name].has(key) + and _data[blackboard_name][key] != null + ) + + +func erase_value(key: Variant, blackboard_name: String = DEFAULT) -> void: + if _data.has(blackboard_name): + _data[blackboard_name][key] = null + +func get_debug_data() -> Dictionary: + return _data diff --git a/godot/addons/beehave/blackboard.gd.uid b/godot/addons/beehave/blackboard.gd.uid new file mode 100644 index 0000000..8d335c7 --- /dev/null +++ b/godot/addons/beehave/blackboard.gd.uid @@ -0,0 +1 @@ +uid://dme5f24l0edsf diff --git a/godot/addons/beehave/debug/debugger.gd b/godot/addons/beehave/debug/debugger.gd new file mode 100644 index 0000000..adb6732 --- /dev/null +++ b/godot/addons/beehave/debug/debugger.gd @@ -0,0 +1,99 @@ +@tool +extends EditorDebuggerPlugin + +const DebuggerTab := preload("debugger_tab.gd") +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +var debugger_tab := DebuggerTab.new() +var floating_window: Window +var session: EditorDebuggerSession + + +func _has_capture(prefix: String) -> bool: + return prefix == "beehave" + + +func _capture(message: String, data: Array, session_id: int) -> bool: + # in case the behavior tree has invalid setup this might be null + if debugger_tab == null: + return false + + if message == "beehave:register_tree": + debugger_tab.register_tree(data[0]) + return true + if message == "beehave:unregister_tree": + debugger_tab.unregister_tree(data[0]) + return true + if message == "beehave:process_interrupt": + debugger_tab.graph.process_interrupt(data[0], data[1]) + return true + if message == "beehave:process_tick": + debugger_tab.graph.process_tick(data[0], data[1], data[2]) + return true + if message == "beehave:process_begin": + debugger_tab.graph.process_begin(data[0], data[1]) + return true + if message == "beehave:process_end": + debugger_tab.graph.process_end(data[0], data[1]) + return true + return false + + +func _setup_session(session_id: int) -> void: + session = get_session(session_id) + session.started.connect(debugger_tab.start) + session.stopped.connect(debugger_tab.stop) + + debugger_tab.name = "🐝 Beehave" + debugger_tab.make_floating.connect(_on_make_floating) + debugger_tab.session = session + session.add_session_tab(debugger_tab) + + +func _on_make_floating() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + if floating_window: + _on_window_close_requested() + return + + var border_size := Vector2(4, 4) * BeehaveUtils.get_editor_scale() + var editor_interface: EditorInterface = plugin.get_editor_interface() + var editor_main_screen = editor_interface.get_editor_main_screen() + debugger_tab.get_parent().remove_child(debugger_tab) + + floating_window = Window.new() + + var panel := Panel.new() + panel.add_theme_stylebox_override( + "panel", + editor_interface.get_base_control().get_theme_stylebox("PanelForeground", "EditorStyles") + ) + panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + floating_window.add_child(panel) + + var margin := MarginContainer.new() + margin.add_child(debugger_tab) + margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + margin.add_theme_constant_override("margin_right", border_size.x) + margin.add_theme_constant_override("margin_left", border_size.x) + margin.add_theme_constant_override("margin_top", border_size.y) + margin.add_theme_constant_override("margin_bottom", border_size.y) + panel.add_child(margin) + + floating_window.title = "🐝 Beehave" + floating_window.wrap_controls = true + floating_window.min_size = Vector2i(600, 350) + floating_window.size = debugger_tab.size + floating_window.position = editor_main_screen.global_position + floating_window.transient = true + floating_window.close_requested.connect(_on_window_close_requested) + editor_interface.get_base_control().add_child(floating_window) + + +func _on_window_close_requested() -> void: + debugger_tab.get_parent().remove_child(debugger_tab) + session.add_session_tab(debugger_tab) + floating_window.queue_free() + floating_window = null diff --git a/godot/addons/beehave/debug/debugger.gd.uid b/godot/addons/beehave/debug/debugger.gd.uid new file mode 100644 index 0000000..72a094c --- /dev/null +++ b/godot/addons/beehave/debug/debugger.gd.uid @@ -0,0 +1 @@ +uid://dhh8iwql2tur2 diff --git a/godot/addons/beehave/debug/debugger_messages.gd b/godot/addons/beehave/debug/debugger_messages.gd new file mode 100644 index 0000000..3eddb09 --- /dev/null +++ b/godot/addons/beehave/debug/debugger_messages.gd @@ -0,0 +1,33 @@ +class_name BeehaveDebuggerMessages + + +static func can_send_message() -> bool: + return not Engine.is_editor_hint() and OS.has_feature("editor") + + +static func register_tree(beehave_tree: Dictionary) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:register_tree", [beehave_tree]) + + +static func unregister_tree(instance_id: int) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:unregister_tree", [instance_id]) + + +static func process_tick(instance_id: int, status: int, blackboard: Dictionary = {}) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_tick", [instance_id, status, blackboard]) + +static func process_interrupt(instance_id: int, blackboard: Dictionary = {}) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_interrupt", [instance_id, blackboard]) + +static func process_begin(instance_id: int, blackboard: Dictionary = {}) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_begin", [instance_id, blackboard]) + + +static func process_end(instance_id: int, blackboard: Dictionary = {}) -> void: + if can_send_message(): + EngineDebugger.send_message("beehave:process_end", [instance_id, blackboard]) diff --git a/godot/addons/beehave/debug/debugger_messages.gd.uid b/godot/addons/beehave/debug/debugger_messages.gd.uid new file mode 100644 index 0000000..2148e0e --- /dev/null +++ b/godot/addons/beehave/debug/debugger_messages.gd.uid @@ -0,0 +1 @@ +uid://ubuuvrubeh85 diff --git a/godot/addons/beehave/debug/debugger_tab.gd b/godot/addons/beehave/debug/debugger_tab.gd new file mode 100644 index 0000000..58c6cc7 --- /dev/null +++ b/godot/addons/beehave/debug/debugger_tab.gd @@ -0,0 +1,215 @@ +@tool +class_name BeehaveDebuggerTab +extends PanelContainer + +const Utils = preload("res://addons/beehave/utils/utils.gd") +const TREE_ICON = preload("../icons/tree.svg") +const OldGraph = preload("old_graph_edit.gd") +const NewGraph = preload("new_graph_edit.gd") +const Blackboard = preload("new_node_blackboard.gd") + +signal make_floating + +var session: EditorDebuggerSession +var first_run = true +var active_trees = {} +var active_tree_id = -1 + +# UI nodes +var container: HSplitContainer +var item_list: ItemList +var graph_container: HSplitContainer +var graph +var blackboard_vbox: VBoxContainer +var message: Label + +func _ready() -> void: + _build_ui() + _init_graph() + stop() + + visibility_changed.connect(_on_visibility_changed) + if visible and is_visible_in_tree(): + get_tree().create_timer(0.5).timeout.connect(_on_visibility_changed) + + +func _build_ui() -> void: + # Main split fills entire panel + container = HSplitContainer.new() + container.set_anchors_preset(Control.PRESET_FULL_RECT) + add_child(container) + + # Left: behavior‐tree list + item_list = ItemList.new() + item_list.custom_minimum_size = Vector2(300, 0) + item_list.item_selected.connect(_on_item_selected) + container.add_child(item_list) + + # Right: graph + (optional) blackboard + graph_container = HSplitContainer.new() + graph_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + container.add_child(graph_container) + + # Blackboard pane: narrow, hidden until needed + blackboard_vbox = VBoxContainer.new() + blackboard_vbox.custom_minimum_size = Vector2(500, 0) + blackboard_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + blackboard_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL + blackboard_vbox.hide() + graph_container.add_child(blackboard_vbox) + + # "Run Project for debugging" overlay + message = Label.new() + message.text = "Run Project for debugging" + message.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + message.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + message.set_anchors_preset(Control.PRESET_CENTER) + add_child(message) + + +func _init_graph() -> void: + var frames = Utils.get_frames() + if Engine.get_version_info().minor >= 2: + graph = NewGraph.new(frames) + else: + graph = OldGraph.new(frames) + + # Graph on the left, blackboard (index 1) on the right + graph.node_selected.connect(_on_graph_node_selected) + graph.node_deselected.connect(_on_graph_node_deselected) + graph.size_flags_horizontal = Control.SIZE_EXPAND_FILL + graph_container.add_child(graph) + graph_container.move_child(graph, 0) + + # "Make Floating" button + var float_btn = Button.new() + float_btn.name = "MakeFloatingButton" + float_btn.flat = true + float_btn.focus_mode = Control.FOCUS_NONE + float_btn.icon = get_theme_icon("ExternalLink", "EditorIcons") + float_btn.pressed.connect(func(): + emit_signal("make_floating") + ) + graph.get_menu_container().add_child(float_btn) + + # "Toggle Panel" button + var toggle_btn = Button.new() + toggle_btn.name = "TogglePanelButton" + toggle_btn.flat = true + toggle_btn.focus_mode = Control.FOCUS_NONE + toggle_btn.icon = get_theme_icon("Back", "EditorIcons") + toggle_btn.pressed.connect(func(): + item_list.visible = not item_list.visible + var icon_name = "Back" if item_list.visible else "Forward" + toggle_btn.icon = get_theme_icon(icon_name, "EditorIcons") + ) + graph.get_menu_container().add_child(toggle_btn) + graph.get_menu_container().move_child(toggle_btn, 0) + + +func start() -> void: + container.visible = true + message.visible = false + + if first_run: + first_run = false + for delay in [0.0, 0.1, 0.5]: + get_tree().create_timer(delay).timeout.connect(_notify_state) + else: + _notify_state() + + # Auto-detach if enabled in project settings - check every time + if ProjectSettings.get_setting("beehave/debugger/start_detached", false): + emit_signal("make_floating") + + +func _notify_state() -> void: + if not session or not visible or not is_visible_in_tree(): + return + session.send_message("beehave:visibility_changed", [true]) + if active_tree_id != -1: + session.send_message("beehave:activate_tree", [active_tree_id]) + + +func stop() -> void: + container.visible = false + message.visible = true + active_trees.clear() + item_list.clear() + graph.beehave_tree = {} + blackboard_vbox.hide() + + +func register_tree(data: Dictionary) -> void: + var id_str = str(data.id) + if not active_trees.has(id_str): + var idx = item_list.add_item(data.name, TREE_ICON) + item_list.set_item_tooltip(idx, data.path) + item_list.set_item_metadata(idx, data.id) + active_trees[id_str] = data + + if data.id.to_int() == active_tree_id: + _notify_state() + + if item_list.get_selected_items().is_empty(): + _select_item_by_id(id_str) + + +func unregister_tree(instance_id: int) -> void: + var id_str = str(instance_id) + for i in range(item_list.get_item_count()): + if item_list.get_item_metadata(i) == id_str: + item_list.remove_item(i) + break + active_trees.erase(id_str) + if graph.beehave_tree.get("id", "") == id_str: + graph.beehave_tree = {} + blackboard_vbox.hide() + + +func _select_item_by_id(id_str: String) -> void: + for i in range(item_list.get_item_count()): + if item_list.get_item_metadata(i) == id_str: + item_list.select(i) + break + get_tree().create_timer(0.2).timeout.connect(func(): + if item_list.is_inside_tree() and not item_list.get_selected_items().is_empty(): + _on_item_selected(item_list.get_selected_items()[0]) + ) + + +func _on_item_selected(idx: int) -> void: + if idx < 0 or idx >= item_list.get_item_count(): + return + + # Immediately hide & clear the blackboard when switching trees + blackboard_vbox.hide() + for child in blackboard_vbox.get_children(): + child.free() + + var id = item_list.get_item_metadata(idx) + var tree_data = active_trees.get(str(id), {}) + if tree_data.is_empty(): + return + + graph.beehave_tree = tree_data + active_tree_id = id.to_int() + _notify_state() + get_tree().create_timer(0.1).timeout.connect(_notify_state) + + +func _on_graph_node_selected(node: GraphNode) -> void: + blackboard_vbox.show() + blackboard_vbox.add_child(Blackboard.new(Utils.get_frames(), node)) + + +func _on_graph_node_deselected(node: GraphNode) -> void: + for child in blackboard_vbox.get_children(): + if child.name == node.name: + child.free() + if blackboard_vbox.get_child_count() == 0: + blackboard_vbox.hide() + + +func _on_visibility_changed() -> void: + _notify_state() diff --git a/godot/addons/beehave/debug/debugger_tab.gd.uid b/godot/addons/beehave/debug/debugger_tab.gd.uid new file mode 100644 index 0000000..dd4b537 --- /dev/null +++ b/godot/addons/beehave/debug/debugger_tab.gd.uid @@ -0,0 +1 @@ +uid://b7hbn0y1kfarn diff --git a/godot/addons/beehave/debug/global_debugger.gd b/godot/addons/beehave/debug/global_debugger.gd new file mode 100644 index 0000000..8d344e3 --- /dev/null +++ b/godot/addons/beehave/debug/global_debugger.gd @@ -0,0 +1,70 @@ +extends Node + +var _registered_trees: Dictionary +var _active_tree +var _pending_activation_id: int = -1 # Store the ID if activation arrives before registration +var _editor_visible: bool = false # Track editor visibility + + +func _enter_tree() -> void: + EngineDebugger.register_message_capture("beehave", _on_debug_message) + + +func _on_debug_message(message: String, data: Array) -> bool: + match message: + "activate_tree": + var requested_id: int = data[0] + _set_active_tree(requested_id) + return true + "visibility_changed": + _editor_visible = data[0] + if _active_tree and is_instance_valid(_active_tree): + # Only update _can_send_message based on editor visibility + _active_tree._can_send_message = _editor_visible + elif _pending_activation_id != -1: + # If we have a pending activation and visibility changes, try to activate it again + _set_active_tree(_pending_activation_id) + return true + return false + + +func _set_active_tree(tree_id: int) -> void: + var tree = _registered_trees.get(tree_id, null) + + if tree and is_instance_valid(tree): + # Tree found, proceed with activation + if _active_tree and is_instance_valid(_active_tree): + _active_tree._can_send_message = false # Deactivate old one + _active_tree = tree + # Activate the new tree ONLY if the editor debugger tab is actually visible + _active_tree._can_send_message = _editor_visible + + # If this was the pending ID, clear it + if _pending_activation_id == tree_id: + _pending_activation_id = -1 + else: + # Tree not found (yet), mark it as pending activation + _pending_activation_id = tree_id + + if _active_tree and is_instance_valid(_active_tree): + _active_tree._can_send_message = false + _active_tree = null # No tree is active now + + +func register_tree(tree) -> void: + var tree_id = tree.get_instance_id() + _registered_trees[tree_id] = tree + + # Check if this tree was waiting for activation + if tree_id == _pending_activation_id: + # Found the pending tree, activate it now + _set_active_tree(tree_id) # This will set _active_tree and handle _can_send_message + + +func unregister_tree(tree) -> void: + var tree_id = tree.get_instance_id() + _registered_trees.erase(tree_id) + if _active_tree == tree: + _active_tree = null + if _pending_activation_id == tree_id: + _pending_activation_id = -1 diff --git a/godot/addons/beehave/debug/global_debugger.gd.uid b/godot/addons/beehave/debug/global_debugger.gd.uid new file mode 100644 index 0000000..06fe496 --- /dev/null +++ b/godot/addons/beehave/debug/global_debugger.gd.uid @@ -0,0 +1 @@ +uid://bl3ma400hpvsh diff --git a/godot/addons/beehave/debug/icons/horizontal_layout.svg b/godot/addons/beehave/debug/icons/horizontal_layout.svg new file mode 100644 index 0000000..235dc62 --- /dev/null +++ b/godot/addons/beehave/debug/icons/horizontal_layout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/horizontal_layout.svg.import b/godot/addons/beehave/debug/icons/horizontal_layout.svg.import new file mode 100644 index 0000000..030a49f --- /dev/null +++ b/godot/addons/beehave/debug/icons/horizontal_layout.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bah77esichnyx" +path="res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/horizontal_layout.svg" +dest_files=["res://.godot/imported/horizontal_layout.svg-d2a7af351e44f9bf61d0c938b6f47fac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/icons/port_bottom.svg b/godot/addons/beehave/debug/icons/port_bottom.svg new file mode 100644 index 0000000..3ce70e7 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_bottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/port_bottom.svg.import b/godot/addons/beehave/debug/icons/port_bottom.svg.import new file mode 100644 index 0000000..73e6829 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_bottom.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://da3b236rjbqns" +path="res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/port_bottom.svg" +dest_files=["res://.godot/imported/port_bottom.svg-e5c5c61b642a79ab9c2b66ff56603d34.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/icons/port_left.svg b/godot/addons/beehave/debug/icons/port_left.svg new file mode 100644 index 0000000..c1f6717 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/port_left.svg.import b/godot/addons/beehave/debug/icons/port_left.svg.import new file mode 100644 index 0000000..2a0c5d3 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_left.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnufc8p6spdtn" +path="res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/port_left.svg" +dest_files=["res://.godot/imported/port_left.svg-69cd927c4db555f1edbb8d1f553ea2fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/icons/port_right.svg b/godot/addons/beehave/debug/icons/port_right.svg new file mode 100644 index 0000000..2560af5 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/port_right.svg.import b/godot/addons/beehave/debug/icons/port_right.svg.import new file mode 100644 index 0000000..ed576a4 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_right.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbmd6vk23ympm" +path="res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/port_right.svg" +dest_files=["res://.godot/imported/port_right.svg-f760bd8be2dd613d0d3848c998c92a2a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/icons/port_top.svg b/godot/addons/beehave/debug/icons/port_top.svg new file mode 100644 index 0000000..d3b99e1 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/port_top.svg.import b/godot/addons/beehave/debug/icons/port_top.svg.import new file mode 100644 index 0000000..c8b5b76 --- /dev/null +++ b/godot/addons/beehave/debug/icons/port_top.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bw8wmxdfom8eh" +path="res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/port_top.svg" +dest_files=["res://.godot/imported/port_top.svg-d1b336cdc6a0dd570305782a1e56f61d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/icons/vertical_layout.svg b/godot/addons/beehave/debug/icons/vertical_layout.svg new file mode 100644 index 0000000..d59ffb0 --- /dev/null +++ b/godot/addons/beehave/debug/icons/vertical_layout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/beehave/debug/icons/vertical_layout.svg.import b/godot/addons/beehave/debug/icons/vertical_layout.svg.import new file mode 100644 index 0000000..96f7769 --- /dev/null +++ b/godot/addons/beehave/debug/icons/vertical_layout.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpyxu6i1dx5qh" +path="res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/debug/icons/vertical_layout.svg" +dest_files=["res://.godot/imported/vertical_layout.svg-1a08fee4b09812a05bcf3defb8afcc4c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/debug/new_frames.gd b/godot/addons/beehave/debug/new_frames.gd new file mode 100644 index 0000000..4b739fd --- /dev/null +++ b/godot/addons/beehave/debug/new_frames.gd @@ -0,0 +1,69 @@ +@tool +extends RefCounted + + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + + +const SUCCESS_COLOR := Color("#07783a") +const NORMAL_COLOR := Color("#15181e") +const FAILURE_COLOR := Color("#82010b") +const RUNNING_COLOR := Color("#c29c06") + +var panel_normal: StyleBoxFlat +var panel_success: StyleBoxFlat +var panel_failure: StyleBoxFlat +var panel_running: StyleBoxFlat + +var titlebar_normal: StyleBoxFlat +var titlebar_success: StyleBoxFlat +var titlebar_failure: StyleBoxFlat +var titlebar_running: StyleBoxFlat + + +func _init() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + + + titlebar_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"titlebar", &"GraphNode")\ + .duplicate() + ) + titlebar_success = titlebar_normal.duplicate() + titlebar_failure = titlebar_normal.duplicate() + titlebar_running = titlebar_normal.duplicate() + + titlebar_success.bg_color = SUCCESS_COLOR + titlebar_failure.bg_color = FAILURE_COLOR + titlebar_running.bg_color = RUNNING_COLOR + + titlebar_success.border_color = SUCCESS_COLOR + titlebar_failure.border_color = FAILURE_COLOR + titlebar_running.border_color = RUNNING_COLOR + + + panel_normal = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel", &"GraphNode") + .duplicate() + ) + panel_success = ( + plugin + .get_editor_interface() + .get_base_control() + .get_theme_stylebox(&"panel_selected", &"GraphNode") + .duplicate() + ) + panel_failure = panel_success.duplicate() + panel_running = panel_success.duplicate() + + panel_success.border_color = SUCCESS_COLOR + panel_failure.border_color = FAILURE_COLOR + panel_running.border_color = RUNNING_COLOR diff --git a/godot/addons/beehave/debug/new_frames.gd.uid b/godot/addons/beehave/debug/new_frames.gd.uid new file mode 100644 index 0000000..aadbeb0 --- /dev/null +++ b/godot/addons/beehave/debug/new_frames.gd.uid @@ -0,0 +1 @@ +uid://tn5lef5pc6ol diff --git a/godot/addons/beehave/debug/new_graph_edit.gd b/godot/addons/beehave/debug/new_graph_edit.gd new file mode 100644 index 0000000..a033789 --- /dev/null +++ b/godot/addons/beehave/debug/new_graph_edit.gd @@ -0,0 +1,319 @@ +@tool +extends GraphEdit + +const BeehaveGraphNode := preload("new_graph_node.gd") + +const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") +const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") + +const PROGRESS_SHIFT: int = 50 +const INACTIVE_COLOR: Color = Color("#000000") +const ACTIVE_COLOR: Color = Color("#c29c06") +const SUCCESS_COLOR: Color = Color("#07783a") +const FAILURE_COLOR: Color = Color("#82010b") + + +var updating_graph: bool = false +var arraging_nodes: bool = false +var beehave_tree: Dictionary: + set(value): + if beehave_tree == value: + return + beehave_tree = value + active_nodes.clear() + _update_graph() + +var horizontal_layout: bool = false: + set(value): + if updating_graph or arraging_nodes: + return + if horizontal_layout == value: + return + horizontal_layout = value + _update_layout_button() + _update_graph() + + +var frames:RefCounted +var active_nodes: Array[String] +var progress: int = 0 +var layout_button: Button + + +func _init(frames:RefCounted) -> void: + self.frames = frames + + +func _ready() -> void: + custom_minimum_size = Vector2(100, 300) + show_arrange_button = false + minimap_enabled = false + layout_button = Button.new() + layout_button.flat = true + layout_button.focus_mode = Control.FOCUS_NONE + layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout) + get_menu_container().add_child(layout_button) + _update_layout_button() + + +func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + # FIXME: hack to hide default lines because empty array crashes Godot :( + const vec1 = Vector2(-9999999, -9999999) + const vec2 = Vector2(-9999999, -9999999) + return [vec1, vec2] + + +func _update_graph() -> void: + if updating_graph: + return + + updating_graph = true + + clear_connections() + + for child in _get_child_nodes(): + remove_child(child) + child.queue_free() + + if not beehave_tree.is_empty(): + _add_nodes(beehave_tree) + _connect_nodes(beehave_tree) + _arrange_nodes.call_deferred(beehave_tree) + + updating_graph = false + + +func _add_nodes(node: Dictionary) -> void: + if node.is_empty(): + return + var gnode := BeehaveGraphNode.new(frames, horizontal_layout) + add_child(gnode) + gnode.title_text = node.name + gnode.name = node.id + gnode.icon = _get_icon(node.type.back()) + + if node.type.has(&"BeehaveTree"): + gnode.set_slots(false, true) + elif node.type.has(&"Leaf"): + gnode.set_slots(true, false) + elif node.type.has(&"Composite") or node.type.has(&"Decorator"): + gnode.set_slots(true, true) + + for child in node.get("children", []): + _add_nodes(child) + + +func _connect_nodes(node: Dictionary) -> void: + for child in node.get("children", []): + connect_node(node.id, 0, child.id, 0) + _connect_nodes(child) + + +func _arrange_nodes(node: Dictionary) -> void: + if arraging_nodes: + return + + arraging_nodes = true + + var tree_node := _create_tree_nodes(node) + tree_node.update_positions(horizontal_layout) + _place_nodes(tree_node) + + arraging_nodes = false + + +func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode: + var tree_node := TreeNode.new(get_node(node.id), root) + for child in node.get("children", []): + var child_node := _create_tree_nodes(child, tree_node) + tree_node.children.push_back(child_node) + return tree_node + + +func _place_nodes(node: TreeNode) -> void: + node.item.position_offset = Vector2(node.x, node.y) + for child in node.children: + _place_nodes(child) + + +func _get_icon(type: StringName) -> Texture2D: + var classes := ProjectSettings.get_global_class_list() + for c in classes: + if c["class"] == type: + var icon_path := c.get("icon", String()) + if not icon_path.is_empty(): + return load(icon_path) + return null + + +func get_menu_container() -> Control: + return call("get_menu_hbox") + + +func get_status(status: int) -> String: + if status == 0: + return "SUCCESS" + elif status == 1: + return "FAILURE" + return "RUNNING" + + +func process_begin(instance_id: int, blackboard = null) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + child.set_meta("status", -1) + + + +func process_interrupt(instance_id: int, blackboard = null) -> void: + var node := get_node_or_null(str(instance_id)) + if node: + node.blackboard = blackboard + # TODO: highlight interrupt somehow (e.g. red border pops up?) + + +func process_tick(instance_id: int, status: int, blackboard = null) -> void: + var node := get_node_or_null(str(instance_id)) + if node: + node.text = "Status: %s" % get_status(status) + node.set_status(status) + node.set_meta("status", status) + node.blackboard = blackboard + if status == BeehaveNode.RUNNING: + if not active_nodes.has(node.name): + active_nodes.push_back(node.name) + + +func process_end(instance_id: int, blackboard = null) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + var status := child.get_meta("status", -1) + match status: + BeehaveNode.SUCCESS: + active_nodes.erase(child.name) + child.set_color(SUCCESS_COLOR) + BeehaveNode.FAILURE: + active_nodes.erase(child.name) + child.set_color(FAILURE_COLOR) + child.set_input_color(FAILURE_COLOR) + child.set_output_color(FAILURE_COLOR) + BeehaveNode.RUNNING: + child.set_color(ACTIVE_COLOR) + _: + child.text = " " + child.set_status(status) + child.set_color(INACTIVE_COLOR) + + +func _is_same_tree(instance_id: int) -> bool: + return str(instance_id) == beehave_tree.get("id", "") + + +func _get_child_nodes() -> Array[Node]: + return get_children().filter(func(child): return child is BeehaveGraphNode) + + +func _get_elbow_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + var points: PackedVector2Array + + points.push_back(from_position) + + var mid_position := ((to_position + from_position) / 2).round() + if horizontal_layout: + points.push_back(Vector2(mid_position.x, from_position.y)) + points.push_back(Vector2(mid_position.x, to_position.y)) + else: + points.push_back(Vector2(from_position.x, mid_position.y)) + points.push_back(Vector2(to_position.x, mid_position.y)) + + points.push_back(to_position) + + return points + + +func _process(delta: float) -> void: + if not active_nodes.is_empty(): + progress += 10 if delta >= 0.05 else 1 + if progress >= 1000: + progress = 0 + queue_redraw() + + +func _draw() -> void: + var circle_size: float = max(4, 8 * zoom) + var progress_shift: float = PROGRESS_SHIFT * zoom + + var connections := get_connection_list() + for c in connections: + var from_node: StringName + var to_node: StringName + + from_node = c.from_node + to_node = c.to_node + + var from := get_node(String(from_node)) + var to := get_node(String(to_node)) + + var from_position: Vector2 = from.position + from.get_custom_output_port_position(horizontal_layout) * zoom + var to_position: Vector2 = to.position + to.get_custom_input_port_position(horizontal_layout) * zoom + + var line := _get_elbow_connection_line(from_position, to_position) + + # Get colors based on node states + var from_color: Color + var to_color: Color + + match from.get_meta("status", -1): + BeehaveNode.SUCCESS: from_color = SUCCESS_COLOR + BeehaveNode.FAILURE: from_color = FAILURE_COLOR + BeehaveNode.RUNNING: from_color = ACTIVE_COLOR + _: from_color = INACTIVE_COLOR + + match to.get_meta("status", -1): + BeehaveNode.SUCCESS: to_color = SUCCESS_COLOR + BeehaveNode.FAILURE: to_color = FAILURE_COLOR + BeehaveNode.RUNNING: to_color = ACTIVE_COLOR + _: to_color = INACTIVE_COLOR + + # Draw the line with gradient colors + var line_color := from_color.lerp(to_color, 0.5) + line_color.a = 0.6 + draw_polyline(line, line_color, 7.0 * zoom, true) + + # Draw a second line with a different alpha for smoother transition + var transition_color := from_color.lerp(to_color, 0.3) + transition_color.a = 0.3 + draw_polyline(line, transition_color, 9.0, true) + + # Only draw dots for active nodes + if not active_nodes.is_empty() and from_node in active_nodes and to_node in active_nodes: + if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: + continue + + var curve = Curve2D.new() + for l in line: + curve.add_point(l) + + var max_steps := int(curve.get_baked_length()) + var current_shift := progress % max_steps + var p := curve.sample_baked(current_shift) + draw_circle(p, circle_size, ACTIVE_COLOR) + + var shift := current_shift - progress_shift + while shift >= 0: + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift -= progress_shift + + shift = current_shift + progress_shift + while shift <= curve.get_baked_length(): + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift += progress_shift + + +func _update_layout_button() -> void: + layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON + layout_button.tooltip_text = "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" diff --git a/godot/addons/beehave/debug/new_graph_edit.gd.uid b/godot/addons/beehave/debug/new_graph_edit.gd.uid new file mode 100644 index 0000000..f422b81 --- /dev/null +++ b/godot/addons/beehave/debug/new_graph_edit.gd.uid @@ -0,0 +1 @@ +uid://nqi2umf7xr2h diff --git a/godot/addons/beehave/debug/new_graph_node.gd b/godot/addons/beehave/debug/new_graph_node.gd new file mode 100644 index 0000000..985054c --- /dev/null +++ b/godot/addons/beehave/debug/new_graph_node.gd @@ -0,0 +1,192 @@ +@tool +extends GraphNode + +signal blackboard_updated + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const PORT_TOP_ICON := preload("icons/port_top.svg") +const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") +const PORT_LEFT_ICON := preload("icons/port_left.svg") +const PORT_RIGHT_ICON := preload("icons/port_right.svg") + + +@export var title_text: String: + set(value): + title_text = value + if title_label: + title_label.text = value + +@export var text: String: + set(value): + text = value + if label: + label.text = " " if text.is_empty() else text + +@export var icon: Texture2D: + set(value): + icon = value + if icon_rect: + icon_rect.texture = value + +@export var blackboard: Dictionary: + set(value): + blackboard = value + blackboard_updated.emit() + +var layout_size: float: + get: + return size.y if horizontal else size.x + + +var icon_rect: TextureRect +var title_label: Label +var label: Label +var titlebar_hbox: HBoxContainer + +var frames: RefCounted +var horizontal: bool = false +var panels_tween: Tween + + +func _init(frames:RefCounted, horizontal: bool = false) -> void: + self.frames = frames + self.horizontal = horizontal + + +func _ready() -> void: + custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() + draggable = false + + add_theme_color_override("close_color", Color.TRANSPARENT) + add_theme_icon_override("close", ImageTexture.new()) + + # For top port + var top_port: Control = Control.new() + add_child(top_port) + + icon_rect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + + titlebar_hbox = get_titlebar_hbox() + titlebar_hbox.get_child(0).queue_free() + titlebar_hbox.alignment = BoxContainer.ALIGNMENT_BEGIN + titlebar_hbox.add_child(icon_rect) + + title_label = Label.new() + title_label.add_theme_color_override("font_color", Color.WHITE) + var title_font: Font = get_theme_font("title_font").duplicate() + if title_font is FontVariation: + title_font.variation_embolden = 1 + elif title_font is FontFile: + title_font.font_weight = 700 + title_label.add_theme_font_override("font", title_font) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text = title_text + titlebar_hbox.add_child(title_label) + + label = Label.new() + label.text = " " if text.is_empty() else text + add_child(label) + + # For bottom port + add_child(Control.new()) + + minimum_size_changed.connect(_on_size_changed) + _on_size_changed.call_deferred() + + +func _draw_port(slot_index: int, port_position: Vector2i, left: bool, color: Color) -> void: + if horizontal: + if is_slot_enabled_left(1): + draw_texture(PORT_LEFT_ICON, Vector2(0, size.y / 2) + Vector2(-10, -11), color) + if is_slot_enabled_right(1): + draw_texture(PORT_RIGHT_ICON, Vector2(size.x, size.y / 2) + Vector2(-9, -11), color) + else: + if slot_index == 0 and is_slot_enabled_left(0): + draw_texture(PORT_TOP_ICON, Vector2(size.x / 2, 0) + Vector2(-10, -15), color) + elif slot_index == 1: + draw_texture(PORT_BOTTOM_ICON, Vector2(size.x / 2, size.y) + Vector2(-10, -9), color) + + +func get_custom_input_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(0, size.y / 2) + else: + return Vector2(size.x/2, 0) + + +func get_custom_output_port_position(horizontal: bool) -> Vector2: + if horizontal: + return Vector2(size.x, size.y / 2) + else: + return Vector2(size.x / 2, size.y) + + +func set_status(status: int) -> void: + match status: + BeehaveNode.SUCCESS: _set_stylebox_overrides(frames.panel_success, frames.titlebar_success) + BeehaveNode.FAILURE: _set_stylebox_overrides(frames.panel_failure, frames.titlebar_failure) + BeehaveNode.RUNNING: _set_stylebox_overrides(frames.panel_running, frames.titlebar_running) + _: _set_stylebox_overrides(frames.panel_normal, frames.titlebar_normal) + + +func set_slots(left_enabled: bool, right_enabled: bool) -> void: + if horizontal: + set_slot(1, left_enabled, -1, Color.WHITE, right_enabled, -1, Color.WHITE, PORT_LEFT_ICON, PORT_RIGHT_ICON) + else: + set_slot(0, left_enabled, -1, Color.WHITE, false, -1, Color.TRANSPARENT, PORT_TOP_ICON, null) + set_slot(2, false, -1, Color.TRANSPARENT, right_enabled, -1, Color.WHITE, null, PORT_BOTTOM_ICON) + + +func set_color(color: Color) -> void: + set_input_color(color) + set_output_color(color) + + +func set_input_color(color: Color) -> void: + set_slot_color_left(1 if horizontal else 0, color) + + +func set_output_color(color: Color) -> void: + set_slot_color_right(1 if horizontal else 2, color) + + +func _set_stylebox_overrides(panel_stylebox: StyleBox, titlebar_stylebox: StyleBox) -> void: + # First update and any status change gets immediate panel update + if not has_theme_stylebox_override("panel") or panel_stylebox != frames.panel_normal: + if panels_tween: + panels_tween.kill() + panels_tween = null + + add_theme_stylebox_override("panel", panel_stylebox) + add_theme_stylebox_override("titlebar", titlebar_stylebox) + return + + # Don't need to do anything if we're already tweening back to normal + if panels_tween: + return + + # Don't need to do anything if our colors are already the same as a normal + var cur_panel_stylebox: StyleBox = get_theme_stylebox("panel") + var cur_titlebar_stylebox: StyleBox = get_theme_stylebox("titlebar") + if cur_panel_stylebox.bg_color == frames.panel_normal.bg_color: + return + + # Apply a duplicate of our current panels that we can tween + add_theme_stylebox_override("panel", cur_panel_stylebox.duplicate()) + add_theme_stylebox_override("titlebar", cur_titlebar_stylebox.duplicate()) + cur_panel_stylebox = get_theme_stylebox("panel") + cur_titlebar_stylebox = get_theme_stylebox("titlebar") + + # Going back to normal is a fade + panels_tween = create_tween() + panels_tween.parallel().tween_property(cur_panel_stylebox, "bg_color", panel_stylebox.bg_color, 1.0) + panels_tween.parallel().tween_property(cur_panel_stylebox, "border_color", panel_stylebox.border_color, 1.0) + panels_tween.parallel().tween_property(cur_titlebar_stylebox, "bg_color", panel_stylebox.bg_color, 1.0) + panels_tween.parallel().tween_property(cur_titlebar_stylebox, "border_color", panel_stylebox.border_color, 1.0) + + +func _on_size_changed(): + add_theme_constant_override("port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x)) diff --git a/godot/addons/beehave/debug/new_graph_node.gd.uid b/godot/addons/beehave/debug/new_graph_node.gd.uid new file mode 100644 index 0000000..fb5a769 --- /dev/null +++ b/godot/addons/beehave/debug/new_graph_node.gd.uid @@ -0,0 +1 @@ +uid://bpjiuilpk7qkl diff --git a/godot/addons/beehave/debug/new_node_blackboard.gd b/godot/addons/beehave/debug/new_node_blackboard.gd new file mode 100644 index 0000000..2b8160d --- /dev/null +++ b/godot/addons/beehave/debug/new_node_blackboard.gd @@ -0,0 +1,81 @@ +extends VBoxContainer + +var frames: RefCounted +var graph_node: GraphNode + +var item_tree: Tree + +func _init(frames: RefCounted, node: GraphNode) -> void: + self.frames = frames + graph_node = node + + graph_node.blackboard_updated.connect(_update_list) + +func _ready() -> void: + name = graph_node.name + + set_anchors_preset(Control.PRESET_FULL_RECT) + size_flags_horizontal = Control.SIZE_EXPAND_FILL + size_flags_vertical = Control.SIZE_EXPAND_FILL + + var title_panel: Panel = Panel.new() + title_panel.set_anchors_preset(Control.PRESET_FULL_RECT) + title_panel.custom_minimum_size = Vector2(200, 50) + add_child(title_panel) + var title_hbox: HBoxContainer = HBoxContainer.new() + title_hbox.alignment = BoxContainer.ALIGNMENT_CENTER + title_hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_hbox.set_anchors_preset(Control.PRESET_FULL_RECT) + title_panel.add_child(title_hbox) + + var icon_rect: TextureRect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon_rect.texture = graph_node.icon + icon_rect.set_size(Vector2(20, 20)) + title_hbox.add_child(icon_rect) + + var title: Label = Label.new() + title.text = graph_node.title_text + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.set_anchors_preset(Control.PRESET_FULL_RECT) + title_hbox.add_child(title) + + item_tree = Tree.new() + item_tree.custom_minimum_size = Vector2(200, 400) + item_tree.size_flags_horizontal = Control.SIZE_EXPAND_FILL + item_tree.size_flags_vertical = Control.SIZE_EXPAND_FILL + item_tree.hide_root = true + item_tree.allow_search = false + item_tree.columns = 2 + add_child(item_tree) + + _update_list() + +func _update_list() -> void: + item_tree.clear() + + var root: TreeItem = item_tree.create_item() + + if graph_node.blackboard.size() == 0: + var no_bb_message: TreeItem = item_tree.create_item(root) + no_bb_message.set_text(0, "No blackboard data") + return + + for bb_name in graph_node.blackboard: + var bb_name_branch: TreeItem = item_tree.create_item(root) + bb_name_branch.set_text(0, bb_name) + + #print(graph_node.blackboard.get(bb_name)) + for key in graph_node.blackboard.get(bb_name): + var bb_kv_leaf: TreeItem = item_tree.create_item(bb_name_branch) + bb_kv_leaf.set_text(0, str(key)) + bb_kv_leaf.set_text(1, str(graph_node.blackboard.get(bb_name).get(key))) + +func _get_icon(type: StringName) -> Texture2D: + var classes := ProjectSettings.get_global_class_list() + for c in classes: + if c["class"] == type: + var icon_path := c.get("icon", String()) + if not icon_path.is_empty(): + return load(icon_path) + return null diff --git a/godot/addons/beehave/debug/new_node_blackboard.gd.uid b/godot/addons/beehave/debug/new_node_blackboard.gd.uid new file mode 100644 index 0000000..9956b98 --- /dev/null +++ b/godot/addons/beehave/debug/new_node_blackboard.gd.uid @@ -0,0 +1 @@ +uid://ne6tky0wxkxy diff --git a/godot/addons/beehave/debug/old_frames.gd b/godot/addons/beehave/debug/old_frames.gd new file mode 100644 index 0000000..a9f6aa8 --- /dev/null +++ b/godot/addons/beehave/debug/old_frames.gd @@ -0,0 +1,47 @@ +@tool +extends RefCounted + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const SUCCESS_COLOR := Color("#009944c8") +const NORMAL_COLOR := Color("#15181e") +const FAILURE_COLOR := Color("#cf000f80") +const RUNNING_COLOR := Color("#ffcc00c8") + +var empty: StyleBoxEmpty +var normal: StyleBoxFlat +var success: StyleBoxFlat +var failure: StyleBoxFlat +var running: StyleBoxFlat + + +func _init() -> void: + var plugin := BeehaveUtils.get_plugin() + if not plugin: + return + + var editor_scale := BeehaveUtils.get_editor_scale() + + empty = StyleBoxEmpty.new() + + normal = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"frame", &"GraphNode") + . duplicate() + ) + + success = ( + plugin + . get_editor_interface() + . get_base_control() + . get_theme_stylebox(&"selected_frame", &"GraphNode") + . duplicate() + ) + failure = success.duplicate() + running = success.duplicate() + + success.border_color = SUCCESS_COLOR + failure.border_color = FAILURE_COLOR + running.border_color = RUNNING_COLOR diff --git a/godot/addons/beehave/debug/old_frames.gd.uid b/godot/addons/beehave/debug/old_frames.gd.uid new file mode 100644 index 0000000..d5b5bc6 --- /dev/null +++ b/godot/addons/beehave/debug/old_frames.gd.uid @@ -0,0 +1 @@ +uid://bhapw50f625uy diff --git a/godot/addons/beehave/debug/old_graph_edit.gd b/godot/addons/beehave/debug/old_graph_edit.gd new file mode 100644 index 0000000..69c11a3 --- /dev/null +++ b/godot/addons/beehave/debug/old_graph_edit.gd @@ -0,0 +1,286 @@ +@tool +extends GraphEdit + +const BeehaveGraphNode := preload("old_graph_node.gd") + +const HORIZONTAL_LAYOUT_ICON := preload("icons/horizontal_layout.svg") +const VERTICAL_LAYOUT_ICON := preload("icons/vertical_layout.svg") + +const PROGRESS_SHIFT: int = 50 +const INACTIVE_COLOR: Color = Color("#898989aa") +const ACTIVE_COLOR: Color = Color("#ffcc00c8") +const SUCCESS_COLOR: Color = Color("#009944c8") + +var updating_graph: bool = false +var arraging_nodes: bool = false +var beehave_tree: Dictionary: + set(value): + if beehave_tree == value: + return + beehave_tree = value + active_nodes.clear() + _update_graph() + +var horizontal_layout: bool = false: + set(value): + if updating_graph or arraging_nodes: + return + if horizontal_layout == value: + return + horizontal_layout = value + _update_layout_button() + _update_graph() + +var frames: RefCounted +var active_nodes: Array[String] +var progress: int = 0 +var layout_button: Button + + +func _init(frames: RefCounted) -> void: + self.frames = frames + + +func _ready() -> void: + custom_minimum_size = Vector2(100, 300) + set("arrange_nodes_button_hidden", true) + minimap_enabled = false + layout_button = Button.new() + layout_button.flat = true + layout_button.focus_mode = Control.FOCUS_NONE + layout_button.pressed.connect(func(): horizontal_layout = not horizontal_layout) + get_menu_container().add_child(layout_button) + _update_layout_button() + + +func _update_graph() -> void: + if updating_graph: + return + + updating_graph = true + + clear_connections() + + for child in _get_child_nodes(): + remove_child(child) + child.queue_free() + + if not beehave_tree.is_empty(): + _add_nodes(beehave_tree) + _connect_nodes(beehave_tree) + _arrange_nodes.call_deferred(beehave_tree) + + updating_graph = false + + +func _add_nodes(node: Dictionary) -> void: + if node.is_empty(): + return + var gnode := BeehaveGraphNode.new(frames, horizontal_layout) + add_child(gnode) + gnode.title_text = node.name + gnode.name = node.id + gnode.icon = _get_icon(node.type.back()) + + if node.type.has(&"BeehaveTree"): + gnode.set_slots(false, true) + elif node.type.has(&"Leaf"): + gnode.set_slots(true, false) + elif node.type.has(&"Composite") or node.type.has(&"Decorator"): + gnode.set_slots(true, true) + + for child in node.get("children", []): + _add_nodes(child) + + +func _connect_nodes(node: Dictionary) -> void: + for child in node.get("children", []): + connect_node(node.id, 0, child.id, 0) + _connect_nodes(child) + + +func _arrange_nodes(node: Dictionary) -> void: + if arraging_nodes: + return + + arraging_nodes = true + + var tree_node := _create_tree_nodes(node) + tree_node.update_positions(horizontal_layout) + _place_nodes(tree_node) + + arraging_nodes = false + + +func _create_tree_nodes(node: Dictionary, root: TreeNode = null) -> TreeNode: + var tree_node := TreeNode.new(get_node(node.id), root) + for child in node.get("children", []): + var child_node := _create_tree_nodes(child, tree_node) + tree_node.children.push_back(child_node) + return tree_node + + +func _place_nodes(node: TreeNode) -> void: + node.item.position_offset = Vector2(node.x, node.y) + for child in node.children: + _place_nodes(child) + + +func _get_icon(type: StringName) -> Texture2D: + var classes := ProjectSettings.get_global_class_list() + for c in classes: + if c["class"] == type: + var icon_path := c.get("icon", String()) + if not icon_path.is_empty(): + return load(icon_path) + return null + + +func get_menu_container() -> Control: + return call("get_zoom_hbox") + + +func get_status(status: int) -> String: + if status == 0: + return "SUCCESS" + elif status == 1: + return "FAILURE" + return "RUNNING" + + +func process_begin(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + child.set_meta("status", -1) + + +func process_tick(instance_id: int, status: int) -> void: + var node := get_node_or_null(str(instance_id)) + if node: + node.text = "Status: %s" % get_status(status) + node.set_status(status) + node.set_meta("status", status) + if status == BeehaveNode.SUCCESS or status == BeehaveNode.RUNNING: + if not active_nodes.has(node.name): + active_nodes.push_back(node.name) + + +func process_end(instance_id: int) -> void: + if not _is_same_tree(instance_id): + return + + for child in _get_child_nodes(): + var status := child.get_meta("status", -1) + match status: + BeehaveNode.SUCCESS: + active_nodes.erase(child.name) + child.set_color(SUCCESS_COLOR) + BeehaveNode.FAILURE: + active_nodes.erase(child.name) + child.set_color(INACTIVE_COLOR) + BeehaveNode.RUNNING: + child.set_color(ACTIVE_COLOR) + _: + child.text = " " + child.set_status(status) + child.set_color(INACTIVE_COLOR) + + +func _is_same_tree(instance_id: int) -> bool: + return str(instance_id) == beehave_tree.get("id", "") + + +func _get_child_nodes() -> Array[Node]: + return get_children().filter(func(child): return child is BeehaveGraphNode) + + +func _get_connection_line(from_position: Vector2, to_position: Vector2) -> PackedVector2Array: + var points: PackedVector2Array + + from_position = from_position.round() + to_position = to_position.round() + + points.push_back(from_position) + + var mid_position := ((to_position + from_position) / 2).round() + if horizontal_layout: + points.push_back(Vector2(mid_position.x, from_position.y)) + points.push_back(Vector2(mid_position.x, to_position.y)) + else: + points.push_back(Vector2(from_position.x, mid_position.y)) + points.push_back(Vector2(to_position.x, mid_position.y)) + + points.push_back(to_position) + + return points + + +func _process(delta: float) -> void: + if not active_nodes.is_empty(): + progress += 10 if delta >= 0.05 else 1 + if progress >= 1000: + progress = 0 + queue_redraw() + + +func _draw() -> void: + if active_nodes.is_empty(): + return + + var circle_size: float = max(3, 6 * zoom) + var progress_shift: float = PROGRESS_SHIFT * zoom + + var connections := get_connection_list() + for c in connections: + var from_node: StringName + var to_node: StringName + + from_node = c.from + to_node = c.to + + if not from_node in active_nodes or not c.to_node in active_nodes: + continue + + var from := get_node(String(from_node)) + var to := get_node(String(to_node)) + + if from.get_meta("status", -1) < 0 or to.get_meta("status", -1) < 0: + return + + var output_port_position: Vector2 + var input_port_position: Vector2 + + output_port_position = ( + from.position + from.call("get_connection_output_position", c.from_port) + ) + input_port_position = to.position + to.call("get_connection_input_position", c.to_port) + + var line := _get_connection_line(output_port_position, input_port_position) + + var curve = Curve2D.new() + for l in line: + curve.add_point(l) + + var max_steps := int(curve.get_baked_length()) + var current_shift := progress % max_steps + var p := curve.sample_baked(current_shift) + draw_circle(p, circle_size, ACTIVE_COLOR) + + var shift := current_shift - progress_shift + while shift >= 0: + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift -= progress_shift + + shift = current_shift + progress_shift + while shift <= curve.get_baked_length(): + draw_circle(curve.sample_baked(shift), circle_size, ACTIVE_COLOR) + shift += progress_shift + + +func _update_layout_button() -> void: + layout_button.icon = VERTICAL_LAYOUT_ICON if horizontal_layout else HORIZONTAL_LAYOUT_ICON + layout_button.tooltip_text = ( + "Switch to Vertical layout" if horizontal_layout else "Switch to Horizontal layout" + ) diff --git a/godot/addons/beehave/debug/old_graph_edit.gd.uid b/godot/addons/beehave/debug/old_graph_edit.gd.uid new file mode 100644 index 0000000..4498be7 --- /dev/null +++ b/godot/addons/beehave/debug/old_graph_edit.gd.uid @@ -0,0 +1 @@ +uid://qhj602h7xnwu diff --git a/godot/addons/beehave/debug/old_graph_node.gd b/godot/addons/beehave/debug/old_graph_node.gd new file mode 100644 index 0000000..28503b1 --- /dev/null +++ b/godot/addons/beehave/debug/old_graph_node.gd @@ -0,0 +1,166 @@ +@tool +extends GraphNode + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +const DEFAULT_COLOR := Color("#dad4cb") + +const PORT_TOP_ICON := preload("icons/port_top.svg") +const PORT_BOTTOM_ICON := preload("icons/port_bottom.svg") +const PORT_LEFT_ICON := preload("icons/port_left.svg") +const PORT_RIGHT_ICON := preload("icons/port_right.svg") + +@export var title_text: String: + set(value): + title_text = value + if title_label: + title_label.text = value + +@export var text: String: + set(value): + text = value + if label: + label.text = " " if text.is_empty() else text + +@export var icon: Texture2D: + set(value): + icon = value + if icon_rect: + icon_rect.texture = value + +var layout_size: float: + get: + return size.y if horizontal else size.x + +var panel: PanelContainer +var icon_rect: TextureRect +var title_label: Label +var container: VBoxContainer +var label: Label + +var frames: RefCounted +var horizontal: bool = false + + +func _init(frames: RefCounted, horizontal: bool = false) -> void: + self.frames = frames + self.horizontal = horizontal + + +func _ready() -> void: + custom_minimum_size = Vector2(50, 50) * BeehaveUtils.get_editor_scale() + draggable = false + + add_theme_stylebox_override("frame", frames.empty if frames != null else null) + add_theme_stylebox_override("selected_frame", frames.empty if frames != null else null) + add_theme_color_override("close_color", Color.TRANSPARENT) + add_theme_icon_override("close", ImageTexture.new()) + + # For top port + add_child(Control.new()) + + panel = PanelContainer.new() + panel.mouse_filter = Control.MOUSE_FILTER_PASS + panel.add_theme_stylebox_override("panel", frames.normal if frames != null else null) + add_child(panel) + + var vbox_container := VBoxContainer.new() + panel.add_child(vbox_container) + + var title_size := 24 * BeehaveUtils.get_editor_scale() + var margin_container := MarginContainer.new() + margin_container.add_theme_constant_override( + "margin_top", -title_size - 2 * BeehaveUtils.get_editor_scale() + ) + margin_container.mouse_filter = Control.MOUSE_FILTER_PASS + vbox_container.add_child(margin_container) + + var title_container := HBoxContainer.new() + title_container.add_child(Control.new()) + title_container.mouse_filter = Control.MOUSE_FILTER_PASS + title_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + margin_container.add_child(title_container) + + icon_rect = TextureRect.new() + icon_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + title_container.add_child(icon_rect) + + title_label = Label.new() + title_label.add_theme_color_override("font_color", DEFAULT_COLOR) + title_label.add_theme_font_override("font", get_theme_font("title_font")) + title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title_label.text = title_text + title_container.add_child(title_label) + + title_container.add_child(Control.new()) + + container = VBoxContainer.new() + container.size_flags_vertical = Control.SIZE_EXPAND_FILL + container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + panel.add_child(container) + + label = Label.new() + label.text = " " if text.is_empty() else text + container.add_child(label) + + # For bottom port + add_child(Control.new()) + + minimum_size_changed.connect(_on_size_changed) + _on_size_changed.call_deferred() + + +func set_status(status: int) -> void: + panel.add_theme_stylebox_override("panel", _get_stylebox(status)) + + +func _get_stylebox(status: int) -> StyleBox: + match status: + BeehaveNode.SUCCESS: + return frames.success + BeehaveNode.FAILURE: + return frames.failure + BeehaveNode.RUNNING: + return frames.running + _: + return frames.normal + + +func set_slots(left_enabled: bool, right_enabled: bool) -> void: + if horizontal: + set_slot( + 1, + left_enabled, + 0, + Color.WHITE, + right_enabled, + 0, + Color.WHITE, + PORT_LEFT_ICON, + PORT_RIGHT_ICON + ) + else: + set_slot(0, left_enabled, 0, Color.WHITE, false, -2, Color.TRANSPARENT, PORT_TOP_ICON, null) + set_slot( + 2, false, -1, Color.TRANSPARENT, right_enabled, 0, Color.WHITE, null, PORT_BOTTOM_ICON + ) + + +func set_color(color: Color) -> void: + set_input_color(color) + set_output_color(color) + + +func set_input_color(color: Color) -> void: + set_slot_color_left(1 if horizontal else 0, color) + + +func set_output_color(color: Color) -> void: + set_slot_color_right(1 if horizontal else 2, color) + + +func _on_size_changed(): + add_theme_constant_override( + "port_offset", 12 * BeehaveUtils.get_editor_scale() if horizontal else round(size.x / 2.0) + ) diff --git a/godot/addons/beehave/debug/old_graph_node.gd.uid b/godot/addons/beehave/debug/old_graph_node.gd.uid new file mode 100644 index 0000000..f347591 --- /dev/null +++ b/godot/addons/beehave/debug/old_graph_node.gd.uid @@ -0,0 +1 @@ +uid://bkng0fnhet2hx diff --git a/godot/addons/beehave/debug/tree_node.gd b/godot/addons/beehave/debug/tree_node.gd new file mode 100644 index 0000000..1377970 --- /dev/null +++ b/godot/addons/beehave/debug/tree_node.gd @@ -0,0 +1,275 @@ +class_name TreeNode +extends RefCounted + +# Based on https://rachel53461.wordpress.com/2014/04/20/algorithm-for-drawing-trees/ + +const SIBLING_DISTANCE: float = 20.0 +const LEVEL_DISTANCE: float = 40.0 + +const BeehaveUtils := preload("res://addons/beehave/utils/utils.gd") + +var x: float +var y: float +var mod: float +var parent: TreeNode +var children: Array[TreeNode] + +var item: GraphNode + + +func _init(p_item: GraphNode = null, p_parent: TreeNode = null) -> void: + parent = p_parent + item = p_item + + +func is_leaf() -> bool: + return children.is_empty() + + +func is_most_left() -> bool: + if not parent: + return true + return parent.children.front() == self + + +func is_most_right() -> bool: + if not parent: + return true + return parent.children.back() == self + + +func get_previous_sibling() -> TreeNode: + if not parent or is_most_left(): + return null + return parent.children[parent.children.find(self) - 1] + + +func get_next_sibling() -> TreeNode: + if not parent or is_most_right(): + return null + return parent.children[parent.children.find(self) + 1] + + +func get_most_left_sibling() -> TreeNode: + if not parent: + return null + + if is_most_left(): + return self + + return parent.children.front() + + +func get_most_left_child() -> TreeNode: + if children.is_empty(): + return null + return children.front() + + +func get_most_right_child() -> TreeNode: + if children.is_empty(): + return null + return children.back() + + +func update_positions(horizontally: bool = false) -> void: + _initialize_nodes(self, 0) + _calculate_initial_x(self) + + _check_all_children_on_screen(self) + _calculate_final_positions(self, 0) + + if horizontally: + _swap_x_y(self) + _calculate_x(self, 0) + else: + _calculate_y(self, 0) + + +func _initialize_nodes(node: TreeNode, depth: int) -> void: + node.x = -1 + node.y = depth + node.mod = 0 + + for child in node.children: + _initialize_nodes(child, depth + 1) + + +func _calculate_initial_x(node: TreeNode) -> void: + for child in node.children: + _calculate_initial_x(child) + if node.is_leaf(): + if not node.is_most_left(): + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) + else: + node.x = 0 + else: + var mid: float + if node.children.size() == 1: + var offset: float = (node.children.front().item.layout_size - node.item.layout_size) / 2 + mid = node.children.front().x + offset + else: + var left_child := node.get_most_left_child() + var right_child := node.get_most_right_child() + mid = ( + ( + left_child.x + + right_child.x + + right_child.item.layout_size + - node.item.layout_size + ) + / 2 + ) + + if node.is_most_left(): + node.x = mid + else: + node.x = ( + node.get_previous_sibling().x + + node.get_previous_sibling().item.layout_size + + SIBLING_DISTANCE + ) + node.mod = node.x - mid + + if not node.is_leaf() and not node.is_most_left(): + _check_for_conflicts(node) + + +func _calculate_final_positions(node: TreeNode, mod_sum: float) -> void: + node.x += mod_sum + mod_sum += node.mod + + for child in node.children: + _calculate_final_positions(child, mod_sum) + + +func _check_all_children_on_screen(node: TreeNode) -> void: + var node_contour: Dictionary = {} + _get_left_contour(node, 0, node_contour) + + var shift_amount: float = 0 + for y in node_contour.keys(): + if node_contour[y] + shift_amount < 0: + shift_amount = (node_contour[y] * -1) + + if shift_amount > 0: + node.x += shift_amount + node.mod += shift_amount + + +func _check_for_conflicts(node: TreeNode) -> void: + var min_distance := SIBLING_DISTANCE + var shift_value: float = 0 + var shift_sibling: TreeNode = null + + var node_contour: Dictionary = {} # { int, float } + _get_left_contour(node, 0, node_contour) + + var sibling := node.get_most_left_sibling() + while sibling != null and sibling != node: + var sibling_contour: Dictionary = {} + _get_right_contour(sibling, 0, sibling_contour) + + for level in range( + node.y + 1, min(sibling_contour.keys().max(), node_contour.keys().max()) + 1 + ): + var distance: float = node_contour[level] - sibling_contour[level] + if distance + shift_value < min_distance: + shift_value = min_distance - distance + shift_sibling = sibling + + sibling = sibling.get_next_sibling() + + if shift_value > 0: + node.x += shift_value + node.mod += shift_value + _center_nodes_between(shift_sibling, node) + + +func _center_nodes_between(left_node: TreeNode, right_node: TreeNode) -> void: + var left_index := left_node.parent.children.find(left_node) + var right_index := left_node.parent.children.find(right_node) + + var num_nodes_between: int = (right_index - left_index) - 1 + if num_nodes_between > 0: + # The extra distance that needs to be split into num_nodes_between + 1 + # in order to find the new node spacing so that nodes are equally spaced + var distance_to_allocate: float = right_node.x - left_node.x - left_node.item.layout_size + # Subtract sizes on nodes in between + for i in range(left_index + 1, right_index): + distance_to_allocate -= left_node.parent.children[i].item.layout_size + # Divide space equally + var distance_between_nodes: float = distance_to_allocate / (num_nodes_between + 1) + + var prev_node := left_node + var middle_node := left_node.get_next_sibling() + while middle_node != right_node: + var desire_x: float = prev_node.x + prev_node.item.layout_size + distance_between_nodes + var offset := desire_x - middle_node.x + middle_node.x += offset + middle_node.mod += offset + prev_node = middle_node + middle_node = middle_node.get_next_sibling() + + +func _get_left_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void: + var node_left: float = node.x + mod_sum + var depth := int(node.y) + if not values.has(depth): + values[depth] = node_left + else: + values[depth] = min(values[depth], node_left) + + mod_sum += node.mod + for child in node.children: + _get_left_contour(child, mod_sum, values) + + +func _get_right_contour(node: TreeNode, mod_sum: float, values: Dictionary) -> void: + var node_right: float = node.x + mod_sum + node.item.layout_size + var depth := int(node.y) + if not values.has(depth): + values[depth] = node_right + else: + values[depth] = max(values[depth], node_right) + + mod_sum += node.mod + for child in node.children: + _get_right_contour(child, mod_sum, values) + + +func _swap_x_y(node: TreeNode) -> void: + for child in node.children: + _swap_x_y(child) + + var temp := node.x + node.x = node.y + node.y = temp + + +func _calculate_x(node: TreeNode, offset: int) -> void: + node.x = offset + var sibling := node.get_most_left_sibling() + var max_size: int = node.item.size.x + while sibling != null: + max_size = max(sibling.item.size.x, max_size) + sibling = sibling.get_next_sibling() + + for child in node.children: + _calculate_x(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale()) + + +func _calculate_y(node: TreeNode, offset: int) -> void: + node.y = offset + var sibling := node.get_most_left_sibling() + var max_size: int = node.item.size.y + while sibling != null: + max_size = max(sibling.item.size.y, max_size) + sibling = sibling.get_next_sibling() + + for child in node.children: + _calculate_y(child, max_size + offset + LEVEL_DISTANCE * BeehaveUtils.get_editor_scale()) diff --git a/godot/addons/beehave/debug/tree_node.gd.uid b/godot/addons/beehave/debug/tree_node.gd.uid new file mode 100644 index 0000000..9b1f539 --- /dev/null +++ b/godot/addons/beehave/debug/tree_node.gd.uid @@ -0,0 +1 @@ +uid://20qccrlr5enw diff --git a/godot/addons/beehave/icons/action.svg b/godot/addons/beehave/icons/action.svg new file mode 100644 index 0000000..3916c89 --- /dev/null +++ b/godot/addons/beehave/icons/action.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/action.svg.import b/godot/addons/beehave/icons/action.svg.import new file mode 100644 index 0000000..7d95d2d --- /dev/null +++ b/godot/addons/beehave/icons/action.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btrq8e0kyxthg" +path="res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/action.svg" +dest_files=["res://.godot/imported/action.svg-e8a91246d0ba9ba3cf84290d65648f06.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/blackboard.svg b/godot/addons/beehave/icons/blackboard.svg new file mode 100644 index 0000000..e4948a5 --- /dev/null +++ b/godot/addons/beehave/icons/blackboard.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/blackboard.svg.import b/godot/addons/beehave/icons/blackboard.svg.import new file mode 100644 index 0000000..40f3ed6 --- /dev/null +++ b/godot/addons/beehave/icons/blackboard.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dw7rom0hiff6c" +path="res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/blackboard.svg" +dest_files=["res://.godot/imported/blackboard.svg-18d4dfd4f6de558de250b67251ff1e69.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/category_bt.svg b/godot/addons/beehave/icons/category_bt.svg new file mode 100644 index 0000000..8be61ae --- /dev/null +++ b/godot/addons/beehave/icons/category_bt.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/category_bt.svg.import b/godot/addons/beehave/icons/category_bt.svg.import new file mode 100644 index 0000000..0c59c01 --- /dev/null +++ b/godot/addons/beehave/icons/category_bt.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qpdd6ue7x82h" +path="res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/category_bt.svg" +dest_files=["res://.godot/imported/category_bt.svg-8537bebd1c5f62dca3d7ee7f17efeed4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/category_composite.svg b/godot/addons/beehave/icons/category_composite.svg new file mode 100644 index 0000000..aa8b866 --- /dev/null +++ b/godot/addons/beehave/icons/category_composite.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/category_composite.svg.import b/godot/addons/beehave/icons/category_composite.svg.import new file mode 100644 index 0000000..44cad8d --- /dev/null +++ b/godot/addons/beehave/icons/category_composite.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://863s568sneja" +path="res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/category_composite.svg" +dest_files=["res://.godot/imported/category_composite.svg-43f66e63a7ccfa5ac8ec6da0583b3246.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/category_decorator.svg b/godot/addons/beehave/icons/category_decorator.svg new file mode 100644 index 0000000..165e3d6 --- /dev/null +++ b/godot/addons/beehave/icons/category_decorator.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/category_decorator.svg.import b/godot/addons/beehave/icons/category_decorator.svg.import new file mode 100644 index 0000000..b37cace --- /dev/null +++ b/godot/addons/beehave/icons/category_decorator.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2ie8m4ddawlb" +path="res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/category_decorator.svg" +dest_files=["res://.godot/imported/category_decorator.svg-79d598d6456f32724156248e09d6eaf3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/category_leaf.svg b/godot/addons/beehave/icons/category_leaf.svg new file mode 100644 index 0000000..1482fe6 --- /dev/null +++ b/godot/addons/beehave/icons/category_leaf.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/category_leaf.svg.import b/godot/addons/beehave/icons/category_leaf.svg.import new file mode 100644 index 0000000..67ca661 --- /dev/null +++ b/godot/addons/beehave/icons/category_leaf.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://eq0sp4g3s75r" +path="res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/category_leaf.svg" +dest_files=["res://.godot/imported/category_leaf.svg-c740ecab6cfae632574ca5e39e46fd2e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/condition.svg b/godot/addons/beehave/icons/condition.svg new file mode 100644 index 0000000..37b2c7a --- /dev/null +++ b/godot/addons/beehave/icons/condition.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/condition.svg.import b/godot/addons/beehave/icons/condition.svg.import new file mode 100644 index 0000000..6b14919 --- /dev/null +++ b/godot/addons/beehave/icons/condition.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ck4toqx0nggiu" +path="res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/condition.svg" +dest_files=["res://.godot/imported/condition.svg-57892684b10a64086f68c09c388b17e5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/cooldown.svg b/godot/addons/beehave/icons/cooldown.svg new file mode 100644 index 0000000..fbdfd6a --- /dev/null +++ b/godot/addons/beehave/icons/cooldown.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/cooldown.svg.import b/godot/addons/beehave/icons/cooldown.svg.import new file mode 100644 index 0000000..46e74a8 --- /dev/null +++ b/godot/addons/beehave/icons/cooldown.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4ybmig18t464" +path="res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/cooldown.svg" +dest_files=["res://.godot/imported/cooldown.svg-2fb8975b5974e35bedad825abb9faf66.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/delayer.svg b/godot/addons/beehave/icons/delayer.svg new file mode 100644 index 0000000..21cb617 --- /dev/null +++ b/godot/addons/beehave/icons/delayer.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/godot/addons/beehave/icons/delayer.svg.import b/godot/addons/beehave/icons/delayer.svg.import new file mode 100644 index 0000000..880dcc5 --- /dev/null +++ b/godot/addons/beehave/icons/delayer.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6jvl1gccuj4c" +path="res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/delayer.svg" +dest_files=["res://.godot/imported/delayer.svg-6f92c97f61b1eb8679428f438e6b08c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/failer.svg b/godot/addons/beehave/icons/failer.svg new file mode 100644 index 0000000..968f7e1 --- /dev/null +++ b/godot/addons/beehave/icons/failer.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/failer.svg.import b/godot/addons/beehave/icons/failer.svg.import new file mode 100644 index 0000000..24862ae --- /dev/null +++ b/godot/addons/beehave/icons/failer.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2fj7htaqvcud" +path="res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/failer.svg" +dest_files=["res://.godot/imported/failer.svg-9a62b840e1eacc0437e7a67b14a302e4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/inverter.svg b/godot/addons/beehave/icons/inverter.svg new file mode 100644 index 0000000..d4e791e --- /dev/null +++ b/godot/addons/beehave/icons/inverter.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/inverter.svg.import b/godot/addons/beehave/icons/inverter.svg.import new file mode 100644 index 0000000..4b57722 --- /dev/null +++ b/godot/addons/beehave/icons/inverter.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cffmoc3og8hux" +path="res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/inverter.svg" +dest_files=["res://.godot/imported/inverter.svg-1f1b976d95de42c4ad99a92fa9a6c5d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/limiter.svg b/godot/addons/beehave/icons/limiter.svg new file mode 100644 index 0000000..7b3fa1d --- /dev/null +++ b/godot/addons/beehave/icons/limiter.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/limiter.svg.import b/godot/addons/beehave/icons/limiter.svg.import new file mode 100644 index 0000000..614f44f --- /dev/null +++ b/godot/addons/beehave/icons/limiter.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7akxvsg0f2by" +path="res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/limiter.svg" +dest_files=["res://.godot/imported/limiter.svg-b4c7646605c46f53c5e403fe21d8f584.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/repeater.svg b/godot/addons/beehave/icons/repeater.svg new file mode 100644 index 0000000..47c46e9 --- /dev/null +++ b/godot/addons/beehave/icons/repeater.svg @@ -0,0 +1,40 @@ + + + + + + + diff --git a/godot/addons/beehave/icons/repeater.svg.import b/godot/addons/beehave/icons/repeater.svg.import new file mode 100644 index 0000000..a58b6eb --- /dev/null +++ b/godot/addons/beehave/icons/repeater.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j551tmw6t4fp" +path="res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/repeater.svg" +dest_files=["res://.godot/imported/repeater.svg-be2d3a7f1a46d7ba1d1939553725f598.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/selector.svg b/godot/addons/beehave/icons/selector.svg new file mode 100644 index 0000000..0ae3b7a --- /dev/null +++ b/godot/addons/beehave/icons/selector.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/selector.svg.import b/godot/addons/beehave/icons/selector.svg.import new file mode 100644 index 0000000..1fd2196 --- /dev/null +++ b/godot/addons/beehave/icons/selector.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2c5d20doh4sp" +path="res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/selector.svg" +dest_files=["res://.godot/imported/selector.svg-78bccfc448bd1676b5a29bfde4b08e5b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/selector_random.svg b/godot/addons/beehave/icons/selector_random.svg new file mode 100644 index 0000000..6f631e9 --- /dev/null +++ b/godot/addons/beehave/icons/selector_random.svg @@ -0,0 +1,35 @@ + + diff --git a/godot/addons/beehave/icons/selector_random.svg.import b/godot/addons/beehave/icons/selector_random.svg.import new file mode 100644 index 0000000..b4d24e0 --- /dev/null +++ b/godot/addons/beehave/icons/selector_random.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmnkcmk7bkdjd" +path="res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/selector_random.svg" +dest_files=["res://.godot/imported/selector_random.svg-d52fea1352c24483ecd9dc8609cf00f3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/selector_reactive.svg b/godot/addons/beehave/icons/selector_reactive.svg new file mode 100644 index 0000000..6db005f --- /dev/null +++ b/godot/addons/beehave/icons/selector_reactive.svg @@ -0,0 +1,45 @@ + + diff --git a/godot/addons/beehave/icons/selector_reactive.svg.import b/godot/addons/beehave/icons/selector_reactive.svg.import new file mode 100644 index 0000000..9e2a781 --- /dev/null +++ b/godot/addons/beehave/icons/selector_reactive.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crkbov0h8sb8l" +path="res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/selector_reactive.svg" +dest_files=["res://.godot/imported/selector_reactive.svg-dd3b8fb8cd2ffe331605aaad1e021cc0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/sequence.svg b/godot/addons/beehave/icons/sequence.svg new file mode 100644 index 0000000..3ebedd9 --- /dev/null +++ b/godot/addons/beehave/icons/sequence.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/sequence.svg.import b/godot/addons/beehave/icons/sequence.svg.import new file mode 100644 index 0000000..c1ab6f8 --- /dev/null +++ b/godot/addons/beehave/icons/sequence.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5gw354thiofm" +path="res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/sequence.svg" +dest_files=["res://.godot/imported/sequence.svg-76e5600611900cc81e9ec286977b8c6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/sequence_random.svg b/godot/addons/beehave/icons/sequence_random.svg new file mode 100644 index 0000000..34e4a12 --- /dev/null +++ b/godot/addons/beehave/icons/sequence_random.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/sequence_random.svg.import b/godot/addons/beehave/icons/sequence_random.svg.import new file mode 100644 index 0000000..bde7dcc --- /dev/null +++ b/godot/addons/beehave/icons/sequence_random.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bat8ptdw5qt1d" +path="res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/sequence_random.svg" +dest_files=["res://.godot/imported/sequence_random.svg-58cee9098c622ef87db941279206422a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/sequence_reactive.svg b/godot/addons/beehave/icons/sequence_reactive.svg new file mode 100644 index 0000000..33d219b --- /dev/null +++ b/godot/addons/beehave/icons/sequence_reactive.svg @@ -0,0 +1,60 @@ + + diff --git a/godot/addons/beehave/icons/sequence_reactive.svg.import b/godot/addons/beehave/icons/sequence_reactive.svg.import new file mode 100644 index 0000000..887d75d --- /dev/null +++ b/godot/addons/beehave/icons/sequence_reactive.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rmiu1slwfkh7" +path="res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/sequence_reactive.svg" +dest_files=["res://.godot/imported/sequence_reactive.svg-7d384ca290f7934adb9e17d9e7116b6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/simple_parallel.svg b/godot/addons/beehave/icons/simple_parallel.svg new file mode 100644 index 0000000..e9c8b00 --- /dev/null +++ b/godot/addons/beehave/icons/simple_parallel.svg @@ -0,0 +1,13 @@ + + simple_parallel + + Layer 1 + + + + + + + + + \ No newline at end of file diff --git a/godot/addons/beehave/icons/simple_parallel.svg.import b/godot/addons/beehave/icons/simple_parallel.svg.import new file mode 100644 index 0000000..c6b21be --- /dev/null +++ b/godot/addons/beehave/icons/simple_parallel.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dy60wsynpm7in" +path="res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/simple_parallel.svg" +dest_files=["res://.godot/imported/simple_parallel.svg-3d4107eaf2e46557f6d3be3249f91430.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/succeeder.svg b/godot/addons/beehave/icons/succeeder.svg new file mode 100644 index 0000000..10f5912 --- /dev/null +++ b/godot/addons/beehave/icons/succeeder.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/succeeder.svg.import b/godot/addons/beehave/icons/succeeder.svg.import new file mode 100644 index 0000000..6ab4093 --- /dev/null +++ b/godot/addons/beehave/icons/succeeder.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dl6wo332kglbe" +path="res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/succeeder.svg" +dest_files=["res://.godot/imported/succeeder.svg-e5cf6f6e04b9b862b82fd2cb479272aa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/tree.svg b/godot/addons/beehave/icons/tree.svg new file mode 100644 index 0000000..6c85ea1 --- /dev/null +++ b/godot/addons/beehave/icons/tree.svg @@ -0,0 +1,38 @@ + + + + + + diff --git a/godot/addons/beehave/icons/tree.svg.import b/godot/addons/beehave/icons/tree.svg.import new file mode 100644 index 0000000..df4a470 --- /dev/null +++ b/godot/addons/beehave/icons/tree.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://deryyg2hbmaaw" +path="res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/tree.svg" +dest_files=["res://.godot/imported/tree.svg-c0b20ed88b2fe300c0296f7236049076.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/icons/until_fail.svg b/godot/addons/beehave/icons/until_fail.svg new file mode 100644 index 0000000..c64a0a0 --- /dev/null +++ b/godot/addons/beehave/icons/until_fail.svg @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/godot/addons/beehave/icons/until_fail.svg.import b/godot/addons/beehave/icons/until_fail.svg.import new file mode 100644 index 0000000..9972295 --- /dev/null +++ b/godot/addons/beehave/icons/until_fail.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dk3sg4lyppgj2" +path="res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/beehave/icons/until_fail.svg" +dest_files=["res://.godot/imported/until_fail.svg-8015014c40e91d9c2668ec34d4118b8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/beehave/metrics/beehave_global_metrics.gd b/godot/addons/beehave/metrics/beehave_global_metrics.gd new file mode 100644 index 0000000..ef29db2 --- /dev/null +++ b/godot/addons/beehave/metrics/beehave_global_metrics.gd @@ -0,0 +1,54 @@ +extends Node + +var _tree_count: int = 0 +var _active_tree_count: int = 0 +var _registered_trees: Array = [] + + +func _enter_tree() -> void: + Performance.add_custom_monitor("beehave/total_trees", _get_total_trees) + Performance.add_custom_monitor("beehave/total_enabled_trees", _get_total_enabled_trees) + + +func register_tree(tree) -> void: + if _registered_trees.has(tree): + return + + _registered_trees.append(tree) + _tree_count += 1 + + if tree.enabled: + _active_tree_count += 1 + + tree.tree_enabled.connect(_on_tree_enabled) + tree.tree_disabled.connect(_on_tree_disabled) + + +func unregister_tree(tree) -> void: + if not _registered_trees.has(tree): + return + + _registered_trees.erase(tree) + _tree_count -= 1 + + if tree.enabled: + _active_tree_count -= 1 + + tree.tree_enabled.disconnect(_on_tree_enabled) + tree.tree_disabled.disconnect(_on_tree_disabled) + + +func _get_total_trees() -> int: + return _tree_count + + +func _get_total_enabled_trees() -> int: + return _active_tree_count + + +func _on_tree_enabled() -> void: + _active_tree_count += 1 + + +func _on_tree_disabled() -> void: + _active_tree_count -= 1 diff --git a/godot/addons/beehave/metrics/beehave_global_metrics.gd.uid b/godot/addons/beehave/metrics/beehave_global_metrics.gd.uid new file mode 100644 index 0000000..3273d56 --- /dev/null +++ b/godot/addons/beehave/metrics/beehave_global_metrics.gd.uid @@ -0,0 +1 @@ +uid://c3ktl6ontsdt7 diff --git a/godot/addons/beehave/nodes/beehave_node.gd b/godot/addons/beehave/nodes/beehave_node.gd new file mode 100644 index 0000000..2817cb3 --- /dev/null +++ b/godot/addons/beehave/nodes/beehave_node.gd @@ -0,0 +1,54 @@ +@tool +class_name BeehaveNode extends Node + +## A node in the behavior tree. Every node must return `SUCCESS`, `FAILURE` or +## `RUNNING` when ticked. + +enum { SUCCESS, FAILURE, RUNNING } + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + if get_children().any(func(x): return not (x is BeehaveNode)): + warnings.append("All children of this node should inherit from BeehaveNode class.") + + return warnings + + +## Executes this node and returns a status code. +## This method must be overwritten. +func tick(actor: Node, blackboard: Blackboard) -> int: + return SUCCESS + + +## Called when this node needs to be interrupted before it can return FAILURE or SUCCESS. +func interrupt(actor: Node, blackboard: Blackboard) -> void: + BeehaveDebuggerMessages.process_interrupt(self.get_instance_id(), blackboard.get_debug_data()) + + +## Called before the first time it ticks by the parent. +func before_run(actor: Node, blackboard: Blackboard) -> void: + pass + + +## Called after the last time it ticks and returns +## [code]SUCCESS[/code] or [code]FAILURE[/code]. +func after_run(actor: Node, blackboard: Blackboard) -> void: + pass + + +func get_class_name() -> Array[StringName]: + return [&"BeehaveNode"] + + +func can_send_message(blackboard: Blackboard) -> bool: + return blackboard.get_value("can_send_message", false) + + +func _safe_tick(actor: Node, blackboard: Blackboard) -> int: + var response = tick(actor, blackboard) + if not response is int: + push_error("All tick methods must return an int, got %s" % response) + return FAILURE + return response diff --git a/godot/addons/beehave/nodes/beehave_node.gd.uid b/godot/addons/beehave/nodes/beehave_node.gd.uid new file mode 100644 index 0000000..4b5360e --- /dev/null +++ b/godot/addons/beehave/nodes/beehave_node.gd.uid @@ -0,0 +1 @@ +uid://c37shq5uf4gk diff --git a/godot/addons/beehave/nodes/beehave_tree.gd b/godot/addons/beehave/nodes/beehave_tree.gd new file mode 100644 index 0000000..3c4fb87 --- /dev/null +++ b/godot/addons/beehave/nodes/beehave_tree.gd @@ -0,0 +1,323 @@ +@tool +@icon("../icons/tree.svg") +class_name BeehaveTree extends Node + +## Controls the flow of execution of the entire behavior tree. + +enum { SUCCESS, FAILURE, RUNNING } + +enum ProcessThread { IDLE, PHYSICS, MANUAL } + +signal tree_enabled +signal tree_disabled + +## Whether this behavior tree should be enabled or not. +@export var enabled: bool = true: + set(value): + enabled = value + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + if value: + tree_enabled.emit() + else: + interrupt() + tree_disabled.emit() + + get: + return enabled + +## How often the tree should tick, in frames. The default value of 1 means +## tick() runs every frame. +@export var tick_rate: int = 1 + +## An optional node path this behavior tree should apply to. +@export_node_path var actor_node_path: NodePath: + set(anp): + actor_node_path = anp + if actor_node_path != null and str(actor_node_path) != "..": + actor = get_node(actor_node_path.get_as_property_path()) + else: + actor = get_parent() + if Engine.is_editor_hint(): + update_configuration_warnings() + +## Whether to run this tree in a physics or idle thread. +@export var process_thread: ProcessThread = ProcessThread.PHYSICS: + set(value): + process_thread = value + self.enabled = self.enabled and process_thread != ProcessThread.MANUAL + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + +## Custom blackboard node. An internal blackboard will be used +## if no blackboard is provided explicitly. +@export var blackboard: Blackboard: + set(b): + blackboard = b + if blackboard and _internal_blackboard: + remove_child(_internal_blackboard) + _internal_blackboard.free() + _internal_blackboard = null + elif not blackboard and not _internal_blackboard: + _internal_blackboard = Blackboard.new() + add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK) + get: + # in case blackboard is accessed before this node is, + # we need to ensure that the internal blackboard is used. + if not blackboard and not _internal_blackboard: + _internal_blackboard = Blackboard.new() + add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK) + return blackboard if blackboard else _internal_blackboard + +## When enabled, this tree is tracked individually +## as a custom monitor. +@export var custom_monitor = false: + set(b): + custom_monitor = b + if custom_monitor and _process_time_metric_name != "": + Performance.add_custom_monitor( + _process_time_metric_name, _get_process_time_metric_value + ) + _get_global_metrics().register_tree(self) + else: + if _process_time_metric_name != "": + # Remove tree metric from the engine + Performance.remove_custom_monitor(_process_time_metric_name) + _get_global_metrics().unregister_tree(self) + + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + +@export var actor: Node: + set(a): + actor = a + if actor == null: + actor = get_parent() + if Engine.is_editor_hint(): + update_configuration_warnings() + +var status: int = -1 +var last_tick: int = -1 + +var _internal_blackboard: Blackboard +var _process_time_metric_name: String +var _process_time_metric_value: float = 0.0 +var _can_send_message: bool = false + + +func _ready() -> void: + var connect_scene_tree_signal = func(signal_name: String, is_added: bool): + if not get_tree().is_connected(signal_name, _on_scene_tree_node_added_removed.bind(is_added)): + get_tree().connect(signal_name, _on_scene_tree_node_added_removed.bind(is_added)) + connect_scene_tree_signal.call("node_added", true) + connect_scene_tree_signal.call("node_removed", false) + + if not process_thread: + process_thread = ProcessThread.PHYSICS + + if not actor: + if actor_node_path: + actor = get_node(actor_node_path) + else: + actor = get_parent() + + if not blackboard: + # invoke setter to auto-initialise the blackboard. + self.blackboard = null + + # Get the name of the parent node name for metric + _process_time_metric_name = ( + "beehave [microseconds]/process_time_%s-%s" % [actor.name, get_instance_id()] + ) + + set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) + set_process(enabled and process_thread == ProcessThread.IDLE) + + # Register custom metric to the engine + if custom_monitor and not Engine.is_editor_hint(): + Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value) + _get_global_metrics().register_tree(self) + + if Engine.is_editor_hint(): + update_configuration_warnings.call_deferred() + else: + # Ensure the local debugger knows about the tree *before* telling the editor. + _get_global_debugger().register_tree(self) + BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)) + + +func _on_scene_tree_node_added_removed(node: Node, is_added: bool) -> void: + if Engine.is_editor_hint(): + return + + if node is BeehaveNode and is_ancestor_of(node): + var sgnal := node.ready if is_added else node.tree_exited + if is_added: + sgnal.connect( + func() -> void: BeehaveDebuggerMessages.register_tree(_get_debugger_data(self)), + CONNECT_ONE_SHOT + ) + else: + sgnal.connect( + func() -> void: + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + request_ready() + ) + + +func _physics_process(_delta: float) -> void: + tick() + + +func _process(_delta: float) -> void: + tick() + + +func tick() -> int: + if Engine.is_editor_hint(): + return -1 + if last_tick != -1 and last_tick < tick_rate - 1: + last_tick += 1 + return -1 + + last_tick = 0 + + # Start timing for metric + var start_time = Time.get_ticks_usec() + blackboard.set_value("can_send_message", _can_send_message) + + if _can_send_message and not Engine.is_editor_hint(): + BeehaveDebuggerMessages.process_begin(get_instance_id(), blackboard.get_debug_data()) + + if actor == null or get_child_count() == 0: + return FAILURE + var child := self.get_child(0) + if status != RUNNING: + child.before_run(actor, blackboard) + + status = child.tick(actor, blackboard) + if _can_send_message: + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status, blackboard.get_debug_data()) + BeehaveDebuggerMessages.process_tick(get_instance_id(), status, blackboard.get_debug_data()) + + # Clear running action if nothing is running + if status != RUNNING: + blackboard.set_value("running_action", null, str(actor.get_instance_id())) + child.after_run(actor, blackboard) + + if _can_send_message and not Engine.is_editor_hint(): + BeehaveDebuggerMessages.process_end(get_instance_id(), blackboard.get_debug_data()) + + # Check the cost for this frame and save it for metric report + _process_time_metric_value = Time.get_ticks_usec() - start_time + + return status + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + if actor == null: + warnings.append("Configure target node on tree") + + if get_children().any(func(x): return not (x is BeehaveNode)): + warnings.append("All children of this node should inherit from BeehaveNode class.") + + if get_child_count() != 1: + warnings.append("BeehaveTree should have exactly one child node.") + + return warnings + + +## Returns the currently running action +func get_running_action() -> ActionLeaf: + return blackboard.get_value("running_action", null, str(actor.get_instance_id())) + + +## Returns the last condition that was executed +func get_last_condition() -> ConditionLeaf: + return blackboard.get_value("last_condition", null, str(actor.get_instance_id())) + + +## Returns the status of the last executed condition +func get_last_condition_status() -> String: + if blackboard.has_value("last_condition_status", str(actor.get_instance_id())): + var status = blackboard.get_value( + "last_condition_status", null, str(actor.get_instance_id()) + ) + if status == SUCCESS: + return "SUCCESS" + elif status == FAILURE: + return "FAILURE" + else: + return "RUNNING" + return "" + + +## interrupts this tree if anything was running +func interrupt() -> void: + if self.get_child_count() != 0: + var first_child = self.get_child(0) + if "interrupt" in first_child: + first_child.interrupt(actor, blackboard) + + +## Enables this tree. +func enable() -> void: + self.enabled = true + + +## Disables this tree. +func disable() -> void: + self.enabled = false + + +func _exit_tree() -> void: + if Engine.is_editor_hint(): + # Skip this when running in editor + return + if custom_monitor: + if _process_time_metric_name != "": + # Remove tree metric from the engine + Performance.remove_custom_monitor(_process_time_metric_name) + _get_global_metrics().unregister_tree(self) + BeehaveDebuggerMessages.unregister_tree(get_instance_id()) + + +# Called by the engine to profile this tree +func _get_process_time_metric_value() -> int: + return int(_process_time_metric_value) + + +func _get_debugger_data(node: Node) -> Dictionary: + if not (node is BeehaveTree or node is BeehaveNode): + return {} + + var data := { + path = node.get_path(), + name = node.name, + type = node.get_class_name(), + id = str(node.get_instance_id()) + } + if node.get_child_count() > 0: + data.children = [] + for child in node.get_children(): + var child_data := _get_debugger_data(child) + if not child_data.is_empty(): + data.children.push_back(child_data) + return data + + +func get_class_name() -> Array[StringName]: + return [&"BeehaveTree"] + + +# required to avoid lifecycle issues on initial load +# due to loading order problems with autoloads +func _get_global_metrics() -> Node: + return get_tree().root.get_node("BeehaveGlobalMetrics") + + +# required to avoid lifecycle issues on initial load +# due to loading order problems with autoloads +func _get_global_debugger() -> Node: + return get_tree().root.get_node("BeehaveGlobalDebugger") diff --git a/godot/addons/beehave/nodes/beehave_tree.gd.uid b/godot/addons/beehave/nodes/beehave_tree.gd.uid new file mode 100644 index 0000000..0e5a153 --- /dev/null +++ b/godot/addons/beehave/nodes/beehave_tree.gd.uid @@ -0,0 +1 @@ +uid://bb0t2ovl7wifo diff --git a/godot/addons/beehave/nodes/composites/composite.gd b/godot/addons/beehave/nodes/composites/composite.gd new file mode 100644 index 0000000..a657292 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/composite.gd @@ -0,0 +1,57 @@ +@tool +@icon("../../icons/category_composite.svg") +class_name Composite extends BeehaveNode + +## A Composite node controls the flow of execution of its children in a specific manner. + +var running_child: BeehaveNode = null + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_children().filter(func(x): return x is BeehaveNode).size() < 2: + warnings.append( + "Any composite node should have at least two children. Otherwise it is not useful." + ) + + return warnings + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + super.interrupt(actor, blackboard) + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + running_child = null + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Composite") + return classes + + +func _cleanup_running(child: Node, actor: Node, blackboard: Blackboard) -> void: + if child == running_child: + running_child = null + var id = str(actor.get_instance_id()) + if child == blackboard.get_value("running_action", null, id): + blackboard.set_value("running_action", null, id) + + +func _interrupt_children(actor: Node, blackboard: Blackboard, from_index: int, last_index: int) -> void: + var children = get_children() + var start = from_index + 1 + var end = 0 + if last_index > from_index: + end = last_index + 1 + else: + return + + for j in range(start, end): + var stale = children[j] + stale.interrupt(actor, blackboard) diff --git a/godot/addons/beehave/nodes/composites/composite.gd.uid b/godot/addons/beehave/nodes/composites/composite.gd.uid new file mode 100644 index 0000000..a7191b4 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/composite.gd.uid @@ -0,0 +1 @@ +uid://fengp7jc44qv diff --git a/godot/addons/beehave/nodes/composites/randomized_composite.gd b/godot/addons/beehave/nodes/composites/randomized_composite.gd new file mode 100644 index 0000000..2afa5e9 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/randomized_composite.gd @@ -0,0 +1,176 @@ +@tool +class_name RandomizedComposite extends Composite + +const WEIGHTS_PREFIX = "Weights/" + +## Sets a predicable seed +@export var random_seed: int = 0: + set(rs): + random_seed = rs + if random_seed != 0: + seed(random_seed) + else: + randomize() + +## Wether to use weights for every child or not. +@export var use_weights: bool: + set(value): + use_weights = value + if use_weights: + _update_weights(get_children()) + _connect_children_changing_signals() + notify_property_list_changed() + +var _weights: Dictionary +var _exiting_tree: bool + + +func _ready(): + _connect_children_changing_signals() + + +func _connect_children_changing_signals(): + if not child_entered_tree.is_connected(_on_child_entered_tree): + child_entered_tree.connect(_on_child_entered_tree) + + if not child_exiting_tree.is_connected(_on_child_exiting_tree): + child_exiting_tree.connect(_on_child_exiting_tree) + + +func get_shuffled_children() -> Array[Node]: + var children_bag: Array[Node] = get_children().duplicate() + if use_weights: + var weights: Array[int] + weights.assign(children_bag.map(func(child): return _weights[child.name])) + children_bag.assign(_weighted_shuffle(children_bag, weights)) + else: + children_bag.shuffle() + return children_bag + + +## Returns a shuffled version of a given array using the supplied array of weights. +## Think of weights as the chance of a given item being the first in the array. +func _weighted_shuffle(items: Array, weights: Array[int]) -> Array: + if len(items) != len(weights): + push_error( + ( + "items and weights size mismatch: expected %d weights, got %d instead." + % [len(items), len(weights)] + ) + ) + return items + + # This method is based on the weighted random sampling algorithm + # by Efraimidis, Spirakis; 2005. This runs in O(n log(n)). + + # For each index, it will calculate random_value^(1/weight). + var chance_calc = func(i): return [i, randf() ** (1.0 / weights[i])] + var random_distribuition = range(len(items)).map(chance_calc) + + # Now we just have to order by the calculated value, descending. + random_distribuition.sort_custom(func(a, b): return a[1] > b[1]) + + return random_distribuition.map(func(dist): return items[dist[0]]) + + +func _get_property_list(): + var properties = [] + + if use_weights: + for key in _weights.keys(): + properties.append( + { + "name": WEIGHTS_PREFIX + key, + "type": TYPE_INT, + "usage": PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "1,100" + } + ) + + return properties + + +func _set(property: StringName, value: Variant) -> bool: + if property.begins_with(WEIGHTS_PREFIX): + var weight_name = property.trim_prefix(WEIGHTS_PREFIX) + _weights[weight_name] = value + return true + + return false + + +func _get(property: StringName): + if property.begins_with(WEIGHTS_PREFIX): + var weight_name = property.trim_prefix(WEIGHTS_PREFIX) + return _weights[weight_name] + + return null + + +func _update_weights(children: Array[Node]) -> void: + var new_weights = {} + for c in children: + if _weights.has(c.name): + new_weights[c.name] = _weights[c.name] + else: + new_weights[c.name] = 1 + _weights = new_weights + notify_property_list_changed() + + +func _exit_tree() -> void: + _exiting_tree = true + + +func _enter_tree() -> void: + _exiting_tree = false + + +func _on_child_entered_tree(node: Node): + _update_weights(get_children()) + + var renamed_callable = _on_child_renamed.bind(node.name, node) + if not node.renamed.is_connected(renamed_callable): + node.renamed.connect(renamed_callable) + + if not node.tree_exited.is_connected(_on_child_tree_exited): + node.tree_exited.connect(_on_child_tree_exited.bind(node)) + + +func _on_child_exiting_tree(node: Node): + var renamed_callable = _on_child_renamed.bind(node.name, node) + if node.renamed.is_connected(renamed_callable): + node.renamed.disconnect(renamed_callable) + + +func _on_child_tree_exited(node: Node) -> void: + # don't erase the individual child if the whole tree is exiting together + if not _exiting_tree: + var children = get_children() + children.erase(node) + _update_weights(children) + + if node.tree_exited.is_connected(_on_child_tree_exited): + node.tree_exited.disconnect(_on_child_tree_exited) + + +func _on_child_renamed(old_name: String, renamed_child: Node): + if old_name == renamed_child.name: + return # No need to update the weights. + + # Disconnect signal with old name... + renamed_child.renamed.disconnect(_on_child_renamed.bind(old_name, renamed_child)) + # ...and connect with the new name. + renamed_child.renamed.connect(_on_child_renamed.bind(renamed_child.name, renamed_child)) + + var original_weight = _weights[old_name] + _weights.erase(old_name) + _weights[renamed_child.name] = original_weight + notify_property_list_changed() + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"RandomizedComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/randomized_composite.gd.uid b/godot/addons/beehave/nodes/composites/randomized_composite.gd.uid new file mode 100644 index 0000000..8bfa216 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/randomized_composite.gd.uid @@ -0,0 +1 @@ +uid://doqgfjw53d1l4 diff --git a/godot/addons/beehave/nodes/composites/selector.gd b/godot/addons/beehave/nodes/composites/selector.gd new file mode 100644 index 0000000..4a59cb3 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector.gd @@ -0,0 +1,102 @@ +@tool +@icon("../../icons/selector.svg") +class_name SelectorComposite extends Composite + + +# A Selector runs its children in order until one succeeds or is running. +# On failure, skips already-processed children across ticks. + + +var last_execution_index: int = 0 +var previous_success_or_running_index: int = -1 +var ready_to_interrupt_all: bool = false + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var children = get_children() + var children_count = children.size() + var processed_count = 0 + + for i in range(children.size()): + var child = children[i] + if child.get_index() < last_execution_index: + processed_count += 1 + continue + + if child != running_child: + child.before_run(actor, blackboard) + + var response = child._safe_tick(actor, blackboard) + processed_count += 1 + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) + + if child is ConditionLeaf: + var id = str(actor.get_instance_id()) + blackboard.set_value("last_condition", child, id) + blackboard.set_value("last_condition_status", response, id) + + match response: + SUCCESS: + if running_child != null: + if running_child != child: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + child.after_run(actor, blackboard) + _interrupt_children(actor, blackboard, i, previous_success_or_running_index) + previous_success_or_running_index = i + ready_to_interrupt_all = false + return SUCCESS + + FAILURE: + if running_child != null and running_child == child: + _cleanup_running(running_child, actor, blackboard) + child.after_run(actor, blackboard) + last_execution_index = max(last_execution_index, child.get_index() + 1) + + RUNNING: + if child != running_child: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + _interrupt_children(actor, blackboard, i, previous_success_or_running_index) + previous_success_or_running_index = i + ready_to_interrupt_all = false + return RUNNING + + # all children failed + ready_to_interrupt_all = (processed_count == children_count) + last_execution_index = 0 + return FAILURE + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + last_execution_index = 0 + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if ready_to_interrupt_all: + # If all children failed, interrupt all children by using indices 0 and children.size()-1 + var children = get_children() + if children.size() > 0: + _interrupt_children(actor, blackboard, -1, children.size() - 1) + ready_to_interrupt_all = false + else: + # Use the normal interrupt logic for partial processing + _interrupt_children(actor, blackboard, last_execution_index, previous_success_or_running_index) + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + last_execution_index = 0 + previous_success_or_running_index = -1 + super(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes = super() + classes.push_back(&"SelectorComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/selector.gd.uid b/godot/addons/beehave/nodes/composites/selector.gd.uid new file mode 100644 index 0000000..ee9656a --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector.gd.uid @@ -0,0 +1 @@ +uid://8hn4kne15ac5 diff --git a/godot/addons/beehave/nodes/composites/selector_random.gd b/godot/addons/beehave/nodes/composites/selector_random.gd new file mode 100644 index 0000000..fd5a28a --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector_random.gd @@ -0,0 +1,85 @@ +@tool +@icon("../../icons/selector_random.svg") +class_name SelectorRandomComposite extends RandomizedComposite + +## This node will attempt to execute all of its children just like a +## [code]SelectorStar[/code] would, with the exception that the children +## will be executed in a random order. + +## A shuffled list of the children that will be executed in reverse order. +var _children_bag: Array[Node] = [] +var c: Node + + +func _ready() -> void: + super() + if random_seed == 0: + randomize() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if _children_bag.is_empty(): + _reset() + + # We need to traverse the array in reverse since we will be manipulating it. + for i in _get_reversed_indexes(): + c = _children_bag[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _children_bag.erase(c) + c.after_run(actor, blackboard) + return SUCCESS + FAILURE: + _children_bag.erase(c) + c.after_run(actor, blackboard) + RUNNING: + if c != running_child: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return FAILURE + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _get_reversed_indexes() -> Array[int]: + var reversed: Array[int] + reversed.assign(range(_children_bag.size())) + reversed.reverse() + return reversed + + +func _reset() -> void: + var new_order = get_shuffled_children() + _children_bag = new_order.duplicate() + _children_bag.reverse() # It needs to run the children in reverse order. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SelectorRandomComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/selector_random.gd.uid b/godot/addons/beehave/nodes/composites/selector_random.gd.uid new file mode 100644 index 0000000..350086c --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector_random.gd.uid @@ -0,0 +1 @@ +uid://dvnmhlldp23hg diff --git a/godot/addons/beehave/nodes/composites/selector_reactive.gd b/godot/addons/beehave/nodes/composites/selector_reactive.gd new file mode 100644 index 0000000..e2f38b5 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector_reactive.gd @@ -0,0 +1,95 @@ +@tool +@icon("../../icons/selector_reactive.svg") +class_name SelectorReactiveComposite extends Composite + +## Selector Reactive nodes will attempt to execute each of its children until one of +## them return `SUCCESS`. If all children return `FAILURE`, this node will also +## return `FAILURE`. +## If a child returns `RUNNING` it will restart. + + + +# Track where we last succeeded – so we detect true branch changes +var previous_success_or_running_index: int = -1 +var ready_to_interrupt_all: bool = false + +func tick(actor: Node, blackboard: Blackboard) -> int: + var children := get_children() + var children_count = children.size() + var processed_count = 0 + + for i in range(children.size()): + var c = children[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + processed_count += 1 + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + # clean up the one that just succeeded + if running_child != null: + if running_child != c: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + c.after_run(actor, blackboard) + + _interrupt_children(actor, blackboard, i, previous_success_or_running_index) + previous_success_or_running_index = i + ready_to_interrupt_all = false + return SUCCESS + + FAILURE: + c.after_run(actor, blackboard) + + RUNNING: + if c != running_child: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + _interrupt_children(actor, blackboard, i, previous_success_or_running_index) + previous_success_or_running_index = i + ready_to_interrupt_all = false + return RUNNING + + # all failed → reset our success‐tracker + ready_to_interrupt_all = (processed_count == children_count) + return FAILURE + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if ready_to_interrupt_all: + # If all children failed, interrupt all children + var children = get_children() + if children.size() > 0: + _interrupt_children(actor, blackboard, -1, children.size() - 1) + ready_to_interrupt_all = false + else: + # Use the normal interrupt logic for partial processing + _interrupt_children(actor, blackboard, -1, previous_success_or_running_index) + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + previous_success_or_running_index = -1 + super(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SelectorReactiveComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/selector_reactive.gd.uid b/godot/addons/beehave/nodes/composites/selector_reactive.gd.uid new file mode 100644 index 0000000..883d250 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/selector_reactive.gd.uid @@ -0,0 +1 @@ +uid://cw22yurt5l74k diff --git a/godot/addons/beehave/nodes/composites/sequence.gd b/godot/addons/beehave/nodes/composites/sequence.gd new file mode 100644 index 0000000..3e00fcd --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence.gd @@ -0,0 +1,92 @@ +@tool +@icon("../../icons/sequence.svg") +class_name SequenceComposite extends Composite + +## Sequence nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and restart. +## In case a child returns `RUNNING` this node will tick again. + +var successful_index: int = 0 +# Track where we last failed – so we detect a backward jump +var previous_failure_or_running_index: int = -1 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var children = get_children() + for i in range(children.size()): + var c = children[i] + if c.get_index() < successful_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + if running_child != null and running_child == c: + # do not interrupt as this child finishes running! + _cleanup_running(running_child, actor, blackboard) + successful_index += 1 + c.after_run(actor, blackboard) + FAILURE: + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + + _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) + + # remember where we failed for next tick + previous_failure_or_running_index = c.get_index() + successful_index = 0 + + # Interrupt any child that was RUNNING before + # but do not reset! + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + if running_child != null and c != running_child: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + if c != running_child: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) + previous_failure_or_running_index = i + return RUNNING + + successful_index = 0 + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _interrupt_children(actor, blackboard, successful_index - 1, previous_failure_or_running_index) + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + _reset() + super(actor, blackboard) + + +func _reset() -> void: + successful_index = 0 + previous_failure_or_running_index = -1 + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/sequence.gd.uid b/godot/addons/beehave/nodes/composites/sequence.gd.uid new file mode 100644 index 0000000..108f546 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence.gd.uid @@ -0,0 +1 @@ +uid://cg016dbe7gs1x diff --git a/godot/addons/beehave/nodes/composites/sequence_random.gd b/godot/addons/beehave/nodes/composites/sequence_random.gd new file mode 100644 index 0000000..1ff08a3 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_random.gd @@ -0,0 +1,99 @@ +@tool +@icon("../../icons/sequence_random.svg") +class_name SequenceRandomComposite extends RandomizedComposite + +## This node will attempt to execute all of its children just like a +## [code]SequenceStar[/code] would, with the exception that the children +## will be executed in a random order. + +# Emitted whenever the children are shuffled. +signal reset(new_order: Array[Node]) + +## Whether the sequence should start where it left off after a previous failure. +@export var resume_on_failure: bool = false +## Whether the sequence should start where it left off after a previous interruption. +@export var resume_on_interrupt: bool = false + +## A shuffled list of the children that will be executed in reverse order. +var _children_bag: Array[Node] = [] +var c: Node + + +func _ready() -> void: + super() + if random_seed == 0: + randomize() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if _children_bag.is_empty(): + _reset() + + # We need to traverse the array in reverse since we will be manipulating it. + for i in _get_reversed_indexes(): + c = _children_bag[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + _children_bag.erase(c) + c.after_run(actor, blackboard) + FAILURE: + _children_bag.erase(c) + # Interrupt any child that was RUNNING before + # but do not reset! + super.interrupt(actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + + return SUCCESS + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + if not resume_on_failure: + _reset() + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if not resume_on_interrupt: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + _reset() + super(actor, blackboard) + + +func _get_reversed_indexes() -> Array[int]: + var reversed: Array[int] + reversed.assign(range(_children_bag.size())) + reversed.reverse() + return reversed + + +func _reset() -> void: + var new_order: Array[Node] = get_shuffled_children() + _children_bag = new_order.duplicate() + _children_bag.reverse() # It needs to run the children in reverse order. + reset.emit(new_order) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceRandomComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/sequence_random.gd.uid b/godot/addons/beehave/nodes/composites/sequence_random.gd.uid new file mode 100644 index 0000000..5961178 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_random.gd.uid @@ -0,0 +1 @@ +uid://b5iop640q3aue diff --git a/godot/addons/beehave/nodes/composites/sequence_reactive.gd b/godot/addons/beehave/nodes/composites/sequence_reactive.gd new file mode 100644 index 0000000..0c6df90 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_reactive.gd @@ -0,0 +1,81 @@ +@tool +@icon("../../icons/sequence_reactive.svg") +class_name SequenceReactiveComposite extends Composite + +## Reactive Sequence nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and restart. +## In case a child returns `RUNNING` this node will restart. + +# Track where we last failed – so we detect a backward jump +var previous_failure_index: int = -1 +# Separate index for running as failure and running can diverge in reactive sequence +var previous_running_index: int = -1 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var children = get_children() + for i in range(children.size()): + var c = children[i] + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + if running_child != null and running_child == c: + # do not interrupt as this child finishes running! + _cleanup_running(running_child, actor, blackboard) + c.after_run(actor, blackboard) + FAILURE: + _interrupt_children(actor, blackboard, i, previous_failure_index) + + # remember where we failed for next tick + previous_failure_index = c.get_index() + + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + _reset() + if running_child != null and running_child != c: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + _interrupt_children(actor, blackboard, i, previous_running_index) + previous_running_index = i + return RUNNING + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _interrupt_children(actor, blackboard, -1, previous_running_index if previous_running_index > previous_failure_index else previous_failure_index) + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + _reset() + super(actor, blackboard) + + +func _reset() -> void: + previous_failure_index = -1 + previous_running_index = -1 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceReactiveComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/sequence_reactive.gd.uid b/godot/addons/beehave/nodes/composites/sequence_reactive.gd.uid new file mode 100644 index 0000000..774959f --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_reactive.gd.uid @@ -0,0 +1 @@ +uid://dcojdhvj8qcw0 diff --git a/godot/addons/beehave/nodes/composites/sequence_star.gd b/godot/addons/beehave/nodes/composites/sequence_star.gd new file mode 100644 index 0000000..22b3f86 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_star.gd @@ -0,0 +1,86 @@ +@tool +@icon("../../icons/sequence_reactive.svg") +class_name SequenceStarComposite extends Composite + +## Sequence Star nodes will attempt to execute all of its children and report +## `SUCCESS` in case all of the children report a `SUCCESS` status code. +## If at least one child reports a `FAILURE` status code, this node will also +## return `FAILURE` and tick again. +## In case a child returns `RUNNING` this node will tick again. + +var successful_index: int = 0 +# Track where we last failed – so we detect a backward jump +var previous_failure_or_running_index: int = -1 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var children = get_children() + for i in range(children.size()): + var c = children[i] + if c.get_index() < successful_index: + continue + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + if running_child != null and running_child == c: + # do not interrupt as this child finishes running! + _cleanup_running(running_child, actor, blackboard) + successful_index += 1 + c.after_run(actor, blackboard) + FAILURE: + _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) + + # remember where we failed for next tick + previous_failure_or_running_index = c.get_index() + + # Interrupt any child that was RUNNING before + # but do not reset! + if running_child != null: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + + c.after_run(actor, blackboard) + return FAILURE + RUNNING: + if running_child != null and running_child != c: + running_child.interrupt(actor, blackboard) + _cleanup_running(running_child, actor, blackboard) + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + _interrupt_children(actor, blackboard, i, previous_failure_or_running_index) + previous_failure_or_running_index = i + return RUNNING + successful_index = 0 + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + _interrupt_children(actor, blackboard, successful_index - 1, previous_failure_or_running_index) + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + _reset() + super(actor, blackboard) + + +func _reset() -> void: + successful_index = 0 + previous_failure_or_running_index = -1 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SequenceStarComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/sequence_star.gd.uid b/godot/addons/beehave/nodes/composites/sequence_star.gd.uid new file mode 100644 index 0000000..9425e65 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/sequence_star.gd.uid @@ -0,0 +1 @@ +uid://bbaojjriq676j diff --git a/godot/addons/beehave/nodes/composites/simple_parallel.gd b/godot/addons/beehave/nodes/composites/simple_parallel.gd new file mode 100644 index 0000000..addd62e --- /dev/null +++ b/godot/addons/beehave/nodes/composites/simple_parallel.gd @@ -0,0 +1,119 @@ +@tool +@icon("../../icons/simple_parallel.svg") +class_name SimpleParallelComposite extends Composite + +## Simple Parallel nodes will attampt to execute all chidren at same time and +## can only have exactly two children. First child as primary node, second +## child as secondary node. +## This node will always report primary node's state, and continue tick while +## primary node return 'RUNNING'. The state of secondary node will be ignored +## and executed like a subtree. +## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt +## secondary node and return primary node's result. +## If this node is running under delay mode, it will wait seconday node +## finish its action after primary node terminates. + +#how many times should secondary node repeat, zero means loop forever +@export var secondary_node_repeat_count: int = 0 + +#wether to wait secondary node finish its current action after primary node finished +@export var delay_mode: bool = false + +var delayed_result := SUCCESS +var main_task_finished: bool = false +var secondary_node_running: bool = false +var secondary_node_repeat_left: int = 0 + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_child_count() != 2: + warnings.append("SimpleParallel should have exactly two child nodes.") + + return warnings + + +func tick(actor, blackboard: Blackboard): + for c in get_children(): + var node_index: int = c.get_index() + if node_index == 0 and not main_task_finished: + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + delayed_result = response + match response: + SUCCESS, FAILURE: + _cleanup_running_task(c, actor, blackboard) + c.after_run(actor, blackboard) + main_task_finished = true + if not delay_mode: + if secondary_node_running: + get_child(1).interrupt(actor, blackboard) + _reset() + return delayed_result + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + + elif node_index == 1: + if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0: + if not secondary_node_running: + c.before_run(actor, blackboard) + var subtree_response = c._safe_tick(actor, blackboard) + if subtree_response != RUNNING: + secondary_node_running = false + c.after_run(actor, blackboard) + if delay_mode and main_task_finished: + _reset() + return delayed_result + elif secondary_node_repeat_left > 0: + secondary_node_repeat_left -= 1 + else: + secondary_node_running = true + + return RUNNING + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + secondary_node_repeat_left = secondary_node_repeat_count + super(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if not main_task_finished: + get_child(0).interrupt(actor, blackboard) + if secondary_node_running: + get_child(1).interrupt(actor, blackboard) + _reset() + super(actor, blackboard) + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + _reset() + super(actor, blackboard) + + +func _reset() -> void: + main_task_finished = false + secondary_node_running = false + + +## Changes `running_action` and `running_child` after the node finishes executing. +func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard): + var blackboard_name = str(actor.get_instance_id()) + if finished_action == running_child: + running_child = null + if finished_action == blackboard.get_value("running_action", null, blackboard_name): + blackboard.set_value("running_action", null, blackboard_name) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"SimpleParallelComposite") + return classes diff --git a/godot/addons/beehave/nodes/composites/simple_parallel.gd.uid b/godot/addons/beehave/nodes/composites/simple_parallel.gd.uid new file mode 100644 index 0000000..d4c8be3 --- /dev/null +++ b/godot/addons/beehave/nodes/composites/simple_parallel.gd.uid @@ -0,0 +1 @@ +uid://bkr7qj44253ue diff --git a/godot/addons/beehave/nodes/decorators/cooldown.gd b/godot/addons/beehave/nodes/decorators/cooldown.gd new file mode 100644 index 0000000..1cbf663 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/cooldown.gd @@ -0,0 +1,55 @@ +@tool +@icon("../../icons/cooldown.svg") +extends Decorator +class_name CooldownDecorator + +## The Cooldown Decorator will return 'FAILURE' for a set amount of time +## after executing its child. +## The timer resets the next time its child is executed and it is not `RUNNING` +## or when the node is interrupted (such as when the behavior tree changes branches). + +## The wait time in seconds +@export var wait_time := 0.0 + +@onready var cache_key = "cooldown_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + var remaining_time: float = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + var response: int + + if c != running_child: + c.before_run(actor, blackboard) + + if remaining_time > 0: + response = FAILURE + + remaining_time -= get_physics_process_delta_time() + blackboard.set_value(cache_key, remaining_time, str(actor.get_instance_id())) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response, blackboard.get_debug_data()) + else: + response = c._safe_tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + else: + c.after_run(actor, blackboard) + blackboard.set_value(cache_key, wait_time, str(actor.get_instance_id())) + + return response + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + # Reset the cooldown when the branch changes + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + super.interrupt(actor, blackboard) diff --git a/godot/addons/beehave/nodes/decorators/cooldown.gd.uid b/godot/addons/beehave/nodes/decorators/cooldown.gd.uid new file mode 100644 index 0000000..df9fcbc --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/cooldown.gd.uid @@ -0,0 +1 @@ +uid://2qri6rrfv8ui diff --git a/godot/addons/beehave/nodes/decorators/decorator.gd b/godot/addons/beehave/nodes/decorators/decorator.gd new file mode 100644 index 0000000..4c85f08 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/decorator.gd @@ -0,0 +1,34 @@ +@tool +@icon("../../icons/category_decorator.svg") +class_name Decorator extends BeehaveNode + +## Decorator nodes are used to transform the result received by its child. +## Must only have one child. + +var running_child: BeehaveNode = null + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = super._get_configuration_warnings() + + if get_child_count() != 1: + warnings.append("Decorator should have exactly one child node.") + + return warnings + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + if running_child != null: + running_child.interrupt(actor, blackboard) + running_child = null + super.interrupt(actor, blackboard) + + +func after_run(actor: Node, blackboard: Blackboard) -> void: + running_child = null + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Decorator") + return classes diff --git a/godot/addons/beehave/nodes/decorators/decorator.gd.uid b/godot/addons/beehave/nodes/decorators/decorator.gd.uid new file mode 100644 index 0000000..4076b03 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/decorator.gd.uid @@ -0,0 +1 @@ +uid://kfhcykujhf38 diff --git a/godot/addons/beehave/nodes/decorators/delayer.gd b/godot/addons/beehave/nodes/decorators/delayer.gd new file mode 100644 index 0000000..41b4321 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/delayer.gd @@ -0,0 +1,57 @@ +@tool +@icon("../../icons/delayer.svg") +extends Decorator +class_name DelayDecorator + +## The Delay Decorator will return 'RUNNING' for a set amount of time +## before executing its child. +## The timer resets when both it and its child are not `RUNNING` +## or when the node is interrupted (such as when the behavior tree changes branches). + +## The wait time in seconds +@export var wait_time := 0.0 + +@onready var cache_key = "time_limiter_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + var total_time: float = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + var response: int + + if c != running_child: + c.before_run(actor, blackboard) + + if total_time < wait_time: + response = RUNNING + + total_time += get_physics_process_delta_time() + blackboard.set_value(cache_key, total_time, str(actor.get_instance_id())) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(self.get_instance_id(), response, blackboard.get_debug_data()) + else: + response = c._safe_tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + else: + c.after_run(actor, blackboard) + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + + return response + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + # Reset the delay timer when the branch changes + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + + super(actor, blackboard) diff --git a/godot/addons/beehave/nodes/decorators/delayer.gd.uid b/godot/addons/beehave/nodes/decorators/delayer.gd.uid new file mode 100644 index 0000000..f5ddfc0 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/delayer.gd.uid @@ -0,0 +1 @@ +uid://dorri1tul8gfx diff --git a/godot/addons/beehave/nodes/decorators/failer.gd b/godot/addons/beehave/nodes/decorators/failer.gd new file mode 100644 index 0000000..934bc71 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/failer.gd @@ -0,0 +1,35 @@ +@tool +@icon("../../icons/failer.svg") +class_name AlwaysFailDecorator extends Decorator + +## A Failer node will always return a `FAILURE` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + else: + c.after_run(actor, blackboard) + return FAILURE + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"AlwaysFailDecorator") + return classes diff --git a/godot/addons/beehave/nodes/decorators/failer.gd.uid b/godot/addons/beehave/nodes/decorators/failer.gd.uid new file mode 100644 index 0000000..65b2ccc --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/failer.gd.uid @@ -0,0 +1 @@ +uid://dwfdg523bk776 diff --git a/godot/addons/beehave/nodes/decorators/inverter.gd b/godot/addons/beehave/nodes/decorators/inverter.gd new file mode 100644 index 0000000..5f97fea --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/inverter.gd @@ -0,0 +1,43 @@ +@tool +@icon("../../icons/inverter.svg") +class_name InverterDecorator extends Decorator + +## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status +## code or `SUCCESS` in case its child returns a `FAILURE` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + match response: + SUCCESS: + c.after_run(actor, blackboard) + return FAILURE + FAILURE: + c.after_run(actor, blackboard) + return SUCCESS + RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + _: + push_error("This should be unreachable") + return -1 + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"InverterDecorator") + return classes diff --git a/godot/addons/beehave/nodes/decorators/inverter.gd.uid b/godot/addons/beehave/nodes/decorators/inverter.gd.uid new file mode 100644 index 0000000..0404ed0 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/inverter.gd.uid @@ -0,0 +1 @@ +uid://crkjak4kyv56m diff --git a/godot/addons/beehave/nodes/decorators/limiter.gd b/godot/addons/beehave/nodes/decorators/limiter.gd new file mode 100644 index 0000000..a51ab89 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/limiter.gd @@ -0,0 +1,77 @@ +@tool +@icon("../../icons/limiter.svg") +class_name LimiterDecorator extends Decorator + +## The limiter will execute its `RUNNING` child `x` amount of times. When the number of +## maximum ticks is reached, it will return a `FAILURE` status code. +## The count resets the next time that a child is not `RUNNING` +## or when the node is interrupted (such as when the behavior tree changes branches). + +@onready var cache_key = "limiter_%s" % self.get_instance_id() + +@export var max_count: int = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if not get_child_count() == 1: + return FAILURE + + var child: BeehaveNode = get_child(0) + var current_count: int = blackboard.get_value(cache_key, 0, str(actor.get_instance_id())) + + if current_count < max_count: + blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id())) + var response: int = child.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + else: + # If the child is no longer running, reset the counter for next time + _reset_counter(actor, blackboard) + child.after_run(actor, blackboard) + + return response + else: + interrupt(actor, blackboard) + child.after_run(actor, blackboard) + return FAILURE + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + # Initialize the counter to 0 when we first start running + _reset_counter(actor, blackboard) + if get_child_count() > 0: + get_child(0).before_run(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + # The tree is changing branches, so the count should reset + _reset_counter(actor, blackboard) + + # Call super, which may affect our blackboard values + super(actor, blackboard) + + +# Resets the counter in the blackboard +func _reset_counter(actor: Node, blackboard: Blackboard) -> void: + blackboard.set_value(cache_key, 0, str(actor.get_instance_id())) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"LimiterDecorator") + return classes + + +func _get_configuration_warnings() -> PackedStringArray: + if not get_child_count() == 1: + return ["Requires exactly one child node"] + return [] diff --git a/godot/addons/beehave/nodes/decorators/limiter.gd.uid b/godot/addons/beehave/nodes/decorators/limiter.gd.uid new file mode 100644 index 0000000..2444c44 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/limiter.gd.uid @@ -0,0 +1 @@ +uid://crjqmr7expgtl diff --git a/godot/addons/beehave/nodes/decorators/repeater.gd b/godot/addons/beehave/nodes/decorators/repeater.gd new file mode 100644 index 0000000..1cf43fa --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/repeater.gd @@ -0,0 +1,66 @@ +## The repeater will execute its child until it returns `SUCCESS` a certain amount of times. +## When the number of maximum ticks is reached, it will return a `SUCCESS` status code. +## If the child returns `FAILURE`, the repeater will return `FAILURE` immediately. +## The counter resets when the node is interrupted (such as when the behavior tree changes branches). +@tool +@icon("../../icons/repeater.svg") +class_name RepeaterDecorator extends Decorator + +@export var repetitions: int = 1 +var current_count: int = 0 + + +func before_run(actor: Node, blackboard: Blackboard): + current_count = 0 + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var child: BeehaveNode = get_child(0) + + if current_count < repetitions: + if running_child == null: + child.before_run(actor, blackboard) + + var response: int = child.tick(actor, blackboard) + + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + return RUNNING + + current_count += 1 + child.after_run(actor, blackboard) + + if running_child != null: + running_child = null + + if response == FAILURE: + return FAILURE + + if current_count >= repetitions: + return SUCCESS + + return RUNNING + else: + return SUCCESS + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + # Reset the internal counter when the node is interrupted + current_count = 0 + + super(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"LimiterDecorator") + return classes diff --git a/godot/addons/beehave/nodes/decorators/repeater.gd.uid b/godot/addons/beehave/nodes/decorators/repeater.gd.uid new file mode 100644 index 0000000..b14f01e --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/repeater.gd.uid @@ -0,0 +1 @@ +uid://c5v7wuqn1xvdy diff --git a/godot/addons/beehave/nodes/decorators/succeeder.gd b/godot/addons/beehave/nodes/decorators/succeeder.gd new file mode 100644 index 0000000..ba52f63 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/succeeder.gd @@ -0,0 +1,35 @@ +@tool +@icon("../../icons/succeeder.svg") +class_name AlwaysSucceedDecorator extends Decorator + +## A succeeder node will always return a `SUCCESS` status code. + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + else: + c.after_run(actor, blackboard) + return SUCCESS + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"AlwaysSucceedDecorator") + return classes diff --git a/godot/addons/beehave/nodes/decorators/succeeder.gd.uid b/godot/addons/beehave/nodes/decorators/succeeder.gd.uid new file mode 100644 index 0000000..f3ae405 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/succeeder.gd.uid @@ -0,0 +1 @@ +uid://dsf3a8vlolhx8 diff --git a/godot/addons/beehave/nodes/decorators/time_limiter.gd b/godot/addons/beehave/nodes/decorators/time_limiter.gd new file mode 100644 index 0000000..bcc7ca8 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/time_limiter.gd @@ -0,0 +1,68 @@ +@tool +@icon("../../icons/limiter.svg") +class_name TimeLimiterDecorator extends Decorator + +## The Time Limit Decorator will give its `RUNNING` child a set amount of time to finish +## before interrupting it and return a `FAILURE` status code. +## The timer resets the next time that a child is not `RUNNING` +## or when the node is interrupted (such as when the behavior tree changes branches). + +@export var wait_time := 0.0 + +@onready var cache_key: String = "time_limiter_%s" % self.get_instance_id() + + +func tick(actor: Node, blackboard: Blackboard) -> int: + if not get_child_count() == 1: + return FAILURE + + var child: BeehaveNode = self.get_child(0) + var time_left: float = blackboard.get_value(cache_key, 0.0, str(actor.get_instance_id())) + + if time_left < wait_time: + time_left += get_physics_process_delta_time() + blackboard.set_value(cache_key, time_left, str(actor.get_instance_id())) + var response: int = child.tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response, blackboard.get_debug_data()) + + if child is ConditionLeaf: + blackboard.set_value("last_condition", child, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = child + if child is ActionLeaf: + blackboard.set_value("running_action", child, str(actor.get_instance_id())) + else: + child.after_run(actor, blackboard) + return response + else: + interrupt(actor, blackboard) + child.after_run(actor, blackboard) + return FAILURE + + +func before_run(actor: Node, blackboard: Blackboard) -> void: + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + if get_child_count() > 0: + get_child(0).before_run(actor, blackboard) + + +func interrupt(actor: Node, blackboard: Blackboard) -> void: + # Reset the timer when the node is interrupted + blackboard.set_value(cache_key, 0.0, str(actor.get_instance_id())) + + super(actor, blackboard) + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"TimeLimiterDecorator") + return classes + + +func _get_configuration_warnings() -> PackedStringArray: + if not get_child_count() == 1: + return ["Requires exactly one child node"] + return [] diff --git a/godot/addons/beehave/nodes/decorators/time_limiter.gd.uid b/godot/addons/beehave/nodes/decorators/time_limiter.gd.uid new file mode 100644 index 0000000..99fbeca --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/time_limiter.gd.uid @@ -0,0 +1 @@ +uid://we22430vcb44 diff --git a/godot/addons/beehave/nodes/decorators/until_fail.gd b/godot/addons/beehave/nodes/decorators/until_fail.gd new file mode 100644 index 0000000..55646a3 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/until_fail.gd @@ -0,0 +1,35 @@ +@tool +@icon("../../icons/until_fail.svg") +class_name UntilFailDecorator +extends Decorator + +## The UntilFail Decorator will return `RUNNING` if its child returns +## `SUCCESS` or `RUNNING` or it will return `SUCCESS` if its child returns +## `FAILURE` + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var c: BeehaveNode = get_child(0) + + if c != running_child: + c.before_run(actor, blackboard) + + var response: int = c._safe_tick(actor, blackboard) + if can_send_message(blackboard): + BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response, blackboard.get_debug_data()) + + if c is ConditionLeaf: + blackboard.set_value("last_condition", c, str(actor.get_instance_id())) + blackboard.set_value("last_condition_status", response, str(actor.get_instance_id())) + + if response == RUNNING: + running_child = c + if c is ActionLeaf: + blackboard.set_value("running_action", c, str(actor.get_instance_id())) + return RUNNING + if response == SUCCESS: + c.after_run(actor, blackboard) + return RUNNING + + c.after_run(actor, blackboard) + return SUCCESS diff --git a/godot/addons/beehave/nodes/decorators/until_fail.gd.uid b/godot/addons/beehave/nodes/decorators/until_fail.gd.uid new file mode 100644 index 0000000..008f0d7 --- /dev/null +++ b/godot/addons/beehave/nodes/decorators/until_fail.gd.uid @@ -0,0 +1 @@ +uid://c84st521ytmk3 diff --git a/godot/addons/beehave/nodes/leaves/action.gd b/godot/addons/beehave/nodes/leaves/action.gd new file mode 100644 index 0000000..9074c07 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/action.gd @@ -0,0 +1,14 @@ +@tool +@icon("../../icons/action.svg") +class_name ActionLeaf extends Leaf + +## Actions are leaf nodes that define a task to be performed by an actor. +## Their execution can be long running, potentially being called across multiple +## frame executions. In this case, the node should return `RUNNING` until the +## action is completed. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"ActionLeaf") + return classes diff --git a/godot/addons/beehave/nodes/leaves/action.gd.uid b/godot/addons/beehave/nodes/leaves/action.gd.uid new file mode 100644 index 0000000..1bb43ae --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/action.gd.uid @@ -0,0 +1 @@ +uid://cv74jjdrwevpr diff --git a/godot/addons/beehave/nodes/leaves/blackboard_compare.gd b/godot/addons/beehave/nodes/leaves/blackboard_compare.gd new file mode 100644 index 0000000..38a870d --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_compare.gd @@ -0,0 +1,65 @@ +@tool +class_name BlackboardCompareCondition extends ConditionLeaf + +## Compares two values using the specified comparison operator. +## Returns [code]FAILURE[/code] if any of the expression fails or the +## comparison operation returns [code]false[/code], otherwise it returns [code]SUCCESS[/code]. + +enum Operators { + EQUAL, + NOT_EQUAL, + GREATER, + LESS, + GREATER_EQUAL, + LESS_EQUAL, +} + +## Expression represetning left operand. +## This value can be any valid GDScript expression. +## In order to use the existing blackboard keys for comparison, +## use get_value("key_name") e.g. get_value("direction").length() +@export_placeholder(EXPRESSION_PLACEHOLDER) var left_operand: String = "" +## Comparison operator. +@export_enum("==", "!=", ">", "<", ">=", "<=") var operator: int = 0 +## Expression represetning right operand. +## This value can be any valid GDScript expression. +## In order to use the existing blackboard keys for comparison, +## use get_value("key_name") e.g. get_value("direction").length() +@export_placeholder(EXPRESSION_PLACEHOLDER) var right_operand: String = "" + +@onready var _left_expression: Expression = _parse_expression(left_operand) +@onready var _right_expression: Expression = _parse_expression(right_operand) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var left: Variant = _left_expression.execute([], blackboard) + + if _left_expression.has_execute_failed(): + return FAILURE + + var right: Variant = _right_expression.execute([], blackboard) + + if _right_expression.has_execute_failed(): + return FAILURE + + var result: bool = false + + match operator: + Operators.EQUAL: + result = left == right + Operators.NOT_EQUAL: + result = left != right + Operators.GREATER: + result = left > right + Operators.LESS: + result = left < right + Operators.GREATER_EQUAL: + result = left >= right + Operators.LESS_EQUAL: + result = left <= right + + return SUCCESS if result else FAILURE + + +func _get_expression_sources() -> Array[String]: + return [left_operand, right_operand] diff --git a/godot/addons/beehave/nodes/leaves/blackboard_compare.gd.uid b/godot/addons/beehave/nodes/leaves/blackboard_compare.gd.uid new file mode 100644 index 0000000..d7d0d26 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_compare.gd.uid @@ -0,0 +1 @@ +uid://uoy6r3dbnq25 diff --git a/godot/addons/beehave/nodes/leaves/blackboard_erase.gd b/godot/addons/beehave/nodes/leaves/blackboard_erase.gd new file mode 100644 index 0000000..e5cc1d4 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_erase.gd @@ -0,0 +1,25 @@ +@tool +class_name BlackboardEraseAction extends ActionLeaf + +## Erases the specified key from the blackboard. +## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + blackboard.erase_value(key_value) + + return SUCCESS + + +func _get_expression_sources() -> Array[String]: + return [key] diff --git a/godot/addons/beehave/nodes/leaves/blackboard_erase.gd.uid b/godot/addons/beehave/nodes/leaves/blackboard_erase.gd.uid new file mode 100644 index 0000000..622ab8d --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_erase.gd.uid @@ -0,0 +1 @@ +uid://r8b0istslwhq diff --git a/godot/addons/beehave/nodes/leaves/blackboard_has.gd b/godot/addons/beehave/nodes/leaves/blackboard_has.gd new file mode 100644 index 0000000..2ee5e92 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_has.gd @@ -0,0 +1,23 @@ +@tool +class_name BlackboardHasCondition extends ConditionLeaf + +## Returns [code]FAILURE[/code] if expression execution fails or the specified key doesn't exist. +## Returns [code]SUCCESS[/code] if blackboard has the specified key. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + return SUCCESS if blackboard.has_value(key_value) else FAILURE + + +func _get_expression_sources() -> Array[String]: + return [key] diff --git a/godot/addons/beehave/nodes/leaves/blackboard_has.gd.uid b/godot/addons/beehave/nodes/leaves/blackboard_has.gd.uid new file mode 100644 index 0000000..a672db6 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_has.gd.uid @@ -0,0 +1 @@ +uid://dd8lrjdy885gg diff --git a/godot/addons/beehave/nodes/leaves/blackboard_set.gd b/godot/addons/beehave/nodes/leaves/blackboard_set.gd new file mode 100644 index 0000000..4f0ed9f --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_set.gd @@ -0,0 +1,33 @@ +@tool +class_name BlackboardSetAction extends ActionLeaf + +## Sets the specified key to the specified value. +## Returns [code]FAILURE[/code] if expression execution fails, otherwise [code]SUCCESS[/code]. + +## Expression representing a blackboard key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var key: String = "" +## Expression representing a blackboard value to assign to the specified key. +@export_placeholder(EXPRESSION_PLACEHOLDER) var value: String = "" + +@onready var _key_expression: Expression = _parse_expression(key) +@onready var _value_expression: Expression = _parse_expression(value) + + +func tick(actor: Node, blackboard: Blackboard) -> int: + var key_value: Variant = _key_expression.execute([], blackboard) + + if _key_expression.has_execute_failed(): + return FAILURE + + var value_value: Variant = _value_expression.execute([], blackboard) + + if _value_expression.has_execute_failed(): + return FAILURE + + blackboard.set_value(key_value, value_value) + + return SUCCESS + + +func _get_expression_sources() -> Array[String]: + return [key, value] diff --git a/godot/addons/beehave/nodes/leaves/blackboard_set.gd.uid b/godot/addons/beehave/nodes/leaves/blackboard_set.gd.uid new file mode 100644 index 0000000..8f9f07c --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/blackboard_set.gd.uid @@ -0,0 +1 @@ +uid://cvq483a337v6s diff --git a/godot/addons/beehave/nodes/leaves/condition.gd b/godot/addons/beehave/nodes/leaves/condition.gd new file mode 100644 index 0000000..f4610b4 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/condition.gd @@ -0,0 +1,12 @@ +@tool +@icon("../../icons/condition.svg") +class_name ConditionLeaf extends Leaf + +## Conditions are leaf nodes that either return SUCCESS or FAILURE depending on +## a single simple condition. They should never return `RUNNING`. + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"ConditionLeaf") + return classes diff --git a/godot/addons/beehave/nodes/leaves/condition.gd.uid b/godot/addons/beehave/nodes/leaves/condition.gd.uid new file mode 100644 index 0000000..26451db --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/condition.gd.uid @@ -0,0 +1 @@ +uid://cbnbvj8prv63i diff --git a/godot/addons/beehave/nodes/leaves/leaf.gd b/godot/addons/beehave/nodes/leaves/leaf.gd new file mode 100644 index 0000000..4946c7d --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/leaf.gd @@ -0,0 +1,48 @@ +@tool +@icon("../../icons/category_leaf.svg") +class_name Leaf extends BeehaveNode + +## Base class for all leaf nodes of the tree. + +const EXPRESSION_PLACEHOLDER: String = "Insert an expression..." + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray = [] + + var children: Array[Node] = get_children() + + if children.any(func(x): return x is BeehaveNode): + warnings.append("Leaf nodes should not have any child nodes. They won't be ticked.") + + for source in _get_expression_sources(): + var error_text: String = _parse_expression(source).get_error_text() + if not error_text.is_empty(): + warnings.append("Expression `%s` is invalid! Error text: `%s`" % [source, error_text]) + + return warnings + + +func _parse_expression(source: String) -> Expression: + var result: Expression = Expression.new() + var error: int = result.parse(source) + + if not Engine.is_editor_hint() and error != OK: + push_error( + ( + "[Leaf] Couldn't parse expression with source: `%s` Error text: `%s`" + % [source, result.get_error_text()] + ) + ) + + return result + + +func _get_expression_sources() -> Array[String]: # virtual + return [] + + +func get_class_name() -> Array[StringName]: + var classes := super() + classes.push_back(&"Leaf") + return classes diff --git a/godot/addons/beehave/nodes/leaves/leaf.gd.uid b/godot/addons/beehave/nodes/leaves/leaf.gd.uid new file mode 100644 index 0000000..be9b695 --- /dev/null +++ b/godot/addons/beehave/nodes/leaves/leaf.gd.uid @@ -0,0 +1 @@ +uid://t88253ohwyv1 diff --git a/godot/addons/beehave/plugin.cfg b/godot/addons/beehave/plugin.cfg new file mode 100644 index 0000000..f5354ee --- /dev/null +++ b/godot/addons/beehave/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Beehave" +description="🐝 Behavior Tree addon for Godot Engine" +author="bitbrain" +version="2.9.1" +script="plugin.gd" diff --git a/godot/addons/beehave/plugin.gd b/godot/addons/beehave/plugin.gd new file mode 100644 index 0000000..c6805af --- /dev/null +++ b/godot/addons/beehave/plugin.gd @@ -0,0 +1,40 @@ +@tool +extends EditorPlugin + +const BeehaveEditorDebugger := preload("debug/debugger.gd") +var editor_debugger: BeehaveEditorDebugger +var frames: RefCounted + + +func _init(): + name = "BeehavePlugin" + add_autoload_singleton("BeehaveGlobalMetrics", "metrics/beehave_global_metrics.gd") + add_autoload_singleton("BeehaveGlobalDebugger", "debug/global_debugger.gd") + + # Add project settings + if not ProjectSettings.has_setting("beehave/debugger/start_detached"): + ProjectSettings.set_setting("beehave/debugger/start_detached", false) + ProjectSettings.set_initial_value("beehave/debugger/start_detached", false) + ProjectSettings.add_property_info({ + "name": "beehave/debugger/start_detached", + "type": TYPE_BOOL, + "hint": PROPERTY_HINT_NONE, + "hint_string": "If enabled, the debugger will start in a separate window", + "usage": PROPERTY_USAGE_DEFAULT + }) + ProjectSettings.save() + + print("Beehave initialized!") + + +func _enter_tree() -> void: + editor_debugger = BeehaveEditorDebugger.new() + if Engine.get_version_info().minor >= 2: + frames = preload("debug/new_frames.gd").new() + else: + frames = preload("debug/old_frames.gd").new() + add_debugger_plugin(editor_debugger) + + +func _exit_tree() -> void: + remove_debugger_plugin(editor_debugger) diff --git a/godot/addons/beehave/plugin.gd.uid b/godot/addons/beehave/plugin.gd.uid new file mode 100644 index 0000000..5bd9030 --- /dev/null +++ b/godot/addons/beehave/plugin.gd.uid @@ -0,0 +1 @@ +uid://cpmewlew40rut diff --git a/godot/addons/beehave/utils/utils.gd b/godot/addons/beehave/utils/utils.gd new file mode 100644 index 0000000..5f51ce7 --- /dev/null +++ b/godot/addons/beehave/utils/utils.gd @@ -0,0 +1,21 @@ +@tool + + +static func get_plugin() -> EditorPlugin: + var tree: SceneTree = Engine.get_main_loop() + return tree.get_root().get_child(0).get_node_or_null("BeehavePlugin") + + +static func get_editor_scale() -> float: + var plugin := get_plugin() + if plugin: + return plugin.get_editor_interface().get_editor_scale() + return 1.0 + + +static func get_frames() -> RefCounted: + var plugin := get_plugin() + if plugin: + return plugin.frames + push_error("Can't find Beehave Plugin") + return null diff --git a/godot/addons/beehave/utils/utils.gd.uid b/godot/addons/beehave/utils/utils.gd.uid new file mode 100644 index 0000000..96a2f8a --- /dev/null +++ b/godot/addons/beehave/utils/utils.gd.uid @@ -0,0 +1 @@ +uid://b7s2xntekg47c diff --git a/godot/addons/better-terrain/BetterTerrain.cs b/godot/addons/better-terrain/BetterTerrain.cs new file mode 100644 index 0000000..31f1677 --- /dev/null +++ b/godot/addons/better-terrain/BetterTerrain.cs @@ -0,0 +1,258 @@ +using Godot; +using Godot.Collections; + +#nullable disable + +/* + +This is a lightweight wrapper for Better Terrain in C#. + +It is not a C# implementation, it merely provides a type safe interface to access +the BetterTerrain autoload from C#. If you are not using Godot in C#, you can ignore +this file. + +The interface is created for a specific tilemap node, which it uses to locate the +autoload, and to fill in as a parameter to simplify all the subsequent calls. +Very simple example: + +``` + BetterTerrain betterTerrain; + + public override void _Ready() + { + TileMapLayer tileMapLayer = GetNode("TileMapLayer"); + betterTerrain = new BetterTerrain(tm); + + var coordinates = new Vector2I(0, 0); + betterTerrain.SetCell(coordinates, 1); + betterTerrain.UpdateTerrainCell(coordinates); + } +``` + +The functions available are the same as BetterTerrain's, though the TileMapLayer or +TileSet parameters are automatically filled in. The help is not duplicated here, +refer to the GDScript version for specifics. + +*/ + +public class BetterTerrain +{ + public enum TerrainType + { + MatchTiles = 0, + MatchVertices = 1, + Category = 2, + Decoration = 3 + } + + public enum SymmetryType + { + None = 0, + Mirror = 1, // Horizontally mirror + Flip = 2, // Vertically flip + Reflect = 3, // All four reflections + RotateClockwise = 4, + RotateCounterClockwise = 5, + Rotate180 = 6, + RotateAll = 7, // All four rotated forms + All = 8 // All rotated and reflected forms + } + + private static readonly NodePath nodePath = new("/root/BetterTerrain"); + private readonly Node betterTerrain; + private readonly TileMapLayer tileMapLayer; + + public BetterTerrain(TileMapLayer tileMapLayer) + { + this.tileMapLayer = tileMapLayer; + betterTerrain = tileMapLayer.GetNode(nodePath); + } + + public Array> GetTerrainCategories() + { + return (Array>)betterTerrain.Call(MethodName.GetTerrainCategories, tileMapLayer.TileSet); + } + + public bool AddTerrain(string name, Color color, TerrainType type, Array categories = null, Godot.Collections.Dictionary icon = null) + { + categories ??= new Array(); + icon ??= new Godot.Collections.Dictionary(); + return (bool)betterTerrain.Call(MethodName.AddTerrain, tileMapLayer.TileSet, name, color, (int)type, categories, icon); + } + + public bool RemoveTerrain(int index) + { + return (bool)betterTerrain.Call(MethodName.RemoveTerrain, tileMapLayer.TileSet, index); + } + + public int TerrainCount() + { + return (int)betterTerrain.Call(MethodName.TerrainCount, tileMapLayer.TileSet); + } + + public Godot.Collections.Dictionary GetTerrain(int index) + { + return (Godot.Collections.Dictionary)betterTerrain.Call(MethodName.GetTerrain, tileMapLayer.TileSet, index); + } + + public bool SetTerrain(int index, string name, Color color, TerrainType type, Array categories = null, Godot.Collections.Dictionary icon = null) + { + categories ??= new Array(); + icon ??= new Godot.Collections.Dictionary(); + return (bool)betterTerrain.Call(MethodName.SetTerrain, tileMapLayer.TileSet, index, name, color, (int)type, categories, icon); + } + + public bool SwapTerrains(int index1, int index2) + { + return (bool)betterTerrain.Call(MethodName.SwapTerrains, tileMapLayer.TileSet, index1, index2); + } + + public bool SetTileTerrainType(TileData tileData, int type) + { + return (bool)betterTerrain.Call(MethodName.SetTileTerrainType, tileMapLayer.TileSet, tileData, type); + } + + public int GetTileTerrainType(TileData tileData) + { + return (int)betterTerrain.Call(MethodName.GetTileTerrainType, tileData); + } + + public bool SetTileSymmetryType(TileData tileData, SymmetryType type) + { + return (bool)betterTerrain.Call(MethodName.SetTileSymmetryType, tileMapLayer.TileSet, tileData, (int)type); + } + + public SymmetryType GetTileSymmetryType(TileData tileData) + { + return (SymmetryType)(int)betterTerrain.Call(MethodName.GetTileSymmetryType, tileData); + } + + public Array GetTilesInTerrain(int type) + { + return (Array)betterTerrain.Call(MethodName.GetTilesInTerrain, tileMapLayer.TileSet, type); + } + + public Array> GetTileSourcesInTerrain(int type) + { + return (Array>)betterTerrain.Call(MethodName.GetTileSourcesInTerrain, tileMapLayer.TileSet, type); + } + + public bool AddTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type) + { + return (bool)betterTerrain.Call(MethodName.AddTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type); + } + + public bool RemoveTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type) + { + return (bool)betterTerrain.Call(MethodName.RemoveTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type); + } + + public Array TilePeeringKeys(TileData tileData) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringKeys, tileData); + } + + public Array TilePeeringTypes(TileData tileData, TileSet.CellNeighbor peering) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringTypes, tileData, (int)peering); + } + + public Array TilePeeringForType(TileData tileData, int type) + { + return (Array)betterTerrain.Call(MethodName.TilePeeringForType, tileData, type); + } + + public bool SetCell(Vector2I coordinate, int type) + { + return (bool)betterTerrain.Call(MethodName.SetCell, tileMapLayer, coordinate, type); + } + + public bool SetCells(Array coordinates, int type) + { + return (bool)betterTerrain.Call(MethodName.SetCells, tileMapLayer, coordinates, type); + } + + public bool ReplaceCell(Vector2I coordinate, int type) + { + return (bool)betterTerrain.Call(MethodName.ReplaceCell, tileMapLayer, coordinate, type); + } + + public bool ReplaceCells(Array coordinates, int type) + { + return (bool)betterTerrain.Call(MethodName.ReplaceCells, tileMapLayer, coordinates, type); + } + + public int GetCell(Vector2I coordinate) + { + return (int)betterTerrain.Call(MethodName.GetCell, tileMapLayer, coordinate); + } + + public void UpdateTerrainCells(Array cells, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainCells, tileMapLayer, cells, updateSurroundingCells); + } + + public void UpdateTerrainCell(Vector2I cell, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainCell, tileMapLayer, cell, updateSurroundingCells); + } + + public void UpdateTerrainArea(Rect2I area, bool updateSurroundingCells = true) + { + betterTerrain.Call(MethodName.UpdateTerrainArea, tileMapLayer, area, updateSurroundingCells); + } + + public Godot.Collections.Dictionary CreateTerrainChangeset(Godot.Collections.Dictionary paint) + { + return (Godot.Collections.Dictionary)betterTerrain.Call(MethodName.CreateTerrainChangeset, tileMapLayer, paint); + } + + public bool IsTerrainChangesetReady(Godot.Collections.Dictionary changeset) + { + return (bool)betterTerrain.Call(MethodName.IsTerrainChangesetReady, changeset); + } + + public void WaitForTerrainChangeset(Godot.Collections.Dictionary changeset) + { + betterTerrain.Call(MethodName.WaitForTerrainChangeset, changeset); + } + + public void ApplyTerrainChangeset(Godot.Collections.Dictionary changeset) + { + betterTerrain.Call(MethodName.ApplyTerrainChangeset, changeset); + } + + private static class MethodName + { + public static readonly StringName GetTerrainCategories = "get_terrain_categories"; + public static readonly StringName AddTerrain = "add_terrain"; + public static readonly StringName RemoveTerrain = "remove_terrain"; + public static readonly StringName TerrainCount = "terrain_count"; + public static readonly StringName GetTerrain = "get_terrain"; + public static readonly StringName SetTerrain = "set_terrain"; + public static readonly StringName SwapTerrains = "swap_terrains"; + public static readonly StringName SetTileTerrainType = "set_tile_terrain_type"; + public static readonly StringName GetTileTerrainType = "get_tile_terrain_type"; + public static readonly StringName SetTileSymmetryType = "set_tile_symmetry_type"; + public static readonly StringName GetTileSymmetryType = "get_tile_symmetry_type"; + public static readonly StringName GetTilesInTerrain = "get_tiles_in_terrain"; + public static readonly StringName GetTileSourcesInTerrain = "get_tile_sources_in_terrain"; + public static readonly StringName AddTilePeeringType = "add_tile_peering_type"; + public static readonly StringName RemoveTilePeeringType = "remove_tile_peering_type"; + public static readonly StringName TilePeeringKeys = "tile_peering_keys"; + public static readonly StringName TilePeeringTypes = "tile_peering_types"; + public static readonly StringName TilePeeringForType = "tile_peering_for_type"; + public static readonly StringName SetCell = "set_cell"; + public static readonly StringName SetCells = "set_cells"; + public static readonly StringName ReplaceCell = "replace_cell"; + public static readonly StringName ReplaceCells = "replace_cells"; + public static readonly StringName GetCell = "get_cell"; + public static readonly StringName UpdateTerrainCells = "update_terrain_cells"; + public static readonly StringName UpdateTerrainCell = "update_terrain_cell"; + public static readonly StringName UpdateTerrainArea = "update_terrain_area"; + public static readonly StringName CreateTerrainChangeset = "create_terrain_changeset"; + public static readonly StringName IsTerrainChangesetReady = "is_terrain_changeset_ready"; + public static readonly StringName WaitForTerrainChangeset = "wait_for_terrain_changeset"; + public static readonly StringName ApplyTerrainChangeset = "apply_terrain_changeset"; + } +} diff --git a/godot/addons/better-terrain/BetterTerrain.gd b/godot/addons/better-terrain/BetterTerrain.gd new file mode 100644 index 0000000..8c53711 --- /dev/null +++ b/godot/addons/better-terrain/BetterTerrain.gd @@ -0,0 +1,1160 @@ +@tool +extends Node + +## A [TileMapLayer] terrain / auto-tiling system. +## +## This is a drop-in replacement for Godot 4's tilemap terrain system, offering +## more versatile and straightforward autotiling. It can be used with any +## existing [TileMapLayer] or [TileSet], either through the editor plugin, or +## directly via code. +## [br][br] +## The [b]BetterTerrain[/b] class contains only static functions, each of which +## either takes a [TileMapLayer], a [TileSet], and sometimes a [TileData]. +## Meta-data is embedded inside the [TileSet] and the [TileData] types to store +## the terrain information. See [method Object.get_meta] for information. +## [br][br] +## Once terrain is set up, it can be written to the tilemap using [method set_cells]. +## Similar to Godot 3.x, setting the cells does not run the terrain solver, so once +## the cells have been set, you need to call an update function such as [method update_terrain_cells]. + + +## The meta-data key used to store terrain information. +const TERRAIN_META = &"_better_terrain" + +## The current version. Used to handle future upgrades. +const TERRAIN_SYSTEM_VERSION = "0.2" + +var _tile_cache = {} +var rng = RandomNumberGenerator.new() +var use_seed := true + +## A helper class that provides functions detailing valid peering bits and +## polygons for different tile types. +var data := load("res://addons/better-terrain/BetterTerrainData.gd"): + get: + return data + +enum TerrainType { + MATCH_TILES, ## Selects tiles by matching against adjacent tiles. + MATCH_VERTICES, ## Select tiles by analysing vertices, similar to wang-style tiles. + CATEGORY, ## Declares a matching type for more sophisticated rules. + DECORATION, ## Fills empty tiles by matching adjacent tiles + MAX, +} + +enum TileCategory { + EMPTY = -1, ## An empty cell, or a tile marked as decoration + NON_TERRAIN = -2, ## A non-empty cell that does not contain a terrain tile + ERROR = -3 +} + +enum SymmetryType { + NONE, + MIRROR, ## Horizontally mirror + FLIP, ## Vertically flip + REFLECT, ## All four reflections + ROTATE_CLOCKWISE, + ROTATE_COUNTER_CLOCKWISE, + ROTATE_180, + ROTATE_ALL, ## All four rotated forms + ALL ## All rotated and reflected forms +} + + +func _intersect(first: Array, second: Array) -> bool: + if first.size() > second.size(): + return _intersect(second, first) # Array 'has' is fast compared to gdscript loop + for f in first: + if second.has(f): + return true + return false + + +# Meta-data functions + +func _get_terrain_meta(ts: TileSet) -> Dictionary: + return ts.get_meta(TERRAIN_META) if ts and ts.has_meta(TERRAIN_META) else { + terrains = [], + decoration = ["Decoration", Color.DIM_GRAY, TerrainType.DECORATION, [], {path = "res://addons/better-terrain/icons/Decoration.svg"}], + version = TERRAIN_SYSTEM_VERSION + } + + +func _set_terrain_meta(ts: TileSet, meta : Dictionary) -> void: + ts.set_meta(TERRAIN_META, meta) + ts.emit_changed() + + +func _get_tile_meta(td: TileData) -> Dictionary: + return td.get_meta(TERRAIN_META) if td.has_meta(TERRAIN_META) else { + type = TileCategory.NON_TERRAIN + } + + +func _set_tile_meta(ts: TileSet, td: TileData, meta) -> void: + td.set_meta(TERRAIN_META, meta) + ts.emit_changed() + + +func _get_cache(ts: TileSet) -> Array: + if _tile_cache.has(ts): + return _tile_cache[ts] + + var cache := [] + if !ts: + return cache + _tile_cache[ts] = cache + + var watcher = Node.new() + watcher.set_script(load("res://addons/better-terrain/Watcher.gd")) + watcher.tileset = ts + watcher.trigger.connect(_purge_cache.bind(ts)) + add_child(watcher) + ts.changed.connect(watcher.activate) + + var types = {} + + var ts_meta := _get_terrain_meta(ts) + for t in ts_meta.terrains.size(): + var terrain = ts_meta.terrains[t] + var bits = terrain[3].duplicate() + bits.push_back(t) + types[t] = bits + cache.push_back([]) + + # Decoration + types[-1] = [TileCategory.EMPTY] + cache.push_back([[-1, Vector2.ZERO, -1, {}, 1.0]]) + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + source.changed.connect(watcher.activate) + for c in source.get_tiles_count(): + var coord := source.get_tile_id(c) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + var td_meta := _get_tile_meta(td) + if td_meta.type < TileCategory.EMPTY or td_meta.type >= cache.size(): + continue + + td.changed.connect(watcher.activate) + var peering := {} + for key in td_meta.keys(): + if !(key is int): + continue + + var targets := [] + for k in types: + if _intersect(types[k], td_meta[key]): + targets.push_back(k) + + peering[key] = targets + + # Decoration tiles without peering are skipped + if td_meta.type == TileCategory.EMPTY and !peering: + continue + + var symmetry = td_meta.get("symmetry", SymmetryType.NONE) + # Branch out no symmetry tiles early + if symmetry == SymmetryType.NONE: + cache[td_meta.type].push_back([source_id, coord, alternate, peering, td.probability]) + continue + + # calculate the symmetry order for this tile + var symmetry_order := 0 + for flags in data.symmetry_mapping[symmetry]: + var symmetric_peering = data.peering_bits_after_symmetry(peering, flags) + if symmetric_peering == peering: + symmetry_order += 1 + + var adjusted_probability = td.probability / symmetry_order + for flags in data.symmetry_mapping[symmetry]: + var symmetric_peering = data.peering_bits_after_symmetry(peering, flags) + cache[td_meta.type].push_back([source_id, coord, alternate | flags, symmetric_peering, adjusted_probability]) + + return cache + + +func _get_cache_terrain(ts_meta : Dictionary, index: int) -> Array: + # the cache and the terrains in ts_meta don't line up because + # decorations are cached too + if index < 0 or index >= ts_meta.terrains.size(): + return ts_meta.decoration + return ts_meta.terrains[index] + + +func _purge_cache(ts: TileSet) -> void: + _tile_cache.erase(ts) + for c in get_children(): + if c.tileset == ts: + c.tidy() + break + + +func _clear_invalid_peering_types(ts: TileSet) -> void: + var ts_meta := _get_terrain_meta(ts) + + var cache := _get_cache(ts) + for t in cache.size(): + var type = _get_cache_terrain(ts_meta, t)[2] + var valid_peering_types = data.get_terrain_peering_cells(ts, type) + + for c in cache[t]: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + if !source: + continue + var td := source.get_tile_data(c[1], c[2]) + var td_meta := _get_tile_meta(td) + + for peering in c[3].keys(): + if valid_peering_types.has(peering): + continue + td_meta.erase(peering) + + _set_tile_meta(ts, td, td_meta) + + # Not strictly necessary + _purge_cache(ts) + + +func _has_invalid_peering_types(ts: TileSet) -> bool: + var ts_meta := _get_terrain_meta(ts) + + var cache := _get_cache(ts) + for t in cache.size(): + var type = _get_cache_terrain(ts_meta, t)[2] + var valid_peering_types = data.get_terrain_peering_cells(ts, type) + + for c in cache[t]: + for peering in c[3].keys(): + if !valid_peering_types.has(peering): + return true + + return false + + +func _update_terrain_data(ts: TileSet) -> void: + var ts_meta = _get_terrain_meta(ts) + var previous_version = ts_meta.get("version") + + # First release: no version info + if !ts_meta.has("version"): + ts_meta["version"] = "0.0" + + # 0.0 -> 0.1: add categories + if ts_meta.version == "0.0": + for t in ts_meta.terrains: + if t.size() == 3: + t.push_back([]) + ts_meta.version = "0.1" + + # 0.1 -> 0.2: add decoration tiles and terrain icons + if ts_meta.version == "0.1": + # Add terrain icon containers + for t in ts_meta.terrains: + if t.size() == 4: + t.push_back({}) + + # Add default decoration data + ts_meta["decoration"] = ["Decoration", Color.DIM_GRAY, TerrainType.DECORATION, [], {path = "res://addons/better-terrain/icons/Decoration.svg"}] + ts_meta.version = "0.2" + + if previous_version != ts_meta.version: + _set_terrain_meta(ts, ts_meta) + + +func _weighted_selection(choices: Array, apply_empty_probability: bool): + if choices.is_empty(): + return null + + var weight = choices.reduce(func(a, c): return a + c[4], 0.0) + + if apply_empty_probability and weight < 1.0 and rng.randf() > weight: + return [-1, Vector2.ZERO, -1, null, 1.0] + + if choices.size() == 1: + return choices[0] + + if weight == 0.0: + return choices[rng.randi() % choices.size()] + + var pick = rng.randf() * weight + for c in choices: + if pick < c[4]: + return c + pick -= c[4] + return choices.back() + + +func _weighted_selection_seeded(choices: Array, coord: Vector2i, apply_empty_probability: bool): + if use_seed: + rng.seed = hash(coord) + return _weighted_selection(choices, apply_empty_probability) + + +func _update_tile_tiles(tm: TileMapLayer, coord: Vector2i, types: Dictionary, cache: Array, apply_empty_probability: bool): + var type = types[coord] + + const reward := 3 + var penalty := -2000 if apply_empty_probability else -10 + + var best_score := -1000 # Impossibly bad score + var best := [] + for t in cache[type]: + var score := 0 + for peering in t[3]: + score += reward if t[3][peering].has(types[tm.get_neighbor_cell(coord, peering)]) else penalty + + if score > best_score: + best_score = score + best = [t] + elif score == best_score: + best.append(t) + + return _weighted_selection_seeded(best, coord, apply_empty_probability) + + +func _probe(tm: TileMapLayer, coord: Vector2i, peering: int, type: int, types: Dictionary) -> int: + var targets = data.associated_vertex_cells(tm, coord, peering) + targets = targets.map(func(c): return types[c]) + + var first = targets[0] + if targets.all(func(t): return t == first): + return first + + # if different, use the lowest non-same + targets = targets.filter(func(t): return t != type) + return targets.reduce(func(a, t): return min(a, t)) + + +func _update_tile_vertices(tm: TileMapLayer, coord: Vector2i, types: Dictionary, cache: Array): + var type = types[coord] + + const reward := 3 + const penalty := -10 + + var best_score := -1000 # Impossibly bad score + var best := [] + for t in cache[type]: + var score := 0 + for peering in t[3]: + score += reward if _probe(tm, coord, peering, type, types) in t[3][peering] else penalty + + if score > best_score: + best_score = score + best = [t] + elif score == best_score: + best.append(t) + + return _weighted_selection_seeded(best, coord, false) + + +func _update_tile_immediate(tm: TileMapLayer, coord: Vector2i, ts_meta: Dictionary, types: Dictionary, cache: Array) -> void: + var type = types[coord] + if type < TileCategory.EMPTY or type >= ts_meta.terrains.size(): + return + + var placement + var terrain = _get_cache_terrain(ts_meta, type) + if terrain[2] in [TerrainType.MATCH_TILES, TerrainType.DECORATION]: + placement = _update_tile_tiles(tm, coord, types, cache, terrain[2] == TerrainType.DECORATION) + elif terrain[2] == TerrainType.MATCH_VERTICES: + placement = _update_tile_vertices(tm, coord, types, cache) + else: + return + + if placement: + tm.set_cell(coord, placement[0], placement[1], placement[2]) + + +func _update_tile_deferred(tm: TileMapLayer, coord: Vector2i, ts_meta: Dictionary, types: Dictionary, cache: Array): + var type = types[coord] + if type >= TileCategory.EMPTY and type < ts_meta.terrains.size(): + var terrain = _get_cache_terrain(ts_meta, type) + if terrain[2] in [TerrainType.MATCH_TILES, TerrainType.DECORATION]: + return _update_tile_tiles(tm, coord, types, cache, terrain[2] == TerrainType.DECORATION) + elif terrain[2] == TerrainType.MATCH_VERTICES: + return _update_tile_vertices(tm, coord, types, cache) + return null + + +func _widen(tm: TileMapLayer, coords: Array) -> Array: + var result := {} + var peering_neighbors = data.get_terrain_peering_cells(tm.tile_set, TerrainType.MATCH_TILES) + for c in coords: + result[c] = true + var neighbors = data.neighboring_coords(tm, c, peering_neighbors) + for t in neighbors: + result[t] = true + return result.keys() + + +func _widen_with_exclusion(tm: TileMapLayer, coords: Array, exclusion: Rect2i) -> Array: + var result := {} + var peering_neighbors = data.get_terrain_peering_cells(tm.tile_set, TerrainType.MATCH_TILES) + for c in coords: + if !exclusion.has_point(c): + result[c] = true + var neighbors = data.neighboring_coords(tm, c, peering_neighbors) + for t in neighbors: + if !exclusion.has_point(t): + result[t] = true + return result.keys() + +# Terrains + +## Returns an [Array] of categories. These are the terrains in the [TileSet] which +## are marked with [enum TerrainType] of [code]CATEGORY[/code]. Each entry in the +## array is a [Dictionary] with [code]name[/code], [code]color[/code], and [code]id[/code]. +func get_terrain_categories(ts: TileSet) -> Array: + var result := [] + if !ts: + return result + + var ts_meta := _get_terrain_meta(ts) + for id in ts_meta.terrains.size(): + var t = ts_meta.terrains[id] + if t[2] == TerrainType.CATEGORY: + result.push_back({name = t[0], color = t[1], id = id}) + + return result + + +## Adds a new terrain to the [TileSet]. Returns [code]true[/code] if this is successful. +## [br][br] +## [code]type[/code] must be one of [enum TerrainType].[br] +## [code]categories[/code] is an indexed list of terrain categories that this terrain +## can match as. The indexes must be valid terrains of the CATEGORY type. +## [code]icon[/code] is a [Dictionary] with either a [code]path[/code] string pointing +## to a resource, or a [code]source_id[/code] [int] and a [code]coord[/code] [Vector2i]. +## The former takes priority if both are present. +func add_terrain(ts: TileSet, name: String, color: Color, type: int, categories: Array = [], icon: Dictionary = {}) -> bool: + if !ts or name.is_empty() or type < 0 or type == TerrainType.DECORATION or type >= TerrainType.MAX: + return false + + var ts_meta := _get_terrain_meta(ts) + + # check categories + if type == TerrainType.CATEGORY and !categories.is_empty(): + return false + for c in categories: + if c < 0 or c >= ts_meta.terrains.size() or ts_meta.terrains[c][2] != TerrainType.CATEGORY: + return false + + if icon and not (icon.has("path") or (icon.has("source_id") and icon.has("coord"))): + return false + + ts_meta.terrains.push_back([name, color, type, categories, icon]) + _set_terrain_meta(ts, ts_meta) + _purge_cache(ts) + return true + + +## Removes the terrain at [code]index[/code] from the [TileSet]. Returns [code]true[/code] +## if the deletion is successful. +func remove_terrain(ts: TileSet, index: int) -> bool: + if !ts or index < 0: + return false + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return false + + if ts_meta.terrains[index][2] == TerrainType.CATEGORY: + for t in ts_meta.terrains: + t[3].erase(index) + + for s in ts.get_source_count(): + var source := ts.get_source(ts.get_source_id(s)) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + continue + + if td_meta.type == index: + _set_tile_meta(ts, td, null) + continue + + if td_meta.type > index: + td_meta.type -= 1 + + for peering in td_meta.keys(): + if !(peering is int): + continue + + var fixed_peering = [] + for p in td_meta[peering]: + if p < index: + fixed_peering.append(p) + elif p > index: + fixed_peering.append(p - 1) + + if fixed_peering.is_empty(): + td_meta.erase(peering) + else: + td_meta[peering] = fixed_peering + + _set_tile_meta(ts, td, td_meta) + + ts_meta.terrains.remove_at(index) + _set_terrain_meta(ts, ts_meta) + + _purge_cache(ts) + return true + + +## Returns the number of terrains in the [TileSet]. +func terrain_count(ts: TileSet) -> int: + if !ts: + return 0 + + var ts_meta := _get_terrain_meta(ts) + return ts_meta.terrains.size() + + +## Retrieves information about the terrain at [code]index[/code] in the [TileSet]. +## [br][br] +## Returns a [Dictionary] describing the terrain. If it succeeds, the key [code]valid[/code] +## will be set to [code]true[/code]. Other keys are [code]name[/code], [code]color[/code], +## [code]type[/code] (a [enum TerrainType]), [code]categories[/code] which is +## an [Array] of category type terrains that this terrain matches as, and +## [code]icon[/code] which is a [Dictionary] with a [code]path[/code] [String] or +## a [code]source_id[/code] [int] and [code]coord[/code] [Vector2i] +func get_terrain(ts: TileSet, index: int) -> Dictionary: + if !ts or index < TileCategory.EMPTY: + return {valid = false} + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return {valid = false} + + var terrain := _get_cache_terrain(ts_meta, index) + return { + id = index, + name = terrain[0], + color = terrain[1], + type = terrain[2], + categories = terrain[3].duplicate(), + icon = terrain[4].duplicate(), + valid = true + } + + +## Updates the details of the terrain at [code]index[/code] in [TileSet]. Returns +## [code]true[/code] if this succeeds. +## [br][br] +## If supplied, the [code]categories[/code] must be a list of indexes to other [code]CATEGORY[/code] +## type terrains. +## [code]icon[/code] is a [Dictionary] with either a [code]path[/code] string pointing +## to a resource, or a [code]source_id[/code] [int] and a [code]coord[/code] [Vector2i]. +func set_terrain(ts: TileSet, index: int, name: String, color: Color, type: int, categories: Array = [], icon: Dictionary = {valid = false}) -> bool: + if !ts or name.is_empty() or index < 0 or type < 0 or type == TerrainType.DECORATION or type >= TerrainType.MAX: + return false + + var ts_meta := _get_terrain_meta(ts) + if index >= ts_meta.terrains.size(): + return false + + if type == TerrainType.CATEGORY and !categories.is_empty(): + return false + for c in categories: + if c < 0 or c == index or c >= ts_meta.terrains.size() or ts_meta.terrains[c][2] != TerrainType.CATEGORY: + return false + + var icon_valid = icon.get("valid", "true") + if icon_valid: + match icon: + {}, {"path"}, {"source_id", "coord"}: pass + _: return false + + if type != TerrainType.CATEGORY: + for t in ts_meta.terrains: + t[3].erase(index) + + ts_meta.terrains[index] = [name, color, type, categories, icon] + _set_terrain_meta(ts, ts_meta) + + _clear_invalid_peering_types(ts) + _purge_cache(ts) + return true + + +## Swaps the terrains at [code]index1[/code] and [code]index2[/code] in [TileSet]. +func swap_terrains(ts: TileSet, index1: int, index2: int) -> bool: + if !ts or index1 < 0 or index2 < 0 or index1 == index2: + return false + + var ts_meta := _get_terrain_meta(ts) + if index1 >= ts_meta.terrains.size() or index2 >= ts_meta.terrains.size(): + return false + + for t in ts_meta.terrains: + var has1 = t[3].has(index1) + var has2 = t[3].has(index2) + + if has1 and !has2: + t[3].erase(index1) + t[3].push_back(index2) + elif has2 and !has1: + t[3].erase(index2) + t[3].push_back(index1) + + for s in ts.get_source_count(): + var source := ts.get_source(ts.get_source_id(s)) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + var td := source.get_tile_data(coord, alternate) + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + continue + + if td_meta.type == index1: + td_meta.type = index2 + elif td_meta.type == index2: + td_meta.type = index1 + + for peering in td_meta.keys(): + if !(peering is int): + continue + + var fixed_peering = [] + for p in td_meta[peering]: + if p == index1: + fixed_peering.append(index2) + elif p == index2: + fixed_peering.append(index1) + else: + fixed_peering.append(p) + td_meta[peering] = fixed_peering + + _set_tile_meta(ts, td, td_meta) + + var temp = ts_meta.terrains[index1] + ts_meta.terrains[index1] = ts_meta.terrains[index2] + ts_meta.terrains[index2] = temp + _set_terrain_meta(ts, ts_meta) + + _purge_cache(ts) + return true + + +# Terrain tile data + +## For a tile in a [TileSet] as specified by [TileData], set the terrain associated +## with that tile to [code]type[/code], which is an index of an existing terrain. +## Returns [code]true[/code] on success. +func set_tile_terrain_type(ts: TileSet, td: TileData, type: int) -> bool: + if !ts or !td or type < TileCategory.NON_TERRAIN: + return false + + var td_meta = _get_tile_meta(td) + td_meta.type = type + if type == TileCategory.NON_TERRAIN: + td_meta = null + _set_tile_meta(ts, td, td_meta) + + _clear_invalid_peering_types(ts) + _purge_cache(ts) + return true + + +## Returns the terrain type associated with tile specified by [TileData]. Returns +## -1 if the tile has no associated terrain. +func get_tile_terrain_type(td: TileData) -> int: + if !td: + return TileCategory.ERROR + var td_meta := _get_tile_meta(td) + return td_meta.type + + +## For a tile represented by [TileData] [code]td[/code] in [TileSet] +## [code]ts[/code], sets [enum SymmetryType] [code]type[/code]. This controls +## how the tile is rotated/mirrored during placement. +func set_tile_symmetry_type(ts: TileSet, td: TileData, type: int) -> bool: + if !ts or !td or type < SymmetryType.NONE or type > SymmetryType.ALL: + return false + + var td_meta := _get_tile_meta(td) + if td_meta.type == TileCategory.NON_TERRAIN: + return false + + td_meta.symmetry = type + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For a tile [code]td[/code], returns the [enum SymmetryType] which that +## tile uses. +func get_tile_symmetry_type(td: TileData) -> int: + if !td: + return SymmetryType.NONE + + var td_meta := _get_tile_meta(td) + return td_meta.get("symmetry", SymmetryType.NONE) + + +## Returns an Array of all [TileData] tiles included in the specified +## terrain [code]type[/code] for the [TileSet] [code]ts[/code] +func get_tiles_in_terrain(ts: TileSet, type: int) -> Array[TileData]: + var result:Array[TileData] = [] + if !ts or type < TileCategory.EMPTY: + return result + + var cache := _get_cache(ts) + if type > cache.size(): + return result + + var tiles = cache[type] + if !tiles: + return result + for c in tiles: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + var td := source.get_tile_data(c[1], c[2]) + result.push_back(td) + + return result + + +## Returns an [Array] of [Dictionary] items including information about each +## tile included in the specified terrain [code]type[/code] for +## the [TileSet] [code]ts[/code]. Each Dictionary item includes +## [TileSetAtlasSource] [code]source[/code], [TileData] [code]td[/code], +## [Vector2i] [code]coord[/code], and [int] [code]alt_id[/code]. +func get_tile_sources_in_terrain(ts: TileSet, type: int) -> Array[Dictionary]: + var result:Array[Dictionary] = [] + + var cache := _get_cache(ts) + var tiles = cache[type] + if !tiles: + return result + for c in tiles: + if c[0] < 0: + continue + var source := ts.get_source(c[0]) as TileSetAtlasSource + if not source: + continue + var td := source.get_tile_data(c[1], c[2]) + result.push_back({ + source = source, + td = td, + coord = c[1], + alt_id = c[2] + }) + + return result + + +## For a [TileSet]'s tile, specified by [TileData], add terrain [code]type[/code] +## (an index of a terrain) to match this tile in direction [code]peering[/code], +## which is of type [enum TileSet.CellNeighbor]. Returns [code]true[/code] on success. +func add_tile_peering_type(ts: TileSet, td: TileData, peering: int, type: int) -> bool: + if !ts or !td or peering < 0 or peering > 15 or type < TileCategory.EMPTY: + return false + + var ts_meta := _get_terrain_meta(ts) + var td_meta := _get_tile_meta(td) + if td_meta.type < TileCategory.EMPTY or td_meta.type >= ts_meta.terrains.size(): + return false + + if !td_meta.has(peering): + td_meta[peering] = [type] + elif !td_meta[peering].has(type): + td_meta[peering].append(type) + else: + return false + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For a [TileSet]'s tile, specified by [TileData], remove terrain [code]type[/code] +## from matching in direction [code]peering[/code], which is of type [enum TileSet.CellNeighbor]. +## Returns [code]true[/code] on success. +func remove_tile_peering_type(ts: TileSet, td: TileData, peering: int, type: int) -> bool: + if !ts or !td or peering < 0 or peering > 15 or type < TileCategory.EMPTY: + return false + + var td_meta := _get_tile_meta(td) + if !td_meta.has(peering): + return false + if !td_meta[peering].has(type): + return false + td_meta[peering].erase(type) + if td_meta[peering].is_empty(): + td_meta.erase(peering) + _set_tile_meta(ts, td, td_meta) + _purge_cache(ts) + return true + + +## For the tile specified by [TileData], return an [Array] of peering directions +## for which terrain matching is set up. These will be of type [enum TileSet.CellNeighbor]. +func tile_peering_keys(td: TileData) -> Array: + if !td: + return [] + + var td_meta := _get_tile_meta(td) + var result := [] + for k in td_meta: + if k is int: + result.append(k) + return result + + +## For the tile specified by [TileData], return the [Array] of terrains that match +## for the direction [code]peering[/code] which should be of type [enum TileSet.CellNeighbor]. +func tile_peering_types(td: TileData, peering: int) -> Array: + if !td or peering < 0 or peering > 15: + return [] + + var td_meta := _get_tile_meta(td) + return td_meta[peering].duplicate() if td_meta.has(peering) else [] + + +## For the tile specified by [TileData], return the [Array] of peering directions +## for the specified terrain type [code]type[/code]. +func tile_peering_for_type(td: TileData, type: int) -> Array: + if !td: + return [] + + var td_meta := _get_tile_meta(td) + var result := [] + var sides := tile_peering_keys(td) + for side in sides: + if td_meta[side].has(type): + result.push_back(side) + + result.sort() + return result + + +# Painting + +## Applies the terrain [code]type[/code] to the [TileMapLayer] for the [Vector2i] +## [code]coord[/code]. Returns [code]true[/code] if it succeeds. Use [method set_cells] +## to change multiple tiles at once. +## [br][br] +## Use terrain type -1 to erase cells. +func set_cell(tm: TileMapLayer, coord: Vector2i, type: int) -> bool: + if !tm or !tm.tile_set or type < TileCategory.EMPTY: + return false + + if type == TileCategory.EMPTY: + tm.erase_cell(coord) + return true + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var tile = cache[type].front() + tm.set_cell(coord, tile[0], tile[1], tile[2]) + return true + + +## Applies the terrain [code]type[/code] to the [TileMapLayer] for the +## [Vector2i] [code]coords[/code]. Returns [code]true[/code] if it succeeds. +## [br][br] +## Note that this does not cause the terrain solver to run, so this will just place +## an arbitrary terrain-associated tile in the given position. To run the solver, +## you must set the require cells, and then call either [method update_terrain_cell], +## [method update_terrain_cels], or [method update_terrain_area]. +## [br][br] +## If you want to prepare changes to the tiles in advance, you can use [method create_terrain_changeset] +## and the associated functions. +## [br][br] +## Use terrain type -1 to erase cells. +func set_cells(tm: TileMapLayer, coords: Array, type: int) -> bool: + if !tm or !tm.tile_set or type < TileCategory.EMPTY: + return false + + if type == TileCategory.EMPTY: + for c in coords: + tm.erase_cell(c) + return true + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var tile = cache[type].front() + for c in coords: + tm.set_cell(c, tile[0], tile[1], tile[2]) + return true + + +## Replaces an existing tile on the [TileMapLayer] for the [Vector2i] +## [code]coord[/code] with a new tile in the provided terrain [code]type[/code] +## *only if* there is a tile with a matching set of peering sides in this terrain. +## Returns [code]true[/code] if any tiles were changed. Use [method replace_cells] +## to replace multiple tiles at once. +func replace_cell(tm: TileMapLayer, coord: Vector2i, type: int) -> bool: + if !tm or !tm.tile_set or type < 0: + return false + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var td = tm.get_cell_tile_data(coord) + if !td: + return false + + var ts_meta := _get_terrain_meta(tm.tile_set) + var categories = ts_meta.terrains[type][3] + var check_types = [type] + categories + + for check_type in check_types: + var placed_peering = tile_peering_for_type(td, check_type) + for pt in get_tiles_in_terrain(tm.tile_set, type): + var check_peering := tile_peering_for_type(pt, check_type) + if placed_peering == check_peering: + var tile = cache[type].front() + tm.set_cell(coord, tile[0], tile[1], tile[2]) + return true + + return false + + +## Replaces existing tiles on the [TileMapLayer] for the [Vector2i] +## [code]coords[/code] with new tiles in the provided terrain [code]type[/code] +## *only if* there is a tile with a matching set of peering sides in this terrain +## for each tile. +## Returns [code]true[/code] if any tiles were changed. +func replace_cells(tm: TileMapLayer, coords: Array, type: int) -> bool: + if !tm or !tm.tile_set or type < 0: + return false + + var cache := _get_cache(tm.tile_set) + if type >= cache.size(): + return false + + if cache[type].is_empty(): + return false + + var ts_meta := _get_terrain_meta(tm.tile_set) + var categories = ts_meta.terrains[type][3] + var check_types = [type] + categories + + var changed = false + var potential_tiles = get_tiles_in_terrain(tm.tile_set, type) + for c in coords: + var found = false + var td = tm.get_cell_tile_data(c) + if !td: + continue + for check_type in check_types: + var placed_peering = tile_peering_for_type(td, check_type) + for pt in potential_tiles: + var check_peering = tile_peering_for_type(pt, check_type) + if placed_peering == check_peering: + var tile = cache[type].front() + tm.set_cell(c, tile[0], tile[1], tile[2]) + changed = true + found = true + break + + if found: + break + + return changed + + +## Returns the terrain type detected in the [TileMapLayer] at specified [Vector2i] +## [code]coord[/code]. Returns -1 if tile is not valid or does not contain a +## tile associated with a terrain. +func get_cell(tm: TileMapLayer, coord: Vector2i) -> int: + if !tm or !tm.tile_set: + return TileCategory.ERROR + + if tm.get_cell_source_id(coord) == -1: + return TileCategory.EMPTY + + var t := tm.get_cell_tile_data(coord) + if !t: + return TileCategory.NON_TERRAIN + + return _get_tile_meta(t).type + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given +## [Vector2i] coordinates in the [code]cells[/code] parameter. By default, +## the surrounding cells are also solved, but this can be adjusted by passing [code]false[/code] +## to the [code]and_surrounding_cells[/code] parameter. +## [br][br] +## See also [method update_terrain_area] and [method update_terrain_cell]. +func update_terrain_cells(tm: TileMapLayer, cells: Array, and_surrounding_cells := true) -> void: + if !tm or !tm.tile_set: + return + + if and_surrounding_cells: + cells = _widen(tm, cells) + var needed_cells := _widen(tm, cells) + + var types := {} + for c in needed_cells: + types[c] = get_cell(tm, c) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var cache := _get_cache(tm.tile_set) + for c in cells: + _update_tile_immediate(tm, c, ts_meta, types, cache) + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given [Vector2i] +## [code]cell[/code]. By default, the surrounding cells are also solved, but +## this can be adjusted by passing [code]false[/code] to the [code]and_surrounding_cells[/code] +## parameter. This calls through to [method update_terrain_cells]. +func update_terrain_cell(tm: TileMapLayer, cell: Vector2i, and_surrounding_cells := true) -> void: + update_terrain_cells(tm, [cell], and_surrounding_cells) + + +## Runs the tile solving algorithm on the [TileMapLayer] for the given [Rect2i] +## [code]area[/code]. By default, the surrounding cells are also solved, but +## this can be adjusted by passing [code]false[/code] to the [code]and_surrounding_cells[/code] +## parameter. +## [br][br] +## See also [method update_terrain_cells]. +func update_terrain_area(tm: TileMapLayer, area: Rect2i, and_surrounding_cells := true) -> void: + if !tm or !tm.tile_set: + return + + # Normalize area and extend so tiles cover inclusive space + area = area.abs() + area.size += Vector2i.ONE + + var edges = [] + for x in range(area.position.x, area.end.x): + edges.append(Vector2i(x, area.position.y)) + edges.append(Vector2i(x, area.end.y - 1)) + for y in range(area.position.y + 1, area.end.y - 1): + edges.append(Vector2i(area.position.x, y)) + edges.append(Vector2i(area.end.x - 1, y)) + + var additional_cells := [] + var needed_cells := _widen_with_exclusion(tm, edges, area) + + if and_surrounding_cells: + additional_cells = needed_cells + needed_cells = _widen_with_exclusion(tm, needed_cells, area) + + var types := {} + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var coord = Vector2i(x, y) + types[coord] = get_cell(tm, coord) + for c in needed_cells: + types[c] = get_cell(tm, c) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var cache := _get_cache(tm.tile_set) + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var coord := Vector2i(x, y) + _update_tile_immediate(tm, coord, ts_meta, types, cache) + for c in additional_cells: + _update_tile_immediate(tm, c, ts_meta, types, cache) + + +## For a [TileMapLayer], create a changeset that will +## be calculated via a [WorkerThreadPool], so it will not delay processing the current +## frame or affect the framerate. +## [br][br] +## The [code]paint[/code] parameter must be a [Dictionary] with keys of type [Vector2i] +## representing map coordinates, and integer values representing terrain types. +## [br][br] +## Returns a [Dictionary] with internal details. See also [method is_terrain_changeset_ready], +## [method apply_terrain_changeset], and [method wait_for_terrain_changeset]. +func create_terrain_changeset(tm: TileMapLayer, paint: Dictionary) -> Dictionary: + # Force cache rebuild if required + var _cache := _get_cache(tm.tile_set) + + var cells := paint.keys() + var needed_cells := _widen(tm, cells) + + var types := {} + for c in needed_cells: + types[c] = paint[c] if paint.has(c) else get_cell(tm, c) + + var placements := [] + placements.resize(cells.size()) + + var ts_meta := _get_terrain_meta(tm.tile_set) + var work := func(n: int): + placements[n] = _update_tile_deferred(tm, cells[n], ts_meta, types, _cache) + + return { + "valid": true, + "tilemap": tm, + "cells": cells, + "placements": placements, + "group_id": WorkerThreadPool.add_group_task(work, cells.size(), -1, false, "BetterTerrain") + } + + +## Returns [code]true[/code] if a changeset created by [method create_terrain_changeset] +## has finished the threaded calculation and is ready to be applied by [method apply_terrain_changeset]. +## See also [method wait_for_terrain_changeset]. +func is_terrain_changeset_ready(change: Dictionary) -> bool: + if !change.has("group_id"): + return false + + return WorkerThreadPool.is_group_task_completed(change.group_id) + + +## Blocks until a changeset created by [method create_terrain_changeset] finishes. +## This is useful to tidy up threaded work in the event that a node is to be removed +## whilst still waiting on threads. +## [br][br] +## Usage example: +## [codeblock] +## func _exit_tree(): +## if changeset.valid: +## BetterTerrain.wait_for_terrain_changeset(changeset) +## [/codeblock] +func wait_for_terrain_changeset(change: Dictionary) -> void: + if change.has("group_id"): + WorkerThreadPool.wait_for_group_task_completion(change.group_id) + + +## Apply the changes in a changeset created by [method create_terrain_changeset] +## once it is confirmed by [method is_terrain_changeset_ready]. The changes will +## be applied to the [TileMapLayer] that the changeset was initialized with. +## [br][br] +## Completed changesets can be applied multiple times, and stored for as long as +## needed once calculated. +func apply_terrain_changeset(change: Dictionary) -> void: + for n in change.cells.size(): + var placement = change.placements[n] + if placement: + change.tilemap.set_cell(change.cells[n], placement[0], placement[1], placement[2]) diff --git a/godot/addons/better-terrain/BetterTerrain.gd.uid b/godot/addons/better-terrain/BetterTerrain.gd.uid new file mode 100644 index 0000000..735e021 --- /dev/null +++ b/godot/addons/better-terrain/BetterTerrain.gd.uid @@ -0,0 +1 @@ +uid://d2so7sid6wvhf diff --git a/godot/addons/better-terrain/BetterTerrainData.gd b/godot/addons/better-terrain/BetterTerrainData.gd new file mode 100644 index 0000000..5d3bcd5 --- /dev/null +++ b/godot/addons/better-terrain/BetterTerrainData.gd @@ -0,0 +1,598 @@ +@tool + +## Data functions for [TileSet] properties. +## +## This data class has functions for retrieving data regarding the mathematical +## properties of a tile set. + +const _terrain_peering_square_tiles : Array[int] = [0, 3, 4, 7, 8, 11, 12, 15] +const _terrain_peering_square_vertices : Array[int] = [3, 7, 11, 15] +const _terrain_peering_isometric_tiles : Array[int] = [1, 2, 5, 6, 9, 10, 13, 14] +const _terrain_peering_isometric_vertices : Array[int] = [1, 5, 9, 13] +const _terrain_peering_horiztonal_tiles : Array[int] = [0, 2, 6, 8, 10, 14] +const _terrain_peering_horiztonal_vertices : Array[int] = [3, 5, 7, 11, 13, 15] +const _terrain_peering_vertical_tiles : Array[int] = [2, 4, 6, 10, 12, 14] +const _terrain_peering_vertical_vertices : Array[int] = [1, 3, 7, 9, 11, 15] +const _terrain_peering_non_modifying : Array[int] = [] + +const _terrain_peering_hflip : Array[int] = [8, 9, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11] +const _terrain_peering_vflip : Array[int] = [0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3] +const _terrain_peering_transpose : Array[int] = [4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7] + +const symmetry_mapping := { + BetterTerrain.SymmetryType.NONE: [0], + BetterTerrain.SymmetryType.MIRROR: [0, TileSetAtlasSource.TRANSFORM_FLIP_H], + BetterTerrain.SymmetryType.FLIP: [0, TileSetAtlasSource.TRANSFORM_FLIP_V], + BetterTerrain.SymmetryType.REFLECT: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H, + TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V + ], + BetterTerrain.SymmetryType.ROTATE_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE], + BetterTerrain.SymmetryType.ROTATE_COUNTER_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE], + BetterTerrain.SymmetryType.ROTATE_180: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V], + BetterTerrain.SymmetryType.ROTATE_ALL: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE + ], + BetterTerrain.SymmetryType.ALL: [ + 0, + TileSetAtlasSource.TRANSFORM_FLIP_H, + TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V, + TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE, + TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE + ] +} + + +## Returns an [Array] of ints of type [enum TileSet.CellNeighbor] which represent +## the valid neighboring tiles for a terrain of [code]type[/code] in TileSet +static func get_terrain_peering_cells(ts: TileSet, type: int) -> Array[int]: + if !ts or type < 0 or type >= BetterTerrain.TerrainType.MAX: + return [] + + if type == BetterTerrain.TerrainType.CATEGORY: + return _terrain_peering_non_modifying + if type == BetterTerrain.TerrainType.DECORATION: + type = BetterTerrain.TerrainType.MATCH_TILES + + match [ts.tile_shape, type]: + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_square_tiles + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_square_vertices + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_isometric_tiles + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_isometric_vertices + + match [ts.tile_offset_axis, type]: + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_vertical_tiles + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_vertical_vertices + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _terrain_peering_horiztonal_tiles + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _terrain_peering_horiztonal_vertices + + return [] + + +## Returns true if [code]peering[/code] is a valid neighboring cell for a terrain of +## [code]type[/code] in [TileSet] +static func is_terrain_peering_cell(ts: TileSet, type: int, peering: int) -> bool: + return peering in get_terrain_peering_cells(ts, type) + + +static func _peering_polygon_square_tiles(peering: int) -> PackedVector2Array: + const t := 1.0 / 3.0 + var result : PackedVector2Array + match peering: + TileSet.CELL_NEIGHBOR_RIGHT_SIDE: result.append(Vector2(2*t, t)) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: result.append(Vector2(2*t, 2*t)) + TileSet.CELL_NEIGHBOR_BOTTOM_SIDE: result.append(Vector2(t, 2*t)) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: result.append(Vector2(0, 2*t)) + TileSet.CELL_NEIGHBOR_LEFT_SIDE: result.append(Vector2(0, t)) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: result.append(Vector2(0, 0)) + TileSet.CELL_NEIGHBOR_TOP_SIDE: result.append(Vector2(t, 0)) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: result.append(Vector2(2*t, 0)) + -1: result.append(Vector2(t, t)) + result.append(result[0] + Vector2(t, 0)) + result.append(result[0] + Vector2(t, t)) + result.append(result[0] + Vector2(0, t)) + return result + + +static func _peering_polygon_square_vertices(peering: int) -> PackedVector2Array: + const t := 1.0 / 2.0 + var result : PackedVector2Array + match peering: + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + result.append(Vector2(1, t)) + result.append(Vector2(1, 1)) + result.append(Vector2(t, 1)) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + result.append(Vector2(0, t)) + result.append(Vector2(t, 1)) + result.append(Vector2(0, 1)) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + result.append(Vector2(0, 0)) + result.append(Vector2(t, 0)) + result.append(Vector2(0, t)) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + result.append(Vector2(t, 0)) + result.append(Vector2(1, 0)) + result.append(Vector2(1, t)) + -1: + result.append(Vector2(t, 0)) + result.append(Vector2(1, t)) + result.append(Vector2(t, 1)) + result.append(Vector2(0, t)) + return result + + +static func _peering_polygon_isometric_tiles(peering: int) -> PackedVector2Array: + const t := 1.0 / 4.0 + match peering: + -1: return PackedVector2Array([Vector2(2 * t, t), Vector2(3 * t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 2 * t)]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, t), Vector2(1, 3 * t)]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, 3 * t), Vector2(3 * t, 1), Vector2(2 * t, 3 * t)]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([Vector2(2 * t, 3 * t), Vector2(3 * t, 1), Vector2(t, 1)]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([Vector2(t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 1), Vector2(0, 3 * t)]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([Vector2(0, t), Vector2(t, 2 * t), Vector2(0, 3 * t)]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([Vector2(t, 0), Vector2(2 * t, t), Vector2(t, 2 * t), Vector2(0, t)]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([Vector2(t, 0), Vector2(3 * t, 0), Vector2(2 * t, t)]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([Vector2(3 * t, 0), Vector2(1, t), Vector2(3 * t, 2 * t), Vector2(2 * t, t)]) + return PackedVector2Array() + + +static func _peering_polygon_isometric_vertices(peering: int) -> PackedVector2Array: + const t := 1.0 / 4.0 + const ttt := 3.0 * t + match peering: + -1: return PackedVector2Array([Vector2(t, t), Vector2(ttt, t), Vector2(ttt, ttt), Vector2(t, ttt)]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([Vector2(ttt, t), Vector2(1, 0), Vector2(1, 1), Vector2(ttt, ttt)]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([Vector2(t, ttt), Vector2(ttt, ttt), Vector2(1, 1), Vector2(0, 1)]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([Vector2(0, 0), Vector2(t, t), Vector2(t, ttt), Vector2(0, 1)]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([Vector2(0, 0), Vector2(1, 0), Vector2(ttt, t), Vector2(t, t)]) + return PackedVector2Array() + + +static func _peering_polygon_horizontal_tiles(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t + w, t - s), + Vector2(t + w, t + s), + Vector2(t, 6 * s), + Vector2(t - w, t + s), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + w, t - s), + Vector2(1, t - e), + Vector2(1, t + e), + Vector2(t + w, t + s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + w, t + s), + Vector2(1, t + e), + Vector2(t, 1), + Vector2(t, 6 * s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t, 6 * s), + Vector2(t, 1), + Vector2(0, t + e), + Vector2(t - w, t + s) + ]) + TileSet.CELL_NEIGHBOR_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - w, t + s), + Vector2(0, t + e), + Vector2(0, t - e), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - w, t - s), + Vector2(0, t - e), + Vector2(t, 0), + Vector2(t, 2 * s) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t, 0), + Vector2(1, t - e), + Vector2(t + w, t - s) + ]) + return PackedVector2Array() + + +static func _peering_polygon_horizontal_vertices(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t - s, t - w), + Vector2(t + s, t - w), + Vector2(6 * s, t), + Vector2(t + s, t + w), + Vector2(t - s, t + w), + Vector2(2 * s, t) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(6 * s, t), + Vector2(1, t), + Vector2(1, t + e), + Vector2(t + e, 1 - s), + Vector2(t + s, t + w) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return PackedVector2Array([ + Vector2(t - s, t + w), + Vector2(t + s, t + w), + Vector2(t + e, 1 - s), + Vector2(t, 1), + Vector2(t - e, 1 - s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return PackedVector2Array([ + Vector2(0, t), + Vector2(2 * s, t), + Vector2(t - s, t + w), + Vector2(t - e, 1 - s), + Vector2(0, t + e) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - e, s), + Vector2(t - s, t - w), + Vector2(2 * s, t), + Vector2(0, t), + Vector2(0, t - e) + ]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return PackedVector2Array([ + Vector2(t, 0), + Vector2(t + e, s), + Vector2(t + s, t - w), + Vector2(t - s, t - w), + Vector2(t - e, s) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t + e, s), + Vector2(1, t - e), + Vector2(1, t), + Vector2(6 * s, t), + Vector2(t + s, t - w) + ]) + return PackedVector2Array() + + +static func _peering_polygon_vertical_tiles(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t - s, t - w), + Vector2(t + s, t - w), + Vector2(6 * s, t), + Vector2(t + s, t + w), + Vector2(t - s, t + w), + Vector2(2 * s, t) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(6 * s, t), + Vector2(1, t), + Vector2(t + e, 1), + Vector2(t + s, t + w) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_SIDE: + return PackedVector2Array([ + Vector2(t - s, t + w), + Vector2(t + s, t + w), + Vector2(t + e, 1), + Vector2(t - e, 1) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + return PackedVector2Array([ + Vector2(0, t), + Vector2(2 * s, t), + Vector2(t - s, t + w), + Vector2(t - e, 1) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t - s, t - w), + Vector2(2 * s, t), + Vector2(0, t) + ]) + TileSet.CELL_NEIGHBOR_TOP_SIDE: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t + e, 0), + Vector2(t + s, t - w), + Vector2(t - s, t - w) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE: + return PackedVector2Array([ + Vector2(t + e, 0), + Vector2(1, t), + Vector2(6 * s, t), + Vector2(t + s, t - w) + ]) + return PackedVector2Array() + + +static func _peering_polygon_vertical_vertices(peering: int) -> PackedVector2Array: + const e := 1.0 / (2.0 * sqrt(3.0)) + const w := sqrt(3.0) / 8.0 + const t := 1.0 / 2.0 + const s := 1.0 / 8.0 + match peering: + -1: + return PackedVector2Array([ + Vector2(t, 2 * s), + Vector2(t + w, t - s), + Vector2(t + w, t + s), + Vector2(t, 6 * s), + Vector2(t - w, t + s), + Vector2(t - w, t - s) + ]) + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(1 - s, t - e), + Vector2(1, t), + Vector2(1 - s, t + e), + Vector2(t + w, t + s), + Vector2(t + w, t - s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t + w, t + s), + Vector2(1 - s, t + e), + Vector2(t + e, 1), + Vector2(t, 1), + Vector2(t, 6 * s) + ]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - w, t + s), + Vector2(t, 6 * s), + Vector2(t, 1), + Vector2(t - e, 1), + Vector2(s, t + e) + ]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return PackedVector2Array([ + Vector2(s, t - e), + Vector2(t - w, t - s), + Vector2(t - w, t + s), + Vector2(s, t + e), + Vector2(0, t) + ]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return PackedVector2Array([ + Vector2(t - e, 0), + Vector2(t, 0), + Vector2(t, 2 * s), + Vector2(t - w, t - s), + Vector2(s, t - e) + ]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return PackedVector2Array([ + Vector2(t, 0), + Vector2(t + e, 0), + Vector2(1 - s, t - e), + Vector2(t + w, t - s), + Vector2(t, 2 * s) + ]) + return PackedVector2Array() + + +static func _peering_non_modifying() -> PackedVector2Array: + const t := 1.0 / 3.0 + return PackedVector2Array([ + Vector2(t, 0), + Vector2(2 * t, 0), + Vector2(1, t), + Vector2(1, 2 * t), + Vector2(2 * t, 1), + Vector2(t, 1), + Vector2(0, 2 * t), + Vector2(0, t) + ]) + + +## Returns a parameterized polygon (coordinated are between 0 and 1) for [code]peering[/code] +## direction for a terrain of [code]type[/code] in [TileSet] +static func peering_polygon(ts: TileSet, type: int, peering: int) -> PackedVector2Array: + if type == BetterTerrain.TerrainType.CATEGORY: + return _peering_non_modifying() + if type == BetterTerrain.TerrainType.DECORATION: + type = BetterTerrain.TerrainType.MATCH_TILES + + match [ts.tile_shape, type]: + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_square_tiles(peering) + [TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_square_vertices(peering) + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_isometric_tiles(peering) + [TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_isometric_vertices(peering) + + match [ts.tile_offset_axis, type]: + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_vertical_tiles(peering) + [TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_vertical_vertices(peering) + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]: + return _peering_polygon_horizontal_tiles(peering) + [TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]: + return _peering_polygon_horizontal_vertices(peering) + + return PackedVector2Array() + + +## Returns as polygon centered on 0, 0 which represents the shape of the cell of +## a tile from [TileSet]. +static func cell_polygon(ts: TileSet) -> PackedVector2Array: + const t := 1.0 / 2.0 + if ts.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_HALF_OFFSET_SQUARE]: + return PackedVector2Array([Vector2(-t, -t), Vector2(t, -t), Vector2(t, t), Vector2(-t, t)]) + if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC: + return PackedVector2Array([Vector2(0, -t), Vector2(t, 0), Vector2(0, t), Vector2(-t, 0)]) + + const e := t - 1.0 / (2.0 * sqrt(3.0)) + if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + return PackedVector2Array([ + Vector2(0, -t), + Vector2(t, -e), + Vector2(t, e), + Vector2(0, t), + Vector2(-t, e), + Vector2(-t, -e), + ]) + + return PackedVector2Array([ + Vector2(-t, 0), + Vector2(-e, -t), + Vector2(e, -t), + Vector2(t, 0), + Vector2(e, t), + Vector2(-e, t), + ]) + + +## Returns an [Array] of coordinated that neighbor [code]coord[/code] based on [code]peering[/code] +## [Array] of [enum TileSet.CellNeighbor] for a [TileSet]. +static func neighboring_coords(tm: TileMapLayer, coord: Vector2i, peerings: Array) -> Array: + return peerings.map(func(p): return tm.get_neighbor_cell(coord, p)) + + +## Returns an [Array] of coordinates which neighbor the vertex describe by [code]corner[/code] +## (which is of type [enum TileSet.CellNeighbor]) from [code]coord[/code] in [TileSet]. +static func associated_vertex_cells(tm: TileMapLayer, coord: Vector2i, corner: int) -> Array: + # get array of associated peering bits + if tm.tile_set.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_ISOMETRIC]: + match corner: + # Square + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [0, 3, 4]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [4, 7, 8]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [8, 11, 12]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [12, 15, 0]) + # Isometric + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 1, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return neighboring_coords(tm, coord, [2, 5, 6]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 9, 10]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return neighboring_coords(tm, coord, [10, 13, 14]) + + if tm.tile_set.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + match corner: + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [0, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_CORNER: + return neighboring_coords(tm, coord, [2, 6]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 8]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [8, 10]) + TileSet.CELL_NEIGHBOR_TOP_CORNER: + return neighboring_coords(tm, coord, [10, 14]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 0]) + + # TileSet.TILE_OFFSET_AXIS_VERTICAL + match corner: + TileSet.CELL_NEIGHBOR_RIGHT_CORNER: + return neighboring_coords(tm, coord, [14, 2]) + TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return neighboring_coords(tm, coord, [2, 4]) + TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return neighboring_coords(tm, coord, [4, 6]) + TileSet.CELL_NEIGHBOR_LEFT_CORNER: + return neighboring_coords(tm, coord, [6, 10]) + TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: + return neighboring_coords(tm, coord, [10, 12]) + TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return neighboring_coords(tm, coord, [12, 14]) + + return [] + + +## Returns an [Array] of [enum TileSet.CellNeighbor] suitable for flood filling +## an area in [TileSet]. +static func cells_adjacent_for_fill(ts: TileSet) -> Array[int]: + if ts.tile_shape == TileSet.TILE_SHAPE_SQUARE: + return [0, 4, 8, 12] + if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC: + return [2, 6, 10, 14] + if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL: + return _terrain_peering_horiztonal_tiles + return _terrain_peering_vertical_tiles + + +static func peering_bit_after_symmetry(bit: int, altflags: int) -> int: + if altflags & TileSetAtlasSource.TRANSFORM_TRANSPOSE: + bit = _terrain_peering_transpose[bit] + if altflags & TileSetAtlasSource.TRANSFORM_FLIP_H: + bit = _terrain_peering_hflip[bit] + if altflags & TileSetAtlasSource.TRANSFORM_FLIP_V: + bit = _terrain_peering_vflip[bit] + return bit + + +static func peering_bits_after_symmetry(dict: Dictionary, altflags: int) -> Dictionary: + # rearrange dictionary keys based on altflags + var result := {} + for k in dict: + result[peering_bit_after_symmetry(k, altflags)] = dict[k] + return result diff --git a/godot/addons/better-terrain/BetterTerrainData.gd.uid b/godot/addons/better-terrain/BetterTerrainData.gd.uid new file mode 100644 index 0000000..7525c09 --- /dev/null +++ b/godot/addons/better-terrain/BetterTerrainData.gd.uid @@ -0,0 +1 @@ +uid://cmeo3arrf2ccv diff --git a/godot/addons/better-terrain/TerrainPlugin.gd b/godot/addons/better-terrain/TerrainPlugin.gd new file mode 100644 index 0000000..eb514ae --- /dev/null +++ b/godot/addons/better-terrain/TerrainPlugin.gd @@ -0,0 +1,73 @@ +@tool +extends EditorPlugin + +const AUTOLOAD_NAME = "BetterTerrain" +var dock : Control +var button : Button + +func _enter_tree() -> void: + # Wait for autoloads to register + await get_tree().process_frame + + if !get_tree().root.get_node_or_null(^"BetterTerrain"): + # Autoload wasn't present on plugin init, which means plugin won't have loaded correctly + add_autoload_singleton(AUTOLOAD_NAME, "res://addons/better-terrain/BetterTerrain.gd") + ProjectSettings.save() + + var confirm = ConfirmationDialog.new() + confirm.dialog_text = "The editor needs to be restarted for Better Terrain to load correctly. Restart now? Note: Unsaved changes will be lost." + confirm.confirmed.connect(func(): + OS.set_restart_on_exit(true, ["-e"]) + get_tree().quit() + ) + get_editor_interface().popup_dialog_centered(confirm) + + dock = load("res://addons/better-terrain/editor/Dock.tscn").instantiate() + dock.update_overlay.connect(self.update_overlays) + get_editor_interface().get_editor_main_screen().mouse_exited.connect(dock.canvas_mouse_exit) + dock.undo_manager = get_undo_redo() + button = add_control_to_bottom_panel(dock, "Terrain") + button.toggled.connect(dock.about_to_be_visible) + dock.force_show_terrains.connect(button.toggled.emit.bind(true)) + button.visible = false + + +func _exit_tree() -> void: + remove_control_from_bottom_panel(dock) + dock.queue_free() + + +func _handles(object) -> bool: + return object is TileMapLayer or object is TileSet + + +func _make_visible(visible) -> void: + button.visible = visible + + +func _edit(object) -> void: + var new_tileset : TileSet = null + + if object is TileMapLayer: + dock.tilemap = object + new_tileset = object.tile_set + if object is TileSet: + dock.tilemap = null + new_tileset = object + + if dock.tileset != new_tileset: + dock.tiles_about_to_change() + dock.tileset = new_tileset + dock.tiles_changed() + + +func _forward_canvas_draw_over_viewport(overlay: Control) -> void: + if dock.visible: + dock.canvas_draw(overlay) + + +func _forward_canvas_gui_input(event: InputEvent) -> bool: + if !dock.visible: + return false + + return dock.canvas_input(event) diff --git a/godot/addons/better-terrain/TerrainPlugin.gd.uid b/godot/addons/better-terrain/TerrainPlugin.gd.uid new file mode 100644 index 0000000..daee621 --- /dev/null +++ b/godot/addons/better-terrain/TerrainPlugin.gd.uid @@ -0,0 +1 @@ +uid://dar1dq8au8dgv diff --git a/godot/addons/better-terrain/Watcher.gd b/godot/addons/better-terrain/Watcher.gd new file mode 100644 index 0000000..0c5fa51 --- /dev/null +++ b/godot/addons/better-terrain/Watcher.gd @@ -0,0 +1,20 @@ +@tool +extends Node + +signal trigger +var complete := false +var tileset : TileSet + +func tidy() -> bool: + if complete: + return false + + complete = true + queue_free() + return true + + +func activate(): + if tidy(): + trigger.emit() + diff --git a/godot/addons/better-terrain/Watcher.gd.uid b/godot/addons/better-terrain/Watcher.gd.uid new file mode 100644 index 0000000..6ed5b77 --- /dev/null +++ b/godot/addons/better-terrain/Watcher.gd.uid @@ -0,0 +1 @@ +uid://b4sdpl0lvpfct diff --git a/godot/addons/better-terrain/editor/Dock.gd b/godot/addons/better-terrain/editor/Dock.gd new file mode 100644 index 0000000..9d2b6f7 --- /dev/null +++ b/godot/addons/better-terrain/editor/Dock.gd @@ -0,0 +1,948 @@ +@tool +extends Control + +signal update_overlay +signal force_show_terrains + +# The maximum individual tiles the overlay will draw before shortcutting the display +# To prevent editor lag when drawing large rectangles or filling large areas +const MAX_CANVAS_RENDER_TILES = 1500 +const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn") +const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn") +const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount" +const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount" + + +# Buttons +@onready var draw_button: Button = $VBox/Toolbar/Draw +@onready var line_button: Button = $VBox/Toolbar/Line +@onready var rectangle_button: Button = $VBox/Toolbar/Rectangle +@onready var fill_button: Button = $VBox/Toolbar/Fill +@onready var replace_button: Button = $VBox/Toolbar/Replace + +@onready var paint_type: Button = $VBox/Toolbar/PaintType +@onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain +@onready var select_tiles: Button = $VBox/Toolbar/SelectTiles + +@onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry +@onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions + +@onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom +@onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer + +@onready var source_selector: MenuBar = $VBox/Toolbar/Sources +@onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources + +@onready var clean_button: Button = $VBox/Toolbar/Clean +@onready var layer_up: Button = $VBox/Toolbar/LayerUp +@onready var layer_down: Button = $VBox/Toolbar/LayerDown +@onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight +@onready var layer_grid: Button = $VBox/Toolbar/LayerGrid + +@onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode +@onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode + +@onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools +@onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain +@onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain +@onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon +@onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp +@onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown +@onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain + +@onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer +@onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList +@onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView + + +var selected_entry := -2 + +var tilemap : TileMapLayer +var tileset : TileSet + +var undo_manager : EditorUndoRedoManager +var terrain_undo + +var draw_overlay := false +var initial_click : Vector2i +var prev_position : Vector2i +var current_position : Vector2i +var tileset_dirty := false +var zoom_slider : HSlider + +enum PaintMode { + NO_PAINT, + PAINT, + ERASE +} + +enum PaintAction { + NO_ACTION, + LINE, + RECT +} + +enum SourceSelectors { + ALL = 1000000, + NONE = 1000001, +} + +var paint_mode := PaintMode.NO_PAINT + +var paint_action := PaintAction.NO_ACTION + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + draw_button.icon = get_theme_icon("Edit", "EditorIcons") + line_button.icon = get_theme_icon("Line", "EditorIcons") + rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons") + fill_button.icon = get_theme_icon("Bucket", "EditorIcons") + select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons") + add_terrain_button.icon = get_theme_icon("Add", "EditorIcons") + edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons") + pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons") + move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons") + move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons") + remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons") + grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons") + quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons") + layer_up.icon = get_theme_icon("MoveUp", "EditorIcons") + layer_down.icon = get_theme_icon("MoveDown", "EditorIcons") + layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons") + layer_grid.icon = get_theme_icon("Grid", "EditorIcons") + + select_tiles.button_group.pressed.connect(_on_bit_button_pressed) + + terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new() + add_child(terrain_undo) + tile_view.undo_manager = undo_manager + tile_view.terrain_undo = terrain_undo + + tile_view.paste_occurred.connect(_on_paste_occurred) + tile_view.change_zoom_level.connect(_on_change_zoom_level) + tile_view.terrain_updated.connect(_on_terrain_updated) + + # Zoom slider is manipulated by settings, make it at runtime + zoom_slider = HSlider.new() + zoom_slider.custom_minimum_size = Vector2(100, 0) + zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed) + zoom_slider_container.add_child(zoom_slider) + + # Init settings if needed + if !ProjectSettings.has_setting(MIN_ZOOM_SETTING): + ProjectSettings.set(MIN_ZOOM_SETTING, 1.0) + ProjectSettings.add_property_info({ + "name": MIN_ZOOM_SETTING, + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0.1,1.0,0.1" + }) + ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0) + ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true) + + if !ProjectSettings.has_setting(MAX_ZOOM_SETTING): + ProjectSettings.set(MAX_ZOOM_SETTING, 8.0) + ProjectSettings.add_property_info({ + "name": MAX_ZOOM_SETTING, + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "2.0,32.0,1.0" + }) + ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0) + ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true) + ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1) + + ProjectSettings.settings_changed.connect(_on_adjust_settings) + _on_adjust_settings() + zoom_slider.value = 1.0 + + +func _process(delta): + scroll_container.scroll_horizontal = 0 + + +func _on_adjust_settings(): + zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0) + zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0) + zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0 + + +func _get_fill_cells(target: Vector2i) -> Array: + var pick := BetterTerrain.get_cell(tilemap, target) + var bounds := tilemap.get_used_rect() + var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset) + + # No sets yet, so use a dictionary + var checked := {} + var pending := [target] + var goal := [] + + while !pending.is_empty(): + var p = pending.pop_front() + if checked.has(p): + continue + checked[p] = true + if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick: + continue + + goal.append(p) + pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors)) + + return goal + + +func tiles_about_to_change() -> void: + if tileset and tileset.changed.is_connected(queue_tiles_changed): + tileset.changed.disconnect(queue_tiles_changed) + + +func tiles_changed() -> void: + # ensure up to date + BetterTerrain._update_terrain_data(tileset) + + # clear terrains + for c in terrain_list.get_children(): + terrain_list.remove_child(c) + c.queue_free() + + # load terrains from tileset + var terrain_count := BetterTerrain.terrain_count(tileset) + var item_count = terrain_count + 1 + for i in terrain_count: + var terrain := BetterTerrain.get_terrain(tileset, i) + if i >= terrain_list.get_child_count(): + add_terrain_entry(terrain, i) + + if item_count > terrain_list.get_child_count(): + var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY) + if terrain.valid: + add_terrain_entry(terrain, item_count - 1) + + while item_count < terrain_list.get_child_count(): + var child = terrain_list.get_child(terrain_list.get_child_count() - 1) + terrain_list.remove_child(child) + child.free() + + source_selector_popup.clear() + source_selector_popup.add_item("All", SourceSelectors.ALL) + source_selector_popup.add_item("None", SourceSelectors.NONE) + var source_count = tileset.get_source_count() if tileset else 0 + for s in source_count: + var source_id = tileset.get_source_id(s) + var source := tileset.get_source(source_id) + if !(source is TileSetAtlasSource): + continue + + var name := source.resource_name + if name.is_empty(): + var texture := (source as TileSetAtlasSource).texture + var texture_name := texture.resource_name if texture else "" + if !texture_name.is_empty(): + name = texture_name + else: + var texture_path := texture.resource_path if texture else "" + if !texture_path.is_empty(): + name = texture_path.get_file() + + if !name.is_empty(): + name += " " + name += " (ID: %d)" % source_id + + source_selector_popup.add_check_item(name, source_id) + + source_selector_popup.set_item_checked( + source_selector_popup.get_item_index(source_id), + not tile_view.disabled_sources.has(source_id) + ) + source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source + + update_tile_view_paint() + tile_view.refresh_tileset(tileset) + + if tileset and !tileset.changed.is_connected(queue_tiles_changed): + tileset.changed.connect(queue_tiles_changed) + + clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset) + + tileset_dirty = false + _on_grid_mode_pressed() + _on_quick_mode_pressed() + + +func about_to_be_visible(visible: bool) -> void: + if !visible: + return + + if tilemap and tileset != tilemap.tile_set: + tiles_about_to_change() + tileset = tilemap.tile_set + tiles_changed() + + var settings := EditorInterface.get_editor_settings() + layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer")) + layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid")) + + +func queue_tiles_changed() -> void: + # Bring terrain data up to date with complex tileset changes + if !tileset or tileset_dirty: + return + + tileset_dirty = true + tiles_changed.call_deferred() + + +func _on_entry_select(index:int): + selected_entry = index + if selected_entry >= BetterTerrain.terrain_count(tileset): + selected_entry = BetterTerrain.TileCategory.EMPTY + for i in range(terrain_list.get_child_count()): + if i != index: + terrain_list.get_child(i).set_selected(false) + update_tile_view_paint() + + +func _on_clean_pressed() -> void: + var confirmed := [false] + var popup := ConfirmationDialog.new() + popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?") + popup.dialog_hide_on_ok = false + popup.confirmed.connect(func(): + confirmed[0] = true + popup.hide() + ) + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + popup.queue_free() + + if confirmed[0]: + undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset) + undo_manager.add_do_method(self, &"tiles_changed") + terrain_undo.create_peering_restore_point(undo_manager, tileset) + undo_manager.add_undo_method(self, &"tiles_changed") + undo_manager.commit_action() + + +func _on_grid_mode_pressed() -> void: + for c in terrain_list.get_children(): + c.grid_mode = grid_mode_button.button_pressed + c.update_style() + + +func _on_quick_mode_pressed() -> void: + edit_tool_buttons.visible = !quick_mode_button.button_pressed + for c in terrain_list.get_children(): + c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES] + + +func update_tile_view_paint() -> void: + tile_view.paint = selected_entry + tile_view.queue_redraw() + + var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY + edit_terrain_button.disabled = !editable + move_up_button.disabled = !editable or tile_view.paint == 0 + move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1 + remove_terrain_button.disabled = !editable + pick_icon_button.disabled = !editable + + +func _on_add_terrain_pressed() -> void: + if !tileset: + return + + var popup := TERRAIN_PROPERTIES_SCENE.instantiate() + popup.set_category_data(BetterTerrain.get_terrain_categories(tileset)) + popup.terrain_name = "New terrain" + popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf()) + popup.terrain_icon = "" + popup.terrain_type = 0 + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + if popup.accepted: + undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_add_terrain", popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon}) + undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1) + undo_manager.commit_action() + popup.queue_free() + + +func _on_edit_terrain_pressed() -> void: + if !tileset: + return + + if selected_entry < 0: + return + + var t := BetterTerrain.get_terrain(tileset, selected_entry) + var categories = BetterTerrain.get_terrain_categories(tileset) + categories = categories.filter(func(x): return x.id != selected_entry) + + var popup := TERRAIN_PROPERTIES_SCENE.instantiate() + popup.set_category_data(categories) + + t.icon = t.icon.duplicate() + + popup.terrain_name = t.name + popup.terrain_type = t.type + popup.terrain_color = t.color + if t.has("icon") and t.icon.has("path"): + popup.terrain_icon = t.icon.path + popup.terrain_categories = t.categories + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + if popup.accepted: + undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_edit_terrain", selected_entry, popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon}) + undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon) + if t.type != popup.terrain_type: + terrain_undo.create_terrain_type_restore_point(undo_manager, tileset) + terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry) + undo_manager.commit_action() + popup.queue_free() + + +func _on_pick_icon_pressed(): + if selected_entry < 0: + return + tile_view.pick_icon_terrain = selected_entry + + +func _on_pick_icon_focus_exited(): + tile_view.pick_icon_terrain_cancel = true + pick_icon_button.button_pressed = false + + +func _on_move_pressed(down: bool) -> void: + if !tileset: + return + + if selected_entry < 0: + return + + var index1 = selected_entry + var index2 = index1 + (1 if down else -1) + if index2 < 0 or index2 >= terrain_list.get_child_count(): + return + + undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2) + undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2) + undo_manager.commit_action() + + +func _on_remove_terrain_pressed() -> void: + if !tileset: + return + + if selected_entry < 0: + return + + # store confirmation in array to pass by ref + var t := BetterTerrain.get_terrain(tileset, selected_entry) + var confirmed := [false] + var popup := ConfirmationDialog.new() + popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name]) + popup.dialog_hide_on_ok = false + popup.confirmed.connect(func(): + confirmed[0] = true + popup.hide() + ) + EditorInterface.popup_dialog_centered(popup) + await popup.visibility_changed + popup.queue_free() + + if confirmed[0]: + undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry) + undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon) + for n in range(terrain_list.get_child_count() - 2, selected_entry, -1): + undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1) + if t.type == BetterTerrain.TerrainType.CATEGORY: + terrain_undo.create_terrain_type_restore_point(undo_manager, tileset) + terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry) + undo_manager.commit_action() + + +func add_terrain_entry(terrain:Dictionary, index:int = -1): + if index < 0: + index = terrain_list.get_child_count() + + var entry = TERRAIN_ENTRY_SCENE.instantiate() + entry.tileset = tileset + entry.terrain = terrain + entry.grid_mode = grid_mode_button.button_pressed + entry.select.connect(_on_entry_select) + + terrain_list.add_child(entry) + terrain_list.move_child(entry, index) + + +func remove_terrain_entry(index: int): + terrain_list.get_child(index).free() + for i in range(index, terrain_list.get_child_count()): + var child = terrain_list.get_child(i) + child.terrain = BetterTerrain.get_terrain(tileset, i) + child.update() + + +func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void: + if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon): + var index = BetterTerrain.terrain_count(tileset) - 1 + var terrain = BetterTerrain.get_terrain(tileset, index) + add_terrain_entry(terrain, index) + + +func perform_remove_terrain(index: int) -> void: + if index >= BetterTerrain.terrain_count(tileset): + return + if BetterTerrain.remove_terrain(tileset, index): + remove_terrain_entry(index) + update_tile_view_paint() + + +func perform_swap_terrain(index1: int, index2: int) -> void: + var lower := min(index1, index2) + var higher := max(index1, index2) + if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count(): + return + var item1 = terrain_list.get_child(lower) + var item2 = terrain_list.get_child(higher) + if BetterTerrain.swap_terrains(tileset, lower, higher): + terrain_list.move_child(item1, higher) + item1.terrain = BetterTerrain.get_terrain(tileset, higher) + item1.update() + item2.terrain = BetterTerrain.get_terrain(tileset, lower) + item2.update() + selected_entry = index2 + terrain_list.get_child(index2).set_selected(true) + update_tile_view_paint() + + +func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void: + if index >= terrain_list.get_child_count(): + return + var entry = terrain_list.get_child(index) + # don't overwrite empty icon + var valid_icon = icon + if icon.has("path") and icon.path.is_empty(): + var terrain = BetterTerrain.get_terrain(tileset, index) + valid_icon = terrain.icon + if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon): + entry.terrain = BetterTerrain.get_terrain(tileset, index) + entry.update() + tile_view.queue_redraw() + + +func _on_shuffle_random_pressed(): + BetterTerrain.use_seed = !shuffle_random.button_pressed + + +func _on_bit_button_pressed(button: BaseButton) -> void: + match select_tiles.button_group.get_pressed_button(): + select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT + paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE + paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING + paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY + _: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT + tile_view.queue_redraw() + + symmetry_options.visible = paint_symmetry.button_pressed + + +func _on_symmetry_selected(index): + tile_view.paint_symmetry = index + + +func _on_paste_occurred(): + select_tiles.button_pressed = true + + +func _on_change_zoom_level(value): + zoom_slider.value = value + + +func _on_terrain_updated(index): + var entry = terrain_list.get_child(index) + entry.terrain = BetterTerrain.get_terrain(tileset, index) + entry.update() + + +func canvas_tilemap_transform() -> Transform2D: + if not tilemap: + return Transform2D.IDENTITY + + var transform := tilemap.get_viewport_transform() * tilemap.global_transform + + # Handle subviewport + var editor_viewport := EditorInterface.get_editor_viewport_2d() + if tilemap.get_viewport() != editor_viewport: + var container = tilemap.get_viewport().get_parent() as SubViewportContainer + if container: + transform = editor_viewport.global_canvas_transform * container.get_transform() * transform + + return transform + + +func canvas_draw(overlay: Control) -> void: + if not draw_overlay or not tilemap: + return + + if selected_entry < 0: + return + + var type = selected_entry + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + + var tiles := [] + var transform := canvas_tilemap_transform() + + if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT: + var area := Rect2i(initial_click, current_position - initial_click).abs() + + # Shortcut fill for large areas + if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES: + var shortcut := PackedVector2Array([ + tilemap.map_to_local(area.position), + tilemap.map_to_local(Vector2i(area.end.x, area.position.y)), + tilemap.map_to_local(area.end), + tilemap.map_to_local(Vector2i(area.position.x, area.end.y)) + ]) + overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5)) + return + + for y in range(area.position.y, area.end.y + 1): + for x in range(area.position.x, area.end.x + 1): + tiles.append(Vector2i(x, y)) + elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT: + var cells := _get_tileset_line(initial_click, current_position, tileset) + var shape = BetterTerrain.data.cell_polygon(tileset) + for c in cells: + var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c)) + overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5)) + elif fill_button.button_pressed: + tiles = _get_fill_cells(current_position) + if tiles.size() > MAX_CANVAS_RENDER_TILES: + tiles.resize(MAX_CANVAS_RENDER_TILES) + else: + tiles.append(current_position) + + var shape = BetterTerrain.data.cell_polygon(tileset) + for t in tiles: + var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t)) + overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5)) + + +func canvas_input(event: InputEvent) -> bool: + if not tilemap: + return false + if selected_entry < 0: + return false + + draw_overlay = true + if event is InputEventMouseMotion: + var tr := canvas_tilemap_transform() + var pos := tr.affine_inverse() * Vector2(event.position) + var event_position := tilemap.local_to_map(pos) + prev_position = current_position + if event_position == current_position: + return false + current_position = event_position + update_overlay.emit() + + var replace_mode = replace_button.button_pressed + + var released : bool = event is InputEventMouseButton and !event.pressed + if released: + terrain_undo.finish_action() + var type = selected_entry + if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT: + var area := Rect2i(initial_click, current_position - initial_click).abs() + # Fill from initial_target to target + undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap) + for y in range(area.position.y, area.end.y + 1): + for x in range(area.position.x, area.end.x + 1): + var coord := Vector2i(x, y) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type) + else: + undo_manager.add_do_method(tilemap, &"erase_cell", coord) + + undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area) + terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area) + undo_manager.commit_action() + update_overlay.emit() + elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT: + undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap) + var cells := _get_tileset_line(initial_click, current_position, tileset) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type) + elif paint_mode == PaintMode.ERASE: + for c in cells: + undo_manager.add_do_method(tilemap, &"erase_cell", c) + undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + update_overlay.emit() + + paint_mode = PaintMode.NO_PAINT + return true + + var clicked : bool = event is InputEventMouseButton and event.pressed + if clicked: + paint_mode = PaintMode.NO_PAINT + + if (event.is_command_or_control_pressed() and !event.shift_pressed): + var pick = BetterTerrain.get_cell(tilemap, current_position) + if pick >= 0: + terrain_list.get_children()[pick]._on_focus_entered() + #_on_entry_select(pick) + return true + + paint_action = PaintAction.NO_ACTION + if rectangle_button.button_pressed: + paint_action = PaintAction.RECT + elif line_button.button_pressed: + paint_action = PaintAction.LINE + elif draw_button.button_pressed: + if event.shift_pressed: + paint_action = PaintAction.LINE + if event.is_command_or_control_pressed(): + paint_action = PaintAction.RECT + + if event.button_index == MOUSE_BUTTON_LEFT: + paint_mode = PaintMode.PAINT + elif event.button_index == MOUSE_BUTTON_RIGHT: + paint_mode = PaintMode.ERASE + else: + return false + + if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT: + if clicked: + initial_click = current_position + terrain_undo.action_index += 1 + terrain_undo.action_count = 0 + var type = selected_entry + + if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT: + # if painting as line, execution happens on release. + # prevent other painting actions from running. + pass + elif draw_button.button_pressed: + undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true) + var cells := _get_tileset_line(prev_position, current_position, tileset) + if paint_mode == PaintMode.PAINT: + if replace_mode: + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type]) + else: + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type]) + elif paint_mode == PaintMode.ERASE: + for c in cells: + terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c]) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells]) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif fill_button.button_pressed: + var cells := _get_fill_cells(current_position) + undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap) + if paint_mode == PaintMode.PAINT: + if replace_mode: + undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type) + else: + undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type) + elif paint_mode == PaintMode.ERASE: + for c in cells: + undo_manager.add_do_method(tilemap, &"erase_cell", c) + undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells) + terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells) + undo_manager.commit_action() + + update_overlay.emit() + return true + + return false + + +func canvas_mouse_exit() -> void: + draw_overlay = false + update_overlay.emit() + + +func _shortcut_input(event) -> void: + if event is InputEventKey: + if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo): + get_viewport().set_input_as_handled() + tile_view.copy_selection() + if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo): + get_viewport().set_input_as_handled() + tile_view.paste_selection() + + +## bresenham alg ported from Geometry2D::bresenham_line() +func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]: + if from == to: + return [to] + + var points:Array[Vector2i] = [] + var delta := (to - from).abs() * 2 + var step := (to - from).sign() + var current := from + + if delta.x > delta.y: + var err:int = delta.x / 2 + while current.x != to.x: + points.push_back(current); + err -= delta.y + if err < 0: + current.y += step.y + err += delta.x + current.x += step.x + else: + var err:int = delta.y / 2 + while current.y != to.y: + points.push_back(current) + err -= delta.x + if err < 0: + current.x += step.x + err += delta.y + current.y += step.y + + points.push_back(current); + return points; + + +## half-offset bresenham alg ported from TileMapEditor::get_line +func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]: + if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE: + return _get_line(from, to) + + var points:Array[Vector2i] = [] + + var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL + if transposed: + from = Vector2i(from.y, from.x) + to = Vector2i(to.y, to.x) + + var delta:Vector2i = to - from + delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y) + var sign:Vector2i = delta.sign() + + var current := from; + points.push_back(Vector2i(current.y, current.x) if transposed else current) + + var err := 0 + if abs(delta.y) < abs(delta.x): + var err_step:Vector2i = 3 * delta.abs() + while current != to: + err += err_step.y + if err > abs(delta.x): + if sign.x == 0: + current += Vector2i(sign.y, 0) + else: + current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y) + err -= err_step.x + else: + current += Vector2i(sign.x, 0) + err += err_step.y + points.push_back(Vector2i(current.y, current.x) if transposed else current) + else: + var err_step:Vector2i = delta.abs() + while current != to: + err += err_step.x + if err > 0: + if sign.x == 0: + current += Vector2i(0, sign.y) + else: + current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y) + err -= err_step.y; + else: + if sign.x == 0: + current += Vector2i(0, sign.y) + else: + current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y) + err += err_step.y + points.push_back(Vector2i(current.y, current.x) if transposed else current) + + return points + + +func _on_terrain_enable_id_pressed(id): + if id in [SourceSelectors.ALL, SourceSelectors.NONE]: + for i in source_selector_popup.item_count: + if source_selector_popup.is_item_checkable(i): + source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL) + else: + var index = source_selector_popup.get_item_index(id) + var checked = source_selector_popup.is_item_checked(index) + source_selector_popup.set_item_checked(index, !checked) + + var disabled_sources : Array[int] + for i in source_selector_popup.item_count: + if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i): + disabled_sources.append(source_selector_popup.get_item_id(i)) + tile_view.disabled_sources = disabled_sources + + +func corresponding_tilemap_editor_button(similar: Button) -> Button: + var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false) + var tile_map_layer_editor = editors[0] + var buttons = tile_map_layer_editor.find_children("*", "Button", true, false) + for button: Button in buttons: + if button.icon == similar.icon: + return button + return null + + +func _on_layer_up_or_down_pressed(button: Button) -> void: + var matching_button = corresponding_tilemap_editor_button(button) + if !matching_button: + return + + # Major hack, to reduce flicker hide the tileset editor briefly + var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false) + var tile_set_editor = editors[0] + + matching_button.pressed.emit() + tile_set_editor.modulate = Color.TRANSPARENT + await get_tree().process_frame + await get_tree().process_frame + force_show_terrains.emit() + tile_set_editor.modulate = Color.WHITE + + + +func _on_layer_up_pressed() -> void: + _on_layer_up_or_down_pressed(layer_up) + + +func _on_layer_down_pressed() -> void: + _on_layer_up_or_down_pressed(layer_down) + + +func _on_layer_highlight_toggled(toggled: bool) -> void: + var settings = EditorInterface.get_editor_settings() + settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled) + + var highlight = corresponding_tilemap_editor_button(layer_highlight) + if highlight: + highlight.toggled.emit(toggled) + + +func _on_layer_grid_toggled(toggled: bool) -> void: + var settings = EditorInterface.get_editor_settings() + settings.set_setting("editors/tiles_editor/display_grid", toggled) + + var grid = corresponding_tilemap_editor_button(layer_grid) + if grid: + grid.toggled.emit(toggled) diff --git a/godot/addons/better-terrain/editor/Dock.gd.uid b/godot/addons/better-terrain/editor/Dock.gd.uid new file mode 100644 index 0000000..daa2961 --- /dev/null +++ b/godot/addons/better-terrain/editor/Dock.gd.uid @@ -0,0 +1 @@ +uid://dgoa61jt54fpq diff --git a/godot/addons/better-terrain/editor/Dock.tscn b/godot/addons/better-terrain/editor/Dock.tscn new file mode 100644 index 0000000..f659343 --- /dev/null +++ b/godot/addons/better-terrain/editor/Dock.tscn @@ -0,0 +1,405 @@ +[gd_scene load_steps=33 format=3 uid="uid://de8b6h6ieal7r"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/Dock.gd" id="1_raoha"] +[ext_resource type="Texture2D" uid="uid://c6lxq2y7mpb18" path="res://addons/better-terrain/icons/EditType.svg" id="2_cpm2t"] +[ext_resource type="Texture2D" uid="uid://y3xy6qdckht6" path="res://addons/better-terrain/icons/Replace.svg" id="2_fvmt6"] +[ext_resource type="Texture2D" uid="uid://bo2cjv08jkvf8" path="res://addons/better-terrain/icons/EditTerrain.svg" id="3_pqb1p"] +[ext_resource type="Texture2D" uid="uid://b0es228gfcykd" path="res://addons/better-terrain/icons/Warning.svg" id="4_6ahwe"] +[ext_resource type="Script" path="res://addons/better-terrain/editor/TileView.gd" id="4_nqppq"] +[ext_resource type="Texture2D" uid="uid://co6gwwmog0pjy" path="res://addons/better-terrain/icons/EditSymmetry.svg" id="5_kfjwu"] +[ext_resource type="Texture2D" uid="uid://cs4mdmluiydj6" path="res://addons/better-terrain/icons/ShuffleRandom.svg" id="5_n3owo"] +[ext_resource type="Texture2D" uid="uid://5hm3bfj3dvej" path="res://addons/better-terrain/icons/SymmetryMirror.svg" id="6_mofuh"] +[ext_resource type="Texture2D" uid="uid://dqmc1jp56or8m" path="res://addons/better-terrain/icons/SymmetryFlip.svg" id="7_ojxs0"] +[ext_resource type="Texture2D" uid="uid://cxoewno1cefua" path="res://addons/better-terrain/icons/SymmetryReflect.svg" id="8_8dhyg"] +[ext_resource type="Texture2D" uid="uid://baxhjy28r1iqj" path="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg" id="9_tq76a"] +[ext_resource type="Texture2D" uid="uid://csbwdkr6bc2db" path="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg" id="10_o5h1f"] +[ext_resource type="Texture2D" uid="uid://8mcycyl3e66r" path="res://addons/better-terrain/icons/SymmetryRotate180.svg" id="11_m6syp"] +[ext_resource type="Texture2D" uid="uid://b7fx4mk18lmls" path="res://addons/better-terrain/icons/SymmetryRotateAll.svg" id="12_11vru"] +[ext_resource type="Texture2D" uid="uid://cyjra4g05dwh" path="res://addons/better-terrain/icons/SymmetryAll.svg" id="13_lp5m2"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_aon7c"] + +[sub_resource type="InputEventKey" id="InputEventKey_saph6"] +device = -1 +keycode = 68 +unicode = 100 + +[sub_resource type="Shortcut" id="Shortcut_3k2al"] +events = [SubResource("InputEventKey_saph6")] + +[sub_resource type="Image" id="Image_3r1gs"] +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_v6msm"] +image = SubResource("Image_3r1gs") + +[sub_resource type="InputEventKey" id="InputEventKey_q1v0d"] +device = -1 +keycode = 76 +unicode = 108 + +[sub_resource type="Shortcut" id="Shortcut_wc6bu"] +events = [SubResource("InputEventKey_q1v0d")] + +[sub_resource type="InputEventKey" id="InputEventKey_68n3h"] +device = -1 +keycode = 82 +unicode = 114 + +[sub_resource type="InputEventKey" id="InputEventKey_qcu1e"] +device = -1 +keycode = 67 +unicode = 99 + +[sub_resource type="Shortcut" id="Shortcut_tcjet"] +events = [SubResource("InputEventKey_68n3h"), SubResource("InputEventKey_qcu1e")] + +[sub_resource type="InputEventKey" id="InputEventKey_grxy4"] +device = -1 +keycode = 66 +unicode = 98 + +[sub_resource type="Shortcut" id="Shortcut_46fac"] +events = [SubResource("InputEventKey_grxy4")] + +[sub_resource type="InputEventKey" id="InputEventKey_xd61m"] +device = -1 +keycode = 80 +unicode = 112 + +[sub_resource type="Shortcut" id="Shortcut_uwwa1"] +events = [SubResource("InputEventKey_xd61m")] + +[sub_resource type="ButtonGroup" id="ButtonGroup_3wrxn"] +allow_unpress = true + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mpeb7"] +bg_color = Color(0, 0, 0, 0.4) + +[node name="Dock" type="Control" node_paths=PackedStringArray("shortcut_context")] +custom_minimum_size = Vector2(0, 100) +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +focus_mode = 2 +shortcut_context = NodePath(".") +script = ExtResource("1_raoha") + +[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="Toolbar" type="HBoxContainer" parent="VBox"] +layout_mode = 2 + +[node name="Draw" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Draw terrain +Shift: Draw line. +Ctrl/Cmd+Shift: Draw rectangle." +toggle_mode = true +button_pressed = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_3k2al") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Line" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Draw line" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_wc6bu") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Rectangle" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Fill a rectangle of terrain" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_tcjet") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Fill" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Bucket fill terrain" +toggle_mode = true +button_group = SubResource("ButtonGroup_aon7c") +shortcut = SubResource("Shortcut_46fac") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Replace" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Toggle replace mode" +toggle_mode = true +shortcut = SubResource("Shortcut_uwwa1") +icon = ExtResource("2_fvmt6") + +[node name="VSeparator" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="SelectTiles" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="PaintType" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint terrain types" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("2_cpm2t") +flat = true + +[node name="PaintTerrain" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint terrain connecting types" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("3_pqb1p") +flat = true + +[node name="PaintSymmetry" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Paint tile symmetry" +toggle_mode = true +button_group = SubResource("ButtonGroup_3wrxn") +icon = ExtResource("5_kfjwu") +flat = true + +[node name="SymmetryOptions" type="OptionButton" parent="VBox/Toolbar"] +visible = false +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +selected = 0 +item_count = 9 +popup/item_0/text = "No symmetry" +popup/item_0/id = 8 +popup/item_1/text = "Mirror" +popup/item_1/icon = ExtResource("6_mofuh") +popup/item_1/id = 1 +popup/item_2/text = "Flip" +popup/item_2/icon = ExtResource("7_ojxs0") +popup/item_2/id = 1 +popup/item_3/text = "Reflect" +popup/item_3/icon = ExtResource("8_8dhyg") +popup/item_3/id = 2 +popup/item_4/text = "Rotate clockwise" +popup/item_4/icon = ExtResource("9_tq76a") +popup/item_4/id = 3 +popup/item_5/text = "Rotate counter-clockwise" +popup/item_5/icon = ExtResource("10_o5h1f") +popup/item_5/id = 4 +popup/item_6/text = "Rotate 180" +popup/item_6/icon = ExtResource("11_m6syp") +popup/item_6/id = 5 +popup/item_7/text = "All rotations" +popup/item_7/icon = ExtResource("12_11vru") +popup/item_7/id = 6 +popup/item_8/text = "All reflections & rotations" +popup/item_8/icon = ExtResource("13_lp5m2") +popup/item_8/id = 7 + +[node name="VSeparator3" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="ZoomContainer" type="VBoxContainer" parent="VBox/Toolbar"] +layout_mode = 2 +alignment = 1 + +[node name="Sources" type="MenuBar" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="Sources" type="PopupMenu" parent="VBox/Toolbar/Sources"] +hide_on_item_selection = false +hide_on_checkable_item_selection = false + +[node name="Spacer" type="Control" parent="VBox/Toolbar"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ShuffleRandom" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Shuffle random tiles each update" +toggle_mode = true +icon = ExtResource("5_n3owo") +flat = true + +[node name="Clean" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +text = "Clean tile data" +icon = ExtResource("4_6ahwe") + +[node name="VSeparator2" type="VSeparator" parent="VBox/Toolbar"] +layout_mode = 2 + +[node name="LayerUp" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select previous layer" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerDown" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Select next layer" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerHighlight" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Highlight selected layer" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="LayerGrid" type="Button" parent="VBox/Toolbar"] +layout_mode = 2 +tooltip_text = "Toggle grid visibility" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="HSplit" type="HSplitContainer" parent="VBox"] +layout_mode = 2 +size_flags_vertical = 3 +split_offset = 325 + +[node name="Terrains" type="VBoxContainer" parent="VBox/HSplit"] +layout_mode = 2 + +[node name="Panel" type="PanelContainer" parent="VBox/HSplit/Terrains"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_mpeb7") + +[node name="ScrollContainer" type="ScrollContainer" parent="VBox/HSplit/Terrains/Panel"] +layout_mode = 2 +horizontal_scroll_mode = 3 + +[node name="TerrainList" type="HFlowContainer" parent="VBox/HSplit/Terrains/Panel/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="LowerToolbar" type="HBoxContainer" parent="VBox/HSplit/Terrains"] +layout_mode = 2 + +[node name="GridMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 +tooltip_text = "Toggle grid view" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="QuickMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"] +auto_translate_mode = 1 +layout_mode = 2 +tooltip_text = "Toggle quick mode. Only shows paintable terrain types." +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="VSeparator" type="VSeparator" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 + +[node name="EditTools" type="HBoxContainer" parent="VBox/HSplit/Terrains/LowerToolbar"] +layout_mode = 2 +size_flags_horizontal = 3 +alignment = 2 + +[node name="AddTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Add terrain type" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="EditTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Edit terrain type" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="PickIcon" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Pick terrain icon from tileset" +toggle_mode = true +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="MoveUp" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Move selected terrain up" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="MoveDown" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Move selected terrain down" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="RemoveTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"] +layout_mode = 2 +tooltip_text = "Remove selected terrain type(s)" +icon = SubResource("ImageTexture_v6msm") +flat = true + +[node name="Panel" type="Panel" parent="VBox/HSplit"] +custom_minimum_size = Vector2(0, 80) +layout_mode = 2 + +[node name="ScrollArea" type="ScrollContainer" parent="VBox/HSplit/Panel"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 + +[node name="TileView" type="Control" parent="VBox/HSplit/Panel/ScrollArea"] +texture_filter = 1 +texture_repeat = 1 +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +focus_mode = 2 +script = ExtResource("4_nqppq") + +[connection signal="item_selected" from="VBox/Toolbar/SymmetryOptions" to="." method="_on_symmetry_selected"] +[connection signal="id_pressed" from="VBox/Toolbar/Sources/Sources" to="." method="_on_terrain_enable_id_pressed"] +[connection signal="pressed" from="VBox/Toolbar/ShuffleRandom" to="." method="_on_shuffle_random_pressed"] +[connection signal="pressed" from="VBox/Toolbar/Clean" to="." method="_on_clean_pressed"] +[connection signal="pressed" from="VBox/Toolbar/LayerUp" to="." method="_on_layer_up_pressed"] +[connection signal="pressed" from="VBox/Toolbar/LayerDown" to="." method="_on_layer_down_pressed"] +[connection signal="toggled" from="VBox/Toolbar/LayerHighlight" to="." method="_on_layer_highlight_toggled"] +[connection signal="toggled" from="VBox/Toolbar/LayerGrid" to="." method="_on_layer_grid_toggled"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/GridMode" to="." method="_on_grid_mode_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/QuickMode" to="." method="_on_quick_mode_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain" to="." method="_on_add_terrain_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain" to="." method="_on_edit_terrain_pressed"] +[connection signal="focus_exited" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_focus_exited"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_pressed"] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp" to="." method="_on_move_pressed" binds= [false]] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown" to="." method="_on_move_pressed" binds= [true]] +[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain" to="." method="_on_remove_terrain_pressed"] +[connection signal="mouse_exited" from="VBox/HSplit/Panel/ScrollArea/TileView" to="VBox/HSplit/Panel/ScrollArea/TileView" method="clear_highlighted_tile"] diff --git a/godot/addons/better-terrain/editor/TerrainEntry.gd b/godot/addons/better-terrain/editor/TerrainEntry.gd new file mode 100644 index 0000000..a937f95 --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainEntry.gd @@ -0,0 +1,185 @@ +@tool +extends PanelContainer + +signal select(index) + +@onready var color_panel := %Color +@onready var terrain_icon_slot := %TerrainIcon +@onready var type_icon_slot := %TypeIcon +@onready var type_icon_panel := %TerrainIconPanel +@onready var name_label := %Name +@onready var layout_container := %Layout +@onready var icon_layout_container := %IconLayout + +var selected := false + +var tileset:TileSet +var terrain:Dictionary + +var grid_mode := false +var color_style_list:StyleBoxFlat +var color_style_grid:StyleBoxFlat +var color_style_decoration:StyleBoxFlat + +var _terrain_texture:Texture2D +var _terrain_texture_rect:Rect2i +var _icon_draw_connected := false + + +func _ready(): + update() + +func update(): + if !terrain or !terrain.valid: + return + if !tileset: + return + + name_label.text = terrain.name + tooltip_text = "%s (%d)" % [terrain.name, terrain.id] + + color_style_list = color_panel.get_theme_stylebox("panel").duplicate() + color_style_grid = color_panel.get_theme_stylebox("panel").duplicate() + color_style_decoration = color_panel.get_theme_stylebox("panel").duplicate() + + color_style_list.bg_color = terrain.color + color_style_list.corner_radius_top_left = 8 + color_style_list.corner_radius_bottom_left = 8 + color_style_list.corner_radius_top_right = 0 + color_style_list.corner_radius_bottom_right = 0 + color_style_list.content_margin_left = -1 + color_style_list.content_margin_right = -1 + color_style_list.border_width_left = 0 + color_style_list.border_width_right = 0 + color_style_list.border_width_top = 0 + color_style_list.border_width_bottom = 0 + + color_style_grid.bg_color = terrain.color + color_style_grid.corner_radius_top_left = 6 + color_style_grid.corner_radius_bottom_left = 6 + color_style_grid.corner_radius_top_right = 6 + color_style_grid.corner_radius_bottom_right = 6 + color_style_grid.content_margin_left = -1 + color_style_grid.content_margin_right = -1 + color_style_grid.border_width_left = 0 + color_style_grid.border_width_right = 0 + color_style_grid.border_width_top = 0 + color_style_grid.border_width_bottom = 0 + + color_style_decoration.bg_color = terrain.color + color_style_decoration.corner_radius_top_left = 8 + color_style_decoration.corner_radius_bottom_left = 8 + color_style_decoration.corner_radius_top_right = 8 + color_style_decoration.corner_radius_bottom_right = 8 + color_style_decoration.content_margin_left = -1 + color_style_decoration.content_margin_right = -1 + color_style_decoration.border_width_left = 4 + color_style_decoration.border_width_right = 4 + color_style_decoration.border_width_top = 4 + color_style_decoration.border_width_bottom = 4 + + match terrain.type: + BetterTerrain.TerrainType.MATCH_TILES: + type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchTiles.svg") + BetterTerrain.TerrainType.MATCH_VERTICES: + type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchVertices.svg") + BetterTerrain.TerrainType.CATEGORY: + type_icon_slot.texture = load("res://addons/better-terrain/icons/NonModifying.svg") + BetterTerrain.TerrainType.DECORATION: + type_icon_slot.texture = load("res://addons/better-terrain/icons/Decoration.svg") + + var has_icon = false + if terrain.has("icon"): + if terrain.icon.has("path") and not terrain.icon.path.is_empty(): + terrain_icon_slot.texture = load(terrain.icon.path) + _terrain_texture = null + terrain_icon_slot.queue_redraw() + has_icon = true + elif terrain.icon.has("source_id") and tileset.has_source(terrain.icon.source_id): + var source := tileset.get_source(terrain.icon.source_id) as TileSetAtlasSource + var coord := terrain.icon.coord as Vector2i + var rect := source.get_tile_texture_region(coord, 0) + _terrain_texture = source.texture + _terrain_texture_rect = rect + terrain_icon_slot.queue_redraw() + has_icon = true + + if not has_icon: + var tiles = BetterTerrain.get_tile_sources_in_terrain(tileset, get_index()) + if tiles.size() > 0: + var source := tiles[0].source as TileSetAtlasSource + var coord := tiles[0].coord as Vector2i + var rect := source.get_tile_texture_region(coord, 0) + _terrain_texture = source.texture + _terrain_texture_rect = rect + terrain_icon_slot.queue_redraw() + + if _terrain_texture: + terrain_icon_slot.texture = null + + if not _icon_draw_connected: + terrain_icon_slot.connect("draw", func(): + if _terrain_texture: + terrain_icon_slot.draw_texture_rect_region(_terrain_texture, Rect2i(0,0, 44, 44), _terrain_texture_rect) + ) + _icon_draw_connected = true + + update_style() + + +func update_style(): + if terrain.type == BetterTerrain.TerrainType.DECORATION: + type_icon_panel.visible = false + color_panel.custom_minimum_size = Vector2i(52,52) + else: + type_icon_panel.visible = true + color_panel.custom_minimum_size = Vector2i(24,24) + + if grid_mode: + if terrain.type == BetterTerrain.TerrainType.DECORATION: + color_panel.add_theme_stylebox_override("panel", color_style_decoration) + color_panel.size_flags_vertical = Control.SIZE_FILL + icon_layout_container.size_flags_vertical = Control.SIZE_EXPAND_FILL + else: + color_panel.add_theme_stylebox_override("panel", color_style_grid) + color_panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + icon_layout_container.size_flags_vertical = Control.SIZE_FILL + custom_minimum_size = Vector2(0, 60) + size_flags_horizontal = Control.SIZE_FILL + layout_container.vertical = true + name_label.visible = false + icon_layout_container.add_theme_constant_override("separation", -24) + else: + if terrain.type == BetterTerrain.TerrainType.DECORATION: + color_panel.add_theme_stylebox_override("panel", color_style_decoration) + else: + color_panel.add_theme_stylebox_override("panel", color_style_list) + icon_layout_container.size_flags_vertical = Control.SIZE_FILL + custom_minimum_size = Vector2(2000, 60) + size_flags_horizontal = Control.SIZE_EXPAND_FILL + layout_container.vertical = false + name_label.visible = true + color_panel.size_flags_vertical = Control.SIZE_FILL + icon_layout_container.add_theme_constant_override("separation", 4) + + +func set_selected(value:bool = true): + selected = value + if value: + select.emit(get_index()) + queue_redraw() + + +func _draw(): + if selected: + draw_rect(Rect2(Vector2.ZERO, get_rect().size), Color(0.15, 0.70, 1, 0.3)) + + +func _on_focus_entered(): + queue_redraw() + selected = true + select.emit(get_index()) + + +func _on_focus_exited(): + queue_redraw() diff --git a/godot/addons/better-terrain/editor/TerrainEntry.gd.uid b/godot/addons/better-terrain/editor/TerrainEntry.gd.uid new file mode 100644 index 0000000..aa1f05b --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainEntry.gd.uid @@ -0,0 +1 @@ +uid://crxmsm22lowxx diff --git a/godot/addons/better-terrain/editor/TerrainEntry.tscn b/godot/addons/better-terrain/editor/TerrainEntry.tscn new file mode 100644 index 0000000..3973bbf --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainEntry.tscn @@ -0,0 +1,114 @@ +[gd_scene load_steps=8 format=3 uid="uid://u2y444hj182c"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/TerrainEntry.gd" id="1_o2na3"] +[ext_resource type="Texture2D" uid="uid://kmypxsqhynyv" path="res://addons/better-terrain/icons/Decoration.svg" id="2_ossyj"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3pdcc"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +draw_center = false + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dqhir"] +bg_color = Color(0.243, 0.816, 0.518, 1) +border_color = Color(0, 0, 0, 0.439216) +corner_radius_top_left = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rohyw"] +content_margin_left = 2.0 +content_margin_top = 2.0 +content_margin_right = 2.0 +content_margin_bottom = 2.0 +bg_color = Color(0, 0, 0, 0.439216) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xa0fl"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0, 0, 0, 0.439216) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b4rkm"] +content_margin_left = 3.0 +bg_color = Color(0, 0, 0, 0.439216) +draw_center = false + +[node name="TerrainEntry" type="PanelContainer"] +custom_minimum_size = Vector2(60, 60) +offset_right = 200.0 +offset_bottom = 60.0 +size_flags_vertical = 3 +focus_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_3pdcc") +script = ExtResource("1_o2na3") + +[node name="Layout" type="BoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="IconLayout" type="HBoxContainer" parent="Layout"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="Color" type="PanelContainer" parent="Layout/IconLayout"] +unique_name_in_owner = true +z_index = 1 +custom_minimum_size = Vector2(24, 24) +layout_mode = 2 +size_flags_horizontal = 0 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_dqhir") + +[node name="PanelContainer" type="PanelContainer" parent="Layout/IconLayout/Color"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_rohyw") + +[node name="TypeIcon" type="TextureRect" parent="Layout/IconLayout/Color/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +texture = ExtResource("2_ossyj") + +[node name="TerrainIconPanel" type="PanelContainer" parent="Layout/IconLayout"] +unique_name_in_owner = true +custom_minimum_size = Vector2(52, 52) +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +mouse_filter = 1 +theme_override_styles/panel = SubResource("StyleBoxFlat_xa0fl") + +[node name="TerrainIcon" type="TextureRect" parent="Layout/IconLayout/TerrainIconPanel"] +unique_name_in_owner = true +texture_filter = 1 +custom_minimum_size = Vector2(40, 40) +layout_mode = 2 +expand_mode = 4 +stretch_mode = 5 + +[node name="Name" type="Label" parent="Layout"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/outline_size = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_b4rkm") +text = "New Terrain" +vertical_alignment = 1 +text_overrun_behavior = 3 + +[connection signal="focus_entered" from="." to="." method="_on_focus_entered"] +[connection signal="focus_exited" from="." to="." method="_on_focus_exited"] diff --git a/godot/addons/better-terrain/editor/TerrainProperties.gd b/godot/addons/better-terrain/editor/TerrainProperties.gd new file mode 100644 index 0000000..09b3170 --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainProperties.gd @@ -0,0 +1,85 @@ +@tool +extends ConfirmationDialog + +var category_icon := load("res://addons/better-terrain/icons/NonModifying.svg") + +const CATEGORY_CHECK_ID = &"category_check_id" + +var accepted := false + +var terrain_name : String: + set(value): %NameEdit.text = value + get: return %NameEdit.text + +var terrain_color : Color: + set(value): %ColorPicker.color = value + get: return %ColorPicker.color + +var terrain_icon : String: + set(value): %IconEdit.text = value + get: return %IconEdit.text + +var terrain_type : int: + set(value): + %TypeOption.selected = value + _on_type_option_item_selected(value) + get: return %TypeOption.selected + +var terrain_categories : Array: set = set_categories, get = get_categories + + +# category is name, color, id +func set_category_data(options: Array) -> void: + if !options.is_empty(): + %CategoryLabel.show() + %CategoryContainer.show() + + for o in options: + var c = CheckBox.new() + c.text = o.name + c.icon = category_icon + c.add_theme_color_override(&"icon_normal_color", o.color) + c.add_theme_color_override(&"icon_disabled_color", Color(o.color, 0.4)) + c.add_theme_color_override(&"icon_focus_color", o.color) + c.add_theme_color_override(&"icon_hover_color", o.color) + c.add_theme_color_override(&"icon_hover_pressed_color", o.color) + c.add_theme_color_override(&"icon_normal_color", o.color) + c.add_theme_color_override(&"icon_pressed_color", o.color) + + c.set_meta(CATEGORY_CHECK_ID, o.id) + %CategoryLayout.add_child(c) + + +func set_categories(ids : Array): + for c in %CategoryLayout.get_children(): + c.button_pressed = c.get_meta(CATEGORY_CHECK_ID) in ids + + +func get_categories() -> Array: + var result := [] + if terrain_type == BetterTerrain.TerrainType.CATEGORY: + return result + for c in %CategoryLayout.get_children(): + if c.button_pressed: + result.push_back(c.get_meta(CATEGORY_CHECK_ID)) + return result + + +func _on_confirmed() -> void: + # confirm valid name + if terrain_name.is_empty(): + var dialog := AcceptDialog.new() + dialog.dialog_text = "Name cannot be empty" + EditorInterface.popup_dialog_centered(dialog) + await dialog.visibility_changed + dialog.queue_free() + return + + accepted = true + hide() + + +func _on_type_option_item_selected(index: int) -> void: + var categories_available = (index != BetterTerrain.TerrainType.CATEGORY) + for c in %CategoryLayout.get_children(): + c.disabled = !categories_available diff --git a/godot/addons/better-terrain/editor/TerrainProperties.gd.uid b/godot/addons/better-terrain/editor/TerrainProperties.gd.uid new file mode 100644 index 0000000..5603beb --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainProperties.gd.uid @@ -0,0 +1 @@ +uid://b4b16t0vx621a diff --git a/godot/addons/better-terrain/editor/TerrainProperties.tscn b/godot/addons/better-terrain/editor/TerrainProperties.tscn new file mode 100644 index 0000000..43fdd2b --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainProperties.tscn @@ -0,0 +1,93 @@ +[gd_scene load_steps=5 format=3 uid="uid://fdjybw6e7whr"] + +[ext_resource type="Script" path="res://addons/better-terrain/editor/TerrainProperties.gd" id="1_52nx8"] +[ext_resource type="Texture2D" uid="uid://d1h1p7pcwdnjk" path="res://addons/better-terrain/icons/MatchTiles.svg" id="2_ncc5p"] +[ext_resource type="Texture2D" uid="uid://dfemy1g6okwlv" path="res://addons/better-terrain/icons/MatchVertices.svg" id="3_0nvmi"] +[ext_resource type="Texture2D" uid="uid://1yr6yruwl63u" path="res://addons/better-terrain/icons/NonModifying.svg" id="5_awp83"] + +[node name="TerrainProperties" type="ConfirmationDialog"] +title = "Edit terrain properties" +initial_position = 2 +size = Vector2i(317, 257) +visible = true +dialog_hide_on_ok = false +script = ExtResource("1_52nx8") + +[node name="GridContainer" type="GridContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 309.0 +offset_bottom = 208.0 +columns = 2 + +[node name="NameLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Name" + +[node name="NameEdit" type="LineEdit" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Terrain name" + +[node name="ColorLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Color" + +[node name="ColorPicker" type="ColorPickerButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +color = Color(1, 0.262745, 0.498039, 1) +edit_alpha = false + +[node name="IconLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Icon" + +[node name="IconEdit" type="LineEdit" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Icon path (optional)" + +[node name="TypeLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Mode" + +[node name="TypeOption" type="OptionButton" parent="GridContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +item_count = 3 +popup/item_0/text = "Match tiles" +popup/item_0/icon = ExtResource("2_ncc5p") +popup/item_1/text = "Match vertices" +popup/item_1/icon = ExtResource("3_0nvmi") +popup/item_1/id = 1 +popup/item_2/text = "Category" +popup/item_2/icon = ExtResource("5_awp83") +popup/item_2/id = 2 + +[node name="CategoryLabel" type="Label" parent="GridContainer"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_vertical = 1 +text = "Categories" + +[node name="CategoryContainer" type="ScrollContainer" parent="GridContainer"] +unique_name_in_owner = true +visible = false +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="CategoryLayout" type="VBoxContainer" parent="GridContainer/CategoryContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +size_flags_vertical = 3 + +[connection signal="confirmed" from="." to="." method="_on_confirmed"] +[connection signal="item_selected" from="GridContainer/TypeOption" to="." method="_on_type_option_item_selected"] diff --git a/godot/addons/better-terrain/editor/TerrainUndo.gd b/godot/addons/better-terrain/editor/TerrainUndo.gd new file mode 100644 index 0000000..201d7df --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainUndo.gd @@ -0,0 +1,190 @@ +@tool +extends Node + +var action_index := 0 +var action_count := 0 +var _current_action_index := 0 +var _current_action_count := 0 + +func create_tile_restore_point(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, cells: Array, and_surrounding_cells: bool = true) -> void: + if and_surrounding_cells: + cells = BetterTerrain._widen(tm, cells) + + var restore := [] + for c in cells: + restore.append([ + c, + tm.get_cell_source_id(c), + tm.get_cell_atlas_coords(c), + tm.get_cell_alternative_tile(c) + ]) + + undo_manager.add_undo_method(self, &"restore_tiles", tm, restore) + + +func create_tile_restore_point_area(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, area: Rect2i, and_surrounding_cells: bool = true) -> void: + area.end += Vector2i.ONE + + var restore := [] + for y in range(area.position.y, area.end.y): + for x in range(area.position.x, area.end.x): + var c := Vector2i(x, y) + restore.append([ + c, + tm.get_cell_source_id(c), + tm.get_cell_atlas_coords(c), + tm.get_cell_alternative_tile(c) + ]) + + undo_manager.add_undo_method(self, &"restore_tiles", tm, restore) + + if !and_surrounding_cells: + return + + var edges := [] + for x in range(area.position.x, area.end.x): + edges.append(Vector2i(x, area.position.y)) + edges.append(Vector2i(x, area.end.y)) + for y in range(area.position.y + 1, area.end.y - 1): + edges.append(Vector2i(area.position.x, y)) + edges.append(Vector2i(area.end.x, y)) + + edges = BetterTerrain._widen_with_exclusion(tm, edges, area) + create_tile_restore_point(undo_manager, tm, edges, false) + + +func restore_tiles(tm: TileMapLayer, restore: Array) -> void: + for r in restore: + tm.set_cell(r[0], r[1], r[2], r[3]) + + +func create_peering_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void: + var restore := [] + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + if tile_type == BetterTerrain.TileCategory.NON_TERRAIN: + continue + + var peering_dict := {} + for c in BetterTerrain.tile_peering_keys(td): + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func create_peering_restore_point_specific(undo_manager: EditorUndoRedoManager, ts: TileSet, protect: int) -> void: + var restore := [] + + for s in ts.get_source_count(): + var source_id := ts.get_source_id(s) + var source := ts.get_source(source_id) as TileSetAtlasSource + if !source: + continue + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + for a in source.get_alternative_tiles_count(coord): + var alternate := source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + if tile_type == BetterTerrain.TileCategory.NON_TERRAIN: + continue + + var to_restore : bool = tile_type == protect + + var terrain := BetterTerrain.get_terrain(ts, tile_type) + var cells = BetterTerrain.data.get_terrain_peering_cells(ts, terrain.type) + for c in cells: + if protect in BetterTerrain.tile_peering_types(td, c): + to_restore = true + break + + if !to_restore: + continue + + var peering_dict := {} + for c in cells: + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func create_peering_restore_point_tile(undo_manager: EditorUndoRedoManager, ts: TileSet, source_id: int, coord: Vector2i, alternate: int) -> void: + var source := ts.get_source(source_id) as TileSetAtlasSource + var td := source.get_tile_data(coord, alternate) + var tile_type := BetterTerrain.get_tile_terrain_type(td) + + var restore := [] + var peering_dict := {} + for c in BetterTerrain.tile_peering_keys(td): + peering_dict[c] = BetterTerrain.tile_peering_types(td, c) + var symmetry = BetterTerrain.get_tile_symmetry_type(td) + restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry]) + + undo_manager.add_undo_method(self, &"restore_peering", ts, restore) + + +func restore_peering(ts: TileSet, restore: Array) -> void: + for r in restore: + var source := ts.get_source(r[0]) as TileSetAtlasSource + var td := source.get_tile_data(r[1], r[2]) + BetterTerrain.set_tile_terrain_type(ts, td, r[3]) + var peering_types = r[4] + for peering in peering_types: + var types := BetterTerrain.tile_peering_types(td, peering) + for t in types: + BetterTerrain.remove_tile_peering_type(ts, td, peering, t) + for t in peering_types[peering]: + BetterTerrain.add_tile_peering_type(ts, td, peering, t) + var symmetry = r[5] + BetterTerrain.set_tile_symmetry_type(ts, td, symmetry) + + +func create_terrain_type_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void: + var count = BetterTerrain.terrain_count(ts) + var restore = [] + for i in count: + restore.push_back(BetterTerrain.get_terrain(ts, i)) + + undo_manager.add_undo_method(self, &"restore_terrain", ts, restore) + + +func restore_terrain(ts: TileSet, restore: Array) -> void: + for i in restore.size(): + var r = restore[i] + BetterTerrain.set_terrain(ts, i, r.name, r.color, r.type, r.categories, r.icon) + + +func add_do_method(undo_manager: EditorUndoRedoManager, object:Object, method:StringName, args:Array): + if action_index > _current_action_index: + _current_action_index = action_index + _current_action_count = action_count + if action_count > _current_action_count: + _current_action_count = action_count + undo_manager.add_do_method(self, "_do_method", object, method, args, action_count) + + +func _do_method(object:Object, method:StringName, args:Array, this_action_count:int): + if this_action_count >= _current_action_count: + object.callv(method, args) + + +func finish_action(): + _current_action_count = 0 diff --git a/godot/addons/better-terrain/editor/TerrainUndo.gd.uid b/godot/addons/better-terrain/editor/TerrainUndo.gd.uid new file mode 100644 index 0000000..f9a0f5c --- /dev/null +++ b/godot/addons/better-terrain/editor/TerrainUndo.gd.uid @@ -0,0 +1 @@ +uid://di6bxgay3uvku diff --git a/godot/addons/better-terrain/editor/TileView.gd b/godot/addons/better-terrain/editor/TileView.gd new file mode 100644 index 0000000..4947d83 --- /dev/null +++ b/godot/addons/better-terrain/editor/TileView.gd @@ -0,0 +1,899 @@ +@tool +extends Control + +signal paste_occurred +signal change_zoom_level(value) +signal terrain_updated(index) + +@onready var checkerboard := get_theme_icon("Checkerboard", "EditorIcons") + +@onready var paint_symmetry_icons := [ + null, + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg"), + preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg"), +] + +# Draw checkerboard and tiles with specific materials in +# individual canvas items via rendering server +var _canvas_item_map = {} +var _canvas_item_background : RID + +var tileset: TileSet +var disabled_sources: Array[int] = []: set = set_disabled_sources + +var paint := BetterTerrain.TileCategory.NON_TERRAIN +var paint_symmetry := BetterTerrain.SymmetryType.NONE +var highlighted_tile_part := { valid = false } +var zoom_level := 1.0 + +var tiles_size : Vector2 +var tile_size : Vector2i +var tile_part_size : Vector2 +var alternate_size : Vector2 +var alternate_lookup := [] +var initial_click : Vector2i +var prev_position : Vector2i +var current_position : Vector2i + +var selection_start : Vector2i +var selection_end : Vector2i +var selection_rect : Rect2i +var selected_tile_states : Array[Dictionary] = [] +var copied_tile_states : Array[Dictionary] = [] +var staged_paste_tile_states : Array[Dictionary] = [] + +var pick_icon_terrain : int = -1 +var pick_icon_terrain_cancel := false + +var undo_manager : EditorUndoRedoManager +var terrain_undo + +# Modes for painting +enum PaintMode { + NO_PAINT, + PAINT_TYPE, + PAINT_PEERING, + PAINT_SYMMETRY, + SELECT, + PASTE +} + +var paint_mode := PaintMode.NO_PAINT + +# Actual interactions for painting +enum PaintAction { + NO_ACTION, + DRAW_TYPE, + ERASE_TYPE, + DRAW_PEERING, + ERASE_PEERING, + DRAW_SYMMETRY, + ERASE_SYMMETRY, + SELECT, + PASTE +} + +var paint_action := PaintAction.NO_ACTION + +const ALTERNATE_TILE_MARGIN := 18 + +func _enter_tree() -> void: + _canvas_item_background = RenderingServer.canvas_item_create() + RenderingServer.canvas_item_set_parent(_canvas_item_background, get_canvas_item()) + RenderingServer.canvas_item_set_draw_behind_parent(_canvas_item_background, true) + + +func _exit_tree() -> void: + RenderingServer.free_rid(_canvas_item_background) + for p in _canvas_item_map: + RenderingServer.free_rid(_canvas_item_map[p]) + _canvas_item_map.clear() + + +func refresh_tileset(ts: TileSet) -> void: + tileset = ts + + tiles_size = Vector2.ZERO + alternate_size = Vector2.ZERO + alternate_lookup = [] + disabled_sources = disabled_sources.filter( + func(id): + return ts.has_source(id) + ) + + if !tileset: + return + + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source or !source.texture: + continue + + tiles_size.x = max(tiles_size.x, source.texture.get_width()) + tiles_size.y += source.texture.get_height() + + tile_size = source.texture_region_size + tile_part_size = Vector2(tile_size) / 3.0 + + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var alt_count := source.get_alternative_tiles_count(coord) + if alt_count <= 1: + continue + + var rect := source.get_tile_texture_region(coord, 0) + alternate_lookup.append([rect.size, source_id, coord]) + alternate_size.x = max(alternate_size.x, rect.size.x * (alt_count - 1)) + alternate_size.y += rect.size.y + + _on_zoom_value_changed(zoom_level) + + +func is_tile_in_source(source: TileSetAtlasSource, coord: Vector2i) -> bool: + var origin := source.get_tile_at_coords(coord) + if origin == Vector2i(-1, -1): + return false + + # Animation frames are not needed + var size := source.get_tile_size_in_atlas(origin) + return coord.x < origin.x + size.x and coord.y < origin.y + size.y + + +func _build_tile_part_from_position(result: Dictionary, position: Vector2i, rect: Rect2) -> void: + result.rect = rect + var type := BetterTerrain.get_tile_terrain_type(result.data) + if type == BetterTerrain.TileCategory.NON_TERRAIN: + return + result.terrain_type = type + + var normalize_position := (Vector2(position) - rect.position) / rect.size + + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type): + var side_polygon = BetterTerrain.data.peering_polygon(tileset, terrain.type, p) + if Geometry2D.is_point_in_polygon(normalize_position, side_polygon): + result.peering = p + result.polygon = side_polygon + break + + +func tile_part_from_position(position: Vector2i) -> Dictionary: + if !tileset: + return { valid = false } + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + if Rect2(alt_offset, zoom_level * alternate_size).has_point(position): + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var next_offset_y = alt_offset.y + zoom_level * a[0].y + if position.y > next_offset_y: + alt_offset.y = next_offset_y + continue + + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if !source: + break + + var count := source.get_alternative_tiles_count(a[2]) + var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1 + + if index < count: + var alt_id := source.get_alternative_tile_id(a[2], index) + var target_rect := Rect2( + alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x, + zoom_level * a[0] + ) + + var result := { + valid = true, + source_id = a[1], + coord = a[2], + alternate = alt_id, + data = source.get_tile_data(a[2], alt_id) + } + _build_tile_part_from_position(result, position, target_rect) + return result + + else: + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source || !source.texture: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + if !target_rect.has_point(position): + continue + + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = 0, + data = source.get_tile_data(coord, 0) + } + _build_tile_part_from_position(result, position, target_rect) + return result + + offset.y += zoom_level * source.texture.get_height() + + return { valid = false } + + +func tile_rect_from_position(position: Vector2i) -> Rect2: + if !tileset: + return Rect2(-1,-1,0,0) + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + if Rect2(alt_offset, zoom_level * alternate_size).has_point(position): + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var next_offset_y = alt_offset.y + zoom_level * a[0].y + if position.y > next_offset_y: + alt_offset.y = next_offset_y + continue + + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if !source: + break + + var count := source.get_alternative_tiles_count(a[2]) + var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1 + + if index < count: + var target_rect := Rect2( + alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x, + zoom_level * a[0] + ) + return target_rect + + else: + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + if target_rect.has_point(position): + return target_rect + + offset.y += zoom_level * source.texture.get_height() + + return Rect2(-1,-1,0,0) + + +func tile_parts_from_rect(rect:Rect2) -> Array[Dictionary]: + if !tileset: + return [] + + var tiles:Array[Dictionary] = [] + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source: + continue + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var tile_rect := source.get_tile_texture_region(coord, 0) + var target_rect := Rect2(offset + zoom_level * tile_rect.position, zoom_level * tile_rect.size) + if target_rect.intersects(rect): + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = 0, + data = source.get_tile_data(coord, 0) + } + var pos = target_rect.position + target_rect.size/2 + _build_tile_part_from_position(result, pos, target_rect) + tiles.push_back(result) + var alt_count := source.get_alternative_tiles_count(coord) + for a in alt_count: + var alt_id := 0 + if a == 0: + continue + + target_rect = Rect2(alt_offset + zoom_level * (a - 1) * tile_rect.size.x * Vector2.RIGHT, zoom_level * tile_rect.size) + alt_id = source.get_alternative_tile_id(coord, a) + if target_rect.intersects(rect): + var td := source.get_tile_data(coord, alt_id) + var result := { + valid = true, + source_id = source_id, + coord = coord, + alternate = alt_id, + data = td + } + var pos = target_rect.position + target_rect.size/2 + _build_tile_part_from_position(result, pos, target_rect) + tiles.push_back(result) + if alt_count > 1: + alt_offset.y += zoom_level * tile_rect.size.y + + offset.y += zoom_level * source.texture.get_height() + + return tiles + + +func _get_canvas_item(td: TileData) -> RID: + if !td.material: + return self.get_canvas_item() + if _canvas_item_map.has(td.material): + return _canvas_item_map[td.material] + + var rid = RenderingServer.canvas_item_create() + RenderingServer.canvas_item_set_material(rid, td.material.get_rid()) + RenderingServer.canvas_item_set_parent(rid, get_canvas_item()) + RenderingServer.canvas_item_set_draw_behind_parent(rid, true) + RenderingServer.canvas_item_set_default_texture_filter(rid, RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_NEAREST) + _canvas_item_map[td.material] = rid + return rid + + +func _draw_tile_data(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_sides: bool = true) -> void: + var flipped_rect := rect + if td.flip_h: + flipped_rect.size.x = -rect.size.x + if td.flip_v: + flipped_rect.size.y = -rect.size.y + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + flipped_rect, + texture.get_rid(), + src_rect, + td.modulate, + td.transpose + ) + + var type := BetterTerrain.get_tile_terrain_type(td) + if type == BetterTerrain.TileCategory.NON_TERRAIN: + draw_rect(rect, Color(0.1, 0.1, 0.1, 0.5), true) + return + + var terrain := BetterTerrain.get_terrain(tileset, type) + if !terrain.valid: + return + + var transform := Transform2D(0.0, rect.size, 0.0, rect.position) + var center_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, -1) + draw_colored_polygon(center_polygon, Color(terrain.color, 0.6)) + if terrain.type == BetterTerrain.TerrainType.DECORATION: + center_polygon.append(center_polygon[0]) + draw_polyline(center_polygon, Color.BLACK) + + if paint < BetterTerrain.TileCategory.EMPTY or paint >= BetterTerrain.terrain_count(tileset): + return + + if not draw_sides: + return + + var paint_terrain := BetterTerrain.get_terrain(tileset, paint) + for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type): + if paint in BetterTerrain.tile_peering_types(td, p): + var side_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, p) + draw_colored_polygon(side_polygon, Color(paint_terrain.color, 0.6)) + if paint_terrain.type == BetterTerrain.TerrainType.DECORATION: + side_polygon.append(side_polygon[0]) + draw_polyline(side_polygon, Color.BLACK) + + +func _draw_tile_symmetry(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_icon: bool = true) -> void: + var flipped_rect := rect + if td.flip_h: + flipped_rect.size.x = -rect.size.x + if td.flip_v: + flipped_rect.size.y = -rect.size.y + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + flipped_rect, + texture.get_rid(), + src_rect, + td.modulate, + td.transpose + ) + + if not draw_icon: + return + + var symmetry_type = BetterTerrain.get_tile_symmetry_type(td) + if symmetry_type == 0: + return + var symmetry_icon = paint_symmetry_icons[symmetry_type] + + RenderingServer.canvas_item_add_texture_rect_region( + _get_canvas_item(td), + rect, + symmetry_icon.get_rid(), + Rect2(Vector2.ZERO, symmetry_icon.get_size()), + Color(1,1,1,0.5) + ) + + +func _draw() -> void: + if !tileset: + return + + # Clear material-based render targets + RenderingServer.canvas_item_clear(_canvas_item_background) + for p in _canvas_item_map: + RenderingServer.canvas_item_clear(_canvas_item_map[p]) + + var offset := Vector2.ZERO + var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + + RenderingServer.canvas_item_add_texture_rect( + _canvas_item_background, + Rect2(alt_offset, zoom_level * alternate_size), + checkerboard.get_rid(), + true + ) + + for s in tileset.get_source_count(): + var source_id := tileset.get_source_id(s) + if source_id in disabled_sources: + continue + var source := tileset.get_source(source_id) as TileSetAtlasSource + if !source or !source.texture: + continue + + RenderingServer.canvas_item_add_texture_rect( + _canvas_item_background, + Rect2(offset, zoom_level * source.texture.get_size()), + checkerboard.get_rid(), + true + ) + for t in source.get_tiles_count(): + var coord := source.get_tile_id(t) + var rect := source.get_tile_texture_region(coord, 0) + var alt_count := source.get_alternative_tiles_count(coord) + var target_rect : Rect2 + for a in alt_count: + var alt_id := 0 + if a == 0: + target_rect = Rect2(offset + zoom_level * rect.position, zoom_level * rect.size) + else: + target_rect = Rect2(alt_offset + zoom_level * (a - 1) * rect.size.x * Vector2.RIGHT, zoom_level * rect.size) + alt_id = source.get_alternative_tile_id(coord, a) + + var td := source.get_tile_data(coord, alt_id) + var drawing_current = BetterTerrain.get_tile_terrain_type(td) == paint + if paint_mode == PaintMode.PAINT_SYMMETRY: + _draw_tile_symmetry(source.texture, target_rect, rect, td, drawing_current) + else: + _draw_tile_data(source.texture, target_rect, rect, td) + + if drawing_current: + draw_rect(target_rect.grow(-1), Color(0,0,0, 0.75), false, 1) + draw_rect(target_rect, Color(1,1,1, 0.75), false, 1) + + if paint_mode == PaintMode.SELECT: + if selected_tile_states.any(func(v): + return v.part.data == td + ): + draw_rect(target_rect.grow(-1), Color.DEEP_SKY_BLUE, false, 2) + + if alt_count > 1: + alt_offset.y += zoom_level * rect.size.y + + # Blank out unused or uninteresting tiles + var size := source.get_atlas_grid_size() + for y in size.y: + for x in size.x: + var pos := Vector2i(x, y) + if !is_tile_in_source(source, pos): + var atlas_pos := source.margins + pos * (source.separation + source.texture_region_size) + draw_rect(Rect2(offset + zoom_level * atlas_pos, zoom_level * source.texture_region_size), Color(0.0, 0.0, 0.0, 0.8), true) + + offset.y += zoom_level * source.texture.get_height() + + # Blank out unused alternate tile sections + alt_offset = Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN) + for a in alternate_lookup: + if a[1] in disabled_sources: + continue + var source := tileset.get_source(a[1]) as TileSetAtlasSource + if source: + var count := source.get_alternative_tiles_count(a[2]) - 1 + var occupied_width = count * zoom_level * a[0].x + var area := Rect2( + alt_offset.x + occupied_width, + alt_offset.y, + zoom_level * alternate_size.x - occupied_width, + zoom_level * a[0].y + ) + draw_rect(area, Color(0.0, 0.0, 0.0, 0.8), true) + alt_offset.y += zoom_level * a[0].y + + if highlighted_tile_part.valid: + if paint_mode == PaintMode.PAINT_PEERING and highlighted_tile_part.has("polygon"): + var transform := Transform2D(0.0, highlighted_tile_part.rect.size - 2 * Vector2.ONE, 0.0, highlighted_tile_part.rect.position + Vector2.ONE) + draw_colored_polygon(transform * highlighted_tile_part.polygon, Color(Color.WHITE, 0.2)) + if paint_mode != PaintMode.NO_PAINT: + var inner_rect := Rect2(highlighted_tile_part.rect.position + Vector2.ONE, highlighted_tile_part.rect.size - 2 * Vector2.ONE) + draw_rect(inner_rect, Color.WHITE, false) + if paint_mode == PaintMode.PAINT_SYMMETRY: + if paint_symmetry > 0: + var symmetry_icon = paint_symmetry_icons[paint_symmetry] + draw_texture_rect(symmetry_icon, highlighted_tile_part.rect, false, Color(0.5,0.75,1,0.5)) + + if paint_mode == PaintMode.SELECT: + draw_rect(selection_rect, Color.WHITE, false) + + if paint_mode == PaintMode.PASTE: + if staged_paste_tile_states.size() > 0: + var base_rect = staged_paste_tile_states[0].base_rect + var paint_terrain := BetterTerrain.get_terrain(tileset, paint) + var paint_terrain_type = paint_terrain.type + if paint_terrain_type == BetterTerrain.TerrainType.CATEGORY: + paint_terrain_type = 0 + for state in staged_paste_tile_states: + var staged_rect:Rect2 = state.base_rect + staged_rect.position -= base_rect.position + base_rect.size / 2 + + staged_rect.position *= zoom_level + staged_rect.size *= zoom_level + + staged_rect.position += Vector2(current_position) + + var real_rect = tile_rect_from_position(staged_rect.get_center()) + if real_rect.position.x >= 0: + draw_rect(real_rect, Color(0,0,0, 0.3), true) + var transform := Transform2D(0.0, real_rect.size, 0.0, real_rect.position) + var tile_sides = BetterTerrain.data.get_terrain_peering_cells(tileset, paint_terrain_type) + for p in tile_sides: + if state.paint in BetterTerrain.tile_peering_types(state.part.data, p): + var side_polygon = BetterTerrain.data.peering_polygon(tileset, paint_terrain_type, p) + var color = Color(paint_terrain.color, 0.6) + draw_colored_polygon(transform * side_polygon, color) + + draw_rect(staged_rect, Color.DEEP_PINK, false) + + + +func delete_selection(): + undo_manager.create_action("Delete tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset) + for t in selected_tile_states: + for side in range(16): + var old_peering = BetterTerrain.tile_peering_types(t.part.data, side) + if old_peering.has(paint): + undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, t.part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, t.part.data, side, paint) + + undo_manager.add_do_method(self, &"queue_redraw") + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + + +func toggle_selection(): + undo_manager.create_action("Toggle tile terrain", UndoRedo.MERGE_DISABLE, tileset, true) + for t in selected_tile_states: + var type := BetterTerrain.get_tile_terrain_type(t.part.data) + var goal := paint if paint != type else BetterTerrain.TileCategory.NON_TERRAIN + + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, t.part.data, goal]) + if goal == BetterTerrain.TileCategory.NON_TERRAIN: + terrain_undo.create_peering_restore_point_tile( + undo_manager, + tileset, + t.part.source_id, + t.part.coord, + t.part.alternate + ) + else: + undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, t.part.data, type) + + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + + +func copy_selection(): + copied_tile_states = selected_tile_states + + +func paste_selection(): + staged_paste_tile_states = copied_tile_states + selected_tile_states = [] + paint_mode = PaintMode.PASTE + paint_action = PaintAction.PASTE + paste_occurred.emit() + queue_redraw() + + +func set_disabled_sources(list): + disabled_sources = list + queue_redraw() + + +func emit_terrain_updated(index): + terrain_updated.emit(index) + + +func _gui_input(event) -> void: + if event is InputEventKey and event.is_pressed(): + if event.keycode == KEY_DELETE and not event.echo: + accept_event() + delete_selection() + if event.keycode == KEY_ENTER and not event.echo: + accept_event() + toggle_selection() + if event.keycode == KEY_ESCAPE and not event.echo: + accept_event() + if paint_action == PaintAction.PASTE: + staged_paste_tile_states = [] + paint_mode = PaintMode.SELECT + paint_action = PaintAction.NO_ACTION + selection_start = Vector2i(-1,-1) + if event.keycode == KEY_C and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + copy_selection() + if event.keycode == KEY_X and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + copy_selection() + delete_selection() + if event.keycode == KEY_V and (event.ctrl_pressed or event.meta_pressed) and not event.echo: + accept_event() + paste_selection() + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_WHEEL_UP and (event.ctrl_pressed or event.meta_pressed): + accept_event() + change_zoom_level.emit(zoom_level * 1.1) + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and (event.ctrl_pressed or event.meta_pressed): + accept_event() + change_zoom_level.emit(zoom_level / 1.1) + + var released : bool = event is InputEventMouseButton and (not event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT)) + if released: + paint_action = PaintAction.NO_ACTION + + if event is InputEventMouseMotion: + prev_position = current_position + current_position = event.position + var tile := tile_part_from_position(event.position) + if tile.valid != highlighted_tile_part.valid or\ + (tile.valid and tile.data != highlighted_tile_part.data) or\ + (tile.valid and tile.get("peering") != highlighted_tile_part.get("peering")) or\ + event.button_mask & MOUSE_BUTTON_LEFT and paint_action == PaintAction.SELECT: + queue_redraw() + highlighted_tile_part = tile + + var clicked : bool = event is InputEventMouseButton and (event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT)) + if clicked: + initial_click = current_position + selection_start = Vector2i(-1,-1) + terrain_undo.action_index += 1 + terrain_undo.action_count = 0 + if released: + terrain_undo.finish_action() + selection_rect = Rect2i(0,0,0,0) + queue_redraw() + + if paint_action == PaintAction.PASTE: + if event is InputEventMouseMotion: + queue_redraw() + + if clicked: + if event.button_index == MOUSE_BUTTON_LEFT and staged_paste_tile_states.size() > 0: + undo_manager.create_action("Paste tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset) + var base_rect = staged_paste_tile_states[0].base_rect + for p in staged_paste_tile_states: + var staged_rect:Rect2 = p.base_rect + staged_rect.position -= base_rect.position + base_rect.size / 2 + + staged_rect.position *= zoom_level + staged_rect.size *= zoom_level + + staged_rect.position += Vector2(current_position) + + var old_tile_part = tile_part_from_position(staged_rect.get_center()) + var new_tile_state = p + if (not old_tile_part.valid) or (not new_tile_state.part.valid): + continue + + for side in range(16): + var old_peering = BetterTerrain.tile_peering_types(old_tile_part.data, side) + var new_sides = new_tile_state.sides + if new_sides.has(side) and not old_peering.has(paint): + undo_manager.add_do_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint) + elif old_peering.has(paint) and not new_sides.has(side): + undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint) + + var old_symmetry = BetterTerrain.get_tile_symmetry_type(old_tile_part.data) + var new_symmetry = new_tile_state.symmetry + if new_symmetry != old_symmetry: + undo_manager.add_do_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, new_symmetry) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, old_symmetry) + + undo_manager.add_do_method(self, &"queue_redraw") + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + + staged_paste_tile_states = [] + paint_mode = PaintMode.SELECT + paint_action = PaintAction.SELECT + return + + if clicked and pick_icon_terrain >= 0: + highlighted_tile_part = tile_part_from_position(current_position) + if !highlighted_tile_part.valid: + return + + var t = BetterTerrain.get_terrain(tileset, paint) + var prev_icon = t.icon.duplicate() + var icon = { + source_id = highlighted_tile_part.source_id, + coord = highlighted_tile_part.coord + } + undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset) + undo_manager.add_do_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, icon) + undo_manager.add_do_method(self, &"emit_terrain_updated", paint) + undo_manager.add_undo_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, prev_icon) + undo_manager.add_undo_method(self, &"emit_terrain_updated", paint) + undo_manager.commit_action() + pick_icon_terrain = -1 + return + + if pick_icon_terrain_cancel: + pick_icon_terrain = -1 + pick_icon_terrain_cancel = false + + if paint != BetterTerrain.TileCategory.NON_TERRAIN and clicked: + paint_action = PaintAction.NO_ACTION + if highlighted_tile_part.valid: + match [paint_mode, event.button_index]: + [PaintMode.PAINT_TYPE, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_TYPE + [PaintMode.PAINT_TYPE, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_TYPE + [PaintMode.PAINT_PEERING, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_PEERING + [PaintMode.PAINT_PEERING, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_PEERING + [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_SYMMETRY + [PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_SYMMETRY + [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT + else: + match [paint_mode, event.button_index]: + [PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT + + if (clicked or event is InputEventMouseMotion) and paint_action != PaintAction.NO_ACTION: + + if paint_action == PaintAction.SELECT: + if clicked: + selection_start = Vector2i(-1,-1) + queue_redraw() + if selection_start.x < 0: + selection_start = current_position + selection_end = current_position + + selection_rect = Rect2i(selection_start, selection_end - selection_start).abs() + var selected_tile_parts = tile_parts_from_rect(selection_rect) + selected_tile_states = [] + for t in selected_tile_parts: + var state := { + part = t, + base_rect = Rect2(t.rect.position / zoom_level, t.rect.size / zoom_level), + paint = paint, + sides = BetterTerrain.tile_peering_for_type(t.data, paint), + symmetry = BetterTerrain.get_tile_symmetry_type(t.data) + } + selected_tile_states.push_back(state) + else: + if !highlighted_tile_part.valid: + return + #slightly crude and non-optimal but way simpler than the "correct" solution + var current_position_vec2 = Vector2(current_position) + var prev_position_vec2 = Vector2(prev_position) + var mouse_dist = current_position_vec2.distance_to(prev_position_vec2) + var step_size = (tile_part_size.x * zoom_level) + var steps = ceil(mouse_dist / step_size) + 1 + for i in range(steps): + var t = float(i) / steps + var check_position = prev_position_vec2.lerp(current_position_vec2, t) + highlighted_tile_part = tile_part_from_position(check_position) + + if !highlighted_tile_part.valid: + continue + + if paint_action == PaintAction.DRAW_TYPE or paint_action == PaintAction.ERASE_TYPE: + var type := BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data) + var goal := paint if paint_action == PaintAction.DRAW_TYPE else BetterTerrain.TileCategory.NON_TERRAIN + if type != goal: + undo_manager.create_action("Set tile terrain type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, highlighted_tile_part.data, goal]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + if goal == BetterTerrain.TileCategory.NON_TERRAIN: + terrain_undo.create_peering_restore_point_tile( + undo_manager, + tileset, + highlighted_tile_part.source_id, + highlighted_tile_part.coord, + highlighted_tile_part.alternate + ) + else: + undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, highlighted_tile_part.data, type) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.DRAW_PEERING: + if highlighted_tile_part.has("peering"): + if !(paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering)): + undo_manager.create_action("Set tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"add_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.ERASE_PEERING: + if highlighted_tile_part.has("peering"): + if paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering): + undo_manager.create_action("Remove tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"remove_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.DRAW_SYMMETRY: + if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data): + undo_manager.create_action("Set tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, paint_symmetry]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + elif paint_action == PaintAction.ERASE_SYMMETRY: + if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data): + undo_manager.create_action("Remove tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true) + var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data) + terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, BetterTerrain.SymmetryType.NONE]) + terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", []) + undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry) + undo_manager.add_undo_method(self, &"queue_redraw") + undo_manager.commit_action() + terrain_undo.action_count += 1 + + +func _on_zoom_value_changed(value) -> void: + zoom_level = value + custom_minimum_size.x = zoom_level * tiles_size.x + if alternate_size.x > 0: + custom_minimum_size.x += ALTERNATE_TILE_MARGIN + zoom_level * alternate_size.x + custom_minimum_size.y = zoom_level * max(tiles_size.y, alternate_size.y) + queue_redraw() + + +func clear_highlighted_tile() -> void: + highlighted_tile_part = { valid = false } + queue_redraw() diff --git a/godot/addons/better-terrain/editor/TileView.gd.uid b/godot/addons/better-terrain/editor/TileView.gd.uid new file mode 100644 index 0000000..bd04da7 --- /dev/null +++ b/godot/addons/better-terrain/editor/TileView.gd.uid @@ -0,0 +1 @@ +uid://bx2gcykbipbxo diff --git a/godot/addons/better-terrain/icon.svg b/godot/addons/better-terrain/icon.svg new file mode 100644 index 0000000..377fba9 --- /dev/null +++ b/godot/addons/better-terrain/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icon.svg.import b/godot/addons/better-terrain/icon.svg.import new file mode 100644 index 0000000..16696c1 --- /dev/null +++ b/godot/addons/better-terrain/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c66nal373iwgd" +path="res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icon.svg" +dest_files=["res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/better-terrain/icons/Decoration.svg b/godot/addons/better-terrain/icons/Decoration.svg new file mode 100644 index 0000000..8ec40b5 --- /dev/null +++ b/godot/addons/better-terrain/icons/Decoration.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/godot/addons/better-terrain/icons/Decoration.svg.import b/godot/addons/better-terrain/icons/Decoration.svg.import new file mode 100644 index 0000000..02587bb --- /dev/null +++ b/godot/addons/better-terrain/icons/Decoration.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kmypxsqhynyv" +path="res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Decoration.svg" +dest_files=["res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/EditSymmetry.svg b/godot/addons/better-terrain/icons/EditSymmetry.svg new file mode 100644 index 0000000..3565800 --- /dev/null +++ b/godot/addons/better-terrain/icons/EditSymmetry.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/godot/addons/better-terrain/icons/EditSymmetry.svg.import b/godot/addons/better-terrain/icons/EditSymmetry.svg.import new file mode 100644 index 0000000..47ffaf5 --- /dev/null +++ b/godot/addons/better-terrain/icons/EditSymmetry.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co6gwwmog0pjy" +path="res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditSymmetry.svg" +dest_files=["res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/EditTerrain.svg b/godot/addons/better-terrain/icons/EditTerrain.svg new file mode 100644 index 0000000..e175385 --- /dev/null +++ b/godot/addons/better-terrain/icons/EditTerrain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/EditTerrain.svg.import b/godot/addons/better-terrain/icons/EditTerrain.svg.import new file mode 100644 index 0000000..948006f --- /dev/null +++ b/godot/addons/better-terrain/icons/EditTerrain.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo2cjv08jkvf8" +path="res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditTerrain.svg" +dest_files=["res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/EditType.svg b/godot/addons/better-terrain/icons/EditType.svg new file mode 100644 index 0000000..51e7d41 --- /dev/null +++ b/godot/addons/better-terrain/icons/EditType.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/EditType.svg.import b/godot/addons/better-terrain/icons/EditType.svg.import new file mode 100644 index 0000000..0aa98cc --- /dev/null +++ b/godot/addons/better-terrain/icons/EditType.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6lxq2y7mpb18" +path="res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/EditType.svg" +dest_files=["res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/MatchTiles.svg b/godot/addons/better-terrain/icons/MatchTiles.svg new file mode 100644 index 0000000..efc5713 --- /dev/null +++ b/godot/addons/better-terrain/icons/MatchTiles.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/MatchTiles.svg.import b/godot/addons/better-terrain/icons/MatchTiles.svg.import new file mode 100644 index 0000000..a137b97 --- /dev/null +++ b/godot/addons/better-terrain/icons/MatchTiles.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1h1p7pcwdnjk" +path="res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/MatchTiles.svg" +dest_files=["res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/MatchVertices.svg b/godot/addons/better-terrain/icons/MatchVertices.svg new file mode 100644 index 0000000..339ee2c --- /dev/null +++ b/godot/addons/better-terrain/icons/MatchVertices.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/MatchVertices.svg.import b/godot/addons/better-terrain/icons/MatchVertices.svg.import new file mode 100644 index 0000000..c914385 --- /dev/null +++ b/godot/addons/better-terrain/icons/MatchVertices.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfemy1g6okwlv" +path="res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/MatchVertices.svg" +dest_files=["res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/NonModifying.svg b/godot/addons/better-terrain/icons/NonModifying.svg new file mode 100644 index 0000000..a1a10dd --- /dev/null +++ b/godot/addons/better-terrain/icons/NonModifying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/NonModifying.svg.import b/godot/addons/better-terrain/icons/NonModifying.svg.import new file mode 100644 index 0000000..04c0853 --- /dev/null +++ b/godot/addons/better-terrain/icons/NonModifying.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1yr6yruwl63u" +path="res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/NonModifying.svg" +dest_files=["res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/Replace.svg b/godot/addons/better-terrain/icons/Replace.svg new file mode 100644 index 0000000..bcb940b --- /dev/null +++ b/godot/addons/better-terrain/icons/Replace.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/godot/addons/better-terrain/icons/Replace.svg.import b/godot/addons/better-terrain/icons/Replace.svg.import new file mode 100644 index 0000000..aa40af7 --- /dev/null +++ b/godot/addons/better-terrain/icons/Replace.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y3xy6qdckht6" +path="res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Replace.svg" +dest_files=["res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/ShuffleRandom.svg b/godot/addons/better-terrain/icons/ShuffleRandom.svg new file mode 100644 index 0000000..a66ba86 --- /dev/null +++ b/godot/addons/better-terrain/icons/ShuffleRandom.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/godot/addons/better-terrain/icons/ShuffleRandom.svg.import b/godot/addons/better-terrain/icons/ShuffleRandom.svg.import new file mode 100644 index 0000000..8aa5e36 --- /dev/null +++ b/godot/addons/better-terrain/icons/ShuffleRandom.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cs4mdmluiydj6" +path="res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/ShuffleRandom.svg" +dest_files=["res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryAll.svg b/godot/addons/better-terrain/icons/SymmetryAll.svg new file mode 100644 index 0000000..4aeca2d --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryAll.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryAll.svg.import b/godot/addons/better-terrain/icons/SymmetryAll.svg.import new file mode 100644 index 0000000..aded76e --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryAll.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cyjra4g05dwh" +path="res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryAll.svg" +dest_files=["res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryFlip.svg b/godot/addons/better-terrain/icons/SymmetryFlip.svg new file mode 100644 index 0000000..a180318 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryFlip.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/SymmetryFlip.svg.import b/godot/addons/better-terrain/icons/SymmetryFlip.svg.import new file mode 100644 index 0000000..8a954de --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryFlip.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqmc1jp56or8m" +path="res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryFlip.svg" +dest_files=["res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryMirror.svg b/godot/addons/better-terrain/icons/SymmetryMirror.svg new file mode 100644 index 0000000..463e09a --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryMirror.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/SymmetryMirror.svg.import b/godot/addons/better-terrain/icons/SymmetryMirror.svg.import new file mode 100644 index 0000000..83348f9 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryMirror.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5hm3bfj3dvej" +path="res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryMirror.svg" +dest_files=["res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryReflect.svg b/godot/addons/better-terrain/icons/SymmetryReflect.svg new file mode 100644 index 0000000..c618809 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryReflect.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryReflect.svg.import b/godot/addons/better-terrain/icons/SymmetryReflect.svg.import new file mode 100644 index 0000000..8a0444e --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryReflect.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxoewno1cefua" +path="res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryReflect.svg" +dest_files=["res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryRotate180.svg b/godot/addons/better-terrain/icons/SymmetryRotate180.svg new file mode 100644 index 0000000..44b65fd --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotate180.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryRotate180.svg.import b/godot/addons/better-terrain/icons/SymmetryRotate180.svg.import new file mode 100644 index 0000000..d7e3503 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotate180.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8mcycyl3e66r" +path="res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotate180.svg" +dest_files=["res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryRotateAll.svg b/godot/addons/better-terrain/icons/SymmetryRotateAll.svg new file mode 100644 index 0000000..1fb8d0e --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateAll.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryRotateAll.svg.import b/godot/addons/better-terrain/icons/SymmetryRotateAll.svg.import new file mode 100644 index 0000000..a244e9e --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateAll.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7fx4mk18lmls" +path="res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateAll.svg" +dest_files=["res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg b/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg new file mode 100644 index 0000000..1f4823a --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import b/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import new file mode 100644 index 0000000..5a53862 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateClockwise.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://baxhjy28r1iqj" +path="res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg b/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg new file mode 100644 index 0000000..6ffb93a --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import b/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import new file mode 100644 index 0000000..b9081b2 --- /dev/null +++ b/godot/addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csbwdkr6bc2db" +path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/Warning.svg b/godot/addons/better-terrain/icons/Warning.svg new file mode 100644 index 0000000..199bf7f --- /dev/null +++ b/godot/addons/better-terrain/icons/Warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/godot/addons/better-terrain/icons/Warning.svg.import b/godot/addons/better-terrain/icons/Warning.svg.import new file mode 100644 index 0000000..928d015 --- /dev/null +++ b/godot/addons/better-terrain/icons/Warning.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0es228gfcykd" +path="res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/Warning.svg" +dest_files=["res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg new file mode 100644 index 0000000..559ca91 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import new file mode 100644 index 0000000..e3fe894 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://iid5buh1t5j5" +path="res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg" +dest_files=["res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg new file mode 100644 index 0000000..0b60a3f --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import new file mode 100644 index 0000000..cdc49df --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brro1lqnf3r5y" +path="res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg" +dest_files=["res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg new file mode 100644 index 0000000..e0a268f --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import new file mode 100644 index 0000000..01d3290 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpf5p8xxn52cb" +path="res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg" +dest_files=["res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg new file mode 100644 index 0000000..5acc95a --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import new file mode 100644 index 0000000..352a731 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d251v4pxpwsre" +path="res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg" +dest_files=["res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg new file mode 100644 index 0000000..677a62b --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import new file mode 100644 index 0000000..4fe09b4 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c1bmbyb3ig0mx" +path="res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg" +dest_files=["res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg new file mode 100644 index 0000000..fc81aae --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import new file mode 100644 index 0000000..559613a --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcky1dfn4umac" +path="res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg" +dest_files=["res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg new file mode 100644 index 0000000..400e11a --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import new file mode 100644 index 0000000..492d59f --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://def0fcqsn6s6x" +path="res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg new file mode 100644 index 0000000..39b5242 --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import new file mode 100644 index 0000000..1db7d1b --- /dev/null +++ b/godot/addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ngej4qhkypb2" +path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg" +dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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=4.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/better-terrain/plugin.cfg b/godot/addons/better-terrain/plugin.cfg new file mode 100644 index 0000000..1bfb36b --- /dev/null +++ b/godot/addons/better-terrain/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="BetterTerrain" +description="This is a drop-in replacement for Godot 4's tilemap terrain system, offering more versatile and straightforward autotiling. It can be used with any existing TileMap or TileSet, either through the editor plugin, or directly via code." +author="Portponky" +version="" +script="TerrainPlugin.gd" diff --git a/godot/addons/dialogic/Core/DialogicGameHandler.gd b/godot/addons/dialogic/Core/DialogicGameHandler.gd new file mode 100644 index 0000000..1d07f93 --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicGameHandler.gd @@ -0,0 +1,469 @@ +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() + +## A timeline that will be played when dialog ends. +## By default this timeline only contains a clear event. +var dialog_ending_timeline: DialogicTimeline + +## Emitted when [member paused] changes to `true`. +signal dialogic_paused +## Emitted when [member paused] changes to `false`. +signal dialogic_resumed + + +## Emitted when a timeline starts by calling either [method start] +## or [method start_timeline]. +signal timeline_started +## Emitted when the timeline ends. +## This can be a timeline ending or [method end_timeline] being called. +signal timeline_ended +## 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. +@warning_ignore("unused_signal") # This is emitted by the signal event. +signal signal_event(argument: Variant) + +## Emitted when a signal event gets fired from a [class TextEvent] event. +@warning_ignore("unused_signal") # This is emitted by the text subsystem. +signal text_signal(argument: String) + + +# Careful, this section is repopulated automatically at certain moments. +#region SUBSYSTEMS + +var Animations := preload("res://addons/dialogic/Modules/Core/subsystem_animation.gd").new(): + get: return get_subsystem("Animations") + +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 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 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 Inputs := preload("res://addons/dialogic/Modules/Core/subsystem_input.gd").new(): + get: return get_subsystem("Inputs") + +var Jump := preload("res://addons/dialogic/Modules/Jump/subsystem_jump.gd").new(): + get: return get_subsystem("Jump") + +var PortraitContainers := preload("res://addons/dialogic/Modules/Character/subsystem_containers.gd").new(): + get: return get_subsystem("PortraitContainers") + +var Portraits := preload("res://addons/dialogic/Modules/Character/subsystem_portraits.gd").new(): + get: return get_subsystem("Portraits") + +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() + + DialogicResourceUtil.update_event_cache() + + dialog_ending_timeline = DialogicTimeline.new() + dialog_ending_timeline.from_text("[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_or_idx: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_or_idx) + 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(): + if not scene.ready.is_connected(clear.bind(ClearFlags.KEEP_VARIABLES)): + scene.ready.connect(clear.bind(ClearFlags.KEEP_VARIABLES)) + if not scene.ready.is_connected(start_timeline.bind(timeline, label_or_idx)): + scene.ready.connect(start_timeline.bind(timeline, label_or_idx)) + else: + start_timeline(timeline, label_or_idx) + + 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) in [TYPE_STRING, TYPE_STRING_NAME]: + #check the lookup table if it's not a full file name + if "://" in timeline: + timeline = load(timeline) + else: + timeline = DialogicResourceUtil.get_timeline_resource(timeline) + + 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) in [TYPE_STRING, TYPE_STRING_NAME]: + 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 + + if not current_timeline == dialog_ending_timeline: + 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) in [TYPE_STRING, TYPE_STRING_NAME]: + if "://" in timeline_resource: + timeline_resource = load(timeline_resource) + else: + timeline_resource = DialogicResourceUtil.get_timeline_resource(timeline_resource) + + 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. +## If [param skip_ending] is `true`, the dialog_ending_timeline is not getting played +func end_timeline(skip_ending := false) -> void: + if not skip_ending and dialog_ending_timeline and current_timeline != dialog_ending_timeline: + start(dialog_ending_timeline) + return + + await clear(ClearFlags.TIMELINE_INFO_ONLY) + + if Styles.has_active_layout_node() and Styles.get_layout_node().is_inside_tree(): + match ProjectSettings.get_setting('dialogic/layout/end_behaviour', 0): + 0: + Styles.get_layout_node().get_parent().remove_child(Styles.get_layout_node()) + Styles.get_layout_node().queue_free() + 1: + Styles.get_layout_node().hide() + + timeline_ended.emit() + + +## Method to check if timeline exists. +## @timeline can be either a loaded timeline resource or a path to a timeline file. +func timeline_exists(timeline:Variant) -> bool: + if typeof(timeline) in [TYPE_STRING, TYPE_STRING_NAME]: + if "://" in timeline and ResourceLoader.exists(timeline): + return load(timeline) is DialogicTimeline + else: + return DialogicResourceUtil.timeline_resource_exists(timeline) + + return timeline is DialogicTimeline + + +## 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 + + # TODO: Check if necessary. This should be impossible. + #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(true) +#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 +################################################################################ + + + +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 "', current_timeline.get_identifier(), '" (',current_timeline.resource_path,').') + print("\n") +#endregion diff --git a/godot/addons/dialogic/Core/DialogicGameHandler.gd.uid b/godot/addons/dialogic/Core/DialogicGameHandler.gd.uid new file mode 100644 index 0000000..1565a56 --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicGameHandler.gd.uid @@ -0,0 +1 @@ +uid://ds2q0uclmolvu diff --git a/godot/addons/dialogic/Core/DialogicResourceUtil.gd b/godot/addons/dialogic/Core/DialogicResourceUtil.gd new file mode 100644 index 0000000..e5449c8 --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicResourceUtil.gd @@ -0,0 +1,376 @@ +@tool +class_name DialogicResourceUtil + +static var label_cache := {} +static var event_cache: Array[DialogicEvent] = [] +static var channel_cache := {} + +static var special_resources := {} + + +static func update() -> void: + update_directory('.dch') + update_directory('.dtl') + update_label_cache() + update_audio_channel_cache() + DialogicStylesUtil.build_style_directory() + + +#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()) + var temp := suggested_name + while suggested_name in directory: + suggested_name = file_path.trim_suffix("/"+suggested_name+"."+file_path.get_extension()).get_file().path_join(suggested_name) + if suggested_name == temp: + break + temp = 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_by_path(file_path:String) -> String: + if not file_path: return "" + var identifier: Variant = get_directory(file_path.get_extension()).find_key(file_path) + if typeof(identifier) == TYPE_STRING: + return identifier + return "" + + +static func get_resource_path_from_identifier(identifier:String, extension:String) -> String: + var value: Variant = get_directory(extension).get(identifier, '') + if value is String: + return value + 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 value: Variant = get_directory(extension).get(identifier, '') + if typeof(value) == TYPE_STRING and ResourceLoader.exists(value): + return load(value) + elif value is Resource: + return value + return null + + +## Returns a boolean that expresses whether the resource exists. +## The expected extension is needed to use the right directory. +static func resource_exists_from_identifier(identifier:String, extension:String) -> bool: + var value: Variant = get_directory(extension).get(identifier, '') + if typeof(value) == TYPE_STRING: + return ResourceLoader.exists(value) + return value is Resource + + +## Editor Only +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) + + +## While usually the directory maps identifiers to paths, this method (only supposed to be used at runtime) +## allows mapping resources that are not saved to an identifier. +static func register_runtime_resource(resource:Resource, identifier:String, extension:String) -> void: + var directory := get_directory(extension) + directory[identifier] = resource + set_directory(extension, directory) + + +static func get_runtime_unique_identifier(resource:Resource, extension:String) -> String: + var identifier: Variant = get_directory(extension).find_key(resource) + if typeof(identifier) == TYPE_STRING: + return identifier + return "" + +#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 AUDIO CHANNEL CACHE +################################################################################ +# The audio channel cache is only for the editor so we don't have to scan all timelines +# whenever we want to suggest channels. This has no use in game and is not always perfect. + +static func get_audio_channel_cache() -> Dictionary: + if not channel_cache.is_empty(): + return channel_cache + + channel_cache = DialogicUtil.get_editor_setting('channel_ref', {}) + return channel_cache + + +static func get_channel_list() -> Array: + if channel_cache.is_empty(): + return [] + + var cached_names := [] + for timeline in channel_cache: + for name in channel_cache[timeline]: + if not cached_names.has(name): + cached_names.append(name) + return cached_names + + +static func set_audio_channel_cache(cache:Dictionary) -> void: + channel_cache = cache + + +static func update_audio_channel_cache() -> void: + var cache := get_audio_channel_cache() + var timelines := get_timeline_directory().values() + for timeline in cache: + if !timeline in timelines: + cache.erase(timeline) + set_audio_channel_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 timeline_resource_exists(timeline_identifier:String) -> bool: + return resource_exists_from_identifier(timeline_identifier, '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) and not FileAccess.file_exists(path + "/" + ".gdignore"): + 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/godot/addons/dialogic/Core/DialogicResourceUtil.gd.uid b/godot/addons/dialogic/Core/DialogicResourceUtil.gd.uid new file mode 100644 index 0000000..21ba6f9 --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicResourceUtil.gd.uid @@ -0,0 +1 @@ +uid://bdt5bbxxkvab4 diff --git a/godot/addons/dialogic/Core/DialogicUtil.gd b/godot/addons/dialogic/Core/DialogicUtil.gd new file mode 100644 index 0000000..897a61e --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicUtil.gd @@ -0,0 +1,803 @@ +@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: + if Engine.is_editor_hint(): + return get_dialogic_plugin().get_editor_interface().get_editor_scale() + return 1.0 + + +## 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" + var subsystems_sorted := [] + + for indexer: DialogicIndexer in get_indexers(true, true): + + for subsystem: Dictionary in indexer._get_subsystems().duplicate(true): + subsystems_sorted.append(subsystem) + + subsystems_sorted.sort_custom(func (a: Dictionary, b: Dictionary) -> bool: + return a.name < b.name + ) + + for subsystem: Dictionary in subsystems_sorted: + 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 not 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 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 = load("res://addons/dialogic/Editor/Events/Fields/field_number.tscn").instantiate() + input.property_name = property_info['name'] + input.use_int_mode() + + if ',' 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)) + else: + input.step = 1 + input.max_value = INF + input.min_value = -INF + + if value != null: + input.set_value(value) + input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_changed)) + TYPE_FLOAT: + input = load("res://addons/dialogic/Editor/Events/Fields/field_number.tscn").instantiate() + input.property_name = property_info['name'] + input.use_float_mode() + 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)) + if value != null: + input.set_value(value) + input.value_changed.connect(DialogicUtil._on_export_number_submitted.bind(property_changed)) + 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_VECTOR2I, TYPE_VECTOR3I, TYPE_VECTOR4I: + var vectorSize: String = type_string(typeof(value))[-2] + input = load("res://addons/dialogic/Editor/Events/Fields/field_vector" + vectorSize + ".tscn").instantiate() + input.step = 1 + input.property_name = property_info['name'] + input.set_value(value) + input.value_changed.connect(DialogicUtil._on_export_vectori_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.show_editing_button = true + 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.set_value(value) + 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(property_name:String, value:float, 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_vectori_submitted(property_name:String, value:Variant, callable: Callable) -> void: + match typeof(value): + TYPE_VECTOR2: value = Vector2i(value) + TYPE_VECTOR3: value = Vector3i(value) + TYPE_VECTOR4: value = Vector4i(value) + 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 + + +static func get_autoload_suggestions(filter:String="") -> Dictionary: + var suggestions := {} + + for prop in ProjectSettings.get_property_list(): + if prop.name.begins_with('autoload/'): + var some_autoload: String = prop.name.trim_prefix('autoload/') + suggestions[some_autoload] = {'value': some_autoload, 'tooltip':some_autoload, 'editor_icon': ["Node", "EditorIcons"]} + if filter.begins_with(some_autoload): + suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]} + return suggestions + + +static func get_autoload_script_resource(autoload_name:String) -> Script: + var script: Script + if 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 + return script + + +static func get_autoload_method_suggestions(filter:String, autoload_name:String) -> Dictionary: + var suggestions := {} + + var script := get_autoload_script_resource(autoload_name) + 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 not filter.is_empty(): + suggestions[filter] = {'value': filter, 'editor_icon':["GuiScrollArrowRight", "EditorIcons"]} + + return suggestions + + +static func get_autoload_property_suggestions(_filter:String, autoload_name:String) -> Dictionary: + var suggestions := {} + var script := get_autoload_script_resource(autoload_name) + if script: + for property in script.get_script_property_list(): + if property.name.ends_with('.gd') or property.name.begins_with('_'): + continue + suggestions[property.name] = {'value': property.name, 'tooltip':property.name, 'editor_icon': ["MemberProperty", "EditorIcons"]} + + return suggestions + + +static func get_audio_bus_suggestions(_filter:= "") -> Dictionary: + var bus_name_list := {} + for i in range(AudioServer.bus_count): + if i == 0: + bus_name_list[AudioServer.get_bus_name(i)] = {'value':''} + else: + bus_name_list[AudioServer.get_bus_name(i)] = {'value':AudioServer.get_bus_name(i)} + return bus_name_list + + +static func get_audio_channel_suggestions(_search_text:String) -> Dictionary: + var suggestions := {} + var channel_defaults := DialogicUtil.get_audio_channel_defaults() + var cached_names := DialogicResourceUtil.get_channel_list() + + for i in channel_defaults.keys(): + if not cached_names.has(i): + cached_names.append(i) + + cached_names.sort() + + for i in cached_names: + if i.is_empty(): + continue + + suggestions[i] = {'value': i} + + if i in channel_defaults.keys(): + suggestions[i]["editor_icon"] = ["ProjectList", "EditorIcons"] + suggestions[i]["tooltip"] = "A default channel defined in the settings." + + else: + suggestions[i]["editor_icon"] = ["AudioStreamPlayer", "EditorIcons"] + suggestions[i]["tooltip"] = "A temporary channel without defaults." + + return suggestions + + +static func get_audio_channel_defaults() -> Dictionary: + return ProjectSettings.get_setting('dialogic/audio/channel_defaults', { + "": { + 'volume': 0.0, + 'audio_bus': '', + 'fade_length': 0.0, + 'loop': false, + }, + "music": { + 'volume': 0.0, + 'audio_bus': '', + 'fade_length': 0.0, + 'loop': true, + }}) + + +static func validate_audio_channel_name(text: String) -> Dictionary: + var result := {} + var channel_name_regex := RegEx.create_from_string(r'(?^-$)|(?[^\w-]{1})') + var matches := channel_name_regex.search_all(text) + var invalid_chars := [] + + for regex_match in matches: + if regex_match.get_string('dash_only'): + result['error_tooltip'] = "Channel name cannot be '-'." + result['valid_text'] = '' + else: + var invalid_char = regex_match.get_string('invalid') + if not invalid_char in invalid_chars: + invalid_chars.append(invalid_char) + + if invalid_chars: + result['valid_text'] = channel_name_regex.sub(text, '', true) + result['error_tooltip'] = "Channel names cannot contain the following characters: " + "".join(invalid_chars) + + return result diff --git a/godot/addons/dialogic/Core/DialogicUtil.gd.uid b/godot/addons/dialogic/Core/DialogicUtil.gd.uid new file mode 100644 index 0000000..a6c1900 --- /dev/null +++ b/godot/addons/dialogic/Core/DialogicUtil.gd.uid @@ -0,0 +1 @@ +uid://c848iwoo6mnms diff --git a/godot/addons/dialogic/Core/Dialogic_Subsystem.gd b/godot/addons/dialogic/Core/Dialogic_Subsystem.gd new file mode 100644 index 0000000..3ff55e6 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Core/Dialogic_Subsystem.gd.uid b/godot/addons/dialogic/Core/Dialogic_Subsystem.gd.uid new file mode 100644 index 0000000..f1010fa --- /dev/null +++ b/godot/addons/dialogic/Core/Dialogic_Subsystem.gd.uid @@ -0,0 +1 @@ +uid://d4iojsqnbdphm diff --git a/godot/addons/dialogic/Core/index_class.gd b/godot/addons/dialogic/Core/index_class.gd new file mode 100644 index 0000000..c6bd7c5 --- /dev/null +++ b/godot/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) or (extension == ".gd" and i.ends_with(".gdc")): + 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/godot/addons/dialogic/Core/index_class.gd.uid b/godot/addons/dialogic/Core/index_class.gd.uid new file mode 100644 index 0000000..92c8db9 --- /dev/null +++ b/godot/addons/dialogic/Core/index_class.gd.uid @@ -0,0 +1 @@ +uid://ciwsx3rjhhmg7 diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd new file mode 100644 index 0000000..9ad94cb --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd.uid new file mode 100644 index 0000000..3ed27df --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.gd.uid @@ -0,0 +1 @@ +uid://bcsda7vbawlgv diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_exports.tscn new file mode 100644 index 0000000..6b23ccd --- /dev/null +++ b/godot/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" uid="uid://bcsda7vbawlgv" 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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd new file mode 100644 index 0000000..2c92085 --- /dev/null +++ b/godot/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.set_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(_property:String, 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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd.uid new file mode 100644 index 0000000..4f28f27 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.gd.uid @@ -0,0 +1 @@ +uid://uv6dx3sofwae diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn new file mode 100644 index 0000000..89121d9 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_layout.tscn @@ -0,0 +1,65 @@ +[gd_scene load_steps=4 format=3 uid="uid://crke8suvv52c6"] + +[ext_resource type="Script" uid="uid://uv6dx3sofwae" 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"] +[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="2_daw3l"] + +[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" parent="HBoxContainer" instance=ExtResource("2_daw3l")] +unique_name_in_owner = true +layout_mode = 2 +mode = 1 +step = 1.0 +min_value = 0.0 +max_value = 100.0 +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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd new file mode 100644 index 0000000..a4be43e --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd.uid new file mode 100644 index 0000000..ca57828 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd.uid @@ -0,0 +1 @@ +uid://busjn8oo7kl1s diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn new file mode 100644 index 0000000..431f940 --- /dev/null +++ b/godot/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" uid="uid://busjn8oo7kl1s" 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_m6kd3"] +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_m6kd3") + +[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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd new file mode 100644 index 0000000..d9ff6e5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd.uid new file mode 100644 index 0000000..d21f2ad --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.gd.uid @@ -0,0 +1 @@ +uid://cp0o6sycac85b diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main_exports.tscn new file mode 100644 index 0000000..98c290f --- /dev/null +++ b/godot/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" uid="uid://cp0o6sycac85b" 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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd new file mode 100644 index 0000000..a5fe514 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd.uid new file mode 100644 index 0000000..3d1db09 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.gd.uid @@ -0,0 +1 @@ +uid://c0nilv2pybryh diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_general.tscn new file mode 100644 index 0000000..2ce41fe --- /dev/null +++ b/godot/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" uid="uid://c0nilv2pybryh" 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_ywoka"] +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_ywoka") + +[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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd new file mode 100644 index 0000000..127aa08 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd @@ -0,0 +1,76 @@ +@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.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.set_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/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd.uid new file mode 100644 index 0000000..b5a784f --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.gd.uid @@ -0,0 +1 @@ +uid://yulfiomudcob diff --git a/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn new file mode 100644 index 0000000..99bb34c --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/char_edit_section_portraits.tscn @@ -0,0 +1,71 @@ +[gd_scene load_steps=5 format=3 uid="uid://cmrgbo8qi145o"] + +[ext_resource type="Script" uid="uid://yulfiomudcob" 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"] +[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="4_w4pvv"] + +[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="MainScaleOld" type="SpinBox" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 2 +size_flags_horizontal = 8 +value = 100.0 +allow_greater = true +alignment = 1 +suffix = "%" + +[node name="MainScale" parent="." instance=ExtResource("4_w4pvv")] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 8 +mode = 1 +step = 1.0 +min_value = 0.0 +max_value = 100.0 +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/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd b/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd new file mode 100644 index 0000000..3233674 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd @@ -0,0 +1,694 @@ +@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(current_resource.resource_path) + + %CharacterName.text = current_resource.get_identifier() + + $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: + if not path.ends_with(".dch"): + path = path.trim_suffix(".") + path += ".dch" + 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) + EditorInterface.get_resource_filesystem().update_file(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/character_prefix_suffix.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) 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), + [current_resource.get_identifier()]) + 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/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd.uid new file mode 100644 index 0000000..4997d86 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor.gd.uid @@ -0,0 +1 @@ +uid://cwhe7tpe75oh7 diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_editor.tscn b/godot/addons/dialogic/Editor/CharacterEditor/character_editor.tscn new file mode 100644 index 0000000..49fc117 --- /dev/null +++ b/godot/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" uid="uid://cwhe7tpe75oh7" 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" uid="uid://deliic6d8vajo" 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_r5ayh"] +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_r5ayh") + +[sub_resource type="Image" id="Image_2j4b6"] +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_2j4b6") + +[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/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd new file mode 100644 index 0000000..55253f3 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd @@ -0,0 +1,42 @@ +@tool +class_name DialogicCharacterEditorMainSection +extends Control + +## Base class for all character editor main sections. Methods should be overriden. + +## Emit this, if something changed +@warning_ignore("unused_signal") # this is used by extending scripts +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/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd.uid new file mode 100644 index 0000000..bc89421 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_main_settings_section.gd.uid @@ -0,0 +1 @@ +uid://wltbab3qq63b diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd new file mode 100644 index 0000000..920f8a5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd.uid new file mode 100644 index 0000000..0067938 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_settings_section.gd.uid @@ -0,0 +1 @@ +uid://ckblurnjla80i diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd new file mode 100644 index 0000000..82ac137 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd @@ -0,0 +1,142 @@ +@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(at_position: Vector2) -> Variant: + var drag_item := get_item_at_position(at_position) + if not drag_item: + return null + + drop_mode_flags = DROP_MODE_INBETWEEN + var preview := Label.new() + preview.text = " "+drag_item.get_text(0) + preview.add_theme_stylebox_override('normal', get_theme_stylebox("Background", "EditorStyles")) + set_drag_preview(preview) + + return drag_item + + +func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: + return data is TreeItem + + +func _drop_data(at_position: Vector2, item: Variant) -> void: + var to_item := get_item_at_position(at_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(at_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/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd.uid new file mode 100644 index 0000000..9f24a8c --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_editor_portrait_tree.gd.uid @@ -0,0 +1 @@ +uid://deliic6d8vajo diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd new file mode 100644 index 0000000..6c54255 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd @@ -0,0 +1,79 @@ +@tool +class_name DialogicCharacterPrefixSuffixSection +extends DialogicCharacterEditorMainSection +## Character Editor Section for setting the prefix and suffix of a character. +## +## loads and sets the prefix and suffix of a character. +## Provides [const PREFIX_CUSTOM_KEY] and [const SUFFIX_CUSTOM_KEY] to +## access the `custom_info` dictionary of the [class DialogicCharacter]. + +@export var prefix_input: LineEdit +@export var suffix_input: LineEdit + +## We won't force any prefixes or suffixes onto the player, +## to ensure their games are working as previously when updating. +const DEFAULT_PREFIX = "" +const DEFAULT_SUFFIX = "" + +## `custom_info` dictionary keys for the prefix. +const PREFIX_CUSTOM_KEY = "prefix" + +## `custom_info` dictionary keys for the prefix. +const SUFFIX_CUSTOM_KEY = "suffix" + +var suffix := "" +var prefix := "" + + +func _ready() -> void: + suffix_input.text_changed.connect(_suffix_changed) + prefix_input.text_changed.connect(_prefix_changed) + + +func _suffix_changed(text: String) -> void: + suffix = text + + +func _prefix_changed(text: String) -> void: + prefix = text + + +func _get_title() -> String: + return "Character Prefix & Suffix" + + +func _show_title() -> bool: + return true + + +func _start_opened() -> bool: + return false + + +func _load_portrait_data(portrait_data: Dictionary) -> void: + _load_prefix_data(portrait_data) + + +## We load the prefix and suffix from the character's `custom_info` dictionary. +func _load_character(resource: DialogicCharacter) -> void: + _load_prefix_data(resource.custom_info) + + +func _load_prefix_data(data: Dictionary) -> void: + suffix = data.get(SUFFIX_CUSTOM_KEY, DEFAULT_SUFFIX) + prefix = data.get(PREFIX_CUSTOM_KEY, DEFAULT_PREFIX) + + suffix_input.text = suffix + prefix_input.text = prefix + + +## Whenever the user makes a save to the character, we save the prefix and suffix. +func _save_changes(character: DialogicCharacter) -> DialogicCharacter: + if not character: + printerr("[Dialogic] Unable to save Prefix and Suffix, the character is missing.") + return character + + character.custom_info[PREFIX_CUSTOM_KEY] = prefix + character.custom_info[SUFFIX_CUSTOM_KEY] = suffix + + return character diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd.uid new file mode 100644 index 0000000..7a3aeb7 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd.uid @@ -0,0 +1 @@ +uid://i1ujoar8jf80 diff --git a/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.tscn b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.tscn new file mode 100644 index 0000000..2ab9c95 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=3 format=3 uid="uid://1ctcs6ywjjtd"] + +[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="1_o3alv"] +[ext_resource type="Script" uid="uid://i1ujoar8jf80" path="res://addons/dialogic/Editor/CharacterEditor/character_prefix_suffix.gd" id="1_tkxff"] + +[node name="CharacterPrefixSuffix" type="GridContainer" node_paths=PackedStringArray("prefix_input", "suffix_input")] +offset_right = 121.0 +offset_bottom = 66.0 +columns = 2 +script = ExtResource("1_tkxff") +prefix_input = NodePath("PrefixInput") +suffix_input = NodePath("SuffixInput") + +[node name="Prefix" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="Prefix"] +layout_mode = 2 +text = "Prefix" + +[node name="HintTooltip" parent="Prefix" instance=ExtResource("1_o3alv")] +layout_mode = 2 +texture = null +hint_text = "If a character speaks, this appears before their text. +Example: Color Tags or Quotation Marks." + +[node name="PrefixInput" type="LineEdit" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +caret_blink = true + +[node name="Suffix" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="Suffix"] +layout_mode = 2 +text = "Suffix" + +[node name="HintTooltip" parent="Suffix" instance=ExtResource("1_o3alv")] +layout_mode = 2 +texture = null +hint_text = "If a character speaks, this appears after their text. +Example: Color Tags or Quotation Marks." + +[node name="SuffixInput" type="LineEdit" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +caret_blink = true diff --git a/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd b/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd new file mode 100644 index 0000000..119f813 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd.uid b/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd.uid new file mode 100644 index 0000000..f8b8094 --- /dev/null +++ b/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.gd.uid @@ -0,0 +1 @@ +uid://iwv7qff6g0f0 diff --git a/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn b/godot/addons/dialogic/Editor/CharacterEditor/portrait_scene_browser.tscn new file mode 100644 index 0000000..f760591 --- /dev/null +++ b/godot/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" uid="uid://iwv7qff6g0f0" 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_h0nfr"] +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_h0nfr") + +[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/godot/addons/dialogic/Editor/Common/BrowserItem.gd b/godot/addons/dialogic/Editor/Common/BrowserItem.gd new file mode 100644 index 0000000..7bfb3cf --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/BrowserItem.gd.uid b/godot/addons/dialogic/Editor/Common/BrowserItem.gd.uid new file mode 100644 index 0000000..6dc7cc1 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/BrowserItem.gd.uid @@ -0,0 +1 @@ +uid://ckthmmkodqqwt diff --git a/godot/addons/dialogic/Editor/Common/BrowserItem.tscn b/godot/addons/dialogic/Editor/Common/BrowserItem.tscn new file mode 100644 index 0000000..8d47d20 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/BrowserItem.tscn @@ -0,0 +1,154 @@ +[gd_scene load_steps=6 format=3 uid="uid://ddlxjde1cx035"] + +[ext_resource type="Script" uid="uid://ckthmmkodqqwt" 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/godot/addons/dialogic/Editor/Common/DCSS.gd b/godot/addons/dialogic/Editor/Common/DCSS.gd new file mode 100644 index 0000000..2a1f95a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/DCSS.gd.uid b/godot/addons/dialogic/Editor/Common/DCSS.gd.uid new file mode 100644 index 0000000..6f4fa14 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/DCSS.gd.uid @@ -0,0 +1 @@ +uid://m3ufop8ao16l diff --git a/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd b/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd new file mode 100644 index 0000000..7d3f355 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd @@ -0,0 +1,122 @@ +@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.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 + + %MatchCase.button_pressed = info.case_sensitive + %WholeWords.button_pressed = info.whole_words + +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/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd.uid b/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd.uid new file mode 100644 index 0000000..6b38dc4 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/ReferenceManager_AddReplacementPanel.gd.uid @@ -0,0 +1 @@ +uid://dca6a1a74jfur diff --git a/godot/addons/dialogic/Editor/Common/TitleBgStylebox.tres b/godot/addons/dialogic/Editor/Common/TitleBgStylebox.tres new file mode 100644 index 0000000..f08bb2c --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd b/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd new file mode 100644 index 0000000..5113c54 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd @@ -0,0 +1,391 @@ +@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.set_column_expand(1, false) + %ReferenceTree.set_column_custom_minimum_width(1, 50) + %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(0, i.timeline) + parent.set_custom_color(0, get_theme_color("disabled_font_color", "Editor")) + parent.set_expand_right(0, true) + 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_cell_mode(0, TreeItem.CELL_MODE_CHECK) + item.set_checked(0, true) + item.set_editable(0, true) + item.set_metadata(0, i) + item.set_text(1, str(i.line_number)+':') + item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT) + item.set_cell_mode(2, TreeItem.CELL_MODE_CUSTOM) + item.set_text(2, i.line) + item.set_tooltip_text(2, i.info.what+' -> '+i.info.forwhat) + item.set_custom_draw_callback(2, _custom_draw) + 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() + + +## Highlights the found text in the result tree +## Inspired by how godot highlights stuff in its search results +func _custom_draw(item:TreeItem, rect:Rect2) -> void: + var text := item.get_text(2) + var find: Dictionary = item.get_metadata(0) + + var font: Font = %ReferenceTree.get_theme_font("font") + var font_size: int = %ReferenceTree.get_theme_font_size("font_size") + + var match_rect := rect + var beginning_index: int = find.match.get_start("replace")-find.line_start-1 + match_rect.position.x += font.get_string_size(text.left(beginning_index), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x -1 + match_rect.size.x = font.get_string_size(find.info.what, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + 1 + match_rect.position.y += 1 * DialogicUtil.get_editor_scale() + match_rect.size.y -= 2 * DialogicUtil.get_editor_scale() + match_rect.position.x += 4 + + %ReferenceTree.draw_rect(match_rect, get_theme_color("highlight_color", "Editor"), true) + %ReferenceTree.draw_rect(match_rect, get_theme_color("box_selection_stroke_color", "Editor"), false) + + +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/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd.uid b/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd.uid new file mode 100644 index 0000000..c18902b --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/broken_reference_manager.gd.uid @@ -0,0 +1 @@ +uid://nrhtjk2rgmgk diff --git a/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.gd b/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.gd new file mode 100644 index 0000000..702fe98 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.gd.uid b/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.gd.uid new file mode 100644 index 0000000..9aeec68 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.gd.uid @@ -0,0 +1 @@ +uid://b0vm440bs3ckd diff --git a/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.tscn b/godot/addons/dialogic/Editor/Common/hint_tooltip_icon.tscn new file mode 100644 index 0000000..b12c058 --- /dev/null +++ b/godot/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" uid="uid://b0vm440bs3ckd" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.gd" id="1_x8t45"] + +[sub_resource type="Image" id="Image_c5s34"] +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_ydy7j"] +image = SubResource("Image_c5s34") + +[node name="HintTooltip" type="TextureRect"] +modulate = Color(0, 0, 0, 1) +texture = SubResource("ImageTexture_ydy7j") +stretch_mode = 3 +script = ExtResource("1_x8t45") diff --git a/godot/addons/dialogic/Editor/Common/reference_manager.gd b/godot/addons/dialogic/Editor/Common/reference_manager.gd new file mode 100644 index 0000000..915e594 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/reference_manager.gd.uid b/godot/addons/dialogic/Editor/Common/reference_manager.gd.uid new file mode 100644 index 0000000..566b3ec --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/reference_manager.gd.uid @@ -0,0 +1 @@ +uid://dugy11ebty3yq diff --git a/godot/addons/dialogic/Editor/Common/reference_manager.tscn b/godot/addons/dialogic/Editor/Common/reference_manager.tscn new file mode 100644 index 0000000..3154a38 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/reference_manager.tscn @@ -0,0 +1,333 @@ +[gd_scene load_steps=14 format=3 uid="uid://c7lmt5cp7bxcm"] + +[ext_resource type="Script" uid="uid://dugy11ebty3yq" path="res://addons/dialogic/Editor/Common/reference_manager.gd" id="1_3t531"] +[ext_resource type="Script" uid="uid://nrhtjk2rgmgk" path="res://addons/dialogic/Editor/Common/broken_reference_manager.gd" id="1_agmg4"] +[ext_resource type="Script" uid="uid://dca6a1a74jfur" 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="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="5_sdymt"] +[ext_resource type="Script" uid="uid://bvbsqai5sh0na" 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_pnutm"] +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_pnutm") + +[sub_resource type="Image" id="Image_asrh0"] +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_2hc6a"] +image = SubResource("Image_asrh0") + +[sub_resource type="Image" id="Image_xvpjt"] +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_xvpjt") + +[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 \"Old\". " +selected = 0 +item_count = 5 +popup/item_0/text = "Pure Text" +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_2hc6a") + +[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_2hc6a") + +[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 +selected = 0 +fit_to_longest_item = false +item_count = 3 +popup/item_0/text = "Everywhere" +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 = 3 +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="HBoxContainer" type="HBoxContainer" parent="Tabs/BrokenReferences/ReplacementSection/FindList"] +layout_mode = 2 +alignment = 1 + +[node name="Replace" type="Button" parent="Tabs/BrokenReferences/ReplacementSection/FindList/HBoxContainer"] +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="HintTooltip" parent="Tabs/BrokenReferences/ReplacementSection/FindList/HBoxContainer" instance=ExtResource("5_sdymt")] +layout_mode = 2 +texture = null +hint_text = "Note that searching and replacing is only implemented for timelines. +E.g. variables used in character display names or glossary entries will have to be replaced manually." + +[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/HBoxContainer/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/godot/addons/dialogic/Editor/Common/reference_manager_window.gd b/godot/addons/dialogic/Editor/Common/reference_manager_window.gd new file mode 100644 index 0000000..c8d8581 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/reference_manager_window.gd @@ -0,0 +1,196 @@ +@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, + 'case_sensitive':case_sensitive, + 'whole_words':whole_words, + }) + + 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) + grab_focus() + + +func _on_close_requested() -> void: + hide() + broken_manager.close() + + +func get_change_count() -> int: + return len(broken_manager.reference_changes) + + +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/godot/addons/dialogic/Editor/Common/reference_manager_window.gd.uid b/godot/addons/dialogic/Editor/Common/reference_manager_window.gd.uid new file mode 100644 index 0000000..90b9f2f --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/reference_manager_window.gd.uid @@ -0,0 +1 @@ +uid://bxr2qomm7wm85 diff --git a/godot/addons/dialogic/Editor/Common/side_bar.tscn b/godot/addons/dialogic/Editor/Common/side_bar.tscn new file mode 100644 index 0000000..1950d70 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/side_bar.tscn @@ -0,0 +1,211 @@ +[gd_scene load_steps=7 format=3 uid="uid://cwe3r2tbh2og1"] + +[ext_resource type="Script" uid="uid://myogqmakusx3" 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 + +[node name="MainVSplit" type="VSplitContainer" parent="VBoxPrimary/Margin"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="VBoxPrimary/Margin/MainVSplit"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Logo" type="TextureRect" parent="VBoxPrimary/Margin/MainVSplit/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/MainVSplit/VBox"] +layout_mode = 2 + +[node name="CurrentResource" type="LineEdit" parent="VBoxPrimary/Margin/MainVSplit/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/MainVSplit/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/MainVSplit/VBox"] +layout_mode = 2 + +[node name="Search" type="LineEdit" parent="VBoxPrimary/Margin/MainVSplit/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="Options" type="Button" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="OptionsPopup" type="Popup" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options"] +unique_name_in_owner = true +transparent_bg = true +position = Vector2i(890, 65) +size = Vector2i(165, 101) +visible = true +transparent = true + +[node name="OptionsPanel" type="PanelContainer" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup"] +unique_name_in_owner = true +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBox" type="VBoxContainer" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel"] +layout_mode = 2 + +[node name="GroupingOptions" type="OptionButton" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Grouping +- None: No Grouping, sorted alphabetically +- Type: Group by type (Characters/Timeilnes) +- Folder: Group based on the parent folder name. +- Path: Group based on folders." +text_overrun_behavior = 1 +clip_text = true +selected = 0 +item_count = 4 +popup/item_0/text = "No Grouping" +popup/item_1/text = "Type Grouping" +popup/item_1/id = 1 +popup/item_2/text = "Folder Grouping" +popup/item_2/id = 2 +popup/item_3/text = "Path Grouping" +popup/item_3/id = 3 + +[node name="FolderColors" type="CheckBox" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +text = "Use Folder Colors" + +[node name="TrimFolderPaths" type="CheckBox" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +text = "Trim Folder Paths" + +[node name="ResourceTree" type="Tree" parent="VBoxPrimary/Margin/MainVSplit/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/MainVSplit/VBox"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxPrimary/Margin/MainVSplit/VBox/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 1 +text = "Sort Order" +vertical_alignment = 1 + +[node name="SortOption" type="OptionButton" parent="VBoxPrimary/Margin/MainVSplit/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/MainVSplit"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 15) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ContentList" type="ItemList" parent="VBoxPrimary/Margin/MainVSplit/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="dragged" from="VBoxPrimary/Margin/MainVSplit" to="." method="_on_main_v_split_dragged"] +[connection signal="gui_input" from="VBoxPrimary/Margin/MainVSplit/VBox/Logo" to="." method="_on_logo_gui_input"] +[connection signal="text_changed" from="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Search" to="." method="_on_search_text_changed"] +[connection signal="text_submitted" from="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Search" to="." method="_on_search_text_submitted"] +[connection signal="pressed" from="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options" to="." method="_on_options_pressed"] +[connection signal="toggled" from="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel/VBox/FolderColors" to="." method="_on_folder_colors_toggled"] +[connection signal="toggled" from="VBoxPrimary/Margin/MainVSplit/VBox/HBoxSearchSort/Options/OptionsPopup/OptionsPanel/VBox/TrimFolderPaths" to="." method="_on_trim_folder_paths_toggled"] +[connection signal="id_pressed" from="RightClickMenu" to="." method="_on_right_click_menu_id_pressed"] diff --git a/godot/addons/dialogic/Editor/Common/sidebar.gd b/godot/addons/dialogic/Editor/Common/sidebar.gd new file mode 100644 index 0000000..dd328f5 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/sidebar.gd @@ -0,0 +1,529 @@ +@tool +class_name DialogicSidebar extends Control + +## Script that handles the editor sidebar. + +signal content_item_activated(item_name) +signal show_sidebar(show: bool) + +# References +@onready var editors_manager = get_parent().get_parent() +@onready var resource_tree: Tree = %ResourceTree + +var current_resource_list: Array = [] + +enum GroupMode { + NONE, + TYPE, + FOLDER, + PATH, +} +var group_mode: GroupMode = GroupMode.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) + resource_tree.item_collapsed.connect(_on_resources_tree_item_collapsed) + + %ContentList.item_selected.connect( + func(idx: int): content_item_activated.emit(%ContentList.get_item_text(idx)) + ) + + %OpenButton.pressed.connect(_show_sidebar) + %CloseButton.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") + %Options.icon = get_theme_icon("GuiTabMenuHl", "EditorIcons") + %OptionsPanel.add_theme_stylebox_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")) + %OptionsPopup.hide() + + %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") + ) + + ## 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 + %GroupingOptions.set_item_icon(0, get_theme_icon("AnimationTrackGroup", "EditorIcons")) + %GroupingOptions.set_item_icon(1, get_theme_icon("Folder", "EditorIcons")) + %GroupingOptions.set_item_icon(2, get_theme_icon("FolderBrowse", "EditorIcons")) + %GroupingOptions.set_item_icon(3, get_theme_icon("AnimationTrackList", "EditorIcons")) + %GroupingOptions.item_selected.connect(_on_grouping_changed) + + await get_tree().process_frame + if DialogicUtil.get_editor_setting("sidebar_collapsed", false): + _hide_sidebar() + + %MainVSplit.split_offset = DialogicUtil.get_editor_setting("sidebar_v_split", 0) + group_mode = DialogicUtil.get_editor_setting("sidebar_group_mode", 0) + %GroupingOptions.select(%GroupingOptions.get_item_index(group_mode)) + + %FolderColors.button_pressed = DialogicUtil.get_editor_setting("sidebar_use_folder_colors", true) + %TrimFolderPaths.button_pressed = DialogicUtil.get_editor_setting("sidebar_trim_folder_paths", true) + + update_resource_list() + + +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) + + +#region 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) + +#endregion + + +################################################################################ +## RESOURCE LIST +################################################################################ + + +func _on_editors_resource_opened(_resource: Resource) -> void: + update_resource_list() + + +func _on_editors_editor_changed(_previous: DialogicEditor, current: DialogicEditor) -> void: + %ContentListSection.visible = current.current_resource is DialogicTimeline + update_resource_list() + + +## Cleans resources that have been deleted from the resource list +func clean_resource_list(resources_list: Array = []) -> PackedStringArray: + return PackedStringArray(resources_list.filter(func(x): return ResourceLoader.exists(x))) + + +#region BULDING/FILTERING THE RESOURCE LIST + +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 not 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 character_items: Array = get_directory_items.call(character_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/character.svg"), resources_list) + var timeline_items: Array = get_directory_items.call(timeline_directory, filter, load("res://addons/dialogic/Editor/Images/Resources/timeline.svg"), resources_list) + var all_items := character_items + timeline_items + + # BUILD TREE + var root: TreeItem = resource_tree.create_item() + + match group_mode: + GroupMode.NONE: + all_items.sort_custom(_sort_by_item_text) + for item in all_items: + add_item(item, root, current_file) + + + GroupMode.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) + + + GroupMode.FOLDER: + var dirs := {} + for item in all_items: + var dir := item.get_parent_directory() as String + if not dirs.has(dir): + dirs[dir] = [] + dirs[dir].append(item) + + for dir in dirs: + var dir_item := add_folder_item(dir, root) + + for item in dirs[dir]: + add_item(item, dir_item, current_file) + + + GroupMode.PATH: + # Collect all different directories that contain resources + var dirs := {} + for item in all_items: + var path := (item.metadata.get_base_dir() as String).trim_prefix("res://") + if not dirs.has(path): + dirs[path] = [] + dirs[path].append(item) + + # Sort them into ones with the same folder name + var dir_names := {} + for dir in dirs: + var sliced: String = dir.get_slice("/", dir.get_slice_count("/")-1) + if not sliced in dir_names: + dir_names[sliced] = {"folders":[dir]} + else: + dir_names[sliced].folders.append(dir) + + # Create a dictionary mapping a unique name to each directory + # If two have been found to have the same folder name, the parent directory is added + var unique_folder_names := {} + for dir_name in dir_names: + if dir_names[dir_name].folders.size() > 1: + for i in dir_names[dir_name].folders: + if "/" in i: + unique_folder_names[i.get_slice("/", i.get_slice_count("/")-2)+"/"+i.get_slice("/", i.get_slice_count("/")-1)] = i + else: + unique_folder_names[i] = i + else: + unique_folder_names[dir_name] = dir_names[dir_name].folders[0] + + # Sort the folder names by their folder name (not by the full path) + var sorted_dir_keys := unique_folder_names.keys() + sorted_dir_keys.sort_custom( + func(x, y): + return x.get_slice("/", x.get_slice_count("/")-1) < y.get_slice("/", y.get_slice_count("/")-1) + ) + var folder_colors: Dictionary = ProjectSettings.get_setting("file_customization/folder_colors", {}) + + for dir in sorted_dir_keys: + var display_name: String = dir + if not %TrimFolderPaths.button_pressed: + display_name = unique_folder_names[dir] + var dir_path: String = unique_folder_names[dir] + var dir_color_path := "" + var dir_color := Color.BLACK + if %FolderColors.button_pressed: + for path in folder_colors: + if String("res://"+dir_path+"/").begins_with(path) and len(path) > len(dir_color_path): + dir_color_path = path + dir_color = folder_colors[path] + + var dir_item := add_folder_item(display_name, root, dir_color, dir_path) + + for item in dirs[dir_path]: + add_item(item, dir_item, current_file) + + + 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) + + var bg_color := parent.get_custom_bg_color(0) + if bg_color != get_theme_color("base_color", "Editor"): + bg_color.a = 0.1 + tree_item.set_custom_bg_color(0, bg_color) + + return tree_item + + +func add_folder_item(label: String, parent:TreeItem, color:= Color.BLACK, tooltip:="") -> 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_tooltip_text(0, tooltip) + if color == Color.BLACK: + folder_item.set_custom_bg_color(0, get_theme_color("base_color", "Editor")) + else: + color.a = 0.2 + folder_item.set_custom_bg_color(0, color) + + if label in DialogicUtil.get_editor_setting("resource_list_collapsed_info", []): + folder_item.collapsed = true + + return folder_item + + +func get_directory_items(directory:Dictionary, filter:String, icon:Texture2D, resources_list:Array) -> 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 + + +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 get_parent_directory() -> String: + return (metadata.get_base_dir() as String).split("/")[-1] + + +func _sort_by_item_text(a: ResourceListItem, b: ResourceListItem) -> bool: + return a.text < b.text + +#endregion + + +#region INTERACTING WITH RESOURCES + + +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: + match 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) + + MOUSE_BUTTON_MIDDLE: + remove_item_from_list(resource_tree.get_selected()) + + MOUSE_BUTTON_RIGHT: + if resource_tree.get_selected().get_metadata(0): + %RightClickMenu.popup_on_parent(Rect2(get_global_mouse_position(), Vector2())) + %RightClickMenu.set_meta("item_clicked", resource_tree.get_selected()) + + +func _on_resources_tree_item_collapsed(item:TreeItem) -> void: + var collapsed_info: Array = DialogicUtil.get_editor_setting("resource_list_collapsed_info", []) + if item.get_text(0) in collapsed_info: + if not item.collapsed: + collapsed_info.erase(item.get_text(0)) + else: + if item.collapsed: + collapsed_info.append(item.get_text(0)) + DialogicUtil.set_editor_setting("resource_list_collapsed_info", collapsed_info) + + +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)) + + +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_by_path( + %RightClickMenu.get_meta("item_clicked").get_metadata(0) + ) + ) +#endregion + + +#region FILTERING + +func _on_search_text_changed(_new_text: String) -> void: + update_resource_list() + for item in resource_tree.get_root().get_children(): + 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() + +#endregion + + +#region CONTENT LIST + +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) + +#endregion + + +#region RESOURCE LIST OPTIONS + +func _on_options_pressed() -> void: + %OptionsPopup.popup_on_parent(Rect2(%Options.global_position+%Options.size*Vector2(0,1), Vector2())) + + +func _on_grouping_changed(idx: int) -> void: + var id: int = %GroupingOptions.get_item_id(idx) + if (GroupMode as Dictionary).values().has(id): + group_mode = (id as GroupMode) + DialogicUtil.set_editor_setting("sidebar_group_mode", id) + update_resource_list() + + %FolderColors.disabled = group_mode != GroupMode.PATH + %TrimFolderPaths.disabled = group_mode != GroupMode.PATH + + +func _on_folder_colors_toggled(toggled_on: bool) -> void: + DialogicUtil.set_editor_setting("sidebar_use_folder_colors", toggled_on) + update_resource_list() + + +func _on_trim_folder_paths_toggled(toggled_on: bool) -> void: + DialogicUtil.set_editor_setting("sidebar_trim_folder_paths", toggled_on) + update_resource_list() + +#endregion + + +func _on_main_v_split_dragged(offset: int) -> void: + DialogicUtil.set_editor_setting("sidebar_v_split", offset) diff --git a/godot/addons/dialogic/Editor/Common/sidebar.gd.uid b/godot/addons/dialogic/Editor/Common/sidebar.gd.uid new file mode 100644 index 0000000..f695352 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/sidebar.gd.uid @@ -0,0 +1 @@ +uid://myogqmakusx3 diff --git a/godot/addons/dialogic/Editor/Common/toolbar.gd b/godot/addons/dialogic/Editor/Common/toolbar.gd new file mode 100644 index 0000000..e4658d3 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/toolbar.gd @@ -0,0 +1,46 @@ +@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/godot/addons/dialogic/Editor/Common/toolbar.gd.uid b/godot/addons/dialogic/Editor/Common/toolbar.gd.uid new file mode 100644 index 0000000..6bebec8 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/toolbar.gd.uid @@ -0,0 +1 @@ +uid://1m3sqaws1hin diff --git a/godot/addons/dialogic/Editor/Common/unique_identifiers_manager.gd b/godot/addons/dialogic/Editor/Common/unique_identifiers_manager.gd new file mode 100644 index 0000000..fc7c993 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Common/unique_identifiers_manager.gd.uid b/godot/addons/dialogic/Editor/Common/unique_identifiers_manager.gd.uid new file mode 100644 index 0000000..50b0d67 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/unique_identifiers_manager.gd.uid @@ -0,0 +1 @@ +uid://bvbsqai5sh0na diff --git a/godot/addons/dialogic/Editor/Common/update_install_window.gd b/godot/addons/dialogic/Editor/Common/update_install_window.gd new file mode 100644 index 0000000..128c62e --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/update_install_window.gd @@ -0,0 +1,179 @@ +@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().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().grab_focus() + + +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('(?<!\\!)\\[(?<text>[^\\]]*)]\\((?<link>[^)]*)\\)') + 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('\\!\\[(?<text>[^\\]]*)]\\((?<link>[^)]*)\\)\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('\\*(?<text>[^\\*\\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)(\\*|-)(?<text>[^\\*\\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('(?<!`)`(?<text>[^`]+)`') + 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('(?<!`)```(?<text>[^`]+)```') + 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/godot/addons/dialogic/Editor/Common/update_install_window.gd.uid b/godot/addons/dialogic/Editor/Common/update_install_window.gd.uid new file mode 100644 index 0000000..4964b43 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/update_install_window.gd.uid @@ -0,0 +1 @@ +uid://cskkip1wso0pu diff --git a/godot/addons/dialogic/Editor/Common/update_install_window.tscn b/godot/addons/dialogic/Editor/Common/update_install_window.tscn new file mode 100644 index 0000000..58186db --- /dev/null +++ b/godot/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" uid="uid://cskkip1wso0pu" 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/godot/addons/dialogic/Editor/Common/update_manager.gd b/godot/addons/dialogic/Editor/Common/update_manager.gd new file mode 100644 index 0000000..cb33506 --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/update_manager.gd @@ -0,0 +1,190 @@ +@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"(?<major>\d+\.\d+)(-(?<state>alpha|beta)-)?(?(2)(?<stateversion>\d*)|\.(?<minor>\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/godot/addons/dialogic/Editor/Common/update_manager.gd.uid b/godot/addons/dialogic/Editor/Common/update_manager.gd.uid new file mode 100644 index 0000000..c6e01cf --- /dev/null +++ b/godot/addons/dialogic/Editor/Common/update_manager.gd.uid @@ -0,0 +1 @@ +uid://1tph6ios6ry2 diff --git a/godot/addons/dialogic/Editor/Events/BranchEnd.gd b/godot/addons/dialogic/Editor/Events/BranchEnd.gd new file mode 100644 index 0000000..d43ebe3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/BranchEnd.gd.uid b/godot/addons/dialogic/Editor/Events/BranchEnd.gd.uid new file mode 100644 index 0000000..f190a6c --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/BranchEnd.gd.uid @@ -0,0 +1 @@ +uid://cyjmcay08lmr8 diff --git a/godot/addons/dialogic/Editor/Events/BranchEnd.tscn b/godot/addons/dialogic/Editor/Events/BranchEnd.tscn new file mode 100644 index 0000000..63df64e --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/BranchEnd.tscn @@ -0,0 +1,48 @@ +[gd_scene load_steps=4 format=3 uid="uid://de13fdeebrkcb"] + +[ext_resource type="Script" uid="uid://cyjmcay08lmr8" path="res://addons/dialogic/Editor/Events/BranchEnd.gd" id="1"] + +[sub_resource type="Image" id="Image_6aqdp"] +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_6aqdp") + +[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/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd b/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd new file mode 100644 index 0000000..47fa94d --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd @@ -0,0 +1,430 @@ +@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() + + add_theme_constant_override("margin_bottom", DialogicUtil.get_editor_setting("event_block_margin", 0) * _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", + DialogicEvent.ValueType.AUDIO_PREVIEW: "res://addons/dialogic/Editor/Events/Fields/field_audio_preview.tscn", + DialogicEvent.ValueType.IMAGE_PREVIEW: "res://addons/dialogic/Editor/Events/Fields/field_image_preview.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 get_field_node(property_name:String) -> Node: + for i in field_list: + if i.get("property", "") == property_name: + return i.node + return null + + +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(4, true) + else: + popup.set_item_disabled(4, false) diff --git a/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd.uid b/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd.uid new file mode 100644 index 0000000..d383047 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/EventBlock/event_block.gd.uid @@ -0,0 +1 @@ +uid://dbncx2w0btjyx diff --git a/godot/addons/dialogic/Editor/Events/EventBlock/event_block.tscn b/godot/addons/dialogic/Editor/Events/EventBlock/event_block.tscn new file mode 100644 index 0000000..5402273 --- /dev/null +++ b/godot/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" uid="uid://dbncx2w0btjyx" 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_cl0tj"] +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_cl0tj") + +[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/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd b/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd new file mode 100644 index 0000000..7fad397 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd @@ -0,0 +1,24 @@ +@tool +extends PopupMenu + +var current_event: Node = null + +func _ready() -> void: + clear() + add_icon_item(get_theme_icon("Duplicate", "EditorIcons"), "Duplicate", 0) + add_separator() + add_icon_item(get_theme_icon("PlayStart", "EditorIcons"), "Play from here", 1) + add_separator() + add_icon_item(get_theme_icon("Help", "EditorIcons"), "Documentation", 2) + add_icon_item(get_theme_icon("CodeHighlighter", "EditorIcons"), "Open Code", 3) + add_separator() + add_icon_item(get_theme_icon("ArrowUp", "EditorIcons"), "Move up", 4) + add_icon_item(get_theme_icon("ArrowDown", "EditorIcons"), "Move down", 5) + add_separator() + add_icon_item(get_theme_icon("Remove", "EditorIcons"), "Delete", 6) + + 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/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd.uid b/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd.uid new file mode 100644 index 0000000..1bf4860 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/EventBlock/event_right_click_menu.gd.uid @@ -0,0 +1 @@ +uid://n1knm2ohcehu diff --git a/godot/addons/dialogic/Editor/Events/Fields/array_part.gd b/godot/addons/dialogic/Editor/Events/Fields/array_part.gd new file mode 100644 index 0000000..20c7c72 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/array_part.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/array_part.gd.uid new file mode 100644 index 0000000..693d1c8 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/array_part.gd.uid @@ -0,0 +1 @@ +uid://cm8w2iamuulp7 diff --git a/godot/addons/dialogic/Editor/Events/Fields/array_part.tscn b/godot/addons/dialogic/Editor/Events/Fields/array_part.tscn new file mode 100644 index 0000000..75932ec --- /dev/null +++ b/godot/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" uid="uid://cm8w2iamuulp7" 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_28ws6"] +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_28ws6") + +[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/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd b/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd new file mode 100644 index 0000000..c5b8b39 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd @@ -0,0 +1,43 @@ +@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/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd.uid new file mode 100644 index 0000000..a73c516 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.gd.uid @@ -0,0 +1 @@ +uid://b41laec1d54io diff --git a/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.tscn b/godot/addons/dialogic/Editor/Events/Fields/dictionary_part.tscn new file mode 100644 index 0000000..e0824b1 --- /dev/null +++ b/godot/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" uid="uid://b41laec1d54io" 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_teqf1"] +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_teqf1") + +[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/godot/addons/dialogic/Editor/Events/Fields/field_array.gd b/godot/addons/dialogic/Editor/Events/Fields/field_array.gd new file mode 100644 index 0000000..301ade5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_array.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_array.gd.uid new file mode 100644 index 0000000..5c155b8 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_array.gd.uid @@ -0,0 +1 @@ +uid://kmn7rns1g4fc diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_array.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_array.tscn new file mode 100644 index 0000000..4ba17ff --- /dev/null +++ b/godot/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" uid="uid://kmn7rns1g4fc" path="res://addons/dialogic/Editor/Events/Fields/field_array.gd" id="2"] + +[sub_resource type="Image" id="Image_v6fhx"] +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_v6fhx") + +[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/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd new file mode 100644 index 0000000..d7da2bc --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd @@ -0,0 +1,52 @@ +@tool +extends DialogicVisualEditorField + + +var file_path: String + + +func _ready() -> void: + self.pressed.connect(_on_pressed) + %AudioStreamPlayer.finished.connect(_on_finished) + + +#region OVERWRITES +################################################################################ + + +## To be overwritten +func _set_value(value:Variant) -> void: + file_path = value + self.disabled = file_path.is_empty() + _stop() + +#endregion + + +#region SIGNAL METHODS +################################################################################ + +func _on_pressed() -> void: + if %AudioStreamPlayer.playing: + _stop() + elif not file_path.is_empty(): + _play() + + +func _on_finished() -> void: + _stop() + +#endregion + + +func _stop() -> void: + %AudioStreamPlayer.stop() + %AudioStreamPlayer.stream = null + self.icon = get_theme_icon("Play", "EditorIcons") + + +func _play() -> void: + if ResourceLoader.exists(file_path): + %AudioStreamPlayer.stream = load(file_path) + %AudioStreamPlayer.play() + self.icon = get_theme_icon("Stop", "EditorIcons") diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd.uid new file mode 100644 index 0000000..185e71c --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.gd.uid @@ -0,0 +1 @@ +uid://lnr24bngydn2 diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.tscn new file mode 100644 index 0000000..949d94c --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_audio_preview.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://dotvrsumm5y5c"] + +[ext_resource type="Script" uid="uid://lnr24bngydn2" path="res://addons/dialogic/Editor/Events/Fields/field_audio_preview.gd" id="1_7wm54"] + +[node name="Field_Audio_Preview" type="Button"] +offset_right = 8.0 +offset_bottom = 8.0 +flat = true +script = ExtResource("1_7wm54") + +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] +unique_name_in_owner = true diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.gd b/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.gd new file mode 100644 index 0000000..1593be3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.gd.uid new file mode 100644 index 0000000..7696868 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.gd.uid @@ -0,0 +1 @@ +uid://do3x030t162u1 diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_bool_button.tscn new file mode 100644 index 0000000..9a8a4df --- /dev/null +++ b/godot/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" uid="uid://do3x030t162u1" 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/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.gd b/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.gd new file mode 100644 index 0000000..d01a116 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.gd.uid new file mode 100644 index 0000000..716669b --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.gd.uid @@ -0,0 +1 @@ +uid://ddxcyihcistll diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_bool_check.tscn new file mode 100644 index 0000000..3eb2eda --- /dev/null +++ b/godot/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" uid="uid://ddxcyihcistll" 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/godot/addons/dialogic/Editor/Events/Fields/field_color.gd b/godot/addons/dialogic/Editor/Events/Fields/field_color.gd new file mode 100644 index 0000000..707dfc5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_color.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_color.gd.uid new file mode 100644 index 0000000..8bdd8f6 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_color.gd.uid @@ -0,0 +1 @@ +uid://o26ppdmyst02 diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_color.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_color.tscn new file mode 100644 index 0000000..3b8fb36 --- /dev/null +++ b/godot/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" uid="uid://o26ppdmyst02" 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/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd b/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd new file mode 100644 index 0000000..9e0ef1a --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd @@ -0,0 +1,266 @@ +@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.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/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd.uid new file mode 100644 index 0000000..5c18a72 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_condition.gd.uid @@ -0,0 +1 @@ +uid://gx1mq5xn4mri diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_condition.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_condition.tscn new file mode 100644 index 0000000..67ef8df --- /dev/null +++ b/godot/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" uid="uid://gx1mq5xn4mri" 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_dmt4s"] +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_dmt4s") + +[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/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.gd b/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.gd new file mode 100644 index 0000000..f76ef27 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.gd.uid new file mode 100644 index 0000000..4dcbcef --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.gd.uid @@ -0,0 +1 @@ +uid://cjhy1b218xsh0 diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_dictionary.tscn new file mode 100644 index 0000000..801906e --- /dev/null +++ b/godot/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" uid="uid://cjhy1b218xsh0" path="res://addons/dialogic/Editor/Events/Fields/field_dictionary.gd" id="1_p4kmu"] + +[sub_resource type="Image" id="Image_mpo34"] +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_mpo34") + +[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/godot/addons/dialogic/Editor/Events/Fields/field_file.gd b/godot/addons/dialogic/Editor/Events/Fields/field_file.gd new file mode 100644 index 0000000..4ad03a0 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_file.gd @@ -0,0 +1,152 @@ +@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 +var show_editing_button := 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) + + %EditButton.icon = get_theme_icon("Edit", "EditorIcons") + %EditButton.button_down.connect(_on_EditButton_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 + + %EditButton.visible = show_editing_button and value + + 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 _on_EditButton_pressed() -> void: + if ResourceLoader.exists(current_value): + EditorInterface.inspect_object(load(current_value), "", true) + + +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 + if current_value == field_text or (file_mode != EditorFileDialog.FILE_MODE_OPEN_DIR and current_value.get_file() == field_text): + return + _on_file_dialog_selected(field_text) + +#endregion diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_file.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_file.gd.uid new file mode 100644 index 0000000..0c00aa9 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_file.gd.uid @@ -0,0 +1 @@ +uid://buepm260xnmaa diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_file.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_file.tscn new file mode 100644 index 0000000..f8dccd3 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_file.tscn @@ -0,0 +1,78 @@ +[gd_scene load_steps=6 format=3 uid="uid://7mvxuaulctcq"] + +[ext_resource type="Script" uid="uid://buepm260xnmaa" 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_6b7on"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wq6bt"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ye6ml"] +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/read_only = SubResource("StyleBoxEmpty_6b7on") +theme_override_styles/focus = SubResource("StyleBoxEmpty_wq6bt") +expand_to_text_length = true + +[node name="OpenButton" type="Button" parent="BG/HBox"] +unique_name_in_owner = true +layout_mode = 2 +flat = true + +[node name="EditButton" type="Button" parent="BG/HBox"] +unique_name_in_owner = true +layout_mode = 2 +flat = true + +[node name="ClearButton" type="Button" parent="BG/HBox"] +unique_name_in_owner = true +layout_mode = 2 +flat = true + +[node name="FocusStyle" type="Panel" parent="."] +visible = false +layout_mode = 2 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_ye6ml") + +[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/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.gd b/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.gd new file mode 100644 index 0000000..be7a98d --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.gd.uid new file mode 100644 index 0000000..2bc636d --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.gd.uid @@ -0,0 +1 @@ +uid://bl8pqdbnw005y diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_flex_value.tscn new file mode 100644 index 0000000..18799bb --- /dev/null +++ b/godot/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" uid="uid://bl8pqdbnw005y" 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/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd new file mode 100644 index 0000000..d5e93f4 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd @@ -0,0 +1,64 @@ +@tool +extends DialogicVisualEditorField + + +var body: Control +var image_path: String + +func _ready() -> void: + body = find_parent('Body') as Control + body.visibility_changed.connect(_on_body_visibility_toggled) + + +func _enter_tree() -> void: + %HiddenLabel.add_theme_color_override( + 'font_color', + event_resource.event_color.lerp(get_theme_color("font_color", "Editor"), 0.8)) + + +#region OVERWRITES +################################################################################ + +## To be overwritten +func _set_value(value:Variant) -> void: + if ResourceLoader.exists(value): + image_path = value + + if is_preview_enabled(): + self.texture = load(value) + custom_minimum_size.y = get_preview_size() + else: + self.texture = null + + minimum_size_changed.emit() + +#endregion + + +#region SIGNAL METHODS +################################################################################ + + +func _on_body_visibility_toggled() -> void: + custom_minimum_size.y = 0 + + if body.is_visible: + %HiddenLabel.visible = not is_preview_enabled() + + if is_preview_enabled() and ResourceLoader.exists(image_path): + self.texture = load(image_path) + custom_minimum_size.y = get_preview_size() + else: + self.texture = null + + minimum_size_changed.emit() + +#endregion + +func is_preview_enabled() -> bool: + return get_preview_size() != 0 + + +func get_preview_size() -> int: + return DialogicUtil.get_editor_setting( + "image_preview_height", 50) * DialogicUtil.get_editor_scale() diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd.uid new file mode 100644 index 0000000..4c45621 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.gd.uid @@ -0,0 +1 @@ +uid://u6evsmx7tynf diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.tscn new file mode 100644 index 0000000..e6007f0 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_image_preview.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=2 format=3 uid="uid://bar0t74j5v4sa"] + +[ext_resource type="Script" uid="uid://u6evsmx7tynf" path="res://addons/dialogic/Editor/Events/Fields/field_image_preview.gd" id="1_e5vbc"] + +[node name="Field_Image_Preview" type="TextureRect"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +expand_mode = 2 +stretch_mode = 4 +script = ExtResource("1_e5vbc") + +[node name="HiddenLabel" type="Label" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 0 +tooltip_text = "Preview hidden because project setting 'dialogic/accessibility/image_preview_height' is 0." +mouse_filter = 1 +text = "(Hidden)" diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_number.gd b/godot/addons/dialogic/Editor/Events/Fields/field_number.gd new file mode 100644 index 0000000..27145d9 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_number.gd @@ -0,0 +1,204 @@ +@tool +class_name DialogicVisualEditorFieldNumber +extends DialogicVisualEditorField + +## Event block field for integers and floats. Improved version of the native spinbox. + +@export_enum("Float", "Int", "Decible") var mode := 0 : + set(new_mode): + mode = new_mode + match mode: + 0: use_float_mode() #FLOAT + 1: use_int_mode() #INT + 2: use_decibel_mode() #DECIBLE +@export var allow_string: bool = false +@export var step: float = 0.1 +@export var enforce_step: bool = true +@export var min_value: float = -INF +@export var max_value: 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: + + for option in info.keys(): + match option: + 'min': min_value = info[option] + 'max': max_value = info[option] + 'prefix': update_prefix(info[option]) + 'suffix': update_suffix(info[option]) + 'step': + enforce_step = true + step = info[option] + 'hide_step_button': %Spin.hide() + + mode = info.get('mode', mode) + +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() -> void: + update_suffix("") + enforce_step = false + + +func use_int_mode() -> void: + update_suffix("") + enforce_step = true + + +func use_decibel_mode() -> void: + max_value = 6 + update_suffix("dB") + min_value = -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, get_node(button) as BaseButton) + + +func _on_decrement_button_down(button: NodePath) -> void: + _on_value_text_submitted(str(value-step)) + _holding_button(-1, 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_value), max_value) + if not enforce_step: + value = temp + else: + value = snapped(temp, step) + elif allow_string: + value = new_text + + if int(step) == step and step != 0: + %Value.text = str(int(value)) + else: + %Value.text = str(value).pad_decimals( + max( + len(str(float(step)-floorf(step)))-2, + len(str(float(value)-floorf(value)))-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_value + %Spin/Increment.disabled = value >= max_value + + +# 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/godot/addons/dialogic/Editor/Events/Fields/field_number.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_number.gd.uid new file mode 100644 index 0000000..57cc0b5 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_number.gd.uid @@ -0,0 +1 @@ +uid://dbegwhxegm271 diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_number.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_number.tscn new file mode 100644 index 0000000..0e125d5 --- /dev/null +++ b/godot/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" uid="uid://dbegwhxegm271" 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 = -1011.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 +size_flags_horizontal = 3 +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/normal = SubResource("StyleBoxEmpty_sj3oj") +theme_override_styles/focus = 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/read_only = SubResource("StyleBoxEmpty_8yqsu") +theme_override_styles/focus = SubResource("StyleBoxEmpty_8yqsu") +text = "0" +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/normal = SubResource("StyleBoxEmpty_smq50") +theme_override_styles/focus = 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"] +auto_translate_mode = 2 +layout_mode = 2 +size_flags_vertical = 3 +focus_neighbor_left = NodePath("../../Value") +focus_neighbor_top = NodePath(".") +focus_neighbor_bottom = NodePath("../Decrement") +theme_override_colors/icon_focus_color = Color(0.412738, 0.550094, 0.760917, 1) +theme_override_colors/icon_hover_color = Color(0.412738, 0.550094, 0.760917, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_increment") +theme_override_styles/pressed = SubResource("StyleBoxFlat_increment") +theme_override_styles/hover = 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"] +auto_translate_mode = 2 +layout_mode = 2 +size_flags_vertical = 3 +focus_neighbor_left = NodePath("../../Value") +focus_neighbor_top = NodePath("../Increment") +focus_neighbor_bottom = NodePath(".") +theme_override_colors/icon_focus_color = Color(0.412738, 0.550094, 0.760917, 1) +theme_override_colors/icon_hover_color = Color(0.412738, 0.550094, 0.760917, 1) +theme_override_styles/normal = SubResource("StyleBoxFlat_decrement") +theme_override_styles/pressed = SubResource("StyleBoxFlat_decrement") +theme_override_styles/hover = 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/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd new file mode 100644 index 0000000..8e1ee7a --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd @@ -0,0 +1,369 @@ +@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, ANY_VALID_STRING} +@export var mode := Modes.PURE_STRING +@export var fit_text_length := true +var collapse_when_empty := false +var valid_file_drop_extension := "" +var suggestions_func: Callable +var validation_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: + set(value): + if current_value != value: + current_value = value + current_value_updated = true +var current_selected := 0 +var current_value_updated := false + +## 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 + update_error_tooltip('') + 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_by_path(value) + Modes.ANY_VALID_STRING when validation_func: + %Search.text = validation_func.call(value).get('valid_text', 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) + suggestions_func = info.get('suggestions_func', suggestions_func) + validation_func = info.get('validation_func', validation_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 + + var error_label_style := StyleBoxFlat.new() + error_label_style.bg_color = get_theme_color('background', 'Editor') + error_label_style.border_color = get_theme_color('error_color', 'Editor') + error_label_style.set_border_width_all(1) + error_label_style.set_corner_radius_all(4) + error_label_style.set_content_margin_all(6) + + %ErrorTooltip.add_theme_stylebox_override('normal', error_label_style) + + +func change_to_empty() -> void: + update_error_tooltip('') + value_changed.emit(property_name, "") + + +func validate() -> void: + if mode == Modes.ANY_VALID_STRING and validation_func: + var validation_result: Dictionary = validation_func.call(current_value) + current_value = validation_result.get('valid_text', current_value) + update_error_tooltip(validation_result.get('error_tooltip', '')) + + +func update_error_tooltip(text: String) -> void: + %ErrorTooltip.text = text + if text.is_empty(): + %ErrorTooltip.hide() + %Search.remove_theme_color_override("font_color") + else: + %ErrorTooltip.reset_size() + %ErrorTooltip.global_position = global_position - Vector2(0, %ErrorTooltip.size.y + 4) + %ErrorTooltip.show() + %Search.add_theme_color_override("font_color", get_theme_color('error_color', 'Editor')) + +#endregion + + +#region SEARCH & SUGGESTION POPUP +################################################################################ + +func _on_Search_text_entered(new_text:String) -> void: + if mode == Modes.ANY_VALID_STRING: + if validation_func: + var validation_result: Dictionary = validation_func.call(new_text) + new_text = validation_result.get('valid_text', new_text) + update_error_tooltip(validation_result.get('error_tooltip', '')) + + set_value(new_text) + + value_changed.emit(property_name, current_value) + current_value_updated = false + hide_suggestions() + return + + 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 not just_update: + change_to_empty() + else: + %Search.show() + + if mode == Modes.ANY_VALID_STRING and !just_update: + if validation_func: + var validation_result: Dictionary = validation_func.call(new_text) + new_text = validation_result.get('valid_text', new_text) + update_error_tooltip(validation_result.get('error_tooltip', '')) + + current_value = new_text + + if just_update and new_text.is_empty() and %Search.text.ends_with("."): + new_text = %Search.text + + var suggestions: Dictionary = suggestions_func.call(new_text) + + var line_length := 0 + var idx := 0 + + if new_text and mode == Modes.ANY_VALID_STRING and not new_text in suggestions.keys(): + %Suggestions.add_item(new_text, get_theme_icon('GuiScrollArrowRight', 'EditorIcons')) + %Suggestions.set_item_metadata(idx, new_text) + line_length = get_theme_font('font', 'Label').get_string_size( + new_text, HORIZONTAL_ALIGNMENT_LEFT, -1, get_theme_font_size("font_size", 'Label') + ).x + %Suggestions.fixed_icon_size.x * %Suggestions.get_icon_scale() + _icon_margin * 2 + _h_separation + idx += 1 + + 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 = int(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 += int(_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) + + update_error_tooltip('') + hide_suggestions() + + grab_focus() + value_changed.emit(property_name, current_value) + current_value_updated = false + + +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() + validate() + + +func _on_search_focus_exited() -> void: + %Focus.hide() + if !%Suggestions.get_global_rect().has_point(get_global_mouse_position()): + hide_suggestions() + validate() + if current_value_updated: + value_changed.emit(property_name, current_value) + current_value_updated = false + +#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_by_path(path) + _set_value(path) + value_changed.emit(property_name, path) + current_value_updated = false + +#endregion diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd.uid new file mode 100644 index 0000000..1e6f222 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.gd.uid @@ -0,0 +1 @@ +uid://cowk63wwk126v diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn new file mode 100644 index 0000000..c2c898e --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn @@ -0,0 +1,144 @@ +[gd_scene load_steps=7 format=3 uid="uid://dpwhshre1n4t6"] + +[ext_resource type="Script" uid="uid://cowk63wwk126v" 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_qw5e6"] +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_qw5e6") + +[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 + +[node name="ErrorTooltip" type="Label" parent="PanelContainer/Focus"] +unique_name_in_owner = true +visible = false +top_level = true +layout_mode = 0 +offset_left = -2.0 +offset_top = -44.5 +offset_right = 11.0 +offset_bottom = -9.5 + +[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="toggled" from="PanelContainer/MarginContainer/HBoxContainer/SelectButton" to="." method="_on_SelectButton_toggled"] diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd b/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd new file mode 100644 index 0000000..b899b0b --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd @@ -0,0 +1,67 @@ +@tool +extends DialogicVisualEditorField + +## Event block field for constant options. For varying options use ComplexPicker. + +var options: Array = []: + set(o): + options = o + if current_value != -1: + set_value(current_value) + +## 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/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd.uid new file mode 100644 index 0000000..15187ec --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.gd.uid @@ -0,0 +1 @@ +uid://l6jbshj3y66l diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_options_fixed.tscn new file mode 100644 index 0000000..ad909ee --- /dev/null +++ b/godot/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" uid="uid://l6jbshj3y66l" 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/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd b/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd new file mode 100644 index 0000000..db46b51 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd.uid new file mode 100644 index 0000000..d2745f7 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.gd.uid @@ -0,0 +1 @@ +uid://do4y48h30412d diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_text_multiline.tscn new file mode 100644 index 0000000..63e095c --- /dev/null +++ b/godot/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" uid="uid://bf2nivn8txcw5" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd" id="2_ww6ga"] +[ext_resource type="Script" uid="uid://do4y48h30412d" 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/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd b/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd new file mode 100644 index 0000000..765f862 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd.uid new file mode 100644 index 0000000..1a2799f --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.gd.uid @@ -0,0 +1 @@ +uid://cgx1rn8km87ya diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_text_singleline.tscn new file mode 100644 index 0000000..374fcb5 --- /dev/null +++ b/godot/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" uid="uid://cgx1rn8km87ya" 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/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd new file mode 100644 index 0000000..8977349 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd @@ -0,0 +1,31 @@ +@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) + + +func _on_step_changed(new_step:float) -> void: + $X.step = new_step + $Y.step = new_step diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd.uid new file mode 100644 index 0000000..23b972e --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.gd.uid @@ -0,0 +1 @@ +uid://2dk2fuatjwru diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector2.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.tscn new file mode 100644 index 0000000..cd70c10 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector2.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=3 format=3 uid="uid://dtimnsj014cu"] + +[ext_resource type="Script" uid="uid://2dk2fuatjwru" 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 +size_flags_horizontal = 3 +enforce_step = false +min = -9999.0 +max = 9999.0 +prefix = "x" + +[node name="Y" parent="." instance=ExtResource("2_a0b6y")] +layout_mode = 2 +size_flags_horizontal = 3 +enforce_step = false +min = -9999.0 +max = 9999.0 +prefix = "y" diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd new file mode 100644 index 0000000..cc244a0 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd @@ -0,0 +1,34 @@ +@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) + + +func _on_step_changed(new_step:float) -> void: + $X.step = new_step + $Y.step = new_step + $Z.step = new_step diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd.uid new file mode 100644 index 0000000..c10644a --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.gd.uid @@ -0,0 +1 @@ +uid://y01tg3q2homo diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector3.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.tscn new file mode 100644 index 0000000..781509a --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector3.tscn @@ -0,0 +1,17 @@ +[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" uid="uid://y01tg3q2homo" 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 +size_flags_horizontal = 3 +enforce_step = false +min = -9999.0 +max = 9999.0 +prefix = "z" diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd new file mode 100644 index 0000000..e584b82 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd @@ -0,0 +1,37 @@ +@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) + + +func _on_step_changed(new_step:float) -> void: + $X.step = new_step + $Y.step = new_step + $Z.step = new_step + $W.step = new_step diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd.uid new file mode 100644 index 0000000..dd8716f --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.gd.uid @@ -0,0 +1 @@ +uid://chr6fkvkjugep diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector4.tscn b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.tscn new file mode 100644 index 0000000..86acc6f --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector4.tscn @@ -0,0 +1,25 @@ +[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" uid="uid://chr6fkvkjugep" 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="X" parent="." index="0"] +size_flags_horizontal = 1 +prefix = "" + +[node name="Y" parent="." index="1"] +size_flags_horizontal = 1 +prefix = "" + +[node name="W" parent="." index="3" instance=ExtResource("3_1jogk")] +layout_mode = 2 +size_flags_horizontal = 3 +enforce_step = false +min = -9999.0 +max = 9999.0 +prefix = "w" diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd b/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd new file mode 100644 index 0000000..c34b41c --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd @@ -0,0 +1,47 @@ +@tool +class_name DialogicVisualEditorFieldVector +extends DialogicVisualEditorField +## Base type for Vector event blocks + +var step := 0.001 : + set(val): + step = val + _on_step_changed(step) + +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 + + +func _on_step_changed(step:float) -> void: + pass diff --git a/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd.uid b/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd.uid new file mode 100644 index 0000000..6273507 --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/Fields/field_vector_base.gd.uid @@ -0,0 +1 @@ +uid://c25u8dv20exfr diff --git a/godot/addons/dialogic/Editor/Events/event_field.gd b/godot/addons/dialogic/Editor/Events/event_field.gd new file mode 100644 index 0000000..920758e --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/event_field.gd @@ -0,0 +1,36 @@ +@tool +class_name DialogicVisualEditorField +extends Control + +@warning_ignore("unused_signal") +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/godot/addons/dialogic/Editor/Events/event_field.gd.uid b/godot/addons/dialogic/Editor/Events/event_field.gd.uid new file mode 100644 index 0000000..47e9cfe --- /dev/null +++ b/godot/addons/dialogic/Editor/Events/event_field.gd.uid @@ -0,0 +1 @@ +uid://cbwisirnxfqtm diff --git a/godot/addons/dialogic/Editor/Events/styles/InputFieldsStyle.tres b/godot/addons/dialogic/Editor/Events/styles/InputFieldsStyle.tres new file mode 100644 index 0000000..fa952ee --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/ResourceMenuHover.tres b/godot/addons/dialogic/Editor/Events/styles/ResourceMenuHover.tres new file mode 100644 index 0000000..ecf8376 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/ResourceMenuNormal.tres b/godot/addons/dialogic/Editor/Events/styles/ResourceMenuNormal.tres new file mode 100644 index 0000000..d14860a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres b/godot/addons/dialogic/Editor/Events/styles/ResourceMenuPanelBackground.tres new file mode 100644 index 0000000..314544b --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/SectionPanel.tres b/godot/addons/dialogic/Editor/Events/styles/SectionPanel.tres new file mode 100644 index 0000000..b886c6e --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/SimpleButtonHover.tres b/godot/addons/dialogic/Editor/Events/styles/SimpleButtonHover.tres new file mode 100644 index 0000000..5be19d4 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/SimpleButtonNormal.tres b/godot/addons/dialogic/Editor/Events/styles/SimpleButtonNormal.tres new file mode 100644 index 0000000..e5c06b4 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/TextBackground.tres b/godot/addons/dialogic/Editor/Events/styles/TextBackground.tres new file mode 100644 index 0000000..0d74e3d --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres b/godot/addons/dialogic/Editor/Events/styles/selected_styleboxflat.tres new file mode 100644 index 0000000..c9312fb --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Events/styles/unselected_stylebox.tres b/godot/addons/dialogic/Editor/Events/styles/unselected_stylebox.tres new file mode 100644 index 0000000..2fae3b3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/HomePage/home_page.gd b/godot/addons/dialogic/Editor/HomePage/home_page.gd new file mode 100644 index 0000000..15aff25 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/HomePage/home_page.gd.uid b/godot/addons/dialogic/Editor/HomePage/home_page.gd.uid new file mode 100644 index 0000000..80bfac7 --- /dev/null +++ b/godot/addons/dialogic/Editor/HomePage/home_page.gd.uid @@ -0,0 +1 @@ +uid://bl3vudjf3wn2x diff --git a/godot/addons/dialogic/Editor/HomePage/home_page.tscn b/godot/addons/dialogic/Editor/HomePage/home_page.tscn new file mode 100644 index 0000000..5e91757 --- /dev/null +++ b/godot/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" uid="uid://bl3vudjf3wn2x" 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_ipcwk"] +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_ipcwk") + +[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/godot/addons/dialogic/Editor/HomePage/icon_bg.png b/godot/addons/dialogic/Editor/HomePage/icon_bg.png new file mode 100644 index 0000000..77f127d Binary files /dev/null and b/godot/addons/dialogic/Editor/HomePage/icon_bg.png differ diff --git a/godot/addons/dialogic/Editor/HomePage/icon_bg.png.import b/godot/addons/dialogic/Editor/HomePage/icon_bg.png.import new file mode 100644 index 0000000..2160d4d --- /dev/null +++ b/godot/addons/dialogic/Editor/HomePage/icon_bg.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvmlp5nxb2rer" +path="res://.godot/imported/icon_bg.png-5937ce0a857c4a8a9d624ea9ebf09a97.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/HomePage/icon_bg.png" +dest_files=["res://.godot/imported/icon_bg.png-5937ce0a857c4a8a9d624ea9ebf09a97.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/HomePage/tips.txt b/godot/addons/dialogic/Editor/HomePage/tips.txt new file mode 100644 index 0000000..4ca5ec7 --- /dev/null +++ b/godot/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]<Oh hi/Hello you/Well, well>[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/godot/addons/dialogic/Editor/Images/Dropdown/default.svg b/godot/addons/dialogic/Editor/Images/Dropdown/default.svg new file mode 100644 index 0000000..1437dbc --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/default.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="8" cy="8" r="3" fill="#2F80ED"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/default.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/default.svg.import new file mode 100644 index 0000000..3106df7 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/default.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsx8dtqf3vych" +path="res://.godot/imported/default.svg-3f34de5e45bef5de4d9c15ef78c00c6c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/default.svg" +dest_files=["res://.godot/imported/default.svg-3f34de5e45bef5de4d9c15ef78c00c6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg b/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg new file mode 100644 index 0000000..7fb881f --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg @@ -0,0 +1,10 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_3666_572)"> +<path d="M11.1812 7.46591V8.57955H4.81756V7.46591H11.1812ZM7.99938 11.483C7.7508 11.483 7.53868 11.3968 7.36301 11.2244C7.19067 11.0488 7.10449 10.8366 7.10449 10.5881C7.10449 10.3494 7.19067 10.1439 7.36301 9.97159C7.53868 9.79924 7.7508 9.71307 7.99938 9.71307C8.23802 9.71307 8.44351 9.79924 8.61586 9.97159C8.7882 10.1439 8.87438 10.3494 8.87438 10.5881C8.87438 10.8366 8.7882 11.0488 8.61586 11.2244C8.44351 11.3968 8.23802 11.483 7.99938 11.483ZM7.99938 6.33239C7.83366 6.33239 7.68285 6.29261 7.54696 6.21307C7.41107 6.13352 7.30336 6.0258 7.22381 5.88991C7.14427 5.75402 7.10449 5.60322 7.10449 5.4375C7.10449 5.19886 7.19067 4.99337 7.36301 4.82102C7.53868 4.64867 7.7508 4.5625 7.99938 4.5625C8.23802 4.5625 8.44351 4.64867 8.61586 4.82102C8.7882 4.99337 8.87438 5.19886 8.87438 5.4375C8.87438 5.68608 8.7882 5.8982 8.61586 6.07386C8.44351 6.24621 8.23802 6.33239 7.99938 6.33239Z" fill="#2F80ED"/> +</g> +<defs> +<clipPath id="clip0_3666_572"> +<rect width="16" height="16" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg.import new file mode 100644 index 0000000..b9ee08d --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/divide.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5laykjsxaxtl" +path="res://.godot/imported/divide.svg-4928f878a07ba93ebc44d8ae73ad4c1f.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/divide.svg" +dest_files=["res://.godot/imported/divide.svg-4928f878a07ba93ebc44d8ae73ad4c1f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/join.svg b/godot/addons/dialogic/Editor/Images/Dropdown/join.svg new file mode 100644 index 0000000..9eb7aa9 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/join.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M4 7V9H7.5V12L11.5 8L7.5 4V7H4Z" fill="#A5EFAC"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/join.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/join.svg.import new file mode 100644 index 0000000..9a91637 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/join.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7j220k0ewh35" +path="res://.godot/imported/join.svg-2f0d7b9e8e01cf0e62b8c3a85aff6213.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/join.svg" +dest_files=["res://.godot/imported/join.svg-2f0d7b9e8e01cf0e62b8c3a85aff6213.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg b/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg new file mode 100644 index 0000000..e234619 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M11.5 9L11.5 7L8 7L8 4L4 8L8 12L8 9L11.5 9Z" fill="#D14A4A"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg.import new file mode 100644 index 0000000..bf243da --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/leave.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cspjyvye6c0r6" +path="res://.godot/imported/leave.svg-c936f6e3d601b8c12c23f205a765084e.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/leave.svg" +dest_files=["res://.godot/imported/leave.svg-c936f6e3d601b8c12c23f205a765084e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg b/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg new file mode 100644 index 0000000..eb5d732 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg @@ -0,0 +1,10 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_3666_568)"> +<path d="M10.2291 7.08807V8.18182H5.77459V7.08807H10.2291Z" fill="#2F80ED"/> +</g> +<defs> +<clipPath id="clip0_3666_568"> +<rect width="16" height="16" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg.import new file mode 100644 index 0000000..63d5e9a --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/minus.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwy14qrkfoeb" +path="res://.godot/imported/minus.svg-29f22d1aa24635bae2c03057c07be8bc.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/minus.svg" +dest_files=["res://.godot/imported/minus.svg-29f22d1aa24635bae2c03057c07be8bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg b/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg new file mode 100644 index 0000000..d4327d1 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg @@ -0,0 +1,10 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_3666_570)"> +<path d="M10.4255 11.2045L4.81756 5.59659L5.57324 4.84091L11.1812 10.4489L10.4255 11.2045ZM5.57324 11.2045L4.81756 10.4489L10.4255 4.84091L11.1812 5.59659L5.57324 11.2045Z" fill="#2F80ED"/> +</g> +<defs> +<clipPath id="clip0_3666_570"> +<rect width="16" height="16" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import new file mode 100644 index 0000000..93bb66a --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/multiply.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddmapfkunbtg7" +path="res://.godot/imported/multiply.svg-0e9db99aafb66d43ee14adcca26c5b47.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/multiply.svg" +dest_files=["res://.godot/imported/multiply.svg-0e9db99aafb66d43ee14adcca26c5b47.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg b/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg new file mode 100644 index 0000000..adf5179 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg @@ -0,0 +1,10 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_3666_566)"> +<path d="M7.44256 11.304V4.74148H8.5562V11.304H7.44256ZM4.71813 8.57955V7.46591H11.2806V8.57955H4.71813Z" fill="#2F80ED"/> +</g> +<defs> +<clipPath id="clip0_3666_566"> +<rect width="16" height="16" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg.import new file mode 100644 index 0000000..80d686d --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/plus.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqqtygfbvgtag" +path="res://.godot/imported/plus.svg-e094b0b8505b5d910717883d06553532.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/plus.svg" +dest_files=["res://.godot/imported/plus.svg-e094b0b8505b5d910717883d06553532.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/set.svg b/godot/addons/dialogic/Editor/Images/Dropdown/set.svg new file mode 100644 index 0000000..16c6a8b --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/set.svg @@ -0,0 +1,10 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_3666_561)"> +<path d="M4.93688 7.08807V6.0142H11.0619V7.08807H4.93688ZM4.93688 10.0312V8.95739H11.0619V10.0312H4.93688Z" fill="#2F80ED"/> +</g> +<defs> +<clipPath id="clip0_3666_561"> +<rect width="16" height="16" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/set.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/set.svg.import new file mode 100644 index 0000000..948482b --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/set.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddcfl67v0r1lw" +path="res://.godot/imported/set.svg-f100fad003be2285d5d0da5c58417203.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/set.svg" +dest_files=["res://.godot/imported/set.svg-f100fad003be2285d5d0da5c58417203.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Dropdown/update.svg b/godot/addons/dialogic/Editor/Images/Dropdown/update.svg new file mode 100644 index 0000000..44f1f3c --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/update.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3.45082 5.30639L4.73587 5.26497L4.79109 6.97825L6.50437 6.92302L6.54579 8.20807L3.54747 8.30471L3.45082 5.30639Z" fill="#2F80ED"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3458 12H12.0601L12.0601 10.2858H10.3459L10.3459 9.00012L13.3458 9.00012L13.3458 12Z" fill="#2F80ED"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8.35461 11.7143C9.76618 11.7143 10.9724 10.8651 11.4559 9.66667H12.8546C12.3227 11.5864 10.5095 13 8.35461 13C6.1997 13 4.38656 11.5864 3.85461 9.66667H5.25336C5.73678 10.8651 6.94305 11.7143 8.35461 11.7143ZM5.41117 7C5.96903 5.98047 7.07784 5.28571 8.35461 5.28571C9.63139 5.28571 10.7402 5.98047 11.2981 7H12.7476C12.1082 5.25221 10.3828 4 8.35461 4C6.32646 4 4.60105 5.25221 3.96159 7H5.41117Z" fill="#2F80ED"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Dropdown/update.svg.import b/godot/addons/dialogic/Editor/Images/Dropdown/update.svg.import new file mode 100644 index 0000000..cd0064c --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Dropdown/update.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://t1roknwygcf3" +path="res://.godot/imported/update.svg-cefa0fe6bfa50911bb9a77982288e485.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Dropdown/update.svg" +dest_files=["res://.godot/imported/update.svg-cefa0fe6bfa50911bb9a77982288e485.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg b/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg new file mode 100644 index 0000000..eb97691 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg @@ -0,0 +1 @@ +<svg width="14" height="6" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m8.046 5.406 4.91-5.134C13.063.16 12.954 0 12.838 0h-2.432L7.303 3.244c-.173.181-.402.189-.582 0L3.618 0H1.166c-.159 0-.208.19-.13.272l4.942 5.134c.54.56 1.538.554 2.068 0z" fill="#e0e0e0" style="fill:#fff;fill-opacity:1;stroke-width:1.05736"/></svg> diff --git a/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import b/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import new file mode 100644 index 0000000..26aa135 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Interactable/decrement_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brjikovneb63n" +path="res://.godot/imported/decrement_icon.svg-9556cf56db91e200fb946372e010fd5e.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Interactable/decrement_icon.svg" +dest_files=["res://.godot/imported/decrement_icon.svg-9556cf56db91e200fb946372e010fd5e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg b/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg new file mode 100644 index 0000000..0b72c07 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg @@ -0,0 +1 @@ +<svg width="14" height="6" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m5.954.418-4.91 5.135c-.107.112.002.271.118.271h2.432l3.103-3.243c.173-.182.402-.19.582 0l3.103 3.243h2.452c.159 0 .208-.19.13-.271L8.021.418c-.54-.56-1.538-.554-2.068 0z" fill="#e0e0e0" style="fill:#fff;fill-opacity:1;stroke-width:1.05736"/></svg> diff --git a/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import b/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import new file mode 100644 index 0000000..f411bd0 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Interactable/increment_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dh1ycbmw8anqh" +path="res://.godot/imported/increment_icon.svg-081e6509e76349f0628c55a41e85fd65.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Interactable/increment_icon.svg" +dest_files=["res://.godot/imported/increment_icon.svg-081e6509e76349f0628c55a41e85fd65.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg b/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg new file mode 100644 index 0000000..7331b61 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg @@ -0,0 +1,4 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M13 10H16V12H13V15H11V12H8V10H11V7H13V10Z" fill="#A5EFAC"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M1.29289 2.29289C1.48043 2.10536 1.73478 2 2 2H8C8.26522 2 8.51957 2.10536 8.70711 2.29289C8.89464 2.48043 9 2.73478 9 3V4C9 4.26522 9.10536 4.51957 9.29289 4.70711C9.48043 4.89464 9.73478 5 10 5H14C14.2652 5 14.5196 5.10536 14.7071 5.29289C14.8946 5.48043 15 5.73478 15 6V9H14V6H10V9H7V9.5V13H10V14H2C1.73478 14 1.48043 13.8946 1.29289 13.7071C1.10536 13.5196 1 13.2652 1 13V11V5V3C1 2.73478 1.10536 2.48043 1.29289 2.29289ZM14 14C14.2652 14 14.5196 13.8946 14.7071 13.7071C14.8946 13.5196 15 13.2652 15 13H14V14Z" fill="#E0E0E0"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import new file mode 100644 index 0000000..cd569e7 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/add-folder.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://babwe22dqjta" +path="res://.godot/imported/add-folder.svg-41a970370f904038e63c13bddbdb6450.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/add-folder.svg" +dest_files=["res://.godot/imported/add-folder.svg-41a970370f904038e63c13bddbdb6450.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg b/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg new file mode 100644 index 0000000..3e3f7af --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><path d="m14 17-6-6 6-6" fill="none" stroke="#fff" stroke-width="2"/></svg> \ No newline at end of file diff --git a/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import new file mode 100644 index 0000000..bc57609 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/close-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bff65e82555qr" +path="res://.godot/imported/close-icon.svg-c630c93ada599b08938f4854f5376f2f.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/close-icon.svg" +dest_files=["res://.godot/imported/close-icon.svg-c630c93ada599b08938f4854f5376f2f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg b/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg new file mode 100644 index 0000000..4032eab --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg @@ -0,0 +1,3 @@ +<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M8 5L14 11L8 17" stroke="white" stroke-width="2"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import new file mode 100644 index 0000000..17fd504 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/closed-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dx3o2ild56i76" +path="res://.godot/imported/closed-icon.svg-b4f16653b91d6792313a130565319b2f.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/closed-icon.svg" +dest_files=["res://.godot/imported/closed-icon.svg-b4f16653b91d6792313a130565319b2f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg b/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg new file mode 100644 index 0000000..3ec3def --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg @@ -0,0 +1,5 @@ +<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<circle cx="10.5" cy="3.5" r="1.5" fill="white"/> +<circle cx="10.5" cy="11" r="1.5" fill="white"/> +<circle cx="10.5" cy="18.5" r="1.5" fill="white"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import new file mode 100644 index 0000000..0d7d7e7 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/expand-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cl03vrbj5wsjk" +path="res://.godot/imported/expand-icon.svg-26099b197ab0f314e2253848fcc22962.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/expand-icon.svg" +dest_files=["res://.godot/imported/expand-icon.svg-26099b197ab0f314e2253848fcc22962.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg b/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg new file mode 100644 index 0000000..c66c422 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg @@ -0,0 +1,3 @@ +<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M17 8L11 14L5 8" stroke="white" stroke-width="2"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import new file mode 100644 index 0000000..1288c3a --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/open-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://mc7a24bcvjo3" +path="res://.godot/imported/open-icon.svg-1a2ae6d0121a79b624c0fb87cc9ceea2.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/open-icon.svg" +dest_files=["res://.godot/imported/open-icon.svg-1a2ae6d0121a79b624c0fb87cc9ceea2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/variable.svg b/godot/addons/dialogic/Editor/Images/Pieces/variable.svg new file mode 100644 index 0000000..236ca35 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/variable.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M13.9645 2.62927L12.8459 5.22444C12.6271 5.04546 12.3835 4.89134 12.1151 4.76208C11.8565 4.62287 11.5881 4.55327 11.3097 4.55327C11.0909 4.55327 10.8821 4.59802 10.6832 4.68751C10.4943 4.77699 10.3203 4.89631 10.1612 5.04546C10.0021 5.19461 9.86293 5.36364 9.74361 5.55256C9.6243 5.73154 9.52486 5.91052 9.44532 6.08949L9.14702 6.93964C9.35583 7.37714 9.55966 7.79475 9.75853 8.19248C9.92756 8.53055 10.1016 8.87359 10.2805 9.2216C10.4595 9.55966 10.6087 9.82813 10.728 10.027C10.9169 10.3153 11.1058 10.6136 11.2948 10.9219C11.4837 11.2202 11.6925 11.4936 11.9212 11.7422C12.0206 11.8516 12.1399 11.9261 12.2791 11.9659C12.4283 11.9957 12.5625 12.0107 12.6818 12.0107C12.8707 12.0107 13.0497 11.9858 13.2188 11.9361C13.3878 11.8864 13.5469 11.8217 13.696 11.7422L14.0689 12.2195C13.9297 12.4382 13.7607 12.657 13.5618 12.8757C13.3629 13.0945 13.1442 13.2933 12.9055 13.4723C12.6769 13.6513 12.4283 13.7955 12.1598 13.9048C11.9013 14.0242 11.6378 14.0838 11.3693 14.0838C11.1307 14.0838 10.9169 14.044 10.728 13.9645C10.549 13.8949 10.3849 13.8004 10.2358 13.6811C10.0867 13.5519 9.94745 13.4027 9.81819 13.2337C9.68893 13.0646 9.55469 12.8906 9.41549 12.7117C9.30611 12.5426 9.1868 12.3388 9.05753 12.1001C8.93822 11.8516 8.81393 11.598 8.68466 11.3395C8.5554 11.081 8.42614 10.8324 8.29688 10.5938C8.16762 10.3452 8.04333 10.1364 7.92401 9.96734C7.8743 10.1364 7.82955 10.3054 7.78978 10.4744C7.75001 10.6136 7.70526 10.7578 7.65555 10.907C7.60583 11.0561 7.56109 11.1705 7.52131 11.25C7.37216 11.5682 7.18324 11.8963 6.95455 12.2344C6.72586 12.5724 6.46236 12.8807 6.16407 13.1591C5.87572 13.4276 5.55753 13.6513 5.20952 13.8303C4.86151 13.9993 4.49361 14.0838 4.10583 14.0838C3.75782 14.0838 3.41975 14.0142 3.09162 13.875C2.7635 13.7457 2.46023 13.5717 2.18182 13.353L3.12145 10.8771C3.43964 11.076 3.78765 11.255 4.16549 11.4141C4.54333 11.5632 4.92117 11.6378 5.29901 11.6378C5.41833 11.6378 5.54262 11.6278 5.67188 11.608C5.80114 11.5781 5.92543 11.5384 6.04475 11.4886C6.17401 11.429 6.28836 11.3594 6.38779 11.2798C6.48722 11.1903 6.5618 11.0859 6.61151 10.9666C6.68111 10.8374 6.75569 10.6683 6.83523 10.4595C6.91478 10.2507 6.99432 10.0419 7.07387 9.8331C7.16336 9.59447 7.25285 9.34091 7.34234 9.07245L4.53836 4.5831C4.4091 4.43395 4.25001 4.31464 4.06109 4.22515C3.88211 4.12572 3.69319 4.076 3.49432 4.076C3.32529 4.076 3.1662 4.1108 3.01705 4.1804C2.8679 4.24006 2.72373 4.32458 2.58452 4.43395L2.18182 3.91194C2.32103 3.70313 2.48509 3.4993 2.67401 3.30043C2.87287 3.09162 3.08665 2.90768 3.31535 2.74859C3.54404 2.57955 3.78765 2.44532 4.04617 2.34589C4.30469 2.23651 4.56819 2.18182 4.83665 2.18182C5.16478 2.18182 5.46805 2.26634 5.74645 2.43537C6.02486 2.59447 6.28339 2.79333 6.52202 3.03197C6.76066 3.2706 6.97941 3.52912 7.17827 3.80753C7.37714 4.08594 7.55611 4.34447 7.7152 4.5831C7.79475 4.69248 7.87927 4.83168 7.96876 5.00072C8.06819 5.15981 8.16265 5.3189 8.25214 5.47799C8.36151 5.65697 8.46591 5.85086 8.56535 6.05966C8.66478 5.82103 8.76918 5.58239 8.87856 5.34376C8.96805 5.14489 9.05753 4.94106 9.14702 4.73225C9.24645 4.5135 9.33594 4.32458 9.41549 4.16549C9.56464 3.88708 9.73367 3.62856 9.92259 3.38992C10.1115 3.15128 10.3203 2.94248 10.549 2.7635C10.7876 2.58452 11.0462 2.44532 11.3246 2.34589C11.603 2.23651 11.9063 2.18182 12.2344 2.18182C12.5426 2.18182 12.8409 2.2216 13.1293 2.30114C13.4176 2.38069 13.696 2.49006 13.9645 2.62927Z" fill="white"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/variable.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/variable.svg.import new file mode 100644 index 0000000..f3eddb1 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/variable.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dih1coellhwm8" +path="res://.godot/imported/variable.svg-50a50248b9d47e5556571e4111e8d5b4.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/variable.svg" +dest_files=["res://.godot/imported/variable.svg-50a50248b9d47e5556571e4111e8d5b4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png b/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png new file mode 100644 index 0000000..779e80f Binary files /dev/null and b/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png differ diff --git a/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import b/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import new file mode 100644 index 0000000..5aed468 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/variable_icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ikdhcat2nq2r" +path="res://.godot/imported/variable_icon.png-df9d711980209a7752dc8762037e39ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/variable_icon.png" +dest_files=["res://.godot/imported/variable_icon.png-df9d711980209a7752dc8762037e39ad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Pieces/warning.svg b/godot/addons/dialogic/Editor/Images/Pieces/warning.svg new file mode 100644 index 0000000..a252bde --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/warning.svg @@ -0,0 +1,3 @@ +<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M12.366 4.5C11.9811 3.83333 11.0189 3.83333 10.634 4.5L3.27276 17.25C2.88786 17.9167 3.36898 18.75 4.13878 18.75H18.8612C19.631 18.75 20.1121 17.9167 19.7272 17.25L12.366 4.5ZM10.6668 14.3809H12.073L12.2723 8.46875H10.4676L10.6668 14.3809ZM12.0555 15.5586C11.8836 15.3906 11.6551 15.3066 11.3699 15.3066C11.0887 15.3066 10.8602 15.3926 10.6844 15.5645C10.5125 15.7324 10.4266 15.9453 10.4266 16.2031C10.4266 16.4609 10.5125 16.6738 10.6844 16.8418C10.8602 17.0098 11.0887 17.0937 11.3699 17.0937C11.6551 17.0937 11.8836 17.0098 12.0555 16.8418C12.2312 16.6738 12.3191 16.4609 12.3191 16.2031C12.3191 15.9414 12.2312 15.7266 12.0555 15.5586Z" fill="#FCFF73"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Pieces/warning.svg.import b/godot/addons/dialogic/Editor/Images/Pieces/warning.svg.import new file mode 100644 index 0000000..06a13ac --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Pieces/warning.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4n3j4lvatwxb" +path="res://.godot/imported/warning.svg-a48ae93c4663637f2aca88d055604495.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Pieces/warning.svg" +dest_files=["res://.godot/imported/warning.svg-a48ae93c4663637f2aca88d055604495.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Resources/character.svg b/godot/addons/dialogic/Editor/Images/Resources/character.svg new file mode 100644 index 0000000..8871f5e --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/character.svg @@ -0,0 +1,4 @@ +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M11.6364 4.36363C11.6364 6.37194 10.0083 7.99999 8 7.99999C5.99169 7.99999 4.36363 6.37194 4.36363 4.36363C4.36363 2.35532 5.99169 0.727264 8 0.727264C10.0083 0.727264 11.6364 2.35532 11.6364 4.36363Z" fill="white"/> +<path d="M12.3636 13.3904C12.3636 15.2727 10.41 15.2727 8 15.2727C5.59003 15.2727 3.63636 15.2727 3.63636 13.3904C3.63636 10.0117 5.59003 7.27272 8 7.27272C10.41 7.27272 12.3636 10.0117 12.3636 13.3904Z" fill="white"/> +</svg> diff --git a/godot/addons/dialogic/Editor/Images/Resources/character.svg.import b/godot/addons/dialogic/Editor/Images/Resources/character.svg.import new file mode 100644 index 0000000..79b6082 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/character.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbea0efx0ybu7" +path="res://.godot/imported/character.svg-48bc1c93fa13733a935ca2c669d933a7.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Resources/character.svg" +dest_files=["res://.godot/imported/character.svg-48bc1c93fa13733a935ca2c669d933a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Resources/icon_character.png b/godot/addons/dialogic/Editor/Images/Resources/icon_character.png new file mode 100644 index 0000000..5858854 Binary files /dev/null and b/godot/addons/dialogic/Editor/Images/Resources/icon_character.png differ diff --git a/godot/addons/dialogic/Editor/Images/Resources/icon_character.png.import b/godot/addons/dialogic/Editor/Images/Resources/icon_character.png.import new file mode 100644 index 0000000..a03e360 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/icon_character.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmwrsq48ywc50" +path="res://.godot/imported/icon_character.png-97a1851bbafe2b302ea88c25a87ee2c1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Resources/icon_character.png" +dest_files=["res://.godot/imported/icon_character.png-97a1851bbafe2b302ea88c25a87ee2c1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Resources/portrait.svg b/godot/addons/dialogic/Editor/Images/Resources/portrait.svg new file mode 100644 index 0000000..006807a --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/portrait.svg @@ -0,0 +1,9 @@ +<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" version="1.1"> + <g> + <title>Layer 1 + + + + + + \ No newline at end of file diff --git a/godot/addons/dialogic/Editor/Images/Resources/portrait.svg.import b/godot/addons/dialogic/Editor/Images/Resources/portrait.svg.import new file mode 100644 index 0000000..d12dd14 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/portrait.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dfi7fhfc4dbc3" +path="res://.godot/imported/portrait.svg-7d29c7cfe3e086d65dce33c3d66c48cd.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Resources/portrait.svg" +dest_files=["res://.godot/imported/portrait.svg-7d29c7cfe3e086d65dce33c3d66c48cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Resources/timeline.svg b/godot/addons/dialogic/Editor/Images/Resources/timeline.svg new file mode 100644 index 0000000..fd0a48a --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/timeline.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Editor/Images/Resources/timeline.svg.import b/godot/addons/dialogic/Editor/Images/Resources/timeline.svg.import new file mode 100644 index 0000000..aaa5521 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Resources/timeline.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://j7ym07anlusi" +path="res://.godot/imported/timeline.svg-4b0b3233c6ce249f8277502cd9b13eaf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Resources/timeline.svg" +dest_files=["res://.godot/imported/timeline.svg-4b0b3233c6ce249f8277502cd9b13eaf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg b/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg new file mode 100644 index 0000000..9a42063 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import b/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import new file mode 100644 index 0000000..3ad3985 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Toolbar/add-character.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://my600mb32ydt" +path="res://.godot/imported/add-character.svg-a658b65c1225b02657a50d5c965e0d5e.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Toolbar/add-character.svg" +dest_files=["res://.godot/imported/add-character.svg-a658b65c1225b02657a50d5c965e0d5e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg b/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg new file mode 100644 index 0000000..6f09e2e --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import b/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import new file mode 100644 index 0000000..9f497f9 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Toolbar/add-timeline.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bymlbr4o2m3jc" +path="res://.godot/imported/add-timeline.svg-86961b528ebdf01f585931a15fea1755.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Toolbar/add-timeline.svg" +dest_files=["res://.godot/imported/add-timeline.svg-86961b528ebdf01f585931a15fea1755.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/Unknown.png b/godot/addons/dialogic/Editor/Images/Unknown.png new file mode 100644 index 0000000..87ab913 Binary files /dev/null and b/godot/addons/dialogic/Editor/Images/Unknown.png differ diff --git a/godot/addons/dialogic/Editor/Images/Unknown.png.import b/godot/addons/dialogic/Editor/Images/Unknown.png.import new file mode 100644 index 0000000..d05ffc3 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/Unknown.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbf2dlmbn12h0" +path="res://.godot/imported/Unknown.png-1cc7645f56036e8d378a70ac1dd772bb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/Unknown.png" +dest_files=["res://.godot/imported/Unknown.png-1cc7645f56036e8d378a70ac1dd772bb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/dialogic-logo.svg b/godot/addons/dialogic/Editor/Images/dialogic-logo.svg new file mode 100644 index 0000000..d6e5b69 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/dialogic-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Editor/Images/dialogic-logo.svg.import b/godot/addons/dialogic/Editor/Images/dialogic-logo.svg.import new file mode 100644 index 0000000..7003395 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/dialogic-logo.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bt87p6qlso0ya" +path="res://.godot/imported/dialogic-logo.svg-e43201cabc9573eeb3f78fd91ea9d909.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/dialogic-logo.svg" +dest_files=["res://.godot/imported/dialogic-logo.svg-e43201cabc9573eeb3f78fd91ea9d909.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/plugin-icon.svg b/godot/addons/dialogic/Editor/Images/plugin-icon.svg new file mode 100644 index 0000000..6f542b6 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/plugin-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/dialogic/Editor/Images/plugin-icon.svg.import b/godot/addons/dialogic/Editor/Images/plugin-icon.svg.import new file mode 100644 index 0000000..a6315d5 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/plugin-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dybg3l5pwetne" +path="res://.godot/imported/plugin-icon.svg-aa6701e8ed73f5fe5d177dfddce3a0e3.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/plugin-icon.svg" +dest_files=["res://.godot/imported/plugin-icon.svg-aa6701e8ed73f5fe5d177dfddce3a0e3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/preview_character.png b/godot/addons/dialogic/Editor/Images/preview_character.png new file mode 100644 index 0000000..0ef6e85 Binary files /dev/null and b/godot/addons/dialogic/Editor/Images/preview_character.png differ diff --git a/godot/addons/dialogic/Editor/Images/preview_character.png.import b/godot/addons/dialogic/Editor/Images/preview_character.png.import new file mode 100644 index 0000000..95b3c15 --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/preview_character.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://41634vnjwsfw" +path="res://.godot/imported/preview_character.png-54f0625ad8281c635fea35a4930d95b6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/preview_character.png" +dest_files=["res://.godot/imported/preview_character.png-54f0625ad8281c635fea35a4930d95b6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Images/preview_character_speaker.png b/godot/addons/dialogic/Editor/Images/preview_character_speaker.png new file mode 100644 index 0000000..1e15626 Binary files /dev/null and b/godot/addons/dialogic/Editor/Images/preview_character_speaker.png differ diff --git a/godot/addons/dialogic/Editor/Images/preview_character_speaker.png.import b/godot/addons/dialogic/Editor/Images/preview_character_speaker.png.import new file mode 100644 index 0000000..80a5a2f --- /dev/null +++ b/godot/addons/dialogic/Editor/Images/preview_character_speaker.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dp7np6b4ipm0a" +path="res://.godot/imported/preview_character_speaker.png-c0667c648e2901adcbe8bf93ddda7f06.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Editor/Images/preview_character_speaker.png" +dest_files=["res://.godot/imported/preview_character_speaker.png-c0667c648e2901adcbe8bf93ddda7f06.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Editor/Inspector/inspector_plugin.gd b/godot/addons/dialogic/Editor/Inspector/inspector_plugin.gd new file mode 100644 index 0000000..0a40bc3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Inspector/inspector_plugin.gd.uid b/godot/addons/dialogic/Editor/Inspector/inspector_plugin.gd.uid new file mode 100644 index 0000000..b44f0e5 --- /dev/null +++ b/godot/addons/dialogic/Editor/Inspector/inspector_plugin.gd.uid @@ -0,0 +1 @@ +uid://bok1je25mskp7 diff --git a/godot/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd b/godot/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd new file mode 100644 index 0000000..78274d1 --- /dev/null +++ b/godot/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.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 = load("res://addons/dialogic/Editor/Images/Resources/timeline.svg") + 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(current_value.get_identifier()) + 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/godot/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd.uid b/godot/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd.uid new file mode 100644 index 0000000..47a91e0 --- /dev/null +++ b/godot/addons/dialogic/Editor/Inspector/timeline_inspector_field.gd.uid @@ -0,0 +1 @@ +uid://58xvx63rw20a diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/csv_file.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/csv_file.gd new file mode 100644 index 0000000..3f8fb3a --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/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/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/csv_file.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/csv_file.gd.uid new file mode 100644 index 0000000..6b17b20 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/csv_file.gd.uid @@ -0,0 +1 @@ +uid://ddof34f216ceq diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd new file mode 100644 index 0000000..5b85512 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd @@ -0,0 +1,154 @@ +@tool +extends DialogicSettingsPage + +## Settings tab that holds dialogic editor settings. + +const _SETTING_IMAGE_PREVIEW_HEIGHT = "image_preview_height" +const _SETTING_EVENT_BLOCK_MARGIN = "event_block_margin" +const _SETTING_SHOW_EVENT_NAMES = "show_event_names" + +const _SETTING_EVENT_COLOR_PALETTE = "color_palette" +const _SETTING_EVENT_SECTION_ODER = "event_section_order" + +var do_timeline_editor_refresh_on_close := false + +func _get_title() -> String: + return "Editor" + + +func _get_priority() -> int: + return 98 + + +func _refresh() -> void: + do_timeline_editor_refresh_on_close = false + %ImagePreviewHeight.value = DialogicUtil.get_editor_setting(_SETTING_IMAGE_PREVIEW_HEIGHT, 100) + %EventBlockMargin.value = DialogicUtil.get_editor_setting(_SETTING_EVENT_BLOCK_MARGIN, 0) + %ShowEventNames.set_pressed_no_signal(DialogicUtil.get_editor_setting(_SETTING_SHOW_EVENT_NAMES, false)) + + update_color_palette() + reload_section_list() + + +func _ready() -> void: + %ResetColorsButton.icon = get_theme_icon("Reload", "EditorIcons") + %ResetColorsButton.pressed.connect(_on_reset_colors_button) + + %ImagePreviewHeight.value_changed.connect(_on_ImagePreviewHeight_value_changed) + %EventBlockMargin.value_changed.connect(_on_EventBlockMargin_value_changed) + %ShowEventNames.toggled.connect(_on_ShowEventNames_toggled) + + +func _about_to_close(): + if do_timeline_editor_refresh_on_close: + refresh_visual_timeline_editor() + + +func refresh_visual_timeline_editor() -> void: + var timeline_node: DialogicEditor = settings_editor.editors_manager.editors["Timeline"]["node"] + timeline_node.get_node("%VisualEditor").load_event_buttons() + + # If the visual editor is open, close and reopen the timeline to have the colors reloaded. + if timeline_node.get_node("%VisualEditor").visible: + + var current_timeline := timeline_node.current_resource + settings_editor.editors_manager.clear_editor(timeline_node) + + settings_editor.editors_manager.edit_resource(current_timeline, true, true) + + + +#region SECTION ORDER +################################################################################ + +func reload_section_list(): + %SectionList.clear() + %SectionList.create_item() + var cached_events := DialogicResourceUtil.get_event_cache() + var sections := [] + var section_order: Array = DialogicUtil.get_editor_setting(_SETTING_EVENT_SECTION_ODER, ['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(_SETTING_EVENT_SECTION_ODER, sections) + do_timeline_editor_refresh_on_close = true + +#endregion + + +#region COLOR PALETTE +################################################################################ + +## Completely reloads the color palette buttons +func update_color_palette() -> void: + 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.popup_closed.connect(_on_palette_color_popup_closed) + + +func _on_palette_color_popup_closed() -> void: + var new_palette := {} + for i in %Colors.get_children(): + new_palette["Color"+str(i.get_index()+1)] = i.color + DialogicUtil.set_editor_setting(_SETTING_EVENT_COLOR_PALETTE, new_palette) + + do_timeline_editor_refresh_on_close = true + + +func _on_reset_colors_button() -> void: + DialogicUtil.set_editor_setting(_SETTING_EVENT_COLOR_PALETTE, null) + update_color_palette() + + do_timeline_editor_refresh_on_close = true + +#endregion + + + + +func _on_ImagePreviewHeight_value_changed(value:float) -> void: + DialogicUtil.set_editor_setting(_SETTING_IMAGE_PREVIEW_HEIGHT, value) + + +func _on_EventBlockMargin_value_changed(value:float) -> void: + DialogicUtil.set_editor_setting(_SETTING_EVENT_BLOCK_MARGIN, value) + do_timeline_editor_refresh_on_close = true + + +func _on_ShowEventNames_toggled(toggled:bool) -> void: + DialogicUtil.set_editor_setting(_SETTING_SHOW_EVENT_NAMES, toggled) + do_timeline_editor_refresh_on_close = true diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd.uid new file mode 100644 index 0000000..e273fb3 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd.uid @@ -0,0 +1 @@ +uid://3akc4p71r5rn diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.tscn b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.tscn new file mode 100644 index 0000000..4587e3f --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.tscn @@ -0,0 +1,161 @@ +[gd_scene load_steps=5 format=3 uid="uid://dbdmosh6v536s"] + +[ext_resource type="Script" uid="uid://3akc4p71r5rn" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.gd" id="1_kdw7t"] +[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_ey6hj"] + +[sub_resource type="Image" id="Image_1n4qk"] +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_dfv70"] +image = SubResource("Image_1n4qk") + +[node name="EditorSettingsPage" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_kdw7t") + +[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_ey6hj")] +layout_mode = 2 +tooltip_text = "These colors are used for the events." +texture = null +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_dfv70") +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 +size_flags_horizontal = 3 + +[node name="TimelineTitle" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="SectionTimelineTitle" type="Label" parent="TimelineTitle"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Visual Events" + +[node name="HintTooltip" parent="TimelineTitle" instance=ExtResource("2_ey6hj")] +layout_mode = 2 +texture = null +hint_text = "These settings affect the visual timeline editor." + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer"] +layout_mode = 2 +text = "Image preview height" + +[node name="HintTooltip" parent="HBoxContainer" instance=ExtResource("2_ey6hj")] +layout_mode = 2 +texture = null +hint_text = "If set to 0, image previews will be disabled." + +[node name="ImagePreviewHeight" type="SpinBox" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +update_on_text_changed = true +suffix = "px" +select_all_on_focus = true + +[node name="HBoxContainer2" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer2"] +layout_mode = 2 +text = "Event block bottom margin" + +[node name="HintTooltip" parent="HBoxContainer2" instance=ExtResource("2_ey6hj")] +layout_mode = 2 +texture = null +hint_text = "This adds extra space at the bottom of event blocks." + +[node name="EventBlockMargin" type="SpinBox" parent="HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true +update_on_text_changed = true +suffix = "px" +select_all_on_focus = true + +[node name="HBoxContainer3" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer3"] +layout_mode = 2 +text = "Show event names" + +[node name="HintTooltip" parent="HBoxContainer3" instance=ExtResource("2_ey6hj")] +layout_mode = 2 +texture = null +hint_text = "Enabling this prepends the event name at the beginning of event blocks." + +[node name="ShowEventNames" type="CheckButton" parent="HBoxContainer3"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HSeparator5" type="HSeparator" parent="."] +layout_mode = 2 + +[node name="HBoxContainer4" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="SectionSections" type="Label" parent="HBoxContainer4"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Section Order" + +[node name="HintTooltip" parent="HBoxContainer4" instance=ExtResource("2_ey6hj")] +layout_mode = 2 +tooltip_text = "You can change the order of the event sections here. " +texture = null +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="button_clicked" from="SectionList" to="." method="_on_section_list_button_clicked"] diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd new file mode 100644 index 0000000..d44907d --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd @@ -0,0 +1,177 @@ +@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) + + + # 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')) + + + +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 != 2: # 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 force_event_button_list_reload() -> void: + find_parent('EditorsManager').editors['Timeline'].node.get_node('%VisualEditor').load_event_buttons() + + +func _on_reload_pressed() -> void: + DialogicUtil._update_autoload_subsystem_access() diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd.uid new file mode 100644 index 0000000..7acfdfc --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd.uid @@ -0,0 +1 @@ +uid://chb81lvjh47jr diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.tscn b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.tscn new file mode 100644 index 0000000..777d634 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.tscn @@ -0,0 +1,234 @@ +[gd_scene load_steps=6 format=3 uid="uid://b873ho41sklv8"] + +[ext_resource type="Script" uid="uid://chb81lvjh47jr" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_general.gd" id="2"] +[ext_resource type="Script" uid="uid://bo0dfmsyky1mm" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd" id="2_3xeuv"] +[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_kqhx5"] +[ext_resource type="Script" uid="uid://vg4wbm0n64ws" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd" id="3_dbfvv"] +[ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="3_i7rug"] + +[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="HBoxContainer2" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="SectionToolsTitle" type="Label" parent="HBoxContainer2"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Tools" + +[node name="Tools" type="Node" parent="."] +script = ExtResource("2_3xeuv") + +[node name="ResaveTool" type="Node" parent="Tools"] +script = ExtResource("3_dbfvv") + +[node name="ToolButtons" type="VBoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 2 + +[node name="ToolProgress" type="ProgressBar" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 2 +max_value = 1.0 +value = 1.0 + +[node name="HSeparator5" type="HSeparator" parent="."] +layout_mode = 2 + +[node name="HBoxContainer5" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="SectionBehaviourTitle" type="Label" parent="HBoxContainer5"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Layout Node Behaviour" + +[node name="HintTooltip" parent="HBoxContainer5" 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 = null +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 +selected = 0 +fit_to_longest_item = false +item_count = 3 +popup/item_0/text = "Delete Layout Node" +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 = null +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 + +[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 = null +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 +selected = 0 +item_count = 4 +popup/item_0/text = "Event only" +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 = null +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 + +[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"] diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd new file mode 100644 index 0000000..a9f159d --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd @@ -0,0 +1,442 @@ +@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/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd.uid new file mode 100644 index 0000000..1ad4804 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd.uid @@ -0,0 +1 @@ +uid://bcu347pvraog6 diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.tscn b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.tscn new file mode 100644 index 0000000..db242b8 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.tscn @@ -0,0 +1,236 @@ +[gd_scene load_steps=7 format=3 uid="uid://o7ljiritpgap"] + +[ext_resource type="Script" uid="uid://bcu347pvraog6" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_modules.gd" id="1_l2hk0"] + +[sub_resource type="Image" id="Image_1yyxk"] +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_1yyxk") + +[sub_resource type="Image" id="Image_bclq7"] +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_bclq7") + +[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/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd new file mode 100644 index 0000000..6b84a79 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd @@ -0,0 +1,80 @@ +@tool +extends Node + +var tool_thread : Thread +var tool_progress := 1.0 +var tool_progress_mutex : Mutex +signal tool_finished_signal + + +func _ready() -> void: + for button in %ToolButtons.get_children(): + button.queue_free() + + for i in get_children(): + var button := Button.new() + button.text = i.button_text + button.tooltip_text = i.tooltip + button.pressed.connect(execute_tool.bind(i.method)) + %ToolButtons.add_child(button) + + +func execute_tool(method:Callable) -> void: + for button in %ToolButtons.get_children(): + button.disabled = true + + var prev_timeline := close_active_timeline() + await get_tree().process_frame + if tool_thread and tool_thread.is_alive(): + tool_thread.wait_to_finish() + + tool_thread = Thread.new() + tool_progress_mutex = Mutex.new() + tool_thread.start(method) + + await tool_finished_signal + silently_open_timeline(prev_timeline) + for button in %ToolButtons.get_children(): + button.disabled = false + + +func _process(_delta: float) -> void: + if (tool_thread and tool_thread.is_alive()) or %ToolProgress.value < 1: + if tool_progress_mutex: tool_progress_mutex.lock() + %ToolProgress.value = tool_progress + if tool_progress_mutex: tool_progress_mutex.unlock() + %ToolProgress.show() + if %ToolProgress.value == 1: + tool_finished_signal.emit() + %ToolProgress.hide() + + +func _exit_tree() -> void: + if tool_thread: + tool_thread.wait_to_finish() + + + +#region HELPERS + +## 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 = get_parent().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. + get_parent().settings_editor.editors_manager.clear_editor(timeline_node, true) + + 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: + get_parent().settings_editor.editors_manager.edit_resource(timeline_to_open, true, true) + +#endregion diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd.uid new file mode 100644 index 0000000..f01f974 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_tools.gd.uid @@ -0,0 +1 @@ +uid://bo0dfmsyky1mm diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd new file mode 100644 index 0000000..d882ec7 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd @@ -0,0 +1,659 @@ +@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 + +## 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.suggestions_func = get_locales + %OrigLocale.resource_icon = get_theme_icon("Translation", "EditorIcons") + %OrigLocale.value_changed.connect(store_changes) + %TestingLocale.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/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd.uid new file mode 100644 index 0000000..76f5d8f --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.gd.uid @@ -0,0 +1 @@ +uid://b0bm772xo8n2j diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.tscn b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.tscn new file mode 100644 index 0000000..eadce4a --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.tscn @@ -0,0 +1,368 @@ +[gd_scene load_steps=7 format=3 uid="uid://chpb1mj03xjxv"] + +[ext_resource type="Script" uid="uid://b0bm772xo8n2j" path="res://addons/dialogic/Editor/Settings/CoreSettingsPages/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_4jaem"] +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_4jaem") + +[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/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd new file mode 100644 index 0000000..03200bb --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd @@ -0,0 +1,32 @@ +@tool +extends Node + +@onready var ToolUtil := get_parent() + +var button_text := "Resave all timelines" +var tooltip := "Opens and resaves all timelines. This can be useful if an update introduced a syntax change." +var method := resave_tool + + +func resave_tool() -> void: + ToolUtil.tool_progress_mutex.lock() + ToolUtil.tool_progress = 0 + ToolUtil.tool_progress_mutex.unlock() + + var index := 0 + var timelines := DialogicResourceUtil.get_timeline_directory() + for timeline_identifier in timelines: + var timeline := DialogicResourceUtil.get_timeline_resource(timeline_identifier) + await timeline.process() + timeline.set_meta("timeline_not_saved", true) + ResourceSaver.save(timeline) + + ToolUtil.tool_progress_mutex.lock() + ToolUtil.tool_progress = 1.0/len(timelines)*index + ToolUtil.tool_progress_mutex.unlock() + + index += 1 + + ToolUtil.tool_progress_mutex.lock() + ToolUtil.tool_progress = 1 + ToolUtil.tool_progress_mutex.unlock() diff --git a/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd.uid b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd.uid new file mode 100644 index 0000000..738db88 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/CoreSettingsPages/tool_resave.gd.uid @@ -0,0 +1 @@ +uid://vg4wbm0n64ws diff --git a/godot/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd b/godot/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd new file mode 100644 index 0000000..f5f11e3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd.uid b/godot/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd.uid new file mode 100644 index 0000000..37185c9 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/HintLabelStylingScript.gd.uid @@ -0,0 +1 @@ +uid://dhl3w5a4mujud diff --git a/godot/addons/dialogic/Editor/Settings/settings_editor.gd b/godot/addons/dialogic/Editor/Settings/settings_editor.gd new file mode 100644 index 0000000..0e01464 --- /dev/null +++ b/godot/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/CoreSettingsPages/settings_general.tscn") + register_settings_section("res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_editor.tscn") + register_settings_section("res://addons/dialogic/Editor/Settings/CoreSettingsPages/settings_translation.tscn") + register_settings_section("res://addons/dialogic/Editor/Settings/CoreSettingsPages/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/godot/addons/dialogic/Editor/Settings/settings_editor.gd.uid b/godot/addons/dialogic/Editor/Settings/settings_editor.gd.uid new file mode 100644 index 0000000..69a43aa --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/settings_editor.gd.uid @@ -0,0 +1 @@ +uid://c1virlyy8gl7 diff --git a/godot/addons/dialogic/Editor/Settings/settings_editor.tscn b/godot/addons/dialogic/Editor/Settings/settings_editor.tscn new file mode 100644 index 0000000..c7ed7a3 --- /dev/null +++ b/godot/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" uid="uid://c1virlyy8gl7" 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/godot/addons/dialogic/Editor/Settings/settings_page.gd b/godot/addons/dialogic/Editor/Settings/settings_page.gd new file mode 100644 index 0000000..06c1b78 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/settings_page.gd @@ -0,0 +1,36 @@ +@tool +extends Control +class_name DialogicSettingsPage + +@export_multiline var short_info := "" +@onready var settings_editor: Control = find_parent('Settings') + +## 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/godot/addons/dialogic/Editor/Settings/settings_page.gd.uid b/godot/addons/dialogic/Editor/Settings/settings_page.gd.uid new file mode 100644 index 0000000..cf365d6 --- /dev/null +++ b/godot/addons/dialogic/Editor/Settings/settings_page.gd.uid @@ -0,0 +1 @@ +uid://be8xcha2jwdur diff --git a/godot/addons/dialogic/Editor/Theme/MainTheme.tres b/godot/addons/dialogic/Editor/Theme/MainTheme.tres new file mode 100644 index 0000000..bd77e0f --- /dev/null +++ b/godot/addons/dialogic/Editor/Theme/MainTheme.tres @@ -0,0 +1,3 @@ +[gd_resource type="Theme" format=3 uid="uid://cqst728xxipcw"] + +[resource] diff --git a/godot/addons/dialogic/Editor/Theme/PickerTheme.tres b/godot/addons/dialogic/Editor/Theme/PickerTheme.tres new file mode 100644 index 0000000..36b1955 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd new file mode 100644 index 0000000..2817951 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd @@ -0,0 +1,333 @@ +@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(r"(?(\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 == '"': + suggest_shortcode_values(text, shortcode_events[code], line, word) + text.update_code_completion_options(true) + return + + # 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 not ' ' 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) + + # USEFUL FOR DEBUGGING + #print(text.get_code_completion_options().map(func(x):return "{display_text}".format(x))) + + + +# Helper that adds all characters as options +func suggest_characters(text:CodeEdit, type := CodeEdit.KIND_MEMBER, event:DialogicEvent=null) -> void: + for character in DialogicResourceUtil.get_character_directory(): + var result: String = character + if " " in character: + result = '"'+character+'"' + if event and event is DialogicTextEvent and load(DialogicResourceUtil.get_character_directory()[character]).portraits.is_empty(): + result += ': ' + elif event and event is DialogicCharacterEvent: + 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 not 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), '" ') + + +func suggest_shortcode_values(text:CodeEdit, event:DialogicEvent, line:String, word:String) -> void: + var current_parameter_gex := completion_shortcode_param_getter_regex.search(line) + if !current_parameter_gex: + return + + var current_parameter := current_parameter_gex.get_string('param') + if !event.get_shortcode_parameters().has(current_parameter): + return + if !event.get_shortcode_parameters()[current_parameter].has('suggestions'): + if typeof(event.get_shortcode_parameters()[current_parameter].default) == TYPE_BOOL: + suggest_bool(text, event.event_color.lerp(syntax_highlighter.normal_color, 0.3)) + elif len(word) > 0: + text.add_code_completion_option(CodeEdit.KIND_VARIABLE, word, word, event.event_color.lerp(syntax_highlighter.normal_color, 0.3), text.get_theme_icon("GuiScrollArrowRight", "EditorIcons"), '" ') + return + + var suggestions: Dictionary = event.get_shortcode_parameters()[current_parameter]['suggestions'].call() + suggest_custom_suggestions(suggestions, text, event.event_color.lerp(syntax_highlighter.normal_color, 0.3)) + + +## 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 and (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/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd.uid new file mode 100644 index 0000000..9fd0176 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd.uid @@ -0,0 +1 @@ +uid://camdhr6iwaywr diff --git a/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd new file mode 100644 index 0000000..2e9ee90 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd @@ -0,0 +1,212 @@ +@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(r"(? 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 + + +func dict_get_color_at_column(dict:Dictionary, column:int) -> Color: + var prev_idx := -1 + for i in dict: + if i > prev_idx and i <= column: + prev_idx = i + if prev_idx != -1: + return dict[prev_idx].color + return normal_color diff --git a/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd.uid new file mode 100644 index 0000000..158c9b9 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/syntax_highlighter.gd.uid @@ -0,0 +1 @@ +uid://bf2nivn8txcw5 diff --git a/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd new file mode 100644 index 0000000..a49ed73 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd @@ -0,0 +1,362 @@ +@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]+)') +var channel_regex := RegEx.create_from_string(r'audio +(?[\w-]{2,}|[\w]+)') + +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) + + get_menu().add_icon_item(get_theme_icon("PlayStart", "EditorIcons"), "Play from here", 42) + get_menu().id_pressed.connect(_on_context_menu_id_pressed) + + +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 _on_context_menu_id_pressed(id:int) -> void: + if id == 42: + play_from_here() + + +func play_from_here() -> void: + timeline_editor.play_timeline(timeline_editor.current_resource.get_index_from_text_line(text, get_caret_line())) + + +func _gui_input(event): + if not event is InputEventKey: return + if not event.is_pressed(): return + match event.as_text(): + "Ctrl+K", "Ctrl+Slash": + toggle_comment() + # TODO clean this up when dropping 4.2 support + "Alt+Up": + if has_method("move_lines_up"): + call("move_lines_up") + "Alt+Down": + if has_method("move_lines_down"): + call("move_lines_down") + + "Ctrl+Shift+D", "Ctrl+D": + duplicate_lines() + + "Ctrl+F6" when OS.get_name() != "macOS": # Play from here + play_from_here() + "Ctrl+Shift+B" when OS.get_name() == "macOS": # Play from here + play_from_here() + "Enter": + if get_code_completion_options(): + return + for caret in range(get_caret_count()): + var line := get_line(get_caret_line(caret)).strip_edges() + var event_res := DialogicTimeline.event_from_string(line, DialogicResourceUtil.get_event_cache()) + var indent_format: String = timeline_editor.current_resource.indent_format + if event_res.can_contain_events: + insert_text_at_caret("\n"+indent_format.repeat(get_indent_level(get_caret_line(caret))/4+1), caret) + else: + insert_text_at_caret("\n"+indent_format.repeat(get_indent_level(get_caret_line(caret))/4), caret) + _: + 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_line(), get_selection_column()), + # TODO When ditching godot 4.2, switch to this, the above methods have been deprecated in 4.3 + #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() + + +## 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] + var line := get_line(get_caret_line()) + if line[get_caret_column()-1] != '"': + result = '"'+result + if line.length() == get_caret_column() or line[get_caret_column()] != '"': + result = result+'"' + + insert_text_at_caret(result) + grab_focus() + + +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) + + var channels: PackedStringArray = [] + for i in channel_regex.search_all(text): + channels.append(i.get_string('channel')) + timeline_editor.update_audio_channel_cache(channels) + + +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, match_case := false, whole_words := false) -> bool: + var flags := 0 + if match_case: + flags = flags | SEARCH_MATCH_CASE + if whole_words: + flags = flags | SEARCH_WHOLE_WORDS + set_meta("current_search", search_text) + set_meta("current_search_flags", flags) + + set_search_text(search_text) + set_search_flags(flags) + queue_redraw() + + var result := search(search_text, flags, get_selection_from_line(), get_selection_from_column()) + if result.y != -1: + select.call_deferred(result.y, result.x, result.y, result.x + search_text.length()) + return result.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: + var pos := get_next_search_position(navigate_up) + if pos.x == -1: + return + 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() + + +func get_next_search_position(navigate_up := false) -> Vector2i: + if not has_meta("current_search"): + return Vector2i(-1, -1) + 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() + + var flags: int = get_meta("current_search_flags", 0) + if navigate_up: + flags = flags | SEARCH_BACKWARDS + + pos = search(get_meta("current_search"), flags, search_from_line, search_from_column) + return pos + + +func replace(replace_text:String) -> void: + if has_selection(): + set_caret_line(get_selection_from_line()) + set_caret_column(get_selection_from_column()) + + var pos := get_next_search_position() + if pos.x == -1: + return + + if not has_meta("current_search"): + return + + begin_complex_operation() + insert_text("@@", pos.y, pos.x) + if get_meta("current_search_flags") & SEARCH_MATCH_CASE: + text = text.replace("@@"+get_meta("current_search"), replace_text) + else: + text = text.replacen("@@"+get_meta("current_search"), replace_text) + end_complex_operation() + + set_caret_line(pos.y) + set_caret_column(pos.x) + + timeline_editor.replace_in_timeline() + + +func replace_all(replace_text:String) -> void: + begin_complex_operation() + var next_pos := get_next_search_position() + var counter := 0 + while next_pos.y != -1: + insert_text("@@", next_pos.y, next_pos.x) + if get_meta("current_search_flags") & SEARCH_MATCH_CASE: + text = text.replace("@@"+get_meta("current_search"), replace_text) + else: + text = text.replacen("@@"+get_meta("current_search"), replace_text) + next_pos = get_next_search_position() + set_caret_line(next_pos.y) + set_caret_column(next_pos.x) + end_complex_operation() + + timeline_editor.replace_in_timeline() + + +################################################################################ +## 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/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd.uid new file mode 100644 index 0000000..6daf3f5 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.gd.uid @@ -0,0 +1 @@ +uid://dshp0vy2xrxv diff --git a/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn b/godot/addons/dialogic/Editor/TimelineEditor/TextEditor/timeline_editor_text.tscn new file mode 100644 index 0000000..172a501 --- /dev/null +++ b/godot/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" uid="uid://dshp0vy2xrxv" 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 +wrap_mode = 1 +minimap_draw = true +caret_blink = true +highlight_current_line = true +draw_tabs = true +symbol_lookup_on_click = 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/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd new file mode 100644 index 0000000..7318bd4 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd.uid new file mode 100644 index 0000000..53cfcea --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.gd.uid @@ -0,0 +1 @@ +uid://dofrrscd4c61l diff --git a/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/AddEventButton.tscn new file mode 100644 index 0000000..0aff01e --- /dev/null +++ b/godot/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" uid="uid://dofrrscd4c61l" 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/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd new file mode 100644 index 0000000..d95062a --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd @@ -0,0 +1,204 @@ +@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: + set(value): + drag_to_position = value + drag_to_position_updated = true +var dragging := false +var drag_to_position_updated := 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 + drag_to_position_updated = false + + +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 drag_to_position_updated and 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/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd.uid new file mode 100644 index 0000000..d1141f6 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd.uid @@ -0,0 +1 @@ +uid://b6ka2avnh1u55 diff --git a/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd new file mode 100644 index 0000000..9c71fb0 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd @@ -0,0 +1,1438 @@ +@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 _cancel_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: + # In case another timeline is still loading + cancel_loading() + + 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 + set_meta("batch_count", len(_batches)) + batch_loaded.emit() + # Reset the scroll position + %TimelineArea.scroll_vertical = 0 + + +func is_loading_timeline() -> bool: + return _building_timeline + +func cancel_loading() -> void: + timeline_editor.set_progress(1) + if _building_timeline: + _cancel_loading = true + await batch_loaded + _cancel_loading = false + _building_timeline = false + + +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: + # Don't try to cast it to Array immedietly, as the item may have become null and will throw a useless error + var current_batch = _batches.pop_front() + if current_batch: + var current_batch_items: Array = current_batch + for i in current_batch_items: + 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 _cancel_loading: + return + + if _batches.size() > 0: + indent_events() + var progress: float = 1-(1.0/get_meta("batch_count")*len(_batches)) + timeline_editor.set_progress(progress) + await get_tree().process_frame + load_batch(_batches) + return + + # This hides the progress bar again + timeline_editor.set_progress(1) + + if opener_events_stack: + for ev in opener_events_stack: + if is_instance_valid(ev): + create_end_branch_event(%Timeline.get_child_count(), ev) + + timeline_loaded.emit() + + 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 channels: PackedStringArray = [] + 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) + + if 'event_name' in event.resource and event.resource is DialogicAudioEvent: + if not event.resource.channel_name in channels: + channels.append(event.resource.channel_name) + + timeline_editor.editors_manager.sidebar.update_content_list(labels) + timeline_editor.update_audio_channel_cache(channels) + + +#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 event.is_released() and not %TimelineArea.dragging and not Input.is_key_pressed(KEY_SHIFT): + if len(selected_items) > 1 and item in selected_items and not Input.is_key_pressed(KEY_CTRL): + deselect_all_items() + select_item(item) + + 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 event_resource.event_name == "Audio": + 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._store_as_string() + + # 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._store_as_string() + 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._load_from_string(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 + + sort_selection() + var event_copy_array := [] + for item in selected_items: + event_copy_array.append(item.resource._store_as_string()) + 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_id_pressed(id:int) -> void: + var item: Control = %EventPopupMenu.current_event + if id == 0: + if not item in selected_items: + selected_items = [item] + duplicate_selected() + + elif id == 1: + play_from_here(%EventPopupMenu.current_event.get_index()) + + elif id == 2: + if not item.resource.help_page_path.is_empty(): + OS.shell_open(item.resource.help_page_path) + + elif id == 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 id == 4 or id == 5: + if id == 4: + offset_blocks_by_index(selected_items, -1) + else: + offset_blocks_by_index(selected_items, +1) + + elif id == 6: + 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 play_from_here(index:=-1) -> void: + if index == -1: + if not selected_items.is_empty(): + index = selected_items[0].get_index() + timeline_editor.play_timeline(index) + +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() + + "Ctrl+F6" when OS.get_name() != "macOS": # Play from here + play_from_here() + "Ctrl+Shift+B" when OS.get_name() == "macOS": # Play from here + play_from_here() + + ## 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": + select_events_indexed(get_events_indexed(selected_items)) + copy_selected_events() + get_viewport().set_input_as_handled() + + "Ctrl+V": + var events_list := get_clipboard_data() + var paste_position := 0 + if selected_items: + paste_position = selected_items[-1].get_index()+1 + else: + paste_position = %Timeline.get_child_count() + 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, 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, match_case := false, whole_words := false) -> bool: + var flags := 0 + if match_case: + flags = flags | TextEdit.SEARCH_MATCH_CASE + if whole_words: + flags = flags | TextEdit.SEARCH_WHOLE_WORDS + + search_results.clear() + + # This checks all text events for whether they contain the text. + # If so, the text field is stored in search_results + # which is later used to navigate through only the relevant text fields. + + for block in %Timeline.get_children(): + if block.resource is DialogicTextEvent: + var text_field: TextEdit = block.get_field_node("text") + + text_field.deselect() + text_field.set_search_text(search_text) + text_field.set_search_flags(flags) + + if text_field.search(search_text, flags, 0, 0).x != -1: + search_results[block] = text_field + + text_field.queue_redraw() + + set_meta("current_search", search_text) + set_meta("current_search_flags", flags) + + 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 next_pos := get_next_search_position(navigate_up) + if next_pos: + var event: Node = next_pos[0] + var field: TextEdit = next_pos[1] + var result: Vector2i = next_pos[2] + if not event in selected_items: + select_item(next_pos[0], false) + %TimelineArea.ensure_control_visible(event) + event._on_ToggleBodyVisibility_toggled(true) + field.call_deferred("select", result.y, result.x, result.y, result.x+len(get_meta("current_search"))) + + +func get_next_search_position(navigate_up:= false, include_current := false) -> Array: + var search_text: String = get_meta("current_search", "") + var search_flags: int = get_meta("current_search_flags", 0) + + if search_results.is_empty() or %Timeline.get_child_count() == 0: + return [] + + # We start the search on the selected item, + # so these checks make sure something sensible is selected + + # Try to select the event that has focus + if get_viewport().gui_get_focus_owner() is TextEdit and get_viewport().gui_get_focus_owner() is DialogicVisualEditorField: + select_item(get_viewport().gui_get_focus_owner().event_resource.editor_node, false) + get_viewport().gui_get_focus_owner().deselect() + + # Select the first event if nothing is selected + if selected_items.is_empty(): + select_item(search_results.keys()[0], false) + + # Loop to the next event that where something was found + if not selected_items[0] in search_results: + var index: int = selected_items[0].get_index() + while not %Timeline.get_child(index) in search_results: + index = wrapi(index+1, 0, %Timeline.get_child_count()-1) + select_item(%Timeline.get_child(index), false) + + + var event: Node = selected_items[0] + var counter := 0 + var first := true + while true: + counter += 1 + var field: TextEdit = search_results[event] + field.queue_redraw() + + # First locates the next result in this field + var result := search_text_field(field, search_text, search_flags, navigate_up, first and include_current) + 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 + + first = false + + # Determines if the found result is valid or navigation should continue into the next event + 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 + elif include_current: + next_is_in_this_event = true + else: + next_is_in_this_event = result.x > current_column or result.y > current_line + + # If the next result was found return it + if next_is_in_this_event: + return [event, field, result] + + # Otherwise deselct this field and continue in the next/previous + 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 + return [] + + +func search_text_field(field:TextEdit, search_text := "", flags:= 0, navigate_up:= false, include_current := 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 + elif include_current: + search_from_line = field.get_selection_from_line() + search_from_column = field.get_selection_from_column() + 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 + + if navigate_up: + flags = flags | TextEdit.SEARCH_BACKWARDS + + var search := field.search(search_text, flags, search_from_line, search_from_column) + return search + + +func replace(replace_text:String) -> void: + var next_pos := get_next_search_position(false, true) + var event: Node = next_pos[0] + var field: TextEdit = next_pos[1] + var result: Vector2i = next_pos[2] + + if field.has_selection(): + field.set_caret_column(field.get_selection_from_column()) + field.set_caret_line(field.get_selection_from_line()) + + field.begin_complex_operation() + field.insert_text("@@", result.y, result.x) + if get_meta("current_search_flags") & TextEdit.SEARCH_MATCH_CASE: + field.text = field.text.replace("@@"+get_meta("current_search"), replace_text) + else: + field.text = field.text.replacen("@@"+get_meta("current_search"), replace_text) + field.end_complex_operation() + + timeline_editor.replace_in_timeline() + + +func replace_all(replace_text:String) -> void: + var next_pos := get_next_search_position() + if not next_pos: + return + var event: Node = next_pos[0] + var field: TextEdit = next_pos[1] + var result: Vector2i = next_pos[2] + field.begin_complex_operation() + while next_pos: + event = next_pos[0] + if field != next_pos[1]: + field.end_complex_operation() + field = next_pos[1] + field.begin_complex_operation() + result = next_pos[2] + + if field.has_selection(): + field.set_caret_column(field.get_selection_from_column()) + field.set_caret_line(field.get_selection_from_line()) + + field.insert_text("@@", result.y, result.x) + if get_meta("current_search_flags") & TextEdit.SEARCH_MATCH_CASE: + field.text = field.text.replace("@@"+get_meta("current_search"), replace_text) + else: + field.text = field.text.replacen("@@"+get_meta("current_search"), replace_text) + + next_pos = get_next_search_position() + field.end_complex_operation() + timeline_editor.replace_in_timeline() + +#endregion diff --git a/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd.uid new file mode 100644 index 0000000..2700172 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.gd.uid @@ -0,0 +1 @@ +uid://cvgok7bxva5e2 diff --git a/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn new file mode 100644 index 0000000..34f9b5c --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/VisualEditor/timeline_editor_visual.tscn @@ -0,0 +1,116 @@ +[gd_scene load_steps=10 format=3 uid="uid://ysqbusmy0qma"] + +[ext_resource type="Script" uid="uid://cvgok7bxva5e2" 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" uid="uid://b6ka2avnh1u55" path="res://addons/dialogic/Editor/TimelineEditor/VisualEditor/TimelineArea.gd" id="3_sap1x"] +[ext_resource type="Script" uid="uid://n1knm2ohcehu" 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_6gqu8"] +bg_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jujwh"] +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_3temo"] +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_xe7d2"] +image = SubResource("Image_3temo") + +[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_6gqu8") +theme_override_styles/hover = SubResource("StyleBoxFlat_jujwh") +item_count = 9 +item_0/text = "Duplicate" +item_0/icon = SubResource("ImageTexture_xe7d2") +item_1/id = -1 +item_1/separator = true +item_2/text = "Documentation" +item_2/icon = SubResource("ImageTexture_xe7d2") +item_2/id = 2 +item_3/text = "Open Code" +item_3/icon = SubResource("ImageTexture_xe7d2") +item_3/id = 3 +item_4/id = -1 +item_4/separator = true +item_5/text = "Move up" +item_5/icon = SubResource("ImageTexture_xe7d2") +item_5/id = 5 +item_6/text = "Move down" +item_6/icon = SubResource("ImageTexture_xe7d2") +item_6/id = 6 +item_7/id = -1 +item_7/separator = true +item_8/text = "Delete" +item_8/icon = SubResource("ImageTexture_xe7d2") +item_8/id = 8 +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="id_pressed" from="View/TimelineArea/EventPopupMenu" to="." method="_on_event_popup_menu_id_pressed"] +[connection signal="resized" from="View/RightSidebar" to="." method="_on_right_sidebar_resized"] diff --git a/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd b/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd new file mode 100644 index 0000000..5661d09 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd @@ -0,0 +1,118 @@ +@tool +extends PanelContainer + + +var shortcuts := [ + + {"shortcut":"Ctrl+T", "text":"Add Text event", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+Shift+T", "text":"Add Text event with current character", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+Alt/Opt+T", "text":"Add Text event with previous character", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+E", "text":"Add Character join event", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+Shift+E", "text":"Add Character update event", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+Alt/Opt+E", "text":"Add Character leave event", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+J", "text":"Add Jump event", "editor":"VisualEditor"}, + {"shortcut":"Ctrl+L", "text":"Add Label event", "editor":"VisualEditor"}, + {}, + {"shortcut":"Alt/Opt+Up", "text":"Move selected events/lines up"}, + {"shortcut":"Alt/Opt+Down", "text":"Move selected events/lines down"}, + {}, + {"shortcut":"Ctrl+F", "text":"Search"}, + {"shortcut":"Ctrl+R", "text":"Replace"}, + {}, + {"shortcut":"Ctrl+F5", "text":"Play timeline", "platform":"-macOS"}, + {"shortcut":"Ctrl+B", "text":"Play timeline", "platform":"macOS"}, + {"shortcut":"Ctrl+F6", "text":"Play timeline from here", "platform":"-macOS"}, + {"shortcut":"Ctrl+Shift+B", "text":"Play timeline from here", "platform":"macOS"}, + + {}, + {"shortcut":"Ctrl+C", "text":"Copy"}, + {"shortcut":"Ctrl+V", "text":"Paste"}, + {"shortcut":"Ctrl+D", "text":"Duplicate selected events/lines"}, + {"shortcut":"Ctrl+X", "text":"Cut selected events/lines"}, + {"shortcut":"Ctrl+K", "text":"Toggle Comment" , "editor":"TextEditor"}, + {"shortcut":"Delete", "text":"Delete events", "editor":"VisualEditor"}, + {}, + {"shortcut":"Ctrl+A", "text":"Select All"}, + {"shortcut":"Ctrl+Shift+A", "text":"Select Nothing", "editor":"VisualEditor"}, + {"shortcut":"Up", "text":"Select previous event", "editor":"VisualEditor"}, + {"shortcut":"Down", "text":"Select next event", "editor":"VisualEditor"}, + {}, + {"shortcut":"Ctrl+Z", "text":"Undo"}, + {"shortcut":"Ctrl+Shift+Z", "text":"Redo"}, + {}, +] + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + %CloseShortcutPanel.icon = get_theme_icon("Close", "EditorIcons") + get_theme_stylebox("panel").bg_color = get_theme_color("dark_color_3", "Editor") + + +func reload_shortcuts() -> void: + for i in %ShortcutList.get_children(): + i.queue_free() + + var is_text_editor: bool = %TextEditor.visible + for i in shortcuts: + if i.is_empty(): + %ShortcutList.add_child(HSeparator.new()) + %ShortcutList.add_child(HSeparator.new()) + continue + + if "editor" in i and not get_node("%"+i.editor).visible: + continue + + if "platform" in i: + var platform := OS.get_name() + if not (platform == i.platform.trim_prefix("-") != i.platform.begins_with("-")): + continue + + var hbox := HBoxContainer.new() + hbox.add_theme_constant_override("separation", 0) + for key_text in i.shortcut.split("+"): + if hbox.get_child_count(): + var plus_l := Label.new() + plus_l.text = "+" + hbox.add_child(plus_l) + + var key := Button.new() + if key_text == "Up": + key.icon = get_theme_icon("ArrowUp", "EditorIcons") + elif key_text == "Down": + key.icon = get_theme_icon("ArrowDown", "EditorIcons") + else: + key_text = key_text.replace("Alt/Opt", "Opt" if OS.get_name() == "macOS" else "Alt") + key.text = key_text + key.disabled = true + key.theme_type_variation = "ShortcutKeyLabel" + key.add_theme_font_override("font", get_theme_font("source", "EditorFonts")) + hbox.add_child(key) + + %ShortcutList.add_child(hbox) + + var text := Label.new() + text.text = i.text.replace("events/lines", "lines" if is_text_editor else "events") + text.theme_type_variation = "DialogicHintText2" + %ShortcutList.add_child(text) + + +func open(): + if visible: + close() + return + reload_shortcuts() + + show() + await get_tree().process_frame + size = get_parent().size - Vector2(100, 100)*DialogicUtil.get_editor_scale() + size.x = %ShortcutList.get_minimum_size().x + 100 + size.y = min(size.y, %ShortcutList.get_minimum_size().y+100) + global_position = get_parent().global_position+get_parent().size/2-size/2 + + +func _on_close_shortcut_panel_pressed() -> void: + close() + +func close() -> void: + hide() diff --git a/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd.uid new file mode 100644 index 0000000..1ab0805 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd.uid @@ -0,0 +1 @@ +uid://b35hvsvrvjjl4 diff --git a/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd b/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd new file mode 100644 index 0000000..2a907a1 --- /dev/null +++ b/godot/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", "") + var start_from_index: int = DialogicUtil.get_editor_setting("play_from_index", -1) + if not current_timeline: + get_tree().quit() + DialogicUtil.autoload().start(current_timeline, start_from_index) + 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:Variant) -> 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/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd.uid new file mode 100644 index 0000000..800625b --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd.uid @@ -0,0 +1 @@ +uid://cbq0n68r4pwu7 diff --git a/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn b/godot/addons/dialogic/Editor/TimelineEditor/test_timeline_scene.tscn new file mode 100644 index 0000000..ed877d5 --- /dev/null +++ b/godot/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" uid="uid://cbq0n68r4pwu7" 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/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd new file mode 100644 index 0000000..fec801e --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd @@ -0,0 +1,321 @@ +@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 = current_resource.get_identifier() + 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() + + if event.keycode == KEY_R and event.pressed: + if Input.is_key_pressed(KEY_CTRL): + if is_ancestor_of(get_viewport().gui_get_focus_owner()): + replace_in_timeline(true) + +## Method to play the current timeline. Connected to the button in the sidebar. +func play_timeline(index := -1) -> 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.set_editor_setting('play_from_index', index) + 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 + if %VisualEditor.is_loading_timeline(): + %VisualEditor.cancel_loading() + else: + %VisualEditor.save_timeline() + %VisualEditor.hide() + %TextEditor.show() + %TextEditor.load_timeline(current_resource) + %SwitchEditorMode.text = "Visual Editor" + _on_search_text_changed(%Search.text) + EditorMode.TEXT: + _on_search_text_changed.bind("") + current_editor_mode = EditorMode.VISUAL + %TextEditor.save_timeline() + %TextEditor.hide() + %VisualEditor.load_timeline(current_resource) + %VisualEditor.show() + %SwitchEditorMode.text = "Text Editor" + if not %VisualEditor.timeline_loaded.is_connected(_on_search_text_changed): + %VisualEditor.timeline_loaded.connect(_on_search_text_changed.bind(%Search.text), CONNECT_ONE_SHOT) + 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() + if not path.ends_with(".dtl"): + path = path.trim_suffix(".") + path += ".dtl" + new_timeline.resource_path = path + new_timeline.set_meta('timeline_not_saved', true) + var err := ResourceSaver.save(new_timeline) + EditorInterface.get_resource_filesystem().update_file(new_timeline.resource_path) + DialogicResourceUtil.update_directory('dtl') + editors_manager.edit_resource(new_timeline) + + +func update_audio_channel_cache(list:PackedStringArray) -> void: + var timeline_directory := DialogicResourceUtil.get_timeline_directory() + var channel_directory := DialogicResourceUtil.get_audio_channel_cache() + if current_resource != null: + for i in timeline_directory: + if timeline_directory[i] == current_resource.resource_path: + channel_directory[i] = list + + # also always store the current timelines channels for easy access + channel_directory[""] = list + + DialogicResourceUtil.set_audio_channel_cache(channel_directory) + + +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() + + %Shortcuts.icon = get_theme_icon("InputEventShortcut", "EditorIcons") + %Shortcuts.pressed.connect(%ShortcutsPanel.open) + + %SearchClose.icon = get_theme_icon("Close", "EditorIcons") + %SearchUp.icon = get_theme_icon("MoveUp", "EditorIcons") + %SearchDown.icon = get_theme_icon("MoveDown", "EditorIcons") + + %ReplaceGlobal.icon = get_theme_icon("ExternalLink", "EditorIcons") + + %ProgressSection.hide() + + %SearchReplaceSection.hide() + %SearchReplaceSection.add_theme_stylebox_override("panel", get_theme_stylebox("PanelForeground", "EditorStyles")) + + +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: + %SearchReplaceSection.show() + %SearchSection.show() + %ReplaceSection.hide() + if get_viewport().gui_get_focus_owner() is TextEdit: + if get_viewport().gui_get_focus_owner().get_selected_text(): + %Search.text = get_viewport().gui_get_focus_owner().get_selected_text() + _on_search_text_changed(%Search.text) + %Search.grab_focus() + + +func _on_close_search_pressed() -> void: + %SearchReplaceSection.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, %MatchCase.button_pressed, %WholeWords.button_pressed) + 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() + + +func _on_match_case_toggled(toggled_on: bool) -> void: + _on_search_text_changed(%Search.text) + + +func _on_whole_words_toggled(toggled_on: bool) -> void: + _on_search_text_changed(%Search.text) + + +#endregion + + +#region REPLACE + +func replace_in_timeline(focus_grab:=false) -> void: + search_timeline() + %ReplaceSection.show() + if focus_grab: + %ReplaceText.grab_focus() + %ReplaceText.select_all() + + +func _on_replace_button_pressed() -> void: + get_current_editor().replace(%ReplaceText.text) + + +func _on_replace_all_button_pressed() -> void: + get_current_editor().replace_all(%ReplaceText.text) + + +func _on_replace_global_pressed() -> void: + editors_manager.reference_manager.add_ref_change(%Search.text, %ReplaceText.text, 0, 0, [], + %WholeWords.button_pressed, %MatchCase.button_pressed) + editors_manager.reference_manager.open() + +#endregion + + +#region PROGRESS + +func set_progress(percentage:float, text := "") -> void: + %ProgressSection.visible = percentage != 1 + + %ProgressBar.value = percentage + %ProgressLabel.text = text + %ProgressLabel.visible = not text.is_empty() + +#endregion diff --git a/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd.uid b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd.uid new file mode 100644 index 0000000..a8c04d2 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.gd.uid @@ -0,0 +1 @@ +uid://x21vral0xsxy diff --git a/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn new file mode 100644 index 0000000..1816c53 --- /dev/null +++ b/godot/addons/dialogic/Editor/TimelineEditor/timeline_editor.tscn @@ -0,0 +1,315 @@ +[gd_scene load_steps=12 format=3 uid="uid://crce0na84rhfd"] + +[ext_resource type="Script" uid="uid://x21vral0xsxy" 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" uid="uid://b35hvsvrvjjl4" path="res://addons/dialogic/Editor/TimelineEditor/shortcut_popup.gd" id="6_rfuk0"] + +[sub_resource type="Image" id="Image_6bv6r"] +data = { +"data": PackedByteArray(255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 92, 92, 127, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 90, 90, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 42, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 93, 93, 41, 255, 90, 90, 0, 255, 94, 94, 0, 255, 91, 91, 42, 255, 93, 93, 233, 255, 92, 92, 232, 255, 92, 92, 0, 255, 92, 92, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 93, 93, 0, 255, 91, 91, 45, 255, 93, 93, 44, 255, 91, 91, 0, 255, 91, 91, 42, 255, 91, 91, 42, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 45, 255, 92, 92, 235, 255, 92, 92, 234, 255, 89, 89, 43, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 91, 91, 0, 255, 92, 92, 0, 255, 92, 92, 0, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 91, 91, 59, 255, 92, 92, 61, 255, 92, 92, 0, 255, 92, 92, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0, 255, 93, 93, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_u2118"] +image = SubResource("Image_6bv6r") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_boacm"] +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="StyleBoxFlat" id="StyleBoxFlat_4bvbc"] +content_margin_left = 0.0 +content_margin_top = 0.0 +content_margin_right = 0.0 +content_margin_bottom = 0.0 +bg_color = Color(0.458405, 0.458405, 0.458405, 1) +draw_center = false +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +border_color = Color(0.370364, 0.370365, 0.370364, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="Theme" id="Theme_feml8"] +HSeparator/constants/separation = 10 +ShortcutKeyLabel/base_type = &"Button" +ShortcutKeyLabel/colors/font_disabled_color = Color(1, 1, 1, 1) +ShortcutKeyLabel/colors/icon_disabled_color = Color(1, 1, 1, 1) +ShortcutKeyLabel/font_sizes/font_size = 14 +ShortcutKeyLabel/styles/disabled = SubResource("StyleBoxFlat_4bvbc") +ShortcutKeyLabel/styles/normal = SubResource("StyleBoxFlat_4bvbc") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j85ew"] +content_margin_left = 20.0 +content_margin_top = 20.0 +content_margin_right = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0, 0, 0, 1) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 +shadow_color = Color(0, 0, 0, 0.407843) +shadow_size = 15 + +[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 = null +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_u2118") + +[node name="Shortcuts" type="Button" parent="VBox/HBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 4 +tooltip_text = "Shortcuts" +icon = SubResource("ImageTexture_u2118") + +[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 + +[node name="SearchReplaceSection" type="PanelContainer" parent="VBox"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_boacm") + +[node name="VBox" type="VBoxContainer" parent="VBox/SearchReplaceSection"] +layout_mode = 2 + +[node name="SearchSection" type="HBoxContainer" parent="VBox/SearchReplaceSection/VBox"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Search" type="LineEdit" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Search" + +[node name="SearchLabel" type="Label" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="SearchUp" type="Button" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +icon = SubResource("ImageTexture_u2118") +flat = true + +[node name="SearchDown" type="Button" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +icon = SubResource("ImageTexture_u2118") +flat = true + +[node name="MatchCase" type="CheckBox" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +text = "Match Case" + +[node name="WholeWords" type="CheckBox" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +text = "Whole Words" + +[node name="SearchClose" type="Button" parent="VBox/SearchReplaceSection/VBox/SearchSection"] +unique_name_in_owner = true +layout_mode = 2 +icon = SubResource("ImageTexture_u2118") +flat = true + +[node name="ReplaceSection" type="HBoxContainer" parent="VBox/SearchReplaceSection/VBox"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="ReplaceText" type="LineEdit" parent="VBox/SearchReplaceSection/VBox/ReplaceSection"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Replace" + +[node name="ReplaceButton" type="Button" parent="VBox/SearchReplaceSection/VBox/ReplaceSection"] +unique_name_in_owner = true +layout_mode = 2 +text = "Replace" + +[node name="ReplaceAllButton" type="Button" parent="VBox/SearchReplaceSection/VBox/ReplaceSection"] +unique_name_in_owner = true +layout_mode = 2 +text = "Replace All" + +[node name="ReplaceGlobal" type="Button" parent="VBox/SearchReplaceSection/VBox/ReplaceSection"] +unique_name_in_owner = true +layout_mode = 2 +tooltip_text = "Replace in all timelines" +icon = SubResource("ImageTexture_u2118") + +[node name="ProgressSection" type="HBoxContainer" parent="VBox"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ProgressBar" type="ProgressBar" parent="VBox/ProgressSection"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +max_value = 1.0 +step = 0.001 + +[node name="ProgressLabel" type="Label" parent="VBox/ProgressSection"] +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_boacm") + +[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" + +[node name="ShortcutsPanel" type="PanelContainer" parent="."] +unique_name_in_owner = true +visible = false +layout_mode = 0 +offset_left = 51.0 +offset_top = 57.0 +offset_right = 571.0 +offset_bottom = 416.0 +theme = SubResource("Theme_feml8") +theme_override_styles/panel = SubResource("StyleBoxFlat_j85ew") +script = ExtResource("6_rfuk0") + +[node name="VBoxContainer" type="VBoxContainer" parent="ShortcutsPanel"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="ShortcutsPanel/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="ShortcutsPanel/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"DialogicSectionBig" +text = "Shortcuts " + +[node name="CloseShortcutPanel" type="Button" parent="ShortcutsPanel/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +icon = SubResource("ImageTexture_u2118") +flat = true + +[node name="ScrollContainer" type="ScrollContainer" parent="ShortcutsPanel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ShortcutList" type="GridContainer" parent="ShortcutsPanel/VBoxContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/h_separation = 11 +columns = 2 + +[connection signal="text_changed" from="VBox/SearchReplaceSection/VBox/SearchSection/Search" to="." method="_on_search_text_changed"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/SearchSection/SearchUp" to="." method="_on_search_up_pressed"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/SearchSection/SearchDown" to="." method="_on_search_down_pressed"] +[connection signal="toggled" from="VBox/SearchReplaceSection/VBox/SearchSection/MatchCase" to="." method="_on_match_case_toggled"] +[connection signal="toggled" from="VBox/SearchReplaceSection/VBox/SearchSection/WholeWords" to="." method="_on_whole_words_toggled"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/SearchSection/SearchClose" to="." method="_on_close_search_pressed"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/ReplaceSection/ReplaceButton" to="." method="_on_replace_button_pressed"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/ReplaceSection/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"] +[connection signal="pressed" from="VBox/SearchReplaceSection/VBox/ReplaceSection/ReplaceGlobal" to="." method="_on_replace_global_pressed"] +[connection signal="pressed" from="NoTimelineScreen/CenterContainer/VBoxContainer/CreateTimelineButton" to="." method="_on_create_timeline_button_pressed"] +[connection signal="pressed" from="ShortcutsPanel/VBoxContainer/HBoxContainer/CloseShortcutPanel" to="ShortcutsPanel" method="_on_close_shortcut_panel_pressed"] diff --git a/godot/addons/dialogic/Editor/dialogic_editor.gd b/godot/addons/dialogic/Editor/dialogic_editor.gd new file mode 100644 index 0000000..4dfbe2a --- /dev/null +++ b/godot/addons/dialogic/Editor/dialogic_editor.gd @@ -0,0 +1,67 @@ +@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() + +@warning_ignore("unused_signal")# This is emitted from the Editors Manager +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/godot/addons/dialogic/Editor/dialogic_editor.gd.uid b/godot/addons/dialogic/Editor/dialogic_editor.gd.uid new file mode 100644 index 0000000..c28b30b --- /dev/null +++ b/godot/addons/dialogic/Editor/dialogic_editor.gd.uid @@ -0,0 +1 @@ +uid://dorbh771j7fyf diff --git a/godot/addons/dialogic/Editor/editor_main.gd b/godot/addons/dialogic/Editor/editor_main.gd new file mode 100644 index 0000000..0e4a445 --- /dev/null +++ b/godot/addons/dialogic/Editor/editor_main.gd @@ -0,0 +1,310 @@ +@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, + "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) + + var new_theme := Theme.new() + + new_theme.set_type_variation("DialogicTitle", "Label") + new_theme.set_font("font", "DialogicTitle", get_theme_font("title", "EditorFonts")) + new_theme.set_color("font_color", "DialogicTitle", get_theme_color("warning_color", "Editor")) + new_theme.set_color( + "font_uneditable_color", "DialogicTitle", get_theme_color("warning_color", "Editor") + ) + new_theme.set_color( + "font_selected_color", "DialogicTitle", get_theme_color("warning_color", "Editor") + ) + new_theme.set_font_size( + "font_size", "DialogicTitle", get_theme_font_size("doc_size", "EditorFonts") + ) + + new_theme.set_type_variation("DialogicSubTitle", "Label") + new_theme.set_font("font", "DialogicSubTitle", get_theme_font("title", "EditorFonts")) + new_theme.set_font_size( + "font_size", "DialogicSubTitle", get_theme_font_size("doc_size", "EditorFonts") + ) + new_theme.set_color("font_color", "DialogicSubTitle", get_theme_color("accent_color", "Editor")) + + new_theme.set_type_variation("DialogicPanelA", "PanelContainer") + var panel_style := ( + DCSS + . inline( + { + "border-radius": 10, + "background": get_theme_color("base_color", "Editor"), + "padding": [5, 5], + } + ) + ) + new_theme.set_stylebox("panel", "DialogicPanelA", panel_style) + new_theme.set_stylebox("normal", "DialogicPanelA", panel_style) + + var dark_panel := panel_style.duplicate() + dark_panel.bg_color = get_theme_color("dark_color_3", "Editor") + new_theme.set_stylebox("panel", "DialogicPanelDarkA", dark_panel) + + var cornerless_panel := panel_style.duplicate() + cornerless_panel.corner_radius_top_left = 0 + new_theme.set_stylebox("panel", "DialogicPanelA_cornerless", cornerless_panel) + + # panel used for example for portrait previews in character editor + new_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.content_margin_left = 0 + side_panel.border_color = get_theme_color("contrast_color_2", "Editor") + new_theme.set_stylebox("panel", "DialogicPanelB", side_panel) + + new_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) + new_theme.set_stylebox("panel", "DialogicEventEdit", edit_panel) + new_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 + new_theme.set_stylebox("focus", "DialogicEventEdit", focus_edit) + + var hover_edit := edit_panel.duplicate() + hover_edit.border_color = get_theme_color("warning_color", "Editor") + + new_theme.set_stylebox("hover", "DialogicEventEdit", hover_edit) + var disabled_edit := edit_panel.duplicate() + disabled_edit.border_color = get_theme_color("property_color", "Editor") + new_theme.set_stylebox("disabled", "DialogicEventEdit", disabled_edit) + + new_theme.set_type_variation("DialogicHintText", "Label") + new_theme.set_color("font_color", "DialogicHintText", get_theme_color("readonly_color", "Editor")) + new_theme.set_font("font", "DialogicHintText", get_theme_font("doc_italic", "EditorFonts")) + + new_theme.set_type_variation("DialogicHintText2", "Label") + new_theme.set_color( + "font_color", "DialogicHintText2", get_theme_color("property_color_w", "Editor") + ) + new_theme.set_font("font", "DialogicHintText2", get_theme_font("doc_italic", "EditorFonts")) + + new_theme.set_type_variation("DialogicSection", "Label") + new_theme.set_font("font", "DialogicSection", get_theme_font("main_msdf", "EditorFonts")) + new_theme.set_color("font_color", "DialogicSection", get_theme_color("property_color_z", "Editor")) + new_theme.set_font_size( + "font_size", "DialogicSection", get_theme_font_size("doc_size", "EditorFonts") + ) + + new_theme.set_type_variation("DialogicSettingsSection", "DialogicSection") + new_theme.set_font("font", "DialogicSettingsSection", get_theme_font("main_msdf", "EditorFonts")) + new_theme.set_color( + "font_color", "DialogicSettingsSection", get_theme_color("property_color_z", "Editor") + ) + new_theme.set_font_size( + "font_size", "DialogicSettingsSection", get_theme_font_size("doc_size", "EditorFonts") + ) + + new_theme.set_type_variation("DialogicSectionBig", "DialogicSection") + new_theme.set_color("font_color", "DialogicSectionBig", get_theme_color("accent_color", "Editor")) + new_theme.set_font_size( + "font_size", "DialogicSectionBig", get_theme_font_size("doc_title_size", "EditorFonts") + ) + + new_theme.set_type_variation("DialogicLink", "LinkButton") + new_theme.set_color("font_hover_color", "DialogicLink", get_theme_color("warning_color", "Editor")) + + new_theme.set_type_variation("DialogicMegaSeparator", "HSeparator") + new_theme.set_stylebox("separator", "DialogicMegaSeparator", + DCSS.inline({ + "border-radius": 10, + "border": 0, + "background": get_theme_color("accent_color", "Editor"), + "padding": [5, 5], + }) + ) + new_theme.set_constant("separation", "DialogicMegaSeparator", 50) + + new_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 + new_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"}) + new_theme.set_type_variation("DialogicEventEditGroup", "PanelContainer") + new_theme.set_stylebox("panel", "DialogicEventEditGroup", event_field_group_panel) + + new_theme.set_icon('Plugin', 'Dialogic', load("res://addons/dialogic/Editor/Images/plugin-icon.svg")) + + theme = new_theme + + +## 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) + + 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) + + editor_file_dialog.file_mode = mode + editor_file_dialog.clear_filters() + 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() + editor_file_dialog.popup_centered_ratio(0.6) + + + return editor_file_dialog diff --git a/godot/addons/dialogic/Editor/editor_main.gd.uid b/godot/addons/dialogic/Editor/editor_main.gd.uid new file mode 100644 index 0000000..04b57ec --- /dev/null +++ b/godot/addons/dialogic/Editor/editor_main.gd.uid @@ -0,0 +1 @@ +uid://q24lthmavkvn diff --git a/godot/addons/dialogic/Editor/editor_main.tscn b/godot/addons/dialogic/Editor/editor_main.tscn new file mode 100644 index 0000000..6b8074c --- /dev/null +++ b/godot/addons/dialogic/Editor/editor_main.tscn @@ -0,0 +1,161 @@ +[gd_scene load_steps=18 format=3 uid="uid://de6yhw4r8jqb3"] + +[ext_resource type="Script" uid="uid://q24lthmavkvn" path="res://addons/dialogic/Editor/editor_main.gd" id="1_x88ov"] +[ext_resource type="Script" uid="uid://cefv8yoo8q7k6" 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" uid="uid://1m3sqaws1hin" 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" uid="uid://bxr2qomm7wm85" path="res://addons/dialogic/Editor/Common/reference_manager_window.gd" id="10_xbkrt"] +[ext_resource type="Script" uid="uid://camdhr6iwaywr" path="res://addons/dialogic/Editor/TimelineEditor/TextEditor/CodeCompletionHelper.gd" id="11_fyce4"] +[ext_resource type="Script" uid="uid://1tph6ios6ry2" 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_4esak"] +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_4esak") + +[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/godot/addons/dialogic/Editor/editors_manager.gd b/godot/addons/dialogic/Editor/editors_manager.gd new file mode 100644 index 0000000..029a919 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Editor/editors_manager.gd.uid b/godot/addons/dialogic/Editor/editors_manager.gd.uid new file mode 100644 index 0000000..4c21af9 --- /dev/null +++ b/godot/addons/dialogic/Editor/editors_manager.gd.uid @@ -0,0 +1 @@ +uid://cefv8yoo8q7k6 diff --git a/godot/addons/dialogic/Example Assets/Fonts/LICENSE.txt b/godot/addons/dialogic/Example Assets/Fonts/LICENSE.txt new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/Fonts/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..d998cf5 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf differ diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import new file mode 100644 index 0000000..0a0d10a --- /dev/null +++ b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://cc4xli25271fd" +path="res://.godot/imported/Roboto-Bold.ttf-a0c3395776dbc11ee676c5f1ea9c0579.fontdata" + +[deps] + +source_file="res://addons/dialogic/Example Assets/Fonts/Roboto-Bold.ttf" +dest_files=["res://.godot/imported/Roboto-Bold.ttf-a0c3395776dbc11ee676c5f1ea9c0579.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 +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=1 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..5b390ff Binary files /dev/null and b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf differ diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import new file mode 100644 index 0000000..df58996 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://b5c0p00x6g6u5" +path="res://.godot/imported/Roboto-Italic.ttf-844485a0171d6031f98f4829003a881a.fontdata" + +[deps] + +source_file="res://addons/dialogic/Example Assets/Fonts/Roboto-Italic.ttf" +dest_files=["res://.godot/imported/Roboto-Italic.ttf-844485a0171d6031f98f4829003a881a.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 +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=1 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..2b6392f Binary files /dev/null and b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf differ diff --git a/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import new file mode 100644 index 0000000..32bcf9f --- /dev/null +++ b/godot/addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://vrrmdx83skor" +path="res://.godot/imported/Roboto-Regular.ttf-d9ce0640effe9e93230b445b37d8e692.fontdata" + +[deps] + +source_file="res://addons/dialogic/Example Assets/Fonts/Roboto-Regular.ttf" +dest_files=["res://.godot/imported/Roboto-Regular.ttf-d9ce0640effe9e93230b445b37d8e692.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 +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=1 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/godot/addons/dialogic/Example Assets/already_read_indicator.gd b/godot/addons/dialogic/Example Assets/already_read_indicator.gd new file mode 100644 index 0000000..4dbf5d3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/already_read_indicator.gd.uid b/godot/addons/dialogic/Example Assets/already_read_indicator.gd.uid new file mode 100644 index 0000000..9909111 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/already_read_indicator.gd.uid @@ -0,0 +1 @@ +uid://qru8kj5n8hup diff --git a/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png b/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png new file mode 100644 index 0000000..c0ff161 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png differ diff --git a/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import b/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import new file mode 100644 index 0000000..993ad63 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/backgrounds/BubbleEnd.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3sccqj6l42w6" +path="res://.godot/imported/BubbleEnd.png-a2bd812e4aeb33a7c97291d41dcc1793.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/backgrounds/BubbleEnd.png" +dest_files=["res://.godot/imported/BubbleEnd.png-a2bd812e4aeb33a7c97291d41dcc1793.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/backgrounds/new-default-dialog.png.import b/godot/addons/dialogic/Example Assets/backgrounds/new-default-dialog.png.import new file mode 100644 index 0000000..7cbe695 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp b/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp new file mode 100644 index 0000000..e1d2a10 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp differ diff --git a/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import b/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import new file mode 100644 index 0000000..49d713f --- /dev/null +++ b/godot/addons/dialogic/Example Assets/backgrounds/rpg_box.webp.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dch8fuekijffp" +path="res://.godot/imported/rpg_box.webp-6ea0804b52e01599dbc94ffacc31d433.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/backgrounds/rpg_box.webp" +dest_files=["res://.godot/imported/rpg_box.webp-6ea0804b52e01599dbc94ffacc31d433.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd b/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd new file mode 100644 index 0000000..536d62a --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd @@ -0,0 +1,84 @@ +@tool +class_name DialogicRichTextTransitionEffect +extends RichTextEffect + +var visible_characters := -1 + +@export var bbcode := "animate_in" +var cache := [] + +@export_range(0.0, 5.0, 0.01) var time := 0.2 +@export_group("Color", "color") +@export var color_modulate: Gradient = null +@export var color_replace: Gradient = null +@export_group("Scale", "scale") +@export var scale_enabled := false +@export var scale_curve := Curve.new() +@export var scale_pivot := Vector2() +@export_group("Position", "position") +@export var position_enabled := false +@export var position_x_curve := Curve.new() +@export var position_y_curve := Curve.new() + +@export_group("Test", "test") +@export_range(-0.1, 1.0, 0.1) var test_value := -0.1 + +var was_skipped := false +var was_reset := false + + +func reset() -> void: + was_reset = true + was_skipped = false + cache.clear() + + +func skip() -> void: + was_skipped = true + + +func _process_custom_fx(char_fx: CharFXTransform) -> bool: + var char_age :float = 0.0 + if test_value >= 0: + char_age = test_value + + else: + if visible_characters == 0: + cache.clear() + return false + if was_reset: + if visible_characters != -1: + was_reset = false + else: + return false + + if len(cache) < visible_characters or visible_characters == -1 or was_skipped: + if char_fx.range.x >= len(cache): + cache.append(char_fx.elapsed_time) + + if was_skipped: + for i in range(len(cache)): + cache[i] = char_fx.elapsed_time-time + + if len(cache) > char_fx.range.x: + char_age = char_fx.elapsed_time - cache[char_fx.range.x] + + var text_server := TextServerManager.get_primary_interface() + var trans: float = clamp(char_age, 0.0, time)/time + + if color_replace: + var c := color_replace.sample(trans) + c.a = 1 + char_fx.color = char_fx.color.lerp(c, color_replace.sample(trans).a) + if color_modulate: + char_fx.color *= color_modulate.sample(trans) + if char_fx.font.is_valid(): + var glyph_size := text_server.font_get_glyph_size(char_fx.font, Vector2i(16,1), char_fx.glyph_index) + if scale_enabled: + char_fx.transform = char_fx.transform.translated_local(scale_pivot*glyph_size*Vector2(1, -1)*(1-scale_curve.sample(trans))) + char_fx.transform = char_fx.transform.scaled_local(Vector2.ONE*scale_curve.sample(trans)) + + if position_enabled: + char_fx.transform = char_fx.transform.translated_local(Vector2(position_x_curve.sample(trans), position_y_curve.sample(trans))*glyph_size) + + return true diff --git a/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd.uid b/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd.uid new file mode 100644 index 0000000..bcd0176 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd.uid @@ -0,0 +1 @@ +uid://wf7hpguw17ex diff --git a/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_in.tres b/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_in.tres new file mode 100644 index 0000000..ac15024 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_in.tres @@ -0,0 +1,17 @@ +[gd_resource type="RichTextEffect" script_class="DialogicRichTextTransitionEffect" load_steps=3 format=3 uid="uid://qegqrr4g2riu"] + +[ext_resource type="Script" uid="uid://wf7hpguw17ex" path="res://addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd" id="1_5w3vn"] + +[sub_resource type="Gradient" id="Gradient_5w3vn"] +colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1) + +[resource] +script = ExtResource("1_5w3vn") +bbcode = "fade_in" +time = 0.2 +color_modulate = SubResource("Gradient_5w3vn") +scale_enabled = false +scale_pivot = Vector2(0, 0) +position_enabled = false +test_value = -0.1 +metadata/_custom_type_script = "uid://wf7hpguw17ex" diff --git a/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_scale_in.tres b/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_scale_in.tres new file mode 100644 index 0000000..0c91435 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/fade_scale_in.tres @@ -0,0 +1,24 @@ +[gd_resource type="RichTextEffect" script_class="DialogicRichTextTransitionEffect" load_steps=4 format=3 uid="uid://hx8qyt5ry3h0"] + +[ext_resource type="Script" uid="uid://wf7hpguw17ex" path="res://addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd" id="1_rsak6"] + +[sub_resource type="Gradient" id="Gradient_rsak6"] +offsets = PackedFloat32Array(0, 0.6351706) +colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1) + +[sub_resource type="Curve" id="Curve_oitc5"] +_limits = [0.0, 2.0, 0.0, 1.0] +_data = [Vector2(0, 2), 0.0, 0.0, 0, 0, Vector2(0.40294844, 1.0066038), -0.13865282, 0.0, 0, 0] +point_count = 2 + +[resource] +script = ExtResource("1_rsak6") +bbcode = "fade_scale_in" +time = 0.30000000000000004 +color_modulate = SubResource("Gradient_rsak6") +scale_enabled = true +scale_curve = SubResource("Curve_oitc5") +scale_pivot = Vector2(0.5, 0.5) +position_enabled = false +test_value = -0.1 +metadata/_custom_type_script = "uid://wf7hpguw17ex" diff --git a/godot/addons/dialogic/Example Assets/bbcode_transitions/fancy_in.tres b/godot/addons/dialogic/Example Assets/bbcode_transitions/fancy_in.tres new file mode 100644 index 0000000..5f48cfc --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/fancy_in.tres @@ -0,0 +1,35 @@ +[gd_resource type="RichTextEffect" script_class="DialogicRichTextTransitionEffect" load_steps=6 format=3 uid="uid://c8b884puc720d"] + +[ext_resource type="Script" uid="uid://wf7hpguw17ex" path="res://addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd" id="1_n3lqs"] + +[sub_resource type="Gradient" id="Gradient_n3lqs"] +offsets = PackedFloat32Array(0, 0.45292622) +colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1) + +[sub_resource type="Gradient" id="Gradient_lhhwu"] +interpolation_mode = 1 +offsets = PackedFloat32Array(0, 0.6666667, 0.86513996) +colors = PackedColorArray(0.5208, 0.76631993, 0.93, 0, 0.5242275, 0.76595265, 0.93170327, 1, 0.5208, 0.76631993, 0.93, 0) + +[sub_resource type="Curve" id="Curve_lhhwu"] +_data = [Vector2(0.002457004, 1), 0.0, -1.993977, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 2 + +[sub_resource type="Curve" id="Curve_4i73d"] +_limits = [0.0, 1.5, 0.0, 1.0] +_data = [Vector2(0, 0.6627359), 0.0, 1.7969435, 0, 0, Vector2(0.4987715, 1.0308962), -1.6188686, -1.6188686, 0, 0, Vector2(0.66093373, 1), -0.34929827, 0.0, 0, 0] +point_count = 3 + +[resource] +script = ExtResource("1_n3lqs") +bbcode = "fancy_in" +time = 0.4 +color_modulate = SubResource("Gradient_n3lqs") +color_replace = SubResource("Gradient_lhhwu") +scale_enabled = true +scale_curve = SubResource("Curve_4i73d") +scale_pivot = Vector2(0.5, 0) +position_enabled = true +position_x_curve = SubResource("Curve_lhhwu") +test_value = -0.1 +metadata/_custom_type_script = "uid://wf7hpguw17ex" diff --git a/godot/addons/dialogic/Example Assets/bbcode_transitions/shaky_in.tres b/godot/addons/dialogic/Example Assets/bbcode_transitions/shaky_in.tres new file mode 100644 index 0000000..f781b31 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/bbcode_transitions/shaky_in.tres @@ -0,0 +1,35 @@ +[gd_resource type="RichTextEffect" script_class="DialogicRichTextTransitionEffect" load_steps=6 format=3 uid="uid://dnxkgwncm1pt5"] + +[ext_resource type="Script" uid="uid://wf7hpguw17ex" path="res://addons/dialogic/Example Assets/bbcode_transitions/bbcode_transition_effect.gd" id="1_ur6c5"] + +[sub_resource type="Gradient" id="Gradient_ur6c5"] +offsets = PackedFloat32Array(0, 0.5089058) +colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 1) + +[sub_resource type="Curve" id="Curve_5qe3f"] +_limits = [-0.5, 0.51556605, 0.0, 1.0] +_data = [Vector2(0, 0.0235914), 0.0, 0.0, 0, 0, Vector2(0.019656021, -0.2441923), 0.0, 0.0, 0, 0, Vector2(0.046683047, 0.17305207), 0.0, 0.0, 0, 0, Vector2(0.1081081, -0.23173726), 0.0, 0.0, 0, 0, Vector2(0.16216215, 0.12323183), 0.0, 0.0, 0, 0, Vector2(0.2997543, -0.16946197), 0.0, 0.0, 0, 0, Vector2(0.38329238, 0.042274), 0.0, 0.0, 0, 0, Vector2(0.46928746, -0.107186675), 0.0, 0.0, 0, 0, Vector2(0.5135135, 0.054729044), 0.0, 0.0, 0, 0, Vector2(0.66339064, -0.07604903), 0.0, 0.0, 0, 0, Vector2(0.86240786, 0.054729044), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 12 + +[sub_resource type="Curve" id="Curve_ur6c5"] +_limits = [-0.5, 0.51556605, 0.0, 1.0] +_data = [Vector2(0, 0.042274), 0.0, 0.0, 0, 0, Vector2(0.051597048, -0.13209677), 0.0, 0.0, 0, 0, Vector2(0.09336609, 0.21041724), 0.0, 0.0, 0, 0, Vector2(0.14742014, -0.25664735), 0.0, 0.0, 0, 0, Vector2(0.22850122, 0.098321736), 0.0, 0.0, 0, 0, Vector2(0.31203932, -0.107186675), 0.0, 0.0, 0, 0, Vector2(0.44717443, 0.054729044), 0.0, 0.0, 0, 0, Vector2(0.5995086, -0.08227658), 0.0, 0.0, 0, 0, Vector2(0.8132678, 0.042274), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0] +point_count = 10 + +[sub_resource type="Curve" id="Curve_qelc7"] +_data = [Vector2(0, 0.57688683), 0.0, 0.0, 0, 0, Vector2(0.14250615, 0.77311325), 0.0, 0.0, 0, 0, Vector2(0.2850123, 0.6443397), 0.0, 0.0, 0, 0, Vector2(0.41769046, 0.7976416), 0.0, 0.0, 0, 0, Vector2(0.5503686, 0.74245286), 0.0, 0.0, 0, 0, Vector2(0.6781328, 0.8712265), 0.0, 0.0, 0, 0, Vector2(0.79606885, 0.8528303), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] +point_count = 8 + +[resource] +script = ExtResource("1_ur6c5") +bbcode = "shaky_in" +time = 0.4 +color_modulate = SubResource("Gradient_ur6c5") +scale_enabled = true +scale_curve = SubResource("Curve_qelc7") +scale_pivot = Vector2(0.5, 0.5) +position_enabled = true +position_x_curve = SubResource("Curve_5qe3f") +position_y_curve = SubResource("Curve_ur6c5") +test_value = -0.1 +metadata/_custom_type_script = "uid://wf7hpguw17ex" diff --git a/godot/addons/dialogic/Example Assets/default_event.gd b/godot/addons/dialogic/Example Assets/default_event.gd new file mode 100644 index 0000000..6ffbc71 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/default_event.gd.uid b/godot/addons/dialogic/Example Assets/default_event.gd.uid new file mode 100644 index 0000000..46e302f --- /dev/null +++ b/godot/addons/dialogic/Example Assets/default_event.gd.uid @@ -0,0 +1 @@ +uid://dgs7be3hh1crk diff --git a/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png new file mode 100644 index 0000000..f26ed34 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png differ diff --git a/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import new file mode 100644 index 0000000..623e114 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bgn2ci6nu85t5" +path="res://.godot/imported/next-indicator-dialogic-1.png-694f122eff55e969b54cc43e62eb4758.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/next-indicator/next-indicator-dialogic-1.png" +dest_files=["res://.godot/imported/next-indicator-dialogic-1.png-694f122eff55e969b54cc43e62eb4758.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png new file mode 100644 index 0000000..896d3cf Binary files /dev/null and b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png differ diff --git a/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import new file mode 100644 index 0000000..626f916 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/next-indicator/next-indicator.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://u0o8qgccs5df" +path="res://.godot/imported/next-indicator.png-e3b7b80d9da791a1d0a061a728b6f781.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/next-indicator/next-indicator.png" +dest_files=["res://.godot/imported/next-indicator.png-e3b7b80d9da791a1d0a061a728b6f781.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png new file mode 100644 index 0000000..167c215 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import new file mode 100644 index 0000000..4ea02cc --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s2jsr1aqiu84" +path="res://.godot/imported/pl5 blink.png-dd40283850366d49ae61df7b137ffd77.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 blink.png" +dest_files=["res://.godot/imported/pl5 blink.png-dd40283850366d49ae61df7b137ffd77.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png new file mode 100644 index 0000000..75d7519 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import new file mode 100644 index 0000000..0200ee0 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bl38l2bv5ny4h" +path="res://.godot/imported/pl5 doubt.png-c657bfaf88fd5c06956ec703146704c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 doubt.png" +dest_files=["res://.godot/imported/pl5 doubt.png-c657bfaf88fd5c06956ec703146704c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png new file mode 100644 index 0000000..a4eb434 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import new file mode 100644 index 0000000..feaa43a --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ch4hvrgmq3j7t" +path="res://.godot/imported/pl5 hate.png-004951da12b71d275d61f3fe7af6c760.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 hate.png" +dest_files=["res://.godot/imported/pl5 hate.png-004951da12b71d275d61f3fe7af6c760.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png new file mode 100644 index 0000000..4763b82 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import new file mode 100644 index 0000000..dff98a8 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1k1be4cqjj4t" +path="res://.godot/imported/pl5 plot.png-7c5bbb51327eb4b7b1b78f4597ed6c60.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 plot.png" +dest_files=["res://.godot/imported/pl5 plot.png-7c5bbb51327eb4b7b1b78f4597ed6c60.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png new file mode 100644 index 0000000..97845b2 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import new file mode 100644 index 0000000..42b345b --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x7yafneltdfy" +path="res://.godot/imported/pl5 sad.png-778e9490c4f77059d6c87f720b2bbff7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 sad.png" +dest_files=["res://.godot/imported/pl5 sad.png-778e9490c4f77059d6c87f720b2bbff7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png new file mode 100644 index 0000000..d54b591 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import new file mode 100644 index 0000000..3a987f9 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://675isgfym2tw" +path="res://.godot/imported/pl5 scoff.png-f0b3e5d0a8895f55d2377978a0992a32.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 scoff.png" +dest_files=["res://.godot/imported/pl5 scoff.png-f0b3e5d0a8895f55d2377978a0992a32.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png new file mode 100644 index 0000000..a131cfa Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import new file mode 100644 index 0000000..afa1345 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gaol7fi1ifkx" +path="res://.godot/imported/pl5 shy.png-db66f14e608e1c150163af82c8a9f341.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 shy.png" +dest_files=["res://.godot/imported/pl5 shy.png-db66f14e608e1c150163af82c8a9f341.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png new file mode 100644 index 0000000..c780781 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import new file mode 100644 index 0000000..765add5 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpvtdr1itkbd7" +path="res://.godot/imported/pl5 surprise.png-9f07d67f3c68589bb2cfec738d68b9a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5 surprise.png" +dest_files=["res://.godot/imported/pl5 surprise.png-9f07d67f3c68589bb2cfec738d68b9a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png new file mode 100644 index 0000000..f676f77 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import new file mode 100644 index 0000000..87c96a6 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Antonio/pl5.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfkpn7mrd786b" +path="res://.godot/imported/pl5.png-0e78d740b51df476d423c20a3850d39a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Antonio/pl5.png" +dest_files=["res://.godot/imported/pl5.png-0e78d740b51df476d423c20a3850d39a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd new file mode 100644 index 0000000..5595991 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd.uid b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd.uid new file mode 100644 index 0000000..5d3a158 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.gd.uid @@ -0,0 +1 @@ +uid://b0dpsqdeb27km diff --git a/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.tscn b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_AnimatedSprite.tscn new file mode 100644 index 0000000..7c0af5b --- /dev/null +++ b/godot/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" uid="uid://b0dpsqdeb27km" 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/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd new file mode 100644 index 0000000..7b1b74a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd.uid b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd.uid new file mode 100644 index 0000000..a847015 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.gd.uid @@ -0,0 +1 @@ +uid://dixl3myysljgs diff --git a/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.tscn b/godot/addons/dialogic/Example Assets/portraits/CustomPortrait_FaceAtlas.tscn new file mode 100644 index 0000000..3af48eb --- /dev/null +++ b/godot/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" uid="uid://dixl3myysljgs" 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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png new file mode 100644 index 0000000..258af79 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import new file mode 100644 index 0000000..ebc5542 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cb3tpn3u3wtis" +path="res://.godot/imported/pl3 avoid.png-f8f5fd2a91f270ef9417e3b4bda0f35d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 avoid.png" +dest_files=["res://.godot/imported/pl3 avoid.png-f8f5fd2a91f270ef9417e3b4bda0f35d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png new file mode 100644 index 0000000..0ccfc28 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import new file mode 100644 index 0000000..86e6bd9 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://khlo6wd16qcd" +path="res://.godot/imported/pl3 blink.png-bc002e72d459c371c5ebb5d3d237500e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 blink.png" +dest_files=["res://.godot/imported/pl3 blink.png-bc002e72d459c371c5ebb5d3d237500e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png new file mode 100644 index 0000000..a14954b Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import new file mode 100644 index 0000000..82e5a64 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmshygun2cd0j" +path="res://.godot/imported/pl3 concept.png-baa2419b24f73cd7e47554567e865964.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 concept.png" +dest_files=["res://.godot/imported/pl3 concept.png-baa2419b24f73cd7e47554567e865964.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png new file mode 100644 index 0000000..7463f0b Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import new file mode 100644 index 0000000..2c034fe --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://r23r6iywkw0n" +path="res://.godot/imported/pl3 confusion.png-447505e4db69107e418a56eb99214d80.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 confusion.png" +dest_files=["res://.godot/imported/pl3 confusion.png-447505e4db69107e418a56eb99214d80.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png new file mode 100644 index 0000000..d34166f Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import new file mode 100644 index 0000000..3a9c584 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0xc8omj2n6h2" +path="res://.godot/imported/pl3 doubt.png-ad639761c380e37b578d46414772df73.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 doubt.png" +dest_files=["res://.godot/imported/pl3 doubt.png-ad639761c380e37b578d46414772df73.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png new file mode 100644 index 0000000..221ab18 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import new file mode 100644 index 0000000..8226488 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgteot3g2xw78" +path="res://.godot/imported/pl3 happy.png-7a49313244ae7097b2150a258b29afbc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 happy.png" +dest_files=["res://.godot/imported/pl3 happy.png-7a49313244ae7097b2150a258b29afbc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png new file mode 100644 index 0000000..f11cbe4 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import new file mode 100644 index 0000000..86b99a1 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uqa1ygtex8sj" +path="res://.godot/imported/pl3 plot.png-92e4eb96f6aac50f2afc301dcb1954fb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 plot.png" +dest_files=["res://.godot/imported/pl3 plot.png-92e4eb96f6aac50f2afc301dcb1954fb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png new file mode 100644 index 0000000..d4cae6c Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import new file mode 100644 index 0000000..fbf041a --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://be07it6s721a0" +path="res://.godot/imported/pl3 sad.png-4ce7b7a2f701590bc444115bab6e0c4e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 sad.png" +dest_files=["res://.godot/imported/pl3 sad.png-4ce7b7a2f701590bc444115bab6e0c4e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png new file mode 100644 index 0000000..fa21ed2 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import new file mode 100644 index 0000000..05bedc2 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bx6037ennf2im" +path="res://.godot/imported/pl3 shy.png-7dc343f1ee98343c9fe2c9cf93a8d574.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 shy.png" +dest_files=["res://.godot/imported/pl3 shy.png-7dc343f1ee98343c9fe2c9cf93a8d574.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png new file mode 100644 index 0000000..ab66ecb Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import new file mode 100644 index 0000000..3020d9f --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8ctomgkvxnta" +path="res://.godot/imported/pl3 surprise.png-92cfd8f7846a35eec2d62e62d532d7e6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Jane/pl3 surprise.png" +dest_files=["res://.godot/imported/pl3 surprise.png-92cfd8f7846a35eec2d62e62d532d7e6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Portrait1.png b/godot/addons/dialogic/Example Assets/portraits/Portrait1.png new file mode 100644 index 0000000..4bac6af Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Portrait1.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Portrait1.png.import b/godot/addons/dialogic/Example Assets/portraits/Portrait1.png.import new file mode 100644 index 0000000..36ed802 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Portrait1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ndmjrpk41eo4" +path="res://.godot/imported/Portrait1.png-c609e542fb60d6627e07ca0a12ddd868.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Portrait1.png" +dest_files=["res://.godot/imported/Portrait1.png-c609e542fb60d6627e07ca0a12ddd868.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Portrait2.png b/godot/addons/dialogic/Example Assets/portraits/Portrait2.png new file mode 100644 index 0000000..9bc4398 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Portrait2.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Portrait2.png.import b/godot/addons/dialogic/Example Assets/portraits/Portrait2.png.import new file mode 100644 index 0000000..ed1bb17 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Portrait2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2u3n3yp222uh" +path="res://.godot/imported/Portrait2.png-c9d044982430f12029c2193cba14c11f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Portrait2.png" +dest_files=["res://.godot/imported/Portrait2.png-c9d044982430f12029c2193cba14c11f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png b/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png new file mode 100644 index 0000000..e63cfac Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png.import new file mode 100644 index 0000000..08035ff --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/anger.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dokv225cp85ja" +path="res://.godot/imported/anger.png-dbcae35ced97cd8763301ede55f7634a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/anger.png" +dest_files=["res://.godot/imported/anger.png-dbcae35ced97cd8763301ede55f7634a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png b/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png new file mode 100644 index 0000000..33b45f5 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png.import new file mode 100644 index 0000000..0a8acca --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/frown.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://5bruuhj5cqu4" +path="res://.godot/imported/frown.png-2ea012492bc6286b36736d621adfd96a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/frown.png" +dest_files=["res://.godot/imported/frown.png-2ea012492bc6286b36736d621adfd96a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png b/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png new file mode 100644 index 0000000..8425066 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png.import new file mode 100644 index 0000000..0af674c --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/joy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dg7c4umbfsyvs" +path="res://.godot/imported/joy.png-a06db3f0763984942582106f69acd2ac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/joy.png" +dest_files=["res://.godot/imported/joy.png-a06db3f0763984942582106f69acd2ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png b/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png new file mode 100644 index 0000000..cd1441a Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import new file mode 100644 index 0000000..6a26bbb --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/neutral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bu3631ymfqxi3" +path="res://.godot/imported/neutral.png-b67f36561d5798f6bdf0e487c71053f7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/neutral.png" +dest_files=["res://.godot/imported/neutral.png-b67f36561d5798f6bdf0e487c71053f7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png b/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png new file mode 100644 index 0000000..4b40887 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import new file mode 100644 index 0000000..becf819 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/princess_blank.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djqit26f4be4f" +path="res://.godot/imported/princess_blank.png-fb2f5b52f38dc68c3bb1b4bf7bd4d155.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/princess_blank.png" +dest_files=["res://.godot/imported/princess_blank.png-fb2f5b52f38dc68c3bb1b4bf7bd4d155.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png b/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png new file mode 100644 index 0000000..2e7f306 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png.import new file mode 100644 index 0000000..638febd --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/shock.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5aku2g01k6c6" +path="res://.godot/imported/shock.png-8c83d26226ef9a4e882afabd3875355f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/shock.png" +dest_files=["res://.godot/imported/shock.png-8c83d26226ef9a4e882afabd3875355f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png b/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png new file mode 100644 index 0000000..45247e4 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png.import b/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png.import new file mode 100644 index 0000000..999cd31 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/Princess/smile.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsid4ye0q74nl" +path="res://.godot/imported/smile.png-763f387a68c52e40326ccdf00129e290.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/Princess/smile.png" +dest_files=["res://.godot/imported/smile.png-763f387a68c52e40326ccdf00129e290.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png new file mode 100644 index 0000000..f77329f Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import new file mode 100644 index 0000000..09aa8cc --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1es1ixchfied" +path="res://.godot/imported/base1.png-d5d7d1c85b1cab665dc0b54194bbd33b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/rpg_portraits/base1.png" +dest_files=["res://.godot/imported/base1.png-d5d7d1c85b1cab665dc0b54194bbd33b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png new file mode 100644 index 0000000..015798f Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import new file mode 100644 index 0000000..72f8317 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0asqyjv6ea0h" +path="res://.godot/imported/base2.png-4cf3c53a4d499097fe6532e4b778d0b3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/rpg_portraits/base2.png" +dest_files=["res://.godot/imported/base2.png-4cf3c53a4d499097fe6532e4b778d0b3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png new file mode 100644 index 0000000..e7feaa9 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import new file mode 100644 index 0000000..5734fba --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vtajb1cdcso" +path="res://.godot/imported/base3.png-65e60d03716a9b546a46a38772fc2ace.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/rpg_portraits/base3.png" +dest_files=["res://.godot/imported/base3.png-65e60d03716a9b546a46a38772fc2ace.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png new file mode 100644 index 0000000..035d6b4 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png differ diff --git a/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import new file mode 100644 index 0000000..3076cf3 --- /dev/null +++ b/godot/addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcm3eo5syiln0" +path="res://.godot/imported/base4.png-ea3084656f4403d3ff87bdd890f73843.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Example Assets/portraits/rpg_portraits/base4.png" +dest_files=["res://.godot/imported/base4.png-ea3084656f4403d3ff87bdd890f73843.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Example Assets/sound-effects/LICENSE.txt b/godot/addons/dialogic/Example Assets/sound-effects/LICENSE.txt new file mode 100644 index 0000000..14b8ff5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Example Assets/sound-effects/typing1.wav b/godot/addons/dialogic/Example Assets/sound-effects/typing1.wav new file mode 100644 index 0000000..bcb9c87 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/sound-effects/typing1.wav differ diff --git a/godot/addons/dialogic/Example Assets/sound-effects/typing1.wav.import b/godot/addons/dialogic/Example Assets/sound-effects/typing1.wav.import new file mode 100644 index 0000000..df5f053 --- /dev/null +++ b/godot/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-b241c6aab4ce82bf04caf8687873cae0.sample" + +[deps] + +source_file="res://addons/dialogic/Example Assets/sound-effects/typing1.wav" +dest_files=["res://.godot/imported/typing1.wav-b241c6aab4ce82bf04caf8687873cae0.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/godot/addons/dialogic/Example Assets/sound-effects/typing2.wav b/godot/addons/dialogic/Example Assets/sound-effects/typing2.wav new file mode 100644 index 0000000..aff48fa Binary files /dev/null and b/godot/addons/dialogic/Example Assets/sound-effects/typing2.wav differ diff --git a/godot/addons/dialogic/Example Assets/sound-effects/typing2.wav.import b/godot/addons/dialogic/Example Assets/sound-effects/typing2.wav.import new file mode 100644 index 0000000..7286fad --- /dev/null +++ b/godot/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-64cf50045b34db7d5ef5da984070e0a7.sample" + +[deps] + +source_file="res://addons/dialogic/Example Assets/sound-effects/typing2.wav" +dest_files=["res://.godot/imported/typing2.wav-64cf50045b34db7d5ef5da984070e0a7.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/godot/addons/dialogic/Example Assets/sound-effects/typing3.wav b/godot/addons/dialogic/Example Assets/sound-effects/typing3.wav new file mode 100644 index 0000000..91f353b Binary files /dev/null and b/godot/addons/dialogic/Example Assets/sound-effects/typing3.wav differ diff --git a/godot/addons/dialogic/Example Assets/sound-effects/typing3.wav.import b/godot/addons/dialogic/Example Assets/sound-effects/typing3.wav.import new file mode 100644 index 0000000..de1c70d --- /dev/null +++ b/godot/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-083712282583242958aaa68128694f95.sample" + +[deps] + +source_file="res://addons/dialogic/Example Assets/sound-effects/typing3.wav" +dest_files=["res://.godot/imported/typing3.wav-083712282583242958aaa68128694f95.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/godot/addons/dialogic/Example Assets/sound-effects/typing4.wav b/godot/addons/dialogic/Example Assets/sound-effects/typing4.wav new file mode 100644 index 0000000..071ba81 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/sound-effects/typing4.wav differ diff --git a/godot/addons/dialogic/Example Assets/sound-effects/typing4.wav.import b/godot/addons/dialogic/Example Assets/sound-effects/typing4.wav.import new file mode 100644 index 0000000..23800d3 --- /dev/null +++ b/godot/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-4e7a00fb19b7dd0bdfd8e323401b6162.sample" + +[deps] + +source_file="res://addons/dialogic/Example Assets/sound-effects/typing4.wav" +dest_files=["res://.godot/imported/typing4.wav-4e7a00fb19b7dd0bdfd8e323401b6162.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/godot/addons/dialogic/Example Assets/sound-effects/typing5.wav b/godot/addons/dialogic/Example Assets/sound-effects/typing5.wav new file mode 100644 index 0000000..9143081 Binary files /dev/null and b/godot/addons/dialogic/Example Assets/sound-effects/typing5.wav differ diff --git a/godot/addons/dialogic/Example Assets/sound-effects/typing5.wav.import b/godot/addons/dialogic/Example Assets/sound-effects/typing5.wav.import new file mode 100644 index 0000000..4b17d98 --- /dev/null +++ b/godot/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-5315fa96c84bd3957d157e8c978c1f04.sample" + +[deps] + +source_file="res://addons/dialogic/Example Assets/sound-effects/typing5.wav" +dest_files=["res://.godot/imported/typing5.wav-5315fa96c84bd3957d157e8c978c1f04.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/godot/addons/dialogic/Modules/Audio/event_audio.gd b/godot/addons/dialogic/Modules/Audio/event_audio.gd new file mode 100644 index 0000000..cd4f8cc --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/event_audio.gd @@ -0,0 +1,397 @@ +@tool +## Event that can play audio on a channel. The channel can be prededinfed +## (with default settings defined in the settings) or created on the spot. +## If no channel is given will play as a One-Shot SFX. +class_name DialogicAudioEvent +extends DialogicEvent + +### Settings + +## The file to play. If empty, the previous audio will be faded out. +var file_path := "": + set(value): + if file_path != value: + file_path = value + ui_update_needed.emit() +## The channel name to use. If none given plays as a One-Shot SFX. +var channel_name := "": + set(value): + if channel_name != channel_name_regex.sub(value, '', true): + channel_name = channel_name_regex.sub(value, '', true) + var defaults: Dictionary = DialogicUtil.get_audio_channel_defaults().get(channel_name, {}) + if defaults: + fade_length = defaults.fade_length + volume = defaults.volume + audio_bus = defaults.audio_bus + loop = defaults.loop + ui_update_needed.emit() + +## The length of the fade. If 0 it's an instant change. +var fade_length: float = 0.0 +## The volume in decibel. +var volume: float = 0.0 +## The audio bus the audio will be played on. +var audio_bus := "" +## If true, the audio will loop, otherwise only play once. +var loop := true +## Sync starting time with different channel (if playing audio on that channel) +var sync_channel := "" + +## Helpers. Set automatically +var set_fade_length := false +var set_volume := false +var set_audio_bus := false +var set_loop := false +var set_sync_channel := false + +var regex := RegEx.create_from_string(r'(?:audio)\s*(?[\w-]{2,}|[\w]*)?\s*(")?(?(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(?:\s*\[(?.*)\])?') +var channel_name_regex := RegEx.create_from_string(r'(?^-$)|(?[^\w-]{1})') + +################################################################################ +## EXECUTE +################################################################################ + +func _execute() -> void: + var audio_settings_overrides := {} + if set_audio_bus: + audio_settings_overrides["audio_bus"] = audio_bus + if set_volume: + audio_settings_overrides["volume"] = volume + if set_fade_length: + audio_settings_overrides["fade_length"] = fade_length + if set_loop: + audio_settings_overrides["loop"] = loop + audio_settings_overrides["sync_channel"] = sync_channel + dialogic.Audio.update_audio(channel_name, file_path, audio_settings_overrides) + + finish() + +################################################################################ +## INITIALIZE +################################################################################ + +func _init() -> void: + event_name = "Audio" + set_default_color('Color7') + event_category = "Audio" + event_sorting_index = 2 + + +func _get_icon() -> Resource: + return load(this_folder.path_join('icon_music.png')) + +################################################################################ +## SAVING/LOADING +################################################################################ + +func to_text () -> String: + var result_string := "audio " + + if not channel_name.is_empty(): + result_string += channel_name + " " + else: + loop = false + + if not file_path.is_empty(): + result_string += "\"" + file_path + "\"" + else: + result_string += "-" + + var shortcode := store_to_shortcode_parameters() + if not shortcode.is_empty(): + result_string += " [" + shortcode + "]" + + return result_string + + +func from_text(string:String) -> void: + # Pre Alpha 17 Conversion + if string.begins_with('[music'): + _music_from_text(string) + return + elif string.begins_with('[sound'): + _sound_from_text(string) + return + + var result := regex.search(string) + + channel_name = result.get_string('channel') + + if result.get_string('file_path') == '-': + file_path = "" + else: + file_path = result.get_string('file_path') + + 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 + "path" : {"property": "file_path", "default": "", "custom_stored":true, "ext_file":true}, + "channel" : {"property": "channel_name", "default": "", "custom_stored":true}, + "fade" : {"property": "fade_length", "default": 0.0}, + "volume" : {"property": "volume", "default": 0.0}, + "bus" : {"property": "audio_bus", "default": "", + "suggestions": DialogicUtil.get_audio_bus_suggestions}, + "loop" : {"property": "loop", "default": true}, + "sync" : {"property": "sync_channel", "default": "", + "suggestions": get_sync_audio_channel_suggestions}, + } + + +## Returns a string with all the shortcode parameters. +func store_to_shortcode_parameters(params:Dictionary = {}) -> String: + if params.is_empty(): + params = get_shortcode_parameters() + var custom_defaults: Dictionary = DialogicUtil.get_custom_event_defaults(event_name) + var channel_defaults := DialogicUtil.get_audio_channel_defaults() + var result_string := "" + for parameter in params.keys(): + var parameter_info: Dictionary = params[parameter] + var value: Variant = get(parameter_info.property) + var default_value: Variant = custom_defaults.get(parameter_info.property, parameter_info.default) + + if parameter_info.get('custom_stored', false): + continue + + if "set_" + parameter_info.property in self and not get("set_" + parameter_info.property): + continue + + if channel_name in channel_defaults.keys(): + default_value = channel_defaults[channel_name].get(parameter_info.property, default_value) + + if typeof(value) == typeof(default_value) and value == default_value: + if not "set_" + parameter_info.property in self or not get("set_" + parameter_info.property): + continue + + result_string += " " + parameter + '="' + value_to_string(value, parameter_info.get("suggestions", Callable())) + '"' + + return result_string.strip_edges() + + +func is_valid_event(string:String) -> bool: + if string.begins_with("audio"): + return true + # Pre Alpha 17 Converter + if string.strip_edges().begins_with('[music '): + return true + if string.strip_edges().begins_with('[sound '): + return true + return false + + +#region PreAlpha17 Conversion + +func _music_from_text(string:String) -> void: + var data := parse_shortcode_parameters(string) + + if data.has('channel') and data['channel'].to_int() > 0: + channel_name = 'music' + str(data['channel'].to_int() + 1) + else: + channel_name = 'music' + + # Reapply original defaults as setting channel name may have overridden them + fade_length = 0.0 + volume = 0.0 + audio_bus = '' + loop = true + + # Apply any custom event defaults + for default_prop in DialogicUtil.get_custom_event_defaults('music'): + if default_prop in self: + set(default_prop, DialogicUtil.get_custom_event_defaults('music')[default_prop]) + + # Apply shortcodes that exist + if data.has('path'): + file_path = data['path'] + if data.has('fade'): + set_fade_length = true + fade_length = data['fade'].to_float() + if data.has('volume'): + set_volume = true + volume = data['volume'].to_float() + if data.has('bus'): + set_audio_bus = true + audio_bus = data['bus'] + if data.has('loop'): + set_loop = true + loop = str_to_var(data['loop']) + update_text_version() + + +func _sound_from_text(string:String) -> void: + var data := parse_shortcode_parameters(string) + + channel_name = '' + + # Reapply original defaults as setting channel name may have overridden them + fade_length = 0.0 + volume = 0.0 + audio_bus = '' + loop = false + + # Apply any custom event defaults + for default_prop in DialogicUtil.get_custom_event_defaults('sound'): + if default_prop in self: + set(default_prop, DialogicUtil.get_custom_event_defaults('sound')[default_prop]) + + # Apply shortcodes that exist + if data.has('path'): + file_path = data['path'] + if data.has('volume'): + set_volume = true + volume = data['volume'].to_float() + if data.has('bus'): + set_audio_bus = true + audio_bus = data['bus'] + if data.has('loop'): + set_loop = true + loop = str_to_var(data['loop']) + update_text_version() + + +#endregion + +################################################################################ +## 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' : "Nothing", + 'editor_icon' : ["AudioStreamMP3", "EditorIcons"]}) + add_header_edit('file_path', ValueType.AUDIO_PREVIEW) + + add_header_edit('channel_name', ValueType.DYNAMIC_OPTIONS, { + 'left_text' :"on", + "right_text" : "channel.", + 'placeholder' : '(One-Shot SFX)', + 'mode' : 3, + 'suggestions_func' : get_audio_channel_suggestions, + 'validation_func' : DialogicUtil.validate_audio_channel_name, + 'tooltip' : 'Use an existing channel or type the name for a new channel.', + }) + + add_header_button('', _open_audio_settings, 'Edit Audio Channels', + editor_node.get_theme_icon("ExternalLink", "EditorIcons")) + + add_body_edit("set_fade_length", ValueType.BOOL_BUTTON,{ + "editor_icon" : ["FadeCross", "EditorIcons"], + "tooltip" : "Overwrite Fade Length" + },"!channel_name.is_empty() and has_channel_defaults()") + add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Fade Time:'}, + '!channel_name.is_empty() and (not has_channel_defaults() or set_fade_length)') + + add_body_edit("set_volume", ValueType.BOOL_BUTTON,{ + "editor_icon" : ["AudioStreamPlayer", "EditorIcons"], + "tooltip" : "Overwrite Volume" + },"!file_path.is_empty() and has_channel_defaults()") + add_body_edit('volume', ValueType.NUMBER, {'left_text':'Volume:', 'mode':2}, + '!file_path.is_empty() and (not has_channel_defaults() or set_volume)') + add_body_edit("set_audio_bus", ValueType.BOOL_BUTTON,{ + "editor_icon" : ["AudioBusBypass", "EditorIcons"], + "tooltip" : "Overwrite Audio Bus" + },"!file_path.is_empty() and has_channel_defaults()") + add_body_edit('audio_bus', ValueType.DYNAMIC_OPTIONS, { + 'left_text':'Audio Bus:', + 'placeholder' : 'Master', + 'mode' : 2, + 'suggestions_func' : DialogicUtil.get_audio_bus_suggestions, + }, '!file_path.is_empty() and (not has_channel_defaults() or set_audio_bus)') + add_body_edit("set_loop", ValueType.BOOL_BUTTON,{ + "editor_icon" : ["Loop", "EditorIcons"], + "tooltip" : "Overwrite Loop" + },"!channel_name.is_empty() and !file_path.is_empty() and has_channel_defaults()") + add_body_edit('loop', ValueType.BOOL, {'left_text':'Loop:'}, + '!channel_name.is_empty() and !file_path.is_empty() and (not has_channel_defaults() or set_loop)') + add_body_line_break("!channel_name.is_empty() and !file_path.is_empty()") + add_body_edit("set_sync_channel", ValueType.BOOL_BUTTON,{ + "editor_icon" : ["TransitionSync", "EditorIcons"], + "tooltip" : "Enable Syncing" + },"!channel_name.is_empty() and !file_path.is_empty()") + + add_body_edit('sync_channel', ValueType.DYNAMIC_OPTIONS, { + 'left_text' :'Sync with:', + 'placeholder' : '(No Sync)', + 'mode' : 3, + 'suggestions_func' : get_sync_audio_channel_suggestions, + 'validation_func' : DialogicUtil.validate_audio_channel_name, + 'tooltip' : "Use an existing channel or type the name for a new channel. If channel doesn't exist, this setting will be ignored.", + }, '!channel_name.is_empty() and !file_path.is_empty() and set_sync_channel') + + +## Used by the button on the visual event +func _open_audio_settings() -> void: + var editor_manager := editor_node.find_parent('EditorsManager') + if editor_manager: + editor_manager.open_editor(editor_manager.editors['Settings']['node'], true, "Audio") + + +## Helper for the visibility conditions +func has_channel_defaults() -> bool: + var defaults := DialogicUtil.get_audio_channel_defaults() + return defaults.has(channel_name) + + +func get_audio_channel_suggestions(filter:String) -> Dictionary: + var suggestions := {} + suggestions["(One-Shot SFX)"] = { + "value":"", + "tooltip": "Used for one shot sounds effects. Plays each sound in its own AudioStreamPlayer.", + "editor_icon": ["GuiRadioUnchecked", "EditorIcons"] + } + # TODO use .merged after dropping 4.2 support + suggestions.merge(DialogicUtil.get_audio_channel_suggestions(filter)) + return suggestions + +func get_sync_audio_channel_suggestions(filter:="") -> Dictionary: + return DialogicUtil.get_audio_channel_suggestions(filter) + + + +####################### CODE COMPLETION ######################################## +################################################################################ + +func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, word:String, symbol:String) -> void: + var line_until: String = CodeCompletionHelper.get_line_untill_caret(line) + if symbol == ' ': + if line_until.count(' ') == 1: + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, "One-Shot SFX", ' ', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.6)) + for i in DialogicUtil.get_audio_channel_suggestions(""): + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i, event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.6), null, " ") + elif line_until.count(" ") == 2: + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, '"', '"', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.6)) + + if symbol == "[" or (symbol == " " and line.count("[")): + for i in ["fade", "volume", "bus", "loop", "sync"]: + if not i+"=" in line: + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'="', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.6)) + + if (symbol == '"' or symbol == "=") and line.count("["): + CodeCompletionHelper.suggest_shortcode_values(TextNode, self, line, word) + + +func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void: + TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'audio', 'audio ', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3)) + + +#################### SYNTAX HIGHLIGHTING ####################################### +################################################################################ + +func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary: + var result := regex.search(line) + + dict[result.get_start()] = {"color":event_color.lerp(Highlighter.normal_color, 0.3)} + dict[result.get_start("channel")] = {"color":event_color.lerp(Highlighter.normal_color, 0.8)} + dict[result.get_start("file_path")] = {"color":event_color.lerp(Highlighter.string_color, 0.8)} + if result.get_string("shortcode"): + dict[result.get_start("shortcode")-1] = {"color":Highlighter.normal_color} + dict = Highlighter.color_shortcode_content(dict, line, result.get_start("shortcode"), 0, event_color) + + return dict diff --git a/godot/addons/dialogic/Modules/Audio/event_audio.gd.uid b/godot/addons/dialogic/Modules/Audio/event_audio.gd.uid new file mode 100644 index 0000000..f22125e --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/event_audio.gd.uid @@ -0,0 +1 @@ +uid://8p4qchmcuj68 diff --git a/godot/addons/dialogic/Modules/Audio/icon_music.png b/godot/addons/dialogic/Modules/Audio/icon_music.png new file mode 100644 index 0000000..c94600f Binary files /dev/null and b/godot/addons/dialogic/Modules/Audio/icon_music.png differ diff --git a/godot/addons/dialogic/Modules/Audio/icon_music.png.import b/godot/addons/dialogic/Modules/Audio/icon_music.png.import new file mode 100644 index 0000000..13d5a06 --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/icon_music.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buvpjsvdt4evk" +path="res://.godot/imported/icon_music.png-ffc971ba1265164a55f745186974be5f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Audio/icon_music.png" +dest_files=["res://.godot/imported/icon_music.png-ffc971ba1265164a55f745186974be5f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Audio/icon_sound.png b/godot/addons/dialogic/Modules/Audio/icon_sound.png new file mode 100644 index 0000000..c117397 Binary files /dev/null and b/godot/addons/dialogic/Modules/Audio/icon_sound.png differ diff --git a/godot/addons/dialogic/Modules/Audio/icon_sound.png.import b/godot/addons/dialogic/Modules/Audio/icon_sound.png.import new file mode 100644 index 0000000..171732d --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/icon_sound.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3ookrkto0yh6" +path="res://.godot/imported/icon_sound.png-7a1a8a5533773d97969b6311b6a9133f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Audio/icon_sound.png" +dest_files=["res://.godot/imported/icon_sound.png-7a1a8a5533773d97969b6311b6a9133f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Audio/index.gd b/godot/addons/dialogic/Modules/Audio/index.gd new file mode 100644 index 0000000..61ca7fa --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/index.gd @@ -0,0 +1,14 @@ +@tool +extends DialogicIndexer + + +func _get_events() -> Array: + return [this_folder.path_join('event_audio.gd')] + + +func _get_subsystems() -> Array: + return [{'name':'Audio', 'script':this_folder.path_join('subsystem_audio.gd')}] + + +func _get_settings_pages() -> Array: + return [this_folder.path_join('settings_audio.tscn')] diff --git a/godot/addons/dialogic/Modules/Audio/index.gd.uid b/godot/addons/dialogic/Modules/Audio/index.gd.uid new file mode 100644 index 0000000..81c5d1f --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/index.gd.uid @@ -0,0 +1 @@ +uid://dk46l1toqeswc diff --git a/godot/addons/dialogic/Modules/Audio/settings_audio.gd b/godot/addons/dialogic/Modules/Audio/settings_audio.gd new file mode 100644 index 0000000..8dbfc72 --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/settings_audio.gd @@ -0,0 +1,241 @@ +@tool +extends DialogicSettingsPage + +## Settings page that contains settings for the audio subsystem + +const TYPE_SOUND_AUDIO_BUS := "dialogic/audio/type_sound_bus" +const CHANNEL_DEFAULTS := "dialogic/audio/channel_defaults" + +var channel_defaults := {} +var _revalidate_channel_names := false + + +func _ready() -> void: + %TypeSoundBus.item_selected.connect(_on_type_sound_bus_item_selected) + $Panel.add_theme_stylebox_override('panel', get_theme_stylebox("Background", "EditorStyles")) + + +func _refresh() -> void: + %TypeSoundBus.clear() + var idx := 0 + for i in range(AudioServer.bus_count): + %TypeSoundBus.add_item(AudioServer.get_bus_name(i)) + if AudioServer.get_bus_name(i) == ProjectSettings.get_setting(TYPE_SOUND_AUDIO_BUS, ""): + idx = i + %TypeSoundBus.select(idx) + + load_channel_defaults(DialogicUtil.get_audio_channel_defaults()) + + +func _about_to_close() -> void: + save_channel_defaults() + + +## TYPE SOUND AUDIO BUS +func _on_type_sound_bus_item_selected(index:int) -> void: + ProjectSettings.set_setting(TYPE_SOUND_AUDIO_BUS, %TypeSoundBus.get_item_text(index)) + ProjectSettings.save() + + +#region AUDIO CHANNELS +################################################################################ + +func load_channel_defaults(dictionary:Dictionary) -> void: + channel_defaults.clear() + for i in %AudioChannelDefaults.get_children(): + i.queue_free() + + var column_names := [ + "Channel Name", + "Volume", + "Audio Bus", + "Fade", + "Loop", + "" + ] + + for column in column_names: + var label := Label.new() + label.text = column + label.theme_type_variation = 'DialogicHintText2' + %AudioChannelDefaults.add_child(label) + + var channel_names := dictionary.keys() + channel_names.sort() + + for channel_name in channel_names: + add_channel_defaults( + channel_name, + dictionary[channel_name].volume, + dictionary[channel_name].audio_bus, + dictionary[channel_name].fade_length, + dictionary[channel_name].loop) + + await get_tree().process_frame + + _revalidate_channel_names = true + revalidate_channel_names.call_deferred() + + +func save_channel_defaults() -> void: + var dictionary := {} + + for i in channel_defaults: + if is_instance_valid(channel_defaults[i].channel_name): + var channel_name := "" + if not channel_defaults[i].channel_name is Label: + if channel_defaults[i].channel_name.current_value.is_empty(): + continue + + channel_name = channel_defaults[i].channel_name.current_value + #channel_name = DialogicUtil.channel_name_regex.sub(channel_name, '', true) + + if channel_name.is_empty(): + dictionary[channel_name] = { + 'volume': channel_defaults[i].volume.get_value(), + 'audio_bus': channel_defaults[i].audio_bus.current_value, + 'fade_length': 0.0, + 'loop': false, + } + else: + dictionary[channel_name] = { + 'volume': channel_defaults[i].volume.get_value(), + 'audio_bus': channel_defaults[i].audio_bus.current_value, + 'fade_length': channel_defaults[i].fade_length.get_value(), + 'loop': channel_defaults[i].loop.button_pressed, + } + + ProjectSettings.set_setting(CHANNEL_DEFAULTS, dictionary) + ProjectSettings.save() + + +func _on_add_channel_defaults_pressed() -> void: + var added_node := add_channel_defaults('new_channel_name', 0.0, '', 0.0, true) + if added_node: + added_node.take_autofocus() + _revalidate_channel_names = true + revalidate_channel_names.call_deferred() + + +func add_channel_defaults(channel_name: String, volume: float, audio_bus: String, fade_length: float, loop: bool) -> Control: + var info := {} + + for i in %AudioChannelDefaultRow.get_children(): + var x := i.duplicate() + %AudioChannelDefaults.add_child(x) + info[i.name] = x + + + if channel_name.is_empty(): + var channel_label := Label.new() + channel_label.text = &"One-Shot SFX" + channel_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + %AudioChannelDefaults.add_child(channel_label) + %AudioChannelDefaults.move_child(channel_label, info.channel_name.get_index()) + info.channel_name.queue_free() + info.channel_name = channel_label + + var HintTooltip := preload("res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn") + var fade_hint := HintTooltip.instantiate() + fade_hint.hint_text = "Fading is disabled for this channel." + %AudioChannelDefaults.add_child(fade_hint) + %AudioChannelDefaults.move_child(fade_hint, info.fade_length.get_index()) + info.fade_length.queue_free() + info.fade_length = fade_hint + + var loop_hint := HintTooltip.instantiate() + loop_hint.hint_text = "Looping is disabled for this channel." + %AudioChannelDefaults.add_child(loop_hint) + %AudioChannelDefaults.move_child(loop_hint, info.loop.get_index()) + info.loop.queue_free() + info.loop = loop_hint + + info.delete.disabled = true + + else: + info.channel_name.suggestions_func = get_audio_channel_suggestions + info.channel_name.validation_func = validate_channel_names.bind(info.channel_name) + info.channel_name.set_value(channel_name) + + info.fade_length.set_value(fade_length) + + info.loop.set_pressed_no_signal(loop) + + info.audio_bus.suggestions_func = DialogicUtil.get_audio_bus_suggestions + info.audio_bus.set_value(audio_bus) + + info.delete.icon = get_theme_icon(&"Remove", &"EditorIcons") + + channel_defaults[len(channel_defaults)] = info + return info['channel_name'] + + +func _on_remove_channel_defaults_pressed(index: int) -> void: + for key in channel_defaults[index]: + channel_defaults[index][key].queue_free() + channel_defaults.erase(index) + + +func get_audio_channel_suggestions(search_text:String) -> Dictionary: + var suggestions := DialogicUtil.get_audio_channel_suggestions(search_text) + + for i in channel_defaults.values(): + if i.channel_name is DialogicVisualEditorField: + suggestions.erase(i.channel_name.current_value) + + for key in suggestions.keys(): + suggestions[key].erase('tooltip') + suggestions[key]['editor_icon'] = ["AudioStreamPlayer", "EditorIcons"] + + return suggestions + + +func revalidate_channel_names() -> void: + _revalidate_channel_names = false + for i in channel_defaults: + if (is_instance_valid(channel_defaults[i].channel_name) + and not channel_defaults[i].channel_name is Label): + channel_defaults[i].channel_name.validate() + + +func validate_channel_names(search_text: String, field_node: Control) -> Dictionary: + var channel_cache = {} + var result := {} + var tooltips := [] + + if search_text.is_empty(): + result['error_tooltip'] = 'Must not be empty.' + return result + + if field_node: + channel_cache[search_text] = [field_node] + if field_node.current_value != search_text: + _revalidate_channel_names = true + revalidate_channel_names.call_deferred() + + # Collect all channel names entered + for i in channel_defaults: + if (is_instance_valid(channel_defaults[i].channel_name) + and not channel_defaults[i].channel_name is Label + and channel_defaults[i].channel_name != field_node): + var text := channel_defaults[i].channel_name.current_value as String + if not channel_cache.has(text): + channel_cache[text] = [] + + channel_cache[text].append(channel_defaults[i].channel_name) + + # Check for duplicate names + if channel_cache.has(search_text) and channel_cache[search_text].size() > 1: + tooltips.append("Duplicate channel name.") + + # Check for invalid characters + result = DialogicUtil.validate_audio_channel_name(search_text) + if result: + tooltips.append(result.error_tooltip) + result.error_tooltip = "\n".join(tooltips) + elif not tooltips.is_empty(): + result['error_tooltip'] = "\n".join(tooltips) + + return result +#endregion diff --git a/godot/addons/dialogic/Modules/Audio/settings_audio.gd.uid b/godot/addons/dialogic/Modules/Audio/settings_audio.gd.uid new file mode 100644 index 0000000..5b8c54f --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/settings_audio.gd.uid @@ -0,0 +1 @@ +uid://cqyhm6offcitc diff --git a/godot/addons/dialogic/Modules/Audio/settings_audio.tscn b/godot/addons/dialogic/Modules/Audio/settings_audio.tscn new file mode 100644 index 0000000..41805b7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/settings_audio.tscn @@ -0,0 +1,113 @@ +[gd_scene load_steps=6 format=3 uid="uid://c2qgetjc3mfo3"] + +[ext_resource type="Script" uid="uid://cqyhm6offcitc" path="res://addons/dialogic/Modules/Audio/settings_audio.gd" id="1_2iyyr"] +[ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_o1ban"] +[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3_bx557"] +[ext_resource type="PackedScene" uid="uid://kdpp3mibml33" path="res://addons/dialogic/Editor/Events/Fields/field_number.tscn" id="4_xfyvc"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m57ns"] +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="Audio" type="VBoxContainer"] +offset_right = 121.0 +offset_bottom = 58.0 +script = ExtResource("1_2iyyr") + +[node name="TypingSoundsTitle" type="Label" parent="."] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Typing Sounds" + +[node name="HBoxContainer2" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer2"] +layout_mode = 2 +text = "Audio Bus" + +[node name="HintTooltip" parent="HBoxContainer2" instance=ExtResource("2_o1ban")] +layout_mode = 2 +texture = null +hint_text = "The default audio bus used by TypeSound nodes." + +[node name="TypeSoundBus" type="OptionButton" parent="HBoxContainer2"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HBoxContainer3" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer3"] +layout_mode = 2 +theme_type_variation = &"DialogicSettingsSection" +text = "Audio Channel Defaults" + +[node name="HintTooltip" parent="HBoxContainer3" instance=ExtResource("2_o1ban")] +layout_mode = 2 +texture = null +hint_text = "Default settings for named audio channels." + +[node name="Panel" type="PanelContainer" parent="."] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_m57ns") + +[node name="VBox" type="VBoxContainer" parent="Panel"] +layout_mode = 2 + +[node name="AudioChannelDefaults" type="GridContainer" parent="Panel/VBox"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +columns = 6 + +[node name="AudioChannelDefaultRow" type="HBoxContainer" parent="Panel/VBox"] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="channel_name" parent="Panel/VBox/AudioChannelDefaultRow" instance=ExtResource("3_bx557")] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Enter Channel Name" +mode = 3 + +[node name="volume" parent="Panel/VBox/AudioChannelDefaultRow" instance=ExtResource("4_xfyvc")] +layout_mode = 2 +mode = 2 +min = -80.0 +max = 6.0 +suffix = "dB" + +[node name="audio_bus" parent="Panel/VBox/AudioChannelDefaultRow" instance=ExtResource("3_bx557")] +layout_mode = 2 +placeholder_text = "Master" +mode = 2 + +[node name="fade_length" parent="Panel/VBox/AudioChannelDefaultRow" instance=ExtResource("4_xfyvc")] +layout_mode = 2 +mode = 0 +enforce_step = false +min = 0.0 + +[node name="loop" type="CheckButton" parent="Panel/VBox/AudioChannelDefaultRow"] +layout_mode = 2 + +[node name="delete" type="Button" parent="Panel/VBox/AudioChannelDefaultRow"] +layout_mode = 2 + +[node name="Add" type="Button" parent="Panel/VBox"] +layout_mode = 2 +size_flags_vertical = 4 +text = "Add channel" + +[connection signal="pressed" from="Panel/VBox/Add" to="." method="_on_add_channel_defaults_pressed"] diff --git a/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd b/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd new file mode 100644 index 0000000..8749828 --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd @@ -0,0 +1,284 @@ +extends DialogicSubsystem +## Subsystem for managing background audio and one-shot sound effects. +## +## This subsystem has many different helper methods for managing audio +## in your timeline. +## For instance, you can listen to audio changes via [signal audio_started]. + + +## Whenever a new audio event 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] +## `channel` | [type String] | The channel name to play the audio on. [br] +## `volume` | [type float] | The volume in `db` of the audio resource that will be set to the [AudioStreamPlayer]. [br] +## `audio_bus` | [type String] | The audio bus name that the [AudioStreamPlayer] will use. [br] +## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br] +signal audio_started(info: Dictionary) + + +## Audio node for holding audio players +var audio_node := Node.new() +## Sound node for holding sound players +var one_shot_audio_node := Node.new() +## Dictionary with info of all current audio channels +var current_audio_channels: Dictionary = {} + +#region STATE +#################################################################################################### + +## Clears the state on this subsystem and stops all audio. +func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: + stop_all_channels() + stop_all_one_shot_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 + + # Pre Alpha 17 Converter + _convert_state_info() + + var info: Dictionary = dialogic.current_state_info.get("audio", {}) + + for channel_name in info.keys(): + if info[channel_name].path.is_empty(): + update_audio(channel_name) + else: + update_audio(channel_name, info[channel_name].path, info[channel_name].settings_overrides) + + +## Pauses playing audio. +func pause() -> void: + for child in audio_node.get_children(): + child.stream_paused = true + for child in one_shot_audio_node.get_children(): + child.stream_paused = true + + +## Resumes playing audio. +func resume() -> void: + for child in audio_node.get_children(): + child.stream_paused = false + for child in one_shot_audio_node.get_children(): + child.stream_paused = false + + +func _on_dialogic_timeline_ended() -> void: + if not dialogic.Styles.get_layout_node(): + clear_game_state() + +#endregion + + +#region MAIN METHODS +#################################################################################################### + +func _ready() -> void: + dialogic.timeline_ended.connect(_on_dialogic_timeline_ended) + + audio_node.name = "Audio" + add_child(audio_node) + one_shot_audio_node.name = "OneShotAudios" + add_child(one_shot_audio_node) + + +## Plays the given file (or nothing) on the given channel. +## No channel given defaults to the "One-Shot SFX" channel, +## which does not save audio but can have multiple audios playing simultaneously. +func update_audio(channel_name:= "", path := "", settings_overrides := {}) -> void: + #volume := 0.0, audio_bus := "", fade_time := 0.0, loop := true, sync_channel := "") -> void: + if not is_channel_playing(channel_name) and path.is_empty(): + return + + ## Determine audio settings + ## TODO use .merged after dropping 4.2 support + var audio_settings: Dictionary = DialogicUtil.get_audio_channel_defaults().get(channel_name, {}) + audio_settings.merge( + {"volume":0, "audio_bus":"", "fade_length":0.0, "loop":true, "sync_channel":""} + ) + audio_settings.merge(settings_overrides, true) + + ## Handle previous audio on channel + if is_channel_playing(channel_name): + var prev_audio_node: AudioStreamPlayer = current_audio_channels[channel_name] + prev_audio_node.name += "_Prev" + if audio_settings.fade_length > 0.0: + var fade_out_tween: Tween = create_tween() + fade_out_tween.tween_method( + interpolate_volume_linearly.bind(prev_audio_node), + db_to_linear(prev_audio_node.volume_db), + 0.0, + audio_settings.fade_length) + fade_out_tween.tween_callback(prev_audio_node.queue_free) + + else: + prev_audio_node.queue_free() + + ## Set state + if not dialogic.current_state_info.has('audio'): + dialogic.current_state_info['audio'] = {} + + if not path: + dialogic.current_state_info['audio'].erase(channel_name) + return + + dialogic.current_state_info['audio'][channel_name] = {'path':path, 'settings_overrides':settings_overrides} + audio_started.emit(dialogic.current_state_info['audio'][channel_name]) + + var new_player := AudioStreamPlayer.new() + if channel_name: + new_player.name = channel_name.validate_node_name() + audio_node.add_child(new_player) + else: + new_player.name = "OneShotSFX" + one_shot_audio_node.add_child(new_player) + + var file := load(path) + if file == null: + printerr("[Dialogic] Audio file \"%s\" failed to load." % path) + return + + new_player.stream = load(path) + + ## Apply audio settings + + ## Volume & Fade + if audio_settings.fade_length > 0.0: + new_player.volume_db = linear_to_db(0.0) + var fade_in_tween := create_tween() + fade_in_tween.tween_method( + interpolate_volume_linearly.bind(new_player), + 0.0, + db_to_linear(audio_settings.volume), + audio_settings.fade_length) + + else: + new_player.volume_db = audio_settings.volume + + ## Audio Bus + new_player.bus = audio_settings.audio_bus + + ## Loop + if "loop" in new_player.stream: + new_player.stream.loop = audio_settings.loop + elif "loop_mode" in new_player.stream: + if audio_settings.loop: + new_player.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD + new_player.stream.loop_begin = 0 + new_player.stream.loop_end = new_player.stream.mix_rate * new_player.stream.get_length() + else: + new_player.stream.loop_mode = AudioStreamWAV.LOOP_DISABLED + + ## Sync & start player + if audio_settings.sync_channel and is_channel_playing(audio_settings.sync_channel): + var play_position: float = current_audio_channels[audio_settings.sync_channel].get_playback_position() + new_player.play(play_position) + + # TODO Remove this once https://github.com/godotengine/godot/issues/18878 is fixed + if new_player.stream is AudioStreamWAV and new_player.stream.format == AudioStreamWAV.FORMAT_IMA_ADPCM: + printerr("[Dialogic] WAV files using Ima-ADPCM compression cannot be synced. Reimport the file using a different compression mode.") + dialogic.print_debug_moment() + else: + new_player.play() + + new_player.finished.connect(_on_audio_finished.bind(new_player, channel_name, path)) + + if channel_name: + current_audio_channels[channel_name] = new_player + + +## Returns `true` if any audio is playing on the given [param channel_name]. +func is_channel_playing(channel_name: String) -> bool: + return (current_audio_channels.has(channel_name) + and is_instance_valid(current_audio_channels[channel_name]) + and current_audio_channels[channel_name].is_playing()) + + +## Stops audio on all channels. +func stop_all_channels(fade := 0.0) -> void: + for channel_name in current_audio_channels.keys(): + update_audio(channel_name, '', {"fade_length":fade}) + + +### Stops all one-shot sounds. +func stop_all_one_shot_sounds() -> void: + for i in one_shot_audio_node.get_children(): + i.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: AudioStreamPlayer) -> void: + node.volume_db = linear_to_db(value) + + +## Returns whether the currently playing audio resource is the same as this +## event's [param resource_path], for [param channel_name]. +func is_channel_playing_file(file_path: String, channel_name: String) -> bool: + return (is_channel_playing(channel_name) + and current_audio_channels[channel_name].stream.resource_path == file_path) + + +## Returns `true` if any channel is playing. +func is_any_channel_playing() -> bool: + for channel in current_audio_channels: + if is_channel_playing(channel): + return true + return false + + +func _on_audio_finished(player: AudioStreamPlayer, channel_name: String, path: String) -> void: + if current_audio_channels.has(channel_name) and current_audio_channels[channel_name] == player: + current_audio_channels.erase(channel_name) + player.queue_free() + if dialogic.current_state_info.get('audio', {}).get(channel_name, {}).get('path', '') == path: + dialogic.current_state_info['audio'].erase(channel_name) + +#endregion + + +#region Pre Alpha 17 Conversion + +func _convert_state_info() -> void: + var info: Dictionary = dialogic.current_state_info.get("music", {}) + if info.is_empty(): + return + + var new_info := {} + if info.has("path"): + # Pre Alpha 16 Save Data Conversion + new_info['music'] = { + "path":info.path, + "settings_overrides": { + "volume":info.volume, + "audio_bus":info.audio_bus, + "loop":info.loop} + } + + else: + # Pre Alpha 17 Save Data Conversion + for channel_id in info.keys(): + if info[channel_id].is_empty(): + continue + + var channel_name = "music" + if channel_id > 0: + channel_name += str(channel_id + 1) + new_info[channel_name] = { + "path": info[channel_id].path, + "settings_overrides":{ + 'volume': info[channel_id].volume, + 'audio_bus': info[channel_id].audio_bus, + 'loop': info[channel_id].loop, + } + } + + dialogic.current_state_info['audio'] = new_info + dialogic.current_state_info.erase('music') + +#endregion diff --git a/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd.uid b/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd.uid new file mode 100644 index 0000000..5911d0a --- /dev/null +++ b/godot/addons/dialogic/Modules/Audio/subsystem_audio.gd.uid @@ -0,0 +1 @@ +uid://do8vgqtp35d6w diff --git a/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd b/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd new file mode 100644 index 0000000..048a61f --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd @@ -0,0 +1,31 @@ +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.begins_with('user://'): + var ext_image = Image.load_from_file(argument) + image_node.texture = ImageTexture.create_from_image(ext_image) + 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/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd.uid b/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd.uid new file mode 100644 index 0000000..9b60d17 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.gd.uid @@ -0,0 +1 @@ +uid://ci7s5odxo7543 diff --git a/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.tscn b/godot/addons/dialogic/Modules/Background/DefaultBackgroundScene/default_background.tscn new file mode 100644 index 0000000..44f6db8 --- /dev/null +++ b/godot/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" uid="uid://ci7s5odxo7543" 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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd new file mode 100644 index 0000000..80154d9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd.uid new file mode 100644 index 0000000..a4edee4 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_down.gd.uid @@ -0,0 +1 @@ +uid://blaaa6obvwknl diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd new file mode 100644 index 0000000..778e473 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd.uid new file mode 100644 index 0000000..9165bf2 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_left.gd.uid @@ -0,0 +1 @@ +uid://6f7qewx7aga diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd new file mode 100644 index 0000000..a7799eb --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd.uid new file mode 100644 index 0000000..52cdacc --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_right.gd.uid @@ -0,0 +1 @@ +uid://m3anyujei6ro diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd new file mode 100644 index 0000000..e75ec92 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd.uid new file mode 100644 index 0000000..bf242dd --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/push_up.gd.uid @@ -0,0 +1 @@ +uid://dnuvmtb036bi3 diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd new file mode 100644 index 0000000..917d367 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd.uid new file mode 100644 index 0000000..1a4ab9f --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.gd.uid @@ -0,0 +1 @@ +uid://bed16hbuh4atn diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.tres b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/simple_fade.tres new file mode 100644 index 0000000..4873e08 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd new file mode 100644 index 0000000..061f943 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd.uid new file mode 100644 index 0000000..d3e25d3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_diagonal_up_left.gd.uid @@ -0,0 +1 @@ +uid://ctoc2p12vahcc diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd new file mode 100644 index 0000000..8b55d41 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd.uid new file mode 100644 index 0000000..57d788a --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_left_to_right.gd.uid @@ -0,0 +1 @@ +uid://dknape5pbyevn diff --git a/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd new file mode 100644 index 0000000..5433d59 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd.uid new file mode 100644 index 0000000..1e1859f --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/Defaults/swipe_right_to_left.gd.uid @@ -0,0 +1 @@ +uid://dwhod30peco4c diff --git a/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd b/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd new file mode 100644 index 0000000..ea5fc14 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd @@ -0,0 +1,57 @@ +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 + + +@warning_ignore("unused_signal") # Used by scripts inheriting this class +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/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd.uid new file mode 100644 index 0000000..884c65b --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/class_dialogic_background_transition.gd.uid @@ -0,0 +1 @@ +uid://cf47aj5eivati diff --git a/godot/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader b/godot/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader new file mode 100644 index 0000000..7fc2cf9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader.uid b/godot/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader.uid new file mode 100644 index 0000000..1396f0c --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/default_transition_shader.gdshader.uid @@ -0,0 +1 @@ +uid://clabj6a02r7iv diff --git a/godot/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader b/godot/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader new file mode 100644 index 0000000..0d29bf1 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader.uid b/godot/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader.uid new file mode 100644 index 0000000..469402e --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/push_transition_shader.gdshader.uid @@ -0,0 +1 @@ +uid://cuj1xsi7d7r5y diff --git a/godot/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd b/godot/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd new file mode 100644 index 0000000..43f1d74 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd.uid new file mode 100644 index 0000000..f5e5125 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/simple_push_transitions.gd.uid @@ -0,0 +1 @@ +uid://bue1pfm6eu7ww diff --git a/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_gradient.tres b/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_gradient.tres new file mode 100644 index 0000000..8f8a2a5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd b/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd new file mode 100644 index 0000000..5705fc7 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd.uid b/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd.uid new file mode 100644 index 0000000..ee5c4e9 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/Transitions/simple_swipe_transitions.gd.uid @@ -0,0 +1 @@ +uid://bkj1kaqcq5208 diff --git a/godot/addons/dialogic/Modules/Background/dialogic_background.gd b/godot/addons/dialogic/Modules/Background/dialogic_background.gd new file mode 100644 index 0000000..9e84a91 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/dialogic_background.gd.uid b/godot/addons/dialogic/Modules/Background/dialogic_background.gd.uid new file mode 100644 index 0000000..2518d6a --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/dialogic_background.gd.uid @@ -0,0 +1 @@ +uid://blsjcvm6gvd78 diff --git a/godot/addons/dialogic/Modules/Background/event_background.gd b/godot/addons/dialogic/Modules/Background/event_background.gd new file mode 100644 index 0000000..f2c0c4c --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/event_background.gd @@ -0,0 +1,164 @@ +@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 := "": + set(value): + if argument != value: + argument = value + ui_update_needed.emit() +## 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": "", "ext_file":true}, + "arg" : {"property": "argument", "default": "", "ext_file":true}, + "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("argument", ValueType.IMAGE_PREVIEW, {'left_text':'Preview:'}, + '(_arg_type == ArgumentTypes.IMAGE or _scene_type == SceneTypes.DEFAULT) and !argument.is_empty()') + add_body_line_break('(_arg_type == ArgumentTypes.IMAGE or _scene_type == SceneTypes.DEFAULT) and !argument.is_empty()') + + 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/godot/addons/dialogic/Modules/Background/event_background.gd.uid b/godot/addons/dialogic/Modules/Background/event_background.gd.uid new file mode 100644 index 0000000..7ab0952 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/event_background.gd.uid @@ -0,0 +1 @@ +uid://sioj2uwexnwx diff --git a/godot/addons/dialogic/Modules/Background/icon.png b/godot/addons/dialogic/Modules/Background/icon.png new file mode 100644 index 0000000..d4cf970 Binary files /dev/null and b/godot/addons/dialogic/Modules/Background/icon.png differ diff --git a/godot/addons/dialogic/Modules/Background/icon.png.import b/godot/addons/dialogic/Modules/Background/icon.png.import new file mode 100644 index 0000000..cae88f7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://517mp8gfj811" +path="res://.godot/imported/icon.png-cab4c78f59b171335e340ba590cf5991.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Background/icon.png" +dest_files=["res://.godot/imported/icon.png-cab4c78f59b171335e340ba590cf5991.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Background/index.gd b/godot/addons/dialogic/Modules/Background/index.gd new file mode 100644 index 0000000..d40f030 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/index.gd.uid b/godot/addons/dialogic/Modules/Background/index.gd.uid new file mode 100644 index 0000000..74d5f01 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/index.gd.uid @@ -0,0 +1 @@ +uid://bj085abnvwkyh diff --git a/godot/addons/dialogic/Modules/Background/node_background_holder.gd b/godot/addons/dialogic/Modules/Background/node_background_holder.gd new file mode 100644 index 0000000..11a2628 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Background/node_background_holder.gd.uid b/godot/addons/dialogic/Modules/Background/node_background_holder.gd.uid new file mode 100644 index 0000000..a96ea96 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/node_background_holder.gd.uid @@ -0,0 +1 @@ +uid://oxcjhq2817c7 diff --git a/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd b/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd new file mode 100644 index 0000000..e2dbec3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd @@ -0,0 +1,194 @@ +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 + + # if there is still a transition going on, stop it now + for node in get_children(): + if node is DialogicBackgroundTransition: + node.queue_free() + + + 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: + old_viewport.name = "OldBackground" + 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: + new_viewport.name = "NewBackground" + 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/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd.uid b/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd.uid new file mode 100644 index 0000000..12c344b --- /dev/null +++ b/godot/addons/dialogic/Modules/Background/subsystem_backgrounds.gd.uid @@ -0,0 +1 @@ +uid://5uwbnllu1kfv diff --git a/godot/addons/dialogic/Modules/Call/event_call.gd b/godot/addons/dialogic/Modules/Call/event_call.gd new file mode 100644 index 0000000..e00a63e --- /dev/null +++ b/godot/addons/dialogic/Modules/Call/event_call.gd @@ -0,0 +1,235 @@ +@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': DialogicUtil.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_method_suggestions(filter:="") -> Dictionary: + return DialogicUtil.get_autoload_method_suggestions(filter, autoload_name) + + +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: + var autoloads := DialogicUtil.get_autoload_suggestions() + var line_until_caret: String = CodeCompletionHelper.get_line_untill_caret(line) + + if line.count(' ') == 1 and not '.' in line: + for i in autoloads: + 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 (line_until_caret.ends_with(".") or symbol == "."): + var some_autoload := line_until_caret.split(" ")[-1].split(".")[0] + if some_autoload in autoloads: + var methods := DialogicUtil.get_autoload_method_suggestions("", some_autoload) + for i in methods.keys(): + TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, i, i+'(', event_color.lerp(TextNode.syntax_highlighter.normal_color, 0.3), TextNode.get_theme_icon("MemberMethod", "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/godot/addons/dialogic/Modules/Call/event_call.gd.uid b/godot/addons/dialogic/Modules/Call/event_call.gd.uid new file mode 100644 index 0000000..551ff02 --- /dev/null +++ b/godot/addons/dialogic/Modules/Call/event_call.gd.uid @@ -0,0 +1 @@ +uid://uhicnbvlk57s diff --git a/godot/addons/dialogic/Modules/Call/icon.png b/godot/addons/dialogic/Modules/Call/icon.png new file mode 100644 index 0000000..0bda4de Binary files /dev/null and b/godot/addons/dialogic/Modules/Call/icon.png differ diff --git a/godot/addons/dialogic/Modules/Call/icon.png.import b/godot/addons/dialogic/Modules/Call/icon.png.import new file mode 100644 index 0000000..430260f --- /dev/null +++ b/godot/addons/dialogic/Modules/Call/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://duvcdvtgy4h4b" +path="res://.godot/imported/icon.png-12e444f0ed59397c7537943ea85b475c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Call/icon.png" +dest_files=["res://.godot/imported/icon.png-12e444f0ed59397c7537943ea85b475c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Call/index.gd b/godot/addons/dialogic/Modules/Call/index.gd new file mode 100644 index 0000000..c8817a7 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Call/index.gd.uid b/godot/addons/dialogic/Modules/Call/index.gd.uid new file mode 100644 index 0000000..3f50c78 --- /dev/null +++ b/godot/addons/dialogic/Modules/Call/index.gd.uid @@ -0,0 +1 @@ +uid://bthb47untmgoo diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd new file mode 100644 index 0000000..b99e409 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd.uid new file mode 100644 index 0000000..2068624 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce.gd.uid @@ -0,0 +1 @@ +uid://qruxugkg6y8w diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd new file mode 100644 index 0000000..d5484d9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd.uid new file mode 100644 index 0000000..d397af9 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd.uid @@ -0,0 +1 @@ +uid://rfgxn0xtuen3 diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd new file mode 100644 index 0000000..847bda7 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd.uid new file mode 100644 index 0000000..c98a48a --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd.uid @@ -0,0 +1 @@ +uid://bcs0jdci4mngb diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd new file mode 100644 index 0000000..e432ecd --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd.uid new file mode 100644 index 0000000..7f7960d --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd.uid @@ -0,0 +1 @@ +uid://fekbbs23rj4m diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd new file mode 100644 index 0000000..83a8ba8 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd.uid new file mode 100644 index 0000000..547dbbf --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd.uid @@ -0,0 +1 @@ +uid://dwnfbyjtc2anb diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd new file mode 100644 index 0000000..36e8eab --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd.uid new file mode 100644 index 0000000..9893d4d --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/heartbeat.gd.uid @@ -0,0 +1 @@ +uid://8ro2ayitmjcp diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd new file mode 100644 index 0000000..3044c69 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd.uid new file mode 100644 index 0000000..b6c4e80 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd.uid @@ -0,0 +1 @@ +uid://cn4yveni7rdr7 diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd new file mode 100644 index 0000000..c71cfde --- /dev/null +++ b/godot/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(0, 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/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd.uid new file mode 100644 index 0000000..df5b2b4 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_x.gd.uid @@ -0,0 +1 @@ +uid://3tqien23j50t diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd new file mode 100644 index 0000000..6a0224c --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd.uid new file mode 100644 index 0000000..9db5c83 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/shake_y.gd.uid @@ -0,0 +1 @@ +uid://lur75holg34f diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd new file mode 100644 index 0000000..d59d34f --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_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 end_position_y: float = base_position.y + node.get_parent().global_position.y + var start_position: float = -get_node_size().y + get_node_origin().y + + if is_reversed: + tween.set_ease(Tween.EASE_IN) + end_position_y = -get_node_size().y + get_node_origin().y + start_position = base_position.y + + node.position.y = start_position + + tween.tween_property(node, 'global_position:y', end_position_y, 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/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd.uid new file mode 100644 index 0000000..ba6b76c --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd.uid @@ -0,0 +1 @@ +uid://d0a5sgbr5imas diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd new file mode 100644 index 0000000..ed9987f --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_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 end_position_x: float = base_position.x + node.get_parent().global_position.x + + if is_reversed: + end_position_x = - get_node_size().x + get_node_origin().x + tween.set_ease(Tween.EASE_IN) + + else: + node.global_position.x = -get_node_size().x + get_node_origin().x + + tween.tween_property(node, 'global_position:x', end_position_x, time) + + await tween.finished + finished_once.emit() + + +func _get_named_variations() -> Dictionary: + return { + "slide from left": {"reversed": false, "type": AnimationType.IN}, + "slide to left": {"reversed": true, "type": AnimationType.OUT}, + } diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd.uid new file mode 100644 index 0000000..adda345 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd.uid @@ -0,0 +1 @@ +uid://c8il877nw3xqw diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd new file mode 100644 index 0000000..b248156 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd @@ -0,0 +1,24 @@ +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 = get_viewport_size().x + var end_position_x : float = base_position.x + node.get_parent().global_position.x + + if is_reversed: + end_position_x = viewport_x + get_node_origin().x + tween.set_ease(Tween.EASE_IN) + else: + node.global_position.x = viewport_x + get_node_origin().x + + tween.tween_property(node, 'global_position:x', end_position_x, time) + tween.finished.connect(emit_signal.bind('finished_once')) + + +func _get_named_variations() -> Dictionary: + return { + "slide from right": {"reversed": false, "type": AnimationType.IN}, + "slide to right": {"reversed": true, "type": AnimationType.OUT}, + } diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd.uid new file mode 100644 index 0000000..fb8adfa --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd.uid @@ -0,0 +1 @@ +uid://daj7cqft5hnxg diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd new file mode 100644 index 0000000..7e89a06 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.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 start_position_y: float = get_viewport_size().y + get_node_origin().y + var end_position_y: float = base_position.y + node.get_parent().global_position.y + + if is_reversed: + tween.set_ease(Tween.EASE_IN) + start_position_y = end_position_y + end_position_y = get_viewport_size().y + get_node_origin().y + + node.global_position.y = start_position_y + tween.tween_property(node, 'global_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/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd.uid new file mode 100644 index 0000000..e63f6f5 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd.uid @@ -0,0 +1 @@ +uid://bj5ak53i7s8ux diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd new file mode 100644 index 0000000..0d578b1 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd.uid new file mode 100644 index 0000000..21553de --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/tada.gd.uid @@ -0,0 +1 @@ +uid://crv1pn60clrvx diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd new file mode 100644 index 0000000..fad8b61 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd.uid new file mode 100644 index 0000000..154ef7d --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd.uid @@ -0,0 +1 @@ +uid://cjwdb0jkjrcxe diff --git a/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd new file mode 100644 index 0000000..21cc338 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd.uid b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd.uid new file mode 100644 index 0000000..05fe814 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd.uid @@ -0,0 +1 @@ +uid://bl5sdpj631mtt diff --git a/godot/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd b/godot/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd new file mode 100644 index 0000000..66908e3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd.uid b/godot/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd.uid new file mode 100644 index 0000000..9ddf912 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/DialogicPortraitAnimationsUtil.gd.uid @@ -0,0 +1 @@ +uid://j2k3uogf5715 diff --git a/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd b/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd new file mode 100644 index 0000000..3c0d8ef --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd @@ -0,0 +1,102 @@ +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" + + +## Tries to return the size of the node to be animated. +## For portraits this uses the portrait containers size. +## This is useful if your animation depends on the size of the node. +func get_node_size() -> Vector2: + if not node: + return Vector2() + if node.get_parent() is DialogicNode_PortraitContainer: + return node.get_parent().size + if "size" in node: + return node.size * node.scale + return node.get_viewport().size + + +func get_node_origin() -> Vector2: + if not node: + return Vector2() + if node.get_parent() is DialogicNode_PortraitContainer: + return node.get_parent()._get_origin_position() + return Vector2() + + +func get_viewport_size() -> Vector2: + return node.get_viewport().get_visible_rect().size diff --git a/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd.uid b/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd.uid new file mode 100644 index 0000000..e8ff8b4 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/class_dialogic_animation.gd.uid @@ -0,0 +1 @@ +uid://0hsjlurlblou diff --git a/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png b/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png new file mode 100644 index 0000000..77d3cf3 Binary files /dev/null and b/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png differ diff --git a/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import b/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import new file mode 100644 index 0000000..bdad57f --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/custom_portrait_thumbnail.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5tu88x32sjkf" +path="res://.godot/imported/custom_portrait_thumbnail.png-0513583853d87342a634d56bc0bec965.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/custom_portrait_thumbnail.png" +dest_files=["res://.godot/imported/custom_portrait_thumbnail.png-0513583853d87342a634d56bc0bec965.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/default_portrait.gd b/godot/addons/dialogic/Modules/Character/default_portrait.gd new file mode 100644 index 0000000..a9c7775 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/default_portrait.gd @@ -0,0 +1,15 @@ +@tool +extends DialogicPortrait + +## Default portrait scene. +## The parent class has a character and portrait variable. + +@export_group('Main') +@export_file var image := "" + + +## Load anything related to the given character and portrait +func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void: + apply_character_and_portrait(passed_character, passed_portrait) + + apply_texture($Portrait, image) diff --git a/godot/addons/dialogic/Modules/Character/default_portrait.gd.uid b/godot/addons/dialogic/Modules/Character/default_portrait.gd.uid new file mode 100644 index 0000000..c4e680f --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/default_portrait.gd.uid @@ -0,0 +1 @@ +uid://cork0heubbx7f diff --git a/godot/addons/dialogic/Modules/Character/default_portrait.tscn b/godot/addons/dialogic/Modules/Character/default_portrait.tscn new file mode 100644 index 0000000..cd3d57d --- /dev/null +++ b/godot/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" uid="uid://cork0heubbx7f" 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/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png b/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png new file mode 100644 index 0000000..369a111 Binary files /dev/null and b/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png differ diff --git a/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import b/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import new file mode 100644 index 0000000..5355a94 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/default_portrait_thumbnail.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0eisk30btlx2" +path="res://.godot/imported/default_portrait_thumbnail.png-f9f92adb946f409def18655d6ab5c5df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/default_portrait_thumbnail.png" +dest_files=["res://.godot/imported/default_portrait_thumbnail.png-f9f92adb946f409def18655d6ab5c5df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/dialogic_portrait.gd b/godot/addons/dialogic/Modules/Character/dialogic_portrait.gd new file mode 100644 index 0000000..b2bf47a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/dialogic_portrait.gd.uid b/godot/addons/dialogic/Modules/Character/dialogic_portrait.gd.uid new file mode 100644 index 0000000..5428091 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/dialogic_portrait.gd.uid @@ -0,0 +1 @@ +uid://djbg8sc0q67ow diff --git a/godot/addons/dialogic/Modules/Character/event_character.gd b/godot/addons/dialogic/Modules/Character/event_character.gd new file mode 100644 index 0000000..41256e3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/event_character.gd @@ -0,0 +1,581 @@ +@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 := character.get_identifier() + if not identifier.is_empty(): + return identifier + return character_identifier + set(value): + character_identifier = value + character = DialogicResourceUtil.get_character_resource(value) + if (not character) or (character and not character.portraits.has(portrait)): + portrait = "" + ui_update_needed.emit() + +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 + + if set_portrait: + await dialogic.Portraits.change_character_portrait(character, portrait, fade_animation, fade_length) + + dialogic.Portraits.change_character_extradata(character, extra_data) + + 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 := character.get_character_name() + + 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, "ext_file":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, self) + 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 == ' ' and line_until_caret.split(" ", false).size() > 1: + if not line.begins_with("leave"): + if not line_until_caret.split(" ", false)[-1] in get_position_suggestions(): + 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", "fade"]: + suggest_parameter(param, line, TextNode) + if "fade=" in line_until_caret: + suggest_parameter("fade_length", 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/godot/addons/dialogic/Modules/Character/event_character.gd.uid b/godot/addons/dialogic/Modules/Character/event_character.gd.uid new file mode 100644 index 0000000..cedaeb7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/event_character.gd.uid @@ -0,0 +1 @@ +uid://b88y7tdin2uu5 diff --git a/godot/addons/dialogic/Modules/Character/icon.png.import b/godot/addons/dialogic/Modules/Character/icon.png.import new file mode 100644 index 0000000..ec40123 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Character/icon.svg b/godot/addons/dialogic/Modules/Character/icon.svg new file mode 100644 index 0000000..6638227 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/dialogic/Modules/Character/icon.svg.import b/godot/addons/dialogic/Modules/Character/icon.svg.import new file mode 100644 index 0000000..4dd47d3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://q6lanmf18ii6" +path="res://.godot/imported/icon.svg-4f340f6efbb83004dbd5c761dd1dc448.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/icon.svg" +dest_files=["res://.godot/imported/icon.svg-4f340f6efbb83004dbd5c761dd1dc448.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/icon_position.png b/godot/addons/dialogic/Modules/Character/icon_position.png new file mode 100644 index 0000000..7f01934 Binary files /dev/null and b/godot/addons/dialogic/Modules/Character/icon_position.png differ diff --git a/godot/addons/dialogic/Modules/Character/icon_position.png.import b/godot/addons/dialogic/Modules/Character/icon_position.png.import new file mode 100644 index 0000000..8ce8e2a --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/icon_position.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cu6cj55m6dr78" +path="res://.godot/imported/icon_position.png-221b7c348ec45b22fd4df49fdb92a7aa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/icon_position.png" +dest_files=["res://.godot/imported/icon_position.png-221b7c348ec45b22fd4df49fdb92a7aa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/index.gd b/godot/addons/dialogic/Modules/Character/index.gd new file mode 100644 index 0000000..3b1dd7e --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/index.gd @@ -0,0 +1,58 @@ +@tool +extends DialogicIndexer + + +func _get_events() -> Array: + return [this_folder.path_join('event_character.gd')] + + +func _get_subsystems() -> Array: + return [{'name':'Portraits', 'script':this_folder.path_join('subsystem_portraits.gd')}, {'name':'PortraitContainers', 'script':this_folder.path_join('subsystem_containers.gd')}] + +func _get_settings_pages() -> Array: + return [this_folder.path_join('settings_portraits.tscn')] + +func _get_text_effects() -> Array[Dictionary]: + return [ + {'command':'portrait', 'subsystem':'Portraits', 'method':'text_effect_portrait', 'arg':true}, + {'command':'extra_data', 'subsystem':'Portraits', 'method':'text_effect_extradata', 'arg':true}, + ] + + +func _get_special_resources() -> Dictionary: + return {&'PortraitAnimation': list_animations("DefaultAnimations")} + + +func _get_portrait_scene_presets() -> Array[Dictionary]: + return [ + { + "path": "", + "name": "Default Scene", + "description": "The default scene defined in Settings>Portraits.", + "author":"Dialogic", + "type": "Default", + "icon":"", + "preview_image":[this_folder.path_join("default_portrait_thumbnail.png")], + "documentation":"", + }, + { + "path": "CUSTOM", + "name": "Custom Scene", + "description": "A custom scene. Should extend DialogicPortrait and be in @tool mode.", + "author":"Dialogic", + "type": "Custom", + "icon":"", + "preview_image":[this_folder.path_join("custom_portrait_thumbnail.png")], + "documentation":"https://docs.dialogic.pro/custom-portraits.html", + }, + { + "path": this_folder.path_join("default_portrait.tscn"), + "name": "Simple Image Portrait", + "description": "Can display images as portraits. Does nothing else.", + "author":"Dialogic", + "type": "General", + "icon":"", + "preview_image":[this_folder.path_join("simple_image_portrait_thumbnail.png")], + "documentation":"", + } + ] diff --git a/godot/addons/dialogic/Modules/Character/index.gd.uid b/godot/addons/dialogic/Modules/Character/index.gd.uid new file mode 100644 index 0000000..0f8ab23 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/index.gd.uid @@ -0,0 +1 @@ +uid://4tu24b2ex257 diff --git a/godot/addons/dialogic/Modules/Character/node_portrait_container.gd b/godot/addons/dialogic/Modules/Character/node_portrait_container.gd new file mode 100644 index 0000000..20d7c5d --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/node_portrait_container.gd @@ -0,0 +1,258 @@ +@tool +class_name DialogicNode_PortraitContainer +extends Control + +## Node that defines a position for dialogic portraits and how to display portraits at that position. + +enum PositionModes { + POSITION, ## This container can be joined/moved to with the Character Event + SPEAKER, ## This container is joined/left automatically based on the speaker. + } + +@export var mode := PositionModes.POSITION + +@export_subgroup('Mode: Position') +## The position this node corresponds to. +@export var container_ids: PackedStringArray = ["1"] + + +@export_subgroup('Mode: Speaker') +## Can be used to use a different portrait. +## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy" +@export var portrait_prefix := '' + +@export_subgroup('Portrait Placement') +enum SizeModes { + KEEP, ## The height and width of the container have no effect, only the origin. + FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale. + FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio. + FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved. + } +## Defines how to affect the scale of the portrait +@export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT : + set(mode): + size_mode = mode + _update_debug_portrait_transform() + +## If true, portraits will be mirrored in this position. +@export var mirrored := false : + set(mirror): + mirrored = mirror + _update_debug_portrait_scene() + + +@export_group('Origin', 'origin') +enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT} +## The portrait will be placed relative to this point in the container. +@export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER : + set(anchor): + origin_anchor = anchor + _update_debug_origin() + +## An offset to apply to the origin. Rarely useful. +@export var origin_offset := Vector2() : + set(offset): + origin_offset = offset + _update_debug_origin() + +enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS} +## Usually you want to rotate or scale around the portrait origin. +## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value]. +@export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN +## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS. +@export var pivot_value := Vector2() + +@export_group('Debug', 'debug') +## A character that will be displayed in the editor, useful for getting the right size. +@export var debug_character: DialogicCharacter = null: + set(character): + debug_character = character + _update_debug_portrait_scene() +@export var debug_character_portrait := "": + set(portrait): + debug_character_portrait = portrait + _update_debug_portrait_scene() + +var debug_character_holder_node: Node2D = null +var debug_character_scene_node: Node = null +var debug_origin: Sprite2D = null +var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn") +# Used if no debug character is specified +var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres")) + +var ignore_resize := false + + +func _ready() -> void: + match mode: + PositionModes.POSITION: + add_to_group('dialogic_portrait_con_position') + PositionModes.SPEAKER: + add_to_group('dialogic_portrait_con_speaker') + + if Engine.is_editor_hint(): + resized.connect(_update_debug_origin) + + if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty(): + default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '') + + debug_origin = Sprite2D.new() + add_child(debug_origin) + debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg") + + _update_debug_origin() + _update_debug_portrait_scene() + else: + resized.connect(update_portrait_transforms) + + +################################################################################ +## MAIN METHODS +################################################################################ + +func update_portrait_transforms() -> void: + if ignore_resize: + return + + match pivot_mode: + PivotModes.AT_ORIGIN: + pivot_offset = _get_origin_position() + PivotModes.PERCENTAGE: + pivot_offset = size*pivot_value + PivotModes.PIXELS: + pivot_offset = pivot_value + + for child in get_children(): + DialogicUtil.autoload().Portraits._update_character_transform(child) + + +## Returns a Rect2 with the position as the position and the scale as the size. +func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2: + var transform := Rect2() + transform.position = _get_origin_position() + + # Mode that ignores the containers size + if size_mode == SizeModes.KEEP: + transform.size = Vector2(1,1) * character_scale + + # Mode that makes sure neither height nor width go out of container + elif size_mode == SizeModes.FIT_IGNORE_SCALE: + if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y: + transform.size = Vector2(1,1) * size.x/portrait_rect.size.x + else: + transform.size = Vector2(1,1) * size.y/portrait_rect.size.y + + # Mode that stretches the portrait to fill the whole container + elif size_mode == SizeModes.FIT_STRETCH: + transform.size = size/portrait_rect.size + + # Mode that size the character so 100% size fills the height + elif size_mode == SizeModes.FIT_SCALE_HEIGHT: + transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale + + return transform + + +## Returns the current origin position +func _get_origin_position(rect_size = null) -> Vector2: + if rect_size == null: + rect_size = size + return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset + + +func is_container(id:Variant) -> bool: + return str(id) in container_ids + +#region DEBUG METHODS +################################################################################ +### USE THIS TO DEBUG THE POSITIONS +#func _draw(): + #draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2) + #draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER) +# +#func _process(delta:float) -> void: + #queue_redraw() + + +## Loads the debug_character with the debug_character_portrait +## Creates a holder node and applies mirror +func _update_debug_portrait_scene() -> void: + if !Engine.is_editor_hint(): + return + if is_instance_valid(debug_character_holder_node): + for child in get_children(): + if child != debug_origin: + child.free() + + # Get character + var character := _get_debug_character() + if not character is DialogicCharacter or character.portraits.is_empty(): + return + + # Determine portrait + var debug_portrait := debug_character_portrait + if debug_portrait.is_empty(): + debug_portrait = character.default_portrait + if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty(): + if portrait_prefix+debug_portrait in character.portraits: + debug_portrait = portrait_prefix+debug_portrait + if not debug_portrait in character.portraits: + debug_portrait = character.default_portrait + + var portrait_info: Dictionary = character.get_portrait_info(debug_portrait) + + # Determine scene + var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene) + if portrait_scene_path.is_empty(): + portrait_scene_path = default_portrait_scene + + debug_character_scene_node = load(portrait_scene_path).instantiate() + + if !is_instance_valid(debug_character_scene_node): + return + + # Load portrait + DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {})) + debug_character_scene_node._update_portrait(character, debug_portrait) + + # Add character node + if !is_instance_valid(debug_character_holder_node): + debug_character_holder_node = Node2D.new() + add_child(debug_character_holder_node) + + # Add portrait node + debug_character_holder_node.add_child(debug_character_scene_node) + move_child(debug_character_holder_node, 0) + debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false)) + + _update_debug_portrait_transform() + + +## Set's the size and position of the holder and scene node +## according to the size_mode +func _update_debug_portrait_transform() -> void: + if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin): + return + var character := _get_debug_character() + var portrait_info := character.get_portrait_info(debug_character_portrait) + var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1)) + debug_character_holder_node.position = transform.position + debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset + + debug_character_holder_node.scale = transform.size + + +## Updates the debug origins position. Also calls _update_debug_portrait_transform() +func _update_debug_origin() -> void: + if !Engine.is_editor_hint() or !is_instance_valid(debug_origin): + return + debug_origin.position = _get_origin_position() + _update_debug_portrait_transform() + + + +## Returns the debug character or the default debug character +func _get_debug_character() -> DialogicCharacter: + return debug_character if debug_character != null else default_debug_character + +#endregion diff --git a/godot/addons/dialogic/Modules/Character/node_portrait_container.gd.uid b/godot/addons/dialogic/Modules/Character/node_portrait_container.gd.uid new file mode 100644 index 0000000..5e01837 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/node_portrait_container.gd.uid @@ -0,0 +1 @@ +uid://d0ptqnbudhkyj diff --git a/godot/addons/dialogic/Modules/Character/portrait_position.svg b/godot/addons/dialogic/Modules/Character/portrait_position.svg new file mode 100644 index 0000000..b022121 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/portrait_position.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/portrait_position.svg.import b/godot/addons/dialogic/Modules/Character/portrait_position.svg.import new file mode 100644 index 0000000..aada16b --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/portrait_position.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn3nq7gw67kye" +path="res://.godot/imported/portrait_position.svg-f025767e40032b9ebdeab1f9e882467a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/portrait_position.svg" +dest_files=["res://.godot/imported/portrait_position.svg-f025767e40032b9ebdeab1f9e882467a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/preview_character.tres b/godot/addons/dialogic/Modules/Character/preview_character.tres new file mode 100644 index 0000000..5ec5b32 --- /dev/null +++ b/godot/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" uid="uid://don4ds5f38byo" 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/godot/addons/dialogic/Modules/Character/settings_portraits.gd b/godot/addons/dialogic/Modules/Character/settings_portraits.gd new file mode 100644 index 0000000..51b45ac --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/settings_portraits.gd @@ -0,0 +1,92 @@ +@tool +extends DialogicSettingsPage + + +const POSITION_SUGGESTION_KEY := 'dialogic/portraits/position_suggestion_names' + +const DEFAULT_PORTRAIT_SCENE_KEY := 'dialogic/portraits/default_portrait' + +const ANIMATION_JOIN_DEFAULT_KEY := 'dialogic/animations/join_default' +const ANIMATION_JOIN_DEFAULT_LENGTH_KEY := 'dialogic/animations/join_default_length' +const ANIMATION_JOIN_DEFAULT_WAIT_KEY := 'dialogic/animations/join_default_wait' +const ANIMATION_LEAVE_DEFAULT_KEY := 'dialogic/animations/leave_default' +const ANIMATION_LEAVE_DEFAULT_LENGTH_KEY := 'dialogic/animations/leave_default_length' +const ANIMATION_LEAVE_DEFAULT_WAIT_KEY := 'dialogic/animations/leave_default_wait' +const ANIMATION_CROSSFADE_DEFAULT_KEY := 'dialogic/animations/cross_fade_default' +const ANIMATION_CROSSFADE_DEFAULT_LENGTH_KEY:= 'dialogic/animations/cross_fade_default_length' + + +func _ready(): + %JoinDefault.suggestions_func = get_join_animation_suggestions + %JoinDefault.mode = 1 + %LeaveDefault.suggestions_func = get_leave_animation_suggestions + %LeaveDefault.mode = 1 + %CrossFadeDefault.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/godot/addons/dialogic/Modules/Character/settings_portraits.gd.uid b/godot/addons/dialogic/Modules/Character/settings_portraits.gd.uid new file mode 100644 index 0000000..4feebae --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/settings_portraits.gd.uid @@ -0,0 +1 @@ +uid://c3hdycwp0hrdm diff --git a/godot/addons/dialogic/Modules/Character/settings_portraits.tscn b/godot/addons/dialogic/Modules/Character/settings_portraits.tscn new file mode 100644 index 0000000..9969701 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/settings_portraits.tscn @@ -0,0 +1,157 @@ +[gd_scene load_steps=5 format=3 uid="uid://cp463rpri5j8a"] + +[ext_resource type="Script" uid="uid://c3hdycwp0hrdm" 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/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png b/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png new file mode 100644 index 0000000..656215f Binary files /dev/null and b/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png differ diff --git a/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import b/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import new file mode 100644 index 0000000..d56dac5 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qihj11n7kx3m" +path="res://.godot/imported/simple_image_portrait_thumbnail.png-3d6c6de8187fd7911017e2ef9d3c40a4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/simple_image_portrait_thumbnail.png" +dest_files=["res://.godot/imported/simple_image_portrait_thumbnail.png-3d6c6de8187fd7911017e2ef9d3c40a4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/subsystem_containers.gd b/godot/addons/dialogic/Modules/Character/subsystem_containers.gd new file mode 100644 index 0000000..239e423 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/subsystem_containers.gd @@ -0,0 +1,285 @@ +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 + container.set_deferred("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_visible_rect().size.x + 'y': value *= get_viewport().get_visible_rect().size.y + + match i.get_string(&'part'): + 'x': vec.x = value + 'y': vec.y = value + return vec + + +func vector_to_str(vec:Vector2) -> String: + return "x" + str(vec.x) + "px y" + str(vec.y) + "px" + + +func reset_all_containers(time:= 0.0, tween:Tween = null) -> void: + for container in get_tree().get_nodes_in_group(&'dialogic_portrait_con_position'): + reset_container(container, time, tween) + + +func reset_container(container:DialogicNode_PortraitContainer, time := 0.0, tween: Tween = null ) -> void: + if container.has_meta(&'default_translation'): + translate_container(container, vector_to_str(container.get_meta(&'default_translation')), false, tween, time) + if container.has_meta(&'default_rotation'): + rotate_container(container, container.get_meta(&'default_rotation'), false, tween, time) + if container.has_meta(&'default_size'): + resize_container(container, vector_to_str(container.get_meta(&'default_size')), false, tween, time) diff --git a/godot/addons/dialogic/Modules/Character/subsystem_containers.gd.uid b/godot/addons/dialogic/Modules/Character/subsystem_containers.gd.uid new file mode 100644 index 0000000..98412db --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/subsystem_containers.gd.uid @@ -0,0 +1 @@ +uid://c5dk5rh4vj8rd diff --git a/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd b/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd new file mode 100644 index 0000000..c87468d --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -0,0 +1,729 @@ +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_identifier in dialogic.current_state_info.get('portraits', {}).keys(): + remove_character(DialogicResourceUtil.get_character_resource(character_identifier)) + 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_identifier in portraits_info: + var character_info: Dictionary = portraits_info[character_identifier] + var character: DialogicCharacter = DialogicResourceUtil.get_character_resource(character_identifier) + if character: + 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', "")) + else: + push_error('[Dialogic] Failed to load character "' + str(character_identifier) + '".') + + # Load Speaker Portrait + var speaker: Variant = dialogic.current_state_info.get("speaker", "") + if speaker: + dialogic.current_state_info["speaker"] = "" + change_speaker(DialogicResourceUtil.get_character_resource(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.has_method('_should_do_portrait_update') and + previous_portrait._should_do_portrait_update(character, portrait)): + portrait_node = previous_portrait + info['same_scene'] = true + + else: + + if ResourceLoader.exists(scene_path): + ResourceLoader.load_threaded_request(scene_path) + + var load_status := ResourceLoader.load_threaded_get_status(scene_path) + while load_status == ResourceLoader.THREAD_LOAD_IN_PROGRESS: + await get_tree().process_frame + load_status = ResourceLoader.load_threaded_get_status(scene_path) + + if load_status == ResourceLoader.THREAD_LOAD_LOADED: + var packed_scene: PackedScene = ResourceLoader.load_threaded_get(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.') + else: + push_error('[Dialogic] Failed to load portrait node "' + str(scene_path) + '" for character [' + character.display_name + '].') + + 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 character_node.get_child_count() > 0 else null + + if latest_portrait and 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: + if not is_instance_valid(character_node): + push_error("[Dialogic] Invalid character node provided.") + return + + if character_node.get_child_count() > 0: + var latest_portrait := character_node.get_child(-1) + + if latest_portrait and latest_portrait.has_method("_set_extra_data"): + latest_portrait._set_extra_data(extra_data) + else: + push_warning("[Dialogic] No portrait found for character node: " + character_node.name) + +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.get_identifier()].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 "', character.get_character_name(), '". 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 := await add_character(character, container, portrait, position_id) + if character_node == null: + return null + + dialogic.current_state_info['portraits'][character.get_identifier()] = {'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.get_identifier()]) + 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 an 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.get_identifier()] = {'portrait': portrait, 'node': character_node, 'position_id': position_id} + + _move_character(character_node, position_id) + await _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:="", 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.get_identifier()].portrait == portrait: + return + + if fade_animation == "": + 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 := await _change_portrait(dialogic.current_state_info.portraits[character.get_identifier()].node, portrait, fade_animation, fade_length) + dialogic.current_state_info.portraits[character.get_identifier()].portrait = info.portrait + _change_portrait_mirror( + dialogic.current_state_info.portraits[character.get_identifier()].node, + dialogic.current_state_info.portraits[character.get_identifier()].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.get_identifier()].node, mirrored, force) + dialogic.current_state_info.portraits[character.get_identifier()]['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.get_identifier()].node, z_index, update_zindex) + if update_zindex: + dialogic.current_state_info.portraits[character.get_identifier()]['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.get_identifier()].node, extra_data) + dialogic.current_state_info.portraits[character.get_identifier()]['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.get_identifier()].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.get_identifier()].position_id == position_id: + return + + _move_character(dialogic.current_state_info.portraits[character.get_identifier()].node, position_id, time, easing, trans) + dialogic.current_state_info.portraits[character.get_identifier()].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.get_identifier()].node): + return dialogic.current_state_info['portraits'][character.get_identifier()].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.get_identifier()) + + +## Returns true if the given character is currently joined. +func is_character_joined(character: DialogicCharacter) -> bool: + if character == null or not character.get_identifier() 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_identifier: String in dialogic.current_state_info.get('portraits', {}).keys(): + chars.append(DialogicResourceUtil.get_character_resource(char_identifier)) + + 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.get_identifier()] + 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) + await animate_out.finished + character_node.queue_free() + else: + character_node.get_parent().remove_child(character_node) + character_node.queue_free() + else: + just_joined = false + + if speaker == null or speaker.portraits.is_empty(): + continue + + if just_joined: + _create_character_node(speaker, container) + + elif portrait.is_empty(): + continue + + if portrait.is_empty(): + portrait = speaker.default_portrait + + var character_node := container.get_child(-1) + + var fade_animation: String = ProjectSettings.get_setting('dialogic/animations/cross_fade_default', "Fade Cross") + var fade_length: float = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5) + + fade_animation = DialogicPortraitAnimationUtil.guess_animation(fade_animation, DialogicPortraitAnimationUtil.AnimationType.CROSSFADE) + + if container.portrait_prefix + portrait in speaker.portraits: + portrait = container.portrait_prefix + portrait + + await _change_portrait(character_node, portrait, fade_animation, fade_length) + + # if the character has no portraits _change_portrait won't actually add a child node + if character_node.get_child_count() == 0: + continue + + if just_joined: + # Change speaker is called before the text is changed. + # In styles where the speaker is IN the textbox, + # this can mean the portrait container isn't sized correctly yet. + character_node.hide() + if not container.is_visible_in_tree(): + await get_tree().process_frame + + # There is chance that the style changed (due to a speaker style) and thus the character node is gone now. + # In that case, just give up. + if not is_instance_valid(character_node): + return + character_node.show() + var join_animation: String = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up") + join_animation = DialogicPortraitAnimationUtil.guess_animation(join_animation, DialogicPortraitAnimationUtil.AnimationType.IN) + var join_animation_length := _get_join_default_length() + + if join_animation and join_animation_length: + await _animate_node(character_node, join_animation, join_animation_length).finished + + _change_portrait_mirror(character_node) + + var prev_speaker: DialogicCharacter = dialogic.Text.get_current_speaker() + if speaker != prev_speaker: + if is_character_joined(prev_speaker): + dialogic.current_state_info["portraits"][prev_speaker.get_identifier()].node.get_child(-1)._unhighlight() + + if is_character_joined(speaker): + dialogic.current_state_info["portraits"][speaker.get_identifier()].node.get_child(-1)._highlight() + +#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: + var current_speaker := dialogic.Text.get_current_speaker() + if current_speaker: + change_character_portrait(current_speaker, argument) + change_speaker(current_speaker, argument) + + +## Called from the [extra_data=something] text effect. +func text_effect_extradata(_text_node:Control, _skipped:bool, argument:String) -> void: + if argument: + if dialogic.Text.get_current_speaker(): + change_character_extradata(dialogic.Text.get_current_speaker(), argument) +#endregion diff --git a/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd.uid b/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd.uid new file mode 100644 index 0000000..7f2b29c --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/subsystem_portraits.gd.uid @@ -0,0 +1 @@ +uid://fyhfur7bpp4v diff --git a/godot/addons/dialogic/Modules/Character/update_mirror.svg b/godot/addons/dialogic/Modules/Character/update_mirror.svg new file mode 100644 index 0000000..561cbc9 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_mirror.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/update_mirror.svg.import b/godot/addons/dialogic/Modules/Character/update_mirror.svg.import new file mode 100644 index 0000000..d2aad72 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_mirror.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c735ss4h37y8i" +path="res://.godot/imported/update_mirror.svg-d6fa4832a7758cad02a7f32282433230.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/update_mirror.svg" +dest_files=["res://.godot/imported/update_mirror.svg-d6fa4832a7758cad02a7f32282433230.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/update_portrait.svg b/godot/addons/dialogic/Modules/Character/update_portrait.svg new file mode 100644 index 0000000..b0856b3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_portrait.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/update_portrait.svg.import b/godot/addons/dialogic/Modules/Character/update_portrait.svg.import new file mode 100644 index 0000000..b6a73d2 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_portrait.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qx5bntelnslj" +path="res://.godot/imported/update_portrait.svg-b90fa6163d3720d34df8578ce2aa35e1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/update_portrait.svg" +dest_files=["res://.godot/imported/update_portrait.svg-b90fa6163d3720d34df8578ce2aa35e1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/update_position.svg b/godot/addons/dialogic/Modules/Character/update_position.svg new file mode 100644 index 0000000..6e15feb --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_position.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg b/godot/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/godot/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import b/godot/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import new file mode 100644 index 0000000..537be0c --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://deu1h3rsp3p8y" +path="res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-d66e70a05e9de92a595b70fa88fca0d4.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/update_position.svg.2023_09_23_08_37_47.0.svg" +dest_files=["res://.godot/imported/update_position.svg.2023_09_23_08_37_47.0.svg-d66e70a05e9de92a595b70fa88fca0d4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/update_position.svg.import b/godot/addons/dialogic/Modules/Character/update_position.svg.import new file mode 100644 index 0000000..0c98cc7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_position.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rslog4jar4gu" +path="res://.godot/imported/update_position.svg-4be50608dc0768e715ff659ca62cf187.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/update_position.svg" +dest_files=["res://.godot/imported/update_position.svg-4be50608dc0768e715ff659ca62cf187.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Character/update_z_index.svg b/godot/addons/dialogic/Modules/Character/update_z_index.svg new file mode 100644 index 0000000..11cf3fc --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_z_index.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Character/update_z_index.svg.import b/godot/addons/dialogic/Modules/Character/update_z_index.svg.import new file mode 100644 index 0000000..0b25a61 --- /dev/null +++ b/godot/addons/dialogic/Modules/Character/update_z_index.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5xa3i541igyk" +path="res://.godot/imported/update_z_index.svg-204064db9241de79bf8dfd23af57c6c6.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Character/update_z_index.svg" +dest_files=["res://.godot/imported/update_z_index.svg-204064db9241de79bf8dfd23af57c6c6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Choice/event_choice.gd b/godot/addons/dialogic/Modules/Choice/event_choice.gd new file mode 100644 index 0000000..cbe15e3 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/event_choice.gd @@ -0,0 +1,236 @@ +@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 + + +func _is_branch_starter() -> bool: + return dialogic.Choices.is_question(dialogic.current_timeline_events.find(self)) + +#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/godot/addons/dialogic/Modules/Choice/event_choice.gd.uid b/godot/addons/dialogic/Modules/Choice/event_choice.gd.uid new file mode 100644 index 0000000..5ea1d4c --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/event_choice.gd.uid @@ -0,0 +1 @@ +uid://cltu1tykths0n diff --git a/godot/addons/dialogic/Modules/Choice/icon.svg b/godot/addons/dialogic/Modules/Choice/icon.svg new file mode 100644 index 0000000..b69a86d --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Choice/icon.svg.import b/godot/addons/dialogic/Modules/Choice/icon.svg.import new file mode 100644 index 0000000..6a28924 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wd4fnxjfmj5m" +path="res://.godot/imported/icon.svg-372a9bfce8bea67fff1988d7acf09a9a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Choice/icon.svg" +dest_files=["res://.godot/imported/icon.svg-372a9bfce8bea67fff1988d7acf09a9a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Choice/index.gd b/godot/addons/dialogic/Modules/Choice/index.gd new file mode 100644 index 0000000..23d0ec2 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Choice/index.gd.uid b/godot/addons/dialogic/Modules/Choice/index.gd.uid new file mode 100644 index 0000000..ea40483 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/index.gd.uid @@ -0,0 +1 @@ +uid://bo0dqybasbqd diff --git a/godot/addons/dialogic/Modules/Choice/node_button_sound.gd b/godot/addons/dialogic/Modules/Choice/node_button_sound.gd new file mode 100644 index 0000000..3f6cdbe --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Choice/node_button_sound.gd.uid b/godot/addons/dialogic/Modules/Choice/node_button_sound.gd.uid new file mode 100644 index 0000000..e40f8b2 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/node_button_sound.gd.uid @@ -0,0 +1 @@ +uid://b1stj4ljd2vo7 diff --git a/godot/addons/dialogic/Modules/Choice/node_choice_button.gd b/godot/addons/dialogic/Modules/Choice/node_choice_button.gd new file mode 100644 index 0000000..724f592 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/node_choice_button.gd @@ -0,0 +1,120 @@ +class_name DialogicNode_ChoiceButton +extends Button +## This button allows the player to make a choice in the Dialogic system. +## +## When a choice is reached Dialogic will automatically show ChoiceButtons +## and call their [code]_load_info()[/code] method which will display the choices. +## You will need to ensure that enough choice buttons are available in the tree +## to display all choices.[br] +## +## [br] +## You can extend this node and implement some custom logic by overwriting +## the [code]_load_info(info:Dictionary)[/code] method. [br] +## [br] +## If you need RichText support, consider adding a RichTextLabel child and setting it as the [member text_node].[br] +## +## [br] +## DialogicChoiceButtons will grab the focus when hovered to avoid a confusing +## focus style being present for players who use the mouse.[br] +## To avoid the opposite situation, when the focus is changed by the player +## and a different button is still hovered the mouse pointer will be moved +## to the now focused button as well. + + +## Emitted when the choice is selected. Unless overridden, this is when the button or its shortcut is pressed. +signal choice_selected + + +## 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. +## This can be used to have a custom text rendering child, like a RichTextLabel. +@export var text_node: Node + + +func _ready() -> void: + add_to_group('dialogic_choice_button') + shortcut_in_tooltip = false + hide() + + # For players who use a mouse to make choices, mouse hover should grab focus. + # Otherwise the auto-focused button will always show a highlighted color when + # the mouse cursor is hovering on another button. + if not mouse_entered.is_connected(grab_focus): + mouse_entered.connect(grab_focus) + if not focus_entered.is_connected(_on_choice_button_focus_entred): + focus_entered.connect(_on_choice_button_focus_entred.bind(self)) + + +## Custom choice buttons can override this for specialized behavior when the choice button is pressed. +func _pressed(): + choice_selected.emit() + + +## Custom choice buttons can override this if their behavior should change +## based on event data. If the custom choice button does not override +## visibility, disabled-ness, nor the choice text, consider +## calling super(choice_info) at the start of the override. +## +## The choice_info Dictionary has the following keys: +## - event_index: The index of the choice event in the timeline. +## - button_index: The relative index of the choice (starts from 1). +## - visible: If the choice should be visible. +## - disabled: If the choice should be selectable. +## - text: The text of the choice. +## - visited_before: If the choice has been selected before. Only available is the History submodule is enabled. +## - *: Information from the event's additional info. +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 + + +## This method moves the mouse to the focused choice when the focus changes +## while a choice button was hovered. [br] +## For players who use many devices (mouse/keyboard/gamepad, etc) at the same time to make choices, +## a grabing-focus triggered by keyboard/gamepad should also change the mouse cursor's +## position otherwise two buttons will have highlighted color(one highlighted button +## triggered by mouse hover and another highlighted button triggered by other devices' choice). +func _on_choice_button_focus_entred(focused_button: Button): + var global_mouse_pos = get_global_mouse_position() + var focused_button_rect = focused_button.get_global_rect() + if focused_button_rect.has_point(global_mouse_pos): + return + # Only change mouse curor position when an unfocused button' rect has the cursor. + for node in get_tree().get_nodes_in_group('dialogic_choice_button'): + if node is Button: + if node != focused_button and node.get_global_rect().has_point(global_mouse_pos): + # We prefer to change only mouse_position.y or mouse_position.x to warp the + # mouse to the focused button's rect to achieve the best visual effect. + var modify_y_pos = Vector2(global_mouse_pos.x, focused_button.get_global_rect().get_center().y) + if focused_button_rect.has_point(modify_y_pos): + get_viewport().warp_mouse(modify_y_pos) + return + + var modify_x_pos = Vector2(focused_button.get_global_rect().get_center().x, global_mouse_pos.y) + if focused_button_rect.has_point(modify_x_pos): + get_viewport().warp_mouse(modify_x_pos) + return + + # Maybe the buttons are not aligned as vertically or horizontlly. + # Or perhaps the length difference between the two buttons is quite large. + # So we just make the cursor hover on the center of the focused button. + get_viewport().warp_mouse(focused_button.get_global_rect().get_center()) + return diff --git a/godot/addons/dialogic/Modules/Choice/node_choice_button.gd.uid b/godot/addons/dialogic/Modules/Choice/node_choice_button.gd.uid new file mode 100644 index 0000000..dbb12ef --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/node_choice_button.gd.uid @@ -0,0 +1 @@ +uid://bldt7xlfum7ov diff --git a/godot/addons/dialogic/Modules/Choice/settings_choices.gd b/godot/addons/dialogic/Modules/Choice/settings_choices.gd new file mode 100644 index 0000000..66e4482 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Choice/settings_choices.gd.uid b/godot/addons/dialogic/Modules/Choice/settings_choices.gd.uid new file mode 100644 index 0000000..70d9924 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/settings_choices.gd.uid @@ -0,0 +1 @@ +uid://dbbbq1hcbhmi2 diff --git a/godot/addons/dialogic/Modules/Choice/settings_choices.tscn b/godot/addons/dialogic/Modules/Choice/settings_choices.tscn new file mode 100644 index 0000000..195c5f8 --- /dev/null +++ b/godot/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" uid="uid://dbbbq1hcbhmi2" 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_xvnnc"] +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_xvnnc") + +[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/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd b/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd new file mode 100644 index 0000000..cbafed6 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd @@ -0,0 +1,301 @@ +extends DialogicSubsystem + +## Subsystem that manages showing and activating of choices. + +## Emitted when a choice button was pressed. Info includes the keys 'button_index', 'text', 'event_index'. +signal choice_selected(info:Dictionary) +## Emitted when a set of choices is reached and shown. +## Info includes the keys 'choices' (an array of dictionaries with infos on all the choices). +signal question_shown(info:Dictionary) + +## Contains information on the latest question. +var last_question_info := {} + +## The delay between the text finishing revealing and the choices appearing +var reveal_delay := 0.0 +## If true the player has to click to reveal choices when they are reached +var reveal_by_input := false +## The delay between the choices becoming visible and being clickable. Can prevent accidental selection. +var block_delay := 0.2 +## If true, the first (top-most) choice will be focused +var autofocus_first_choice := true +## If true the dialogic input action is used to trigger choices. +## However mouse events will be ignored no matter what. +var use_input_action := false + +enum FalseBehaviour {HIDE=0, DISABLE=1} +## The behaviour of choices with a false condition and else_action set to DEFAULT. +var default_false_behaviour := FalseBehaviour.HIDE + +enum HotkeyBehaviour {NONE, NUMBERS} +## Will add some hotkeys to the choices if different then HotkeyBehaviour.NONE. +var hotkey_behaviour := HotkeyBehaviour.NONE + + +### INTERNALS + +## Used to block choices from being clicked for a couple of seconds (if delay is set in settings). +var _choice_blocker := Timer.new() + +#region STATE +#################################################################################################### + +func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void: + hide_all_choices() + + +func _ready() -> void: + _choice_blocker.one_shot = true + DialogicUtil.update_timer_process_callback(_choice_blocker) + add_child(_choice_blocker) + + reveal_delay = float(ProjectSettings.get_setting('dialogic/choices/reveal_delay', reveal_delay)) + reveal_by_input = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', reveal_by_input) + block_delay = ProjectSettings.get_setting('dialogic/choices/delay', block_delay) + autofocus_first_choice = ProjectSettings.get_setting('dialogic/choices/autofocus_first', autofocus_first_choice) + hotkey_behaviour = ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', hotkey_behaviour) + default_false_behaviour = ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', default_false_behaviour) + + +func post_install() -> void: + dialogic.Inputs.dialogic_action.connect(_on_dialogic_action) + +#endregion + + +#region MAIN METHODS +#################################################################################################### + +## Hides all choice buttons. +func hide_all_choices() -> void: + for node in get_tree().get_nodes_in_group('dialogic_choice_button'): + node.hide() + if node.choice_selected.is_connected(_on_choice_selected): + node.choice_selected.disconnect(_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, 1) + + 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) + last_question_info['choices'].append(choice_info['text']) + + 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(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.choice_selected.is_connected(_on_choice_selected): + node.choice_selected.disconnect(_on_choice_selected) + node.choice_selected.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 focus_choice(button_index:int) -> void: + var node: DialogicNode_ChoiceButton = get_choice_button(button_index) + if node: + node.grab_focus() + + +func select_choice(button_index:int) -> void: + var node: DialogicNode_ChoiceButton = get_choice_button(button_index) + if node: + node.choice_selected.emit() + + +func select_focused_choice() -> void: + if get_viewport().gui_get_focus_owner() is DialogicNode_ChoiceButton: + (get_viewport().gui_get_focus_owner() as DialogicNode_ChoiceButton).choice_selected.emit() + + +func get_choice_button(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) + + +## Returns the indexes of the choice events related to the current question. +func get_current_choice_indexes() -> Array: + var choices := [] + var index := dialogic.current_event_idx-1 + while true: + index += 1 + if index >= len(dialogic.current_timeline_events): + break + + var event: DialogicEvent = dialogic.current_timeline_events[index] + if event is DialogicChoiceEvent: + choices.append(index) + index = event.get_end_branch_index() + else: + break + + return choices + + +## Forward the dialogic action to the focused button +func _on_dialogic_action() -> void: + if use_input_action and not dialogic.Inputs.input_was_mouse_input: + select_focused_choice() + + +#endregion + + +#region HELPERS +#################################################################################################### + +## Returns `true` if the given index is a text event before a question or the first choice event of a question. +func is_question(index:int) -> bool: + var event: DialogicEvent = dialogic.current_timeline_events[index] + if event is DialogicTextEvent: + if len(dialogic.current_timeline_events)-1 != index: + var next_event: DialogicEvent = dialogic.current_timeline_events[index+1] + if next_event is DialogicChoiceEvent: + return true + + if event is DialogicChoiceEvent: + if index == 0: + return true + var prev_event: DialogicEvent = dialogic.current_timeline_events[index-1] + if not prev_event is DialogicEndBranchEvent: + return true + var prev_event_opener: DialogicEvent = dialogic.current_timeline_events[prev_event.get_opening_index()] + if prev_event_opener is DialogicChoiceEvent: + return false + else: + return true + + return false + +#endregion diff --git a/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd.uid b/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd.uid new file mode 100644 index 0000000..1f06988 --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/subsystem_choices.gd.uid @@ -0,0 +1 @@ +uid://cewv4d3aw0kj3 diff --git a/godot/addons/dialogic/Modules/Choice/ui_choice_end.gd b/godot/addons/dialogic/Modules/Choice/ui_choice_end.gd new file mode 100644 index 0000000..acc5d82 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Choice/ui_choice_end.gd.uid b/godot/addons/dialogic/Modules/Choice/ui_choice_end.gd.uid new file mode 100644 index 0000000..04c99df --- /dev/null +++ b/godot/addons/dialogic/Modules/Choice/ui_choice_end.gd.uid @@ -0,0 +1 @@ +uid://d28x7h2ufh3dd diff --git a/godot/addons/dialogic/Modules/Choice/ui_choice_end.tscn b/godot/addons/dialogic/Modules/Choice/ui_choice_end.tscn new file mode 100644 index 0000000..835a65c --- /dev/null +++ b/godot/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" uid="uid://d28x7h2ufh3dd" 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/godot/addons/dialogic/Modules/Clear/clear_background.svg b/godot/addons/dialogic/Modules/Clear/clear_background.svg new file mode 100644 index 0000000..9bb6889 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_background.svg @@ -0,0 +1,2 @@ + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_background.svg.import b/godot/addons/dialogic/Modules/Clear/clear_background.svg.import new file mode 100644 index 0000000..67f5a98 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_background.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brc74uf1yvlfr" +path="res://.godot/imported/clear_background.svg-32f657ae646e5867a8ceb0543ae207de.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_background.svg" +dest_files=["res://.godot/imported/clear_background.svg-32f657ae646e5867a8ceb0543ae207de.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/clear_characters.svg b/godot/addons/dialogic/Modules/Clear/clear_characters.svg new file mode 100644 index 0000000..868afba --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_characters.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_characters.svg.import b/godot/addons/dialogic/Modules/Clear/clear_characters.svg.import new file mode 100644 index 0000000..23cfa81 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_characters.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3b85xt58e8ej" +path="res://.godot/imported/clear_characters.svg-58481eec0869d46b2d90f2102edd1687.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_characters.svg" +dest_files=["res://.godot/imported/clear_characters.svg-58481eec0869d46b2d90f2102edd1687.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/clear_music.svg b/godot/addons/dialogic/Modules/Clear/clear_music.svg new file mode 100644 index 0000000..e992063 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_music.svg @@ -0,0 +1,2 @@ + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_music.svg.import b/godot/addons/dialogic/Modules/Clear/clear_music.svg.import new file mode 100644 index 0000000..043a2da --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_music.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c70ten8d456b5" +path="res://.godot/imported/clear_music.svg-ab19a5f3dc85de8ba17b7c720b838a73.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_music.svg" +dest_files=["res://.godot/imported/clear_music.svg-ab19a5f3dc85de8ba17b7c720b838a73.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/clear_positions.svg b/godot/addons/dialogic/Modules/Clear/clear_positions.svg new file mode 100644 index 0000000..78e5ac7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_positions.svg @@ -0,0 +1,2 @@ + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_positions.svg.import b/godot/addons/dialogic/Modules/Clear/clear_positions.svg.import new file mode 100644 index 0000000..a01717d --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_positions.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rs76jb858p8e" +path="res://.godot/imported/clear_positions.svg-9c6ef9c2e28a63b87a3c7d0ed63620d8.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_positions.svg" +dest_files=["res://.godot/imported/clear_positions.svg-9c6ef9c2e28a63b87a3c7d0ed63620d8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/clear_style.svg b/godot/addons/dialogic/Modules/Clear/clear_style.svg new file mode 100644 index 0000000..d9ca782 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_style.svg @@ -0,0 +1,2 @@ + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_style.svg.import b/godot/addons/dialogic/Modules/Clear/clear_style.svg.import new file mode 100644 index 0000000..d1f6c7e --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_style.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clpxmppelspva" +path="res://.godot/imported/clear_style.svg-ab3288e6e47f45c4e12ce1862403a99c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_style.svg" +dest_files=["res://.godot/imported/clear_style.svg-ab3288e6e47f45c4e12ce1862403a99c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/clear_textbox.svg b/godot/addons/dialogic/Modules/Clear/clear_textbox.svg new file mode 100644 index 0000000..6383b72 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_textbox.svg @@ -0,0 +1,2 @@ + + diff --git a/godot/addons/dialogic/Modules/Clear/clear_textbox.svg.import b/godot/addons/dialogic/Modules/Clear/clear_textbox.svg.import new file mode 100644 index 0000000..e658a4f --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/clear_textbox.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co8t6o06m76ed" +path="res://.godot/imported/clear_textbox.svg-04935e3d82350d24a197adcb47df6f57.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/clear_textbox.svg" +dest_files=["res://.godot/imported/clear_textbox.svg-04935e3d82350d24a197adcb47df6f57.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/event_clear.gd b/godot/addons/dialogic/Modules/Clear/event_clear.gd new file mode 100644 index 0000000..9b1b3d7 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/event_clear.gd @@ -0,0 +1,114 @@ +@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") and dialogic.Text.is_textbox_visible(): + dialogic.Text.update_dialog_text('') + dialogic.Text.hide_textbox(final_time == 0) + 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'): + dialogic.Audio.stop_all_one_shot_sounds() + if dialogic.Audio.is_any_channel_playing(): + dialogic.Audio.stop_all_channels(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 Audio'}) + 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/godot/addons/dialogic/Modules/Clear/event_clear.gd.uid b/godot/addons/dialogic/Modules/Clear/event_clear.gd.uid new file mode 100644 index 0000000..7f8fe27 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/event_clear.gd.uid @@ -0,0 +1 @@ +uid://7aikid38is1o diff --git a/godot/addons/dialogic/Modules/Clear/icon.png b/godot/addons/dialogic/Modules/Clear/icon.png new file mode 100644 index 0000000..d975480 Binary files /dev/null and b/godot/addons/dialogic/Modules/Clear/icon.png differ diff --git a/godot/addons/dialogic/Modules/Clear/icon.png.import b/godot/addons/dialogic/Modules/Clear/icon.png.import new file mode 100644 index 0000000..eebcb46 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://p1dnehrtjq1o" +path="res://.godot/imported/icon.png-b4986c88bf1e20891dc480c8f4703ca4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Clear/icon.png" +dest_files=["res://.godot/imported/icon.png-b4986c88bf1e20891dc480c8f4703ca4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Clear/index.gd b/godot/addons/dialogic/Modules/Clear/index.gd new file mode 100644 index 0000000..4f18d14 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Clear/index.gd.uid b/godot/addons/dialogic/Modules/Clear/index.gd.uid new file mode 100644 index 0000000..e637770 --- /dev/null +++ b/godot/addons/dialogic/Modules/Clear/index.gd.uid @@ -0,0 +1 @@ +uid://cyftouhvjfun2 diff --git a/godot/addons/dialogic/Modules/Comment/event_comment.gd b/godot/addons/dialogic/Modules/Comment/event_comment.gd new file mode 100644 index 0000000..ad0b1dd --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Comment/event_comment.gd.uid b/godot/addons/dialogic/Modules/Comment/event_comment.gd.uid new file mode 100644 index 0000000..ba26e81 --- /dev/null +++ b/godot/addons/dialogic/Modules/Comment/event_comment.gd.uid @@ -0,0 +1 @@ +uid://dbcesveorhh6m diff --git a/godot/addons/dialogic/Modules/Comment/icon.png b/godot/addons/dialogic/Modules/Comment/icon.png new file mode 100644 index 0000000..417537c Binary files /dev/null and b/godot/addons/dialogic/Modules/Comment/icon.png differ diff --git a/godot/addons/dialogic/Modules/Comment/icon.png.import b/godot/addons/dialogic/Modules/Comment/icon.png.import new file mode 100644 index 0000000..8fc2a38 --- /dev/null +++ b/godot/addons/dialogic/Modules/Comment/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hbx10ru6sxfb" +path="res://.godot/imported/icon.png-fab76b8a5886dac0012cf73c317dbee4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Comment/icon.png" +dest_files=["res://.godot/imported/icon.png-fab76b8a5886dac0012cf73c317dbee4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Comment/index.gd b/godot/addons/dialogic/Modules/Comment/index.gd new file mode 100644 index 0000000..53ceb32 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Comment/index.gd.uid b/godot/addons/dialogic/Modules/Comment/index.gd.uid new file mode 100644 index 0000000..47a801f --- /dev/null +++ b/godot/addons/dialogic/Modules/Comment/index.gd.uid @@ -0,0 +1 @@ +uid://b27oui35aoff4 diff --git a/godot/addons/dialogic/Modules/Condition/event_condition.gd b/godot/addons/dialogic/Modules/Condition/event_condition.gd new file mode 100644 index 0000000..936a690 --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/event_condition.gd @@ -0,0 +1,137 @@ +@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: + dialogic.current_event_idx = get_end_branch_index() + + finish() + + +func _is_branch_starter() -> 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/godot/addons/dialogic/Modules/Condition/event_condition.gd.uid b/godot/addons/dialogic/Modules/Condition/event_condition.gd.uid new file mode 100644 index 0000000..91be9b8 --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/event_condition.gd.uid @@ -0,0 +1 @@ +uid://g8gaor7ewun6 diff --git a/godot/addons/dialogic/Modules/Condition/icon.svg b/godot/addons/dialogic/Modules/Condition/icon.svg new file mode 100644 index 0000000..805e95d --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/Condition/icon.svg.import b/godot/addons/dialogic/Modules/Condition/icon.svg.import new file mode 100644 index 0000000..d792f29 --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bco4t1ey0fkpm" +path="res://.godot/imported/icon.svg-502d45c064cc30f576b7b105a01357ce.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Condition/icon.svg" +dest_files=["res://.godot/imported/icon.svg-502d45c064cc30f576b7b105a01357ce.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Condition/index.gd b/godot/addons/dialogic/Modules/Condition/index.gd new file mode 100644 index 0000000..c84f897 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Condition/index.gd.uid b/godot/addons/dialogic/Modules/Condition/index.gd.uid new file mode 100644 index 0000000..b3b4e52 --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/index.gd.uid @@ -0,0 +1 @@ +uid://bsn2cv832qlam diff --git a/godot/addons/dialogic/Modules/Condition/ui_condition_end.gd b/godot/addons/dialogic/Modules/Condition/ui_condition_end.gd new file mode 100644 index 0000000..4a5ac1d --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Condition/ui_condition_end.gd.uid b/godot/addons/dialogic/Modules/Condition/ui_condition_end.gd.uid new file mode 100644 index 0000000..4c3145c --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/ui_condition_end.gd.uid @@ -0,0 +1 @@ +uid://hiahx6lrlm17 diff --git a/godot/addons/dialogic/Modules/Condition/ui_condition_end.tscn b/godot/addons/dialogic/Modules/Condition/ui_condition_end.tscn new file mode 100644 index 0000000..00c36ba --- /dev/null +++ b/godot/addons/dialogic/Modules/Condition/ui_condition_end.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://dpt6fwem03sqw"] + +[ext_resource type="Script" uid="uid://hiahx6lrlm17" 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/godot/addons/dialogic/Modules/Core/event_end_branch.gd b/godot/addons/dialogic/Modules/Core/event_end_branch.gd new file mode 100644 index 0000000..306adc5 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/event_end_branch.gd @@ -0,0 +1,81 @@ +@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() + + +## Returns the index of the first event that +## - is on the same "indentation" +## - is not a branching event (unless it is a branch starter) +func find_next_index() -> int: + var idx: int = dialogic.current_event_idx + while true: + idx += 1 + var event: DialogicEvent = dialogic.current_timeline.get_event(idx) + if not event: + return idx + + if event.can_contain_events: + if event._is_branch_starter(): + break + else: + idx = event.get_end_branch_index() + break + else: + break + + return idx + + +func get_opening_index() -> int: + var index: int = dialogic.current_timeline_events.find(self) + while true: + index -= 1 + if index < 0: + break + var event: DialogicEvent = dialogic.current_timeline_events[index] + if event is DialogicEndBranchEvent: + index = event.get_opening_index() + elif event.can_contain_events: + return index + return 0 + +#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/godot/addons/dialogic/Modules/Core/event_end_branch.gd.uid b/godot/addons/dialogic/Modules/Core/event_end_branch.gd.uid new file mode 100644 index 0000000..892b16c --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/event_end_branch.gd.uid @@ -0,0 +1 @@ +uid://3wq6lhrhifgj diff --git a/godot/addons/dialogic/Modules/Core/icon.png b/godot/addons/dialogic/Modules/Core/icon.png new file mode 100644 index 0000000..d975480 Binary files /dev/null and b/godot/addons/dialogic/Modules/Core/icon.png differ diff --git a/godot/addons/dialogic/Modules/Core/icon.png.import b/godot/addons/dialogic/Modules/Core/icon.png.import new file mode 100644 index 0000000..3e4e832 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0h5mknqmos7r" +path="res://.godot/imported/icon.png-6a95c038153eac5ac32780e8a0c1529b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Core/icon.png" +dest_files=["res://.godot/imported/icon.png-6a95c038153eac5ac32780e8a0c1529b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Core/index.gd b/godot/addons/dialogic/Modules/Core/index.gd new file mode 100644 index 0000000..4842c34 --- /dev/null +++ b/godot/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, "order":20}, + ] diff --git a/godot/addons/dialogic/Modules/Core/index.gd.uid b/godot/addons/dialogic/Modules/Core/index.gd.uid new file mode 100644 index 0000000..ab8c9c1 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/index.gd.uid @@ -0,0 +1 @@ +uid://hdi17v8hqb0p diff --git a/godot/addons/dialogic/Modules/Core/subsystem_animation.gd b/godot/addons/dialogic/Modules/Core/subsystem_animation.gd new file mode 100644 index 0000000..4769134 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Core/subsystem_animation.gd.uid b/godot/addons/dialogic/Modules/Core/subsystem_animation.gd.uid new file mode 100644 index 0000000..f107f50 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/subsystem_animation.gd.uid @@ -0,0 +1 @@ +uid://drgk63svehoum diff --git a/godot/addons/dialogic/Modules/Core/subsystem_expression.gd b/godot/addons/dialogic/Modules/Core/subsystem_expression.gd new file mode 100644 index 0000000..763d741 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/subsystem_expression.gd @@ -0,0 +1,103 @@ +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)\}|true\b|false\b))(?(\\\]|\\\/|[^\]\/])*)(\/(?(\\\]|[^\]])*))?\]") +func modifier_condition(text:String) -> String: + for find in condition_modifier_regex.search_all(text): + var insert := "" + if execute_condition(find.get_string("condition")): + insert = find.get_string("truetext") + else: + insert = find.get_string("falsetext") + + # Avoid double spaces at the insert position if the insert is empty. + if not insert.strip_edges() and " "+find.get_string()+" " in text: + text = text.replace(find.get_string()+" ", insert.strip_edges()) + else: + text = text.replace(find.get_string(), insert.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/godot/addons/dialogic/Modules/Core/subsystem_expression.gd.uid b/godot/addons/dialogic/Modules/Core/subsystem_expression.gd.uid new file mode 100644 index 0000000..de98ff1 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/subsystem_expression.gd.uid @@ -0,0 +1 @@ +uid://cn3846afxgeux diff --git a/godot/addons/dialogic/Modules/Core/subsystem_input.gd b/godot/addons/dialogic/Modules/Core/subsystem_input.gd new file mode 100644 index 0000000..8b31564 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/subsystem_input.gd @@ -0,0 +1,217 @@ +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 input_was_mouse_input := 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() + input_was_mouse_input = false + + +## 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 + input_was_mouse_input = false + 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: + return + if get_tree().get_nodes_in_group('dialogic_input').any(func(node):return node.is_visible_in_tree()): + return + input_was_mouse_input = true + 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: + input_was_mouse_input = true + handle_input() + + +func is_input_blocked() -> bool: + return input_block_timer.time_left > 0.0 and not auto_skip.enabled + + +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/godot/addons/dialogic/Modules/Core/subsystem_input.gd.uid b/godot/addons/dialogic/Modules/Core/subsystem_input.gd.uid new file mode 100644 index 0000000..29cc846 --- /dev/null +++ b/godot/addons/dialogic/Modules/Core/subsystem_input.gd.uid @@ -0,0 +1 @@ +uid://crdsvy044intj diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd new file mode 100644 index 0000000..af6878c --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd.uid new file mode 100644 index 0000000..b39227f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.gd.uid @@ -0,0 +1 @@ +uid://yivwmkfwrvfr diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/default_layout_base.tscn new file mode 100644 index 0000000..da9159f --- /dev/null +++ b/godot/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" uid="uid://yivwmkfwrvfr" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_Default/part_config.cfg new file mode 100644 index 0000000..99751e8 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/part_config.cfg new file mode 100644 index 0000000..7b1adc9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd new file mode 100644 index 0000000..604b46b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd @@ -0,0 +1,95 @@ +@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 + if not bubble_to_use.visible: + 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd.uid new file mode 100644 index 0000000..b1e68a3 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.gd.uid @@ -0,0 +1 @@ +uid://v8guu7n6gv8a diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Base_TextBubble/text_bubble_base.tscn new file mode 100644 index 0000000..9b08a54 --- /dev/null +++ b/godot/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" uid="uid://v8guu7n6gv8a" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg new file mode 100644 index 0000000..3e0b300 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import new file mode 100644 index 0000000..0897452 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cm8w3lr5o038d" +path="res://.godot/imported/background_layer_icon.svg-021cd7ab7c646ee621f9b89b8dfc9d60.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/background_layer_icon.svg" +dest_files=["res://.godot/imported/background_layer_icon.svg-021cd7ab7c646ee621f9b89b8dfc9d60.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd new file mode 100644 index 0000000..9162300 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd @@ -0,0 +1,2 @@ +@tool +extends DialogicLayoutLayer diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd.uid new file mode 100644 index 0000000..6006d13 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd.uid @@ -0,0 +1 @@ +uid://bqdylb4maacf0 diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.tscn new file mode 100644 index 0000000..7e2bdcb --- /dev/null +++ b/godot/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" uid="uid://bqdylb4maacf0" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/full_background_layer.gd" id="1_tu40u"] +[ext_resource type="Script" uid="uid://oxcjhq2817c7" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/part_config.cfg new file mode 100644 index 0000000..2dd606a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png new file mode 100644 index 0000000..9c42d7d Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import new file mode 100644 index 0000000..025759d --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3hagnfudj3nk" +path="res://.godot/imported/preview.png-4e6ca7ca01626d69870923f306b0d377.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_FullBackground/preview.png" +dest_files=["res://.godot/imported/preview.png-4e6ca7ca01626d69870923f306b0d377.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd new file mode 100644 index 0000000..bcc1eef --- /dev/null +++ b/godot/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: Font + 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd.uid new file mode 100644 index 0000000..2204abd --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.gd.uid @@ -0,0 +1 @@ +uid://o2cnjam8bomr diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/glossary_popup_layer.tscn new file mode 100644 index 0000000..933fc6e --- /dev/null +++ b/godot/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" uid="uid://o2cnjam8bomr" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/part_config.cfg new file mode 100644 index 0000000..e7e772b --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg new file mode 100644 index 0000000..bedd4a7 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import new file mode 100644 index 0000000..3c17af6 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3q86ma7r6l57" +path="res://.godot/imported/popup_glossary_layer_icon.svg-4af96bdb70714a5289a4ffe42cf8f357.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/popup_glossary_layer_icon.svg" +dest_files=["res://.godot/imported/popup_glossary_layer_icon.svg-4af96bdb70714a5289a4ffe42cf8f357.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png new file mode 100644 index 0000000..8dc08bb Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import new file mode 100644 index 0000000..06b40ed --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dydv1g180rqex" +path="res://.godot/imported/preview.png-573567ffd6162e40c78fe5aca0af73c9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Glossary/preview.png" +dest_files=["res://.godot/imported/preview.png-573567ffd6162e40c78fe5aca0af73c9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd new file mode 100644 index 0000000..83fef50 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd.uid new file mode 100644 index 0000000..a06b4bf --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.gd.uid @@ -0,0 +1 @@ +uid://67t6pr2eatac diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/example_history_item.tscn new file mode 100644 index 0000000..4ae76d4 --- /dev/null +++ b/godot/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" uid="uid://67t6pr2eatac" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg new file mode 100644 index 0000000..4d21936 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import new file mode 100644 index 0000000..a1619ef --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvq7qbkngeoud" +path="res://.godot/imported/history_icon.svg-0be9d23e65368ea3983968e4ce0e43d3.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_icon.svg" +dest_files=["res://.godot/imported/history_icon.svg-0be9d23e65368ea3983968e4ce0e43d3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd new file mode 100644 index 0000000..d420021 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd.uid new file mode 100644 index 0000000..5641a01 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.gd.uid @@ -0,0 +1 @@ +uid://gv8mqbjor5nu diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/history_layer.tscn new file mode 100644 index 0000000..030e357 --- /dev/null +++ b/godot/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" uid="uid://gv8mqbjor5nu" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/part_config.cfg new file mode 100644 index 0000000..fd104d9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png new file mode 100644 index 0000000..e22843f Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import new file mode 100644 index 0000000..936047e --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7ep7q40d2amu" +path="res://.godot/imported/preview.png-b328f7e30aebed2adb4720cdbf791362.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_History/preview.png" +dest_files=["res://.godot/imported/preview.png-b328f7e30aebed2adb4720cdbf791362.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd new file mode 100644 index 0000000..e48a1f8 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd.uid new file mode 100644 index 0000000..b70af5f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd.uid @@ -0,0 +1 @@ +uid://dwml743wmb7mn diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.tscn new file mode 100644 index 0000000..e66cc34 --- /dev/null +++ b/godot/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" uid="uid://dwml743wmb7mn" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/full_advance_input_layer.gd" id="1_3cmha"] +[ext_resource type="Script" uid="uid://ctog34kdg222q" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg new file mode 100644 index 0000000..512ca5b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import new file mode 100644 index 0000000..0dfa079 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bv38272320783" +path="res://.godot/imported/input_layer_icon.svg-3f439e08e8c66cbf3522ce60714f7588.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/input_layer_icon.svg" +dest_files=["res://.godot/imported/input_layer_icon.svg-3f439e08e8c66cbf3522ce60714f7588.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/part_config.cfg new file mode 100644 index 0000000..89a7679 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png new file mode 100644 index 0000000..4cb5144 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import new file mode 100644 index 0000000..b404155 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6wifoui7fdf1" +path="res://.godot/imported/preview.png-c20c74f5253522c0ea9ec9a21fcd804d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Input/preview.png" +dest_files=["res://.godot/imported/preview.png-c20c74f5253522c0ea9ec9a21fcd804d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/default_stylebox.tres new file mode 100644 index 0000000..57300ef --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/part_config.cfg new file mode 100644 index 0000000..e3fc33c --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png new file mode 100644 index 0000000..1c61ce8 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import new file mode 100644 index 0000000..cec9777 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s5s4lmhfnav2" +path="res://.godot/imported/preview.png-dcc8befd9aebdd8b4c988971a734c72f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/preview.png" +dest_files=["res://.godot/imported/preview.png-dcc8befd9aebdd8b4c988971a734c72f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg new file mode 100644 index 0000000..02b5b02 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import new file mode 100644 index 0000000..ea16b0c --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0v3akss47tgy" +path="res://.godot/imported/speaker-textbox-icon.svg-e67964032c31cfdc4bf5a376d48a985e.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker-textbox-icon.svg" +dest_files=["res://.godot/imported/speaker-textbox-icon.svg-e67964032c31cfdc4bf5a376d48a985e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd new file mode 100644 index 0000000..61e3714 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd.uid new file mode 100644 index 0000000..080aec0 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd.uid @@ -0,0 +1 @@ +uid://bk84r61kckpxa diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/textbox_with_speaker_portrait.tscn new file mode 100644 index 0000000..ae6a3b3 --- /dev/null +++ b/godot/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" uid="uid://d0ptqnbudhkyj" path="res://addons/dialogic/Modules/Character/node_portrait_container.gd" id="1_4jxq7"] +[ext_resource type="Script" uid="uid://bk84r61kckpxa" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_SpeakerPortraitTextbox/speaker_portrait_textbox_layer.gd" id="1_7jt4d"] +[ext_resource type="Script" uid="uid://bak74s0kcr0ao" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="2_y0h34"] +[ext_resource type="Script" uid="uid://drhfq6rmdeuri" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_11puy"] +[ext_resource type="Script" uid="uid://dpv2dfiv5dhmr" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/part_config.cfg new file mode 100644 index 0000000..b6f8add --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png new file mode 100644 index 0000000..8b591b6 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import new file mode 100644 index 0000000..a0587e5 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn2b53n51smoo" +path="res://.godot/imported/preview.png-bcceb5d2b7f92c1d0f818071294dc895.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/preview.png" +dest_files=["res://.godot/imported/preview.png-bcceb5d2b7f92c1d0f818071294dc895.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd new file mode 100644 index 0000000..bd853aa --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd.uid new file mode 100644 index 0000000..e67f09f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd.uid @@ -0,0 +1 @@ +uid://cjdvjwjrj86sj diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.tscn new file mode 100644 index 0000000..4198bd7 --- /dev/null +++ b/godot/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" uid="uid://cjdvjwjrj86sj" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer.gd" id="1_7ahrn"] +[ext_resource type="Script" uid="uid://biqpjv0qyefvf" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg new file mode 100644 index 0000000..9f8d592 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import new file mode 100644 index 0000000..181a884 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1irtbrawo1jp" +path="res://.godot/imported/text_input_layer_icon.svg-5a1e8bca317bf45f6805d82812407215.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_TextInput/text_input_layer_icon.svg" +dest_files=["res://.godot/imported/text_input_layer_icon.svg-5a1e8bca317bf45f6805d82812407215.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/part_config.cfg new file mode 100644 index 0000000..203a685 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png new file mode 100644 index 0000000..54b08d6 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import new file mode 100644 index 0000000..086ab46 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x0kxo1qd4jqf" +path="res://.godot/imported/preview.png-136e526350ab989f1e1d2ba13d756aaa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/preview.png" +dest_files=["res://.godot/imported/preview.png-136e526350ab989f1e1d2ba13d756aaa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader new file mode 100644 index 0000000..c1e348f --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader.uid new file mode 100644 index 0000000..f6eb360 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/speech_bubble.gdshader.uid @@ -0,0 +1 @@ +uid://cs2o12y1i6cti diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd new file mode 100644 index 0000000..ff7913c --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd @@ -0,0 +1,246 @@ +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: + set(val): + node_to_point_at = val + base_position = get_speaker_canvas_position() + base_direction * safe_zone + position = base_position + +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: + set_process(false) + scale = Vector2.ZERO + modulate.a = 0.0 + + tail.points = [] + bubble_rect = Rect2(0,0,2,2) + + base_position = get_speaker_canvas_position() + base_direction * safe_zone + 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(await 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 + + # Avoid choice_container's flickering(because some ticks will happen in + # `await get_base_content_size()` which will make choice_container exist + # at its old position for several tens of milliseconds). + choice_container.modulate.a = 0 + + var content_size := await 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) + + # Now, choice_container has changed to its new position, so we can make it + # actually show up. + choice_container.modulate.a = 1 + + +func get_base_content_size() -> Vector2: + var font: Font = text.get_theme_font(&"normal_font") + var text_width = font.get_multiline_string_size( + text.get_parsed_text(), + HORIZONTAL_ALIGNMENT_LEFT, + max_width, + text.get_theme_font_size(&"normal_font_size") + ).x + + # Let text use content's width, and let text auto shrink height to its content. + text.size = Vector2(text_width, 0) + await get_tree().process_frame + + # Don't know why text.size.y != content's height, + # so we re-set text.size.y to 0 to let text shrink to its content again. + # Finally works this time. + text.size.y = 0 + await get_tree().process_frame + return text.size + + +func add_choice_container(node:Container, alignment:=FlowContainer.ALIGNMENT_BEGIN, choices_button_path:="", maximum_choices:=5) -> 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 + + var choices_button: PackedScene = null + if not choices_button_path.is_empty(): + if ResourceLoader.exists(choices_button_path): + choices_button = (load(choices_button_path) as PackedScene) + else: + printerr("[Dialogic] Unable to load custom choice button from ", choices_button_path) + + for i:int in range(maximum_choices): + var new_button : DialogicNode_ChoiceButton + if choices_button == null: + new_button = DialogicNode_ChoiceButton.new() + else: + new_button = (choices_button.instantiate() as DialogicNode_ChoiceButton) + choice_container.add_child(new_button) + 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 + + +## Changes the property of mouse filter of the bubble and its children (text and label). +func change_mouse_filter(mouse_filter: Control.MouseFilter) -> void: + mouse_filter = mouse_filter + text.mouse_filter = mouse_filter + name_label_box.mouse_filter = mouse_filter + name_label_holder.mouse_filter = mouse_filter diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd.uid new file mode 100644 index 0000000..354cf8b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd.uid @@ -0,0 +1 @@ +uid://bminl7x3r40vc diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader new file mode 100644 index 0000000..60ebcee --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader.uid new file mode 100644 index 0000000..1a32b4d --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader.uid @@ -0,0 +1 @@ +uid://byskpb7ypvuab diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn new file mode 100644 index 0000000..010ec4b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.tscn @@ -0,0 +1,112 @@ +[gd_scene load_steps=11 format=3 uid="uid://dlx7jcvm52tyw"] + +[ext_resource type="Script" uid="uid://bminl7x3r40vc" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gd" id="1_jdhpk"] +[ext_resource type="Shader" uid="uid://byskpb7ypvuab" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble.gdshader" id="2_1mhvf"] +[ext_resource type="Script" uid="uid://drhfq6rmdeuri" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_syv35"] +[ext_resource type="Script" uid="uid://dpv2dfiv5dhmr" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="4_7bm4b"] +[ext_resource type="Script" uid="uid://bak74s0kcr0ao" 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/deformation_sampler = SubResource("NoiseTexture2D_kr7hw") +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 + +[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) +fit_content = true +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd new file mode 100644 index 0000000..394095d --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd @@ -0,0 +1,191 @@ +@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 var behaviour_mouse_filter: Control.MouseFilter + +@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 = "" + +@export_subgroup('Behavior') +@export var maximum_choices: int = 5 +@export_file('*.tscn') var choices_custom_button: 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(&"italics_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 + bubble.change_mouse_filter(behaviour_mouse_filter) + + + ## 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, choices_custom_button, maximum_choices) + else: + bubble.add_choice_container(HFlowContainer.new(), choices_layout_alignment, choices_custom_button, maximum_choices) + + 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd.uid new file mode 100644 index 0000000..51bd1b9 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.gd.uid @@ -0,0 +1 @@ +uid://dcae6nsh8fgtp diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer.tscn new file mode 100644 index 0000000..141e298 --- /dev/null +++ b/godot/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" uid="uid://dcae6nsh8fgtp" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg new file mode 100644 index 0000000..da53086 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import new file mode 100644 index 0000000..d98f682 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgpyaea4qw8a5" +path="res://.godot/imported/text_bubble_layer_icon.svg-d46d5806bbf83c1dc50f8d7fc8dac67d.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_Textbubble/text_bubble_layer_icon.svg" +dest_files=["res://.godot/imported/text_bubble_layer_icon.svg-d46d5806bbf83c1dc50f8d7fc8dac67d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_focus.tres new file mode 100644 index 0000000..8293f0c --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_hover.tres new file mode 100644 index 0000000..b67de21 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choice_panel_normal.tres new file mode 100644 index 0000000..1fe561b --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg new file mode 100644 index 0000000..bc00f8f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import new file mode 100644 index 0000000..f382b1e --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xcxex6r1v6xk" +path="res://.godot/imported/choices_layer_icon.svg-2f676308da08dddba733cb2bfba8fc69.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/choices_layer_icon.svg" +dest_files=["res://.godot/imported/choices_layer_icon.svg-2f676308da08dddba733cb2bfba8fc69.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/part_config.cfg new file mode 100644 index 0000000..8dbc1cd --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png new file mode 100644 index 0000000..20879db Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import new file mode 100644 index 0000000..7242d06 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://h1qtatxnadhj" +path="res://.godot/imported/preview.png-ae89c99370d002f2ecf00af8e270d88c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/preview.png" +dest_files=["res://.godot/imported/preview.png-ae89c99370d002f2ecf00af8e270d88c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd new file mode 100644 index 0000000..2eb30f2 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd @@ -0,0 +1,143 @@ +@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" + +@export_group('Choices') +@export_subgroup('Behavior') +@export var maximum_choices: int = 10 +@export_file('*.tscn') var choices_custom_button: String = "" + +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) + + var choices : Control = get_choices() + choices.add_theme_constant_override(&"separation", boxes_v_separation) + self.position = boxes_offset + + # replace choice buttons and apply settings + for child: Node in choices.get_children(): + if child is DialogicNode_ChoiceButton: + child.queue_free() + + var choices_button: PackedScene = null + if not choices_custom_button.is_empty(): + if ResourceLoader.exists(choices_custom_button): + choices_button = (load(choices_custom_button) as PackedScene) + else: + printerr("[Dialogic] Unable to load custom choice button from ", choices_custom_button) + + for i in range(0, maximum_choices): + var new_choice : DialogicNode_ChoiceButton + if choices_button != null: + new_choice = (choices_button.instantiate() as DialogicNode_ChoiceButton) + else: + new_choice = DialogicNode_ChoiceButton.new() + choices.add_child(new_choice) + + if boxes_fill_width: + new_choice.size_flags_horizontal = Control.SIZE_FILL + else: + new_choice.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + + new_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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd.uid new file mode 100644 index 0000000..8207b1a --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd.uid @@ -0,0 +1 @@ +uid://ij0qm2ew6plv diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn new file mode 100644 index 0000000..926d6ec --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=7 format=3 uid="uid://dhk6j6eb6e3q"] + +[ext_resource type="Script" uid="uid://ij0qm2ew6plv" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Choices/vn_choice_layer.gd" id="1_kurgw"] +[ext_resource type="Script" uid="uid://bldt7xlfum7ov" path="res://addons/dialogic/Modules/Choice/node_choice_button.gd" id="1_w632k"] +[ext_resource type="Script" uid="uid://b1stj4ljd2vo7" 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_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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/part_config.cfg new file mode 100644 index 0000000..fb709b5 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg new file mode 100644 index 0000000..9e00fc6 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import new file mode 100644 index 0000000..11a0b4f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://fwi64s4gbob2" +path="res://.godot/imported/portrait_layer_icon.svg-4bc8b0ebd4dd0977a12c09f30758d7e1.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/portrait_layer_icon.svg" +dest_files=["res://.godot/imported/portrait_layer_icon.svg-4bc8b0ebd4dd0977a12c09f30758d7e1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png new file mode 100644 index 0000000..3c75bd8 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import new file mode 100644 index 0000000..98f6f42 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ypmfci4n2abt" +path="res://.godot/imported/preview.png-8a6dae1a8e205382d354326ea6961ed2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/preview.png" +dest_files=["res://.godot/imported/preview.png-8a6dae1a8e205382d354326ea6961ed2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd new file mode 100644 index 0000000..08576ca --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd @@ -0,0 +1,14 @@ +@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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd.uid new file mode 100644 index 0000000..a69879b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd.uid @@ -0,0 +1 @@ +uid://cx1i44s2olq2d diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.tscn new file mode 100644 index 0000000..2f7ca1e --- /dev/null +++ b/godot/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" uid="uid://cx1i44s2olq2d" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Portraits/vn_portrait_layer.gd" id="1_1i7em"] +[ext_resource type="Script" uid="uid://d0ptqnbudhkyj" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd new file mode 100644 index 0000000..c03b3ee --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd.uid new file mode 100644 index 0000000..b19a51f --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd.uid @@ -0,0 +1 @@ +uid://bfc03rn8slceu diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd new file mode 100644 index 0000000..a4384bd --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd.uid new file mode 100644 index 0000000..54c1e69 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd.uid @@ -0,0 +1 @@ +uid://bklme8oymw6h7 diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg new file mode 100644 index 0000000..ae877a2 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import new file mode 100644 index 0000000..7a3fef1 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0rpqfg4fhebk" +path="res://.godot/imported/next.svg-689f85597f487815b8ddefa23d22bf6f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg" +dest_files=["res://.godot/imported/next.svg-689f85597f487815b8ddefa23d22bf6f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/part_config.cfg new file mode 100644 index 0000000..e94bc6f --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png new file mode 100644 index 0000000..413d09b Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import new file mode 100644 index 0000000..0a3961e --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd4pvssu5dlqf" +path="res://.godot/imported/preview.png-38205c265cdc5033fdb5a79a6f5d3394.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/preview.png" +dest_files=["res://.godot/imported/preview.png-38205c265cdc5033fdb5a79a6f5d3394.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg new file mode 100644 index 0000000..698f5d9 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import new file mode 100644 index 0000000..fdd52d2 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgx0ejya2mtmn" +path="res://.godot/imported/textbox_layer_icon.svg-d6678fedd53dcb59cc32e1c443754ad5.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/textbox_layer_icon.svg" +dest_files=["res://.godot/imported/textbox_layer_icon.svg-d6678fedd53dcb59cc32e1c443754ad5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_default_panel.tres new file mode 100644 index 0000000..07489b4 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd new file mode 100644 index 0000000..95a4ce3 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd.uid new file mode 100644 index 0000000..d7ca10e --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd.uid @@ -0,0 +1 @@ +uid://bl43m5qw8pso3 diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.tscn new file mode 100644 index 0000000..4621c61 --- /dev/null +++ b/godot/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" uid="uid://bl43m5qw8pso3" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_layer.gd" id="1_bpydr"] +[ext_resource type="Script" uid="uid://bfc03rn8slceu" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/animations.gd" id="2_xy7a2"] +[ext_resource type="Script" uid="uid://drhfq6rmdeuri" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="3_4634k"] +[ext_resource type="Script" uid="uid://dpv2dfiv5dhmr" path="res://addons/dialogic/Modules/Text/node_type_sound.gd" id="4_ma5mw"] +[ext_resource type="Script" uid="uid://dve1vwse2peji" path="res://addons/dialogic/Modules/Text/node_next_indicator.gd" id="5_40a50"] +[ext_resource type="Script" uid="uid://bklme8oymw6h7" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/autoadvance_indicator.gd" id="6_07xym"] +[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="Texture2D" uid="uid://b0rpqfg4fhebk" path="res://addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/next.svg" id="6_uch03"] +[ext_resource type="Script" uid="uid://bak74s0kcr0ao" 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/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Layer_VN_Textbox/vn_textbox_name_label_panel.tres new file mode 100644 index 0000000..cc88fd9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/part_config.cfg new file mode 100644 index 0000000..ceaac42 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png new file mode 100644 index 0000000..0adc6da Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import new file mode 100644 index 0000000..55f22e4 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmlqow4is4acg" +path="res://.godot/imported/preview.png-fffb64b5a80dfa536274e4a967369cfc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/preview.png" +dest_files=["res://.godot/imported/preview.png-fffb64b5a80dfa536274e4a967369cfc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres new file mode 100644 index 0000000..ce61f62 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_SpeakerTextbox/speaker_textbox_style.tres @@ -0,0 +1,68 @@ +[gd_resource type="Resource" script_class="DialogicStyle" load_steps=18 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" uid="uid://bwg6yncmh2cml" 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" uid="uid://dv08k6ljua6fm" path="res://addons/dialogic/Resources/dialogic_style.gd" id="9_sdr6x"] + +[sub_resource type="Resource" id="Resource_1cyj6"] +script = ExtResource("2_i34tx") +overrides = {} + +[sub_resource type="Resource" id="Resource_jk75o"] +script = ExtResource("2_i34tx") +scene = ExtResource("1_sde84") +overrides = {} + +[sub_resource type="Resource" id="Resource_l2hgc"] +script = ExtResource("2_i34tx") +scene = ExtResource("4_8y2vo") +overrides = {} + +[sub_resource type="Resource" id="Resource_iqsmu"] +script = ExtResource("2_i34tx") +scene = ExtResource("3_epko4") +overrides = {} + +[sub_resource type="Resource" id="Resource_axty6"] +script = ExtResource("2_i34tx") +scene = ExtResource("5_ll78j") +overrides = {} + +[sub_resource type="Resource" id="Resource_xh5pc"] +script = ExtResource("2_i34tx") +scene = ExtResource("6_36eid") +overrides = {} + +[sub_resource type="Resource" id="Resource_ytmk0"] +script = ExtResource("2_i34tx") +scene = ExtResource("7_hx5el") +overrides = {} + +[sub_resource type="Resource" id="Resource_yjxtw"] +script = ExtResource("2_i34tx") +scene = ExtResource("8_00chv") +overrides = {} + +[resource] +script = ExtResource("9_sdr6x") +name = "Speaker Textbox Style" +layer_list = Array[String](["10", "11", "12", "13", "14", "15", "16"]) +layer_info = { +"": SubResource("Resource_1cyj6"), +"10": SubResource("Resource_jk75o"), +"11": SubResource("Resource_l2hgc"), +"12": SubResource("Resource_iqsmu"), +"13": SubResource("Resource_axty6"), +"14": SubResource("Resource_xh5pc"), +"15": SubResource("Resource_ytmk0"), +"16": SubResource("Resource_yjxtw") +} +base_overrides = {} +layers = Array[ExtResource("2_i34tx")]([]) +metadata/_latest_layer = "" diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/part_config.cfg new file mode 100644 index 0000000..47b4ec6 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png new file mode 100644 index 0000000..74bc8fd Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import new file mode 100644 index 0000000..2987c2b --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ryu3i2tv5xg8" +path="res://.godot/imported/preview.png-566f98d1e24a8b079521a4925ee523c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/preview.png" +dest_files=["res://.godot/imported/preview.png-566f98d1e24a8b079521a4925ee523c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres new file mode 100644 index 0000000..78bdbc1 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_TextBubbles/textbubble_style.tres @@ -0,0 +1,35 @@ +[gd_resource type="Resource" script_class="DialogicStyle" load_steps=9 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" uid="uid://dv08k6ljua6fm" 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" uid="uid://bwg6yncmh2cml" 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_u2jf8"] +script = ExtResource("3_3a5cc") +scene = ExtResource("1_a7s28") +overrides = {} + +[sub_resource type="Resource" id="Resource_ajt4g"] +script = ExtResource("3_3a5cc") +scene = ExtResource("4_rr4hm") +overrides = {} + +[sub_resource type="Resource" id="Resource_phvdv"] +script = ExtResource("3_3a5cc") +scene = ExtResource("2_ctkoo") +overrides = {} + +[resource] +script = ExtResource("1_q3xp1") +name = "Textbubble Style" +layer_list = Array[String](["10", "11"]) +layer_info = { +"": SubResource("Resource_u2jf8"), +"10": SubResource("Resource_ajt4g"), +"11": SubResource("Resource_phvdv") +} +base_overrides = {} +layers = Array[ExtResource("3_3a5cc")]([]) +metadata/_latest_layer = "" diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres new file mode 100644 index 0000000..673fb88 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/default_vn_style.tres @@ -0,0 +1,75 @@ +[gd_resource type="Resource" script_class="DialogicStyle" load_steps=20 format=3 uid="uid://8t1mr302tmqs"] + +[ext_resource type="Script" uid="uid://dv08k6ljua6fm" path="res://addons/dialogic/Resources/dialogic_style.gd" id="1_mvpc0"] +[ext_resource type="Script" uid="uid://bwg6yncmh2cml" 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_3dixn"] +script = ExtResource("2_3b8ue") +overrides = {} + +[sub_resource type="Resource" id="Resource_gen8e"] +script = ExtResource("2_3b8ue") +scene = ExtResource("2_dtgi6") +overrides = {} + +[sub_resource type="Resource" id="Resource_nit0s"] +script = ExtResource("2_3b8ue") +scene = ExtResource("4_q1t5h") +overrides = {} + +[sub_resource type="Resource" id="Resource_1ak3n"] +script = ExtResource("2_3b8ue") +scene = ExtResource("6_j6olx") +overrides = {} + +[sub_resource type="Resource" id="Resource_05bhv"] +script = ExtResource("2_3b8ue") +scene = ExtResource("5_o6sv8") +overrides = {} + +[sub_resource type="Resource" id="Resource_pvwog"] +script = ExtResource("2_3b8ue") +scene = ExtResource("7_vw5f4") +overrides = {} + +[sub_resource type="Resource" id="Resource_spe5r"] +script = ExtResource("2_3b8ue") +scene = ExtResource("8_tc6v1") +overrides = {} + +[sub_resource type="Resource" id="Resource_jf1ol"] +script = ExtResource("2_3b8ue") +scene = ExtResource("9_tufw5") +overrides = {} + +[sub_resource type="Resource" id="Resource_gs5pl"] +script = ExtResource("2_3b8ue") +scene = ExtResource("10_8v8jj") +overrides = {} + +[resource] +script = ExtResource("1_mvpc0") +name = "Visual Novel Style" +layer_list = Array[String](["10", "11", "12", "13", "14", "15", "16", "17"]) +layer_info = { +"": SubResource("Resource_3dixn"), +"10": SubResource("Resource_gen8e"), +"11": SubResource("Resource_nit0s"), +"12": SubResource("Resource_1ak3n"), +"13": SubResource("Resource_05bhv"), +"14": SubResource("Resource_pvwog"), +"15": SubResource("Resource_spe5r"), +"16": SubResource("Resource_jf1ol"), +"17": SubResource("Resource_gs5pl") +} +base_overrides = {} +layers = Array[ExtResource("2_3b8ue")]([]) +metadata/_latest_layer = "17" diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/part_config.cfg b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/part_config.cfg new file mode 100644 index 0000000..92cf452 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png new file mode 100644 index 0000000..70ad618 Binary files /dev/null and b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png differ diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import new file mode 100644 index 0000000..d484e25 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0s7elvxjug5l" +path="res://.godot/imported/preview.png-d02c673e03782a715fbd3fbe644d96d1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/DefaultLayoutParts/Style_VN_Default/preview.png" +dest_files=["res://.godot/imported/preview.png-d02c673e03782a715fbd3fbe644d96d1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd b/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd new file mode 100644 index 0000000..f07b3b3 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd @@ -0,0 +1,6 @@ +@tool +extends DialogicIndexer + + +func _get_layout_parts() -> Array[Dictionary]: + return scan_for_layout_parts() diff --git a/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd.uid b/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd.uid new file mode 100644 index 0000000..c644837 --- /dev/null +++ b/godot/addons/dialogic/Modules/DefaultLayoutParts/index.gd.uid @@ -0,0 +1 @@ +uid://c1dc5xk6urgfg diff --git a/godot/addons/dialogic/Modules/End/event_end.gd b/godot/addons/dialogic/Modules/End/event_end.gd new file mode 100644 index 0000000..b853119 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/End/event_end.gd.uid b/godot/addons/dialogic/Modules/End/event_end.gd.uid new file mode 100644 index 0000000..2941508 --- /dev/null +++ b/godot/addons/dialogic/Modules/End/event_end.gd.uid @@ -0,0 +1 @@ +uid://gyh5a2l1ltmc diff --git a/godot/addons/dialogic/Modules/End/icon.png b/godot/addons/dialogic/Modules/End/icon.png new file mode 100644 index 0000000..d975480 Binary files /dev/null and b/godot/addons/dialogic/Modules/End/icon.png differ diff --git a/godot/addons/dialogic/Modules/End/icon.png.import b/godot/addons/dialogic/Modules/End/icon.png.import new file mode 100644 index 0000000..28b17a0 --- /dev/null +++ b/godot/addons/dialogic/Modules/End/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cstw2y41yacgc" +path="res://.godot/imported/icon.png-8e345f81e023043fdbb4f75b1b0e9bb0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/End/icon.png" +dest_files=["res://.godot/imported/icon.png-8e345f81e023043fdbb4f75b1b0e9bb0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/End/index.gd b/godot/addons/dialogic/Modules/End/index.gd new file mode 100644 index 0000000..0997630 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/End/index.gd.uid b/godot/addons/dialogic/Modules/End/index.gd.uid new file mode 100644 index 0000000..c5234f1 --- /dev/null +++ b/godot/addons/dialogic/Modules/End/index.gd.uid @@ -0,0 +1 @@ +uid://rvs5ddidyxmc diff --git a/godot/addons/dialogic/Modules/Glossary/add-glossary.svg b/godot/addons/dialogic/Modules/Glossary/add-glossary.svg new file mode 100644 index 0000000..f1b9f87 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/add-glossary.svg @@ -0,0 +1,4 @@ + + + + diff --git a/godot/addons/dialogic/Modules/Glossary/add-glossary.svg.import b/godot/addons/dialogic/Modules/Glossary/add-glossary.svg.import new file mode 100644 index 0000000..7499bce --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/add-glossary.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cenut3sc5cul0" +path="res://.godot/imported/add-glossary.svg-1cde77c043d3874d9bc84cc14d0ec9dc.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Glossary/add-glossary.svg" +dest_files=["res://.godot/imported/add-glossary.svg-1cde77c043d3874d9bc84cc14d0ec9dc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Glossary/event_glossary.gd b/godot/addons/dialogic/Modules/Glossary/event_glossary.gd new file mode 100644 index 0000000..d8fce3a --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Glossary/event_glossary.gd.uid b/godot/addons/dialogic/Modules/Glossary/event_glossary.gd.uid new file mode 100644 index 0000000..9ec06ad --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/event_glossary.gd.uid @@ -0,0 +1 @@ +uid://or77lo7du0ot diff --git a/godot/addons/dialogic/Modules/Glossary/glossary_editor.gd b/godot/addons/dialogic/Modules/Glossary/glossary_editor.gd new file mode 100644 index 0000000..403ed6d --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Glossary/glossary_editor.gd.uid b/godot/addons/dialogic/Modules/Glossary/glossary_editor.gd.uid new file mode 100644 index 0000000..608b717 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/glossary_editor.gd.uid @@ -0,0 +1 @@ +uid://bqu863yjcjc8y diff --git a/godot/addons/dialogic/Modules/Glossary/glossary_editor.tscn b/godot/addons/dialogic/Modules/Glossary/glossary_editor.tscn new file mode 100644 index 0000000..a30d33d --- /dev/null +++ b/godot/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" uid="uid://bqu863yjcjc8y" 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_2730t"] +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_2730t") + +[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/godot/addons/dialogic/Modules/Glossary/glossary_resource.gd b/godot/addons/dialogic/Modules/Glossary/glossary_resource.gd new file mode 100644 index 0000000..638d036 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Glossary/glossary_resource.gd.uid b/godot/addons/dialogic/Modules/Glossary/glossary_resource.gd.uid new file mode 100644 index 0000000..da87365 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/glossary_resource.gd.uid @@ -0,0 +1 @@ +uid://80elcodyqohb diff --git a/godot/addons/dialogic/Modules/Glossary/icon.png.import b/godot/addons/dialogic/Modules/Glossary/icon.png.import new file mode 100644 index 0000000..e921f64 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Glossary/icon.svg b/godot/addons/dialogic/Modules/Glossary/icon.svg new file mode 100644 index 0000000..175e284 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/dialogic/Modules/Glossary/icon.svg.import b/godot/addons/dialogic/Modules/Glossary/icon.svg.import new file mode 100644 index 0000000..3fcaaf9 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5xwnxdb7064n" +path="res://.godot/imported/icon.svg-4fc0c12c53379638e37d654e7bbaea1a.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/Glossary/icon.svg" +dest_files=["res://.godot/imported/icon.svg-4fc0c12c53379638e37d654e7bbaea1a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/Glossary/index.gd b/godot/addons/dialogic/Modules/Glossary/index.gd new file mode 100644 index 0000000..4b0f997 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/Glossary/index.gd.uid b/godot/addons/dialogic/Modules/Glossary/index.gd.uid new file mode 100644 index 0000000..66d012a --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/index.gd.uid @@ -0,0 +1 @@ +uid://787tgkcn11ya diff --git a/godot/addons/dialogic/Modules/Glossary/subsystem_glossary.gd b/godot/addons/dialogic/Modules/Glossary/subsystem_glossary.gd new file mode 100644 index 0000000..74077b7 --- /dev/null +++ b/godot/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 = r'(?<=\W|^)(?' + regex_options + r')(?!])(?=\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/godot/addons/dialogic/Modules/Glossary/subsystem_glossary.gd.uid b/godot/addons/dialogic/Modules/Glossary/subsystem_glossary.gd.uid new file mode 100644 index 0000000..666f800 --- /dev/null +++ b/godot/addons/dialogic/Modules/Glossary/subsystem_glossary.gd.uid @@ -0,0 +1 @@ +uid://drwqxg5amy4u diff --git a/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png b/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png new file mode 100644 index 0000000..33a594c Binary files /dev/null and b/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png differ diff --git a/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import b/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import new file mode 100644 index 0000000..1c112db --- /dev/null +++ b/godot/addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtt0l7qbkknfx" +path="res://.godot/imported/highlight_portrait_thumbnail.png-6fa128edf7f6ec29e7a5313b9c3e7412.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/HighlightPortrait/highlight_portrait_thumbnail.png" +dest_files=["res://.godot/imported/highlight_portrait_thumbnail.png-6fa128edf7f6ec29e7a5313b9c3e7412.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/HighlightPortrait/index.gd b/godot/addons/dialogic/Modules/HighlightPortrait/index.gd new file mode 100644 index 0000000..ec9b315 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/HighlightPortrait/index.gd.uid b/godot/addons/dialogic/Modules/HighlightPortrait/index.gd.uid new file mode 100644 index 0000000..46483fc --- /dev/null +++ b/godot/addons/dialogic/Modules/HighlightPortrait/index.gd.uid @@ -0,0 +1 @@ +uid://rsqhj802vgdj diff --git a/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd b/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd new file mode 100644 index 0000000..f2d39f3 --- /dev/null +++ b/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd @@ -0,0 +1,34 @@ +@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 _should_do_portrait_update(_character: DialogicCharacter, _portrait: String) -> bool: + return true + + +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/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd.uid b/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd.uid new file mode 100644 index 0000000..6952887 --- /dev/null +++ b/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.gd.uid @@ -0,0 +1 @@ +uid://24gbl2wbl1d5 diff --git a/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.tscn b/godot/addons/dialogic/Modules/HighlightPortrait/simple_highlight_portrait.tscn new file mode 100644 index 0000000..4d831c0 --- /dev/null +++ b/godot/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" uid="uid://24gbl2wbl1d5" 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/godot/addons/dialogic/Modules/History/definition.svg b/godot/addons/dialogic/Modules/History/definition.svg new file mode 100644 index 0000000..236ca35 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/definition.svg @@ -0,0 +1,3 @@ + + + diff --git a/godot/addons/dialogic/Modules/History/definition.svg.import b/godot/addons/dialogic/Modules/History/definition.svg.import new file mode 100644 index 0000000..93c651a --- /dev/null +++ b/godot/addons/dialogic/Modules/History/definition.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlwtdexd63bxi" +path="res://.godot/imported/definition.svg-dbaabe55d84e4ad95047a50fc6c13843.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/History/definition.svg" +dest_files=["res://.godot/imported/definition.svg-dbaabe55d84e4ad95047a50fc6c13843.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/History/event_history.gd b/godot/addons/dialogic/Modules/History/event_history.gd new file mode 100644 index 0000000..180d7f0 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/History/event_history.gd.uid b/godot/addons/dialogic/Modules/History/event_history.gd.uid new file mode 100644 index 0000000..74dde80 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/event_history.gd.uid @@ -0,0 +1 @@ +uid://dmedm3l78l7gx diff --git a/godot/addons/dialogic/Modules/History/icon.svg b/godot/addons/dialogic/Modules/History/icon.svg new file mode 100644 index 0000000..19c9239 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/godot/addons/dialogic/Modules/History/icon.svg.import b/godot/addons/dialogic/Modules/History/icon.svg.import new file mode 100644 index 0000000..e1faac1 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/icon.svg.import @@ -0,0 +1,44 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://1n5bqdv34pmy" +path="res://.godot/imported/icon.svg-82841efe3f86e947d4f66fd24dc8f52c.ctex" +metadata={ +"has_editor_variant": true, +"vram_texture": false +} + +[deps] + +source_file="res://addons/dialogic/Modules/History/icon.svg" +dest_files=["res://.godot/imported/icon.svg-82841efe3f86e947d4f66fd24dc8f52c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +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/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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/godot/addons/dialogic/Modules/History/index.gd b/godot/addons/dialogic/Modules/History/index.gd new file mode 100644 index 0000000..7791b1f --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/History/index.gd.uid b/godot/addons/dialogic/Modules/History/index.gd.uid new file mode 100644 index 0000000..47238a4 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/index.gd.uid @@ -0,0 +1 @@ +uid://bpcgxmwxm5pif diff --git a/godot/addons/dialogic/Modules/History/settings_history.gd b/godot/addons/dialogic/Modules/History/settings_history.gd new file mode 100644 index 0000000..97b5cd9 --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/History/settings_history.gd.uid b/godot/addons/dialogic/Modules/History/settings_history.gd.uid new file mode 100644 index 0000000..a1c9502 --- /dev/null +++ b/godot/addons/dialogic/Modules/History/settings_history.gd.uid @@ -0,0 +1 @@ +uid://cqfmftweedlvb diff --git a/godot/addons/dialogic/Modules/History/settings_history.tscn b/godot/addons/dialogic/Modules/History/settings_history.tscn new file mode 100644 index 0000000..f8451d0 --- /dev/null +++ b/godot/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" uid="uid://cqfmftweedlvb" 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_cvlu0"] +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_cvlu0") + +[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/godot/addons/dialogic/Modules/History/subsystem_history.gd b/godot/addons/dialogic/Modules/History/subsystem_history.gd new file mode 100644 index 0000000..d1371ea --- /dev/null +++ b/godot/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/godot/addons/dialogic/Modules/History/subsystem_history.gd.uid b/godot/addons/dialogic/Modules/History/subsystem_history.gd.uid new file mode 100644 index 0000000..53c77cc --- /dev/null +++ b/godot/addons/dialogic/Modules/History/subsystem_history.gd.uid @@ -0,0 +1 @@ +uid://bfcdxeqbr1by1 diff --git a/godot/addons/dialogic/Modules/Jump/event_jump.gd b/godot/addons/dialogic/Modules/Jump/event_jump.gd new file mode 100644 index 0000000..9566e46 --- /dev/null +++ b/godot/addons/dialogic/Modules/Jump/event_jump.gd @@ -0,0 +1,166 @@ +@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 := timeline.get_identifier() + if not identifier.is_empty(): + return identifier + return timeline_identifier + set(value): + timeline_identifier = value + timeline = DialogicResourceUtil.get_timeline_resource(value) + if (not timeline_identifier in DialogicResourceUtil.get_label_cache().keys() + or not label_name in DialogicResourceUtil.get_label_cache()[timeline_identifier]): + label_name = "" + ui_update_needed.emit() + + +################################################################################ +## 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 (?.*\/)?(?