First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
64
addons/dialogic/Resources/CharacterResourceLoader.gd
Normal file
64
addons/dialogic/Resources/CharacterResourceLoader.gd
Normal file
@@ -0,0 +1,64 @@
|
||||
@tool
|
||||
class_name DialogicCharacterFormatLoader
|
||||
extends ResourceFormatLoader
|
||||
|
||||
|
||||
|
||||
## Returns all excepted extenstions
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return PackedStringArray(["dch"])
|
||||
|
||||
|
||||
## Returns "Resource" if this file can/should be loaded by this script
|
||||
func _get_resource_type(path: String) -> String:
|
||||
var ext := path.get_extension().to_lower()
|
||||
if ext == "dch":
|
||||
return "Resource"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Returns the script class associated with a Resource
|
||||
func _get_resource_script_class(path: String) -> String:
|
||||
var ext := path.get_extension().to_lower()
|
||||
if ext == "dch":
|
||||
return "DialogicCharacter"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Return true if this type is handled
|
||||
func _handles_type(typename: StringName) -> bool:
|
||||
return ClassDB.is_parent_class(typename, "Resource")
|
||||
|
||||
|
||||
## Parse the file and return a resource
|
||||
func _load(path: String, _original_path: String, _use_sub_threads: bool, _cache_mode: int) -> Variant:
|
||||
# print('[Dialogic] Reimporting character "' , path, '"')
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
|
||||
if not file:
|
||||
# For now, just let editor know that for some reason you can't
|
||||
# read the file.
|
||||
print("[Dialogic] Error opening file:", FileAccess.get_open_error())
|
||||
return FileAccess.get_open_error()
|
||||
|
||||
return dict_to_inst(str_to_var(file.get_as_text()))
|
||||
|
||||
|
||||
func _get_dependencies(path:String, _add_type:bool) -> PackedStringArray:
|
||||
var depends_on: PackedStringArray = []
|
||||
var character: DialogicCharacter = load(path)
|
||||
for p in character.portraits.values():
|
||||
if 'path' in p and p.path:
|
||||
depends_on.append(p.path)
|
||||
return depends_on
|
||||
|
||||
|
||||
func _rename_dependencies(path: String, renames: Dictionary) -> Error:
|
||||
var character: DialogicCharacter = load(path)
|
||||
for p in character.portraits:
|
||||
if 'path' in character.portraits[p] and character.portraits[p].path in renames:
|
||||
character.portraits[p].path = renames[character.portraits[p].path]
|
||||
ResourceSaver.save(character, path)
|
||||
return OK
|
||||
1
addons/dialogic/Resources/CharacterResourceLoader.gd.uid
Normal file
1
addons/dialogic/Resources/CharacterResourceLoader.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://31xuen3lew2p
|
||||
34
addons/dialogic/Resources/CharacterResourceSaver.gd
Normal file
34
addons/dialogic/Resources/CharacterResourceSaver.gd
Normal file
@@ -0,0 +1,34 @@
|
||||
@tool
|
||||
class_name DialogicCharacterFormatSaver
|
||||
extends ResourceFormatSaver
|
||||
|
||||
|
||||
func _get_recognized_extensions(_resource: Resource) -> PackedStringArray:
|
||||
return PackedStringArray(["dch"])
|
||||
|
||||
|
||||
## Return true if this resource should be loaded as a DialogicCharacter
|
||||
func _recognize(resource: Resource) -> bool:
|
||||
# Cast instead of using "is" keyword in case is a subclass
|
||||
resource = resource as DialogicCharacter
|
||||
|
||||
if resource:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
## Save the resource
|
||||
func _save(resource: Resource, path: String = '', _flags: int = 0) -> Error:
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
|
||||
if not file:
|
||||
# For now, just let editor know that for some reason you can't
|
||||
# read the file.
|
||||
print("[Dialogic] Error opening file:", FileAccess.get_open_error())
|
||||
return FileAccess.get_open_error()
|
||||
|
||||
var result := var_to_str(inst_to_dict(resource))
|
||||
file.store_string(result)
|
||||
# print('[Dialogic] Saved character "' , path, '"')
|
||||
return OK
|
||||
1
addons/dialogic/Resources/CharacterResourceSaver.gd.uid
Normal file
1
addons/dialogic/Resources/CharacterResourceSaver.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://tpot7s4x2llq
|
||||
46
addons/dialogic/Resources/TimelineResourceLoader.gd
Normal file
46
addons/dialogic/Resources/TimelineResourceLoader.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
@tool
|
||||
class_name DialogicTimelineFormatLoader
|
||||
extends ResourceFormatLoader
|
||||
|
||||
|
||||
## Returns all excepted extenstions
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return PackedStringArray(["dtl"])
|
||||
|
||||
|
||||
## Returns "Resource" if this file can/should be loaded by this script
|
||||
func _get_resource_type(path: String) -> String:
|
||||
var ext := path.get_extension().to_lower()
|
||||
if ext == "dtl":
|
||||
return "Resource"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Returns the script class associated with a Resource
|
||||
func _get_resource_script_class(path: String) -> String:
|
||||
var ext := path.get_extension().to_lower()
|
||||
if ext == "dtl":
|
||||
return "DialogicTimeline"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Return true if this type is handled
|
||||
func _handles_type(typename: StringName) -> bool:
|
||||
return ClassDB.is_parent_class(typename, "Resource")
|
||||
|
||||
|
||||
## Parse the file and return a resource
|
||||
func _load(path: String, _original_path: String, _use_sub_threads: bool, _cache_mode: int) -> Variant:
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
|
||||
if not file:
|
||||
# For now, just let editor know that for some reason you can't
|
||||
# read the file.
|
||||
print("[Dialogic] Error opening file:", FileAccess.get_open_error())
|
||||
return FileAccess.get_open_error()
|
||||
|
||||
var tml := DialogicTimeline.new()
|
||||
tml.from_text(file.get_as_text())
|
||||
return tml
|
||||
1
addons/dialogic/Resources/TimelineResourceLoader.gd.uid
Normal file
1
addons/dialogic/Resources/TimelineResourceLoader.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://0s2nlh50e8ie
|
||||
61
addons/dialogic/Resources/TimelineResourceSaver.gd
Normal file
61
addons/dialogic/Resources/TimelineResourceSaver.gd
Normal file
@@ -0,0 +1,61 @@
|
||||
@tool
|
||||
class_name DialogicTimelineFormatSaver
|
||||
extends ResourceFormatSaver
|
||||
|
||||
|
||||
func _get_recognized_extensions(_resource: Resource) -> PackedStringArray:
|
||||
return PackedStringArray(["dtl"])
|
||||
|
||||
|
||||
## Return true if this resource should be loaded as a DialogicTimeline
|
||||
func _recognize(resource: Resource) -> bool:
|
||||
# Cast instead of using "is" keyword in case is a subclass
|
||||
resource = resource as DialogicTimeline
|
||||
|
||||
if resource:
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
## Save the resource
|
||||
## TODO: This should use timeline.as_text(), why is this still here?
|
||||
func _save(resource: Resource, path: String = '', _flags: int = 0) -> Error:
|
||||
if resource.get_meta("timeline_not_saved", false):
|
||||
|
||||
var timeline_as_text := ""
|
||||
# if events are resources, create text
|
||||
if resource.events_processed:
|
||||
|
||||
var indent := 0
|
||||
for idx in range(0, len(resource.events)):
|
||||
if resource.events[idx]:
|
||||
var event: DialogicEvent = resource.events[idx]
|
||||
if event.event_name == 'End Branch':
|
||||
indent -=1
|
||||
continue
|
||||
|
||||
for i in event.empty_lines_above:
|
||||
timeline_as_text += '\t'.repeat(indent) + '\n'
|
||||
|
||||
if event != null:
|
||||
timeline_as_text += "\t".repeat(indent)+ event.event_node_as_text + "\n"
|
||||
if event.can_contain_events:
|
||||
indent += 1
|
||||
if indent < 0:
|
||||
indent = 0
|
||||
|
||||
# if events are string lines, just save them
|
||||
else:
|
||||
for event in resource.events:
|
||||
timeline_as_text += event + "\n"
|
||||
|
||||
# Now do the actual saving
|
||||
var file := FileAccess.open(path, FileAccess.WRITE)
|
||||
if !file:
|
||||
print("[Dialogic] Error opening file:", FileAccess.get_open_error())
|
||||
return ERR_CANT_OPEN
|
||||
file.store_string(timeline_as_text)
|
||||
file.close()
|
||||
|
||||
return OK
|
||||
1
addons/dialogic/Resources/TimelineResourceSaver.gd.uid
Normal file
1
addons/dialogic/Resources/TimelineResourceSaver.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://61aj5oo1ko0u
|
||||
142
addons/dialogic/Resources/character.gd
Normal file
142
addons/dialogic/Resources/character.gd
Normal file
@@ -0,0 +1,142 @@
|
||||
@tool
|
||||
extends Resource
|
||||
class_name DialogicCharacter
|
||||
|
||||
|
||||
## Resource that represents a character in dialog.
|
||||
## Manages/contains portraits, custom info and translation of characters.
|
||||
|
||||
@export var display_name := ""
|
||||
@export var nicknames := []
|
||||
|
||||
@export var color := Color()
|
||||
@export var description := ""
|
||||
|
||||
@export var scale := 1.0
|
||||
@export var offset := Vector2()
|
||||
@export var mirror := false
|
||||
|
||||
@export var default_portrait := ""
|
||||
@export var portraits := {}
|
||||
|
||||
@export var custom_info := {}
|
||||
|
||||
## All valid properties that can be accessed by their translation.
|
||||
enum TranslatedProperties {
|
||||
NAME,
|
||||
NICKNAMES,
|
||||
}
|
||||
|
||||
var _translation_id := ""
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "[{name}:{id}]".format({"name":get_character_name(), "id":get_instance_id()})
|
||||
|
||||
|
||||
## Adds a translation ID to the character.
|
||||
func add_translation_id() -> String:
|
||||
_translation_id = DialogicUtil.get_next_translation_id()
|
||||
return _translation_id
|
||||
|
||||
|
||||
## Returns the character's translation ID.
|
||||
## Adds a translation ID to the character if it doesn't have one.
|
||||
func get_set_translation_id() -> String:
|
||||
if _translation_id == null or _translation_id.is_empty():
|
||||
return add_translation_id()
|
||||
else:
|
||||
return _translation_id
|
||||
|
||||
|
||||
## Removes the translation ID from the character.
|
||||
func remove_translation_id() -> void:
|
||||
_translation_id = ""
|
||||
|
||||
|
||||
## Checks [param property] and matches it to a translation key.
|
||||
##
|
||||
## Undefined behaviour if an invalid integer is passed.
|
||||
func get_property_translation_key(property: TranslatedProperties) -> String:
|
||||
var property_key := ""
|
||||
|
||||
match property:
|
||||
TranslatedProperties.NAME:
|
||||
property_key = "name"
|
||||
TranslatedProperties.NICKNAMES:
|
||||
property_key = "nicknames"
|
||||
|
||||
return "Character".path_join(_translation_id).path_join(property_key)
|
||||
|
||||
|
||||
## Accesses the original text of the character.
|
||||
##
|
||||
## Undefined behaviour if an invalid integer is passed.
|
||||
func _get_property_original_text(property: TranslatedProperties) -> String:
|
||||
match property:
|
||||
TranslatedProperties.NAME:
|
||||
return display_name
|
||||
TranslatedProperties.NICKNAMES:
|
||||
return ", ".join(nicknames)
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
## Access a property of the character and if conditions are met, attempts to
|
||||
## translate the property.
|
||||
##
|
||||
## The translation feature must be enabled in the project settings.
|
||||
## The translation ID must be set.
|
||||
## Otherwise, returns the text property as is.
|
||||
##
|
||||
## Undefined behaviour if an invalid integer is passed.
|
||||
func _get_property_translated(property: TranslatedProperties) -> String:
|
||||
var try_translation: bool = (_translation_id != null
|
||||
and not _translation_id.is_empty()
|
||||
and ProjectSettings.get_setting('dialogic/translation/enabled', false)
|
||||
)
|
||||
|
||||
if try_translation:
|
||||
var translation_key := get_property_translation_key(property)
|
||||
var translated_property := tr(translation_key)
|
||||
|
||||
# If no translation is found, tr() returns the ID.
|
||||
# However, we want to fallback to the original text.
|
||||
if translated_property == translation_key:
|
||||
return _get_property_original_text(property)
|
||||
|
||||
return translated_property
|
||||
|
||||
else:
|
||||
return _get_property_original_text(property)
|
||||
|
||||
|
||||
## Translates the nicknames of the characters and then returns them as an array
|
||||
## of strings.
|
||||
func get_nicknames_translated() -> Array:
|
||||
var translated_nicknames := _get_property_translated(TranslatedProperties.NICKNAMES)
|
||||
return (translated_nicknames.split(", ") as Array)
|
||||
|
||||
|
||||
## Translates and returns the display name of the character.
|
||||
func get_display_name_translated() -> String:
|
||||
return _get_property_translated(TranslatedProperties.NAME)
|
||||
|
||||
|
||||
## Returns the best name for this character.
|
||||
func get_character_name() -> String:
|
||||
var unique_identifier := DialogicResourceUtil.get_unique_identifier(resource_path)
|
||||
if not unique_identifier.is_empty():
|
||||
return unique_identifier
|
||||
if not resource_path.is_empty():
|
||||
return resource_path.get_file().trim_suffix('.dch')
|
||||
elif not display_name.is_empty():
|
||||
return display_name.validate_node_name()
|
||||
else:
|
||||
return "UnnamedCharacter"
|
||||
|
||||
|
||||
## Returns the info of the given portrait.
|
||||
## Uses the default portrait if the given portrait doesn't exist.
|
||||
func get_portrait_info(portrait_name:String) -> Dictionary:
|
||||
return portraits.get(portrait_name, portraits.get(default_portrait, {}))
|
||||
1
addons/dialogic/Resources/character.gd.uid
Normal file
1
addons/dialogic/Resources/character.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dga1u6ytv3wk1
|
||||
78
addons/dialogic/Resources/dialogic_layout_base.gd
Normal file
78
addons/dialogic/Resources/dialogic_layout_base.gd
Normal file
@@ -0,0 +1,78 @@
|
||||
@tool
|
||||
class_name DialogicLayoutBase
|
||||
extends Node
|
||||
|
||||
## Base class that should be extended by custom layouts.
|
||||
|
||||
|
||||
## Method that adds a node as a layer
|
||||
func add_layer(layer:DialogicLayoutLayer) -> Node:
|
||||
add_child(layer)
|
||||
return layer
|
||||
|
||||
|
||||
## Method that returns the given child
|
||||
func get_layer(index:int) -> Node:
|
||||
return get_child(index)
|
||||
|
||||
|
||||
## Method to return all the layers
|
||||
func get_layers() -> Array:
|
||||
var layers := []
|
||||
for child in get_children():
|
||||
if child is DialogicLayoutLayer:
|
||||
layers.append(child)
|
||||
return layers
|
||||
|
||||
|
||||
## Method that is called to load the export overrides.
|
||||
## This happens when the style is first introduced,
|
||||
## but also when switching to a different style using the same scene!
|
||||
func apply_export_overrides() -> void:
|
||||
_apply_export_overrides()
|
||||
for child in get_children():
|
||||
if child.has_method('_apply_export_overrides'):
|
||||
child._apply_export_overrides()
|
||||
|
||||
|
||||
## Returns a setting on this base.
|
||||
## This is useful so that layers can share settings like base_color, etc.
|
||||
func get_global_setting(setting:StringName, default:Variant) -> Variant:
|
||||
if setting in self:
|
||||
return get(setting)
|
||||
|
||||
if str(setting).to_lower() in self:
|
||||
return get(setting.to_lower())
|
||||
|
||||
if 'global_'+str(setting) in self:
|
||||
return get('global_'+str(setting))
|
||||
|
||||
return default
|
||||
|
||||
|
||||
## To be overwritten. Apply the settings to your scene here.
|
||||
func _apply_export_overrides() -> void:
|
||||
pass
|
||||
|
||||
|
||||
#region HANDLE PERSISTENT DATA
|
||||
################################################################################
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_load_persistent_info(Engine.get_meta("dialogic_persistent_style_info", {}))
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Engine.set_meta("dialogic_persistent_style_info", _get_persistent_info())
|
||||
|
||||
|
||||
## To be overwritten. Return any info that a later used style might want to know.
|
||||
func _get_persistent_info() -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
## To be overwritten. Apply any info that a previous style might have stored and this style should use.
|
||||
func _load_persistent_info(info: Dictionary) -> void:
|
||||
pass
|
||||
|
||||
#endregion
|
||||
1
addons/dialogic/Resources/dialogic_layout_base.gd.uid
Normal file
1
addons/dialogic/Resources/dialogic_layout_base.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://fu0bnj4wy2d7
|
||||
44
addons/dialogic/Resources/dialogic_layout_layer.gd
Normal file
44
addons/dialogic/Resources/dialogic_layout_layer.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
@tool
|
||||
class_name DialogicLayoutLayer
|
||||
extends Node
|
||||
|
||||
## Base class that should be extended by custom dialogic layout layers.
|
||||
|
||||
@export_group('Layer')
|
||||
@export_subgroup('Disabled')
|
||||
@export var disabled := false
|
||||
|
||||
## This is turned on automatically when the layout is realized [br] [br]
|
||||
## Turn it off, if you want to modify the settings of the nodes yourself.
|
||||
@export_group('Private')
|
||||
@export var apply_overrides_on_ready := false
|
||||
|
||||
var this_folder: String = get_script().resource_path.get_base_dir()
|
||||
|
||||
func _ready() -> void:
|
||||
if apply_overrides_on_ready and not Engine.is_editor_hint():
|
||||
_apply_export_overrides()
|
||||
|
||||
|
||||
|
||||
## Override this and load all your exported settings (apply them to the scene)
|
||||
func _apply_export_overrides() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func apply_export_overrides() -> void:
|
||||
if disabled:
|
||||
if "visible" in self:
|
||||
set('visible', false)
|
||||
process_mode = Node.PROCESS_MODE_DISABLED
|
||||
else:
|
||||
if "visible" in self:
|
||||
set('visible', true)
|
||||
process_mode = Node.PROCESS_MODE_INHERIT
|
||||
|
||||
_apply_export_overrides()
|
||||
|
||||
|
||||
## Use this to get potential global settings.
|
||||
func get_global_setting(setting_name:StringName, default:Variant) -> Variant:
|
||||
return get_parent().get_global_setting(setting_name, default)
|
||||
1
addons/dialogic/Resources/dialogic_layout_layer.gd.uid
Normal file
1
addons/dialogic/Resources/dialogic_layout_layer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cd2jfavry1y6y
|
||||
299
addons/dialogic/Resources/dialogic_style.gd
Normal file
299
addons/dialogic/Resources/dialogic_style.gd
Normal file
@@ -0,0 +1,299 @@
|
||||
@tool
|
||||
extends Resource
|
||||
class_name DialogicStyle
|
||||
|
||||
## A style represents a collection of layers and settings.
|
||||
## A style can inherit from another style.
|
||||
|
||||
|
||||
@export var name := "Style":
|
||||
get:
|
||||
if name.is_empty():
|
||||
return "Unkown Style"
|
||||
return name
|
||||
|
||||
@export var inherits: DialogicStyle = null
|
||||
|
||||
## Stores the layer order
|
||||
@export var layer_list: Array[String] = []
|
||||
## Stores the layer infos
|
||||
@export var layer_info := {
|
||||
"" : DialogicStyleLayer.new()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func _init(_name := "") -> void:
|
||||
if not _name.is_empty():
|
||||
name = _name
|
||||
|
||||
|
||||
|
||||
#region BASE METHODS
|
||||
# These methods are local, meaning they do NOT take inheritance into account.
|
||||
|
||||
|
||||
## Returns the amount of layers (the base layer is not included).
|
||||
func get_layer_count() -> int:
|
||||
return layer_list.size()
|
||||
|
||||
|
||||
## Returns the index of the layer with [param id] in the layer list.
|
||||
## Returns -1 for the base layer (id=="") which is not in the layer list.
|
||||
func get_layer_index(id:String) -> int:
|
||||
return layer_list.find(id)
|
||||
|
||||
|
||||
## Returns `true` if [param id] is a valid id for a layer.
|
||||
func has_layer(id:String) -> bool:
|
||||
return id in layer_info or id == ""
|
||||
|
||||
|
||||
## Returns `true` if [param index] is a valid index for a layer.
|
||||
func has_layer_index(index:int) -> bool:
|
||||
return index < layer_list.size()
|
||||
|
||||
|
||||
## Returns the id of the layer at [param index].
|
||||
func get_layer_id_at_index(index:int) -> String:
|
||||
if index == -1:
|
||||
return ""
|
||||
if has_layer_index(index):
|
||||
return layer_list[index]
|
||||
return ""
|
||||
|
||||
|
||||
func get_layer_info(id:String) -> Dictionary:
|
||||
var info := {"id": id, "path": "", "overrides": {}}
|
||||
|
||||
if has_layer(id):
|
||||
var layer_resource: DialogicStyleLayer = layer_info[id]
|
||||
|
||||
if layer_resource.scene != null:
|
||||
info.path = layer_resource.scene.resource_path
|
||||
elif id == "":
|
||||
info.path = DialogicUtil.get_default_layout_base().resource_path
|
||||
|
||||
info.overrides = layer_resource.overrides.duplicate()
|
||||
|
||||
return info
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region MODIFICATION METHODS
|
||||
# These methods modify the layers of this style.
|
||||
|
||||
|
||||
## Returns a new layer id not yet in use.
|
||||
func get_new_layer_id() -> String:
|
||||
var i := 16
|
||||
while String.num_int64(i, 16) in layer_info:
|
||||
i += 1
|
||||
return String.num_int64(i, 16)
|
||||
|
||||
|
||||
## Adds a layer with the given scene and overrides.
|
||||
## Returns the new layers id.
|
||||
func add_layer(scene:String, overrides:Dictionary = {}, id:= "##") -> String:
|
||||
if id == "##":
|
||||
id = get_new_layer_id()
|
||||
layer_info[id] = DialogicStyleLayer.new(scene, overrides)
|
||||
layer_list.append(id)
|
||||
changed.emit()
|
||||
return id
|
||||
|
||||
|
||||
## Deletes the layer with the given id.
|
||||
## Deleting the base layer is not allowed.
|
||||
func delete_layer(id:String) -> void:
|
||||
if not has_layer(id) or id == "":
|
||||
return
|
||||
|
||||
layer_info.erase(id)
|
||||
layer_list.erase(id)
|
||||
|
||||
changed.emit()
|
||||
|
||||
|
||||
## Moves the layer at [param from_index] to [param to_index].
|
||||
func move_layer(from_index:int, to_index:int) -> void:
|
||||
if not has_layer_index(from_index) or not has_layer_index(to_index-1):
|
||||
return
|
||||
|
||||
var id := layer_list.pop_at(from_index)
|
||||
layer_list.insert(to_index, id)
|
||||
|
||||
changed.emit()
|
||||
|
||||
|
||||
## Changes the scene property of the DialogicStyleLayer resource at [param layer_id].
|
||||
func set_layer_scene(layer_id:String, scene:String) -> void:
|
||||
if not has_layer(layer_id):
|
||||
return
|
||||
|
||||
layer_info[layer_id].scene = load(scene)
|
||||
changed.emit()
|
||||
|
||||
|
||||
func set_layer_overrides(layer_id:String, overrides:Dictionary) -> void:
|
||||
if not has_layer(layer_id):
|
||||
return
|
||||
|
||||
layer_info[layer_id].overrides = overrides
|
||||
changed.emit()
|
||||
|
||||
|
||||
## Changes an override of the DialogicStyleLayer resource at [param layer_id].
|
||||
func set_layer_setting(layer_id:String, setting:String, value:Variant) -> void:
|
||||
if not has_layer(layer_id):
|
||||
return
|
||||
|
||||
layer_info[layer_id].overrides[setting] = value
|
||||
changed.emit()
|
||||
|
||||
|
||||
## Resets (removes) an override of the DialogicStyleLayer resource at [param layer_id].
|
||||
func remove_layer_setting(layer_id:String, setting:String) -> void:
|
||||
if not has_layer(layer_id):
|
||||
return
|
||||
|
||||
layer_info[layer_id].overrides.erase(setting)
|
||||
changed.emit()
|
||||
|
||||
#
|
||||
#endregion
|
||||
|
||||
|
||||
#region INHERITANCE METHODS
|
||||
# These methods are what you should usually use to get info about this style.
|
||||
|
||||
|
||||
## Returns `true` if this style is inheriting from another style.
|
||||
func inherits_anything() -> bool:
|
||||
return inherits != null
|
||||
|
||||
|
||||
## Returns the base style of this style.
|
||||
func get_inheritance_root() -> DialogicStyle:
|
||||
if not inherits_anything():
|
||||
return self
|
||||
|
||||
var style: DialogicStyle = self
|
||||
while style.inherits_anything():
|
||||
style = style.inherits
|
||||
|
||||
return style
|
||||
|
||||
|
||||
## This merges some [param layer_info] with it's param ancestors layer info.
|
||||
func merge_layer_infos(layer_info:Dictionary, ancestor_info:Dictionary) -> Dictionary:
|
||||
var combined := layer_info.duplicate(true)
|
||||
|
||||
combined.path = ancestor_info.path
|
||||
combined.overrides.merge(ancestor_info.overrides)
|
||||
|
||||
return combined
|
||||
|
||||
|
||||
## Returns the layer info of the layer at [param id] taking into account inherited info.
|
||||
## If [param inherited_only] is `true`, the local info is not included.
|
||||
func get_layer_inherited_info(id:String, inherited_only := false) -> Dictionary:
|
||||
var style := self
|
||||
var info := {"id": id, "path": "", "overrides": {}}
|
||||
|
||||
if not inherited_only:
|
||||
info = get_layer_info(id)
|
||||
|
||||
while style.inherits_anything():
|
||||
style = style.inherits
|
||||
info = merge_layer_infos(info, style.get_layer_info(id))
|
||||
|
||||
return info
|
||||
|
||||
|
||||
## Returns the layer list of the root style.
|
||||
func get_layer_inherited_list() -> Array:
|
||||
var list := layer_list
|
||||
|
||||
if inherits_anything():
|
||||
list = get_inheritance_root().layer_list
|
||||
|
||||
return list
|
||||
|
||||
|
||||
## Applies inherited info to the local layers.
|
||||
## Then removes inheritance.
|
||||
func realize_inheritance() -> void:
|
||||
layer_list = get_layer_inherited_list()
|
||||
|
||||
var new_layer_info := {}
|
||||
for id in layer_info:
|
||||
var info := get_layer_inherited_info(id)
|
||||
new_layer_info[id] = DialogicStyleLayer.new(info.get("path", ""), info.get("overrides", {}))
|
||||
|
||||
layer_info = new_layer_info
|
||||
inherits = null
|
||||
changed.emit()
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
## Creates a fresh new style with the same settings.
|
||||
func clone() -> DialogicStyle:
|
||||
var style := DialogicStyle.new()
|
||||
style.name = name
|
||||
style.inherits = inherits
|
||||
|
||||
var base_info := get_layer_info("")
|
||||
set_layer_scene("", base_info.path)
|
||||
set_layer_overrides("", base_info.overrides)
|
||||
|
||||
for id in layer_list:
|
||||
var info := get_layer_info(id)
|
||||
style.add_layer(info.path, info.overrides, id)
|
||||
|
||||
return style
|
||||
|
||||
|
||||
## Starts preloading all the scenes used by this style.
|
||||
func prepare() -> void:
|
||||
for id in layer_info:
|
||||
if layer_info[id].scene:
|
||||
ResourceLoader.load_threaded_request(layer_info[id].scene.resource_path)
|
||||
|
||||
|
||||
#region UPDATE OLD STYLES
|
||||
# TODO deprecated when going into beta
|
||||
|
||||
# TODO Deprecated, only for Styles before alpha 16!
|
||||
@export var base_scene: PackedScene = null
|
||||
# TODO Deprecated, only for Styles before alpha 16!
|
||||
@export var base_overrides := {}
|
||||
# TODO Deprecated, only for Styles before alpha 16!
|
||||
@export var layers: Array[DialogicStyleLayer] = []
|
||||
|
||||
func update_from_pre_alpha16() -> void:
|
||||
if not layers.is_empty():
|
||||
var idx := 0
|
||||
for layer in layers:
|
||||
var id := "##"
|
||||
if inherits_anything():
|
||||
id = get_layer_inherited_list()[idx]
|
||||
if layer.scene:
|
||||
add_layer(layer.scene.resource_path, layer.overrides, id)
|
||||
else:
|
||||
add_layer("", layer.overrides, id)
|
||||
idx += 1
|
||||
layers.clear()
|
||||
|
||||
if not base_scene == null:
|
||||
set_layer_scene("", base_scene.resource_path)
|
||||
base_scene = null
|
||||
if not base_overrides.is_empty():
|
||||
set_layer_overrides("", base_overrides)
|
||||
base_overrides.clear()
|
||||
|
||||
|
||||
#endregion
|
||||
1
addons/dialogic/Resources/dialogic_style.gd.uid
Normal file
1
addons/dialogic/Resources/dialogic_style.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b5nqsdfdl0jjg
|
||||
14
addons/dialogic/Resources/dialogic_style_layer.gd
Normal file
14
addons/dialogic/Resources/dialogic_style_layer.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
@tool
|
||||
class_name DialogicStyleLayer
|
||||
extends Resource
|
||||
|
||||
@export var scene: PackedScene = null
|
||||
@export var overrides := {}
|
||||
|
||||
|
||||
func _init(scene_path:Variant=null, scene_overrides:Dictionary={}):
|
||||
if scene_path is PackedScene:
|
||||
scene = scene_path
|
||||
elif scene_path is String and ResourceLoader.exists(scene_path):
|
||||
scene = load(scene_path)
|
||||
overrides = scene_overrides
|
||||
1
addons/dialogic/Resources/dialogic_style_layer.gd.uid
Normal file
1
addons/dialogic/Resources/dialogic_style_layer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1qacwh2qaoiq
|
||||
549
addons/dialogic/Resources/event.gd
Normal file
549
addons/dialogic/Resources/event.gd
Normal file
@@ -0,0 +1,549 @@
|
||||
@tool
|
||||
class_name DialogicEvent
|
||||
extends Resource
|
||||
|
||||
## Base event class for all dialogic events.
|
||||
## Implements basic properties, translation, shortcode saving and usefull methods for creating
|
||||
## the editor UI.
|
||||
|
||||
|
||||
## Emmited when the event starts.
|
||||
## The signal is emmited with the event resource [code]event_resource[/code]
|
||||
signal event_started(event_resource:DialogicEvent)
|
||||
|
||||
## Emmited when the event finish.
|
||||
## The signal is emmited with the event resource [code]event_resource[/code]
|
||||
signal event_finished(event_resource:DialogicEvent)
|
||||
|
||||
|
||||
### Main Event Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
## The event name that'll be displayed in the editor.
|
||||
var event_name := "Event"
|
||||
## Unique identifier used for translatable events.
|
||||
var _translation_id := ""
|
||||
## A reference to dialogic during execution, can be used the same as Dialogic (reference to the autoload)
|
||||
var dialogic: DialogicGameHandler = null
|
||||
|
||||
|
||||
### Special Event Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
### (these properties store how this event affects indentation/flow of timeline)
|
||||
|
||||
## If true this event can not be toplevel (e.g. Choice)
|
||||
var needs_indentation := false
|
||||
## If true this event will spawn with an END BRANCH event and higher the indentation
|
||||
var can_contain_events := false
|
||||
## If [can_contain_events] is true this is a reference to the end branch event
|
||||
var end_branch_event: DialogicEndBranchEvent = null
|
||||
## If this is true this event will group with other similar events (like choices do).
|
||||
var wants_to_group := false
|
||||
|
||||
|
||||
### Saving/Loading Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
## Stores the event in a text format. Does NOT automatically update.
|
||||
var event_node_as_text := ""
|
||||
## Flags if the event has been processed or is only stored as text
|
||||
var event_node_ready := false
|
||||
## How many empty lines are before this event
|
||||
var empty_lines_above: int = 0
|
||||
|
||||
|
||||
### Editor UI Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
## The event color that event node will take in the editor
|
||||
var event_color := Color("FBB13C")
|
||||
## If you are using the default color palette
|
||||
var dialogic_color_name: = ""
|
||||
## To sort the buttons shown in the editor. Lower index is placed at the top of a category
|
||||
var event_sorting_index: int = 0
|
||||
## If true the event will not have a button in the visual editor sidebar
|
||||
var disable_editor_button := false
|
||||
## If false the event will hide it's body by default. Recommended for most events
|
||||
var expand_by_default := false
|
||||
## The URL to open when right_click>Documentation is selected
|
||||
var help_page_path := ""
|
||||
## Is the event block created by a button?
|
||||
var created_by_button := false
|
||||
|
||||
## Reference to the node, that represents this event. Only works while in visual editor mode.
|
||||
## Use with care.
|
||||
var editor_node: Control = null
|
||||
|
||||
## The categories and which one to put it in (in the visual editor sidebar)
|
||||
var event_category := "Other"
|
||||
|
||||
|
||||
### Editor UI creation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
## To differentiate fields that should go to the header and to the body
|
||||
enum Location {HEADER, BODY}
|
||||
|
||||
## To differentiate the different types of fields for event properties in the visual editor
|
||||
enum ValueType {
|
||||
# Strings
|
||||
MULTILINE_TEXT, SINGLELINE_TEXT, CONDITION, FILE,
|
||||
# Booleans
|
||||
BOOL, BOOL_BUTTON,
|
||||
# Options
|
||||
DYNAMIC_OPTIONS, FIXED_OPTIONS,
|
||||
# Containers,
|
||||
ARRAY, DICTIONARY,
|
||||
# Numbers
|
||||
NUMBER,
|
||||
VECTOR2, VECTOR3, VECTOR4,
|
||||
# Other
|
||||
CUSTOM, BUTTON, LABEL, COLOR, AUDIO_PREVIEW
|
||||
}
|
||||
## List that stores the fields for the editor
|
||||
var editor_list: Array = []
|
||||
|
||||
var this_folder: String = get_script().resource_path.get_base_dir()
|
||||
|
||||
## Singal that notifies the visual editor block to update
|
||||
signal ui_update_needed
|
||||
signal ui_update_warning(text:String)
|
||||
|
||||
|
||||
## Makes this resource printable.
|
||||
func _to_string() -> String:
|
||||
return "[{name}:{id}]".format({"name":event_name, "id":get_instance_id()})
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region EXECUTION
|
||||
################################################################################
|
||||
|
||||
## Executes the event behaviour. In subclasses [_execute] (not this one) should be overriden!
|
||||
func execute(_dialogic_game_handler) -> void:
|
||||
event_started.emit(self)
|
||||
dialogic = _dialogic_game_handler
|
||||
call_deferred("_execute")
|
||||
|
||||
|
||||
## Ends the event behaviour.
|
||||
func finish() -> void:
|
||||
event_finished.emit(self)
|
||||
|
||||
|
||||
## Called before executing the next event or before clear(any flags) / load_full_state().
|
||||
##
|
||||
## Should be overridden if the event stores temporary state into dialogic.current_state_info
|
||||
## or some other cleanup is needed before another event can run.
|
||||
func _clear_state() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## To be overridden by subclasses.
|
||||
func _execute() -> void:
|
||||
finish()
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region OVERRIDABLES
|
||||
################################################################################
|
||||
|
||||
## to be overridden by sub-classes
|
||||
## only called if can_contain_events is true.
|
||||
## return a control node that should show on the END BRANCH node
|
||||
func get_end_branch_control() -> Control:
|
||||
return null
|
||||
|
||||
|
||||
## to be overridden by sub-classes
|
||||
## only called if can_contain_events is true and the previous event was an end-branch event
|
||||
## return true if this event should be executed if the previous event was an end-branch event
|
||||
## basically only important for the Condition event but who knows. Some day someone might need this.
|
||||
func should_execute_this_branch() -> bool:
|
||||
return false
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region TRANSLATIONS
|
||||
################################################################################
|
||||
|
||||
## Overwrite if this events needs translation.
|
||||
func _get_translatable_properties() -> Array:
|
||||
return []
|
||||
|
||||
|
||||
## Overwrite if this events needs translation.
|
||||
func _get_property_original_translation(_property_name:String) -> String:
|
||||
return ''
|
||||
|
||||
|
||||
## Returns true if there is any translatable properties on this event.
|
||||
## Overwrite [_get_translatable_properties()] to change this.
|
||||
func can_be_translated() -> bool:
|
||||
return !_get_translatable_properties().is_empty()
|
||||
|
||||
|
||||
## This is automatically called, no need to use this.
|
||||
func add_translation_id() -> String:
|
||||
_translation_id = DialogicUtil.get_next_translation_id()
|
||||
return _translation_id
|
||||
|
||||
|
||||
func remove_translation_id() -> void:
|
||||
_translation_id = ""
|
||||
|
||||
|
||||
func get_property_translation_key(property_name:String) -> String:
|
||||
return event_name.path_join(_translation_id).path_join(property_name)
|
||||
|
||||
|
||||
## Call this whenever you are using a translatable property
|
||||
func get_property_translated(property_name:String) -> String:
|
||||
if !_translation_id.is_empty() and ProjectSettings.get_setting('dialogic/translation/enabled', false):
|
||||
var translation := tr(get_property_translation_key(property_name))
|
||||
# if no translation is found tr() returns the id, but we want to fallback to the original
|
||||
return translation if translation != get_property_translation_key(property_name) else _get_property_original_translation(property_name)
|
||||
else:
|
||||
return _get_property_original_translation(property_name)
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region SAVE / LOAD (internal, don't override)
|
||||
################################################################################
|
||||
### These functions are used by the timeline loader/saver
|
||||
### They mainly use the overridable behaviour below, but enforce the unique_id saving
|
||||
|
||||
## Used by the Timeline saver.
|
||||
func _store_as_string() -> String:
|
||||
if !_translation_id.is_empty() and can_be_translated():
|
||||
return to_text() + ' #id:'+str(_translation_id)
|
||||
else:
|
||||
return to_text()
|
||||
|
||||
|
||||
## Call this if you updated an event and want the changes to be saved.
|
||||
func update_text_version() -> void:
|
||||
event_node_as_text = _store_as_string()
|
||||
|
||||
|
||||
## Used by timeline processor.
|
||||
func _load_from_string(string:String) -> void:
|
||||
_load_custom_defaults()
|
||||
if '#id:' in string and can_be_translated():
|
||||
_translation_id = string.get_slice('#id:', 1).strip_edges()
|
||||
from_text(string.get_slice('#id:', 0))
|
||||
else:
|
||||
from_text(string)
|
||||
event_node_ready = true
|
||||
|
||||
|
||||
## Assigns the custom defaults
|
||||
func _load_custom_defaults() -> void:
|
||||
for default_prop in DialogicUtil.get_custom_event_defaults(event_name):
|
||||
if default_prop in self:
|
||||
set(default_prop, DialogicUtil.get_custom_event_defaults(event_name)[default_prop])
|
||||
|
||||
|
||||
## Used by the timeline processor.
|
||||
func _test_event_string(string:String) -> bool:
|
||||
if '#id:' in string and can_be_translated():
|
||||
return is_valid_event(string.get_slice('#id:', 0))
|
||||
return is_valid_event(string.strip_edges())
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region SAVE / LOAD
|
||||
################################################################################
|
||||
### All of these functions can/should be overridden by the sub classes
|
||||
|
||||
## If this uses the short-code format, return the shortcode.
|
||||
func get_shortcode() -> String:
|
||||
return 'default_shortcode'
|
||||
|
||||
|
||||
## If this uses the short-code format, return the parameters and corresponding property names.
|
||||
func get_shortcode_parameters() -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
## Returns a readable presentation of the event (This is how it's stored).
|
||||
## By default it uses a shortcode format, but can be overridden.
|
||||
func to_text() -> String:
|
||||
var shortcode := store_to_shortcode_parameters()
|
||||
if shortcode:
|
||||
return "[" + self.get_shortcode() + " " + store_to_shortcode_parameters() + "]"
|
||||
else:
|
||||
return "[" + self.get_shortcode() + "]"
|
||||
|
||||
|
||||
## Loads the variables from the string stored by [method to_text].
|
||||
## By default it uses the shortcode format, but can be overridden.
|
||||
func from_text(string: String) -> void:
|
||||
load_from_shortcode_parameters(string)
|
||||
|
||||
|
||||
## 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 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 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 value_to_string(value: Variant, suggestions := Callable()) -> String:
|
||||
var value_as_string := ""
|
||||
match typeof(value):
|
||||
TYPE_OBJECT:
|
||||
value_as_string = str(value.resource_path)
|
||||
|
||||
TYPE_STRING:
|
||||
value_as_string = value
|
||||
|
||||
TYPE_INT when suggestions.is_valid():
|
||||
# HANDLE TEXT ALTERNATIVES FOR ENUMS
|
||||
for option in suggestions.call().values():
|
||||
if option.value != value:
|
||||
continue
|
||||
|
||||
if option.has('text_alt'):
|
||||
value_as_string = option.text_alt[0]
|
||||
else:
|
||||
value_as_string = var_to_str(option.value)
|
||||
|
||||
break
|
||||
|
||||
TYPE_DICTIONARY:
|
||||
value_as_string = JSON.stringify(value)
|
||||
|
||||
_:
|
||||
value_as_string = var_to_str(value)
|
||||
|
||||
if not ((value_as_string.begins_with("[") and value_as_string.ends_with("]")) or (value_as_string.begins_with("{") and value_as_string.ends_with("}"))):
|
||||
value_as_string.replace('"', '\\"')
|
||||
|
||||
return value_as_string
|
||||
|
||||
|
||||
func load_from_shortcode_parameters(string:String) -> void:
|
||||
var data: Dictionary = parse_shortcode_parameters(string)
|
||||
var params: Dictionary = get_shortcode_parameters()
|
||||
for parameter in params.keys():
|
||||
var parameter_info: Dictionary = params[parameter]
|
||||
if parameter_info.get('custom_stored', false):
|
||||
continue
|
||||
|
||||
if not parameter in data:
|
||||
if "set_" + parameter_info.property in self:
|
||||
set("set_" + parameter_info.property, false)
|
||||
continue
|
||||
|
||||
if "set_" + parameter_info.property in self:
|
||||
set("set_" + parameter_info.property, true)
|
||||
|
||||
var param_value: String = data[parameter].replace('\\"', '"')
|
||||
var value: Variant
|
||||
match typeof(get(parameter_info.property)):
|
||||
TYPE_STRING:
|
||||
value = param_value
|
||||
|
||||
TYPE_INT:
|
||||
# If a string is given
|
||||
if parameter_info.has('suggestions'):
|
||||
for option in parameter_info.suggestions.call().values():
|
||||
if option.has('text_alt') and param_value in option.text_alt:
|
||||
value = option.value
|
||||
break
|
||||
|
||||
if not value:
|
||||
value = float(param_value)
|
||||
|
||||
_:
|
||||
value = str_to_var(param_value)
|
||||
|
||||
set(parameter_info.property, value)
|
||||
|
||||
## Has to return `true`, if the given string can be interpreted as this event.
|
||||
## By default it uses the shortcode formta, but can be overridden.
|
||||
func is_valid_event(string: String) -> bool:
|
||||
if string.strip_edges().begins_with('['+get_shortcode()+' ') or string.strip_edges().begins_with('['+get_shortcode()+']'):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## has to return true if this string seems to be a full event of this kind
|
||||
## (only tested if is_valid_event() returned true)
|
||||
## if a shortcode it used it will default to true if the string ends with ']'
|
||||
func is_string_full_event(string: String) -> bool:
|
||||
if get_shortcode() != 'default_shortcode': return string.strip_edges().ends_with(']')
|
||||
return true
|
||||
|
||||
|
||||
## Used to get all the shortcode parameters in a string as a dictionary.
|
||||
func parse_shortcode_parameters(shortcode: String) -> Dictionary:
|
||||
var regex := RegEx.new()
|
||||
regex.compile(r'(?<parameter>[^\s=]*)\s*=\s*"(?<value>(\{[^}]*\}|\[[^]]*\]|([^"]|\\")*|))(?<!\\)\"')
|
||||
var dict := {}
|
||||
for result in regex.search_all(shortcode):
|
||||
dict[result.get_string('parameter')] = result.get_string('value')
|
||||
return dict
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region EDITOR REPRESENTATION
|
||||
################################################################################
|
||||
|
||||
func _get_icon() -> Resource:
|
||||
var _icon_file_name := "res://addons/dialogic/Editor/Images/Pieces/closed-icon.svg" # Default
|
||||
# Check for both svg and png, but prefer svg if available
|
||||
if ResourceLoader.exists(self.get_script().get_path().get_base_dir() + "/icon.svg"):
|
||||
_icon_file_name = self.get_script().get_path().get_base_dir() + "/icon.svg"
|
||||
elif ResourceLoader.exists(self.get_script().get_path().get_base_dir() + "/icon.png"):
|
||||
_icon_file_name = self.get_script().get_path().get_base_dir() + "/icon.png"
|
||||
return load(_icon_file_name)
|
||||
|
||||
|
||||
func set_default_color(value:Variant) -> void:
|
||||
dialogic_color_name = value
|
||||
event_color = DialogicUtil.get_color(value)
|
||||
|
||||
|
||||
## Called when the resource is assigned to a event block in the visual editor
|
||||
func _enter_visual_editor(_timeline_editor:DialogicEditor) -> void:
|
||||
pass
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region CODE COMPLETION
|
||||
################################################################################
|
||||
|
||||
## This method can be overwritten to implement code completion for custom syntaxes
|
||||
func _get_code_completion(_CodeCompletionHelper:Node, _TextNode:TextEdit, _line:String, _word:String, _symbol:String) -> void:
|
||||
pass
|
||||
|
||||
## This method can be overwritten to add starting suggestions for this event
|
||||
func _get_start_code_completion(_CodeCompletionHelper:Node, _TextNode:TextEdit) -> void:
|
||||
pass
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region SYNTAX HIGHLIGHTING
|
||||
################################################################################
|
||||
|
||||
func _get_syntax_highlighting(_Highlighter:SyntaxHighlighter, dict:Dictionary, _line:String) -> Dictionary:
|
||||
return dict
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region EVENT FIELDS
|
||||
################################################################################
|
||||
|
||||
func get_event_editor_info() -> Array:
|
||||
if Engine.is_editor_hint():
|
||||
if editor_list != null:
|
||||
editor_list.clear()
|
||||
else:
|
||||
editor_list = []
|
||||
|
||||
build_event_editor()
|
||||
return editor_list
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
## to be overwritten by the sub_classes
|
||||
func build_event_editor() -> void:
|
||||
pass
|
||||
|
||||
## For the methods below the arguments are mostly similar:
|
||||
## @variable: String name of the property this field is for
|
||||
## @condition: String that will be executed as an expression. If it false
|
||||
## @editor_type: One of the ValueTypes (see ValueType enum). Defines type of field.
|
||||
## @left_text: Text that will be shown to the left of the field
|
||||
## @right_text: Text that will be shown to the right of the field
|
||||
## @extra_info: Allows passing a lot more info to the field.
|
||||
## What info can be passed is differnet for every field
|
||||
|
||||
func add_header_label(text:String, condition:= "") -> void:
|
||||
editor_list.append({
|
||||
"name" : "something",
|
||||
"type" :+ TYPE_STRING,
|
||||
"location" : Location.HEADER,
|
||||
"usage" : PROPERTY_USAGE_EDITOR,
|
||||
"field_type" : ValueType.LABEL,
|
||||
"display_info" : {"text":text},
|
||||
"condition" : condition
|
||||
})
|
||||
|
||||
|
||||
func add_header_edit(variable:String, editor_type := ValueType.LABEL, extra_info:= {}, condition:= "") -> void:
|
||||
editor_list.append({
|
||||
"name" : variable,
|
||||
"type" : typeof(get(variable)),
|
||||
"location" : Location.HEADER,
|
||||
"usage" : PROPERTY_USAGE_DEFAULT,
|
||||
"field_type" : editor_type,
|
||||
"display_info" : extra_info,
|
||||
"left_text" : extra_info.get('left_text', ''),
|
||||
"right_text" : extra_info.get('right_text', ''),
|
||||
"condition" : condition,
|
||||
})
|
||||
|
||||
|
||||
func add_header_button(text:String, callable:Callable, tooltip:String, icon: Variant = null, condition:= "") -> void:
|
||||
editor_list.append({
|
||||
"name" : "Button",
|
||||
"type" : TYPE_STRING,
|
||||
"location" : Location.HEADER,
|
||||
"usage" : PROPERTY_USAGE_DEFAULT,
|
||||
"field_type" : ValueType.BUTTON,
|
||||
"display_info" : {'text':text, 'tooltip':tooltip, 'callable':callable, 'icon':icon},
|
||||
"condition" : condition,
|
||||
})
|
||||
|
||||
|
||||
func add_body_edit(variable:String, editor_type := ValueType.LABEL, extra_info:= {}, condition:= "") -> void:
|
||||
editor_list.append({
|
||||
"name" : variable,
|
||||
"type" : typeof(get(variable)),
|
||||
"location" : Location.BODY,
|
||||
"usage" : PROPERTY_USAGE_DEFAULT,
|
||||
"field_type" : editor_type,
|
||||
"display_info" : extra_info,
|
||||
"left_text" : extra_info.get('left_text', ''),
|
||||
"right_text" : extra_info.get('right_text', ''),
|
||||
"condition" : condition,
|
||||
})
|
||||
|
||||
|
||||
func add_body_line_break(condition:= "") -> void:
|
||||
editor_list.append({
|
||||
"name" : "linebreak",
|
||||
"type" : TYPE_BOOL,
|
||||
"location" : Location.BODY,
|
||||
"usage" : PROPERTY_USAGE_DEFAULT,
|
||||
"condition" : condition,
|
||||
})
|
||||
|
||||
#endregion
|
||||
1
addons/dialogic/Resources/event.gd.uid
Normal file
1
addons/dialogic/Resources/event.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bv800geo7gnog
|
||||
166
addons/dialogic/Resources/timeline.gd
Normal file
166
addons/dialogic/Resources/timeline.gd
Normal file
@@ -0,0 +1,166 @@
|
||||
@tool
|
||||
extends Resource
|
||||
class_name DialogicTimeline
|
||||
|
||||
## Resource that defines a list of events.
|
||||
## It can store them as text and load them from text too.
|
||||
|
||||
var events: Array = []
|
||||
var events_processed := false
|
||||
|
||||
|
||||
## Method used for printing timeline resources identifiably
|
||||
func _to_string() -> String:
|
||||
return "[DialogicTimeline:{file}]".format({"file":resource_path})
|
||||
|
||||
|
||||
## Helper method
|
||||
func get_event(index:int) -> Variant:
|
||||
if index >= len(events):
|
||||
return null
|
||||
return events[index]
|
||||
|
||||
|
||||
## Parses the lines as seperate events and insert them in an array,
|
||||
## so they can be converted to DialogicEvent's when processed later
|
||||
func from_text(text:String) -> void:
|
||||
events = text.split('\n', true)
|
||||
events_processed = false
|
||||
|
||||
|
||||
## Stores all events in their text format and returns them as a string
|
||||
func as_text() -> String:
|
||||
var result := ""
|
||||
|
||||
if events_processed:
|
||||
var indent := 0
|
||||
for idx in range(0, len(events)):
|
||||
var event: DialogicEvent = events[idx]
|
||||
|
||||
if event.event_name == 'End Branch':
|
||||
indent -= 1
|
||||
continue
|
||||
|
||||
if event != null:
|
||||
for i in event.empty_lines_above:
|
||||
result += "\t".repeat(indent)+"\n"
|
||||
result += "\t".repeat(indent)+event.event_node_as_text.replace('\n', "\n"+"\t".repeat(indent)) + "\n"
|
||||
if event.can_contain_events:
|
||||
indent += 1
|
||||
if indent < 0:
|
||||
indent = 0
|
||||
else:
|
||||
for event in events:
|
||||
result += str(event)+"\n"
|
||||
|
||||
result.trim_suffix('\n')
|
||||
|
||||
return result.strip_edges()
|
||||
|
||||
|
||||
## Method that loads all the event resources from the strings, if it wasn't done before
|
||||
func process() -> void:
|
||||
if typeof(events[0]) == TYPE_STRING:
|
||||
events_processed = false
|
||||
|
||||
# if the timeline is already processed
|
||||
if events_processed:
|
||||
for event in events:
|
||||
event.event_node_ready = true
|
||||
return
|
||||
|
||||
var event_cache := DialogicResourceUtil.get_event_cache()
|
||||
var end_event := DialogicEndBranchEvent.new()
|
||||
|
||||
var prev_indent := ""
|
||||
var processed_events := []
|
||||
|
||||
# this is needed to add an end branch event even to empty conditions/choices
|
||||
var prev_was_opener := false
|
||||
|
||||
var lines := events
|
||||
var idx := -1
|
||||
var empty_lines := 0
|
||||
while idx < len(lines)-1:
|
||||
idx += 1
|
||||
|
||||
# make sure we are using the string version, in case this was already converted
|
||||
var line := ""
|
||||
if typeof(lines[idx]) == TYPE_STRING:
|
||||
line = lines[idx]
|
||||
else:
|
||||
line = lines[idx].event_node_as_text
|
||||
|
||||
## Ignore empty lines, but record them in @empty_lines
|
||||
var line_stripped: String = line.strip_edges(true, false)
|
||||
if line_stripped.is_empty():
|
||||
empty_lines += 1
|
||||
continue
|
||||
|
||||
## Add an end event if the indent is smaller then previously
|
||||
var indent: String = line.substr(0,len(line)-len(line_stripped))
|
||||
if len(indent) < len(prev_indent):
|
||||
for i in range(len(prev_indent)-len(indent)):
|
||||
processed_events.append(end_event.duplicate())
|
||||
## Add an end event if the indent is the same but the previous was an opener
|
||||
## (so for example choice that is empty)
|
||||
if prev_was_opener and len(indent) <= len(prev_indent):
|
||||
processed_events.append(end_event.duplicate())
|
||||
|
||||
prev_indent = indent
|
||||
|
||||
## Now we process the event into a resource
|
||||
## by checking on each event if it recognizes this string
|
||||
var event_content: String = line_stripped
|
||||
var event: DialogicEvent
|
||||
for i in event_cache:
|
||||
if i._test_event_string(event_content):
|
||||
event = i.duplicate()
|
||||
break
|
||||
|
||||
event.empty_lines_above = empty_lines
|
||||
# add the following lines until the event says it's full or there is an empty line
|
||||
while !event.is_string_full_event(event_content):
|
||||
idx += 1
|
||||
if idx == len(lines):
|
||||
break
|
||||
|
||||
var following_line_stripped: String = lines[idx].strip_edges(true, false)
|
||||
|
||||
if following_line_stripped.is_empty():
|
||||
break
|
||||
|
||||
event_content += "\n"+following_line_stripped
|
||||
|
||||
event._load_from_string(event_content)
|
||||
event.event_node_as_text = event_content
|
||||
|
||||
processed_events.append(event)
|
||||
prev_was_opener = event.can_contain_events
|
||||
empty_lines = 0
|
||||
|
||||
if !prev_indent.is_empty():
|
||||
for i in range(len(prev_indent)):
|
||||
processed_events.append(end_event.duplicate())
|
||||
|
||||
events = processed_events
|
||||
events_processed = true
|
||||
|
||||
|
||||
## This method makes sure that all events in a timeline are correctly reset
|
||||
func clean() -> void:
|
||||
if not events_processed:
|
||||
return
|
||||
reference()
|
||||
# This is necessary because otherwise INTERNAL GODOT ONESHOT CONNECTIONS
|
||||
# are disconnected before they can disconnect themselves.
|
||||
await Engine.get_main_loop().process_frame
|
||||
|
||||
for event:DialogicEvent in events:
|
||||
for con_in in event.get_incoming_connections():
|
||||
con_in.signal.disconnect(con_in.callable)
|
||||
|
||||
for sig in event.get_signal_list():
|
||||
for con_out in event.get_signal_connection_list(sig.name):
|
||||
con_out.signal.disconnect(con_out.callable)
|
||||
unreference()
|
||||
1
addons/dialogic/Resources/timeline.gd.uid
Normal file
1
addons/dialogic/Resources/timeline.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cw32xk122cjta
|
||||
Reference in New Issue
Block a user