2 class_name DialogicEvent
5 ## Base event class for all dialogic events.
6 ## Implements basic properties, translation, shortcode saving and usefull methods for creating
10 ## Emmited when the event starts.
11 ## The signal is emmited with the event resource [code]event_resource[/code]
12 signal event_started(event_resource:DialogicEvent)
14 ## Emmited when the event finish.
15 ## The signal is emmited with the event resource [code]event_resource[/code]
16 signal event_finished(event_resource:DialogicEvent)
19 ### Main Event Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 ## The event name that'll be displayed in the editor.
22 var event_name := "Event"
23 ## Unique identifier used for translatable events.
24 var _translation_id := ""
25 ## A reference to dialogic during execution, can be used the same as Dialogic (reference to the autoload)
26 var dialogic: DialogicGameHandler = null
29 ### Special Event Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 ### (these properties store how this event affects indentation/flow of timeline)
32 ## If true this event can not be toplevel (e.g. Choice)
33 var needs_indentation := false
34 ## If true this event will spawn with an END BRANCH event and higher the indentation
35 var can_contain_events := false
36 ## If [can_contain_events] is true this is a reference to the end branch event
37 var end_branch_event: DialogicEndBranchEvent = null
38 ## If this is true this event will group with other similar events (like choices do).
39 var wants_to_group := false
42 ### Saving/Loading Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 ## Stores the event in a text format. Does NOT automatically update.
45 var event_node_as_text := ""
46 ## Flags if the event has been processed or is only stored as text
47 var event_node_ready := false
48 ## How many empty lines are before this event
49 var empty_lines_above: int = 0
52 ### Editor UI Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 ## The event color that event node will take in the editor
55 var event_color := Color("FBB13C")
56 ## If you are using the default color palette
57 var dialogic_color_name: = ""
58 ## To sort the buttons shown in the editor. Lower index is placed at the top of a category
59 var event_sorting_index: int = 0
60 ## If true the event will not have a button in the visual editor sidebar
61 var disable_editor_button := false
62 ## If false the event will hide it's body by default. Recommended for most events
63 var expand_by_default := false
64 ## The URL to open when right_click>Documentation is selected
65 var help_page_path := ""
66 ## Is the event block created by a button?
67 var created_by_button := false
69 ## Reference to the node, that represents this event. Only works while in visual editor mode.
71 var editor_node: Control = null
73 ## The categories and which one to put it in (in the visual editor sidebar)
74 var event_category := "Other"
77 ### Editor UI creation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79 ## To differentiate fields that should go to the header and to the body
80 enum Location {HEADER, BODY}
82 ## To differentiate the different types of fields for event properties in the visual editor
85 MULTILINE_TEXT, SINGLELINE_TEXT, CONDITION, FILE,
89 DYNAMIC_OPTIONS, FIXED_OPTIONS,
94 VECTOR2, VECTOR3, VECTOR4,
96 CUSTOM, BUTTON, LABEL, COLOR, AUDIO_PREVIEW
98 ## List that stores the fields for the editor
99 var editor_list: Array = []
101 var this_folder: String = get_script().resource_path.get_base_dir()
103 ## Singal that notifies the visual editor block to update
104 signal ui_update_needed
105 signal ui_update_warning(text:String)
108 ## Makes this resource printable.
109 func _to_string() -> String:
110 return "[{name}:{id}]".format({"name":event_name, "id":get_instance_id()})
116 ################################################################################
118 ## Executes the event behaviour. In subclasses [_execute] (not this one) should be overriden!
119 func execute(_dialogic_game_handler) -> void:
120 event_started.emit(self)
121 dialogic = _dialogic_game_handler
122 call_deferred("_execute")
125 ## Ends the event behaviour.
126 func finish() -> void:
127 event_finished.emit(self)
130 ## Called before executing the next event or before clear(any flags) / load_full_state().
132 ## Should be overridden if the event stores temporary state into dialogic.current_state_info
133 ## or some other cleanup is needed before another event can run.
134 func _clear_state() -> void:
138 ## To be overridden by subclasses.
139 func _execute() -> void:
146 ################################################################################
148 ## to be overridden by sub-classes
149 ## only called if can_contain_events is true.
150 ## return a control node that should show on the END BRANCH node
151 func get_end_branch_control() -> Control:
155 ## to be overridden by sub-classes
156 ## only called if can_contain_events is true and the previous event was an end-branch event
157 ## return true if this event should be executed if the previous event was an end-branch event
158 ## basically only important for the Condition event but who knows. Some day someone might need this.
159 func should_execute_this_branch() -> bool:
166 ################################################################################
168 ## Overwrite if this events needs translation.
169 func _get_translatable_properties() -> Array:
173 ## Overwrite if this events needs translation.
174 func _get_property_original_translation(_property_name:String) -> String:
178 ## Returns true if there is any translatable properties on this event.
179 ## Overwrite [_get_translatable_properties()] to change this.
180 func can_be_translated() -> bool:
181 return !_get_translatable_properties().is_empty()
184 ## This is automatically called, no need to use this.
185 func add_translation_id() -> String:
186 _translation_id = DialogicUtil.get_next_translation_id()
187 return _translation_id
190 func remove_translation_id() -> void:
194 func get_property_translation_key(property_name:String) -> String:
195 return event_name.path_join(_translation_id).path_join(property_name)
198 ## Call this whenever you are using a translatable property
199 func get_property_translated(property_name:String) -> String:
200 if !_translation_id.is_empty() and ProjectSettings.get_setting('dialogic/translation/enabled', false):
201 var translation := tr(get_property_translation_key(property_name))
202 # if no translation is found tr() returns the id, but we want to fallback to the original
203 return translation if translation != get_property_translation_key(property_name) else _get_property_original_translation(property_name)
205 return _get_property_original_translation(property_name)
210 #region SAVE / LOAD (internal, don't override)
211 ################################################################################
212 ### These functions are used by the timeline loader/saver
213 ### They mainly use the overridable behaviour below, but enforce the unique_id saving
215 ## Used by the Timeline saver.
216 func _store_as_string() -> String:
217 if !_translation_id.is_empty() and can_be_translated():
218 return to_text() + ' #id:'+str(_translation_id)
223 ## Call this if you updated an event and want the changes to be saved.
224 func update_text_version() -> void:
225 event_node_as_text = _store_as_string()
228 ## Used by timeline processor.
229 func _load_from_string(string:String) -> void:
230 _load_custom_defaults()
231 if '#id:' in string and can_be_translated():
232 _translation_id = string.get_slice('#id:', 1).strip_edges()
233 from_text(string.get_slice('#id:', 0))
236 event_node_ready = true
239 ## Assigns the custom defaults
240 func _load_custom_defaults() -> void:
241 for default_prop in DialogicUtil.get_custom_event_defaults(event_name):
242 if default_prop in self:
243 set(default_prop, DialogicUtil.get_custom_event_defaults(event_name)[default_prop])
246 ## Used by the timeline processor.
247 func _test_event_string(string:String) -> bool:
248 if '#id:' in string and can_be_translated():
249 return is_valid_event(string.get_slice('#id:', 0))
250 return is_valid_event(string.strip_edges())
256 ################################################################################
257 ### All of these functions can/should be overridden by the sub classes
259 ## If this uses the short-code format, return the shortcode.
260 func get_shortcode() -> String:
261 return 'default_shortcode'
264 ## If this uses the short-code format, return the parameters and corresponding property names.
265 func get_shortcode_parameters() -> Dictionary:
269 ## Returns a readable presentation of the event (This is how it's stored).
270 ## By default it uses a shortcode format, but can be overridden.
271 func to_text() -> String:
272 var shortcode := store_to_shortcode_parameters()
274 return "[" + self.get_shortcode() + " " + store_to_shortcode_parameters() + "]"
276 return "[" + self.get_shortcode() + "]"
279 ## Loads the variables from the string stored by [method to_text].
280 ## By default it uses the shortcode format, but can be overridden.
281 func from_text(string: String) -> void:
282 load_from_shortcode_parameters(string)
285 ## Returns a string with all the shortcode parameters.
286 func store_to_shortcode_parameters(params:Dictionary = {}) -> String:
287 if params.is_empty():
288 params = get_shortcode_parameters()
289 var custom_defaults: Dictionary = DialogicUtil.get_custom_event_defaults(event_name)
290 var result_string := ""
291 for parameter in params.keys():
292 var parameter_info: Dictionary = params[parameter]
293 var value: Variant = get(parameter_info.property)
294 var default_value: Variant = custom_defaults.get(parameter_info.property, parameter_info.default)
296 if parameter_info.get('custom_stored', false):
299 if "set_" + parameter_info.property in self and not get("set_" + parameter_info.property):
302 if typeof(value) == typeof(default_value) and value == default_value:
303 if not "set_" + parameter_info.property in self or not get("set_" + parameter_info.property):
306 result_string += " " + parameter + '="' + value_to_string(value, parameter_info.get("suggestions", Callable())) + '"'
308 return result_string.strip_edges()
311 func value_to_string(value: Variant, suggestions := Callable()) -> String:
312 var value_as_string := ""
315 value_as_string = str(value.resource_path)
318 value_as_string = value
320 TYPE_INT when suggestions.is_valid():
321 # HANDLE TEXT ALTERNATIVES FOR ENUMS
322 for option in suggestions.call().values():
323 if option.value != value:
326 if option.has('text_alt'):
327 value_as_string = option.text_alt[0]
329 value_as_string = var_to_str(option.value)
334 value_as_string = JSON.stringify(value)
337 value_as_string = var_to_str(value)
339 if not ((value_as_string.begins_with("[") and value_as_string.ends_with("]")) or (value_as_string.begins_with("{") and value_as_string.ends_with("}"))):
340 value_as_string.replace('"', '\\"')
342 return value_as_string
345 func load_from_shortcode_parameters(string:String) -> void:
346 var data: Dictionary = parse_shortcode_parameters(string)
347 var params: Dictionary = get_shortcode_parameters()
348 for parameter in params.keys():
349 var parameter_info: Dictionary = params[parameter]
350 if parameter_info.get('custom_stored', false):
353 if not parameter in data:
354 if "set_" + parameter_info.property in self:
355 set("set_" + parameter_info.property, false)
358 if "set_" + parameter_info.property in self:
359 set("set_" + parameter_info.property, true)
361 var param_value: String = data[parameter].replace('\\"', '"')
363 match typeof(get(parameter_info.property)):
368 # If a string is given
369 if parameter_info.has('suggestions'):
370 for option in parameter_info.suggestions.call().values():
371 if option.has('text_alt') and param_value in option.text_alt:
376 value = float(param_value)
379 value = str_to_var(param_value)
381 set(parameter_info.property, value)
383 ## Has to return `true`, if the given string can be interpreted as this event.
384 ## By default it uses the shortcode formta, but can be overridden.
385 func is_valid_event(string: String) -> bool:
386 if string.strip_edges().begins_with('['+get_shortcode()+' ') or string.strip_edges().begins_with('['+get_shortcode()+']'):
391 ## has to return true if this string seems to be a full event of this kind
392 ## (only tested if is_valid_event() returned true)
393 ## if a shortcode it used it will default to true if the string ends with ']'
394 func is_string_full_event(string: String) -> bool:
395 if get_shortcode() != 'default_shortcode': return string.strip_edges().ends_with(']')
399 ## Used to get all the shortcode parameters in a string as a dictionary.
400 func parse_shortcode_parameters(shortcode: String) -> Dictionary:
401 var regex := RegEx.new()
402 regex.compile(r'(?<parameter>[^\s=]*)\s*=\s*"(?<value>(\{[^}]*\}|\[[^]]*\]|([^"]|\\")*|))(?<!\\)\"')
404 for result in regex.search_all(shortcode):
405 dict[result.get_string('parameter')] = result.get_string('value')
411 #region EDITOR REPRESENTATION
412 ################################################################################
414 func _get_icon() -> Resource:
415 var _icon_file_name := "res://addons/dialogic/Editor/Images/Pieces/closed-icon.svg" # Default
416 # Check for both svg and png, but prefer svg if available
417 if ResourceLoader.exists(self.get_script().get_path().get_base_dir() + "/icon.svg"):
418 _icon_file_name = self.get_script().get_path().get_base_dir() + "/icon.svg"
419 elif ResourceLoader.exists(self.get_script().get_path().get_base_dir() + "/icon.png"):
420 _icon_file_name = self.get_script().get_path().get_base_dir() + "/icon.png"
421 return load(_icon_file_name)
424 func set_default_color(value:Variant) -> void:
425 dialogic_color_name = value
426 event_color = DialogicUtil.get_color(value)
429 ## Called when the resource is assigned to a event block in the visual editor
430 func _enter_visual_editor(_timeline_editor:DialogicEditor) -> void:
436 #region CODE COMPLETION
437 ################################################################################
439 ## This method can be overwritten to implement code completion for custom syntaxes
440 func _get_code_completion(_CodeCompletionHelper:Node, _TextNode:TextEdit, _line:String, _word:String, _symbol:String) -> void:
443 ## This method can be overwritten to add starting suggestions for this event
444 func _get_start_code_completion(_CodeCompletionHelper:Node, _TextNode:TextEdit) -> void:
450 #region SYNTAX HIGHLIGHTING
451 ################################################################################
453 func _get_syntax_highlighting(_Highlighter:SyntaxHighlighter, dict:Dictionary, _line:String) -> Dictionary:
460 ################################################################################
462 func get_event_editor_info() -> Array:
463 if Engine.is_editor_hint():
464 if editor_list != null:
475 ## to be overwritten by the sub_classes
476 func build_event_editor() -> void:
479 ## For the methods below the arguments are mostly similar:
480 ## @variable: String name of the property this field is for
481 ## @condition: String that will be executed as an expression. If it false
482 ## @editor_type: One of the ValueTypes (see ValueType enum). Defines type of field.
483 ## @left_text: Text that will be shown to the left of the field
484 ## @right_text: Text that will be shown to the right of the field
485 ## @extra_info: Allows passing a lot more info to the field.
486 ## What info can be passed is different for every field
488 func add_header_label(text:String, condition:= "") -> void:
490 "name" : "something",
491 "type" :+ TYPE_STRING,
492 "location" : Location.HEADER,
493 "usage" : PROPERTY_USAGE_EDITOR,
494 "field_type" : ValueType.LABEL,
495 "display_info" : {"text":text},
496 "condition" : condition
500 func add_header_edit(variable:String, editor_type := ValueType.LABEL, extra_info:= {}, condition:= "") -> void:
503 "type" : typeof(get(variable)),
504 "location" : Location.HEADER,
505 "usage" : PROPERTY_USAGE_DEFAULT,
506 "field_type" : editor_type,
507 "display_info" : extra_info,
508 "left_text" : extra_info.get('left_text', ''),
509 "right_text" : extra_info.get('right_text', ''),
510 "condition" : condition,
514 func add_header_button(text:String, callable:Callable, tooltip:String, icon: Variant = null, condition:= "") -> void:
517 "type" : TYPE_STRING,
518 "location" : Location.HEADER,
519 "usage" : PROPERTY_USAGE_DEFAULT,
520 "field_type" : ValueType.BUTTON,
521 "display_info" : {'text':text, 'tooltip':tooltip, 'callable':callable, 'icon':icon},
522 "condition" : condition,
526 func add_body_edit(variable:String, editor_type := ValueType.LABEL, extra_info:= {}, condition:= "") -> void:
529 "type" : typeof(get(variable)),
530 "location" : Location.BODY,
531 "usage" : PROPERTY_USAGE_DEFAULT,
532 "field_type" : editor_type,
533 "display_info" : extra_info,
534 "left_text" : extra_info.get('left_text', ''),
535 "right_text" : extra_info.get('right_text', ''),
536 "condition" : condition,
540 func add_body_line_break(condition:= "") -> void:
542 "name" : "linebreak",
544 "location" : Location.BODY,
545 "usage" : PROPERTY_USAGE_DEFAULT,
546 "condition" : condition,