No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dialog_node.gd 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. tool
  2. extends Control
  3. var last_mouse_mode = null
  4. var input_next: String = 'ui_accept'
  5. var dialog_index: int = 0
  6. var finished: bool = false
  7. var text_speed = 0.02 # Higher = lower speed
  8. var waiting_for_answer: bool = false
  9. var waiting_for_input: bool = false
  10. var waiting = false
  11. var preview = false
  12. var definitions = {}
  13. var definition_visible = false
  14. var settings
  15. var current_theme
  16. var current_timeline := ''
  17. ## The timeline to load when starting the scene
  18. export(String, "TimelineDropdown") var timeline: String
  19. ## Should we clear saved data (definitions and timeline progress) on start?
  20. export(bool) var reset_saves = true
  21. ## Should we show debug information when running?
  22. export(bool) var debug_mode = true
  23. signal event_start(type, event)
  24. signal event_end(type)
  25. signal dialogic_signal(value)
  26. var dialog_resource
  27. var characters
  28. onready var ChoiceButton = load("res://addons/dialogic/Nodes/ChoiceButton.tscn")
  29. onready var Portrait = load("res://addons/dialogic/Nodes/Portrait.tscn")
  30. var dialog_script = {}
  31. var questions #for keeping track of the questions answered
  32. func _ready():
  33. # Loading the config files
  34. load_config_files()
  35. # Checking if the dialog should read the code from a external file
  36. if not timeline.empty():
  37. dialog_script = set_current_dialog(timeline)
  38. elif dialog_script.keys().size() == 0:
  39. dialog_script = {
  40. "events":[{"character":"","portrait":"",
  41. "text":"[Dialogic Error] No timeline specified."}]
  42. }
  43. # Connecting resize signal
  44. get_viewport().connect("size_changed", self, "resize_main")
  45. resize_main()
  46. # Setting everything up for the node to be default
  47. $TextBubble/NameLabel.text = ''
  48. $Background.visible = false
  49. $TextBubble/RichTextLabel.meta_underlined = false
  50. $DefinitionInfo.visible = false
  51. # Getting the character information
  52. characters = DialogicUtil.get_character_list()
  53. if not Engine.is_editor_hint():
  54. load_dialog()
  55. func load_config_files():
  56. if not Engine.is_editor_hint():
  57. if reset_saves:
  58. DialogicSingleton.init(reset_saves)
  59. definitions = DialogicSingleton.get_definitions()
  60. else:
  61. definitions = DialogicResources.get_default_definitions()
  62. settings = DialogicResources.get_settings_config()
  63. var theme_file = 'res://addons/dialogic/Editor/ThemeEditor/default-theme.cfg'
  64. if settings.has_section('theme'):
  65. theme_file = settings.get_value('theme', 'default')
  66. current_theme = load_theme(theme_file)
  67. func resize_main():
  68. # This function makes sure that the dialog is displayed at the correct
  69. # size and position in the screen.
  70. if Engine.is_editor_hint() == false:
  71. set_global_position(Vector2(0,0))
  72. if ProjectSettings.get_setting("display/window/stretch/mode") != '2d':
  73. set_deferred('rect_size', get_viewport().size)
  74. dprint("Viewport", get_viewport().size)
  75. $TextBubble.rect_position.x = (rect_size.x / 2) - ($TextBubble.rect_size.x / 2)
  76. $TextBubble.rect_position.y = (rect_size.y) - ($TextBubble.rect_size.y) - current_theme.get_value('box', 'bottom_gap', 40)
  77. func set_current_dialog(dialog_path: String):
  78. current_timeline = dialog_path
  79. var dialog_script = DialogicResources.get_timeline_json(dialog_path)
  80. # All this parse events should be happening in the same loop ideally
  81. # But until performance is not an issue I will probably stay lazy
  82. # And keep adding different functions for each parsing operation.
  83. if settings.has_section_key('dialog', 'auto_color_names'):
  84. if settings.get_value('dialog', 'auto_color_names'):
  85. dialog_script = parse_characters(dialog_script)
  86. else:
  87. dialog_script = parse_characters(dialog_script)
  88. dialog_script = parse_text_lines(dialog_script)
  89. dialog_script = parse_branches(dialog_script)
  90. return dialog_script
  91. func parse_characters(dialog_script):
  92. var names = DialogicUtil.get_character_list()
  93. # I should use regex here, but this is way easier :)
  94. if names.size() > 0:
  95. var index = 0
  96. for t in dialog_script['events']:
  97. if t.has('text'):
  98. for n in names:
  99. if n.has('name'):
  100. dialog_script['events'][index]['text'] = t['text'].replace(n['name'],
  101. '[color=#' + n['color'].to_html() + ']' + n['name'] + '[/color]'
  102. )
  103. index += 1
  104. return dialog_script
  105. func parse_text_lines(unparsed_dialog_script: Dictionary) -> Dictionary:
  106. var parsed_dialog: Dictionary = unparsed_dialog_script
  107. var new_events: Array = []
  108. var alignment = 'Left'
  109. var split_new_lines = true
  110. var remove_empty_messages = true
  111. # Return the same thing if it doesn't have events
  112. if unparsed_dialog_script.has('events') == false:
  113. return unparsed_dialog_script
  114. # Getting extra settings
  115. if settings.has_section_key('dialog', 'remove_empty_messages'):
  116. remove_empty_messages = settings.get_value('dialog', 'remove_empty_messages')
  117. if settings.has_section_key('dialog', 'new_lines'):
  118. split_new_lines = settings.get_value('dialog', 'new_lines')
  119. if current_theme != null:
  120. alignment = current_theme.get_value('text', 'alignment', 'Left')
  121. dprint('preview ', preview)
  122. # Parsing
  123. for event in unparsed_dialog_script['events']:
  124. if event.has('text') and event.has('character') and event.has('portrait'):
  125. if event['text'] == '' and remove_empty_messages == true:
  126. pass
  127. elif '\n' in event['text'] and preview == false and split_new_lines == true:
  128. var lines = event['text'].split('\n')
  129. var i = 0
  130. for line in lines:
  131. var text = lines[i]
  132. if alignment == 'Center':
  133. text = '[center]' + lines[i] + '[/center]'
  134. elif alignment == 'Right':
  135. text = '[right]' + lines[i] + '[/right]'
  136. var _e = {
  137. 'text': text,
  138. 'character': event['character'],
  139. 'portrait': event['portrait']
  140. }
  141. new_events.append(_e)
  142. i += 1
  143. else:
  144. var text = event['text']
  145. if alignment == 'Center':
  146. event['text'] = '[center]' + text + '[/center]'
  147. elif alignment == 'Right':
  148. event['text'] = '[right]' + text + '[/right]'
  149. new_events.append(event)
  150. else:
  151. new_events.append(event)
  152. parsed_dialog['events'] = new_events
  153. return parsed_dialog
  154. func parse_branches(dialog_script: Dictionary) -> Dictionary:
  155. questions = [] # Resetting the questions
  156. # Return the same thing if it doesn't have events
  157. if dialog_script.has('events') == false:
  158. return dialog_script
  159. var parser_queue = [] # This saves the last question opened, and it gets removed once it was consumed by a endbranch event
  160. var event_id: int = 0 # The current id for jumping later on
  161. var question_id: int = 0 # identifying the questions to assign options to it
  162. for event in dialog_script['events']:
  163. if event.has('question'):
  164. event['event_id'] = event_id
  165. event['question_id'] = question_id
  166. event['answered'] = false
  167. question_id += 1
  168. questions.append(event)
  169. parser_queue.append(event)
  170. if event.has('condition'):
  171. event['event_id'] = event_id
  172. event['question_id'] = question_id
  173. event['answered'] = false
  174. question_id += 1
  175. questions.append(event)
  176. parser_queue.append(event)
  177. if event.has('choice'):
  178. var opened_branch = parser_queue.back()
  179. dialog_script['events'][opened_branch['event_id']]['options'].append({
  180. 'question_id': opened_branch['question_id'],
  181. 'label': event['choice'],
  182. 'event_id': event_id,
  183. })
  184. event['question_id'] = opened_branch['question_id']
  185. if event.has('endbranch'):
  186. event['event_id'] = event_id
  187. var opened_branch = parser_queue.pop_back()
  188. event['end_branch_of'] = opened_branch['question_id']
  189. dialog_script['events'][opened_branch['event_id']]['end_id'] = event_id
  190. event_id += 1
  191. return dialog_script
  192. func parse_definitions(text: String):
  193. var words = []
  194. if Engine.is_editor_hint():
  195. # Loading variables again to avoid issues in the preview dialog
  196. load_config_files()
  197. var final_text: String;
  198. final_text = _insert_variable_definitions(text)
  199. final_text = _insert_glossary_definitions(final_text)
  200. return final_text
  201. func _insert_variable_definitions(text: String):
  202. var final_text := text;
  203. for d in definitions['variables']:
  204. var name : String = d['name'];
  205. final_text = final_text.replace('[' + name + ']', d['value'])
  206. return final_text;
  207. func _insert_glossary_definitions(text: String):
  208. var color = self.current_theme.get_value('definitions', 'color', '#ffbebebe')
  209. var final_text := text;
  210. # I should use regex here, but this is way easier :)
  211. for d in definitions['glossary']:
  212. final_text = final_text.replace(d['name'],
  213. '[url=' + d['id'] + ']' +
  214. '[color=' + color + ']' + d['name'] + '[/color]' +
  215. '[/url]'
  216. )
  217. return final_text;
  218. func _process(delta):
  219. $TextBubble/NextIndicator.visible = finished
  220. if not Engine.is_editor_hint():
  221. # Multiple choices
  222. if waiting_for_answer:
  223. $Options.visible = finished
  224. else:
  225. $Options.visible = false
  226. func _input(event: InputEvent) -> void:
  227. if not Engine.is_editor_hint() and event.is_action_pressed(input_next) and not waiting:
  228. if $TextBubble/Tween.is_active():
  229. # Skip to end if key is pressed during the text animation
  230. $TextBubble/Tween.seek(999)
  231. finished = true
  232. else:
  233. if waiting_for_answer == false and waiting_for_input == false:
  234. load_dialog()
  235. if settings.has_section_key('dialog', 'propagate_input'):
  236. var propagate_input: bool = settings.get_value('dialog', 'propagate_input')
  237. if not propagate_input:
  238. get_tree().set_input_as_handled()
  239. func show_dialog():
  240. visible = true
  241. func start_text_tween():
  242. # This will start the animation that makes the text appear letter by letter
  243. var tween_duration = text_speed * $TextBubble/RichTextLabel.get_total_character_count()
  244. $TextBubble/Tween.interpolate_property(
  245. $TextBubble/RichTextLabel, "percent_visible", 0, 1, tween_duration,
  246. Tween.TRANS_LINEAR, Tween.EASE_IN_OUT
  247. )
  248. $TextBubble/Tween.start()
  249. func update_name(character, color: Color = Color.white) -> void:
  250. if character.has('name'):
  251. var parsed_name = character['name']
  252. if character.has('display_name'):
  253. if character['display_name'] != '':
  254. parsed_name = character['display_name']
  255. if character.has('color'):
  256. color = character['color']
  257. parsed_name = parse_definitions(parsed_name)
  258. $TextBubble/NameLabel.visible = true
  259. # Hack to reset the size
  260. $TextBubble/NameLabel.rect_min_size = Vector2(0, 0)
  261. $TextBubble/NameLabel.rect_size = Vector2(-1, 40)
  262. # Setting the color and text
  263. $TextBubble/NameLabel.text = parsed_name
  264. $TextBubble/NameLabel.set('custom_colors/font_color', color)
  265. else:
  266. $TextBubble/NameLabel.visible = false
  267. func update_text(text):
  268. # Updating the text and starting the animation from 0
  269. $TextBubble/RichTextLabel.bbcode_text = parse_definitions(text)
  270. $TextBubble/RichTextLabel.percent_visible = 0
  271. # The call to this function needs to be deferred.
  272. # More info: https://github.com/godotengine/godot/issues/36381
  273. call_deferred("start_text_tween")
  274. return true
  275. func on_timeline_start():
  276. if not Engine.is_editor_hint():
  277. DialogicSingleton.save_definitions()
  278. DialogicSingleton.set_current_timeline(current_timeline)
  279. emit_signal("event_start", "timeline", current_timeline)
  280. func on_timeline_end():
  281. if not Engine.is_editor_hint():
  282. DialogicSingleton.save_definitions()
  283. DialogicSingleton.set_current_timeline('')
  284. emit_signal("event_end", "timeline")
  285. func load_dialog(skip_add = false):
  286. # Emitting signals
  287. if dialog_script.has('events'):
  288. if dialog_index == 0:
  289. on_timeline_start()
  290. elif dialog_index == dialog_script['events'].size():
  291. on_timeline_end()
  292. # Hiding definitions popup
  293. definition_visible = false
  294. $DefinitionInfo.visible = definition_visible
  295. # This will load the next entry in the dialog_script array.
  296. if dialog_script.has('events'):
  297. if dialog_index < dialog_script['events'].size():
  298. event_handler(dialog_script['events'][dialog_index])
  299. else:
  300. if Engine.is_editor_hint() == false:
  301. queue_free()
  302. if skip_add == false:
  303. dialog_index += 1
  304. func reset_dialog_extras():
  305. $TextBubble/NameLabel.text = ''
  306. $TextBubble/NameLabel.visible = false
  307. func get_character(character_id):
  308. for c in characters:
  309. if c['file'] == character_id:
  310. return c
  311. return {}
  312. func event_handler(event: Dictionary):
  313. # Handling an event and updating the available nodes accordingly.
  314. reset_dialog_extras()
  315. dprint('[D] Current Event: ', event)
  316. match event:
  317. {'text', 'character', 'portrait'}:
  318. emit_signal("event_start", "text", event)
  319. show_dialog()
  320. finished = false
  321. var character_data = get_character(event['character'])
  322. update_name(character_data)
  323. grab_portrait_focus(character_data, event)
  324. update_text(event['text'])
  325. {'question', 'question_id', 'options', ..}:
  326. emit_signal("event_start", "question", event)
  327. show_dialog()
  328. finished = false
  329. waiting_for_answer = true
  330. if event.has('name'):
  331. update_name(event['name'])
  332. update_text(event['question'])
  333. if event.has('options'):
  334. for o in event['options']:
  335. add_choice_button(o)
  336. {'choice', 'question_id'}:
  337. emit_signal("event_start", "choice", event)
  338. for q in questions:
  339. if q['question_id'] == event['question_id']:
  340. if q['answered']:
  341. # If the option is for an answered question, skip to the end of it.
  342. dialog_index = q['end_id']
  343. load_dialog(true)
  344. {'input', ..}:
  345. emit_signal("event_start", "input", event)
  346. show_dialog()
  347. finished = false
  348. waiting_for_input = true
  349. update_text(event['input'])
  350. $TextInputDialog.window_title = event['window_title']
  351. $TextInputDialog.popup_centered()
  352. $TextInputDialog.connect("confirmed", self, "_on_input_set", [event['variable']])
  353. {'action', ..}:
  354. emit_signal("event_start", "action", event)
  355. if event['action'] == 'leaveall':
  356. if event['character'] == '[All]':
  357. for p in $Portraits.get_children():
  358. p.fade_out()
  359. else:
  360. for p in $Portraits.get_children():
  361. if p.character_data['file'] == event['character']:
  362. p.fade_out()
  363. go_to_next_event()
  364. elif event['action'] == 'join':
  365. if event['character'] == '':
  366. go_to_next_event()
  367. else:
  368. var character_data = get_character(event['character'])
  369. var exists = grab_portrait_focus(character_data)
  370. if exists == false:
  371. var p = Portrait.instance()
  372. var char_portrait = event['portrait']
  373. if char_portrait == '':
  374. char_portrait = 'Default'
  375. p.character_data = character_data
  376. p.init(char_portrait, get_character_position(event['position']))
  377. $Portraits.add_child(p)
  378. p.fade_in()
  379. go_to_next_event()
  380. {'scene'}:
  381. get_tree().change_scene(event['scene'])
  382. {'background'}:
  383. emit_signal("event_start", "background", event)
  384. $Background.visible = true
  385. $Background.texture = load(event['background'])
  386. go_to_next_event()
  387. {'audio'}, {'audio', 'file'}:
  388. emit_signal("event_start", "audio", event)
  389. if event['audio'] == 'play' and 'file' in event.keys() and not event['file'].empty():
  390. $FX/AudioStreamPlayer.stream = load(event['file'])
  391. $FX/AudioStreamPlayer.play()
  392. else:
  393. $FX/AudioStreamPlayer.stop()
  394. go_to_next_event()
  395. {'background-music'}, {'background-music', 'file'}:
  396. emit_signal("event_start", "background-music", event)
  397. if event['background-music'] == 'play' and 'file' in event.keys() and not event['file'].empty():
  398. $FX/BackgroundMusic.crossfade_to(event['file'])
  399. else:
  400. $FX/BackgroundMusic.fade_out()
  401. go_to_next_event()
  402. {'endbranch', ..}:
  403. emit_signal("event_start", "endbranch", event)
  404. go_to_next_event()
  405. {'change_scene'}:
  406. get_tree().change_scene(event['change_scene'])
  407. {'emit_signal', ..}:
  408. dprint('[!] Emitting signal: dialogic_signal(', event['emit_signal'], ')')
  409. emit_signal("dialogic_signal", event['emit_signal'])
  410. go_to_next_event()
  411. {'close_dialog'}:
  412. emit_signal("event_start", "close_dialog", event)
  413. on_timeline_end()
  414. queue_free()
  415. {'set_theme'}:
  416. emit_signal("event_start", "set_theme", event)
  417. if event['set_theme'] != '':
  418. current_theme = load_theme(event['set_theme'])
  419. go_to_next_event()
  420. {'wait_seconds'}:
  421. emit_signal("event_start", "wait", event)
  422. wait_seconds(event['wait_seconds'])
  423. waiting = true
  424. {'change_timeline'}:
  425. dialog_script = set_current_dialog(event['change_timeline'])
  426. dialog_index = -1
  427. go_to_next_event()
  428. {'condition', 'definition', 'value', 'question_id', ..}:
  429. # Treating this conditional as an option on a regular question event
  430. var def_value = null
  431. var current_question = questions[event['question_id']]
  432. for d in definitions['variables']:
  433. if d['id'] == event['definition']:
  434. def_value = d['value']
  435. var condition_met = def_value != null and _compare_definitions(def_value, event['value'], event['condition']);
  436. current_question['answered'] = !condition_met
  437. if !condition_met:
  438. # condition not met, skipping branch
  439. dialog_index = current_question['end_id']
  440. load_dialog(true)
  441. else:
  442. # condition met, entering branch
  443. go_to_next_event()
  444. {'set_value', 'definition', ..}:
  445. emit_signal("event_start", "set_value", event)
  446. var operation = '='
  447. if 'operation' in event and not event['operation'].empty():
  448. operation = event["operation"]
  449. DialogicSingleton.set_variable_from_id(event['definition'], event['set_value'], operation)
  450. go_to_next_event()
  451. _:
  452. visible = false
  453. dprint('Other event. ', event)
  454. func _on_input_set(variable):
  455. var input_value = $TextInputDialog/LineEdit.text
  456. if input_value == '':
  457. $TextInputDialog.popup_centered()
  458. else:
  459. dialog_resource.custom_variables[variable] = input_value
  460. waiting_for_input = false
  461. $TextInputDialog/LineEdit.text = ''
  462. $TextInputDialog.disconnect("confirmed", self, '_on_input_set')
  463. $TextInputDialog.visible = false
  464. load_dialog()
  465. dprint('[!] Input selected: ', input_value)
  466. dprint('[!] dialog variables: ', dialog_resource.custom_variables)
  467. func reset_options():
  468. # Clearing out the options after one was selected.
  469. for option in $Options.get_children():
  470. option.queue_free()
  471. func add_choice_button(option):
  472. var theme = current_theme
  473. var button = ChoiceButton.instance()
  474. button.text = option['label']
  475. # Text
  476. button.set('custom_fonts/font', load(theme.get_value('text', 'font', "res://addons/dialogic/Fonts/DefaultFont.tres")))
  477. var text_color = Color(theme.get_value('text', 'color', "#ffffffff"))
  478. button.set('custom_colors/font_color', text_color)
  479. button.set('custom_colors/font_color_hover', text_color)
  480. button.set('custom_colors/font_color_pressed', text_color)
  481. if theme.get_value('buttons', 'text_color_enabled', true):
  482. var button_text_color = Color(theme.get_value('buttons', 'text_color', "#ffffffff"))
  483. button.set('custom_colors/font_color', button_text_color)
  484. button.set('custom_colors/font_color_hover', button_text_color)
  485. button.set('custom_colors/font_color_pressed', button_text_color)
  486. # Background
  487. button.get_node('ColorRect').color = Color(theme.get_value('buttons', 'background_color', '#ff000000'))
  488. button.get_node('ColorRect').visible = theme.get_value('buttons', 'use_background_color', false)
  489. button.get_node('TextureRect').visible = theme.get_value('buttons', 'use_image', true)
  490. if theme.get_value('buttons', 'use_image', true):
  491. button.get_node('TextureRect').texture = load(theme.get_value('buttons', 'image', "res://addons/dialogic/Images/background/background-2.png"))
  492. var padding = theme.get_value('buttons', 'padding', Vector2(5,5))
  493. button.get_node('ColorRect').set('margin_left', -1 * padding.x)
  494. button.get_node('ColorRect').set('margin_right', padding.x)
  495. button.get_node('ColorRect').set('margin_top', -1 * padding.y)
  496. button.get_node('ColorRect').set('margin_bottom', padding.y)
  497. button.get_node('TextureRect').set('margin_left', -1 * padding.x)
  498. button.get_node('TextureRect').set('margin_right', padding.x)
  499. button.get_node('TextureRect').set('margin_top', -1 * padding.y)
  500. button.get_node('TextureRect').set('margin_bottom', padding.y)
  501. $Options.set('custom_constants/separation', theme.get_value('buttons', 'gap', 20) + (padding.y*2))
  502. button.connect("pressed", self, "answer_question", [button, option['event_id'], option['question_id']])
  503. $Options.add_child(button)
  504. if Input.get_mouse_mode() != Input.MOUSE_MODE_VISIBLE:
  505. last_mouse_mode = Input.get_mouse_mode()
  506. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) # Make sure the cursor is visible for the options selection
  507. func answer_question(i, event_id, question_id):
  508. dprint('[!] Going to ', event_id + 1, i, 'question_id:', question_id)
  509. dprint('')
  510. waiting_for_answer = false
  511. dialog_index = event_id + 1
  512. questions[question_id]['answered'] = true
  513. dprint(' dialog_index = ', dialog_index)
  514. reset_options()
  515. load_dialog()
  516. if last_mouse_mode != null:
  517. Input.set_mouse_mode(last_mouse_mode) # Revert to last mouse mode when selection is done
  518. last_mouse_mode = null
  519. func _on_option_selected(option, variable, value):
  520. dialog_resource.custom_variables[variable] = value
  521. waiting_for_answer = false
  522. reset_options()
  523. load_dialog()
  524. dprint('[!] Option selected: ', option.text, ' value= ' , value)
  525. func _on_Tween_tween_completed(object, key):
  526. #$TextBubble/RichTextLabel.meta_underlined = true
  527. finished = true
  528. func _on_TextInputDialog_confirmed():
  529. pass # Replace with function body.
  530. func go_to_next_event():
  531. # The entire event reading system should be refactored... but not today!
  532. dialog_index += 1
  533. load_dialog(true)
  534. func grab_portrait_focus(character_data, event: Dictionary = {}) -> bool:
  535. var exists = false
  536. for portrait in $Portraits.get_children():
  537. if portrait.character_data == character_data:
  538. exists = true
  539. portrait.focus()
  540. if event.has('portrait'):
  541. if event['portrait'] != '':
  542. portrait.set_portrait(event['portrait'])
  543. else:
  544. portrait.focusout()
  545. return exists
  546. func get_character_position(positions) -> String:
  547. if positions['0']:
  548. return 'left'
  549. if positions['1']:
  550. return 'center_left'
  551. if positions['2']:
  552. return 'center'
  553. if positions['3']:
  554. return 'center_right'
  555. if positions['4']:
  556. return 'right'
  557. return 'left'
  558. func deferred_resize(current_size, result):
  559. #var result = theme.get_value('box', 'size', Vector2(910, 167))
  560. $TextBubble.rect_size = result
  561. if current_size != $TextBubble.rect_size:
  562. resize_main()
  563. func load_theme(filename):
  564. var theme = DialogicResources.get_theme_config(filename)
  565. # Box size
  566. call_deferred('deferred_resize', $TextBubble.rect_size, theme.get_value('box', 'size', Vector2(910, 167)))
  567. # Text
  568. var theme_font = load(theme.get_value('text', 'font', 'res://addons/dialogic/Fonts/DefaultFont.tres'))
  569. $TextBubble/RichTextLabel.set('custom_fonts/normal_font', theme_font)
  570. $TextBubble/NameLabel.set('custom_fonts/font', theme_font)
  571. var text_color = Color(theme.get_value('text', 'color', '#ffffffff'))
  572. $TextBubble/RichTextLabel.set('custom_colors/default_color', text_color)
  573. $TextBubble/NameLabel.set('custom_colors/font_color', text_color)
  574. $TextBubble/RichTextLabel.set('custom_colors/font_color_shadow', Color('#00ffffff'))
  575. $TextBubble/NameLabel.set('custom_colors/font_color_shadow', Color('#00ffffff'))
  576. if theme.get_value('text', 'shadow', false):
  577. var text_shadow_color = Color(theme.get_value('text', 'shadow_color', '#9e000000'))
  578. $TextBubble/RichTextLabel.set('custom_colors/font_color_shadow', text_shadow_color)
  579. var shadow_offset = theme.get_value('text', 'shadow_offset', Vector2(2,2))
  580. $TextBubble/RichTextLabel.set('custom_constants/shadow_offset_x', shadow_offset.x)
  581. $TextBubble/RichTextLabel.set('custom_constants/shadow_offset_y', shadow_offset.y)
  582. # Text speed
  583. text_speed = theme.get_value('text','speed', 2) * 0.01
  584. # Margin
  585. var text_margin = theme.get_value('text', 'margin', Vector2(20, 10))
  586. $TextBubble/RichTextLabel.set('margin_left', text_margin.x)
  587. $TextBubble/RichTextLabel.set('margin_right', text_margin.x * -1)
  588. $TextBubble/RichTextLabel.set('margin_top', text_margin.y)
  589. $TextBubble/RichTextLabel.set('margin_bottom', text_margin.y * -1)
  590. # Backgrounds
  591. $TextBubble/TextureRect.texture = load(theme.get_value('background','image', "res://addons/dialogic/Images/background/background-2.png"))
  592. $TextBubble/ColorRect.color = Color(theme.get_value('background','color', "#ff000000"))
  593. $TextBubble/ColorRect.visible = theme.get_value('background', 'use_color', false)
  594. $TextBubble/TextureRect.visible = theme.get_value('background', 'use_image', true)
  595. # Next image
  596. $TextBubble/NextIndicator.texture = load(theme.get_value('next_indicator', 'image', 'res://addons/dialogic/Images/next-indicator.png'))
  597. input_next = theme.get_value('settings', 'action_key', 'ui_accept')
  598. # Definitions
  599. var definitions_font = load(theme.get_value('definitions', 'font', 'res://addons/dialogic/Fonts/GlossaryFont.tres'))
  600. $DefinitionInfo/VBoxContainer/Title.set('custom_fonts/normal_font', definitions_font)
  601. $DefinitionInfo/VBoxContainer/Content.set('custom_fonts/normal_font', definitions_font)
  602. $DefinitionInfo/VBoxContainer/Extra.set('custom_fonts/normal_font', definitions_font)
  603. # Character Name
  604. $TextBubble/NameLabel/ColorRect.visible = theme.get_value('name', 'background_visible', false)
  605. $TextBubble/NameLabel/ColorRect.color = Color(theme.get_value('name', 'background', '#282828'))
  606. $TextBubble/NameLabel/TextureRect.visible = theme.get_value('name', 'image_visible', false)
  607. $TextBubble/NameLabel/TextureRect.texture = load(theme.get_value('name','image', "res://addons/dialogic/Images/background/background-2.png"))
  608. var name_shadow_offset = theme.get_value('name', 'shadow_offset', Vector2(2,2))
  609. if theme.get_value('name', 'shadow_visible', false):
  610. $TextBubble/NameLabel.set('custom_colors/font_color_shadow', Color(theme.get_value('name', 'shadow', '#9e000000')))
  611. $TextBubble/NameLabel.set('custom_constants/shadow_offset_x', name_shadow_offset.x)
  612. $TextBubble/NameLabel.set('custom_constants/shadow_offset_y', name_shadow_offset.y)
  613. $TextBubble/NameLabel.rect_position.y = theme.get_value('name', 'bottom_gap', 48) * -1
  614. # Setting next indicator animation
  615. $TextBubble/NextIndicator.self_modulate = Color('#ffffff')
  616. $TextBubble/NextIndicator/AnimationPlayer.play(
  617. theme.get_value('next_indicator', 'animation', 'Up and down')
  618. )
  619. return theme
  620. func _on_RichTextLabel_meta_hover_started(meta):
  621. var correct_type = false
  622. for d in definitions['glossary']:
  623. if d['id'] == meta:
  624. $DefinitionInfo.load_preview({
  625. 'title': d['title'],
  626. 'body': d['text'],
  627. 'extra': d['extra'],
  628. 'color': current_theme.get_value('definitions', 'color', '#ffbebebe'),
  629. })
  630. correct_type = true
  631. print(d)
  632. if correct_type:
  633. definition_visible = true
  634. $DefinitionInfo.visible = definition_visible
  635. # Adding a timer to avoid a graphical glitch
  636. $DefinitionInfo/Timer.stop()
  637. func _on_RichTextLabel_meta_hover_ended(meta):
  638. # Adding a timer to avoid a graphical glitch
  639. $DefinitionInfo/Timer.start(0.1)
  640. func _on_Definition_Timer_timeout():
  641. # Adding a timer to avoid a graphical glitch
  642. definition_visible = false
  643. $DefinitionInfo.visible = definition_visible
  644. func wait_seconds(seconds):
  645. $WaitSeconds.start(seconds)
  646. $TextBubble.visible = false
  647. func _on_WaitSeconds_timeout():
  648. emit_signal("event_end", "wait")
  649. waiting = false
  650. $WaitSeconds.stop()
  651. $TextBubble.visible = true
  652. load_dialog()
  653. func dprint(string, arg1='', arg2='', arg3='', arg4='' ):
  654. # HAHAHA if you are here wondering what this is...
  655. # I ask myself the same question :')
  656. if debug_mode:
  657. print(str(string) + str(arg1) + str(arg2) + str(arg3) + str(arg4))
  658. func _compare_definitions(def_value: String, event_value: String, condition: String):
  659. var condition_met = false;
  660. if def_value != null and event_value != null:
  661. var converted_def_value = def_value
  662. var converted_event_value = event_value
  663. if def_value.is_valid_float() and event_value.is_valid_float():
  664. converted_def_value = float(def_value)
  665. converted_event_value = float(event_value)
  666. match condition:
  667. "==":
  668. condition_met = converted_def_value == converted_event_value
  669. "!=":
  670. condition_met = converted_def_value != converted_event_value
  671. ">":
  672. condition_met = converted_def_value > converted_event_value
  673. ">=":
  674. condition_met = converted_def_value >= converted_event_value
  675. "<":
  676. condition_met = converted_def_value < converted_event_value
  677. "<=":
  678. condition_met = converted_def_value <= converted_event_value
  679. return condition_met