]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Character/event_character.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Character / event_character.gd
1 @tool
2 class_name DialogicCharacterEvent
3 extends DialogicEvent
4 ## Event that allows to manipulate character portraits.
5
6 enum Actions {JOIN, LEAVE, UPDATE}
7
8 ### Settings
9
10 ## The type of action of this event (JOIN/LEAVE/UPDATE). See [Actions].
11 var action :=  Actions.JOIN
12 ## The character that will join/leave/update.
13 var character: DialogicCharacter = null
14 ## For Join/Update, this will be the portrait of the character that is shown.
15 ## Not used on Leave.
16 ## If empty, the default portrait will be used.
17 var portrait := ""
18 ## The index of the position this character should move to
19 var transform := "center"
20
21 ## Name of the animation script (extending DialogicAnimation).
22 ## On Join/Leave empty (default) will fallback to the animations set in the settings.
23 ## On Update empty will mean no animation.
24 var animation_name := ""
25 ## Length of the animation.
26 var animation_length: float = 0.5
27 ## How often the animation is repeated. Only for Update events.
28 var animation_repeats: int = 1
29 ## If true, the events waits for the animation to finish before the next event starts.
30 var animation_wait := false
31
32 ## The fade animation to use. If left empty, the default cross-fade animation AND time will be used.
33 var fade_animation := ""
34 var fade_length := 0.5
35
36 ## For Update only. If bigger then 0, the portrait will tween to the
37 ## new position (if changed) in this time (in seconds).
38 var transform_time: float = 0.0
39 var transform_ease := Tween.EaseType.EASE_IN_OUT
40 var transform_trans := Tween.TransitionType.TRANS_SINE
41
42 var ease_options := [
43                 {'label': 'In',          'value': Tween.EASE_IN},
44                 {'label': 'Out',         'value': Tween.EASE_OUT},
45                 {'label': 'In_Out', 'value': Tween.EASE_IN_OUT},
46                 {'label': 'Out_In', 'value': Tween.EASE_OUT_IN},
47                 ]
48
49 var trans_options := [
50                 {'label': 'Linear',     'value': Tween.TRANS_LINEAR},
51                 {'label': 'Sine',               'value': Tween.TRANS_SINE},
52                 {'label': 'Quint',              'value': Tween.TRANS_QUINT},
53                 {'label': 'Quart',              'value': Tween.TRANS_QUART},
54                 {'label': 'Quad',               'value': Tween.TRANS_QUAD},
55                 {'label': 'Expo',               'value': Tween.TRANS_EXPO},
56                 {'label': 'Elastic',    'value': Tween.TRANS_ELASTIC},
57                 {'label': 'Cubic',              'value': Tween.TRANS_CUBIC},
58                 {'label': 'Circ',               'value': Tween.TRANS_CIRC},
59                 {'label': 'Bounce',     'value': Tween.TRANS_BOUNCE},
60                 {'label': 'Back',               'value': Tween.TRANS_BACK},
61                 {'label': 'Spring',     'value': Tween.TRANS_SPRING}
62                 ]
63
64 ## The z_index that the portrait should have.
65 var z_index: int = 0
66 ## If true, the portrait will be set to mirrored.
67 var mirrored := false
68 ## If set, will be passed to the portrait scene.
69 var extra_data := ""
70
71
72 ### Helpers
73
74 ## Indicators for whether something should be updated (UPDATE mode only)
75 var set_portrait := false
76 var set_transform := false
77 var set_z_index := false
78 var set_mirrored := false
79 ## Used to set the character resource from the unique name identifier and vice versa
80 var character_identifier: String:
81         get:
82                 if character_identifier == '--All--':
83                         return '--All--'
84                 if character:
85                         var identifier := DialogicResourceUtil.get_unique_identifier(character.resource_path)
86                         if not identifier.is_empty():
87                                 return identifier
88                 return character_identifier
89         set(value):
90                 character_identifier = value
91                 character = DialogicResourceUtil.get_character_resource(value)
92                 if character and not character.portraits.has(portrait):
93                         portrait = ""
94                         ui_update_needed.emit()
95
96 var regex := RegEx.create_from_string(r'(?<type>join|update|leave)\s*(")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?<portrait>.*)\))?(\s*(?<transform>[^\[]*))?(\s*\[(?<shortcode>.*)\])?')
97
98 ################################################################################
99 ##                                              EXECUTION
100 ################################################################################
101
102 func _execute() -> void:
103         if not character and not character_identifier == "--All--":
104                 finish()
105                 return
106
107         # Calculate animation time (can be shortened during skipping)
108         var final_animation_length: float = animation_length
109         var final_position_move_time: float = transform_time
110         if dialogic.Inputs.auto_skip.enabled:
111                 var max_time: float = dialogic.Inputs.auto_skip.time_per_event
112                 final_animation_length = min(max_time, animation_length)
113                 final_position_move_time = min(max_time, transform_time)
114
115
116         # JOIN -------------------------------------
117         if action == Actions.JOIN:
118                 if dialogic.has_subsystem('History') and !dialogic.Portraits.is_character_joined(character):
119                         var character_name_text := dialogic.Text.get_character_name_parsed(character)
120                         dialogic.History.store_simple_history_entry(character_name_text + " joined", event_name, {'character': character_name_text, 'mode':'Join'})
121
122                 await dialogic.Portraits.join_character(
123                         character, portrait, transform,
124                         mirrored, z_index, extra_data,
125                         animation_name, final_animation_length, animation_wait)
126
127         # LEAVE -------------------------------------
128         elif action == Actions.LEAVE:
129                 if character_identifier == '--All--':
130                         if dialogic.has_subsystem('History') and len(dialogic.Portraits.get_joined_characters()):
131                                 dialogic.History.store_simple_history_entry("Everyone left", event_name, {'character': "All", 'mode':'Leave'})
132
133                         await dialogic.Portraits.leave_all_characters(
134                                 animation_name,
135                                 final_animation_length,
136                                 animation_wait
137                         )
138
139                 elif character:
140                         if dialogic.has_subsystem('History') and dialogic.Portraits.is_character_joined(character):
141                                 var character_name_text := dialogic.Text.get_character_name_parsed(character)
142                                 dialogic.History.store_simple_history_entry(character_name_text+" left", event_name, {'character': character_name_text, 'mode':'Leave'})
143
144                         await dialogic.Portraits.leave_character(
145                                 character,
146                                 animation_name,
147                                 final_animation_length,
148                                 animation_wait
149                         )
150
151         # UPDATE -------------------------------------
152         elif action == Actions.UPDATE:
153                 if not character or not dialogic.Portraits.is_character_joined(character):
154                         finish()
155                         return
156
157                 if set_portrait:
158                         dialogic.Portraits.change_character_portrait(character, portrait, fade_animation, fade_length)
159
160                 dialogic.Portraits.change_character_extradata(character, extra_data)
161
162                 if set_mirrored:
163                         dialogic.Portraits.change_character_mirror(character, mirrored)
164
165                 if set_z_index:
166                         dialogic.Portraits.change_character_z_index(character, z_index)
167
168                 if set_transform:
169                         dialogic.Portraits.move_character(character, transform, final_position_move_time, transform_ease, transform_trans)
170
171                 if animation_name:
172                         var final_animation_repetitions: int = animation_repeats
173
174                         if dialogic.Inputs.auto_skip.enabled:
175                                 var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event
176                                 var time_for_repetitions: float = time_per_event / animation_repeats
177                                 final_animation_length = time_for_repetitions
178
179                         var animation := dialogic.Portraits.animate_character(
180                                 character,
181                                 animation_name,
182                                 final_animation_length,
183                                 final_animation_repetitions,
184                         )
185
186                         if animation_wait:
187                                 dialogic.current_state = DialogicGameHandler.States.ANIMATING
188                                 await animation.finished
189                                 dialogic.current_state = DialogicGameHandler.States.IDLE
190
191
192         finish()
193
194
195 #region INITIALIZE
196 ###############################################################################
197
198 func _init() -> void:
199         event_name = "Character"
200         set_default_color('Color2')
201         event_category = "Main"
202         event_sorting_index = 2
203
204
205 func _get_icon() -> Resource:
206         return load(self.get_script().get_path().get_base_dir().path_join('icon.svg'))
207
208 #endregion
209
210 #region SAVING, LOADING, DEFAULTS
211 ################################################################################
212
213 func to_text() -> String:
214         var result_string := ""
215
216         # ACTIONS
217         match action:
218                 Actions.JOIN: result_string += "join "
219                 Actions.LEAVE: result_string += "leave "
220                 Actions.UPDATE: result_string += "update "
221
222         var default_values := DialogicUtil.get_custom_event_defaults(event_name)
223
224         # CHARACTER IDENTIFIER
225         if action == Actions.LEAVE and character_identifier == '--All--':
226                 result_string += "--All--"
227         elif character:
228                 var name := DialogicResourceUtil.get_unique_identifier(character.resource_path)
229
230                 if name.count(" ") > 0:
231                         name = '"' + name + '"'
232
233                 result_string += name
234
235                 # PORTRAIT
236                 if portrait.strip_edges() != default_values.get('portrait', ''):
237                         if action != Actions.LEAVE and (action != Actions.UPDATE or set_portrait):
238                                 result_string += " (" + portrait + ")"
239
240         # TRANSFORM
241         if action == Actions.JOIN or (action == Actions.UPDATE and set_transform):
242                 result_string += " " + str(transform)
243
244         # SETS:
245         if action == Actions.JOIN or action == Actions.LEAVE:
246                 set_mirrored = mirrored != default_values.get("mirrored", false)
247                 set_z_index = z_index != default_values.get("z_index", 0)
248
249         var shortcode := store_to_shortcode_parameters()
250
251         if shortcode != "":
252                 result_string += " [" + shortcode + "]"
253
254         return result_string
255
256
257 func from_text(string:String) -> void:
258         # Load default character
259         character = DialogicResourceUtil.get_character_resource(character_identifier)
260
261         var result := regex.search(string)
262
263         # ACTION
264         match result.get_string('type'):
265                 "join": action = Actions.JOIN
266                 "leave": action = Actions.LEAVE
267                 "update": action = Actions.UPDATE
268
269         # CHARACTER
270         var given_name := result.get_string('name').strip_edges()
271         var given_portrait := result.get_string('portrait').strip_edges()
272         var given_transform := result.get_string('transform').strip_edges()
273
274         if given_name:
275                 if action == Actions.LEAVE and given_name == "--All--":
276                         character_identifier = '--All--'
277                 else:
278                         character = DialogicResourceUtil.get_character_resource(given_name)
279
280         # PORTRAIT
281         if given_portrait:
282                 portrait = given_portrait.trim_prefix('(').trim_suffix(')')
283                 set_portrait = true
284
285         # TRANSFORM
286         if given_transform:
287                 transform = given_transform
288                 set_transform = true
289
290         # SHORTCODE
291         if not result.get_string('shortcode'):
292                 return
293
294         load_from_shortcode_parameters(result.get_string('shortcode'))
295
296
297 func get_shortcode_parameters() -> Dictionary:
298         return {
299                 #param_name     : property_info
300                 "action"                : {"property": "action",                                        "default": 0, "custom_stored":true,
301                                                         "suggestions": func(): return {'Join':
302                                                                                 {'value':Actions.JOIN},
303                                                                                 'Leave':{'value':Actions.LEAVE},
304                                                                                 'Update':{'value':Actions.UPDATE}}},
305                 "character"     : {"property": "character_identifier",  "default": "", "custom_stored":true,},
306                 "portrait"              : {"property": "portrait",                              "default": "", "custom_stored":true,},
307                 "transform"     : {"property": "transform",                     "default": "center", "custom_stored":true,},
308
309                 "animation"             : {"property": "animation_name",                        "default": ""},
310                 "length"                : {"property": "animation_length",                      "default": 0.5},
311                 "wait"                  : {"property": "animation_wait",                        "default": false},
312                 "repeat"                : {"property": "animation_repeats",             "default": 1},
313
314                 "z_index"               : {"property": "z_index",                                               "default": 0},
315                 "mirrored"              : {"property": "mirrored",                                              "default": false},
316                 "fade"                  : {"property": "fade_animation",                                "default":""},
317                 "fade_length"   : {"property": "fade_length",                                   "default":0.5},
318                 "move_time"             : {"property": "transform_time",                                "default": 0.0},
319                 "move_ease"     : {"property": "transform_ease",        "default": Tween.EaseType.EASE_IN_OUT,
320                                                                 "suggestions": func(): return list_to_suggestions(ease_options)},
321                 "move_trans"    : {"property": "transform_trans",       "default": Tween.TransitionType.TRANS_SINE,
322                                                                 "suggestions": func(): return list_to_suggestions(trans_options)},
323                 "extra_data"    : {"property": "extra_data",                                    "default": ""},
324         }
325
326
327 func is_valid_event(string:String) -> bool:
328         if string.begins_with("join") or string.begins_with("leave") or string.begins_with("update"):
329                 return true
330         return false
331
332 #endregion
333
334 ################################################################################
335 ##                                              EDITOR REPRESENTATION
336 ################################################################################
337
338 func build_event_editor() -> void:
339         add_header_edit('action', ValueType.FIXED_OPTIONS, {
340                 'options': [
341                         {
342                                 'label': 'Join',
343                                 'value': Actions.JOIN,
344                                 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/join.svg")
345                         },
346                         {
347                                 'label': 'Leave',
348                                 'value': Actions.LEAVE,
349                                 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/leave.svg")
350                         },
351                         {
352                                 'label': 'Update',
353                                 'value': Actions.UPDATE,
354                                 'icon': load("res://addons/dialogic/Editor/Images/Dropdown/update.svg")
355                         }
356                 ]
357         })
358         add_header_edit('character_identifier', ValueType.DYNAMIC_OPTIONS,
359                         {'placeholder'          : 'Character',
360                         'file_extension'        : '.dch',
361                         'mode'                          : 2,
362                         'suggestions_func'      : get_character_suggestions,
363                         'icon'                          : load("res://addons/dialogic/Editor/Images/Resources/character.svg"),
364                         'autofocus'                     : true})
365
366         add_header_edit('set_portrait', ValueType.BOOL_BUTTON,
367                         {'icon':load("res://addons/dialogic/Modules/Character/update_portrait.svg"),
368                          'tooltip':'Change Portrait'}, "should_show_portrait_selector() and action == Actions.UPDATE")
369         add_header_edit('portrait', ValueType.DYNAMIC_OPTIONS,
370                         {'placeholder'          : 'Default',
371                         'collapse_when_empty':true,
372                         'suggestions_func'      : get_portrait_suggestions,
373                         'icon'                          : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")},
374                         'should_show_portrait_selector() and (action != Actions.UPDATE or set_portrait)')
375         add_header_edit('set_transform', ValueType.BOOL_BUTTON,
376                         {'icon': load("res://addons/dialogic/Modules/Character/update_position.svg"), 'tooltip':'Change Position'}, "character != null and !has_no_portraits() and action == Actions.UPDATE")
377         add_header_label('at position', 'character != null and !has_no_portraits() and action == Actions.JOIN')
378         add_header_label('to position', 'character != null and !has_no_portraits() and action == Actions.UPDATE and set_transform')
379         add_header_edit('transform', ValueType.DYNAMIC_OPTIONS,
380                         {'placeholder'          : 'center',
381                         'mode'                          : 0,
382                         'suggestions_func'      : get_position_suggestions,
383                         'tooltip'               : "You can use a predefined position or a custom transform like 'pos=x0.5y1 size=x0.5y1 rot=10'.\nLearn more about this in the documentation."},
384                         'character != null and !has_no_portraits() and action != %s and (action != Actions.UPDATE or set_transform)' %Actions.LEAVE)
385
386         # Body
387         add_body_edit('fade_animation', ValueType.DYNAMIC_OPTIONS,
388                         {'left_text'            : 'Fade:',
389                         'suggestions_func'      : get_fade_suggestions,
390                         'editor_icon'                   : ["Animation", "EditorIcons"],
391                         'placeholder'                   : 'Default',
392                         'enable_pretty_name'    : true},
393                         'should_show_fade_options()')
394         add_body_edit('fade_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
395                         'should_show_fade_options() and !fade_animation.is_empty()')
396         add_body_line_break("should_show_fade_options()")
397         add_body_edit('animation_name', ValueType.DYNAMIC_OPTIONS,
398                         {'left_text'            : 'Animation:',
399                         'suggestions_func'      : get_animation_suggestions,
400                         'editor_icon'                   : ["Animation", "EditorIcons"],
401                         'placeholder'                   : 'Default',
402                         'enable_pretty_name'    : true},
403                         'should_show_animation_options()')
404         add_body_edit('animation_length', ValueType.NUMBER, {'left_text':'Length:', 'suffix':'s', "min":0},
405                         'should_show_animation_options() and !animation_name.is_empty()')
406         add_body_edit('animation_wait', ValueType.BOOL, {'left_text':'Await end:'},
407                         'should_show_animation_options() and !animation_name.is_empty()')
408         add_body_edit('animation_repeats', ValueType.NUMBER, {'left_text':'Repeat:', 'mode':1, "min":1},
409                         'should_show_animation_options() and !animation_name.is_empty() and action == %s)' %Actions.UPDATE)
410         add_body_line_break()
411         add_body_edit('transform_time', ValueType.NUMBER, {'left_text':'Movement duration:', "min":0},
412                         "should_show_transform_options()")
413         add_body_edit("transform_trans", ValueType.FIXED_OPTIONS, {'options':trans_options, 'left_text':"Trans:"}, 'should_show_transform_options() and transform_time > 0')
414         add_body_edit("transform_ease", ValueType.FIXED_OPTIONS, {'options':ease_options, 'left_text':"Ease:"}, 'should_show_transform_options() and transform_time > 0')
415
416         add_body_edit('set_z_index', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_z_index.svg"), 'tooltip':'Change Z-Index'}, "character != null and action == Actions.UPDATE")
417         add_body_edit('z_index', ValueType.NUMBER, {'left_text':'Z-index:', 'mode':1},
418                         'action != %s and (action != Actions.UPDATE or set_z_index)' %Actions.LEAVE)
419         add_body_edit('set_mirrored', ValueType.BOOL_BUTTON, {'icon':load("res://addons/dialogic/Modules/Character/update_mirror.svg"), 'tooltip':'Change Mirroring'}, "character != null and action == Actions.UPDATE")
420         add_body_edit('mirrored', ValueType.BOOL, {'left_text':'Mirrored:'},
421                         'action != %s and (action != Actions.UPDATE or set_mirrored)' %Actions.LEAVE)
422         add_body_edit('extra_data', ValueType.SINGLELINE_TEXT, {'left_text':'Extra Data:'}, 'action != Actions.LEAVE')
423
424
425 func should_show_transform_options() -> bool:
426         return action == Actions.UPDATE and set_transform
427
428
429 func should_show_animation_options() -> bool:
430         return (character and !character.portraits.is_empty()) or character_identifier == '--All--'
431
432
433 func should_show_fade_options() -> bool:
434         return action == Actions.UPDATE and set_portrait and character and not character.portraits.is_empty()
435
436
437 func should_show_portrait_selector() -> bool:
438         return character and len(character.portraits) > 1 and action != Actions.LEAVE
439
440
441 func has_no_portraits() -> bool:
442         return character and character.portraits.is_empty()
443
444
445 func get_character_suggestions(search_text:String) -> Dictionary:
446         return DialogicUtil.get_character_suggestions(search_text, character, false, action == Actions.LEAVE, editor_node)
447
448
449 func get_portrait_suggestions(search_text:String) -> Dictionary:
450         var empty_text := "Don't Change"
451         if action == Actions.JOIN:
452                 empty_text = "Default portrait"
453         return DialogicUtil.get_portrait_suggestions(search_text, character, true, empty_text)
454
455
456 func get_position_suggestions(search_text:String='') -> Dictionary:
457         return DialogicUtil.get_portrait_position_suggestions(search_text)
458
459
460 func get_animation_suggestions(search_text:String='') -> Dictionary:
461         var DPAU := DialogicPortraitAnimationUtil
462         match action:
463                 Actions.JOIN:
464                         return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.IN)
465                 Actions.LEAVE:
466                         return DPAU.get_suggestions(search_text, animation_name, "Default", DPAU.AnimationType.OUT)
467                 Actions.UPDATE:
468                         return DPAU.get_suggestions(search_text, animation_name, "None", DPAU.AnimationType.ACTION)
469         return {}
470
471
472 func get_fade_suggestions(search_text:String='') -> Dictionary:
473         return DialogicPortraitAnimationUtil.get_suggestions(search_text, fade_animation, "Default", DialogicPortraitAnimationUtil.AnimationType.CROSSFADE)
474
475
476 ####################### CODE COMPLETION ########################################
477 ################################################################################
478
479 func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:String, _word:String, symbol:String) -> void:
480         var line_until_caret: String = CodeCompletionHelper.get_line_untill_caret(line)
481         if symbol == ' ' and line_until_caret.count(' ') == 1:
482                 CodeCompletionHelper.suggest_characters(TextNode, CodeEdit.KIND_MEMBER)
483                 if line.begins_with('leave'):
484                         TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, 'All', '--All-- ', event_color, TextNode.get_theme_icon("GuiEllipsis", "EditorIcons"))
485
486         if symbol == '(':
487                 var completion_character := regex.search(line).get_string('name')
488                 CodeCompletionHelper.suggest_portraits(TextNode, completion_character)
489
490         elif not '[' in line_until_caret and symbol == ' ':
491                 if not line.begins_with("leave"):
492                         for position in get_position_suggestions():
493                                 TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, position, position+' ', TextNode.syntax_highlighter.normal_color)
494
495         # Shortcode Part
496         if '[' in line_until_caret:
497                 # Suggest Parameters
498                 if symbol == '[' or symbol == ' ' and line_until_caret.count('"')%2 == 0:# and (symbol == "[" or (symbol == " " and line_until_caret.rfind('="') < line_until_caret.rfind('"')-1)):
499                         suggest_parameter("animation", line, TextNode)
500
501                         if "animation=" in line:
502                                 for param in ["length", "wait"]:
503                                         suggest_parameter(param, line, TextNode)
504                                 if line.begins_with('update'):
505                                         suggest_parameter("repeat", line, TextNode)
506                         if line.begins_with("update"):
507                                 for param in ["move_time", "move_trans", "move_ease"]:
508                                         suggest_parameter(param, line, TextNode)
509                         if not line.begins_with('leave'):
510                                 for param in ["mirrored", "z_index", "extra_data"]:
511                                         suggest_parameter(param, line, TextNode)
512
513                 # Suggest Values
514                 else:
515                         var current_param: RegExMatch = CodeCompletionHelper.completion_shortcode_param_getter_regex.search(line)
516                         if not current_param:
517                                 return
518
519                         match current_param.get_string("param"):
520                                 "animation":
521                                         var animations := {}
522                                         if line.begins_with('join'):
523                                                 animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.IN)
524                                         elif line.begins_with('update'):
525                                                 animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.ACTION)
526                                         elif line.begins_with('leave'):
527                                                 animations = DialogicPortraitAnimationUtil.get_portrait_animations_filtered(DialogicPortraitAnimationUtil.AnimationType.OUT)
528
529                                         for script: String  in animations:
530                                                 TextNode.add_code_completion_option(CodeEdit.KIND_VARIABLE, DialogicUtil.pretty_name(script), DialogicUtil.pretty_name(script), TextNode.syntax_highlighter.normal_color, null, '" ')
531
532                                 "wait", "mirrored":
533                                         CodeCompletionHelper.suggest_bool(TextNode, TextNode.syntax_highlighter.normal_color)
534                                 "move_trans":
535                                         CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(trans_options), TextNode, TextNode.syntax_highlighter.normal_color)
536                                 "move_ease":
537                                         CodeCompletionHelper.suggest_custom_suggestions(list_to_suggestions(ease_options), TextNode, TextNode.syntax_highlighter.normal_color)
538
539
540 func suggest_parameter(parameter:String, line:String, TextNode:TextEdit) -> void:
541         if not parameter + "=" in line:
542                 TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, parameter, parameter + '="', TextNode.syntax_highlighter.normal_color)
543
544
545 func _get_start_code_completion(_CodeCompletionHelper:Node, TextNode:TextEdit) -> void:
546         TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'join', 'join ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/join.svg'))
547         TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'leave', 'leave ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/leave.svg'))
548         TextNode.add_code_completion_option(CodeEdit.KIND_PLAIN_TEXT, 'update', 'update ', event_color, load('res://addons/dialogic/Editor/Images/Dropdown/update.svg'))
549
550
551 #################### SYNTAX HIGHLIGHTING #######################################
552 ################################################################################
553
554 func _get_syntax_highlighting(Highlighter:SyntaxHighlighter, dict:Dictionary, line:String) -> Dictionary:
555         var word := line.get_slice(' ', 0)
556
557         dict[line.find(word)] = {"color":event_color}
558         dict[line.find(word)+len(word)] = {"color":Highlighter.normal_color}
559         var result := regex.search(line)
560         if result.get_string('name'):
561                 dict[result.get_start('name')] = {"color":event_color.lerp(Highlighter.normal_color, 0.5)}
562                 dict[result.get_end('name')] = {"color":Highlighter.normal_color}
563         if result.get_string('portrait'):
564                 dict[result.get_start('portrait')] = {"color":event_color.lerp(Highlighter.normal_color, 0.6)}
565                 dict[result.get_end('portrait')] = {"color":Highlighter.normal_color}
566         if result.get_string('shortcode'):
567                 dict = Highlighter.color_shortcode_content(dict, line, result.get_start('shortcode'), result.get_end('shortcode'), event_color)
568         return dict
569
570
571 ## HELPER
572 func list_to_suggestions(list:Array) -> Dictionary:
573         return list.reduce(
574                 func(accum, value):
575                         accum[value.label] = value
576                         accum[value.label]["text_alt"] = [value.label.to_lower()]
577                         return accum,
578                 {})