mirror of
https://github.com/RPG-Research/bcirpg.git
synced 2024-04-16 14:23:01 +00:00
Made a toolset folder, and added the Dialogic plugin.
This commit is contained in:
538
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicClass.gd
Normal file
538
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicClass.gd
Normal file
@ -0,0 +1,538 @@
|
||||
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 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", 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()
|
||||
|
||||
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']
|
||||
dialog_node.timeline_name = timeline
|
||||
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
|
||||
dialog_node.timeline_name = timeline
|
||||
return returned_dialog_node
|
||||
|
||||
# Just in case everything else fails.
|
||||
return returned_dialog_node
|
||||
|
||||
# Loads the given timeline into the active DialogNode
|
||||
# This means it's state (theme, characters, background, music) is preserved.
|
||||
#
|
||||
# @param timeline the name of the timeline to load
|
||||
static func change_timeline(timeline: String) -> void:
|
||||
# Set Timeline
|
||||
set_current_timeline(timeline)
|
||||
|
||||
# If there is a dialog node
|
||||
if has_current_dialog_node():
|
||||
var dialog_node = Engine.get_main_loop().get_meta('latest_dialogic_node')
|
||||
|
||||
# Get file name
|
||||
var timeline_file = _get_timeline_file_from_name(timeline)
|
||||
|
||||
dialog_node.change_timeline(timeline_file)
|
||||
else:
|
||||
print("[D] Tried to change timeline, but no DialogNode exists!")
|
||||
|
||||
# Immediately plays the next event.
|
||||
#
|
||||
# @param discreetly determines whether the Passing Audio will be played in the process
|
||||
static func next_event(discreetly: bool = false):
|
||||
|
||||
# If there is a dialog node
|
||||
if has_current_dialog_node():
|
||||
var dialog_node = Engine.get_main_loop().get_meta('latest_dialogic_node')
|
||||
|
||||
dialog_node.next_event(discreetly)
|
||||
|
||||
|
||||
################################################################################
|
||||
## Test to see if a timeline exists
|
||||
################################################################################
|
||||
|
||||
## Check to see if a timeline with a given name/path exists. Useful for verifying
|
||||
## before calling a timeline, or for automated tests to make sure timeline calls
|
||||
## are valid. Returns a boolean of true if the timeline exists, and false if it
|
||||
## does not.
|
||||
static func timeline_exists(timeline: String):
|
||||
var timeline_file = _get_timeline_file_from_name(timeline)
|
||||
if timeline_file:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
################################################################################
|
||||
## 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.
|
||||
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
|
||||
## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
# clears all variables
|
||||
static func clear_all_variables():
|
||||
for d in _get_definitions()['variables']:
|
||||
d['value'] = ""
|
||||
|
||||
# sets the value of the value definition with the given name
|
||||
static func set_variable(name: String, value):
|
||||
var exists = false
|
||||
|
||||
if '/' in name:
|
||||
var variable_id = _get_variable_from_file_name(name)
|
||||
if variable_id != '':
|
||||
for d in _get_definitions()['variables']:
|
||||
if d['id'] == variable_id:
|
||||
d['value'] = str(value)
|
||||
exists = true
|
||||
else:
|
||||
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):
|
||||
if '/' in name:
|
||||
var variable_id = _get_variable_from_file_name(name)
|
||||
for d in _get_definitions()['variables']:
|
||||
if d['id'] == variable_id:
|
||||
return d['value']
|
||||
print("[Dialogic] Warning! the variable [" + name + "] doesn't exists.")
|
||||
return default
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
################################################################################
|
||||
## HISTORY
|
||||
################################################################################
|
||||
|
||||
# Used to toggle the history timeline display. Only useful if you do not wish to
|
||||
# use the provided buttons
|
||||
static func toggle_history():
|
||||
if has_current_dialog_node():
|
||||
var dialog_node = Engine.get_main_loop().get_meta('latest_dialogic_node')
|
||||
dialog_node.HistoryTimeline._on_toggle_history()
|
||||
else:
|
||||
print('[D] Tried to toggle history, but no dialog node exists.')
|
||||
|
||||
|
||||
################################################################################
|
||||
## 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
|
||||
|
||||
|
||||
# Returns a string with the action button set on the project settings
|
||||
static func get_action_button():
|
||||
return DialogicResources.get_settings_value('input', 'default_action_key', 'dialogic_default_action')
|
||||
|
||||
################################################################################
|
||||
## 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']
|
||||
|
||||
# Checks for slash in the name, and uses the folder search if there is
|
||||
if '/' in timeline_name_path:
|
||||
#Add leading slash if its a path and it is missing, for paths that have subfolders but no leading slash
|
||||
if(timeline_name_path.left(1) != '/'):
|
||||
timeline_name_path = "/" + timeline_name_path
|
||||
var parts = timeline_name_path.split('/', false)
|
||||
|
||||
# First check if it's a timeline in the root folder
|
||||
if parts.size() == 1:
|
||||
for t in DialogicUtil.get_timeline_list():
|
||||
for f in timelines['files']:
|
||||
if t['file'] == f && t['name'] == parts[0]:
|
||||
return t['file']
|
||||
if parts.size() > 1:
|
||||
var current_data
|
||||
var current_depth = 0
|
||||
for p in parts:
|
||||
if current_depth == 0:
|
||||
# Starting the crawl
|
||||
if (timelines['folders'].has(p) ):
|
||||
current_data = timelines['folders'][p]
|
||||
else:
|
||||
return ''
|
||||
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
|
||||
if (current_data['folders'].size() > 0):
|
||||
if p in current_data['folders']:
|
||||
current_data = current_data['folders'][p]
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
current_depth += 1
|
||||
return ''
|
||||
else:
|
||||
# Searching for any timeline that could match that name
|
||||
for t in DialogicUtil.get_timeline_list():
|
||||
if t['name'] == timeline_name_path:
|
||||
return t['file']
|
||||
return ''
|
||||
|
||||
static func _get_variable_from_file_name(variable_name_path: String) -> String:
|
||||
#First add the leading slash if it is missing so algorithm works properly
|
||||
if(variable_name_path.left(1) != '/'):
|
||||
variable_name_path = "/" + variable_name_path
|
||||
|
||||
var definitions = DialogicUtil.get_full_resource_folder_structure()['folders']['Definitions']
|
||||
var parts = variable_name_path.split('/', false)
|
||||
|
||||
# Check the root if it's a variable in the root folder
|
||||
if parts.size() == 1:
|
||||
for t in _get_definitions()['variables']:
|
||||
for f in definitions['files']:
|
||||
if t['id'] == f && t['name'] == parts[0]:
|
||||
return t['id']
|
||||
if parts.size() > 1:
|
||||
var current_data
|
||||
var current_depth = 0
|
||||
|
||||
for p in parts:
|
||||
if current_depth == 0:
|
||||
|
||||
# Starting the crawl
|
||||
if (definitions['folders'].has(p)):
|
||||
current_data = definitions['folders'][p]
|
||||
else:
|
||||
return ''
|
||||
elif current_depth == parts.size() - 1:
|
||||
# The final destination
|
||||
for t in _get_definitions()['variables']:
|
||||
for f in current_data['files']:
|
||||
if t['id'] == f && t['name'] == p:
|
||||
return t['id']
|
||||
|
||||
else:
|
||||
# Still going deeper
|
||||
if (current_data['folders'].size() > 0):
|
||||
if p in current_data['folders']:
|
||||
current_data = current_data['folders'][p]
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
current_depth += 1
|
||||
return ''
|
@ -0,0 +1,72 @@
|
||||
extends Node
|
||||
class_name DialogicDefinitionsUtil
|
||||
|
||||
## This class handles definitions
|
||||
|
||||
static func get_definition_by_key(data: Dictionary, key: String, value: String):
|
||||
var variables : Array = data['variables']
|
||||
var glossary : Array = data['glossary']
|
||||
for v in variables:
|
||||
if v[key] == value:
|
||||
return v
|
||||
for g in glossary:
|
||||
if g[key] == value:
|
||||
return g
|
||||
return null
|
||||
|
||||
|
||||
static func get_definition_by_id(data: Dictionary, id: String):
|
||||
return get_definition_by_key(data, 'id', id)
|
||||
|
||||
|
||||
static func get_definition_by_name(data: Dictionary, id: String):
|
||||
return get_definition_by_key(data, 'name', id)
|
||||
|
||||
|
||||
static func set_definition(section: String, data: Dictionary, elem: Dictionary):
|
||||
delete_definition(data, elem['id'])
|
||||
var array: Array = data[section]
|
||||
var found = false;
|
||||
for e in array:
|
||||
if e['id'] == elem['id']:
|
||||
found = true
|
||||
array.erase(e)
|
||||
array.append(elem)
|
||||
break
|
||||
if not found:
|
||||
array.append(elem)
|
||||
|
||||
|
||||
static func set_definition_variable(data: Dictionary, id: String, name: String, value):
|
||||
set_definition('variables', data, {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'value': value,
|
||||
'type': 0
|
||||
})
|
||||
|
||||
|
||||
static func set_definition_glossary(data: Dictionary, id: String, name: String, title: String, text: String, extra: String):
|
||||
set_definition('glossary', data, {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'title': title,
|
||||
'text': text,
|
||||
'extra': extra,
|
||||
'type': 1
|
||||
})
|
||||
|
||||
|
||||
static func delete_definition(data: Dictionary, id: String):
|
||||
var variables : Array = data['variables']
|
||||
var glossary : Array = data['glossary']
|
||||
var item = get_definition_by_id(data, id);
|
||||
if item != null:
|
||||
if (item['type'] == 0):
|
||||
variables.erase(item)
|
||||
else:
|
||||
glossary.erase(item)
|
||||
|
||||
|
||||
static func definitions_json_to_array(data: Dictionary) -> Array:
|
||||
return data['variables'] + data['glossary']
|
@ -0,0 +1,461 @@
|
||||
tool
|
||||
class_name DialogicResources
|
||||
|
||||
## This class is used by the DialogicEditor to access the resources files
|
||||
## For example by the Editors (Timeline, Character, Theme), the MasterTree and the EventParts
|
||||
|
||||
## It is also used by the DialogicUtil class
|
||||
|
||||
const RESOURCES_DIR: String = "res://dialogic" # Readonly, used for static data
|
||||
const WORKING_DIR: String = "user://dialogic" # Readwrite, used for saves
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## BASIC JSON FUNCTION
|
||||
## *****************************************************************************
|
||||
static func load_json(path: String, default: Dictionary={}) -> Dictionary:
|
||||
# An easy function to load json files and handle common errors.
|
||||
var file := File.new()
|
||||
if file.open(path, File.READ) != OK:
|
||||
file.close()
|
||||
return default
|
||||
var data_text: String = file.get_as_text()
|
||||
file.close()
|
||||
if data_text.empty():
|
||||
return default
|
||||
var data_parse: JSONParseResult = JSON.parse(data_text)
|
||||
if data_parse.error != OK:
|
||||
return default
|
||||
|
||||
var final_data = data_parse.result
|
||||
if typeof(final_data) == TYPE_DICTIONARY:
|
||||
return final_data
|
||||
|
||||
# If everything else fails
|
||||
return default
|
||||
|
||||
|
||||
static func set_json(path: String, data: Dictionary):
|
||||
var file = File.new()
|
||||
var err = file.open(path, File.WRITE)
|
||||
if err == OK:
|
||||
file.store_line(JSON.print(data, '\t', true))
|
||||
file.close()
|
||||
return err
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## INITIALIZATION
|
||||
## *****************************************************************************
|
||||
static func init_dialogic_files() -> void:
|
||||
# This functions makes sure that the needed files and folders
|
||||
# exists when the plugin is loaded. If they don't, we create
|
||||
# them.
|
||||
# WARNING: only call while in the editor
|
||||
var directory = Directory.new()
|
||||
var paths = get_working_directories()
|
||||
var files = get_config_files_paths()
|
||||
# Create directories
|
||||
for dir in paths:
|
||||
if not directory.dir_exists(paths[dir]):
|
||||
directory.make_dir_recursive(paths[dir])
|
||||
if dir == 'THEME_DIR':
|
||||
directory.copy('res://addons/dialogic/Editor/ThemeEditor/default-theme.cfg', str(paths[dir], '/default-theme.cfg'))
|
||||
# Create empty files
|
||||
for f in files:
|
||||
if not directory.file_exists(files[f]):
|
||||
create_empty_file(files[f])
|
||||
|
||||
|
||||
static func get_working_directories() -> Dictionary:
|
||||
return {
|
||||
'RESOURCES_DIR': RESOURCES_DIR,
|
||||
'WORKING_DIR': WORKING_DIR,
|
||||
'TIMELINE_DIR': RESOURCES_DIR + "/timelines",
|
||||
'THEME_DIR': RESOURCES_DIR + "/themes",
|
||||
'CHAR_DIR': RESOURCES_DIR + "/characters",
|
||||
'CUSTOM_EVENTS_DIR': RESOURCES_DIR + "/custom-events",
|
||||
'SOUNDS':RESOURCES_DIR + "/sounds"
|
||||
}
|
||||
|
||||
|
||||
static func get_config_files_paths() -> Dictionary:
|
||||
return {
|
||||
'SETTINGS_FILE': RESOURCES_DIR + "/settings.cfg",
|
||||
'DEFAULT_DEFINITIONS_FILE': RESOURCES_DIR + "/definitions.json",
|
||||
'FOLDER_STRUCTURE_FILE': RESOURCES_DIR + "/folder_structure.json",
|
||||
'DEFINITIONS_DEFAULT_SAVE': WORKING_DIR + "/definitions_default_save.json",
|
||||
'STATE_DEFAULT_SAVE': WORKING_DIR + "/state_default_save.json"
|
||||
}
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## BASIC FILE FUNCTION
|
||||
## *****************************************************************************
|
||||
static func get_path(name: String, extra: String ='') -> String:
|
||||
var paths: Dictionary = get_working_directories()
|
||||
if extra != '':
|
||||
return paths[name] + '/' + extra
|
||||
else:
|
||||
return paths[name]
|
||||
|
||||
|
||||
static func get_filename_from_path(path: String, extension = false) -> String:
|
||||
var file_name: String = path.split('/')[-1]
|
||||
if extension == false:
|
||||
file_name = file_name.split('.')[0]
|
||||
return file_name
|
||||
|
||||
|
||||
static func listdir(path: String) -> Array:
|
||||
# https://docs.godotengine.org/en/stable/classes/class_directory.html#description
|
||||
var files: Array = []
|
||||
var dir := Directory.new()
|
||||
var err = dir.open(path)
|
||||
if err == OK:
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
if not dir.current_is_dir() and not file_name.begins_with("."):
|
||||
files.append(file_name)
|
||||
file_name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
else:
|
||||
print("[Dialogic] Error while accessing path " + path + " - Error: " + str(err))
|
||||
return files
|
||||
|
||||
|
||||
static func create_empty_file(path):
|
||||
var file = File.new()
|
||||
file.open(path, File.WRITE)
|
||||
file.store_string('')
|
||||
file.close()
|
||||
|
||||
|
||||
static func remove_file(path: String):
|
||||
var dir = Directory.new()
|
||||
var _err = dir.remove(path)
|
||||
|
||||
if _err != OK:
|
||||
print("[D] There was an error when deleting file at {filepath}. Error: {error}".format(
|
||||
{"filepath":path,"error":_err}
|
||||
))
|
||||
|
||||
|
||||
static func copy_file(path_from, path_to):
|
||||
if (path_from == ''):
|
||||
push_error("[Dialogic] Could not copy empty filename")
|
||||
return ERR_FILE_BAD_PATH
|
||||
|
||||
if (path_to == ''):
|
||||
push_error("[Dialogic] Could not copy to empty filename")
|
||||
return ERR_FILE_BAD_PATH
|
||||
|
||||
var dir = Directory.new()
|
||||
if (not dir.file_exists(path_from)):
|
||||
push_error("[Dialogic] Could not copy file %s, File does not exists" % [ path_from ])
|
||||
return ERR_FILE_NOT_FOUND
|
||||
|
||||
if (dir.file_exists(path_to)):
|
||||
push_error("[Dialogic] Could not copy file to %s, file already exists" % [ path_to ])
|
||||
return ERR_ALREADY_EXISTS
|
||||
|
||||
var error = dir.copy(path_from, path_to)
|
||||
if (error):
|
||||
push_error("[Dialogic] Error while copying %s to %s" % [ path_from, path_to ])
|
||||
push_error(error)
|
||||
return error
|
||||
|
||||
return OK
|
||||
pass
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## CONFIG
|
||||
## *****************************************************************************
|
||||
static func get_config(id: String) -> ConfigFile:
|
||||
var paths := get_config_files_paths()
|
||||
var config := ConfigFile.new()
|
||||
if id in paths.keys():
|
||||
var err = config.load(paths[id])
|
||||
if err != OK:
|
||||
print("[Dialogic] Error while opening config file " + paths[id] + ". Error: " + str(err))
|
||||
return config
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## TIMELINES
|
||||
## *****************************************************************************
|
||||
# Can only be edited in the editor
|
||||
|
||||
static func get_timeline_json(path: String):
|
||||
return load_json(get_path('TIMELINE_DIR', path))
|
||||
|
||||
|
||||
static func set_timeline(timeline: Dictionary):
|
||||
# WARNING: For use in the editor only
|
||||
set_json(get_path('TIMELINE_DIR', timeline['metadata']['file']), timeline)
|
||||
|
||||
|
||||
static func delete_timeline(filename: String):
|
||||
# WARNING: For use in the editor only
|
||||
remove_file(get_path('TIMELINE_DIR', filename))
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## CHARACTERS
|
||||
## *****************************************************************************
|
||||
# Can only be edited in the editor
|
||||
|
||||
static func get_character_json(path: String):
|
||||
return load_json(get_path('CHAR_DIR', path))
|
||||
|
||||
|
||||
static func set_character(character: Dictionary):
|
||||
# WARNING: For use in the editor only
|
||||
set_json(get_path('CHAR_DIR', character['id']), character)
|
||||
|
||||
|
||||
static func delete_character(filename: String):
|
||||
# WARNING: For use in the editor only
|
||||
remove_file(get_path('CHAR_DIR', filename))
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## THEMES
|
||||
## *****************************************************************************
|
||||
# Can only be edited in the editor
|
||||
|
||||
static func get_theme_config(filename: String):
|
||||
var config = ConfigFile.new()
|
||||
var path
|
||||
if filename.begins_with('res://'):
|
||||
path = filename
|
||||
else:
|
||||
path = get_path('THEME_DIR', filename)
|
||||
var err = config.load(path)
|
||||
if err == OK:
|
||||
return config
|
||||
|
||||
|
||||
static func set_theme_value(filename:String, section:String, key:String, value):
|
||||
# WARNING: For use in the editor only
|
||||
var config = get_theme_config(filename)
|
||||
config.set_value(section, key, value)
|
||||
config.save(get_path('THEME_DIR', filename))
|
||||
|
||||
|
||||
static func add_theme(filename: String):
|
||||
create_empty_file(get_path('THEME_DIR', filename))
|
||||
|
||||
|
||||
static func delete_theme(filename: String):
|
||||
remove_file(get_path('THEME_DIR', filename))
|
||||
|
||||
|
||||
static func duplicate_theme(from_filename: String, to_filename: String):
|
||||
copy_file(get_path('THEME_DIR', from_filename), get_path('THEME_DIR', to_filename))
|
||||
|
||||
## *****************************************************************************
|
||||
## SETTINGS
|
||||
## *****************************************************************************
|
||||
# Can only be edited in the editor
|
||||
|
||||
|
||||
static func get_settings_config() -> ConfigFile:
|
||||
return get_config("SETTINGS_FILE")
|
||||
|
||||
|
||||
static func set_settings_value(section: String, key: String, value):
|
||||
var config = get_settings_config()
|
||||
config.set_value(section, key, value)
|
||||
config.save(get_config_files_paths()['SETTINGS_FILE'])
|
||||
|
||||
static func get_settings_value(section:String, key: String, default):
|
||||
var config = get_settings_config()
|
||||
return config.get_value(section, key, default)
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## DEFAULT DEFINITIONS
|
||||
## *****************************************************************************
|
||||
# Can only be edited in the editor
|
||||
|
||||
|
||||
static func get_default_definitions() -> Dictionary:
|
||||
return load_json(get_config_files_paths()['DEFAULT_DEFINITIONS_FILE'], {'variables': [], 'glossary': []})
|
||||
|
||||
|
||||
static func save_default_definitions(data: Dictionary):
|
||||
set_json(get_config_files_paths()['DEFAULT_DEFINITIONS_FILE'], data)
|
||||
|
||||
|
||||
static func get_default_definition_item(id: String):
|
||||
var data = get_default_definitions()
|
||||
return DialogicDefinitionsUtil.get_definition_by_id(data, id)
|
||||
|
||||
|
||||
static func set_default_definition_variable(id: String, name: String, value):
|
||||
# WARNING: For use in the editor only
|
||||
var data = get_default_definitions()
|
||||
DialogicDefinitionsUtil.set_definition_variable(data, id, name, value)
|
||||
save_default_definitions(data)
|
||||
|
||||
|
||||
static func set_default_definition_glossary(id: String, name: String, extra_title: String, extra_text: String, extra_extra: String):
|
||||
# WARNING: For use in the editor only
|
||||
var data = get_default_definitions()
|
||||
DialogicDefinitionsUtil.set_definition_glossary(data, id, name, extra_title, extra_text, extra_extra)
|
||||
save_default_definitions(data)
|
||||
|
||||
|
||||
static func delete_default_definition(id: String):
|
||||
# WARNING: For use in the editor only
|
||||
var data = get_default_definitions()
|
||||
DialogicDefinitionsUtil.delete_definition(data, id)
|
||||
save_default_definitions(data)
|
||||
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## SAVES DURING GAME
|
||||
## *****************************************************************************
|
||||
# Folders in the user://dialogic directory function as save_slots.
|
||||
|
||||
# retruns a list of all save folders.
|
||||
# -> this returns a list of the save_slot-names
|
||||
static func get_saves_folders() -> Array:
|
||||
var save_folders = []
|
||||
var directory := Directory.new()
|
||||
if directory.open(WORKING_DIR) != OK:
|
||||
print("[D] Error: Failed to access working directory.")
|
||||
return []
|
||||
|
||||
directory.list_dir_begin()
|
||||
var file_name = directory.get_next()
|
||||
while file_name != "":
|
||||
if directory.current_is_dir() and not file_name.begins_with("."):
|
||||
save_folders.append(file_name)
|
||||
file_name = directory.get_next()
|
||||
|
||||
return save_folders
|
||||
|
||||
# this adds a new save folder with the given name
|
||||
static func add_save_folder(save_name: String) -> void:
|
||||
var directory := Directory.new()
|
||||
if directory.open(WORKING_DIR) != OK:
|
||||
print("[D] Error: Failed to access working directory.")
|
||||
return
|
||||
directory.make_dir(save_name)
|
||||
|
||||
var file := File.new()
|
||||
if file.open(WORKING_DIR+"/"+save_name+"/definitions.json", File.WRITE) == OK:
|
||||
file.store_string('')
|
||||
file.close()
|
||||
if file.open(WORKING_DIR+"/"+save_name+"/state.json", File.WRITE) == OK:
|
||||
file.store_string('')
|
||||
file.close()
|
||||
|
||||
# this removes the given folder
|
||||
static func remove_save_folder(save_name: String) -> void:
|
||||
var directory := Directory.new()
|
||||
if directory.open(WORKING_DIR+"/"+save_name) != OK:
|
||||
print("[D] Error: Failed to access save folder '"+save_name+"'.")
|
||||
return
|
||||
|
||||
directory.list_dir_begin()
|
||||
var file_name = directory.get_next()
|
||||
while file_name != "":
|
||||
directory.remove(file_name)
|
||||
file_name = directory.get_next()
|
||||
directory.remove(WORKING_DIR+"/"+save_name)
|
||||
|
||||
# reset the definitions and state of the given save folder (or default)
|
||||
static func reset_save(save_name: String = '') -> void:
|
||||
save_state_info(save_name, {})
|
||||
save_definitions(save_name, get_default_definitions())
|
||||
|
||||
# saves the state_info into the state.json file in the save folder "save_name"
|
||||
static func save_state_info(save_name: String, state_info: Dictionary) -> void:
|
||||
if save_name == '':
|
||||
set_json(get_config_files_paths()['STATE_DEFAULT_SAVE'], state_info)
|
||||
return
|
||||
|
||||
if not save_name in get_saves_folders():
|
||||
add_save_folder(save_name)
|
||||
|
||||
set_json(WORKING_DIR+"/"+save_name+"/state.json", state_info)
|
||||
|
||||
# return the state_info from the state.json file in the save folder "save_name"
|
||||
static func get_saved_state_info(save_name: String) -> Dictionary:
|
||||
if save_name == '':
|
||||
return load_json(get_config_files_paths()['STATE_DEFAULT_SAVE'], {})
|
||||
|
||||
if not save_name in get_saves_folders():
|
||||
return {}
|
||||
|
||||
return load_json(WORKING_DIR+"/"+save_name+"/state.json", {})
|
||||
|
||||
# saves the given definitions into the definitions.json file in the save folder "save name"
|
||||
static func save_definitions(save_name: String, definitions_info: Dictionary) -> void:
|
||||
if save_name == "":
|
||||
set_json(get_config_files_paths()['DEFINITIONS_DEFAULT_SAVE'], definitions_info)
|
||||
return
|
||||
|
||||
if not save_name in get_saves_folders():
|
||||
add_save_folder(save_name)
|
||||
|
||||
set_json(WORKING_DIR+"/"+save_name+"/definitions.json", definitions_info)
|
||||
|
||||
# return the definition info from the definiiotn.json in the save folder "save name"
|
||||
static func get_saved_definitions(save_name: String = '') -> Dictionary:
|
||||
if save_name == '':
|
||||
return load_json(get_config_files_paths()['DEFINITIONS_DEFAULT_SAVE'], get_default_definitions())
|
||||
|
||||
if not save_name in get_saves_folders():
|
||||
print("[D] Wasn't able to find save '"+save_name+"'. Loaded the default definitions.")
|
||||
return get_default_definitions()
|
||||
|
||||
return load_json(WORKING_DIR+"/"+save_name+"/definitions.json", {})
|
||||
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## FOLDER STRUCTURE
|
||||
## *****************************************************************************
|
||||
# The DialogicEditor uses a fake folder structure
|
||||
# Can only be edited in the editor
|
||||
|
||||
static func get_resource_folder_structure() -> Dictionary:
|
||||
return load_json(get_config_files_paths()['FOLDER_STRUCTURE_FILE'],
|
||||
{"folders":
|
||||
{"Timelines":
|
||||
{
|
||||
"folders":{},
|
||||
"files":[],
|
||||
'metadata':{'color':null, 'folded':false}
|
||||
},
|
||||
"Characters":
|
||||
{
|
||||
"folders":{},
|
||||
"files":[],
|
||||
'metadata':{'color':null, 'folded':false}
|
||||
},
|
||||
"Definitions":
|
||||
{
|
||||
"folders":{},
|
||||
"files":[],
|
||||
'metadata':{'color':null, 'folded':false}
|
||||
},
|
||||
"Themes":
|
||||
{
|
||||
"folders":{},
|
||||
"files":[],
|
||||
'metadata':{'color':null, 'folded':false}
|
||||
},
|
||||
},
|
||||
"files":[]
|
||||
})
|
||||
|
||||
static func save_resource_folder_structure(data):
|
||||
set_json(get_config_files_paths()['FOLDER_STRUCTURE_FILE'], data)
|
127
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicSharp.cs
Normal file
127
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicSharp.cs
Normal file
@ -0,0 +1,127 @@
|
||||
using Godot;
|
||||
using GC = Godot.Collections;
|
||||
using System;
|
||||
|
||||
public static class DialogicSharp
|
||||
{
|
||||
private static Script _dialogic = GD.Load<Script>("res://addons/dialogic/Other/DialogicClass.gd");
|
||||
private const String DEFAULT_DIALOG_RESOURCE = "res://addons/dialogic/Nodes/DialogNode.tscn";
|
||||
|
||||
// Check the documentation of the DialogicClass for more information on how to use these functions!
|
||||
public static Node Start(String timeline = "", String default_timeline = "", bool useCanvasInstead = true)
|
||||
{
|
||||
return Start<Node>(timeline, default_timeline, DEFAULT_DIALOG_RESOURCE, useCanvasInstead);
|
||||
}
|
||||
|
||||
public static T Start<T>(String timeline = "", String default_timeline = "", String dialogScenePath = "", bool useCanvasInstead = true) where T : class
|
||||
{
|
||||
return (T)_dialogic.Call("start", timeline, default_timeline, dialogScenePath, useCanvasInstead);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// SAVING/LOADING
|
||||
// ------------------------------------------------------------------------------------------
|
||||
public static void Load(String slot_name = "")
|
||||
{
|
||||
_dialogic.Call("load", slot_name);
|
||||
}
|
||||
|
||||
public static void Save(String slot_name = "")
|
||||
{
|
||||
_dialogic.Call("save", slot_name);
|
||||
}
|
||||
|
||||
public static GC.Array GetSlotNames()
|
||||
{
|
||||
return (GC.Array)_dialogic.Call("get_slot_names");
|
||||
}
|
||||
|
||||
public static void EraseSlot(String slot_name)
|
||||
{
|
||||
_dialogic.Call("erase_slot", slot_name);
|
||||
}
|
||||
|
||||
public static bool HasCurrentDialogNode()
|
||||
{
|
||||
return (bool)_dialogic.Call("has_current_dialog_node");
|
||||
}
|
||||
|
||||
public static void ResetSaves()
|
||||
{
|
||||
_dialogic.Call("reset_saves");
|
||||
}
|
||||
|
||||
public static String GetCurrentSlot()
|
||||
{
|
||||
return (String)_dialogic.Call("get_current_slot");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// IMPORT/EXPORT
|
||||
// ------------------------------------------------------------------------------------------
|
||||
public static GC.Dictionary Export()
|
||||
{
|
||||
return (GC.Dictionary)_dialogic.Call("export");
|
||||
}
|
||||
|
||||
public static void Import(GC.Dictionary data)
|
||||
{
|
||||
_dialogic.Call("import", data);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// DEFINITIONS
|
||||
// ------------------------------------------------------------------------------------------
|
||||
public static String GetVariable(String name)
|
||||
{
|
||||
return (String)_dialogic.Call("get_variable", name);
|
||||
}
|
||||
|
||||
public static void SetVariable(String name, String value)
|
||||
{
|
||||
_dialogic.Call("set_variable", name, value);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// OTHER STUFF
|
||||
// ------------------------------------------------------------------------------------------
|
||||
public static String CurrentTimeline
|
||||
{
|
||||
get
|
||||
{
|
||||
return (String)_dialogic.Call("get_current_timeline");
|
||||
}
|
||||
set
|
||||
{
|
||||
_dialogic.Call("set_current_timeline", value);
|
||||
}
|
||||
}
|
||||
|
||||
public static GC.Dictionary Definitions
|
||||
{
|
||||
get
|
||||
{
|
||||
return (GC.Dictionary)_dialogic.Call("get_definitions");
|
||||
}
|
||||
}
|
||||
|
||||
public static GC.Dictionary DefaultDefinitions
|
||||
{
|
||||
get
|
||||
{
|
||||
return (GC.Dictionary)_dialogic.Call("get_default_definitions");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Autosave
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)_dialogic.Call("get_autosave");
|
||||
}
|
||||
set
|
||||
{
|
||||
_dialogic.Call("set_autosave", value);
|
||||
}
|
||||
}
|
||||
}
|
628
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicUtil.gd
Normal file
628
Phase2/Godot_Toolset/Luke/addons/dialogic/Other/DialogicUtil.gd
Normal file
@ -0,0 +1,628 @@
|
||||
tool
|
||||
class_name DialogicUtil
|
||||
|
||||
## This class is used by the DialogicEditor
|
||||
## For example by the Editors (Timeline, Character, Theme), the MasterTree and the EventParts
|
||||
|
||||
static func list_to_dict(list):
|
||||
var dict := {}
|
||||
for val in list:
|
||||
dict[val["file"]] = val
|
||||
return dict
|
||||
|
||||
## *****************************************************************************
|
||||
## CHARACTERS
|
||||
## *****************************************************************************
|
||||
|
||||
static func get_character_list() -> Array:
|
||||
var characters: Array = []
|
||||
for file in DialogicResources.listdir(DialogicResources.get_path('CHAR_DIR')):
|
||||
if '.json' in file:
|
||||
var data: Dictionary = DialogicResources.get_character_json(file)
|
||||
|
||||
characters.append({
|
||||
'name': data.get('name', data['id']),
|
||||
'color': Color(data.get('color', "#ffffff")),
|
||||
'file': file,
|
||||
'portraits': data.get('portraits', []),
|
||||
'display_name': data.get('display_name', ''),
|
||||
'nickname': data.get('nickname', ''),
|
||||
'data': data # This should be the only thing passed... not sure what I was thinking
|
||||
})
|
||||
return characters
|
||||
|
||||
|
||||
|
||||
static func get_characters_dict():
|
||||
return list_to_dict(get_character_list())
|
||||
|
||||
|
||||
static func get_sorted_character_list():
|
||||
var array = get_character_list()
|
||||
array.sort_custom(DialgicSorter, 'sort_resources')
|
||||
return array
|
||||
|
||||
|
||||
# helper that allows to get a character by file
|
||||
static func get_character(character_id):
|
||||
var characters = get_character_list()
|
||||
for c in characters:
|
||||
if c['file'] == character_id:
|
||||
return c
|
||||
return {}
|
||||
|
||||
## *****************************************************************************
|
||||
## TIMELINES
|
||||
## *****************************************************************************
|
||||
|
||||
|
||||
static func get_timeline_list() -> Array:
|
||||
var timelines: Array = []
|
||||
for file in DialogicResources.listdir(DialogicResources.get_path('TIMELINE_DIR')):
|
||||
if '.json' in file: # TODO check for real .json because if .json is in the middle of the sentence it still thinks it is a timeline
|
||||
var data = DialogicResources.get_timeline_json(file)
|
||||
if data.has('error') == false:
|
||||
if data.has('metadata'):
|
||||
var metadata = data['metadata']
|
||||
var color = Color("#ffffff")
|
||||
if metadata.has('name'):
|
||||
timelines.append({'name':metadata['name'], 'color': color, 'file': file })
|
||||
else:
|
||||
timelines.append({'name':file.split('.')[0], 'color': color, 'file': file })
|
||||
return timelines
|
||||
|
||||
# returns a dictionary with file_names as keys and metadata as values
|
||||
static func get_timeline_dict() -> Dictionary:
|
||||
return list_to_dict(get_timeline_list())
|
||||
|
||||
|
||||
static func get_sorted_timeline_list():
|
||||
var array = get_timeline_list()
|
||||
array.sort_custom(DialgicSorter, 'sort_resources')
|
||||
return array
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## THEMES
|
||||
## *****************************************************************************
|
||||
|
||||
static func get_theme_list() -> Array:
|
||||
var themes: Array = []
|
||||
for file in DialogicResources.listdir(DialogicResources.get_path('THEME_DIR')):
|
||||
if '.cfg' in file:
|
||||
var config = DialogicResources.get_theme_config(file)
|
||||
themes.append({
|
||||
'file': file,
|
||||
'name': config.get_value('settings','name', file),
|
||||
'config': config
|
||||
})
|
||||
return themes
|
||||
|
||||
# returns a dictionary with file_names as keys and metadata as values
|
||||
static func get_theme_dict() -> Dictionary:
|
||||
return list_to_dict(get_theme_list())
|
||||
|
||||
|
||||
static func get_sorted_theme_list():
|
||||
var array = get_theme_list()
|
||||
array.sort_custom(DialgicSorter, 'sort_resources')
|
||||
return array
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## DEFINITIONS
|
||||
## *****************************************************************************
|
||||
|
||||
static func get_default_definitions_list() -> Array:
|
||||
return DialogicDefinitionsUtil.definitions_json_to_array(DialogicResources.get_default_definitions())
|
||||
|
||||
|
||||
static func get_default_definitions_dict():
|
||||
var dict = {}
|
||||
for val in get_default_definitions_list():
|
||||
dict[val['id']] = val
|
||||
return dict
|
||||
|
||||
|
||||
static func get_sorted_default_definitions_list():
|
||||
var array = get_default_definitions_list()
|
||||
array.sort_custom(DialgicSorter, 'sort_resources')
|
||||
return array
|
||||
|
||||
# returns the result of the given dialogic comparison
|
||||
static func compare_definitions(def_value: String, event_value: String, condition: String):
|
||||
var definitions
|
||||
if not Engine.is_editor_hint():
|
||||
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)
|
||||
else:
|
||||
definitions = DialogicResources.get_default_definitions()
|
||||
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)
|
||||
if condition == '':
|
||||
condition = '==' # The default condition is Equal to
|
||||
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
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## RESOURCE FOLDER MANAGEMENT
|
||||
## *****************************************************************************
|
||||
# The MasterTree uses a "fake" folder structure
|
||||
|
||||
## PATH FUNCTIONS
|
||||
# removes the last thing from a path
|
||||
static func get_parent_path(path: String):
|
||||
return path.replace("/"+path.split("/")[-1], "")
|
||||
|
||||
|
||||
## GETTERS
|
||||
# returns the full resource structure
|
||||
static func get_full_resource_folder_structure():
|
||||
return DialogicResources.get_resource_folder_structure()
|
||||
|
||||
static func get_timelines_folder_structure():
|
||||
return get_folder_at_path("Timelines")
|
||||
|
||||
static func get_characters_folder_structure():
|
||||
return get_folder_at_path("Characters")
|
||||
|
||||
static func get_definitions_folder_structure():
|
||||
return get_folder_at_path("Definitions")
|
||||
|
||||
static func get_theme_folder_structure():
|
||||
return get_folder_at_path("Themes")
|
||||
|
||||
# this gets the content of the folder at a path
|
||||
# a path consists of the foldernames divided by '/'
|
||||
static func get_folder_at_path(path):
|
||||
var folder_data = get_full_resource_folder_structure()
|
||||
|
||||
for folder in path.split("/"):
|
||||
if folder:
|
||||
folder_data = folder_data['folders'][folder]
|
||||
|
||||
if folder_data == null:
|
||||
folder_data = {"folders":{}, "files":[]}
|
||||
return folder_data
|
||||
|
||||
|
||||
## SETTERS
|
||||
static func set_folder_content_recursive(path_array: Array, orig_data: Dictionary, new_data: Dictionary) -> Dictionary:
|
||||
if len(path_array) == 1:
|
||||
if path_array[0] in orig_data['folders'].keys():
|
||||
if new_data.empty():
|
||||
orig_data['folders'].erase(path_array[0])
|
||||
else:
|
||||
orig_data["folders"][path_array[0]] = new_data
|
||||
else:
|
||||
var current_folder = path_array.pop_front()
|
||||
orig_data["folders"][current_folder] = set_folder_content_recursive(path_array, orig_data["folders"][current_folder], new_data)
|
||||
return orig_data
|
||||
|
||||
static func set_folder_at_path(path: String, data:Dictionary):
|
||||
var orig_structure = get_full_resource_folder_structure()
|
||||
var new_data = set_folder_content_recursive(path.split("/"), orig_structure, data)
|
||||
DialogicResources.save_resource_folder_structure(new_data)
|
||||
return OK
|
||||
|
||||
## FOLDER METADATA
|
||||
static func set_folder_meta(folder_path: String, key:String, value):
|
||||
var data = get_folder_at_path(folder_path)
|
||||
data['metadata'][key] = value
|
||||
set_folder_at_path(folder_path, data)
|
||||
|
||||
static func get_folder_meta(folder_path: String, key:String):
|
||||
return get_folder_at_path(folder_path)['metadata'][key]
|
||||
|
||||
|
||||
## FOLDER FUNCTIONS
|
||||
static func add_folder(path:String, folder_name:String):
|
||||
# check if the name is allowed
|
||||
if folder_name in get_folder_at_path(path)['folders'].keys():
|
||||
print("[D] A folder with the name '"+folder_name+"' already exists in the target folder '"+path+"'.")
|
||||
return ERR_ALREADY_EXISTS
|
||||
|
||||
var folder_data = get_folder_at_path(path)
|
||||
folder_data['folders'][folder_name] = {"folders":{}, "files":[], 'metadata':{'color':null, 'folded':false}}
|
||||
set_folder_at_path(path, folder_data)
|
||||
|
||||
return OK
|
||||
|
||||
static func remove_folder(folder_path:String, delete_files:bool = true):
|
||||
#print("[D] Removing 'Folder' "+folder_path)
|
||||
for folder in get_folder_at_path(folder_path)['folders']:
|
||||
remove_folder(folder_path+"/"+folder, delete_files)
|
||||
|
||||
if delete_files:
|
||||
for file in get_folder_at_path(folder_path)['files']:
|
||||
#print("[D] Removing file ", file)
|
||||
match folder_path.split("/")[0]:
|
||||
'Timelines':
|
||||
DialogicResources.delete_timeline(file)
|
||||
'Characters':
|
||||
DialogicResources.delete_character(file)
|
||||
'Definitions':
|
||||
DialogicResources.delete_default_definition(file)
|
||||
'Themes':
|
||||
DialogicResources.delete_theme(file)
|
||||
set_folder_at_path(folder_path, {})
|
||||
|
||||
static func rename_folder(path:String, new_folder_name:String):
|
||||
# check if the name is allowed
|
||||
if new_folder_name in get_folder_at_path(get_parent_path(path))['folders'].keys():
|
||||
print("[D] A folder with the name '"+new_folder_name+"' already exists in the target folder '"+get_parent_path(path)+"'.")
|
||||
return ERR_ALREADY_EXISTS
|
||||
elif new_folder_name.empty():
|
||||
return ERR_PRINTER_ON_FIRE
|
||||
|
||||
|
||||
# save the content
|
||||
var folder_content = get_folder_at_path(path)
|
||||
|
||||
# remove the old folder BUT NOT THE FILES !!!!!
|
||||
remove_folder(path, false)
|
||||
|
||||
# add the new folder
|
||||
add_folder(get_parent_path(path), new_folder_name)
|
||||
var new_path = get_parent_path(path)+ "/"+new_folder_name
|
||||
set_folder_at_path(new_path, folder_content)
|
||||
|
||||
return OK
|
||||
|
||||
static func move_folder_to_folder(orig_path, target_folder):
|
||||
# check if the name is allowed
|
||||
if orig_path.split("/")[-1] in get_folder_at_path(target_folder)['folders'].keys():
|
||||
print("[D] A folder with the name '"+orig_path.split("/")[-1]+"' already exists in the target folder '"+target_folder+"'.")
|
||||
return ERR_ALREADY_EXISTS
|
||||
|
||||
# save the content
|
||||
var folder_content = get_folder_at_path(orig_path)
|
||||
|
||||
# remove the old folder BUT DON'T DELETE THE FILES!!!!!!!!!!!
|
||||
# took me ages to find this when I forgot it..
|
||||
remove_folder(orig_path, false)
|
||||
|
||||
# add the new folder
|
||||
var folder_name = orig_path.split("/")[-1]
|
||||
add_folder(target_folder, folder_name)
|
||||
var new_path = target_folder+ "/"+folder_name
|
||||
set_folder_at_path(new_path, folder_content)
|
||||
|
||||
return OK
|
||||
|
||||
## FILE FUNCTIONS
|
||||
static func move_file_to_folder(file_name, orig_folder, target_folder):
|
||||
remove_file_from_folder(orig_folder, file_name)
|
||||
add_file_to_folder(target_folder, file_name)
|
||||
|
||||
static func add_file_to_folder(folder_path, file_name):
|
||||
var folder_data = get_folder_at_path(folder_path)
|
||||
folder_data["files"].append(file_name)
|
||||
set_folder_at_path(folder_path, folder_data)
|
||||
|
||||
static func remove_file_from_folder(folder_path, file_name):
|
||||
var folder_data = get_folder_at_path(folder_path)
|
||||
folder_data["files"].erase(file_name)
|
||||
set_folder_at_path(folder_path, folder_data)
|
||||
|
||||
|
||||
## STRUCTURE UPDATES
|
||||
#should be called when files got deleted and on program start
|
||||
static func update_resource_folder_structure():
|
||||
var character_files = DialogicResources.listdir(DialogicResources.get_path('CHAR_DIR'))
|
||||
var timeline_files = DialogicResources.listdir(DialogicResources.get_path('TIMELINE_DIR'))
|
||||
var theme_files = DialogicResources.listdir(DialogicResources.get_path('THEME_DIR'))
|
||||
var definition_files = get_default_definitions_dict().keys()
|
||||
|
||||
var folder_structure = DialogicResources.get_resource_folder_structure()
|
||||
|
||||
folder_structure['folders']['Timelines'] = check_folders_section(folder_structure['folders']['Timelines'], timeline_files)
|
||||
folder_structure['folders']['Characters'] = check_folders_section(folder_structure['folders']['Characters'], character_files)
|
||||
folder_structure['folders']['Themes'] = check_folders_section(folder_structure['folders']['Themes'], theme_files)
|
||||
folder_structure['folders']['Definitions'] = check_folders_section(folder_structure['folders']['Definitions'], definition_files)
|
||||
|
||||
DialogicResources.save_resource_folder_structure(folder_structure)
|
||||
|
||||
# calls the check_folders_recursive
|
||||
static func check_folders_section(section_structure: Dictionary, section_files:Array):
|
||||
var result = check_folders_recursive(section_structure, section_files)
|
||||
section_structure = result[0]
|
||||
section_structure['files'] += result[1]
|
||||
return section_structure
|
||||
|
||||
static func check_folders_recursive(folder_data: Dictionary, file_names:Array):
|
||||
if not folder_data.has('metadata'):
|
||||
folder_data['metadata'] = {'color':null, 'folded':false}
|
||||
for folder in folder_data['folders'].keys():
|
||||
var result = check_folders_recursive(folder_data["folders"][folder], file_names)
|
||||
folder_data['folders'][folder] = result[0]
|
||||
file_names = result[1]
|
||||
for file in folder_data['files']:
|
||||
if not file in file_names:
|
||||
folder_data["files"].erase(file)
|
||||
#print("[D] The file ", file, " was deleted!")
|
||||
else:
|
||||
file_names.erase(file)
|
||||
return [folder_data, file_names]
|
||||
|
||||
|
||||
static func beautify_filename(animation_name: String) -> String:
|
||||
if animation_name == '[Default]' or animation_name == '[No Animation]':
|
||||
return animation_name
|
||||
var a_string = animation_name.get_file().trim_suffix('.gd')
|
||||
if '-' in a_string:
|
||||
a_string = a_string.split('-')[1].capitalize()
|
||||
else:
|
||||
a_string = a_string.capitalize()
|
||||
return a_string
|
||||
|
||||
## *****************************************************************************
|
||||
## USEFUL FUNCTIONS
|
||||
## *****************************************************************************
|
||||
|
||||
static func generate_random_id() -> String:
|
||||
return str(OS.get_unix_time()) + '-' + str(100 + randi()%899+1)
|
||||
|
||||
|
||||
static func compare_dicts(dict_1: Dictionary, dict_2: Dictionary) -> bool:
|
||||
# I tried using the .hash() function but it was returning different numbers
|
||||
# even when the dictionary was exactly the same.
|
||||
if str(dict_1) != "Null" and str(dict_2) != "Null":
|
||||
if str(dict_1) == str(dict_2):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func path_fixer_load(path):
|
||||
# This function was added because some of the default assets shipped with
|
||||
# Dialogic 1.0 were moved for version 1.1. If by any chance they still
|
||||
# Use those resources, we redirect the paths from the old place to the new
|
||||
# ones. This can be safely removed and replace all instances of
|
||||
# DialogicUtil.path_fixer_load(x) with just load(x) on version 2.0
|
||||
# since we will break compatibility.
|
||||
|
||||
match path:
|
||||
'res://addons/dialogic/Fonts/DefaultFont.tres':
|
||||
return load("res://addons/dialogic/Example Assets/Fonts/DefaultFont.tres")
|
||||
'res://addons/dialogic/Fonts/GlossaryFont.tres':
|
||||
return load('res://addons/dialogic/Example Assets/Fonts/GlossaryFont.tres')
|
||||
'res://addons/dialogic/Images/background/background-1.png':
|
||||
return load('res://addons/dialogic/Example Assets/backgrounds/background-1.png')
|
||||
'res://addons/dialogic/Images/background/background-2.png':
|
||||
return load('res://addons/dialogic/Example Assets/backgrounds/background-2.png')
|
||||
'res://addons/dialogic/Images/next-indicator.png':
|
||||
return load('res://addons/dialogic/Example Assets/next-indicator/next-indicator.png')
|
||||
|
||||
return load(path)
|
||||
|
||||
# This function contains necessary updates.
|
||||
# This should be deleted in 2.0
|
||||
static func resource_fixer():
|
||||
var update_index = DialogicResources.get_settings_config().get_value("updates", "updatenumber", 0)
|
||||
|
||||
if update_index < 1:
|
||||
print("[D] Update NR. "+str(update_index)+" | Adds event ids. Don't worry about this.")
|
||||
for timeline_info in get_timeline_list():
|
||||
var timeline = DialogicResources.get_timeline_json(timeline_info['file'])
|
||||
|
||||
var events = timeline["events"]
|
||||
for i in events:
|
||||
if not i.has("event_id"):
|
||||
match i:
|
||||
# MAIN EVENTS
|
||||
# Text event
|
||||
{'text', 'character', 'portrait'}:
|
||||
i['event_id'] = 'dialogic_001'
|
||||
# Join event
|
||||
{'character', 'action', 'position', 'portrait',..}:
|
||||
i['event_id'] = 'dialogic_002'
|
||||
# Character Leave event
|
||||
{'character', 'action'}:
|
||||
i['event_id'] = 'dialogic_003'
|
||||
|
||||
# LOGIC EVENTS
|
||||
# Question event
|
||||
{'question', 'options', ..}:
|
||||
i['event_id'] = 'dialogic_010'
|
||||
# Choice event
|
||||
{'choice', ..}:
|
||||
i['event_id'] = 'dialogic_011'
|
||||
# Condition event
|
||||
{'condition', 'definition', 'value'}:
|
||||
i['event_id'] = 'dialogic_012'
|
||||
# End Branch event
|
||||
{'endbranch'}:
|
||||
i['event_id'] = 'dialogic_013'
|
||||
# Set Value event
|
||||
{'set_value', 'definition', ..}:
|
||||
i['event_id'] = 'dialogic_014'
|
||||
|
||||
# TIMELINE EVENTS
|
||||
# Change Timeline event
|
||||
{'change_timeline'}:
|
||||
i['event_id'] = 'dialogic_020'
|
||||
# Change Backround event
|
||||
{'background'}:
|
||||
i['event_id'] = 'dialogic_021'
|
||||
# Close Dialog event
|
||||
{'close_dialog', ..}:
|
||||
i['event_id'] = 'dialogic_022'
|
||||
# Wait seconds event
|
||||
{'wait_seconds'}:
|
||||
i['event_id'] = 'dialogic_023'
|
||||
# Set Theme event
|
||||
{'set_theme'}:
|
||||
i['event_id'] = 'dialogic_024'
|
||||
|
||||
# AUDIO EVENTS
|
||||
# Audio event
|
||||
{'audio', 'file', ..}:
|
||||
i['event_id'] = 'dialogic_030'
|
||||
# Background Music event
|
||||
{'background-music', 'file', ..}:
|
||||
i['event_id'] = 'dialogic_031'
|
||||
|
||||
# GODOT EVENTS
|
||||
# Emit signal event
|
||||
{'emit_signal'}:
|
||||
i['event_id'] = 'dialogic_040'
|
||||
# Change Scene event
|
||||
{'change_scene'}:
|
||||
i['event_id'] = 'dialogic_041'
|
||||
# Call Node event
|
||||
{'call_node'}:
|
||||
i['event_id'] = 'dialogic_042'
|
||||
timeline['events'] = events
|
||||
DialogicResources.set_timeline(timeline)
|
||||
if update_index < 2:
|
||||
# Updates the text alignment to be saved as int like all anchors
|
||||
print("[D] Update NR. "+str(update_index)+" | Changes how some theme values are saved. No need to worry about this.")
|
||||
for theme_info in get_theme_list():
|
||||
var theme = DialogicResources.get_theme_config(theme_info['file'])
|
||||
|
||||
match theme.get_value('text', 'alignment', 'Left'):
|
||||
'Left':
|
||||
DialogicResources.set_theme_value(theme_info['file'], 'text', 'alignment', 0)
|
||||
'Center':
|
||||
DialogicResources.set_theme_value(theme_info['file'], 'text', 'alignment', 1)
|
||||
'Right':
|
||||
DialogicResources.set_theme_value(theme_info['file'], 'text', 'alignment', 2)
|
||||
|
||||
if update_index < 3:
|
||||
# Character Join and Character Leave have been unified to a new Character event
|
||||
print("[D] Update NR. "+str(update_index)+" | Removes Character Join and Character Leave events in favor of the new 'Character' event. No need to worry about this.")
|
||||
for timeline_info in get_timeline_list():
|
||||
var timeline = DialogicResources.get_timeline_json(timeline_info['file'])
|
||||
var events = timeline["events"]
|
||||
for i in range(len(events)):
|
||||
if events[i]['event_id'] == 'dialogic_002':
|
||||
var new_event = {
|
||||
'event_id':'dialogic_002',
|
||||
'type':0,
|
||||
'character':events[i].get('character', ''),
|
||||
'portrait':events[i].get('portrait','Default'),
|
||||
'position':events[i].get('position'),
|
||||
'animation':'[Default]',
|
||||
'animation_length':0.5,
|
||||
'mirror_portrait':events[i].get('mirror', false),
|
||||
'z_index': events[i].get('z_index', 0),
|
||||
}
|
||||
if new_event['portrait'].empty(): new_event['portrait'] = 'Default'
|
||||
events[i] = new_event
|
||||
elif events[i]['event_id'] == 'dialogic_003':
|
||||
var new_event = {
|
||||
'event_id':'dialogic_002',
|
||||
'type':1,
|
||||
'character':events[i].get('character', ''),
|
||||
'animation':'[Default]',
|
||||
'animation_length':0.5,
|
||||
'mirror_portrait':events[i].get('mirror', false),
|
||||
'z_index':events[i].get('z_index', 0),
|
||||
}
|
||||
events[i] = new_event
|
||||
timeline['events'] = events
|
||||
DialogicResources.set_timeline(timeline)
|
||||
|
||||
DialogicResources.set_settings_value("updates", "updatenumber", 3)
|
||||
|
||||
if !ProjectSettings.has_setting('input/dialogic_default_action'):
|
||||
print("[D] Added the 'dialogic_default_action' to the InputMap. This is the default if you didn't select a different one in the dialogic settings. You will have to force the InputMap editor to update before you can see the action (reload project or add a new input action).")
|
||||
var input_enter = InputEventKey.new()
|
||||
input_enter.scancode = KEY_ENTER
|
||||
var input_left_click = InputEventMouseButton.new()
|
||||
input_left_click.button_index = BUTTON_LEFT
|
||||
input_left_click.pressed = true
|
||||
var input_space = InputEventKey.new()
|
||||
input_space.scancode = KEY_SPACE
|
||||
var input_x = InputEventKey.new()
|
||||
input_x.scancode = KEY_X
|
||||
var input_controller = InputEventJoypadButton.new()
|
||||
input_controller.button_index = JOY_BUTTON_0
|
||||
|
||||
ProjectSettings.set_setting('input/dialogic_default_action', {'deadzone':0.5, 'events':[input_enter, input_left_click, input_space, input_x, input_controller]})
|
||||
ProjectSettings.save()
|
||||
if DialogicResources.get_settings_value('input', 'default_action_key', '[Default]') == '[Default]':
|
||||
DialogicResources.set_settings_value('input', 'default_action_key', 'dialogic_default_action')
|
||||
|
||||
static func get_editor_scale(ref) -> float:
|
||||
# There hasn't been a proper way of reliably getting the editor scale
|
||||
# so this function aims at fixing that by identifying what the scale is and
|
||||
# returning a value to use as a multiplier for manual UI tweaks
|
||||
|
||||
# The way of getting the scale could change, but this is the most reliable
|
||||
# solution I could find that works in many different computer/monitors.
|
||||
var _scale = ref.get_constant("inspector_margin", "Editor")
|
||||
_scale = _scale * 0.125
|
||||
|
||||
return _scale
|
||||
|
||||
|
||||
static func list_dir(path: String) -> Array:
|
||||
var files = []
|
||||
var dir = Directory.new()
|
||||
dir.open(path)
|
||||
dir.list_dir_begin(true)
|
||||
|
||||
var file = dir.get_next()
|
||||
while file != '':
|
||||
files += [file]
|
||||
file = dir.get_next()
|
||||
return files
|
||||
|
||||
|
||||
## *****************************************************************************
|
||||
## DIALOGIC_SORTER CLASS
|
||||
## *****************************************************************************
|
||||
|
||||
# This class is only used by this script to sort the resource lists
|
||||
class DialgicSorter:
|
||||
|
||||
static func key_available(key, a: Dictionary) -> bool:
|
||||
return key in a.keys() and not a[key].empty()
|
||||
|
||||
static func get_compare_value(a: Dictionary) -> String:
|
||||
if key_available('display_name', a):
|
||||
return a['display_name']
|
||||
|
||||
if key_available('name', a):
|
||||
return a['name']
|
||||
|
||||
if key_available('id', a):
|
||||
return a['id']
|
||||
|
||||
if 'metadata' in a.keys():
|
||||
var a_metadata = a['metadata']
|
||||
if key_available('name', a_metadata):
|
||||
return a_metadata['name']
|
||||
if key_available('file', a_metadata):
|
||||
return a_metadata['file']
|
||||
return ''
|
||||
|
||||
static func sort_resources(a: Dictionary, b: Dictionary):
|
||||
return get_compare_value(a).to_lower() < get_compare_value(b).to_lower()
|
@ -0,0 +1,28 @@
|
||||
extends EditorExportPlugin
|
||||
|
||||
func _export_begin(features: PoolStringArray, is_debug: bool, path: String, flags: int) -> void:
|
||||
# This fixes the android export 'multiple file in zip' bug
|
||||
for feature in features:
|
||||
if feature == "Android":
|
||||
return
|
||||
|
||||
var file = File.new()
|
||||
var directory = Directory.new()
|
||||
|
||||
var paths = DialogicResources.get_working_directories()
|
||||
|
||||
# Loops through every file in the working directories
|
||||
# and adds it as a custom export file
|
||||
for dir in paths:
|
||||
if directory.open(paths[dir]) == OK:
|
||||
directory.list_dir_begin()
|
||||
var file_name = directory.get_next()
|
||||
while file_name != "":
|
||||
if not directory.current_is_dir():
|
||||
var file_lower = file_name.to_lower()
|
||||
if '.json' in file_lower or '.cfg' in file_lower:
|
||||
var file_path = paths[dir] + "/" + file_name
|
||||
if file.open(file_path, File.READ) == OK:
|
||||
add_file(file_path, file.get_buffer(file.get_len()), false)
|
||||
file.close()
|
||||
file_name = directory.get_next()
|
@ -0,0 +1,36 @@
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
var TimelinePicker = preload("res://addons/dialogic/Other/timeline_picker.gd")
|
||||
var dialogic_editor_plugin = null
|
||||
var dialogic_editor_view = null
|
||||
|
||||
|
||||
func can_handle(object):
|
||||
# We support all objects in this example.
|
||||
return true
|
||||
|
||||
|
||||
func parse_property(object, type, path, hint, hint_text, usage):
|
||||
# We check for this hint text. It would look like: export(String, "TimelineDropdown")
|
||||
if hint_text == "TimelineDropdown":
|
||||
# We handle properties of type string.
|
||||
if type == TYPE_STRING:
|
||||
# Create an instance of the custom property editor and register
|
||||
# it to a specific property path.
|
||||
var picker = TimelinePicker.new()
|
||||
picker.editor_inspector_plugin = self
|
||||
add_property_editor(path, picker)
|
||||
# Inform the editor to remove the default property editor for
|
||||
# this property type.
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func switch_to_dialogic_timeline(timeline: String):
|
||||
if (dialogic_editor_plugin != null):
|
||||
var master_tree = dialogic_editor_view.get_node('MainPanel/MasterTreeContainer/MasterTree')
|
||||
dialogic_editor_plugin.get_editor_interface().set_main_screen_editor("Dialogic")
|
||||
|
||||
master_tree.timeline_editor.batches.clear()
|
||||
master_tree.timeline_editor.load_timeline(timeline)
|
||||
master_tree.show_timeline_editor()
|
@ -0,0 +1,109 @@
|
||||
tool
|
||||
extends EditorProperty
|
||||
|
||||
# The main controls for editing the property.
|
||||
var timelines_dropdown = MenuButton.new()
|
||||
var container = HBoxContainer.new()
|
||||
var edit_button = Button.new()
|
||||
|
||||
# reference to the inspector plugin
|
||||
var editor_inspector_plugin = null
|
||||
|
||||
# An internal value of the property.
|
||||
var current_value = ''
|
||||
# A guard against internal changes when the property is updated.
|
||||
var updating = false
|
||||
|
||||
# @Override
|
||||
func get_tooltip_text():
|
||||
return "Click to select a Dialogic timeline.\nPress the tool button to directly switch to the editor"
|
||||
|
||||
|
||||
func _ready():
|
||||
edit_button.icon = get_icon("Tools", "EditorIcons")
|
||||
|
||||
|
||||
func _init():
|
||||
# setup controls
|
||||
timelines_dropdown.rect_min_size.x = 80
|
||||
timelines_dropdown.set_h_size_flags(SIZE_EXPAND_FILL)
|
||||
timelines_dropdown.clip_text = true
|
||||
container.add_child(timelines_dropdown)
|
||||
container.add_child(edit_button)
|
||||
edit_button.flat = true
|
||||
edit_button.hint_tooltip = "Edit Timeline"
|
||||
edit_button.disabled = true
|
||||
|
||||
# Add the container as a direct child
|
||||
add_child(container)
|
||||
|
||||
# Make sure the control is able to retain the focus.
|
||||
add_focusable(timelines_dropdown)
|
||||
|
||||
# Setup the initial state and connect to the signal to track changes.
|
||||
timelines_dropdown.connect("about_to_show", self, "_about_to_show_menu")
|
||||
timelines_dropdown.get_popup().connect("index_pressed", self, '_on_timeline_selected')
|
||||
edit_button.connect("pressed", self, "_on_editTimelineButton_pressed")
|
||||
|
||||
|
||||
func _about_to_show_menu():
|
||||
# Ignore the signal if the property is currently being updated.
|
||||
if (updating):
|
||||
return
|
||||
|
||||
# Adding timelines
|
||||
timelines_dropdown.get_popup().clear()
|
||||
var index = 0
|
||||
for c in DialogicUtil.get_sorted_timeline_list():
|
||||
timelines_dropdown.get_popup().add_item(c['name'])
|
||||
timelines_dropdown.get_popup().set_item_metadata(index, {'file': c['file'], 'color': c['color']})
|
||||
index += 1
|
||||
|
||||
|
||||
func _on_timeline_selected(index):
|
||||
var text = timelines_dropdown.get_popup().get_item_text(index)
|
||||
var metadata = timelines_dropdown.get_popup().get_item_metadata(index)
|
||||
current_value = metadata['file']
|
||||
timelines_dropdown.text = text
|
||||
timelines_dropdown.hint_tooltip = text
|
||||
_update_edit_button(current_value)
|
||||
emit_changed(get_edited_property(), current_value)
|
||||
|
||||
|
||||
func _on_editTimelineButton_pressed():
|
||||
if (current_value != '' and editor_inspector_plugin != null):
|
||||
editor_inspector_plugin.switch_to_dialogic_timeline(current_value)
|
||||
|
||||
|
||||
func update_property():
|
||||
# Read the current value from the property.
|
||||
var new_value = get_edited_object()[get_edited_property()]
|
||||
_update_edit_button(new_value)
|
||||
|
||||
if (new_value == current_value):
|
||||
return
|
||||
|
||||
# Update the control with the new value.
|
||||
updating = true
|
||||
current_value = new_value
|
||||
# Checking for the display name
|
||||
timelines_dropdown.text = ''
|
||||
|
||||
if (current_value == ''):
|
||||
timelines_dropdown.hint_tooltip = 'Click to select a timeline'
|
||||
|
||||
for c in DialogicUtil.get_timeline_list():
|
||||
if c['file'] == current_value:
|
||||
timelines_dropdown.text = c['name']
|
||||
timelines_dropdown.hint_tooltip = c['name']
|
||||
|
||||
updating = false
|
||||
|
||||
_update_edit_button(current_value)
|
||||
|
||||
|
||||
func _update_edit_button(value):
|
||||
if (value == ''):
|
||||
edit_button.disabled = true
|
||||
else:
|
||||
edit_button.disabled = false
|
Reference in New Issue
Block a user