You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
7.6 KiB
233 lines
7.6 KiB
6 months ago
|
extends RefCounted
|
||
|
class_name DialogicAutoAdvance
|
||
|
## This class holds the settings for the Auto-Advance feature.
|
||
|
## Changing the variables will alter the behaviour of Auto-Advance.
|
||
|
##
|
||
|
## Auto-Advance is a feature that automatically advances the timeline after
|
||
|
## a player-specific amount of time.
|
||
|
## This is useful for visual novels that want the player to read the text
|
||
|
## without having to press.
|
||
|
##
|
||
|
## Unlike [class DialogicAutoSkip], Auto-Advance uses multiple enable flags,
|
||
|
## allowing to track the different instances that enabled Auto-Advance.
|
||
|
## For instance, if a timeline event forces Auto-Advance to be enabled and later
|
||
|
## disables it, the Auto-Advance will still be enabled if the player didn't
|
||
|
## cancel it.
|
||
|
|
||
|
signal autoadvance
|
||
|
signal toggled(enabled: bool)
|
||
|
|
||
|
var autoadvance_timer := Timer.new()
|
||
|
|
||
|
var fixed_delay: float = 1.0
|
||
|
var delay_modifier: float = 1.0
|
||
|
|
||
|
var per_word_delay: float = 0.0
|
||
|
var per_character_delay: float = 0.1
|
||
|
|
||
|
var ignored_characters_enabled := false
|
||
|
var ignored_characters := {}
|
||
|
|
||
|
var await_playing_voice := true
|
||
|
|
||
|
var override_delay_for_current_event: float = -1.0
|
||
|
|
||
|
## Private variable to track the last Auto-Advance state.
|
||
|
## This will be used to emit the [signal toggled] signal.
|
||
|
var _last_enable_state := false
|
||
|
|
||
|
## If true, Auto-Advance will be active until the next event.
|
||
|
##
|
||
|
## Use this flag to create a temporary Auto-Advance mode.
|
||
|
## You can utilise [variable override_delay_for_current_event] to set a
|
||
|
## temporary Auto-Advance delay for this event.
|
||
|
##
|
||
|
## Stacks with [variable enabled_forced] and [variable enabled_until_user_input].
|
||
|
var enabled_until_next_event := false :
|
||
|
set(enabled):
|
||
|
enabled_until_next_event = enabled
|
||
|
_try_emit_toggled()
|
||
|
|
||
|
## If true, Auto-Advance will stay enabled until this is set to false.
|
||
|
##
|
||
|
## This boolean can be used to create an automatic text display.
|
||
|
##
|
||
|
## Stacks with [variable enabled_until_next_event] and [variable enabled_until_user_input].
|
||
|
var enabled_forced := false :
|
||
|
set(enabled):
|
||
|
enabled_forced = enabled
|
||
|
_try_emit_toggled()
|
||
|
|
||
|
## If true, Auto-Advance will be active until the player presses a button.
|
||
|
##
|
||
|
## Use this flag when the player wants to enable Auto-Advance.
|
||
|
##
|
||
|
## Stacks with [variable enabled_forced] and [variable enabled_until_next_event].
|
||
|
var enabled_until_user_input := false :
|
||
|
set(enabled):
|
||
|
enabled_until_user_input = enabled
|
||
|
_try_emit_toggled()
|
||
|
|
||
|
|
||
|
func _init() -> void:
|
||
|
DialogicUtil.autoload().Inputs.add_child(autoadvance_timer)
|
||
|
autoadvance_timer.one_shot = true
|
||
|
autoadvance_timer.timeout.connect(_on_autoadvance_timer_timeout)
|
||
|
toggled.connect(_on_toggled)
|
||
|
|
||
|
enabled_forced = ProjectSettings.get_setting('dialogic/text/autoadvance_enabled', false)
|
||
|
fixed_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_fixed_delay', 1)
|
||
|
per_word_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_word_delay', 0)
|
||
|
per_character_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_character_delay', 0.1)
|
||
|
ignored_characters_enabled = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters_enabled', true)
|
||
|
ignored_characters = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters', {})
|
||
|
|
||
|
#region AUTOADVANCE INTERNALS
|
||
|
|
||
|
func start() -> void:
|
||
|
if not is_enabled():
|
||
|
return
|
||
|
|
||
|
var parsed_text: String = DialogicUtil.autoload().current_state_info['text_parsed']
|
||
|
var delay := _calculate_autoadvance_delay(parsed_text)
|
||
|
|
||
|
await DialogicUtil.autoload().get_tree().process_frame
|
||
|
if delay == 0:
|
||
|
_on_autoadvance_timer_timeout()
|
||
|
else:
|
||
|
autoadvance_timer.start(delay)
|
||
|
|
||
|
|
||
|
## Calculates the autoadvance-time based on settings and text.
|
||
|
##
|
||
|
## Takes into account:
|
||
|
## - temporary delay time override
|
||
|
## - delay per word
|
||
|
## - delay per character
|
||
|
## - fixed delay
|
||
|
## - text time taken
|
||
|
## - autoadvance delay modifier
|
||
|
## - voice audio
|
||
|
func _calculate_autoadvance_delay(text: String = "") -> float:
|
||
|
var delay := 0.0
|
||
|
|
||
|
# Check for temporary time override
|
||
|
if override_delay_for_current_event >= 0:
|
||
|
delay = override_delay_for_current_event
|
||
|
else:
|
||
|
# Add per word and per character delay
|
||
|
delay = _calculate_per_word_delay(text) + _calculate_per_character_delay(text)
|
||
|
|
||
|
delay *= delay_modifier
|
||
|
# Apply fixed delay last, so it's not affected by the delay modifier
|
||
|
delay += fixed_delay
|
||
|
|
||
|
delay = max(0, delay)
|
||
|
|
||
|
# Wait for the voice clip (if longer than the current delay)
|
||
|
if await_playing_voice and DialogicUtil.autoload().has_subsystem('Voice') and DialogicUtil.autoload().Voice.is_running():
|
||
|
delay = max(delay, DialogicUtil.autoload().Voice.get_remaining_time())
|
||
|
|
||
|
return delay
|
||
|
|
||
|
|
||
|
## Checks how many words can be found by separating the text by whitespace.
|
||
|
## (Uses ` ` aka SPACE right now, could be extended in the future)
|
||
|
func _calculate_per_word_delay(text: String) -> float:
|
||
|
return float(text.split(' ', false).size() * per_word_delay)
|
||
|
|
||
|
|
||
|
## Checks how many characters can be found by iterating each letter.
|
||
|
func _calculate_per_character_delay(text: String) -> float:
|
||
|
var calculated_delay: float = 0
|
||
|
|
||
|
if per_character_delay > 0:
|
||
|
# If we have characters to ignore, we will iterate each letter.
|
||
|
if ignored_characters_enabled:
|
||
|
for character in text:
|
||
|
if character in ignored_characters:
|
||
|
continue
|
||
|
calculated_delay += per_character_delay
|
||
|
|
||
|
# Otherwise, we can just multiply the length of the text by the delay.
|
||
|
else:
|
||
|
calculated_delay = text.length() * per_character_delay
|
||
|
|
||
|
return calculated_delay
|
||
|
|
||
|
|
||
|
func _on_autoadvance_timer_timeout() -> void:
|
||
|
autoadvance.emit()
|
||
|
autoadvance_timer.stop()
|
||
|
|
||
|
|
||
|
## Switches the auto-advance mode on or off based on [param is_enabled].
|
||
|
func _on_toggled(is_enabled: bool) -> void:
|
||
|
# If auto-advance is enabled and we are not auto-advancing yet,
|
||
|
# we will initiate the auto-advance mode.
|
||
|
if (is_enabled and !is_advancing()
|
||
|
and DialogicUtil.autoload().current_state == DialogicGameHandler.States.IDLE
|
||
|
and not DialogicUtil.autoload().current_state_info.get('text', '').is_empty()):
|
||
|
start()
|
||
|
|
||
|
# If auto-advance is disabled and we are auto-advancing,
|
||
|
# we want to cancel the auto-advance mode.
|
||
|
elif !is_enabled and is_advancing():
|
||
|
DialogicUtil.autoload().Inputs.stop_timers()
|
||
|
#endregion
|
||
|
|
||
|
#region AUTOADVANCE HELPERS
|
||
|
func is_advancing() -> bool:
|
||
|
return !autoadvance_timer.is_stopped()
|
||
|
|
||
|
|
||
|
func get_time_left() -> float:
|
||
|
return autoadvance_timer.time_left
|
||
|
|
||
|
|
||
|
func get_time() -> float:
|
||
|
return autoadvance_timer.wait_time
|
||
|
|
||
|
|
||
|
## Returns whether Auto-Advance is currently considered enabled.
|
||
|
## Auto-Advance uses three different enable flags:
|
||
|
## - enabled_until_user_input (becomes false on any dialogic input action)
|
||
|
## - enabled_until_next_event (becomes false on each text event)
|
||
|
## - enabled_forced (becomes false only when disabled via code)
|
||
|
##
|
||
|
## All three can be set with dedicated methods.
|
||
|
func is_enabled() -> bool:
|
||
|
return (enabled_until_next_event
|
||
|
or enabled_until_user_input
|
||
|
or enabled_forced)
|
||
|
|
||
|
|
||
|
## Updates the [member _autoadvance_enabled] variable to properly check if the value has changed.
|
||
|
## If it changed, emits the [member toggled] signal.
|
||
|
func _try_emit_toggled() -> void:
|
||
|
var old_autoadvance_state := _last_enable_state
|
||
|
_last_enable_state = is_enabled()
|
||
|
|
||
|
if old_autoadvance_state != _last_enable_state:
|
||
|
toggled.emit(_last_enable_state)
|
||
|
|
||
|
|
||
|
## An internal method connected to changes on the Delay Modifier setting.
|
||
|
func _update_autoadvance_delay_modifier(delay_modifier_value: float) -> void:
|
||
|
delay_modifier = delay_modifier_value
|
||
|
|
||
|
|
||
|
## Returns the progress of the auto-advance timer on a scale between 0 and 1.
|
||
|
## The higher the value, the closer the timer is to finishing.
|
||
|
## If auto-advancing is disabled, returns -1.
|
||
|
func get_progress() -> float:
|
||
|
if !is_advancing():
|
||
|
return -1
|
||
|
|
||
|
var total_time: float = get_time()
|
||
|
var time_left: float = get_time_left()
|
||
|
var progress: float = (total_time - time_left) / total_time
|
||
|
|
||
|
return progress
|
||
|
#endregion
|