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.
469 lines
16 KiB
469 lines
16 KiB
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 |
|
|
|
## ***************************************************************************** |
|
## 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 |
|
|
|
## ***************************************************************************** |
|
## 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 |
|
|
|
# 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] |
|
|
|
|
|
## ***************************************************************************** |
|
## 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) |
|
|
|
DialogicResources.set_settings_value("updates", "updatenumber", 1) |
|
|
|
|
|
## ***************************************************************************** |
|
## 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()
|
|
|