]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Variable/subsystem_variables.gd
Adding import files
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Variable / subsystem_variables.gd
1 extends DialogicSubsystem
2
3 ## Subsystem that manages variables and allows to access them.
4
5 ## Emitted if a dialogic variable changes, gives a dictionary with the following keys:[br]
6 ## [br]
7 ## Key         |   Value Type  | Value [br]
8 ## ----------- | ------------- | ----- [br]
9 ## `variable`  | [type String] | The name of the variable that is getting changed. [br]
10 ## `new_value` | [type Variant]| The value that [variable] has after the change (the result). [br]
11 signal variable_changed(info:Dictionary)
12
13 ## Emitted on a set variable event, gives a dictionary with the following keys:[br]
14 ## [br]
15 ## Key         |   Value Type  | Value [br]
16 ## ----------- | ------------- | ----- [br]
17 ## `variable`  | [type String] | The name of the variable that is getting changed. [br]
18 ## `orig_value`| [type Variant]| The value that [variable] had before. [br]
19 ## `new_value` | [type Variant]| The value that [variable] has after the change (the result). [br]
20 ## `value`     | [type Variant]| The value that the variable is changed by/to. [br]
21 ## `value_str` | [type String] | Whatever has been given as the value (not interpreted, so a variable is just a string).[br]
22 signal variable_was_set(info:Dictionary)
23
24
25 #region STATE
26 ####################################################################################################
27
28 func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR):
29         # loading default variables
30         if ! clear_flag & DialogicGameHandler.ClearFlags.KEEP_VARIABLES:
31                 reset()
32
33
34 func load_game_state(load_flag:=LoadFlags.FULL_LOAD):
35         if load_flag == LoadFlags.ONLY_DNODES:
36                 return
37         dialogic.current_state_info['variables'] = merge_folder(dialogic.current_state_info['variables'], ProjectSettings.get_setting('dialogic/variables', {}).duplicate(true))
38
39 #endregion
40
41
42 #region MAIN METHODS
43 ####################################################################################################
44
45 ## This function will try to get the value of variables provided inside curly brackets
46 ## and replace them with their values.
47 ## It will:
48 ## - look for the strings to replace
49 ## - search all autoloads
50 ## - try to get the value from context
51 ##
52 ## So if you provide a string like `Hello, how are you doing {Game.player_name}
53 ## it will try to search for an autoload with the name `Game` and get the value
54 ## of `player_name` to replace it.
55 func parse_variables(text:String) -> String:
56         # First some dirty checks to avoid parsing
57         if not '{' in text:
58                 return text
59
60         # Trying to extract the curly brackets from the text
61         var regex := RegEx.new()
62         regex.compile("(?<!\\\\)\\{(?<variable>([^{}]|\\{.*\\})*)\\}")
63
64         var parsed := text.replace('\\{', '{')
65         for result in regex.search_all(text):
66                 var value: Variant = get_variable(result.get_string('variable'), "<NOT FOUND>")
67                 parsed = parsed.replace("{"+result.get_string('variable')+"}", str(value))
68
69         return parsed
70
71
72 func set_variable(variable_name: String, value: Variant) -> bool:
73         variable_name = variable_name.trim_prefix('{').trim_suffix('}')
74
75         # First assume this is a simple dialogic variable
76         if has(variable_name):
77                 DialogicUtil._set_value_in_dictionary(variable_name, dialogic.current_state_info['variables'], value)
78                 variable_changed.emit({'variable':variable_name, 'new_value':value})
79                 return true
80
81         # Second assume this is an autoload variable
82         elif '.' in variable_name:
83                 var from := variable_name.get_slice('.', 0)
84                 var variable := variable_name.trim_prefix(from+'.')
85
86                 var autoloads := get_autoloads()
87                 var object: Object = null
88                 if from in autoloads:
89                         object = autoloads[from]
90                         while variable.count("."):
91                                 from = variable.get_slice('.', 0)
92                                 if from in object and object.get(from) is Object:
93                                         object = object.get(from)
94                                 variable = variable.trim_prefix(from+'.')
95
96                 if object:
97                         var sub_idx := ""
98                         if '[' in variable:
99                                 sub_idx = variable.substr(variable.find('['))
100                         variable = variable.trim_suffix(sub_idx)
101                         sub_idx = sub_idx.trim_prefix('[').trim_suffix(']')
102
103                         if variable in object:
104                                 match typeof(object.get(variable)):
105                                         TYPE_ARRAY:
106                                                 if not sub_idx:
107                                                         if typeof(value) == TYPE_ARRAY:
108                                                                 object.set(variable, value)
109                                                                 return true
110                                                 elif sub_idx.is_valid_float():
111                                                         object.get(variable).remove_at(int(sub_idx))
112                                                         object.get(variable).insert(int(sub_idx), value)
113                                                         return true
114                                         TYPE_DICTIONARY:
115                                                 if not sub_idx:
116                                                         if typeof(value) == TYPE_DICTIONARY:
117                                                                 object.set(variable, value)
118                                                                 return true
119                                                 else:
120                                                         object.get(variable).merge({str_to_var(sub_idx):value}, true)
121                                                         return true
122                                         _:
123                                                 object.set(variable, value)
124                                                 return true
125
126         printerr("[Dialogic] Tried setting non-existant variable '"+variable_name+"'.")
127         return false
128
129
130 func get_variable(variable_path:String, default: Variant = null, no_warning := false) -> Variant:
131         if variable_path.begins_with('{') and variable_path.ends_with('}') and variable_path.count('{') == 1:
132                 variable_path = variable_path.trim_prefix('{').trim_suffix('}')
133
134         # First assume this is just a single variable
135         var value: Variant = DialogicUtil._get_value_in_dictionary(variable_path, dialogic.current_state_info['variables'])
136         if value != null:
137                 return value
138
139         # Second assume this is an expression.
140         else:
141                 value = dialogic.Expressions.execute_string(variable_path, null, no_warning)
142                 if value != null:
143                         return value
144
145         # If everything fails, tell the user and return the default
146         if not no_warning:
147                 printerr("[Dialogic] Failed parsing variable/expression '"+variable_path+"'.")
148         return default
149
150
151 ## Resets all variables or a specific variable to the value(s) defined in the variable editor
152 func reset(variable:="") -> void:
153         if variable.is_empty():
154                 dialogic.current_state_info['variables'] = ProjectSettings.get_setting("dialogic/variables", {}).duplicate(true)
155         else:
156                 DialogicUtil._set_value_in_dictionary(variable, dialogic.current_state_info['variables'], DialogicUtil._get_value_in_dictionary(variable, ProjectSettings.get_setting('dialogic/variables', {})))
157
158
159 ## Returns true if a variable with the given path exists
160 func has(variable:="") -> bool:
161         return DialogicUtil._get_value_in_dictionary(variable, dialogic.current_state_info['variables']) != null
162
163
164
165 ## Allows to set dialogic built-in variables
166 func _set(property, value) -> bool:
167         property = str(property)
168         var vars: Dictionary = dialogic.current_state_info['variables']
169         if property in vars.keys():
170                 if typeof(vars[property]) != TYPE_DICTIONARY:
171                         vars[property] = value
172                         return true
173                 if value is VariableFolder:
174                         return true
175         return false
176
177
178 ## Allows to get dialogic built-in variables
179 func _get(property):
180         property = str(property)
181         if property in dialogic.current_state_info['variables'].keys():
182                 if typeof(dialogic.current_state_info['variables'][property]) == TYPE_DICTIONARY:
183                         return VariableFolder.new(dialogic.current_state_info['variables'][property], property, self)
184                 else:
185                         return DialogicUtil.logical_convert(dialogic.current_state_info['variables'][property])
186
187
188 func folders() -> Array:
189         var result := []
190         for i in dialogic.current_state_info['variables'].keys():
191                 if dialogic.current_state_info['variables'][i] is Dictionary:
192                         result.append(VariableFolder.new(dialogic.current_state_info['variables'][i], i, self))
193         return result
194
195
196 func variables(_absolute:=false) -> Array:
197         var result := []
198         for i in dialogic.current_state_info['variables'].keys():
199                 if not dialogic.current_state_info['variables'][i] is Dictionary:
200                         result.append(i)
201         return result
202 #endregion
203
204 #region HELPERS
205 ################################################################################
206
207 func get_autoloads() -> Dictionary:
208         var autoloads := {}
209         for node: Node in get_tree().root.get_children():
210                 autoloads[node.name] = node
211         return autoloads
212
213
214 func merge_folder(new:Dictionary, defs:Dictionary) -> Dictionary:
215         # also go through all groups in this folder
216         for x in new.keys():
217                 if x in defs and typeof(new[x]) == TYPE_DICTIONARY:
218                         new[x] = merge_folder(new[x], defs[x])
219         # add all new variables
220         for x in defs.keys():
221                 if not x in new:
222                         new[x] = defs[x]
223         return new
224
225 #endregion
226
227 #region VARIABLE FOLDER
228 ################################################################################
229 class VariableFolder:
230         var data := {}
231         var path := ""
232         var outside: DialogicSubsystem
233
234         func _init(_data:Dictionary, _path:String, _outside:DialogicSubsystem):
235                 data = _data
236                 path = _path
237                 outside = _outside
238
239
240         func _get(property:StringName):
241                 property = str(property)
242                 if property in data:
243                         if typeof(data[property]) == TYPE_DICTIONARY:
244                                 return VariableFolder.new(data[property], path+"."+property, outside)
245                         else:
246                                 return DialogicUtil.logical_convert(data[property])
247
248
249         func _set(property:StringName, value:Variant) -> bool:
250                 property = str(property)
251                 if not value is VariableFolder:
252                         DialogicUtil._set_value_in_dictionary(path+"."+property, outside.dialogic.current_state_info['variables'], value)
253                 return true
254
255
256         func has(key:String) -> bool:
257                 return key in data
258
259
260         func folders() -> Array:
261                 var result := []
262                 for i in data.keys():
263                         if data[i] is Dictionary:
264                                 result.append(VariableFolder.new(data[i], path+"."+i, outside))
265                 return result
266
267
268         func variables(absolute:=false) -> Array:
269                 var result := []
270                 for i in data.keys():
271                         if not data[i] is Dictionary:
272                                 if absolute:
273                                         result.append(path+'.'+i)
274                                 else:
275                                         result.append(i)
276                 return result
277
278 #endregion