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 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  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. # Make sure saves are ready
  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='#FFFFFF'):
  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'].to_html()
  257. $TextBubble/NameLabel.bbcode_text = '[color=' + color + ']' + parsed_name + '[/color]'
  258. else:
  259. $TextBubble/NameLabel.bbcode_text = ''
  260. return true
  261. func update_text(text):
  262. # Updating the text and starting the animation from 0
  263. $TextBubble/RichTextLabel.bbcode_text = self.parse_definitions(text)
  264. $TextBubble/RichTextLabel.percent_visible = 0
  265. # The call to this function needs to be deferred.
  266. # More info: https://github.com/godotengine/godot/issues/36381
  267. call_deferred("start_text_tween")
  268. return true
  269. func on_timeline_start():
  270. if not Engine.is_editor_hint():
  271. DialogicSingleton.save_definitions()
  272. DialogicSingleton.set_current_timeline(current_timeline)
  273. emit_signal("event_start", "timeline", current_timeline)
  274. func on_timeline_end():
  275. if not Engine.is_editor_hint():
  276. DialogicSingleton.save_definitions()
  277. DialogicSingleton.set_current_timeline('')
  278. emit_signal("event_end", "timeline")
  279. func load_dialog(skip_add = false):
  280. # Emitting signals
  281. if dialog_script.has('events'):
  282. if dialog_index == 0:
  283. on_timeline_start()
  284. elif dialog_index == dialog_script['events'].size():
  285. on_timeline_end()
  286. # Hiding definitions popup
  287. definition_visible = false
  288. $DefinitionInfo.visible = definition_visible
  289. # This will load the next entry in the dialog_script array.
  290. if dialog_script.has('events'):
  291. if dialog_index < dialog_script['events'].size():
  292. event_handler(dialog_script['events'][dialog_index])
  293. else:
  294. if Engine.is_editor_hint() == false:
  295. queue_free()
  296. if skip_add == false:
  297. dialog_index += 1
  298. func reset_dialog_extras():
  299. $TextBubble/NameLabel.bbcode_text = ''
  300. func get_character(character_id):
  301. for c in characters:
  302. if c['file'] == character_id:
  303. return c
  304. return {}
  305. func event_handler(event: Dictionary):
  306. # Handling an event and updating the available nodes accordingly.
  307. reset_dialog_extras()
  308. dprint('[D] Current Event: ', event)
  309. match event:
  310. {'text', 'character', 'portrait'}:
  311. emit_signal("event_start", "text", event)
  312. show_dialog()
  313. finished = false
  314. var character_data = get_character(event['character'])
  315. update_name(character_data)
  316. grab_portrait_focus(character_data, event)
  317. update_text(event['text'])
  318. {'question', 'question_id', 'options', ..}:
  319. emit_signal("event_start", "question", event)
  320. show_dialog()
  321. finished = false
  322. waiting_for_answer = true
  323. if event.has('name'):
  324. update_name(event['name'])
  325. update_text(event['question'])
  326. if event.has('options'):
  327. for o in event['options']:
  328. add_choice_button(o)
  329. {'choice', 'question_id'}:
  330. emit_signal("event_start", "choice", event)
  331. for q in questions:
  332. if q['question_id'] == event['question_id']:
  333. if q['answered']:
  334. # If the option is for an answered question, skip to the end of it.
  335. dialog_index = q['end_id']
  336. load_dialog(true)
  337. {'input', ..}:
  338. emit_signal("event_start", "input", event)
  339. show_dialog()
  340. finished = false
  341. waiting_for_input = true
  342. update_text(event['input'])
  343. $TextInputDialog.window_title = event['window_title']
  344. $TextInputDialog.popup_centered()
  345. $TextInputDialog.connect("confirmed", self, "_on_input_set", [event['variable']])
  346. {'action', ..}:
  347. emit_signal("event_start", "action", event)
  348. if event['action'] == 'leaveall':
  349. if event['character'] == '[All]':
  350. for p in $Portraits.get_children():
  351. p.fade_out()
  352. else:
  353. for p in $Portraits.get_children():
  354. if p.character_data['file'] == event['character']:
  355. p.fade_out()
  356. go_to_next_event()
  357. elif event['action'] == 'join':
  358. if event['character'] == '':
  359. go_to_next_event()
  360. else:
  361. var character_data = get_character(event['character'])
  362. var exists = grab_portrait_focus(character_data)
  363. if exists == false:
  364. var p = Portrait.instance()
  365. var char_portrait = event['portrait']
  366. if char_portrait == '':
  367. char_portrait = 'Default'
  368. p.character_data = character_data
  369. p.init(char_portrait, get_character_position(event['position']))
  370. $Portraits.add_child(p)
  371. p.fade_in()
  372. go_to_next_event()
  373. {'scene'}:
  374. get_tree().change_scene(event['scene'])
  375. {'background'}:
  376. emit_signal("event_start", "background", event)
  377. $Background.visible = true
  378. $Background.texture = load(event['background'])
  379. go_to_next_event()
  380. {'audio'}, {'audio', 'file'}:
  381. emit_signal("event_start", "audio", event)
  382. if event['audio'] == 'play':
  383. $FX/AudioStreamPlayer.stream = load(event['file'])
  384. $FX/AudioStreamPlayer.play()
  385. # Todo: audio stop
  386. go_to_next_event()
  387. {'endbranch', ..}:
  388. emit_signal("event_start", "endbranch", event)
  389. go_to_next_event()
  390. {'change_scene'}:
  391. get_tree().change_scene(event['change_scene'])
  392. {'emit_signal', ..}:
  393. dprint('[!] Emitting signal: dialogic_signal(', event['emit_signal'], ')')
  394. emit_signal("dialogic_signal", event['emit_signal'])
  395. go_to_next_event()
  396. {'close_dialog'}:
  397. emit_signal("event_start", "close_dialog", event)
  398. on_timeline_end()
  399. queue_free()
  400. {'set_theme'}:
  401. emit_signal("event_start", "set_theme", event)
  402. if event['set_theme'] != '':
  403. current_theme = load_theme(event['set_theme'])
  404. go_to_next_event()
  405. {'wait_seconds'}:
  406. emit_signal("event_start", "wait", event)
  407. wait_seconds(event['wait_seconds'])
  408. waiting = true
  409. {'change_timeline'}:
  410. dialog_script = set_current_dialog(event['change_timeline'])
  411. dialog_index = -1
  412. go_to_next_event()
  413. {'condition', 'definition', 'value', 'question_id', ..}:
  414. # Treating this conditional as an option on a regular question event
  415. var def_value = null
  416. var current_question = questions[event['question_id']]
  417. for d in definitions['variables']:
  418. if d['id'] == event['definition']:
  419. def_value = d['value']
  420. var condition_met = def_value != null and _compare_definitions(def_value, event['value'], event['condition']);
  421. current_question['answered'] = !condition_met
  422. if !condition_met:
  423. # condition not met, skipping branch
  424. dialog_index = current_question['end_id']
  425. load_dialog(true)
  426. else:
  427. # condition met, entering branch
  428. go_to_next_event()
  429. {'set_value', 'definition'}:
  430. emit_signal("event_start", "set_value", event)
  431. DialogicSingleton.set_variable_from_id(event['definition'], event['set_value'])
  432. go_to_next_event()
  433. _:
  434. visible = false
  435. dprint('Other event. ', event)
  436. func _on_input_set(variable):
  437. var input_value = $TextInputDialog/LineEdit.text
  438. if input_value == '':
  439. $TextInputDialog.popup_centered()
  440. else:
  441. dialog_resource.custom_variables[variable] = input_value
  442. waiting_for_input = false
  443. $TextInputDialog/LineEdit.text = ''
  444. $TextInputDialog.disconnect("confirmed", self, '_on_input_set')
  445. $TextInputDialog.visible = false
  446. load_dialog()
  447. dprint('[!] Input selected: ', input_value)
  448. dprint('[!] dialog variables: ', dialog_resource.custom_variables)
  449. func reset_options():
  450. # Clearing out the options after one was selected.
  451. for option in $Options.get_children():
  452. option.queue_free()
  453. func add_choice_button(option):
  454. var theme = current_theme
  455. var button = ChoiceButton.instance()
  456. button.text = option['label']
  457. # Text
  458. button.set('custom_fonts/font', load(theme.get_value('text', 'font', "res://addons/dialogic/Fonts/DefaultFont.tres")))
  459. var text_color = Color(theme.get_value('text', 'color', "#ffffffff"))
  460. button.set('custom_colors/font_color', text_color)
  461. button.set('custom_colors/font_color_hover', text_color)
  462. button.set('custom_colors/font_color_pressed', text_color)
  463. if theme.get_value('buttons', 'text_color_enabled', true):
  464. var button_text_color = Color(theme.get_value('buttons', 'text_color', "#ffffffff"))
  465. button.set('custom_colors/font_color', button_text_color)
  466. button.set('custom_colors/font_color_hover', button_text_color)
  467. button.set('custom_colors/font_color_pressed', button_text_color)
  468. # Background
  469. button.get_node('ColorRect').color = Color(theme.get_value('buttons', 'background_color', '#ff000000'))
  470. button.get_node('ColorRect').visible = theme.get_value('buttons', 'use_background_color', false)
  471. button.get_node('TextureRect').visible = theme.get_value('buttons', 'use_image', true)
  472. if theme.get_value('buttons', 'use_image', true):
  473. button.get_node('TextureRect').texture = load(theme.get_value('buttons', 'image', "res://addons/dialogic/Images/background/background-2.png"))
  474. var padding = theme.get_value('buttons', 'padding', Vector2(5,5))
  475. button.get_node('ColorRect').set('margin_left', -1 * padding.x)
  476. button.get_node('ColorRect').set('margin_right', padding.x)
  477. button.get_node('ColorRect').set('margin_top', -1 * padding.y)
  478. button.get_node('ColorRect').set('margin_bottom', padding.y)
  479. button.get_node('TextureRect').set('margin_left', -1 * padding.x)
  480. button.get_node('TextureRect').set('margin_right', padding.x)
  481. button.get_node('TextureRect').set('margin_top', -1 * padding.y)
  482. button.get_node('TextureRect').set('margin_bottom', padding.y)
  483. $Options.set('custom_constants/separation', theme.get_value('buttons', 'gap', 20) + (padding.y*2))
  484. button.connect("pressed", self, "answer_question", [button, option['event_id'], option['question_id']])
  485. $Options.add_child(button)
  486. if Input.get_mouse_mode() != Input.MOUSE_MODE_VISIBLE:
  487. last_mouse_mode = Input.get_mouse_mode()
  488. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) # Make sure the cursor is visible for the options selection
  489. func answer_question(i, event_id, question_id):
  490. dprint('[!] Going to ', event_id + 1, i, 'question_id:', question_id)
  491. dprint('')
  492. waiting_for_answer = false
  493. dialog_index = event_id + 1
  494. questions[question_id]['answered'] = true
  495. dprint(' dialog_index = ', dialog_index)
  496. reset_options()
  497. load_dialog()
  498. if last_mouse_mode != null:
  499. Input.set_mouse_mode(last_mouse_mode) # Revert to last mouse mode when selection is done
  500. last_mouse_mode = null
  501. func _on_option_selected(option, variable, value):
  502. dialog_resource.custom_variables[variable] = value
  503. waiting_for_answer = false
  504. reset_options()
  505. load_dialog()
  506. dprint('[!] Option selected: ', option.text, ' value= ' , value)
  507. func _on_Tween_tween_completed(object, key):
  508. #$TextBubble/RichTextLabel.meta_underlined = true
  509. finished = true
  510. func _on_TextInputDialog_confirmed():
  511. pass # Replace with function body.
  512. func go_to_next_event():
  513. # The entire event reading system should be refactored... but not today!
  514. dialog_index += 1
  515. load_dialog(true)
  516. func grab_portrait_focus(character_data, event: Dictionary = {}) -> bool:
  517. var exists = false
  518. for portrait in $Portraits.get_children():
  519. if portrait.character_data == character_data:
  520. exists = true
  521. portrait.focus()
  522. if event.has('portrait'):
  523. if event['portrait'] != '':
  524. portrait.set_portrait(event['portrait'])
  525. else:
  526. portrait.focusout()
  527. return exists
  528. func get_character_position(positions) -> String:
  529. if positions['0']:
  530. return 'left'
  531. if positions['1']:
  532. return 'center_left'
  533. if positions['2']:
  534. return 'center'
  535. if positions['3']:
  536. return 'center_right'
  537. if positions['4']:
  538. return 'right'
  539. return 'left'
  540. func deferred_resize(current_size, result):
  541. #var result = theme.get_value('box', 'size', Vector2(910, 167))
  542. $TextBubble.rect_size = result
  543. if current_size != $TextBubble.rect_size:
  544. resize_main()
  545. func load_theme(filename):
  546. var theme = DialogicResources.get_theme_config(filename)
  547. # Box size
  548. call_deferred('deferred_resize', $TextBubble.rect_size, theme.get_value('box', 'size', Vector2(910, 167)))
  549. # Text
  550. var theme_font = load(theme.get_value('text', 'font', 'res://addons/dialogic/Fonts/DefaultFont.tres'))
  551. $TextBubble/RichTextLabel.set('custom_fonts/normal_font', theme_font)
  552. $TextBubble/NameLabel.set('custom_fonts/normal_font', theme_font)
  553. var text_color = Color(theme.get_value('text', 'color', '#ffffffff'))
  554. $TextBubble/RichTextLabel.set('custom_colors/default_color', text_color)
  555. $TextBubble/NameLabel.set('custom_colors/default_color', text_color)
  556. $TextBubble/RichTextLabel.set('custom_colors/font_color_shadow', Color('#00ffffff'))
  557. $TextBubble/NameLabel.set('custom_colors/font_color_shadow', Color('#00ffffff'))
  558. if theme.get_value('text', 'shadow', false):
  559. var text_shadow_color = Color(theme.get_value('text', 'shadow_color', '#9e000000'))
  560. $TextBubble/RichTextLabel.set('custom_colors/font_color_shadow', text_shadow_color)
  561. $TextBubble/NameLabel.set('custom_colors/font_color_shadow', text_shadow_color)
  562. var shadow_offset = theme.get_value('text', 'shadow_offset', Vector2(2,2))
  563. $TextBubble/RichTextLabel.set('custom_constants/shadow_offset_x', shadow_offset.x)
  564. $TextBubble/NameLabel.set('custom_constants/shadow_offset_x', shadow_offset.x)
  565. $TextBubble/RichTextLabel.set('custom_constants/shadow_offset_y', shadow_offset.y)
  566. $TextBubble/NameLabel.set('custom_constants/shadow_offset_y', shadow_offset.y)
  567. # Text speed
  568. text_speed = theme.get_value('text','speed', 2) * 0.01
  569. # Margin
  570. var text_margin = theme.get_value('text', 'margin', Vector2(20, 10))
  571. $TextBubble/RichTextLabel.set('margin_left', text_margin.x)
  572. $TextBubble/RichTextLabel.set('margin_right', text_margin.x * -1)
  573. $TextBubble/RichTextLabel.set('margin_top', text_margin.y)
  574. $TextBubble/RichTextLabel.set('margin_bottom', text_margin.y * -1)
  575. # Backgrounds
  576. $TextBubble/TextureRect.texture = load(theme.get_value('background','image', "res://addons/dialogic/Images/background/background-2.png"))
  577. $TextBubble/ColorRect.color = Color(theme.get_value('background','color', "#ff000000"))
  578. $TextBubble/ColorRect.visible = theme.get_value('background', 'use_color', false)
  579. $TextBubble/TextureRect.visible = theme.get_value('background', 'use_image', true)
  580. # Next image
  581. $TextBubble/NextIndicator.texture = load(theme.get_value('next_indicator', 'image', 'res://addons/dialogic/Images/next-indicator.png'))
  582. input_next = theme.get_value('settings', 'action_key', 'ui_accept')
  583. # Definitions
  584. var definitions_font = load(theme.get_value('definitions', 'font', 'res://addons/dialogic/Fonts/GlossaryFont.tres'))
  585. $DefinitionInfo/VBoxContainer/Title.set('custom_fonts/normal_font', definitions_font)
  586. $DefinitionInfo/VBoxContainer/Content.set('custom_fonts/normal_font', definitions_font)
  587. $DefinitionInfo/VBoxContainer/Extra.set('custom_fonts/normal_font', definitions_font)
  588. return theme
  589. func _on_RichTextLabel_meta_hover_started(meta):
  590. var correct_type = false
  591. for d in definitions['glossary']:
  592. if d['id'] == meta:
  593. $DefinitionInfo.load_preview({
  594. 'title': d['title'],
  595. 'body': d['text'],
  596. 'extra': d['extra'],
  597. 'color': current_theme.get_value('definitions', 'color', '#ffbebebe'),
  598. })
  599. correct_type = true
  600. print(d)
  601. if correct_type:
  602. definition_visible = true
  603. $DefinitionInfo.visible = definition_visible
  604. # Adding a timer to avoid a graphical glitch
  605. $DefinitionInfo/Timer.stop()
  606. func _on_RichTextLabel_meta_hover_ended(meta):
  607. # Adding a timer to avoid a graphical glitch
  608. $DefinitionInfo/Timer.start(0.1)
  609. func _on_Definition_Timer_timeout():
  610. # Adding a timer to avoid a graphical glitch
  611. definition_visible = false
  612. $DefinitionInfo.visible = definition_visible
  613. func wait_seconds(seconds):
  614. $WaitSeconds.start(seconds)
  615. $TextBubble.visible = false
  616. func _on_WaitSeconds_timeout():
  617. emit_signal("event_end", "wait")
  618. waiting = false
  619. $WaitSeconds.stop()
  620. $TextBubble.visible = true
  621. load_dialog()
  622. func dprint(string, arg1='', arg2='', arg3='', arg4='' ):
  623. # HAHAHA if you are here wondering what this is...
  624. # I ask myself the same question :')
  625. if debug_mode:
  626. print(str(string) + str(arg1) + str(arg2) + str(arg3) + str(arg4))
  627. func _compare_definitions(def_value: String, event_value: String, condition: String):
  628. var condition_met = false;
  629. if def_value != null and event_value != null:
  630. var converted_def_value = def_value
  631. var converted_event_value = event_value
  632. if def_value.is_valid_float() and event_value.is_valid_float():
  633. converted_def_value = float(def_value)
  634. converted_event_value = float(event_value)
  635. match condition:
  636. "==":
  637. condition_met = converted_def_value == converted_event_value
  638. "!=":
  639. condition_met = converted_def_value != converted_event_value
  640. ">":
  641. condition_met = converted_def_value > converted_event_value
  642. ">=":
  643. condition_met = converted_def_value >= converted_event_value
  644. "<":
  645. condition_met = converted_def_value < converted_event_value
  646. "<=":
  647. condition_met = converted_def_value <= converted_event_value
  648. return condition_met