]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Choice/subsystem_choices.gd
Updated export config options
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Choice / subsystem_choices.gd
1 extends DialogicSubsystem
2
3 ## Subsystem that manages showing and activating of choices.
4
5 ## Emitted when a choice button was pressed. Info includes the keys 'button_index', 'text', 'event_index'.
6 signal choice_selected(info:Dictionary)
7 ## Emitted when a set of choices is reached and shown.
8 ## Info includes the keys 'choices' (an array of dictionaries with infos on all the choices).
9 signal question_shown(info:Dictionary)
10
11 ## Contains information on the latest question.
12 var last_question_info := {}
13
14 ## The delay between the text finishing revealing and the choices appearing
15 var reveal_delay := 0.0
16 ## If true the player has to click to reveal choices when they are reached
17 var reveal_by_input := false
18 ## The delay between the choices becoming visible and being clickable. Can prevent accidental selection.
19 var block_delay := 0.2
20 ## If true, the first (top-most) choice will be focused
21 var autofocus_first_choice := true
22 ## If true the dialogic input action is used to trigger choices.
23 ## However mouse events will be ignored no matter what.
24 var use_input_action := false
25
26 enum FalseBehaviour {HIDE=0, DISABLE=1}
27 ## The behaviour of choices with a false condition and else_action set to DEFAULT.
28 var default_false_behaviour := FalseBehaviour.HIDE
29
30 enum HotkeyBehaviour {NONE, NUMBERS}
31 ## Will add some hotkeys to the choices if different then HotkeyBehaviour.NONE.
32 var hotkey_behaviour := HotkeyBehaviour.NONE
33
34
35 ### INTERNALS
36
37 ## Used to block choices from being clicked for a couple of seconds (if delay is set in settings).
38 var _choice_blocker := Timer.new()
39
40 #region STATE
41 ####################################################################################################
42
43 func clear_game_state(_clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
44         hide_all_choices()
45
46
47 func _ready() -> void:
48         _choice_blocker.one_shot = true
49         DialogicUtil.update_timer_process_callback(_choice_blocker)
50         add_child(_choice_blocker)
51
52         reveal_delay = float(ProjectSettings.get_setting('dialogic/choices/reveal_delay', reveal_delay))
53         reveal_by_input = ProjectSettings.get_setting('dialogic/choices/reveal_by_input', reveal_by_input)
54         block_delay = ProjectSettings.get_setting('dialogic/choices/delay', block_delay)
55         autofocus_first_choice = ProjectSettings.get_setting('dialogic/choices/autofocus_first', autofocus_first_choice)
56         hotkey_behaviour = ProjectSettings.get_setting('dialogic/choices/hotkey_behaviour', hotkey_behaviour)
57         default_false_behaviour = ProjectSettings.get_setting('dialogic/choices/def_false_behaviour', default_false_behaviour)
58
59
60 func post_install() -> void:
61         dialogic.Inputs.dialogic_action.connect(_on_dialogic_action)
62
63 #endregion
64
65
66 #region MAIN METHODS
67 ####################################################################################################
68
69 ## Hides all choice buttons.
70 func hide_all_choices() -> void:
71         for node in get_tree().get_nodes_in_group('dialogic_choice_button'):
72                 node.hide()
73                 if node.is_connected('button_up', _on_choice_selected):
74                         node.disconnect('button_up', _on_choice_selected)
75
76
77 ## Collects information on all the choices of the current question.
78 ## The result is a dictionary like this:
79 ## {'choices':
80 ##      [
81 ##              {'event_index':10, 'button_index':1, 'disabled':false, 'text':"My Choice", 'visible':true},
82 ##              {'event_index':15, 'button_index':2, 'disabled':false, 'text':"My Choice2", 'visible':true},
83 ##      ]
84 func get_current_question_info() -> Dictionary:
85         var question_info := {'choices':[]}
86
87         var button_idx := 1
88         last_question_info = {'choices':[]}
89
90         for choice_index in get_current_choice_indexes():
91                 var event: DialogicEvent = dialogic.current_timeline_events[choice_index]
92
93                 if not event is DialogicChoiceEvent:
94                         continue
95
96                 var choice_event: DialogicChoiceEvent = event
97                 var choice_info := {}
98                 choice_info['event_index'] = choice_index
99                 choice_info['button_index'] = button_idx
100
101                 # Check Condition
102                 var condition: String = choice_event.condition
103
104                 if condition.is_empty() or dialogic.Expressions.execute_condition(choice_event.condition):
105                         choice_info['disabled'] = false
106                         choice_info['text'] = choice_event.get_property_translated('text')
107                         choice_info['visible'] = true
108                         button_idx += 1
109                 else:
110                         choice_info['disabled'] = true
111                         if not choice_event.disabled_text.is_empty():
112                                 choice_info['text'] = choice_event.get_property_translated('disabled_text')
113                         else:
114                                 choice_info['text'] = choice_event.get_property_translated('text')
115
116                         var hide := choice_event.else_action == DialogicChoiceEvent.ElseActions.HIDE
117                         hide = hide or choice_event.else_action == DialogicChoiceEvent.ElseActions.DEFAULT and default_false_behaviour == DialogicChoiceEvent.ElseActions.HIDE
118                         choice_info['visible'] = not hide
119
120                         if not hide:
121                                 button_idx += 1
122
123                 choice_info.text = dialogic.Text.parse_text(choice_info.text, true, true, false, true, false, false)
124
125                 choice_info.merge(choice_event.extra_data)
126
127                 if dialogic.has_subsystem('History'):
128                         choice_info['visited_before'] = dialogic.History.has_event_been_visited(choice_index)
129
130                 question_info['choices'].append(choice_info)
131
132         return question_info
133
134
135 ## Lists all current choices and shows buttons.
136 func show_current_question(instant:=true) -> void:
137         hide_all_choices()
138         _choice_blocker.stop()
139
140         if !instant and (reveal_delay != 0 or reveal_by_input):
141                 if reveal_delay != 0:
142                         _choice_blocker.start(reveal_delay)
143                         _choice_blocker.timeout.connect(show_current_question)
144                 if reveal_by_input:
145                         dialogic.Inputs.dialogic_action.connect(show_current_question)
146                 return
147
148         if _choice_blocker.timeout.is_connected(show_current_question):
149                 _choice_blocker.timeout.disconnect(show_current_question)
150         if dialogic.Inputs.dialogic_action.is_connected(show_current_question):
151                 dialogic.Inputs.dialogic_action.disconnect(show_current_question)
152
153         var missing_button := false
154
155         var question_info := get_current_question_info()
156
157         for choice in question_info.choices:
158                 var node: DialogicNode_ChoiceButton = get_choice_button_node(choice.button_index)
159
160                 if not node:
161                         missing_button = true
162                         continue
163
164                 node._load_info(choice)
165
166                 if choice.button_index == 1 and autofocus_first_choice:
167                         node.grab_focus()
168
169                 match hotkey_behaviour:
170                         ## Add 1 to 9 as shortcuts if it's enabled
171                         HotkeyBehaviour.NUMBERS:
172                                 if choice.button_index > 0 or choice.button_index < 10:
173                                         var shortcut: Shortcut
174                                         if node.shortcut != null:
175                                                 shortcut = node.shortcut
176                                         else:
177                                                 shortcut = Shortcut.new()
178
179                                         var input_key := InputEventKey.new()
180                                         input_key.keycode = OS.find_keycode_from_string(str(choice.button_index))
181                                         shortcut.events.append(input_key)
182                                         node.shortcut = shortcut
183
184                 if node.pressed.is_connected(_on_choice_selected):
185                         node.pressed.disconnect(_on_choice_selected)
186                 node.pressed.connect(_on_choice_selected.bind(choice))
187
188         _choice_blocker.start(block_delay)
189         question_shown.emit(question_info)
190
191         if missing_button:
192                 printerr("[Dialogic] The layout you are using doesn't have enough Choice Buttons for the choices you are trying to display.")
193
194
195
196 func get_choice_button_node(button_index:int) -> DialogicNode_ChoiceButton:
197         var idx := 1
198         for node: DialogicNode_ChoiceButton in get_tree().get_nodes_in_group('dialogic_choice_button'):
199                 if !node.get_parent().is_visible_in_tree():
200                         continue
201                 if node.choice_index == button_index or (node.choice_index == -1 and idx == button_index):
202                         return node
203
204                 if node.choice_index > 0:
205                         idx = node.choice_index
206                 idx += 1
207
208         return null
209
210
211 func _on_choice_selected(choice_info := {}) -> void:
212         if dialogic.paused or not _choice_blocker.is_stopped():
213                 return
214
215         if dialogic.has_subsystem('History'):
216                 var all_choices: Array = dialogic.Choices.last_question_info['choices']
217                 if dialogic.has_subsystem('VAR'):
218                         dialogic.History.store_simple_history_entry(dialogic.VAR.parse_variables(choice_info.text), "Choice", {'all_choices': all_choices})
219                 else:
220                         dialogic.History.store_simple_history_entry(choice_info.text, "Choice", {'all_choices': all_choices})
221                 if dialogic.has_subsystem("History"):
222                         dialogic.History.mark_event_as_visited(choice_info.event_index)
223
224         choice_selected.emit(choice_info)
225         hide_all_choices()
226         dialogic.current_state = dialogic.States.IDLE
227         dialogic.handle_event(choice_info.event_index + 1)
228
229
230
231 func get_current_choice_indexes() -> Array:
232         var choices := []
233         var evt_idx := dialogic.current_event_idx
234         var ignore := 0
235         while true:
236                 if evt_idx >= len(dialogic.current_timeline_events):
237                         break
238                 if dialogic.current_timeline_events[evt_idx] is DialogicChoiceEvent:
239                         if ignore == 0:
240                                 choices.append(evt_idx)
241                         ignore += 1
242                 elif dialogic.current_timeline_events[evt_idx].can_contain_events:
243                         ignore += 1
244                 else:
245                         if ignore == 0:
246                                 break
247
248                 if dialogic.current_timeline_events[evt_idx] is DialogicEndBranchEvent:
249                         ignore -= 1
250                 evt_idx += 1
251         return choices
252
253
254 func _on_dialogic_action() -> void:
255         if get_viewport().gui_get_focus_owner() is DialogicNode_ChoiceButton and use_input_action and not dialogic.Inputs.input_was_mouse_input:
256                 get_viewport().gui_get_focus_owner().pressed.emit()
257
258
259 #endregion
260
261
262 #region HELPERS
263 ####################################################################################################
264
265 func is_question(index:int) -> bool:
266         if dialogic.current_timeline_events[index] is DialogicTextEvent:
267                 if len(dialogic.current_timeline_events)-1 != index:
268                         if dialogic.current_timeline_events[index+1] is DialogicChoiceEvent:
269                                 return true
270
271         if dialogic.current_timeline_events[index] is DialogicChoiceEvent:
272                 if index != 0 and dialogic.current_timeline_events[index-1] is DialogicEndBranchEvent:
273                         if dialogic.current_timeline_events[dialogic.current_timeline_events[index-1].find_opening_index(index-1)] is DialogicChoiceEvent:
274                                 return false
275                         else:
276                                 return true
277                 else:
278                         return true
279         return false
280
281 #endregion