First work on dialogic, resized guild, and started implementing portraits.

This commit is contained in:
2025-08-14 10:26:24 -04:00
parent 95a7db036b
commit 3aeb3d44e6
959 changed files with 47688 additions and 46 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View File

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

View File

@@ -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},
]

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,95 @@
extends DialogicSubsystem
## Subsystem that allows executing strings (with the Expression class).
## This is used by conditions and to allow expresions as variables.
#region MAIN METHODS
####################################################################################################
func execute_string(string:String, default: Variant = null, no_warning := false) -> Variant:
# Some methods are not supported by the expression class, but very useful.
# Thus they are recreated below and secretly added.
string = string.replace('range(', 'd_range(')
string = string.replace('len(', 'd_len(')
string = string.replace('regex(', 'd_regex(')
var regex: RegEx = RegEx.create_from_string('{([^{}]*)}')
for res in regex.search_all(string):
var value: Variant = dialogic.VAR.get_variable(res.get_string())
string = string.replace(res.get_string(), var_to_str(value))
if string.begins_with("{") and string.ends_with('}') and string.count("{") == 1:
string = string.trim_prefix("{").trim_suffix("}")
var expr := Expression.new()
var autoloads := []
var autoload_names := []
for c in get_tree().root.get_children():
autoloads.append(c)
autoload_names.append(c.name)
if expr.parse(string, autoload_names) != OK:
if not no_warning:
printerr('[Dialogic] Expression "', string, '" failed to parse.')
printerr(' ', expr.get_error_text())
dialogic.print_debug_moment()
return default
var result: Variant = expr.execute(autoloads, self)
if expr.has_execute_failed():
if not no_warning:
printerr('[Dialogic] Expression "', string, '" failed to parse.')
printerr(' ', expr.get_error_text())
dialogic.print_debug_moment()
return default
return result
func execute_condition(condition:String) -> bool:
if execute_string(condition, false):
return true
return false
var condition_modifier_regex := RegEx.create_from_string(r"(?(DEFINE)(?<nobraces>([^{}]|\{(?P>nobraces)\})*))\[if *(?<condition>\{(?P>nobraces)\})(?<truetext>(\\\]|\\\/|[^\]\/])*)(\/(?<falsetext>(\\\]|[^\]])*))?\]")
func modifier_condition(text:String) -> String:
for find in condition_modifier_regex.search_all(text):
if execute_condition(find.get_string("condition")):
text = text.replace(find.get_string(), find.get_string("truetext").strip_edges())
else:
text = text.replace(find.get_string(), find.get_string("falsetext").strip_edges())
return text
#endregion
#region HELPERS
####################################################################################################
func d_range(a1, a2=null,a3=null,a4=null) -> Array:
if !a2:
return range(a1)
elif !a3:
return range(a1, a2)
elif !a4:
return range(a1, a2, a3)
else:
return range(a1, a2, a3, a4)
func d_len(arg:Variant) -> int:
return len(arg)
# Checks if there is a match in a string based on a regex pattern string.
func d_regex(input: String, pattern: String, offset: int = 0, end: int = -1) -> bool:
var regex: RegEx = RegEx.create_from_string(pattern)
regex.compile(pattern)
var match := regex.search(input, offset, end)
if match:
return true
else:
return false
#endregion

View File

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

View File

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

View File

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