]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Text/auto_advance.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Text / auto_advance.gd
1 class_name DialogicAutoAdvance
2 extends RefCounted
3 ## This class holds the settings for the Auto-Advance feature.
4 ## Changing the variables will alter the behaviour of Auto-Advance.
5 ##
6 ## Auto-Advance is a feature that automatically advances the timeline after
7 ## a player-specific amount of time.
8 ## This is useful for visual novels that want the player to read the text
9 ## without having to press.
10 ##
11 ## Unlike [class DialogicAutoSkip], Auto-Advance uses multiple enable flags,
12 ## allowing to track the different instances that enabled Auto-Advance.
13 ## For instance, if a timeline event forces Auto-Advance to be enabled and later
14 ## disables it, the Auto-Advance will still be enabled if the player didn't
15 ## cancel it.
16
17 signal autoadvance
18 signal toggled(enabled: bool)
19
20 var autoadvance_timer := Timer.new()
21
22 var fixed_delay: float = 1.0
23 var delay_modifier: float = 1.0
24
25 var per_word_delay: float = 0.0
26 var per_character_delay: float = 0.1
27
28 var ignored_characters_enabled := false
29 var ignored_characters := {}
30
31 var await_playing_voice := true
32
33 var override_delay_for_current_event: float = -1.0
34
35 ## Private variable to track the last Auto-Advance state.
36 ## This will be used to emit the [signal toggled] signal.
37 var _last_enable_state := false
38
39 ## If true, Auto-Advance will be active until the next event.
40 ##
41 ## Use this flag to create a temporary Auto-Advance mode.
42 ## You can utilise [variable override_delay_for_current_event] to set a
43 ## temporary Auto-Advance delay for this event.
44 ##
45 ## Stacks with [variable enabled_forced] and [variable enabled_until_user_input].
46 var enabled_until_next_event := false :
47         set(enabled):
48                 enabled_until_next_event = enabled
49                 _try_emit_toggled()
50
51 ## If true, Auto-Advance will stay enabled until this is set to false.
52 ##
53 ## This boolean can be used to create an automatic text display.
54 ##
55 ## Stacks with [variable enabled_until_next_event] and [variable enabled_until_user_input].
56 var enabled_forced := false :
57         set(enabled):
58                 enabled_forced = enabled
59                 _try_emit_toggled()
60
61 ## If true, Auto-Advance will be active until the player presses a button.
62 ##
63 ## Use this flag when the player wants to enable Auto-Advance.
64 ##
65 ## Stacks with [variable enabled_forced] and [variable enabled_until_next_event].
66 var enabled_until_user_input := false :
67         set(enabled):
68                 enabled_until_user_input = enabled
69                 _try_emit_toggled()
70
71
72 func _init() -> void:
73         DialogicUtil.autoload().Inputs.add_child(autoadvance_timer)
74         autoadvance_timer.one_shot = true
75         autoadvance_timer.timeout.connect(_on_autoadvance_timer_timeout)
76         toggled.connect(_on_toggled)
77
78         enabled_forced = ProjectSettings.get_setting('dialogic/text/autoadvance_enabled', false)
79         fixed_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_fixed_delay', 1)
80         per_word_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_word_delay', 0)
81         per_character_delay = ProjectSettings.get_setting('dialogic/text/autoadvance_per_character_delay', 0.1)
82         ignored_characters_enabled = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters_enabled', true)
83         ignored_characters = ProjectSettings.get_setting('dialogic/text/autoadvance_ignored_characters', {})
84
85 #region AUTOADVANCE INTERNALS
86
87 func start() -> void:
88         if not is_enabled():
89                 return
90
91         var parsed_text: String = DialogicUtil.autoload().current_state_info['text_parsed']
92         var delay := _calculate_autoadvance_delay(parsed_text)
93
94         await DialogicUtil.autoload().get_tree().process_frame
95         if delay == 0:
96                 _on_autoadvance_timer_timeout()
97         else:
98                 autoadvance_timer.start(delay)
99
100
101 ## Calculates the autoadvance-time based on settings and text.
102 ##
103 ## Takes into account:
104 ## - temporary delay time override
105 ## - delay per word
106 ## - delay per character
107 ## - fixed delay
108 ## - text time taken
109 ## - autoadvance delay modifier
110 ## - voice audio
111 func _calculate_autoadvance_delay(text: String = "") -> float:
112         var delay := 0.0
113
114         # Check for temporary time override
115         if override_delay_for_current_event >= 0:
116                 delay = override_delay_for_current_event
117         else:
118                 # Add per word and per character delay
119                 delay = _calculate_per_word_delay(text) + _calculate_per_character_delay(text)
120
121                 delay *= delay_modifier
122                 # Apply fixed delay last, so it's not affected by the delay modifier
123                 delay += fixed_delay
124
125                 delay = max(0, delay)
126
127         # Wait for the voice clip (if longer than the current delay)
128         if await_playing_voice and DialogicUtil.autoload().has_subsystem('Voice') and DialogicUtil.autoload().Voice.is_running():
129                 delay = max(delay, DialogicUtil.autoload().Voice.get_remaining_time())
130
131         return delay
132
133
134 ## Checks how many words can be found by separating the text by whitespace.
135 ##   (Uses ` ` aka SPACE right now, could be extended in the future)
136 func _calculate_per_word_delay(text: String) -> float:
137         return float(text.split(' ', false).size() * per_word_delay)
138
139
140 ## Checks how many characters can be found by iterating each letter.
141 func _calculate_per_character_delay(text: String) -> float:
142         var calculated_delay: float = 0
143
144         if per_character_delay > 0:
145                 # If we have characters to ignore, we will iterate each letter.
146                 if ignored_characters_enabled:
147                         for character in text:
148                                 if character in ignored_characters:
149                                         continue
150                                 calculated_delay += per_character_delay
151
152                 # Otherwise, we can just multiply the length of the text by the delay.
153                 else:
154                         calculated_delay = text.length() * per_character_delay
155
156         return calculated_delay
157
158
159 func _on_autoadvance_timer_timeout() -> void:
160         autoadvance.emit()
161         autoadvance_timer.stop()
162
163
164 ## Switches the auto-advance mode on or off based on [param enabled].
165 func _on_toggled(enabled: bool) -> void:
166         # If auto-advance is enabled and we are not auto-advancing yet,
167         # we will initiate the auto-advance mode.
168         if (enabled and !is_advancing()
169         and DialogicUtil.autoload().current_state == DialogicGameHandler.States.IDLE
170         and not DialogicUtil.autoload().current_state_info.get('text', '').is_empty()):
171                 start()
172
173         # If auto-advance is disabled and we are auto-advancing,
174         # we want to cancel the auto-advance mode.
175         elif !enabled and is_advancing():
176                 DialogicUtil.autoload().Inputs.stop_timers()
177 #endregion
178
179 #region AUTOADVANCE HELPERS
180 func is_advancing() -> bool:
181         return !autoadvance_timer.is_stopped()
182
183
184 func get_time_left() -> float:
185         return autoadvance_timer.time_left
186
187
188 func get_time() -> float:
189         return autoadvance_timer.wait_time
190
191
192 ## Returns whether Auto-Advance is currently considered enabled.
193 ## Auto-Advance uses three different enable flags:
194 ## - enabled_until_user_input (becomes false on any dialogic input action)
195 ## - enabled_until_next_event (becomes false on each text event)
196 ## - enabled_forced (becomes false only when disabled via code)
197 ##
198 ## All three can be set with dedicated methods.
199 func is_enabled() -> bool:
200         return (enabled_until_next_event
201                 or enabled_until_user_input
202                 or enabled_forced)
203
204
205 ## Updates the [member _autoadvance_enabled] variable to properly check if the value has changed.
206 ## If it changed, emits the [member toggled] signal.
207 func _try_emit_toggled() -> void:
208         var old_autoadvance_state := _last_enable_state
209         _last_enable_state = is_enabled()
210
211         if old_autoadvance_state != _last_enable_state:
212                 toggled.emit(_last_enable_state)
213
214
215 ## An internal method connected to changes on the Delay Modifier setting.
216 func _update_autoadvance_delay_modifier(delay_modifier_value: float) -> void:
217         delay_modifier = delay_modifier_value
218
219
220 ## Returns the progress of the auto-advance timer on a scale between 0 and 1.
221 ## The higher the value, the closer the timer is to finishing.
222 ## If auto-advancing is disabled, returns -1.
223 func get_progress() -> float:
224         if !is_advancing():
225                 return -1
226
227         var total_time: float = get_time()
228         var time_left: float = get_time_left()
229         var progress: float = (total_time - time_left) / total_time
230
231         return progress
232 #endregion