diff --git a/addons/dialogic/Editor/DefinitionEditor/DefinitionEditor.gd b/addons/dialogic/Editor/DefinitionEditor/DefinitionEditor.gd index ff3190a..2de0e31 100644 --- a/addons/dialogic/Editor/DefinitionEditor/DefinitionEditor.gd +++ b/addons/dialogic/Editor/DefinitionEditor/DefinitionEditor.gd @@ -3,7 +3,7 @@ extends ScrollContainer var editor_reference onready var master_tree = get_node('../MasterTree') -var current_section = '' +var current_definition = null onready var nodes = { 'name' : $VBoxContainer/HBoxContainer/VBoxContainer/Name, @@ -22,19 +22,19 @@ func _ready(): nodes['type'].connect('item_selected', self, '_on_type_selected') -func load_definition(section): - current_section = section +func load_definition(id): + current_definition = DialogicResources.get_default_definition_item(id) reset_editor() nodes['name'].editable = true - nodes['name'].text = get_definition('name', 'Unnamed') - var type = get_definition('type', 0) + nodes['name'].text = current_definition['name'] + var type = current_definition['type'] nodes['type'].select(type) if type == 0: - nodes['value'].text = get_definition('value', '') + nodes['value'].text = current_definition['value'] if type == 1: - nodes['extra_title'].text = get_definition('extra_title', '') - nodes['extra_text'].text = get_definition('extra_text', '') - nodes['extra_extra'].text = get_definition('extra_extra', '') + nodes['extra_title'].text = current_definition['title'] + nodes['extra_text'].text = current_definition['text'] + nodes['extra_extra'].text = current_definition['extra'] show_sub_editor(type) @@ -44,7 +44,10 @@ func reset_editor(): nodes['extra_title'].text = '' nodes['extra_text'].text = '' nodes['extra_extra'].text = '' - nodes['type'].select(get_definition('type', 0)) + var type = 0 + if current_definition != null: + type = current_definition['type'] + nodes['type'].select(type) func _on_name_changed(text): @@ -70,23 +73,16 @@ func show_sub_editor(type): nodes['extra_editor'].visible = true -func get_definition(key: String, default): - if current_section != '': - return DialogicResources.get_default_definition_key(current_section, key, default) - else: - return default - - func new_definition(): - var section = DialogicUtil.generate_random_id() - DialogicResources.add_default_definition_variable(section, 'New definition', 0, '') - master_tree.add_definition({'section': section,'name': 'New definition', 'type': 0}, true) + var id = DialogicUtil.generate_random_id() + DialogicResources.set_default_definition_variable(id, 'New definition', '') + master_tree.add_definition({'id': id, 'name': 'New definition', 'type': 0}, true) func save_definition(): - if current_section != '': + if current_definition['id'] != '': var type: int = nodes['type'].selected if type == 0: - DialogicResources.set_default_definition_variable(current_section, nodes['name'].text, nodes['value'].text) + DialogicResources.set_default_definition_variable(current_definition['id'], nodes['name'].text, nodes['value'].text) if type == 1: - DialogicResources.set_default_definition_glossary(current_section, nodes['name'].text, nodes['extra_title'].text, nodes['extra_text'].text, nodes['extra_extra'].text) + DialogicResources.set_default_definition_glossary(current_definition['id'], nodes['name'].text, nodes['extra_title'].text, nodes['extra_text'].text, nodes['extra_extra'].text) diff --git a/addons/dialogic/Editor/EditorView.gd b/addons/dialogic/Editor/EditorView.gd index 08aa949..e89d5ed 100644 --- a/addons/dialogic/Editor/EditorView.gd +++ b/addons/dialogic/Editor/EditorView.gd @@ -67,9 +67,9 @@ func _on_TimelinePopupMenu_id_pressed(id): func _on_RemoveTimelineConfirmation_confirmed(): var dir = Directory.new() - var target = $MainPanel/TimelineEditor.working_timeline_file + var target = $MainPanel/TimelineEditor.timeline_file print('target: ', target) - dir.remove(target) + DialogicResources.delete_timeline(target) $MainPanel/MasterTree.remove_selected() $MainPanel/MasterTree.hide_all_editors(true) @@ -99,7 +99,7 @@ func _on_DefinitionPopupMenu_id_pressed(id): func _on_RemoveDefinitionConfirmation_confirmed(): - var target = $MainPanel/DefinitionEditor.current_section + var target = $MainPanel/DefinitionEditor.current_definition['id'] DialogicResources.delete_default_definition(target) $MainPanel/MasterTree.remove_selected() $MainPanel/MasterTree.hide_all_editors(true) @@ -114,7 +114,7 @@ func _on_RemoveCharacterConfirmation_confirmed(): func _on_RemoveThemeConfirmation_confirmed(): var filename = $MainPanel/MasterTree.get_selected().get_metadata(0)['file'] - DialogicResources.delete_timeline(filename) + DialogicResources.delete_theme(filename) $MainPanel/MasterTree.remove_selected() $MainPanel/MasterTree.hide_all_editors(true) diff --git a/addons/dialogic/Editor/MasterTree/MasterTree.gd b/addons/dialogic/Editor/MasterTree/MasterTree.gd index 509c222..fa6309d 100644 --- a/addons/dialogic/Editor/MasterTree/MasterTree.gd +++ b/addons/dialogic/Editor/MasterTree/MasterTree.gd @@ -69,7 +69,7 @@ func _ready(): add_character(c) # Adding Definitions - for d in DialogicUtil.get_default_definition_list(): + for d in DialogicUtil.get_default_definitions_list(): add_definition(d) # Adding Themes @@ -155,7 +155,7 @@ func _on_item_selected(): character_editor.load_character(metadata['file']) if metadata['editor'] == 'Definition': definition_editor.visible = true - definition_editor.load_definition(metadata['section']) + definition_editor.load_definition(metadata['id']) if metadata['editor'] == 'Theme': theme_editor.load_theme(metadata['file']) theme_editor.visible = true diff --git a/addons/dialogic/Editor/Pieces/Common/DefinitionPicker.gd b/addons/dialogic/Editor/Pieces/Common/DefinitionPicker.gd index b3c9090..e538c1a 100644 --- a/addons/dialogic/Editor/Pieces/Common/DefinitionPicker.gd +++ b/addons/dialogic/Editor/Pieces/Common/DefinitionPicker.gd @@ -12,13 +12,12 @@ func _ready(): func _on_MenuButton_about_to_show(): get_popup().clear() var index = 0 - for d in DialogicUtil.get_default_definition_list(): - if d['type'] == 0: - get_popup().add_item(d['name']) - get_popup().set_item_metadata(index, { - 'section': d['section'], - }) - index += 1 + for d in DialogicResources.get_default_definitions()['variables']: + get_popup().add_item(d['name']) + get_popup().set_item_metadata(index, { + 'id': d['id'], + }) + index += 1 func _on_entry_selected(index): @@ -27,10 +26,10 @@ func _on_entry_selected(index): text = _text -func load_definition(section): - if section != '': - for d in DialogicUtil.get_default_definition_list(): - if d['section'] == section: +func load_definition(id): + if id != '': + for d in DialogicResources.get_default_definitions()['variables']: + if d['id'] == id: text = d['name'] else: text = default_text diff --git a/addons/dialogic/Editor/Pieces/IfCondition.gd b/addons/dialogic/Editor/Pieces/IfCondition.gd index 825d01b..3287366 100644 --- a/addons/dialogic/Editor/Pieces/IfCondition.gd +++ b/addons/dialogic/Editor/Pieces/IfCondition.gd @@ -36,7 +36,7 @@ func load_data(data): func _on_definition_entry_selected(index): var metadata = nodes['definition_picker'].get_popup().get_item_metadata(index) - event_data['definition'] = metadata['section'] + event_data['definition'] = metadata['id'] func _on_condition_entry_selected(index): diff --git a/addons/dialogic/Editor/Pieces/SetValue.gd b/addons/dialogic/Editor/Pieces/SetValue.gd index 5b23676..9e4bd16 100644 --- a/addons/dialogic/Editor/Pieces/SetValue.gd +++ b/addons/dialogic/Editor/Pieces/SetValue.gd @@ -21,7 +21,7 @@ func _ready(): func _on_definition_entry_selected(index): var metadata = nodes['definition_picker'].get_popup().get_item_metadata(index) - event_data['definition'] = metadata['section'] + event_data['definition'] = metadata['id'] func load_data(data): diff --git a/addons/dialogic/Editor/ThemeEditor/ThemeEditor.gd b/addons/dialogic/Editor/ThemeEditor/ThemeEditor.gd index 4dda6bf..7f246ec 100644 --- a/addons/dialogic/Editor/ThemeEditor/ThemeEditor.gd +++ b/addons/dialogic/Editor/ThemeEditor/ThemeEditor.gd @@ -323,4 +323,4 @@ func _on_Alignment_item_selected(index): func _on_Preview_text_changed(): - DialogicUtil.set_theme_value(current_theme, 'text', 'preview', n['text_preview'].text) + DialogicResources.set_theme_value(current_theme, 'text', 'preview', n['text_preview'].text) diff --git a/addons/dialogic/Nodes/dialog_node.gd b/addons/dialogic/Nodes/dialog_node.gd index 701f643..39fa0fc 100644 --- a/addons/dialogic/Nodes/dialog_node.gd +++ b/addons/dialogic/Nodes/dialog_node.gd @@ -10,15 +10,20 @@ var waiting_for_answer: bool = false var waiting_for_input: bool = false var waiting = false var preview = false -var definitions +var definitions = {} var definition_visible = false var settings var current_theme +var current_timeline := '' +## The timeline to load when starting the scene export(String, "TimelineDropdown") var timeline: String +## Should we clear saved data (definitions and timeline progress) on start? export(bool) var reset_saves = true +## Should we show debug information when running? export(bool) var debug_mode = true + signal event_start(type, event) signal event_end(type) signal dialogic_signal(value) @@ -35,13 +40,15 @@ func _ready(): # Loading the config files load_config_files() - # Make sure saves are ready - DialogicResources.init_definitions_saves(reset_saves) - # Checking if the dialog should read the code from a external file - if timeline != '': - dialog_script = set_current_dialog(timeline + '.json') - + if not timeline.empty(): + dialog_script = set_current_dialog(timeline) + elif dialog_script.keys().size() == 0: + dialog_script = { + "events":[{"character":"","portrait":"", + "text":"[Dialogic Error] No timeline specified."}] + } + # Connecting resize signal get_viewport().connect("size_changed", self, "resize_main") resize_main() @@ -55,12 +62,17 @@ func _ready(): # Getting the character information characters = DialogicUtil.get_character_list() - if Engine.is_editor_hint() == false: + if not Engine.is_editor_hint(): load_dialog() func load_config_files(): - definitions = DialogicUtil.get_definition_list() + if not Engine.is_editor_hint(): + # Make sure saves are ready + DialogicSingleton.init(reset_saves) + definitions = DialogicSingleton.get_definitions() + else: + definitions = DialogicResources.get_default_definitions() settings = DialogicResources.get_settings_config() var theme_file = 'res://addons/dialogic/Editor/ThemeEditor/default-theme.cfg' if settings.has_section('theme'): @@ -80,7 +92,8 @@ func resize_main(): $TextBubble.rect_position.y = (rect_size.y) - ($TextBubble.rect_size.y) - current_theme.get_value('box', 'bottom_gap', 40) -func set_current_dialog(dialog_path): +func set_current_dialog(dialog_path: String): + current_timeline = dialog_path var dialog_script = DialogicResources.get_timeline_json(dialog_path) # All this parse events should be happening in the same loop ideally # But until performance is not an issue I will probably stay lazy @@ -217,7 +230,6 @@ func parse_branches(dialog_script: Dictionary) -> Dictionary: func parse_definitions(text: String): var words = [] - var definition_list = DialogicUtil.get_definition_list() if Engine.is_editor_hint(): # Loading variables again to avoid issues in the preview dialog load_config_files() @@ -229,11 +241,9 @@ func parse_definitions(text: String): func _insert_variable_definitions(text: String): var final_text := text; - for d in definitions: - if d['type'] == 0: - var name : String = d['name']; - var value = DialogicUtil.get_var(name) - final_text = final_text.replace('[' + name + ']', value) + for d in definitions['variables']: + var name : String = d['name']; + final_text = final_text.replace('[' + name + ']', d['value']) return final_text; @@ -241,13 +251,12 @@ func _insert_glossary_definitions(text: String): var color = self.current_theme.get_value('definitions', 'color', '#ffbebebe') var final_text := text; # I should use regex here, but this is way easier :) - for d in definitions: - if d['type'] == 1: - final_text = final_text.replace(d['name'], - '[url=' + d['section'] + ']' + - '[color=' + color + ']' + d['name'] + '[/color]' + - '[/url]' - ) + for d in definitions['glossary']: + final_text = final_text.replace(d['name'], + '[url=' + d['id'] + ']' + + '[color=' + color + ']' + d['name'] + '[/color]' + + '[/url]' + ) return final_text; @@ -309,13 +318,26 @@ func update_text(text): return true +func on_timeline_start(): + if not Engine.is_editor_hint(): + DialogicSingleton.save_definitions() + DialogicSingleton.set_current_timeline(current_timeline) + emit_signal("event_start", "timeline", current_timeline) + + +func on_timeline_end(): + if not Engine.is_editor_hint(): + DialogicSingleton.save_definitions() + DialogicSingleton.set_current_timeline('') + emit_signal("event_end", "timeline") + func load_dialog(skip_add = false): # Emitting signals if dialog_script.has('events'): if dialog_index == 0: - emit_signal("event_start", "timeline", timeline) + on_timeline_start() elif dialog_index == dialog_script['events'].size(): - emit_signal("event_end", "timeline") + on_timeline_end() # Hiding definitions popup definition_visible = false @@ -346,8 +368,6 @@ func get_character(character_id): func event_handler(event: Dictionary): # Handling an event and updating the available nodes accordingly. reset_dialog_extras() - # Updating the settings and definitions in case that they were modified by a timelien - load_config_files() dprint('[D] Current Event: ', event) match event: @@ -440,6 +460,7 @@ func event_handler(event: Dictionary): go_to_next_event() {'close_dialog'}: emit_signal("event_start", "close_dialog", event) + on_timeline_end() queue_free() {'set_theme'}: emit_signal("event_start", "set_theme", event) @@ -458,11 +479,12 @@ func event_handler(event: Dictionary): # Treating this conditional as an option on a regular question event var def_value = null var current_question = questions[event['question_id']] - for d in definitions: - if d['section'] == event['definition']: - def_value = DialogicUtil.get_var(d['name']) - var condition_met = self._compare_definitions(def_value, event['value'], event['condition']); + for d in definitions['variables']: + if d['id'] == event['definition']: + def_value = d['value'] + + var condition_met = def_value != null and _compare_definitions(def_value, event['value'], event['condition']); current_question['answered'] = !condition_met if !condition_met: @@ -474,7 +496,7 @@ func event_handler(event: Dictionary): go_to_next_event() {'set_value', 'definition'}: emit_signal("event_start", "set_value", event) - DialogicResources.set_saved_definition_variable_value(event['definition'], event['set_value']) + DialogicSingleton.set_variable_from_id(event['definition'], event['set_value']) go_to_next_event() _: visible = false @@ -570,7 +592,6 @@ func _on_option_selected(option, variable, value): waiting_for_answer = false reset_options() load_dialog() - #print(dialog_resource.custom_variables) dprint('[!] Option selected: ', option.text, ' value= ' , value) @@ -684,17 +705,16 @@ func load_theme(filename): func _on_RichTextLabel_meta_hover_started(meta): var correct_type = false - for d in definitions: - if d['section'] == meta: - if d['type'] == 1: - $DefinitionInfo.load_preview({ - 'title': d['config'].get_value(d['section'], 'extra_title', ''), - 'body': d['config'].get_value(d['section'], 'extra_text', ''), - 'extra': d['config'].get_value(d['section'], 'extra_extra', ''), - 'color': current_theme.get_value('definitions', 'color', '#ffbebebe'), - }) - correct_type = true - print(d) + for d in definitions['glossary']: + if d['id'] == meta: + $DefinitionInfo.load_preview({ + 'title': d['title'], + 'body': d['text'], + 'extra': d['extra'], + 'color': current_theme.get_value('definitions', 'color', '#ffbebebe'), + }) + correct_type = true + print(d) if correct_type: definition_visible = true diff --git a/addons/dialogic/Other/DialogicClass.gd b/addons/dialogic/Other/DialogicClass.gd index f8f8837..b8c7c88 100644 --- a/addons/dialogic/Other/DialogicClass.gd +++ b/addons/dialogic/Other/DialogicClass.gd @@ -1,29 +1,137 @@ extends Node + +## Exposed and safe to use methods for Dialogic +## See documentation here: +## https://github.com/coppolaemilio/dialogic + +## ### /!\ ### +## 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 -static func start(timeline: String, dialog_scene_path: String="res://addons/dialogic/Dialog.tscn", debug_mode: bool=false): - var dialog = load(dialog_scene_path) +## 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 exactly the same as 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. +## @param reset_saves True to reset dialogic saved data such as definitions. +## @param dialog_scene_path If you made a custom Dialog scene or moved it from its default path, you can specify its new path here. +## @param debug_mode Debug is disabled by default but can be enabled if needed. +## @returns A Dialog node to be added into the scene tree. +static func start(timeline: String, reset_saves: bool=true, dialog_scene_path: String="res://addons/dialogic/Dialog.tscn", debug_mode: bool=false): + + var dialog: = load(dialog_scene_path) var d = dialog.instance() + d.reset_saves = reset_saves d.debug_mode = debug_mode - for t in DialogicUtil.get_timeline_list(): - if t['name'] == timeline: - d.timeline = t['file'].replace('.json', '') - return d - d.dialog_script = { - "events":[{"character":"","portrait":"", - "text":"[Dialogic Error] Loading dialog [color=red]" + timeline + "[/color]. It seems like the timeline doesn't exists. Maybe the name is wrong?"}] - } + if not timeline.empty(): + for t in DialogicUtil.get_timeline_list(): + if t['name'] == timeline or t['file'] == timeline: + d.timeline = t['file'] + return d + d.dialog_script = { + "events":[{"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 d -static func reset_saves(): - DialogicResources.init_definitions_saves(true) +## Same as the start method above, but using the last timeline saved. +## +## @param initial_timeline The timeline to load in case no save is found. +## @param dialog_scene_path If you made a custom Dialog scene or moved it from its default path, you can specify its new path here. +## @param debug_mode Debug is disabled by default but can be enabled if needed. +## @returns A Dialog node to be added into the scene tree. +static func start_from_save(initial_timeline: String, dialog_scene_path: String="res://addons/dialogic/Dialog.tscn", debug_mode: bool=false): + var current := get_current_timeline() + if current.empty(): + current = initial_timeline + return start(current, false, dialog_scene_path, debug_mode) + +## Gets default values for definitions. +## +## @returns Dictionary in the format {'variables': [], 'glossary': []} +static func get_default_definitions() -> Dictionary: + return DialogicSingleton.get_default_definitions() -static func get_var(variable: String): - return DialogicUtil.get_var(variable) +## Gets currently saved values for definitions. +## +## @returns Dictionary in the format {'variables': [], 'glossary': []} +static func get_definitions() -> Dictionary: + return DialogicSingleton.get_definitions() -static func set_var(variable: String, value): - DialogicUtil.set_var(variable, value) +## Save current definitions to the filesystem. +## Definitions are automatically saved on timeline start/end +## +## @returns Error status, OK if all went well +func save_definitions(): + return DialogicSingleton.save_definitions() + + +## Resets data to default values. This is the same as calling start with reset_saves to true +func reset_saves(): + DialogicSingleton.init(true) + + +## Gets the value for the variable with the given name. +## The returned value is a String but can be easily converted into a number +## using Godot built-in methods: +## [`is_valid_float`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-is-valid-float) +## [`float()`](https://docs.godotengine.org/en/stable/classes/class_float.html#class-float-method-float). +## +## @param name The name of the variable to find. +## @returns The variable's value as string, or an empty string if not found. +static func get_variable(name: String) -> String: + return DialogicSingleton.get_variable(name) + + +## Sets the value for the variable with the given name. +## The given value will be converted to string using the +## [`str()`](https://docs.godotengine.org/en/stable/classes/class_string.html) function. +## +## @param name The name of the variable to edit. +## @param value The value of the variable to set. +static func set_variable(name: String, value) -> void: + DialogicSingleton.set_variable(name, value) + + +## Gets the glossary data for the definition with the given name. +## Returned format: +## { title': '', 'text' : '', 'extra': '' } +## +## @param name The name of the glossary to find. +## @returns The glossary data as a Dictionary. +## A structure with empty strings is returned if the glossary was not found. +static func get_glossary(name: String) -> Dictionary: + return DialogicSingleton.get_glossary(name) + + +## Sets the data for the glossary of the given name. +## +## @param name The name of the glossary to edit. +## @param title The title to show in the information box. +## @param text The text to show in the information box. +## @param extra The extra information at the bottom of the box. +static func set_glossary(name: String, title: String, text: String, extra: String) -> void: + DialogicSingleton.set_glossary(name, title, text, extra) + + +## Gets the currently saved timeline. +## Timeline saves are set on timeline start, and cleared on end. +## This means you can keep track of timeline changes and detect when the dialog ends. +## +## @returns The current timeline filename, or an empty string if none was saved. +static func get_current_timeline() -> String: + return DialogicSingleton.get_current_timeline() diff --git a/addons/dialogic/Other/DialogicDefinitionsUtil.gd b/addons/dialogic/Other/DialogicDefinitionsUtil.gd new file mode 100644 index 0000000..8810c81 --- /dev/null +++ b/addons/dialogic/Other/DialogicDefinitionsUtil.gd @@ -0,0 +1,71 @@ +extends Node +class_name DialogicDefinitionsUtil + + +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'] diff --git a/addons/dialogic/Other/DialogicResources.gd b/addons/dialogic/Other/DialogicResources.gd index 02727e7..94a108a 100644 --- a/addons/dialogic/Other/DialogicResources.gd +++ b/addons/dialogic/Other/DialogicResources.gd @@ -6,24 +6,26 @@ const RESOURCES_DIR: String = "res://dialogic" # Readonly, used for static data const WORKING_DIR: String = "user://dialogic" # Readwrite, used for saves -static func load_json(path: String) -> Dictionary: +static func load_json(path: String, default: Dictionary={}) -> Dictionary: # An easy function to load json files and handle common errors. - var file:File = File.new() + var file := File.new() if file.open(path, File.READ) != OK: file.close() - return {'error':'file read error'} + return default var data_text: String = file.get_as_text() file.close() - var data_parse:JSONParseResult = JSON.parse(data_text) + if data_text.empty(): + return default + var data_parse: JSONParseResult = JSON.parse(data_text) if data_parse.error != OK: - return {'error':'data parse error'} + return default var final_data = data_parse.result if typeof(final_data) == TYPE_DICTIONARY: return final_data # If everything else fails - return {'error':'data parse error'} + return default static func init_dialogic_files() -> void: @@ -57,43 +59,68 @@ static func get_working_directories() -> Dictionary: static func get_config_files_paths() -> Dictionary: return { 'SETTINGS_FILE': RESOURCES_DIR + "/settings.cfg", - 'DEFAULT_DEFINITIONS_FILE': RESOURCES_DIR + "/definitions.cfg", - 'SAVED_DEFINITIONS_FILE': WORKING_DIR + "/definitions.cfg", + 'DEFAULT_DEFINITIONS_FILE': RESOURCES_DIR + "/definitions.json", + 'SAVED_DEFINITIONS_FILE': WORKING_DIR + "/definitions.json", + 'SAVED_STATE_FILE': WORKING_DIR + "/state.json", } +static func init_saves(overwrite: bool=true): + var err = init_working_dir() + var paths := get_config_files_paths() + + if err == OK: + init_state_saves(overwrite) + init_definitions_saves(overwrite) + else: + print('Error creating working directory: ' + str(err)) + + +static func init_working_dir(): + var directory := Directory.new() + return directory.make_dir_recursive(get_working_directories()['WORKING_DIR']) + + +static func init_state_saves(overwrite: bool=true): + var file := File.new() + var paths := get_config_files_paths() + + if not file.file_exists(paths["SAVED_STATE_FILE"]) or overwrite: + var err = file.open(paths["SAVED_STATE_FILE"], File.WRITE) + if err == OK: + file.store_string('') + file.close() + else: + print('Error opening saved state file: ' + str(err)) + + static func init_definitions_saves(overwrite: bool=true): var directory := Directory.new() var source := File.new() var sink := File.new() var paths := get_config_files_paths() - - var err := directory.make_dir_recursive(get_working_directories()['WORKING_DIR']) - - if err == OK: - if not directory.file_exists(paths["SAVED_DEFINITIONS_FILE"]): - err = sink.open(paths["SAVED_DEFINITIONS_FILE"], File.WRITE) - print('Saved definitions not present, creating file: ' + str(err)) - if err == OK: - sink.store_string('') - sink.close() - else: - print('Error opening saved definitions file: ' + str(err)) - - err = sink.open(paths["SAVED_DEFINITIONS_FILE"], File.READ_WRITE) + var err + if not directory.file_exists(paths["SAVED_DEFINITIONS_FILE"]): + err = sink.open(paths["SAVED_DEFINITIONS_FILE"], File.WRITE) + print('Saved definitions not present, creating file: ' + str(err)) if err == OK: - if overwrite or sink.get_len() == 0: - err = source.open(paths["DEFAULT_DEFINITIONS_FILE"], File.READ) - if err == OK: - sink.store_string(source.get_as_text()) - else: - print('Error opening default definitions file: ' + str(err)) - else: - print('Did not overwrite previous saved definitions') + sink.store_string('') + sink.close() else: print('Error opening saved definitions file: ' + str(err)) + + err = sink.open(paths["SAVED_DEFINITIONS_FILE"], File.READ_WRITE) + if err == OK: + if overwrite or sink.get_len() == 0: + err = source.open(paths["DEFAULT_DEFINITIONS_FILE"], File.READ) + if err == OK: + sink.store_string(source.get_as_text()) + else: + print('Error opening default definitions file: ' + str(err)) + else: + print('Did not overwrite previous saved definitions') else: - print('Error creating working directory: ' + str(err)) + print('Error opening saved definitions file: ' + str(err)) source.close() sink.close() @@ -163,30 +190,26 @@ static func remove_file(path: String): # JSON UTIL -static func get_json(dir_id: String, path: String): - return load_json(get_path(dir_id, path)) - - -static func set_json(dir_id: String, path: String, data: Dictionary): - var directory = Directory.new() - var base_path := get_path(dir_id) - if not directory.dir_exists(base_path): - directory.make_dir_recursive(base_path) +static func set_json(path: String, data: Dictionary): var file = File.new() - file.open(get_path(dir_id, path), File.WRITE) - file.store_line(to_json(data)) - file.close() + var err = file.open(path, File.WRITE) + if err == OK: + file.store_line(to_json(data)) + file.close() + return err # TIMELINE +# Can only be edited in the editor + static func get_timeline_json(path: String): - return get_json('TIMELINE_DIR', path) + return load_json(get_path('TIMELINE_DIR', path)) static func set_timeline(timeline: Dictionary): # WARNING: For use in the editor only - set_json('TIMELINE_DIR', timeline['metadata']['file'], timeline) + set_json(get_path('TIMELINE_DIR', timeline['metadata']['file']), timeline) static func delete_timeline(filename: String): @@ -195,15 +218,16 @@ static func delete_timeline(filename: String): # CHARACTER +# Can only be edited in the editor static func get_character_json(path: String): - return get_json('CHAR_DIR', path) + return load_json(get_path('CHAR_DIR', path)) static func set_character(character: Dictionary): # WARNING: For use in the editor only - set_json('CHAR_DIR', character['id'], character) + set_json(get_path('CHAR_DIR', character['id']), character) static func delete_character(filename: String): @@ -212,6 +236,7 @@ static func delete_character(filename: String): # THEME +# Can only be edited in the editor static func get_theme_config(filename: String): @@ -228,8 +253,6 @@ static func get_theme_config(filename: String): static func set_theme_value(filename, section, key, value): # WARNING: For use in the editor only - print('=> theme update') - print(filename) var config = get_theme_config(filename) config.set_value(section, key, value) config.save(get_path('THEME_DIR', filename)) @@ -239,10 +262,14 @@ 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)) + # SETTINGS +# Can only be edited in the editor -static func get_settings_config(): +static func get_settings_config() -> ConfigFile: return get_config("SETTINGS_FILE") @@ -252,98 +279,76 @@ static func set_settings_value(section: String, key: String, value): config.save(get_config_files_paths()['SETTINGS_FILE']) -# DEFINITIONS UTIL -# used by default and saved definitions +# STATE -static func get_definition_key(config_id: String, section: String, key: String, default): - var config = get_config(config_id) - if config.has_section(section): - return config.get_value(section, key, default) + +static func get_saved_state() -> Dictionary: + return load_json(get_config_files_paths()['SAVED_STATE_FILE'], {'general': {}}) + + +static func save_saved_state_config(data: Dictionary): + set_json(get_config_files_paths()['SAVED_STATE_FILE'], data) + + +static func get_saved_state_general_key(key: String) -> String: + var data = get_saved_state() + if key in data['general'].keys(): + return data['general'][key] else: - return default + return '' -static func set_definition_variable(config_id: String, section: String, name: String, value): - var config = get_config(config_id) - config.set_value(section, 'name', name) - config.set_value(section, 'type', 0) - config.set_value(section, 'value', str(value)) - return config.save(get_config_files_paths()[config_id]) - - -static func set_definition_glossary(config_id: String, section: String, name: String, extra_title: String, extra_text: String, extra_extra: String): - var config = get_config(config_id) - config.set_value(section, 'name', name) - config.set_value(section, 'type', 1) - config.set_value(section, 'extra_title', extra_title) - config.set_value(section, 'extra_text', extra_text) - config.set_value(section, 'extra_extra', extra_extra) - return config.save(get_config_files_paths()[config_id]) - - -static func add_definition_variable(config_id: String, section: String, name: String, type: int, value): - var config = get_config(config_id) - config.set_value(section, 'name', name) - config.set_value(section, 'type', type) - config.set_value(section, 'value', str(value)) - return config.save(get_config_files_paths()[config_id]) - - -static func delete_definition(config_id: String, section: String): - var config = get_config(config_id) - config.erase_section(section) - return config.save(get_config_files_paths()[config_id]) - +static func set_saved_state_general_key(key: String, value): + var data = get_saved_state() + data['general'][key] = str(value) + save_saved_state_config(data) # DEFAULT DEFINITIONS # Can only be edited in the editor -static func get_default_definitions_config(): - return get_config('DEFAULT_DEFINITIONS_FILE') + +static func get_default_definitions() -> Dictionary: + return load_json(get_config_files_paths()['DEFAULT_DEFINITIONS_FILE'], {'variables': [], 'glossary': []}) -static func get_default_definition_key(section: String, key: String, default): - return get_definition_key('DEFAULT_DEFINITIONS_FILE', section, key, default) +static func save_default_definitions(data: Dictionary): + set_json(get_config_files_paths()['DEFAULT_DEFINITIONS_FILE'], data) -static func set_default_definition_variable(section: String, name: String, value): +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 - return set_definition_variable('DEFAULT_DEFINITIONS_FILE', section, name, value) + var data = get_default_definitions() + DialogicDefinitionsUtil.set_definition_variable(data, id, name, value) + save_default_definitions(data) -static func set_default_definition_glossary(section: String, name: String, extra_title: String, extra_text: String, extra_extra: String): +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 - return set_definition_glossary('DEFAULT_DEFINITIONS_FILE', section, name, extra_title, extra_text, extra_extra) + var data = get_default_definitions() + DialogicDefinitionsUtil.set_definition_glossary(data, id, name, extra_title, extra_text, extra_extra) + save_default_definitions(data) -static func add_default_definition_variable(section: String, name: String, type: int, value): +static func delete_default_definition(id: String): # WARNING: For use in the editor only - return add_definition_variable('DEFAULT_DEFINITIONS_FILE', section, name, type, value) - - -static func delete_default_definition(section: String): - # WARNING: For use in the editor only - return delete_definition('DEFAULT_DEFINITIONS_FILE', section) + var data = get_default_definitions() + DialogicDefinitionsUtil.delete_definition(data, id) + save_default_definitions(data) # SAVED DEFINITIONS # Can be edited at runtime, and will persist across runs -static func get_saved_definitions_config(): - return get_config("SAVED_DEFINITIONS_FILE") +static func get_saved_definitions() -> Dictionary: + return load_json(get_config_files_paths()['SAVED_DEFINITIONS_FILE'], {'variables': [], 'glossary': []}) -static func set_saved_definition_variable(section: String, name: String, value): - return set_definition_variable('SAVED_DEFINITIONS_FILE', section, name, value) - - -static func set_saved_definition_variable_value(section: String, value): - var config = get_saved_definitions_config() - return set_definition_variable('SAVED_DEFINITIONS_FILE', section, config.get_value(section, 'name', section), value) - - -static func set_saved_definition_glossary(section: String, name: String, extra_title: String, extra_text: String, extra_extra: String): - return set_definition_glossary('SAVED_DEFINITIONS_FILE', section, name, extra_title, extra_text, extra_extra) - +static func save_saved_definitions(data: Dictionary): + return set_json(get_config_files_paths()['SAVED_DEFINITIONS_FILE'], data) diff --git a/addons/dialogic/Other/DialogicSingleton.gd b/addons/dialogic/Other/DialogicSingleton.gd new file mode 100644 index 0000000..49ce4f6 --- /dev/null +++ b/addons/dialogic/Other/DialogicSingleton.gd @@ -0,0 +1,86 @@ +extends Node + +var current_definitions := {} +var default_definitions := {} + +var current_timeline := '' + +func _init() -> void: + init(false) + + +func init(reset: bool=false) -> void: + # Loads saved definitions into memory + DialogicResources.init_saves(reset) + current_definitions = DialogicResources.get_saved_definitions() + default_definitions = DialogicResources.get_default_definitions() + current_timeline = DialogicResources.get_saved_state_general_key('timeline') + + +func get_definitions_list() -> Array: + return DialogicDefinitionsUtil.definitions_json_to_array(current_definitions) + + +func get_definitions() -> Dictionary: + return current_definitions + + +func get_default_definitions() -> Dictionary: + return default_definitions + + +func get_default_definitions_list() -> Array: + return DialogicDefinitionsUtil.definitions_json_to_array(default_definitions) + + +func save_definitions(): + return DialogicResources.save_saved_definitions(current_definitions) + + +func get_variable(name: String) -> String: + for d in current_definitions['variables']: + if d['name'] == name: + return d['value'] + return '' + + +func set_variable(name: String, value) -> void: + for d in current_definitions['variables']: + if d['name'] == name: + d['value'] = str(value) + + +func set_variable_from_id(id: String, value) -> void: + for d in current_definitions['variables']: + if d['id'] == id: + d['value'] = str(value) + + +func get_glossary(name: String) -> Dictionary: + for d in current_definitions['glossary']: + if d['name'] == name: + return d + return { + 'title': '', + 'text': '', + 'extra': '' + } + + +func set_glossary(name: String, title: String, text: String, extra: String) -> void: + for d in current_definitions['glossary']: + if d['name'] == name: + d['title'] = title + d['text'] = text + d['extra'] = extra + + +func set_current_timeline(timeline: String): + current_timeline = timeline + DialogicResources.set_saved_state_general_key('timeline', timeline) + + +func get_current_timeline() -> String: + return current_timeline + + diff --git a/addons/dialogic/Other/DialogicUtil.gd b/addons/dialogic/Other/DialogicUtil.gd index 3744f50..76f5096 100644 --- a/addons/dialogic/Other/DialogicUtil.gd +++ b/addons/dialogic/Other/DialogicUtil.gd @@ -68,43 +68,8 @@ static func get_theme_list() -> Array: return themes -static func get_default_definition_list() -> Array: - var definitions: Array = [] - var config = DialogicResources.get_default_definitions_config() - for section in config.get_sections(): - definitions.append({ - 'section': section, - 'name': config.get_value(section, 'name', section), - 'config': config, - 'type': config.get_value(section, 'type', 0), - }) - return definitions - - -static func get_definition_list() -> Array: - var definitions: Array = [] - var config = DialogicResources.get_saved_definitions_config() - for section in config.get_sections(): - definitions.append({ - 'section': section, - 'name': config.get_value(section, 'name', section), - 'config': config, - 'type': config.get_value(section, 'type', 0), - }) - return definitions - - -static func get_var(variable: String) -> String: - for d in get_definition_list(): - if d['name'] == variable: - return d['config'].get_value(d['section'], 'value') - return '' - - -static func set_var(variable: String, value) -> void: - for d in get_definition_list(): - if d['name'] == variable: - DialogicResources.set_saved_definition_variable(d['section'], d['name'], value) +static func get_default_definitions_list() -> Array: + return DialogicDefinitionsUtil.definitions_json_to_array(DialogicResources.get_default_definitions()) static func generate_random_id() -> String: diff --git a/addons/dialogic/Other/timeline_picker.gd b/addons/dialogic/Other/timeline_picker.gd index 4d3927a..7d6f899 100644 --- a/addons/dialogic/Other/timeline_picker.gd +++ b/addons/dialogic/Other/timeline_picker.gd @@ -36,7 +36,7 @@ func _about_to_show_menu(): 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'].replace('.json', '') + current_value = metadata['file'] timelines_dropdown.text = text emit_changed(get_edited_property(), current_value) @@ -52,6 +52,6 @@ func update_property(): current_value = new_value # Checking for the display name for c in DialogicUtil.get_timeline_list(): - if c['file'].replace('.json', '') == current_value: + if c['file'] == current_value: timelines_dropdown.text = c['name'] updating = false diff --git a/addons/dialogic/dialogic.gd b/addons/dialogic/dialogic.gd index 4581c94..18a551f 100644 --- a/addons/dialogic/dialogic.gd +++ b/addons/dialogic/dialogic.gd @@ -4,6 +4,13 @@ extends EditorPlugin var _editor_view var _parts_inspector +func _init(): + if Engine.editor_hint: + # Make sure the core files exist + DialogicResources.init_dialogic_files() + add_autoload_singleton('DialogicSingleton', "res://addons/dialogic/Other/DialogicSingleton.gd") + + func _enter_tree() -> void: _parts_inspector = load("res://addons/dialogic/Other/inspector_timeline_picker.gd").new() add_inspector_plugin(_parts_inspector) @@ -14,8 +21,6 @@ func _enter_tree() -> void: func _ready(): if Engine.editor_hint: - # Make sure the core files exist - DialogicResources.init_dialogic_files() # Force Godot to show the dialogic folder get_editor_interface().get_resource_filesystem().scan()