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.
388 lines
14 KiB
388 lines
14 KiB
extends Node |
|
|
|
## Exposed and safe to use methods for Dialogic |
|
## See documentation under 'https://github.com/coppolaemilio/dialogic' or in the editor: |
|
|
|
## ### /!\ ### |
|
## Do not use methods from other classes as it could break the plugin's integrity |
|
## ### /!\ ### |
|
|
|
## Trying to follow this documentation convention: https://github.com/godotengine/godot/pull/41095 |
|
class_name Dialogic |
|
|
|
|
|
## Refactor the start function for 2.0 there should be a cleaner way to do it :) |
|
|
|
## Starts the dialog for the given timeline and returns a Dialog node. |
|
## You must then add it manually to the scene to display the dialog. |
|
## |
|
## Example: |
|
## var new_dialog = Dialogic.start('Your Timeline Name Here') |
|
## add_child(new_dialog) |
|
## |
|
## This is similar to using the editor: |
|
## you can drag and drop the scene located at /addons/dialogic/Dialog.tscn |
|
## and set the current timeline via the inspector. |
|
## |
|
## @param timeline The timeline to load. You can provide the timeline name or the filename. |
|
## If you leave it empty, it will try to load from current data |
|
## In that case, you should do Dialogic.load() or Dialogic.import() before. |
|
## @param default_timeline If timeline == '' and no valid data was found, this will be loaded. |
|
## @param dialog_scene_path If you made a custom Dialog scene or moved it from its default path, you can specify its new path here. |
|
## @param debug_mode Debug is disabled by default but can be enabled if needed. |
|
## @param use_canvas_instead Create the Dialog inside a canvas layer to make it show up regardless of the camera 2D/3D situation. |
|
## @returns A Dialog node to be added into the scene tree. |
|
static func start(timeline: String = '', default_timeline: String ='', dialog_scene_path: String="res://addons/dialogic/Nodes/DialogNode.tscn", debug_mode: bool=false, use_canvas_instead=true): |
|
var dialog_scene = load(dialog_scene_path) |
|
var dialog_node = null |
|
var canvas_dialog_node = null |
|
var returned_dialog_node = null |
|
|
|
if use_canvas_instead: |
|
var canvas_dialog_script = load("res://addons/dialogic/Nodes/canvas_dialog_node.gd") |
|
canvas_dialog_node = canvas_dialog_script.new() |
|
canvas_dialog_node.set_dialog_node_scene(dialog_scene) |
|
dialog_node = canvas_dialog_node.dialog_node |
|
else: |
|
dialog_node = dialog_scene.instance() |
|
|
|
dialog_node.debug_mode = debug_mode |
|
|
|
returned_dialog_node = dialog_node if not canvas_dialog_node else canvas_dialog_node |
|
|
|
## 1. Case: A slot has been loaded OR data has been imported |
|
if timeline == '': |
|
if (Engine.get_main_loop().has_meta('last_dialog_state') |
|
and not Engine.get_main_loop().get_meta('last_dialog_state').empty() |
|
and not Engine.get_main_loop().get_meta('last_dialog_state').get('timeline', '').empty()): |
|
|
|
dialog_node.resume_state_from_info(Engine.get_main_loop().get_meta('last_dialog_state')) |
|
return returned_dialog_node |
|
|
|
## The loaded data isn't complete |
|
elif (Engine.get_main_loop().has_meta('current_timeline') |
|
and not Engine.get_main_loop().get_meta('current_timeline').empty()): |
|
timeline = Engine.get_main_loop().get_meta('current_timeline') |
|
|
|
## Else load the default timeline |
|
else: |
|
timeline = default_timeline |
|
|
|
## 2. Case: A specific timeline should be started |
|
|
|
# check if it's a file name |
|
if timeline.ends_with('.json'): |
|
for t in DialogicUtil.get_timeline_list(): |
|
if t['file'] == timeline: |
|
dialog_node.timeline = t['file'] |
|
return returned_dialog_node |
|
# No file found. Show error |
|
dialog_node.dialog_script = { |
|
"events":[ |
|
{"event_id":'dialogic_001', |
|
"character":"", |
|
"portrait":"", |
|
"text":"[Dialogic Error] Loading dialog [color=red]" + timeline + "[/color]. It seems like the timeline doesn't exists. Maybe the name is wrong?" |
|
}] |
|
} |
|
return returned_dialog_node |
|
|
|
# else get the file from the name |
|
var timeline_file = _get_timeline_file_from_name(timeline) |
|
if timeline_file: |
|
dialog_node.timeline = timeline_file |
|
return returned_dialog_node |
|
|
|
# Just in case everything else fails. |
|
return returned_dialog_node |
|
|
|
|
|
|
|
################################################################################ |
|
## BUILT-IN SAVING/LOADING |
|
################################################################################ |
|
|
|
## Loads the given slot |
|
static func load(slot_name: String = ''): |
|
_load_from_slot(slot_name) |
|
Engine.get_main_loop().set_meta('current_save_slot', slot_name) |
|
|
|
|
|
## Saves the current definitions and the latest added dialog nodes state info. |
|
## |
|
## @param slot_name The name of the save slot. To load this save you have to specify the same |
|
## If the slot folder doesn't exist it will be created. |
|
## Leaving this empty will use the last loaded save slot. |
|
static func save(slot_name: String = '', is_autosave = false) -> void: |
|
# check if to save (if this is a autosave) |
|
if is_autosave and not get_autosave(): |
|
return |
|
|
|
# gather the info |
|
var current_dialog_info = {} |
|
if has_current_dialog_node(): |
|
current_dialog_info = Engine.get_main_loop().get_meta('latest_dialogic_node').get_current_state_info() |
|
|
|
var game_state = {} |
|
if Engine.get_main_loop().has_meta('game_state'): |
|
game_state = Engine.get_main_loop().get_meta('game_state') |
|
|
|
var save_data = { |
|
'game_state': game_state, |
|
'dialog_state': current_dialog_info |
|
} |
|
|
|
# save the information |
|
_save_state_and_definitions(slot_name, save_data) |
|
|
|
|
|
## Returns an array with the names of all available slots. |
|
static func get_slot_names() -> Array: |
|
return DialogicResources.get_saves_folders() |
|
|
|
|
|
## Will permanently erase the data in the given save_slot. |
|
## |
|
## @param slot_name The name of the slot folder. |
|
static func erase_slot(slot_name: String) -> void: |
|
DialogicResources.remove_save_folder(slot_name) |
|
|
|
|
|
## Whether a save can be performed |
|
## |
|
## @returns True if a save can be performed; otherwise False |
|
static func has_current_dialog_node() -> bool: |
|
return Engine.get_main_loop().has_meta('latest_dialogic_node') and is_instance_valid(Engine.get_main_loop().get_meta('latest_dialogic_node')) |
|
|
|
|
|
## Resets the state and definitions of the given save slot |
|
## |
|
## By default this will also LOAD that reseted save |
|
static func reset_saves(slot_name: String = '', reload:= true) -> void: |
|
DialogicResources.reset_save(slot_name) |
|
if reload: _load_from_slot(slot_name) |
|
|
|
|
|
## Returns the currently loaded save slot |
|
static func get_current_slot(): |
|
if Engine.get_main_loop().has_meta('current_save_slot'): |
|
return Engine.get_main_loop().get_meta('current_save_slot') |
|
else: |
|
return '' |
|
|
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
## EXPORT / IMPORT |
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
|
|
# this returns a dictionary with the DEFINITIONS, the GAME STATE and the DIALOG STATE |
|
static func export(dialog_node = null) -> Dictionary: |
|
# gather the data |
|
var current_dialog_info = {} |
|
if dialog_node == null and has_current_dialog_node(): |
|
dialog_node = Engine.get_main_loop().get_meta('latest_dialogic_node') |
|
if dialog_node: |
|
current_dialog_info = dialog_node.get_current_state_info() |
|
|
|
# return it |
|
return { |
|
'definitions': _get_definitions(), |
|
'state': Engine.get_main_loop().get_meta('game_state'), |
|
'dialog_state': current_dialog_info |
|
} |
|
|
|
|
|
# this loads a dictionary with GAME STATE, DEFINITIONS and DIALOG_STATE |
|
static func import(data: Dictionary) -> void: |
|
## Tell the future we want to use the imported data |
|
Engine.get_main_loop().set_meta('current_save_lot', '/') |
|
|
|
# load the data |
|
Engine.get_main_loop().set_meta('definitions', data['definitions']) |
|
Engine.get_main_loop().set_meta('game_state', data['state']) |
|
Engine.get_main_loop().set_meta('last_dialog_state', data.get('dialog_state', null)) |
|
set_current_timeline(get_saved_state_general_key('timeline')) |
|
|
|
|
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
## DEFINITIONS |
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
|
|
# sets the value of the value definition with the given name |
|
static func set_variable(name: String, value): |
|
var exists = false |
|
for d in _get_definitions()['variables']: |
|
if d['name'] == name: |
|
d['value'] = str(value) |
|
exists = true |
|
if exists == false: |
|
# TODO it would be great to automatically generate that missing variable here so they don't |
|
# have to create it from the editor. |
|
print("[Dialogic] Warning! the variable [" + name + "] doesn't exists. Create it from the Dialogic editor.") |
|
return value |
|
|
|
# returns the value of the value definition with the given name |
|
static func get_variable(name: String, default = null): |
|
for d in _get_definitions()['variables']: |
|
if d['name'] == name: |
|
return d['value'] |
|
print("[Dialogic] Warning! the variable [" + name + "] doesn't exists.") |
|
return default |
|
|
|
|
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
## GAME STATE |
|
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
# the game state is a global dictionary that can be used to store custom data |
|
# these functions should be renamed in 2.0! These names are outdated. |
|
|
|
# this sets a value in the GAME STATE dictionary |
|
static func get_saved_state_general_key(key: String, default = '') -> String: |
|
if not Engine.get_main_loop().has_meta('game_state'): |
|
return default |
|
if key in Engine.get_main_loop().get_meta('game_state').keys(): |
|
return Engine.get_main_loop().get_meta('game_state')[key] |
|
else: |
|
return default |
|
|
|
|
|
# this gets a value from the GAME STATE dictionary |
|
static func set_saved_state_general_key(key: String, value) -> void: |
|
if not Engine.get_main_loop().has_meta('game_state'): |
|
Engine.get_main_loop().set_meta('game_state', {}) |
|
Engine.get_main_loop().get_meta('game_state')[key] = str(value) |
|
save('', true) |
|
|
|
|
|
|
|
################################################################################ |
|
## COULD BE USED |
|
################################################################################ |
|
# these are old things, that have little use. |
|
|
|
static func get_autosave() -> bool: |
|
if Engine.get_main_loop().has_meta('autoload'): |
|
return Engine.get_main_loop().get_meta('autoload') |
|
return true |
|
|
|
|
|
static func set_autosave(autoload): |
|
Engine.get_main_loop().set_meta('autoload', autoload) |
|
|
|
|
|
static func set_current_timeline(timeline): |
|
Engine.get_main_loop().set_meta('current_timeline', timeline) |
|
return timeline |
|
|
|
|
|
static func get_current_timeline(): |
|
var timeline |
|
timeline = Engine.get_main_loop().get_meta('current_timeline') |
|
if timeline == null: |
|
timeline = '' |
|
return timeline |
|
|
|
################################################################################ |
|
## NOT TO BE USED FROM OUTSIDE |
|
################################################################################ |
|
## this loads the saves definitions and returns the saves state_info ditionary |
|
static func _load_from_slot(slot_name: String = '') -> Dictionary: |
|
Engine.get_main_loop().set_meta('definitions', DialogicResources.get_saved_definitions(slot_name)) |
|
|
|
var state_info = DialogicResources.get_saved_state_info(slot_name) |
|
Engine.get_main_loop().set_meta('last_dialog_state', state_info.get('dialog_state', null)) |
|
Engine.get_main_loop().set_meta('game_state', state_info.get('game_state', null)) |
|
|
|
return state_info.get('dialog_state', {}) |
|
|
|
|
|
## this saves the current definitions and the given state info into the save folder @save_name |
|
static func _save_state_and_definitions(save_name: String, state_info: Dictionary) -> void: |
|
DialogicResources.save_definitions(save_name, _get_definitions()) |
|
DialogicResources.save_state_info(save_name, state_info) |
|
|
|
|
|
|
|
static func _get_definitions() -> Dictionary: |
|
var definitions |
|
if Engine.get_main_loop().has_meta('definitions'): |
|
definitions = Engine.get_main_loop().get_meta('definitions') |
|
else: |
|
definitions = DialogicResources.get_default_definitions() |
|
Engine.get_main_loop().set_meta('definitions', definitions) |
|
return definitions |
|
|
|
|
|
# used by the DialogNode |
|
static func set_glossary_from_id(id: String, title: String, text: String, extra:String) -> void: |
|
var target_def: Dictionary; |
|
for d in _get_definitions()['glossary']: |
|
if d['id'] == id: |
|
target_def = d; |
|
if target_def != null: |
|
if title and title != "[No Change]": |
|
target_def['title'] = title |
|
if text and text != "[No Change]": |
|
target_def['text'] = text |
|
if extra and extra != "[No Change]": |
|
target_def['extra'] = extra |
|
|
|
# used by the DialogNode |
|
static func set_variable_from_id(id: String, value: String, operation: String) -> void: |
|
var target_def: Dictionary; |
|
for d in _get_definitions()['variables']: |
|
if d['id'] == id: |
|
target_def = d; |
|
if target_def != null: |
|
var converted_set_value = value |
|
var converted_target_value = target_def['value'] |
|
var is_number = converted_set_value.is_valid_float() and converted_target_value.is_valid_float() |
|
if is_number: |
|
converted_set_value = float(value) |
|
converted_target_value = float(target_def['value']) |
|
var result = target_def['value'] |
|
# Do nothing for -, * and / operations on string |
|
match operation: |
|
'=': |
|
result = converted_set_value |
|
'+': |
|
result = converted_target_value + converted_set_value |
|
'-': |
|
if is_number: |
|
result = converted_target_value - converted_set_value |
|
'*': |
|
if is_number: |
|
result = converted_target_value * converted_set_value |
|
'/': |
|
if is_number: |
|
result = converted_target_value / converted_set_value |
|
target_def['value'] = str(result) |
|
|
|
# tries to find the path of a given timeline |
|
static func _get_timeline_file_from_name(timeline_name_path: String) -> String: |
|
var timelines = DialogicUtil.get_full_resource_folder_structure()['folders']['Timelines'] |
|
var parts = timeline_name_path.split('/', false) |
|
if parts.size() > 1: |
|
var current_data |
|
var current_depth = 0 |
|
for p in parts: |
|
if current_depth == 0: |
|
# Starting the crawl |
|
current_data = timelines['folders'][p] |
|
elif current_depth == parts.size() - 1: |
|
# The final destination |
|
for t in DialogicUtil.get_timeline_list(): |
|
for f in current_data['files']: |
|
if t['file'] == f && t['name'] == p: |
|
return t['file'] |
|
|
|
else: |
|
# Still going deeper |
|
current_data = current_data['folders'][p] |
|
current_depth += 1 |
|
else: |
|
# Searching for any timeline that could match that name |
|
for t in DialogicUtil.get_timeline_list(): |
|
if parts.size(): |
|
if t['name'] == parts[0]: |
|
return t['file'] |
|
return ''
|
|
|