Updated dialogic

This commit is contained in:
MaddoScientisto 2026-01-05 16:00:41 +01:00
commit cbb82512ee
483 changed files with 5743 additions and 2177 deletions

View file

@ -50,15 +50,25 @@ 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)
if 'scene' in p and p.scene:
depends_on.append(p.scene)
for i in p.get("export_overrides", []):
if typeof(p.export_overrides[i]) == TYPE_STRING and "://" in p.export_overrides[i]:
depends_on.append(p.export_overrides[i].trim_prefix('"').trim_suffix('"'))
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]
for p in character.portraits.values():
if 'scene' in p and p.scene in renames:
p.scene = renames[p.scene]
for i in p.get("export_overrides", []):
if typeof(p.export_overrides[i]) == TYPE_STRING and "://" in p.export_overrides[i]:
var i_path := str(p.export_overrides[i]).trim_prefix('"').trim_suffix('"')
if i_path in renames:
p.export_overrides[i] = '"'+renames[i_path]+'"'
ResourceSaver.save(character, path)
return OK

View file

@ -1 +1 @@
uid://ca2202bvyvsq8
uid://bma748hbtk7sb

View file

@ -1 +1 @@
uid://bvijft505cs0l
uid://fqvcs0pexdf

View file

@ -44,3 +44,24 @@ func _load(path: String, _original_path: String, _use_sub_threads: bool, _cache_
var tml := DialogicTimeline.new()
tml.from_text(file.get_as_text())
return tml
func _get_dependencies(path: String, _add_types: bool) -> PackedStringArray:
var deps := PackedStringArray()
var tml: DialogicTimeline = load(path)
tml.process()
for ev in tml.events:
deps += ev.get_dependencies()
var clean_deps := PackedStringArray()
for i in deps:
var clean := i
if clean.begins_with("res://"):
clean = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(clean))
if not clean.is_empty() and not clean in clean_deps:
clean_deps.append(clean)
return clean_deps

View file

@ -1 +1 @@
uid://b4emo1x6y8rxm
uid://23ha2f6di4hb

View file

@ -19,40 +19,12 @@ func _recognize(resource: Resource) -> bool:
## 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: String = resource.as_text()
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:
if not file:
print("[Dialogic] Error opening file:", FileAccess.get_open_error())
return ERR_CANT_OPEN
file.store_string(timeline_as_text)

View file

@ -1 +1 @@
uid://cug52prms3don
uid://crtkmees3crbr

View file

@ -1,5 +1,6 @@
@tool
extends Resource
@icon("uid://bbea0efx0ybu7")
extends "res://addons/dialogic/Resources/dialogic_identifiable_resource.gd"
class_name DialogicCharacter
@ -30,8 +31,12 @@ enum TranslatedProperties {
var _translation_id := ""
func _to_string() -> String:
return "[{name}:{id}]".format({"name":get_character_name(), "id":get_instance_id()})
func _get_extension() -> String:
return "dch"
func _get_resource_name() -> String:
return "DialogicCharacter"
## Adds a translation ID to the character.
@ -125,7 +130,7 @@ func get_display_name_translated() -> String:
## Returns the best name for this character.
func get_character_name() -> String:
var unique_identifier := DialogicResourceUtil.get_unique_identifier(resource_path)
var unique_identifier := get_identifier()
if not unique_identifier.is_empty():
return unique_identifier
if not resource_path.is_empty():
@ -140,3 +145,13 @@ func get_character_name() -> String:
## 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, {}))
## Helper method intended for a simplified creation of portraits at runtime.
## For more complex needs, manually writing to the portraits dict is recommended.
func add_portrait(name:String, image:String, scene:= "") -> void:
portraits[name] = {
"scene": scene,
"export_overrides": {
"image": image}
}

View file

@ -1 +1 @@
uid://w3vtr3asq7b3
uid://don4ds5f38byo

View file

@ -0,0 +1,36 @@
@tool
extends Resource
func _get_extension() -> String:
return ""
func _get_resource_name() -> String:
return "DialogicIdentifiableResource"
func _to_string() -> String:
return "[{name}:{id}]".format({"name":_get_resource_name(), "id":get_identifier()})
## Returns the best name for this character.
func get_identifier() -> String:
if resource_path:
return DialogicResourceUtil.get_unique_identifier_by_path(resource_path)
if not Engine.is_editor_hint():
return DialogicResourceUtil.get_runtime_unique_identifier(self, _get_extension())
return ""
## Sets the unique identifier-string of this resource.
## In editor (if the resource is already saved) the identifier will be stored.
## In game (if the resource is not stored) the resource will be temporarily registered.
func set_identifier(new_identifier:String) -> bool:
if resource_path and Engine.is_editor_hint():
DialogicResourceUtil.change_unique_identifier(resource_path, new_identifier)
return true
if not resource_path and not Engine.is_editor_hint():
DialogicResourceUtil.register_runtime_resource(self, new_identifier, _get_extension())
return true
return false

View file

@ -0,0 +1 @@
uid://de6cm6skfjtni

View file

@ -58,12 +58,14 @@ func _apply_export_overrides() -> void:
#region HANDLE PERSISTENT DATA
################################################################################
func _enter_tree() -> void:
func _init() -> 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())
var info: Dictionary = Engine.get_meta("dialogic_persistent_style_info", {})
info.merge(_get_persistent_info(), true)
Engine.set_meta("dialogic_persistent_style_info", info)
## To be overwritten. Return any info that a later used style might want to know.
@ -72,7 +74,7 @@ func _get_persistent_info() -> Dictionary:
## 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:
func _load_persistent_info(_info: Dictionary) -> void:
pass
#endregion

View file

@ -1 +1 @@
uid://cd42fevaaab2x
uid://c0qys72ixawvk

View file

@ -1 +1 @@
uid://bvd0nw6f6mrhk
uid://c8d470kti274h

View file

@ -73,7 +73,7 @@ func get_layer_info(id:String) -> Dictionary:
if layer_resource.scene != null:
info.path = layer_resource.scene.resource_path
elif id == "":
info.path = DialogicUtil.get_default_layout_base().resource_path
info.path = DialogicStylesUtil.get_default_layout_base().resource_path
info.overrides = layer_resource.overrides.duplicate()
@ -122,7 +122,7 @@ 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)
var id: String = layer_list.pop_at(from_index)
layer_list.insert(to_index, id)
changed.emit()
@ -132,7 +132,6 @@ func move_layer(from_index:int, to_index:int) -> void:
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()
@ -188,8 +187,8 @@ func get_inheritance_root() -> DialogicStyle:
## 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)
func merge_layer_infos(new_layer_info:Dictionary, ancestor_info:Dictionary) -> Dictionary:
var combined := new_layer_info.duplicate(true)
combined.path = ancestor_info.path
combined.overrides.merge(ancestor_info.overrides)
@ -247,8 +246,8 @@ func clone() -> DialogicStyle:
style.inherits = inherits
var base_info := get_layer_info("")
set_layer_scene("", base_info.path)
set_layer_overrides("", base_info.overrides)
style.set_layer_scene("", base_info.path)
style.set_layer_overrides("", base_info.overrides)
for id in layer_list:
var info := get_layer_info(id)

View file

@ -1 +1 @@
uid://dfja8ptqdlfix
uid://dv08k6ljua6fm

View file

@ -12,3 +12,10 @@ func _init(scene_path:Variant=null, scene_overrides:Dictionary={}):
elif scene_path is String and ResourceLoader.exists(scene_path):
scene = load(scene_path)
overrides = scene_overrides
func _to_string() -> String:
if scene:
return "<Layer:" + scene.resource_path + " {" + str(len(overrides)) + " overrides} >"
else:
return "<Layer:no-scene>"

View file

@ -1 +1 @@
uid://dh4po8pgey4yv
uid://bwg6yncmh2cml

View file

@ -52,7 +52,11 @@ 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")
var event_color := Color("FBB13C"):
get:
if dialogic_color_name:
return DialogicUtil.get_color(dialogic_color_name)
return event_color
## 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
@ -93,7 +97,7 @@ enum ValueType {
NUMBER,
VECTOR2, VECTOR3, VECTOR4,
# Other
CUSTOM, BUTTON, LABEL, COLOR, AUDIO_PREVIEW
CUSTOM, BUTTON, LABEL, COLOR, AUDIO_PREVIEW, IMAGE_PREVIEW
}
## List that stores the fields for the editor
var editor_list: Array = []
@ -101,7 +105,9 @@ var editor_list: Array = []
var this_folder: String = get_script().resource_path.get_base_dir()
## Singal that notifies the visual editor block to update
@warning_ignore("unused_signal")
signal ui_update_needed
@warning_ignore("unused_signal")
signal ui_update_warning(text:String)
@ -142,23 +148,38 @@ func _execute() -> void:
#endregion
#region OVERRIDABLES
#region BRANCHING EVENTS
################################################################################
## All of this section should only be in use if [member can_contain_events] is `true`.
## 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:
## To be overridden. Only called if [member 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:
## To be overridden. Return `true` if this event should be executed even if a previous branch has been executed.
## E.g. IF events returns true, but ELIF and ELSE do not. The first choice of a question does this, while the others don't.
func _is_branch_starter() -> bool:
return false
## Returns the index of the end branch event of this event (Only use if can_contain_events is true).
func get_end_branch_index() -> int:
var idx: int = dialogic.current_timeline_events.find(self)
while true:
idx += 1
var event: DialogicEvent = dialogic.current_timeline.get_event(idx)
if not event:
break
if event.can_contain_events:
idx = event.get_end_branch_index()
if event is DialogicEndBranchEvent:
break
return idx
#endregion
@ -249,6 +270,21 @@ func _test_event_string(string:String) -> bool:
return is_valid_event(string.get_slice('#id:', 0))
return is_valid_event(string.strip_edges())
func get_dependencies() -> PackedStringArray:
var deps := PackedStringArray()
var params := get_shortcode_parameters()
for i in params:
if params[i].has("ext_file"):
var path: String = get(params[i].property)
if path.begins_with("res://") or path.begins_with("uid://"):
deps.append(path)
elif i == "character":
deps.append(DialogicResourceUtil.get_resource_path_from_identifier(path, "dch"))
elif i == "timeline":
deps.append(DialogicResourceUtil.get_resource_path_from_identifier(path, "dtl"))
return deps
#endregion
@ -422,6 +458,9 @@ func _get_icon() -> Resource:
func set_default_color(value:Variant) -> void:
# Skip in running games
if not Engine.is_editor_hint():
return
dialogic_color_name = value
event_color = DialogicUtil.get_color(value)
@ -466,6 +505,8 @@ func get_event_editor_info() -> Array:
else:
editor_list = []
if DialogicUtil.get_editor_setting('show_event_names', false):
add_header_label(event_name)
build_event_editor()
return editor_list
else:
@ -483,7 +524,7 @@ func build_event_editor() -> void:
## @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
## What info can be passed is different for every field
func add_header_label(text:String, condition:= "") -> void:
editor_list.append({

View file

@ -1 +1 @@
uid://der4v0svcgi5f
uid://dw4fa15orthvq

View file

@ -1,17 +1,24 @@
@tool
extends Resource
@icon("uid://j7ym07anlusi")
extends "res://addons/dialogic/Resources/dialogic_identifiable_resource.gd"
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
var text_lines_indexed := {}
var indent_format := "\t"
## Method used for printing timeline resources identifiably
func _to_string() -> String:
return "[DialogicTimeline:{file}]".format({"file":resource_path})
func _get_extension() -> String:
return "dtl"
func _get_resource_name() -> String:
return "DialogicTimeline"
## Helper method
@ -27,6 +34,13 @@ func from_text(text:String) -> void:
events = text.split('\n', true)
events_processed = false
## Take an initial guess about the indentation format
## because the text editor needs it but doesn't call _process()
for ev in events:
indent_format = ev.substr(0, len(ev) - len(ev.strip_edges(true, false)))
if indent_format:
break
## Stores all events in their text format and returns them as a string
func as_text() -> String:
@ -43,8 +57,8 @@ func as_text() -> String:
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"
result += indent_format.repeat(indent)+"\n"
result += indent_format.repeat(indent)+event.event_node_as_text.replace('\n', "\n"+indent_format.repeat(indent)) + "\n"
if event.can_contain_events:
indent += 1
if indent < 0:
@ -58,8 +72,18 @@ func as_text() -> String:
return result.strip_edges()
## Returns the index of the event that corresponds to a specific line
func get_index_from_text_line(text:String, line) -> int:
from_text(text)
process()
return text_lines_indexed[line]
## Method that loads all the event resources from the strings, if it wasn't done before
func process() -> void:
if len(events) == 0:
return
if typeof(events[0]) == TYPE_STRING:
events_processed = false
@ -73,7 +97,9 @@ func process() -> void:
var end_event := DialogicEndBranchEvent.new()
var prev_indent := ""
indent_format = ""
var processed_events := []
text_lines_indexed = {}
# this is needed to add an end branch event even to empty conditions/choices
var prev_was_opener := false
@ -83,6 +109,7 @@ func process() -> void:
var empty_lines := 0
while idx < len(lines)-1:
idx += 1
text_lines_indexed[idx] = len(processed_events)
# make sure we are using the string version, in case this was already converted
var line := ""
@ -99,8 +126,14 @@ func process() -> void:
## Add an end event if the indent is smaller then previously
var indent: String = line.substr(0,len(line)-len(line_stripped))
if indent and ((not indent_format) or not (indent_format in indent)):
if not indent_format.is_empty():
printerr("Timeline contains varying indentation. Found {0} instead of expected {1}.".format([indent, indent_format]))
else:
indent_format = indent
if len(indent) < len(prev_indent):
for i in range(len(prev_indent)-len(indent)):
@warning_ignore("integer_division")
for i in range(len(prev_indent)/len(indent_format)-len(indent)/len(indent_format)):
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)
@ -112,16 +145,13 @@ func process() -> void:
## 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
var event: DialogicEvent = event_from_string(event_content, event_cache)
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):
while not event.is_string_full_event(event_content):
idx += 1
text_lines_indexed[idx] = len(processed_events)
if idx == len(lines):
break
@ -132,20 +162,24 @@ func process() -> void:
event_content += "\n"+following_line_stripped
event._load_from_string(event_content)
event.event_node_as_text = event_content
event._load_from_string(event_content)
processed_events.append(event)
prev_was_opener = event.can_contain_events
empty_lines = 0
if !prev_indent.is_empty():
if not prev_indent.is_empty():
for i in range(len(prev_indent)):
processed_events.append(end_event.duplicate())
events = processed_events
events_processed = true
if indent_format.is_empty():
indent_format = "\t"
## This method makes sure that all events in a timeline are correctly reset
func clean() -> void:
@ -164,3 +198,10 @@ func clean() -> void:
for con_out in event.get_signal_connection_list(sig.name):
con_out.signal.disconnect(con_out.callable)
unreference()
static func event_from_string(event_content:String, event_cache:Array) -> DialogicEvent:
for i in event_cache:
if i._test_event_string(event_content):
return i.duplicate()
return DialogicTextEvent.new()

View file

@ -1 +1 @@
uid://c15y0po020ftd
uid://cm0lw40qto3sd