819 lines
26 KiB
GDScript
819 lines
26 KiB
GDScript
tool
|
|
extends Control
|
|
|
|
var last_mouse_mode = null
|
|
var input_next: String = 'ui_accept'
|
|
var dialog_index: int = 0
|
|
var finished: bool = false
|
|
var waiting_for_answer: bool = false
|
|
var waiting_for_input: bool = false
|
|
var waiting: bool = false
|
|
var preview: bool = false
|
|
var definitions: Dictionary = {}
|
|
var definition_visible: bool = false
|
|
|
|
var settings: ConfigFile
|
|
var current_theme: ConfigFile
|
|
var current_timeline: String = ''
|
|
|
|
## The timeline to load when starting the scene
|
|
export(String, "TimelineDropdown") var timeline: String
|
|
## Should we clear saved data (definitions and timeline progress) on start?
|
|
export(bool) var reset_saves = true
|
|
## Should we show debug information when running?
|
|
export(bool) var debug_mode = true
|
|
|
|
signal event_start(type, event)
|
|
signal event_end(type)
|
|
signal dialogic_signal(value)
|
|
|
|
var dialog_resource
|
|
var characters
|
|
|
|
onready var ChoiceButton = load("res://addons/dialogic/Nodes/ChoiceButton.tscn")
|
|
onready var Portrait = load("res://addons/dialogic/Nodes/Portrait.tscn")
|
|
var dialog_script: Dictionary = {}
|
|
var questions #for keeping track of the questions answered
|
|
|
|
|
|
func _ready():
|
|
# Loading the config files
|
|
load_config_files()
|
|
|
|
# Checking if the dialog should read the code from a external file
|
|
if not timeline.empty():
|
|
dialog_script = set_current_dialog(timeline)
|
|
elif dialog_script.keys().size() == 0:
|
|
dialog_script = {
|
|
"events":[{"character":"","portrait":"",
|
|
"text":"[Dialogic Error] No timeline specified."}]
|
|
}
|
|
|
|
# Connecting resize signal
|
|
get_viewport().connect("size_changed", self, "resize_main")
|
|
resize_main()
|
|
|
|
# Setting everything up for the node to be default
|
|
$DefinitionInfo.visible = false
|
|
$TextBubble.connect("text_completed", self, "_on_text_completed")
|
|
|
|
# Getting the character information
|
|
characters = DialogicUtil.get_character_list()
|
|
|
|
if Engine.is_editor_hint():
|
|
if preview:
|
|
load_dialog()
|
|
else:
|
|
load_dialog()
|
|
|
|
|
|
func load_config_files():
|
|
if not Engine.is_editor_hint():
|
|
if reset_saves:
|
|
DialogicSingleton.init(reset_saves)
|
|
definitions = DialogicSingleton.get_definitions()
|
|
else:
|
|
definitions = DialogicResources.get_default_definitions()
|
|
settings = DialogicResources.get_settings_config()
|
|
var theme_file = 'res://addons/dialogic/Editor/ThemeEditor/default-theme.cfg'
|
|
if settings.has_section('theme'):
|
|
theme_file = settings.get_value('theme', 'default')
|
|
current_theme = load_theme(theme_file)
|
|
|
|
|
|
func resize_main():
|
|
# This function makes sure that the dialog is displayed at the correct
|
|
# size and position in the screen.
|
|
if Engine.is_editor_hint() == false:
|
|
set_global_position(Vector2(0,0))
|
|
if ProjectSettings.get_setting("display/window/stretch/mode") != '2d':
|
|
set_deferred('rect_size', get_viewport().size)
|
|
dprint("Viewport", get_viewport().size)
|
|
$TextBubble.rect_position.x = (rect_size.x / 2) - ($TextBubble.rect_size.x / 2)
|
|
if current_theme != null:
|
|
$TextBubble.rect_position.y = (rect_size.y) - ($TextBubble.rect_size.y) - current_theme.get_value('box', 'bottom_gap', 40)
|
|
|
|
|
|
func set_current_dialog(dialog_path: String):
|
|
current_timeline = dialog_path
|
|
var dialog_script = DialogicResources.get_timeline_json(dialog_path)
|
|
# All this parse events should be happening in the same loop ideally
|
|
# But until performance is not an issue I will probably stay lazy
|
|
# And keep adding different functions for each parsing operation.
|
|
if settings.has_section_key('dialog', 'auto_color_names'):
|
|
if settings.get_value('dialog', 'auto_color_names'):
|
|
dialog_script = parse_characters(dialog_script)
|
|
else:
|
|
dialog_script = parse_characters(dialog_script)
|
|
|
|
dialog_script = parse_text_lines(dialog_script)
|
|
dialog_script = parse_branches(dialog_script)
|
|
return dialog_script
|
|
|
|
|
|
func parse_characters(dialog_script):
|
|
var names = DialogicUtil.get_character_list()
|
|
# I should use regex here, but this is way easier :)
|
|
if names.size() > 0:
|
|
var index = 0
|
|
for t in dialog_script['events']:
|
|
if t.has('text'):
|
|
for n in names:
|
|
if n.has('name'):
|
|
dialog_script['events'][index]['text'] = t['text'].replace(n['name'],
|
|
'[color=#' + n['color'].to_html() + ']' + n['name'] + '[/color]'
|
|
)
|
|
index += 1
|
|
return dialog_script
|
|
|
|
|
|
func parse_text_lines(unparsed_dialog_script: Dictionary) -> Dictionary:
|
|
var parsed_dialog: Dictionary = unparsed_dialog_script
|
|
var new_events: Array = []
|
|
var split_new_lines = true
|
|
var remove_empty_messages = true
|
|
|
|
# Return the same thing if it doesn't have events
|
|
if unparsed_dialog_script.has('events') == false:
|
|
return unparsed_dialog_script
|
|
|
|
# Getting extra settings
|
|
if settings.has_section_key('dialog', 'remove_empty_messages'):
|
|
remove_empty_messages = settings.get_value('dialog', 'remove_empty_messages')
|
|
if settings.has_section_key('dialog', 'new_lines'):
|
|
split_new_lines = settings.get_value('dialog', 'new_lines')
|
|
|
|
# Parsing
|
|
for event in unparsed_dialog_script['events']:
|
|
if event.has('text') and event.has('character') and event.has('portrait'):
|
|
if event['text'] == '' and remove_empty_messages == true:
|
|
pass
|
|
elif '\n' in event['text'] and preview == false and split_new_lines == true:
|
|
var lines = event['text'].split('\n')
|
|
var i = 0
|
|
for line in lines:
|
|
var _e = {
|
|
'text': lines[i],
|
|
'character': event['character'],
|
|
'portrait': event['portrait']
|
|
}
|
|
new_events.append(_e)
|
|
i += 1
|
|
else:
|
|
new_events.append(event)
|
|
else:
|
|
new_events.append(event)
|
|
|
|
parsed_dialog['events'] = new_events
|
|
|
|
return parsed_dialog
|
|
|
|
|
|
func parse_alignment(text):
|
|
var alignment = current_theme.get_value('text', 'alignment', 'Left')
|
|
var fname = current_theme.get_value('settings', 'name', 'none')
|
|
if alignment == 'Center':
|
|
text = '[center]' + text + '[/center]'
|
|
elif alignment == 'Right':
|
|
text = '[right]' + text + '[/right]'
|
|
return text
|
|
|
|
|
|
func parse_branches(dialog_script: Dictionary) -> Dictionary:
|
|
questions = [] # Resetting the questions
|
|
|
|
# Return the same thing if it doesn't have events
|
|
if dialog_script.has('events') == false:
|
|
return dialog_script
|
|
|
|
var parser_queue = [] # This saves the last question opened, and it gets removed once it was consumed by a endbranch event
|
|
var event_id: int = 0 # The current id for jumping later on
|
|
var question_id: int = 0 # identifying the questions to assign options to it
|
|
for event in dialog_script['events']:
|
|
if event.has('question'):
|
|
event['event_id'] = event_id
|
|
event['question_id'] = question_id
|
|
event['answered'] = false
|
|
question_id += 1
|
|
questions.append(event)
|
|
parser_queue.append(event)
|
|
|
|
if event.has('condition'):
|
|
event['event_id'] = event_id
|
|
event['question_id'] = question_id
|
|
event['answered'] = false
|
|
question_id += 1
|
|
questions.append(event)
|
|
parser_queue.append(event)
|
|
|
|
if event.has('choice'):
|
|
var opened_branch = parser_queue.back()
|
|
dialog_script['events'][opened_branch['event_id']]['options'].append({
|
|
'question_id': opened_branch['question_id'],
|
|
'label': event['choice'],
|
|
'event_id': event_id,
|
|
})
|
|
event['question_id'] = opened_branch['question_id']
|
|
|
|
if event.has('endbranch'):
|
|
event['event_id'] = event_id
|
|
var opened_branch = parser_queue.pop_back()
|
|
event['end_branch_of'] = opened_branch['question_id']
|
|
dialog_script['events'][opened_branch['event_id']]['end_id'] = event_id
|
|
event_id += 1
|
|
|
|
return dialog_script
|
|
|
|
|
|
func parse_definitions(text: String, variables: bool = true, glossary: bool = true):
|
|
var final_text: String = text
|
|
if variables:
|
|
final_text = _insert_variable_definitions(text)
|
|
if glossary:
|
|
final_text = _insert_glossary_definitions(final_text)
|
|
return final_text
|
|
|
|
|
|
func _insert_variable_definitions(text: String):
|
|
var final_text := text;
|
|
for d in definitions['variables']:
|
|
var name : String = d['name'];
|
|
final_text = final_text.replace('[' + name + ']', d['value'])
|
|
return final_text;
|
|
|
|
|
|
func _insert_glossary_definitions(text: String):
|
|
var color = current_theme.get_value('definitions', 'color', '#ffbebebe')
|
|
var final_text := text;
|
|
# I should use regex here, but this is way easier :)
|
|
for d in definitions['glossary']:
|
|
final_text = final_text.replace(d['name'],
|
|
'[url=' + d['id'] + ']' +
|
|
'[color=' + color + ']' + d['name'] + '[/color]' +
|
|
'[/url]'
|
|
)
|
|
return final_text;
|
|
|
|
|
|
func _process(delta):
|
|
$TextBubble/NextIndicator.visible = finished
|
|
if waiting_for_answer and Input.is_action_just_released(input_next):
|
|
if $Options.get_child_count() > 0:
|
|
$Options.get_child(0).grab_focus()
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if not Engine.is_editor_hint() and event.is_action_pressed(input_next) and not waiting:
|
|
if not $TextBubble.is_finished():
|
|
# Skip to end if key is pressed during the text animation
|
|
$TextBubble.skip()
|
|
else:
|
|
if waiting_for_answer == false and waiting_for_input == false:
|
|
load_dialog()
|
|
if settings.has_section_key('dialog', 'propagate_input'):
|
|
var propagate_input: bool = settings.get_value('dialog', 'propagate_input')
|
|
if not propagate_input:
|
|
get_tree().set_input_as_handled()
|
|
|
|
|
|
func show_dialog():
|
|
visible = true
|
|
|
|
|
|
func update_name(character) -> void:
|
|
if character.has('name'):
|
|
var parsed_name = character['name']
|
|
var color = Color.white
|
|
if character.has('display_name'):
|
|
if character['display_name'] != '':
|
|
parsed_name = character['display_name']
|
|
if character.has('color'):
|
|
color = character['color']
|
|
parsed_name = parse_definitions(parsed_name, true, false)
|
|
$TextBubble.update_name(parsed_name, color, current_theme.get_value('name', 'auto_color', true))
|
|
else:
|
|
$TextBubble.update_name('')
|
|
|
|
|
|
func update_text(text: String) -> String:
|
|
var final_text = parse_definitions(parse_alignment(text))
|
|
final_text = final_text.replace('[br]', '\n')
|
|
$TextBubble.update_text(final_text)
|
|
return final_text
|
|
|
|
|
|
func _on_text_completed():
|
|
finished = true
|
|
|
|
func on_timeline_start():
|
|
if not Engine.is_editor_hint():
|
|
DialogicSingleton.save_definitions()
|
|
DialogicSingleton.set_current_timeline(current_timeline)
|
|
emit_signal("event_start", "timeline", current_timeline)
|
|
|
|
|
|
func on_timeline_end():
|
|
if not Engine.is_editor_hint():
|
|
DialogicSingleton.save_definitions()
|
|
DialogicSingleton.set_current_timeline('')
|
|
emit_signal("event_end", "timeline")
|
|
|
|
|
|
func load_dialog(skip_add = false):
|
|
# Emitting signals
|
|
if dialog_script.has('events'):
|
|
if dialog_index == 0:
|
|
on_timeline_start()
|
|
elif dialog_index == dialog_script['events'].size():
|
|
on_timeline_end()
|
|
|
|
# Hiding definitions popup
|
|
definition_visible = false
|
|
$DefinitionInfo.visible = definition_visible
|
|
|
|
# This will load the next entry in the dialog_script array.
|
|
if dialog_script.has('events'):
|
|
if dialog_index < dialog_script['events'].size():
|
|
var func_state = event_handler(dialog_script['events'][dialog_index])
|
|
if (func_state is GDScriptFunctionState):
|
|
yield(func_state, "completed")
|
|
else:
|
|
if Engine.is_editor_hint() == false:
|
|
queue_free()
|
|
if skip_add == false:
|
|
dialog_index += 1
|
|
|
|
|
|
func reset_dialog_extras():
|
|
$TextBubble/NameLabel.text = ''
|
|
$TextBubble/NameLabel.visible = false
|
|
|
|
|
|
func get_character(character_id):
|
|
for c in characters:
|
|
if c['file'] == character_id:
|
|
return c
|
|
return {}
|
|
|
|
|
|
func event_handler(event: Dictionary):
|
|
# Handling an event and updating the available nodes accordingly.
|
|
$TextBubble.reset()
|
|
reset_options()
|
|
|
|
dprint('[D] Current Event: ', event)
|
|
match event:
|
|
{'text', 'character', 'portrait'}:
|
|
emit_signal("event_start", "text", event)
|
|
show_dialog()
|
|
finished = false
|
|
var character_data = get_character(event['character'])
|
|
update_name(character_data)
|
|
grab_portrait_focus(character_data, event)
|
|
update_text(event['text'])
|
|
{'question', 'question_id', 'options', ..}:
|
|
emit_signal("event_start", "question", event)
|
|
show_dialog()
|
|
finished = false
|
|
waiting_for_answer = true
|
|
if event.has('name'):
|
|
update_name(event['name'])
|
|
update_text(event['question'])
|
|
if event.has('options'):
|
|
for o in event['options']:
|
|
add_choice_button(o)
|
|
{'choice', 'question_id'}:
|
|
emit_signal("event_start", "choice", event)
|
|
for q in questions:
|
|
if q['question_id'] == event['question_id']:
|
|
if q['answered']:
|
|
# If the option is for an answered question, skip to the end of it.
|
|
dialog_index = q['end_id']
|
|
load_dialog(true)
|
|
{'action', ..}:
|
|
emit_signal("event_start", "action", event)
|
|
if event['action'] == 'leaveall':
|
|
if event['character'] == '[All]':
|
|
characters_leave_all()
|
|
else:
|
|
for p in $Portraits.get_children():
|
|
if p.character_data['file'] == event['character']:
|
|
p.fade_out()
|
|
|
|
go_to_next_event()
|
|
elif event['action'] == 'join':
|
|
if event['character'] == '':
|
|
go_to_next_event()
|
|
else:
|
|
var character_data = get_character(event['character'])
|
|
var exists = grab_portrait_focus(character_data)
|
|
if exists == false:
|
|
var p = Portrait.instance()
|
|
var char_portrait = event['portrait']
|
|
if char_portrait == '':
|
|
char_portrait = 'Default'
|
|
p.character_data = character_data
|
|
p.init(char_portrait, get_character_position(event['position']))
|
|
$Portraits.add_child(p)
|
|
p.fade_in()
|
|
go_to_next_event()
|
|
{'scene'}:
|
|
get_tree().change_scene(event['scene'])
|
|
{'background'}:
|
|
emit_signal("event_start", "background", event)
|
|
var background = get_node_or_null('Background')
|
|
if event['background'] == '' and background != null:
|
|
background.queue_free()
|
|
else:
|
|
if background == null:
|
|
background = TextureRect.new()
|
|
background.expand = true
|
|
background.name = 'Background'
|
|
background.anchor_right = 1
|
|
background.anchor_bottom = 1
|
|
background.stretch_mode = TextureRect.STRETCH_SCALE
|
|
background.show_behind_parent = true
|
|
add_child(background)
|
|
background.texture = null
|
|
if (background.get_child_count() > 0):
|
|
for c in background.get_children():
|
|
c.get_parent().remove_child(c)
|
|
c.queue_free()
|
|
if (event['background'].ends_with('.tscn')):
|
|
var bg_scene = load(event['background'])
|
|
if (bg_scene):
|
|
bg_scene = bg_scene.instance()
|
|
background.add_child(bg_scene)
|
|
elif (event['background'] != ''):
|
|
background.texture = load(event['background'])
|
|
go_to_next_event()
|
|
{'audio'}, {'audio', 'file'}:
|
|
emit_signal("event_start", "audio", event)
|
|
if event['audio'] == 'play' and 'file' in event.keys() and not event['file'].empty():
|
|
var audio = get_node_or_null('AudioEvent')
|
|
if audio == null:
|
|
audio = AudioStreamPlayer.new()
|
|
audio.name = 'AudioEvent'
|
|
add_child(audio)
|
|
audio.stream = load(event['file'])
|
|
audio.play()
|
|
print('play')
|
|
else:
|
|
var audio = get_node_or_null('AudioEvent')
|
|
if audio != null:
|
|
audio.stop()
|
|
audio.queue_free()
|
|
go_to_next_event()
|
|
{'background-music'}, {'background-music', 'file'}:
|
|
emit_signal("event_start", "background-music", event)
|
|
if event['background-music'] == 'play' and 'file' in event.keys() and not event['file'].empty():
|
|
$FX/BackgroundMusic.crossfade_to(event['file'])
|
|
else:
|
|
$FX/BackgroundMusic.fade_out()
|
|
go_to_next_event()
|
|
{'endbranch', ..}:
|
|
emit_signal("event_start", "endbranch", event)
|
|
go_to_next_event()
|
|
{'change_scene'}:
|
|
get_tree().change_scene(event['change_scene'])
|
|
{'emit_signal', ..}:
|
|
dprint('[!] Emitting signal: dialogic_signal(', event['emit_signal'], ')')
|
|
emit_signal("dialogic_signal", event['emit_signal'])
|
|
go_to_next_event()
|
|
{'close_dialog'}:
|
|
emit_signal("event_start", "close_dialog", event)
|
|
close_dialog_event()
|
|
{'set_theme'}:
|
|
emit_signal("event_start", "set_theme", event)
|
|
if event['set_theme'] != '':
|
|
current_theme = load_theme(event['set_theme'])
|
|
go_to_next_event()
|
|
{'wait_seconds'}:
|
|
emit_signal("event_start", "wait", event)
|
|
wait_seconds(event['wait_seconds'])
|
|
waiting = true
|
|
{'change_timeline'}:
|
|
dialog_script = set_current_dialog(event['change_timeline'])
|
|
dialog_index = -1
|
|
go_to_next_event()
|
|
{'condition', 'definition', 'value', 'question_id', ..}:
|
|
# Treating this conditional as an option on a regular question event
|
|
var def_value = null
|
|
var current_question = questions[event['question_id']]
|
|
|
|
for d in definitions['variables']:
|
|
if d['id'] == event['definition']:
|
|
def_value = d['value']
|
|
|
|
var condition_met = def_value != null and _compare_definitions(def_value, event['value'], event['condition']);
|
|
|
|
current_question['answered'] = !condition_met
|
|
if !condition_met:
|
|
# condition not met, skipping branch
|
|
dialog_index = current_question['end_id']
|
|
load_dialog(true)
|
|
else:
|
|
# condition met, entering branch
|
|
go_to_next_event()
|
|
{'set_value', 'definition', ..}:
|
|
emit_signal("event_start", "set_value", event)
|
|
var operation = '='
|
|
if 'operation' in event and not event['operation'].empty():
|
|
operation = event["operation"]
|
|
DialogicSingleton.set_variable_from_id(event['definition'], event['set_value'], operation)
|
|
go_to_next_event()
|
|
{'call_node', ..}:
|
|
dprint('[!] Call Node signal: dialogic_signal(call_node) ', var2str(event['call_node']))
|
|
emit_signal("event_start", "call_node", event)
|
|
$TextBubble.visible = false
|
|
waiting = true
|
|
var target = get_node_or_null(event['call_node']['target_node_path'])
|
|
var method_name = event['call_node']['method_name']
|
|
var args = event['call_node']['arguments']
|
|
if (not args is Array):
|
|
args = []
|
|
|
|
if (target != null):
|
|
if (target.has_method(method_name)):
|
|
if (args.empty()):
|
|
var func_result = target.call(method_name)
|
|
if (func_result is GDScriptFunctionState):
|
|
yield(func_result, "completed")
|
|
else:
|
|
var func_result = target.call(method_name, args)
|
|
if (func_result is GDScriptFunctionState):
|
|
yield(func_result, "completed")
|
|
|
|
waiting = false
|
|
$TextBubble.visible = true
|
|
go_to_next_event()
|
|
_:
|
|
visible = false
|
|
dprint('Other event. ', event)
|
|
|
|
$Options.visible = waiting_for_answer
|
|
|
|
|
|
func reset_options():
|
|
# Clearing out the options after one was selected.
|
|
for option in $Options.get_children():
|
|
option.queue_free()
|
|
|
|
|
|
func add_choice_button(option):
|
|
var theme = current_theme
|
|
|
|
var button = ChoiceButton.instance()
|
|
button.text = option['label']
|
|
# Text
|
|
button.set('custom_fonts/font', DialogicUtil.path_fixer_load(theme.get_value('text', 'font', "res://addons/dialogic/Example Assets/Fonts/DefaultFont.tres")))
|
|
|
|
if not theme.get_value('buttons', 'use_native', false):
|
|
var text_color = Color(theme.get_value('text', 'color', "#ffffffff"))
|
|
button.set('custom_colors/font_color', text_color)
|
|
button.set('custom_colors/font_color_hover', text_color)
|
|
button.set('custom_colors/font_color_pressed', text_color)
|
|
|
|
if theme.get_value('buttons', 'text_color_enabled', true):
|
|
var button_text_color = Color(theme.get_value('buttons', 'text_color', "#ffffffff"))
|
|
button.set('custom_colors/font_color', button_text_color)
|
|
button.set('custom_colors/font_color_hover', button_text_color)
|
|
button.set('custom_colors/font_color_pressed', button_text_color)
|
|
|
|
# Background
|
|
button.get_node('ColorRect').color = Color(theme.get_value('buttons', 'background_color', '#ff000000'))
|
|
button.get_node('ColorRect').visible = theme.get_value('buttons', 'use_background_color', false)
|
|
|
|
button.get_node('TextureRect').visible = theme.get_value('buttons', 'use_image', true)
|
|
if theme.get_value('buttons', 'use_image', true):
|
|
button.get_node('TextureRect').texture = DialogicUtil.path_fixer_load(theme.get_value('buttons', 'image', "res://addons/dialogic/Example Assets/backgrounds/background-2.png"))
|
|
if theme.get_value('buttons', 'modulation', false):
|
|
button.get_node('TextureRect').modulate = Color(theme.get_value('buttons', 'modulation_color', "#ffffffff"))
|
|
|
|
var padding = theme.get_value('buttons', 'padding', Vector2(5,5))
|
|
button.get_node('ColorRect').set('margin_left', -1 * padding.x)
|
|
button.get_node('ColorRect').set('margin_right', padding.x)
|
|
button.get_node('ColorRect').set('margin_top', -1 * padding.y)
|
|
button.get_node('ColorRect').set('margin_bottom', padding.y)
|
|
|
|
button.get_node('TextureRect').set('margin_left', -1 * padding.x)
|
|
button.get_node('TextureRect').set('margin_right', padding.x)
|
|
button.get_node('TextureRect').set('margin_top', -1 * padding.y)
|
|
button.get_node('TextureRect').set('margin_bottom', padding.y)
|
|
|
|
$Options.set('custom_constants/separation', theme.get_value('buttons', 'gap', 20) + (padding.y*2))
|
|
else:
|
|
button.get_node('ColorRect').visible = false
|
|
button.get_node('TextureRect').visible = false
|
|
button.set_flat(false)
|
|
|
|
$Options.set('custom_constants/separation', theme.get_value('buttons', 'gap', 20))
|
|
|
|
button.connect("pressed", self, "answer_question", [button, option['event_id'], option['question_id']])
|
|
|
|
$Options.add_child(button)
|
|
|
|
if Input.get_mouse_mode() != Input.MOUSE_MODE_VISIBLE:
|
|
last_mouse_mode = Input.get_mouse_mode()
|
|
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) # Make sure the cursor is visible for the options selection
|
|
|
|
|
|
func answer_question(i, event_id, question_id):
|
|
dprint('[!] Going to ', event_id + 1, i, 'question_id:', question_id)
|
|
dprint('')
|
|
waiting_for_answer = false
|
|
dialog_index = event_id + 1
|
|
questions[question_id]['answered'] = true
|
|
dprint(' dialog_index = ', dialog_index)
|
|
reset_options()
|
|
load_dialog()
|
|
if last_mouse_mode != null:
|
|
Input.set_mouse_mode(last_mouse_mode) # Revert to last mouse mode when selection is done
|
|
last_mouse_mode = null
|
|
|
|
|
|
func _on_option_selected(option, variable, value):
|
|
dialog_resource.custom_variables[variable] = value
|
|
waiting_for_answer = false
|
|
reset_options()
|
|
load_dialog()
|
|
dprint('[!] Option selected: ', option.text, ' value= ' , value)
|
|
|
|
|
|
func go_to_next_event():
|
|
# The entire event reading system should be refactored... but not today!
|
|
dialog_index += 1
|
|
load_dialog(true)
|
|
|
|
|
|
func grab_portrait_focus(character_data, event: Dictionary = {}) -> bool:
|
|
var exists = false
|
|
var visually_focus = true
|
|
if settings.has_section_key('dialog', 'dim_characters'):
|
|
visually_focus = settings.get_value('dialog', 'dim_characters')
|
|
|
|
for portrait in $Portraits.get_children():
|
|
if portrait.character_data == character_data:
|
|
exists = true
|
|
|
|
if visually_focus:
|
|
portrait.focus()
|
|
if event.has('portrait'):
|
|
if event['portrait'] != '':
|
|
portrait.set_portrait(event['portrait'])
|
|
else:
|
|
if visually_focus:
|
|
portrait.focusout()
|
|
return exists
|
|
|
|
|
|
func get_character_position(positions) -> String:
|
|
if positions['0']:
|
|
return 'left'
|
|
if positions['1']:
|
|
return 'center_left'
|
|
if positions['2']:
|
|
return 'center'
|
|
if positions['3']:
|
|
return 'center_right'
|
|
if positions['4']:
|
|
return 'right'
|
|
return 'left'
|
|
|
|
|
|
func deferred_resize(current_size, result):
|
|
#var result = theme.get_value('box', 'size', Vector2(910, 167))
|
|
$TextBubble.rect_size = result
|
|
if current_size != $TextBubble.rect_size:
|
|
resize_main()
|
|
|
|
|
|
func load_theme(filename):
|
|
var theme = DialogicResources.get_theme_config(filename)
|
|
|
|
# Box size
|
|
call_deferred('deferred_resize', $TextBubble.rect_size, theme.get_value('box', 'size', Vector2(910, 167)))
|
|
|
|
input_next = theme.get_value('settings', 'action_key', 'ui_accept')
|
|
|
|
# Definitions
|
|
var definitions_font = DialogicUtil.path_fixer_load(theme.get_value('definitions', 'font', "res://addons/dialogic/Example Assets/Fonts/GlossaryFont.tres"))
|
|
$DefinitionInfo/VBoxContainer/Title.set('custom_fonts/normal_font', definitions_font)
|
|
$DefinitionInfo/VBoxContainer/Content.set('custom_fonts/normal_font', definitions_font)
|
|
$DefinitionInfo/VBoxContainer/Extra.set('custom_fonts/normal_font', definitions_font)
|
|
|
|
$TextBubble.load_theme(theme)
|
|
|
|
return theme
|
|
|
|
|
|
func _on_RichTextLabel_meta_hover_started(meta):
|
|
var correct_type = false
|
|
for d in definitions['glossary']:
|
|
if d['id'] == meta:
|
|
$DefinitionInfo.load_preview({
|
|
'title': d['title'],
|
|
'body': d['text'],
|
|
'extra': d['extra'],
|
|
'color': current_theme.get_value('definitions', 'color', '#ffbebebe'),
|
|
})
|
|
correct_type = true
|
|
dprint(d)
|
|
|
|
if correct_type:
|
|
definition_visible = true
|
|
$DefinitionInfo.visible = definition_visible
|
|
# Adding a timer to avoid a graphical glitch
|
|
$DefinitionInfo/Timer.stop()
|
|
|
|
|
|
func _on_RichTextLabel_meta_hover_ended(meta):
|
|
# Adding a timer to avoid a graphical glitch
|
|
|
|
$DefinitionInfo/Timer.start(0.1)
|
|
|
|
|
|
func _on_Definition_Timer_timeout():
|
|
# Adding a timer to avoid a graphical glitch
|
|
definition_visible = false
|
|
$DefinitionInfo.visible = definition_visible
|
|
|
|
|
|
func wait_seconds(seconds):
|
|
var timer = Timer.new()
|
|
timer.name = 'WaitSeconds'
|
|
add_child(timer)
|
|
timer.connect("timeout", self, '_on_WaitSeconds_timeout')
|
|
timer.start(seconds)
|
|
$TextBubble.visible = false
|
|
|
|
|
|
func _on_WaitSeconds_timeout():
|
|
emit_signal("event_end", "wait")
|
|
waiting = false
|
|
$WaitSeconds.stop()
|
|
$WaitSeconds.queue_free()
|
|
$TextBubble.visible = true
|
|
load_dialog()
|
|
|
|
|
|
func dprint(string, arg1='', arg2='', arg3='', arg4='' ):
|
|
# HAHAHA if you are here wondering what this is...
|
|
# I ask myself the same question :')
|
|
if debug_mode:
|
|
print(str(string) + str(arg1) + str(arg2) + str(arg3) + str(arg4))
|
|
|
|
|
|
func _compare_definitions(def_value: String, event_value: String, condition: String):
|
|
var condition_met = false;
|
|
if def_value != null and event_value != null:
|
|
# check if event_value equals a definition name and use that instead
|
|
for d in definitions['variables']:
|
|
if (d['name'] != '' and d['name'] == event_value):
|
|
event_value = d['value']
|
|
break;
|
|
var converted_def_value = def_value
|
|
var converted_event_value = event_value
|
|
if def_value.is_valid_float() and event_value.is_valid_float():
|
|
converted_def_value = float(def_value)
|
|
converted_event_value = float(event_value)
|
|
match condition:
|
|
"==":
|
|
condition_met = converted_def_value == converted_event_value
|
|
"!=":
|
|
condition_met = converted_def_value != converted_event_value
|
|
">":
|
|
condition_met = converted_def_value > converted_event_value
|
|
">=":
|
|
condition_met = converted_def_value >= converted_event_value
|
|
"<":
|
|
condition_met = converted_def_value < converted_event_value
|
|
"<=":
|
|
condition_met = converted_def_value <= converted_event_value
|
|
return condition_met
|
|
|
|
|
|
func characters_leave_all():
|
|
var portraits = get_node_or_null('Portraits')
|
|
if portraits != null:
|
|
for p in portraits.get_children():
|
|
p.fade_out()
|
|
|
|
|
|
func close_dialog_event():
|
|
var tween = Tween.new()
|
|
add_child(tween)
|
|
tween.interpolate_property($TextBubble, "modulate",
|
|
$TextBubble.modulate, Color('#00ffffff'), 1,
|
|
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
|
|
tween.start()
|
|
var close_dialog_timer = Timer.new()
|
|
close_dialog_timer.connect("timeout", self, '_on_close_dialog_timeout')
|
|
add_child(close_dialog_timer)
|
|
close_dialog_timer.start(2)
|
|
characters_leave_all()
|
|
|
|
|
|
func _on_close_dialog_timeout():
|
|
on_timeline_end()
|
|
queue_free()
|