First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
82
addons/dialogic/Modules/Core/event_end_branch.gd
Normal file
82
addons/dialogic/Modules/Core/event_end_branch.gd
Normal 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
|
||||
1
addons/dialogic/Modules/Core/event_end_branch.gd.uid
Normal file
1
addons/dialogic/Modules/Core/event_end_branch.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csxye0jh4dc78
|
||||
BIN
addons/dialogic/Modules/Core/icon.png
Normal file
BIN
addons/dialogic/Modules/Core/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 558 B |
40
addons/dialogic/Modules/Core/icon.png.import
Normal file
40
addons/dialogic/Modules/Core/icon.png.import
Normal 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
|
||||
27
addons/dialogic/Modules/Core/index.gd
Normal file
27
addons/dialogic/Modules/Core/index.gd
Normal 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},
|
||||
]
|
||||
1
addons/dialogic/Modules/Core/index.gd.uid
Normal file
1
addons/dialogic/Modules/Core/index.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bsvxrqqfucd68
|
||||
42
addons/dialogic/Modules/Core/subsystem_animation.gd
Normal file
42
addons/dialogic/Modules/Core/subsystem_animation.gd
Normal 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
|
||||
1
addons/dialogic/Modules/Core/subsystem_animation.gd.uid
Normal file
1
addons/dialogic/Modules/Core/subsystem_animation.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dojjtu0jfbovk
|
||||
95
addons/dialogic/Modules/Core/subsystem_expression.gd
Normal file
95
addons/dialogic/Modules/Core/subsystem_expression.gd
Normal 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
|
||||
1
addons/dialogic/Modules/Core/subsystem_expression.gd.uid
Normal file
1
addons/dialogic/Modules/Core/subsystem_expression.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csb0dtnmm0yle
|
||||
217
addons/dialogic/Modules/Core/subsystem_input.gd
Normal file
217
addons/dialogic/Modules/Core/subsystem_input.gd
Normal 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
|
||||
1
addons/dialogic/Modules/Core/subsystem_input.gd.uid
Normal file
1
addons/dialogic/Modules/Core/subsystem_input.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bw2fk2ny623g5
|
||||
Reference in New Issue
Block a user