tool extends Control signal changed_story signal close_pressed signal dialog_edit_pressed(story, did) onready var _Dialog_Record_Root = self.get_node("VBoxContainer/HBoxContainer3/Panel/VScrollBar/Dialog_Record_Root") onready var _Dir = Directory.new() onready var _Filename_LBL = self.get_node("VBoxContainer/HBoxContainer2/Filename_LBL") onready var _Filter_Menu = self.get_node("VBoxContainer/HBoxContainer2/Filter_MenuButton") onready var _Group_List = self.get_node("VBoxContainer/HBoxContainer3/Group_Manager/Group_ItemList") onready var _Group_Manager_Panel = self.get_node("VBoxContainer/HBoxContainer3/Group_Manager") onready var _Group_Selector = self.get_node("VBoxContainer/HBoxContainer/Group_Selector_BTN") onready var _New_Group_LineEdit = self.get_node("VBoxContainer/HBoxContainer3/Group_Manager/HBoxContainer/Add_Group_LineEdit") onready var _Search_LineEdit = self.get_node("VBoxContainer/HBoxContainer2/Search_LineEdit") onready var _Search_Option_BTN = self.get_node("VBoxContainer/HBoxContainer2/Search_OptionButton") onready var _Story_Menu = self.get_node("VBoxContainer/HBoxContainer/Story") var _Dialog_Record = preload("res://addons/EXP-System-Dialog/Story Editor/Dialog Record/Dialog_Record.tscn") var _EXP_Baked_Story = preload("res://addons/EXP-System-Dialog/Resource_BakedStory/EXP_BakedStory.gd") var _EXP_Story = preload("res://addons/EXP-System-Dialog/Resource_EditorStory/EXP_EditorStory.gd") var _Record_Rename_Box_TSCN = preload("res://addons/EXP-System-Dialog/Story Editor/Rename Record Box/Rename_Record_Box.tscn") var _available_dids : Array var _Bake_Story_As : EditorFileDialog var _checked_dialogs : Array = [] var _groups : Array var _Load_CSV : EditorFileDialog var _Load_Story : EditorFileDialog var _record_names : Dictionary var _Record_Rename_Box var _Save_CSV_As : EditorFileDialog var _Save_Story_As : EditorFileDialog var _story : Dictionary #Virtual Methods func _ready(): self._create_rename_box() self._populate_story_menu() self._setup_dialogs() self._Filter_Menu.get_popup().connect("index_pressed", self, "_on_Filter_Menu_index_pressed") self._Filter_Menu.get_popup().hide_on_checkable_item_selection = false self._populate_filter_menu() self._populate_searchby_menu() #Callback Methods func _on_Add_Group_BTN_pressed(): self._add_group() func _on_Add_Group_LineEdit_text_entered(new_text): self._add_group() func _on_Apply_Group_BTN_pressed(): var id = self._Group_Selector.get_selected_id() if id == -1: return var idx = self._Group_Selector.get_item_index(id) var group = self._Group_Selector.get_popup().get_item_text(idx) for record in self._checked_dialogs.duplicate(): var did = record.get_did() self._dialog_apply_group(did, group) record.uncheck() self.emit_signal("changed_story") func _on_Bake_Story_As_file_selected(filename : String): self._bake_data_to(filename) func _on_Bake_Story_BTN_pressed(): self._Bake_Story_As.popup_centered_ratio(0.7) func _on_Check_All_BTN_pressed(): var records = self._Dialog_Record_Root.get_children() for record in records: if record.visible: record.check() func _on_Close_BTN_pressed(): self.emit_signal("close_pressed") func _on_Create_Dialog_BTN_pressed(): self._create_dialog_record() func _on_Delete_Dialog_BTN_pressed(): self._delete_checked_dialogs() func _on_Delete_Group_BTN_pressed(): var idxs = self._Group_List.get_selected_items() var group for idx in idxs: group = self._Group_List.get_item_text(idx) self._Group_List.remove_item(idx) self._delete_group(group) self._populate_group_selector() self._populate_filter_menu() func _on_Dialog_changed_human_readable_text(did : int, new_text : String): self.set_dialog_property(did, "human_readable_description", new_text) self.emit_signal("changed_story") func _on_Dialog_checked(dialog): self._checked_dialogs.push_front(dialog) func _on_Dialog_edit_pressed(did : int): self.emit_signal("dialog_edit_pressed", self, did) func _on_Dialog_unchecked(dialog): self._checked_dialogs.erase(dialog) func _on_Filter_Menu_index_pressed(idx): var checked = self._Filter_Menu.get_popup().is_item_checked(idx) if not checked: self._Filter_Menu.get_popup().set_item_checked(idx, true) else: self._Filter_Menu.get_popup().set_item_checked(idx, false) self._update_filter() func _on_Group_Manager_BTN_toggled(button_pressed : bool): if button_pressed: self._Group_Manager_Panel.visible = true else: self._Group_Manager_Panel.visible = false func _on_Group_Selector_BTN_pressed(): self._populate_group_selector() func _on_Load_CSV_BTN_pressed(): self._Load_CSV.popup_centered_ratio(0.7) func _on_Load_CSV_file_selected(filepath : String): var csv_file = File.new() var status = csv_file.open(filepath, File.READ) if not status == OK: print_debug("EXP_Story_Editor: Error loading file \"" + filepath + "\".") return csv_file.get_csv_line() while not csv_file.eof_reached(): var line = csv_file.get_csv_line() if line.empty(): continue var did = int(line[0]) var nid = int(line[1]) var dialog = String(line[2]) if not self._story.has(did): continue if not self._story[did]["nodes"].has(nid): continue self._story[did]["nodes"][nid]["text"] = dialog csv_file.close() func _on_Load_Story_BTN_pressed(): self._Load_Story.popup_centered_ratio(0.7) func _on_Load_Story_file_selected(filename : String): var file_data = load(filename) if not file_data.TYPE == "EXP_Story_editor": return self._clear_story() self._load_data_from(file_data) self._Filename_LBL.text = filename.get_file() for group in self._groups: self._Group_List.add_item(group) self._populate_filter_menu() for did in self.get_dids(): var new_dialog_record = _Dialog_Record.instance() self._Dialog_Record_Root.add_child(new_dialog_record) new_dialog_record.set_story_editor(self) new_dialog_record.connect("checked", self, "_on_Dialog_checked") new_dialog_record.connect("unchecked", self, "_on_Dialog_unchecked") new_dialog_record.connect("changed_human_readable_text", self, "_on_Dialog_changed_human_readable_text") new_dialog_record.connect("edit_pressed", self, "_on_Dialog_edit_pressed") new_dialog_record.connect("rename_pressed", self, "_on_Record_Rename_pressed") new_dialog_record.set_did(did) var human_readable_description = self.get_dialog_property(did, "human_readable_description") new_dialog_record.update_human_readable_description(human_readable_description) if self._story[did].has("name"): var record_name = self._story[did]["name"] new_dialog_record.set_record_name(record_name) func _on_New_Story_BTN_pressed(): self._clear_story() func _on_Record_Rename_pressed(record): self._Record_Rename_Box.set_target_record(record) self._Record_Rename_Box.visible = true func _on_Remove_Group_BTN_pressed(): var id = self._Group_Selector.get_selected_id() if id == -1: return var idx = self._Group_Selector.get_item_index(id) var group = self._Group_Selector.get_popup().get_item_text(idx) for record in self._checked_dialogs.duplicate(): var did = record.get_did() self._dialog_remove_group(did, group) record.uncheck() self.emit_signal("changed_story") func _on_Rename_Box_Rename(rename : String): var record = self._Record_Rename_Box.get_target_record() var old_name = record.get_record_name() var record_did = record.get_did() if rename.empty() or rename == "NAME": record.set_record_name("NAME") self._story[record_did].erase("name") self._record_names.erase(old_name) return if self._record_names.has(rename): return self._record_names.erase(old_name) self._record_names[rename] = record_did self._story[record_did]["name"] = rename record.set_record_name(rename) func _on_Save_CSV_BTN_pressed(): self._Save_CSV_As.popup_centered_ratio(0.7) func _on_Save_CVS_As_file_selected(filepath : String): var csv_file = File.new() var status = csv_file.open(filepath, File.WRITE) if not status == OK: print_debug("EXP_Story_Editor: Error saving csv file \"" + filepath + "\".") return csv_file.store_csv_line(["DID", "NID", "Dialog"], ",") for did in self._story.keys(): for nid in self._story[did]["nodes"].keys(): var dialog = self._story[did]["nodes"][nid]["text"] csv_file.store_csv_line([did, nid, dialog], ",") csv_file.close() func _on_Save_Story_As_file_selected(filename : String): self._save_data_to(filename) self._Filename_LBL.text = filename.get_file() func _on_Save_Story_BTN_pressed(): self._Save_Story_As.popup_centered_ratio(0.7) func _on_Search_LineEdit_text_changed(new_text : String): self._update_filter() func _on_Search_OptionButton_item_selected(id): self._update_filter() func _on_story_menu_option_pressed(id): match id: 0: self._on_New_Story_BTN_pressed() 1: self._on_Load_Story_BTN_pressed() 2: self._on_Save_Story_BTN_pressed() 3: self._on_Bake_Story_BTN_pressed() 4: self._on_Save_CSV_BTN_pressed() 5: self._on_Load_CSV_BTN_pressed() func _on_Uncheck_All_BTN_pressed(): var records = self._Dialog_Record_Root.get_children() for record in records: if record.visible: record.uncheck() #Public Methods func create_node(did : int, type : String) -> int: var new_nid = self._generate_nid(did) var node_data = {"type": type, "text": "", "graph_offset": Vector2(40, 40), "rect_size": Vector2(0,0) ,"links": {}, "slot_amount": 1} self._story[did]["nodes"][new_nid] = node_data return new_nid func dialog_get_groups(did : int): return self._story[did]["groups"] func erase_all_links(did: int, nid : int): self._story[did]["nodes"][nid]["links"].clear() func erase_dialog(did : int): self._story.erase(did) self._make_did_available(did) func erase_link(did : int, nid : int, slot : int): self._story[did]["nodes"][nid]["links"].erase(slot) func erase_node(did :int, nid :int): self._story[did]["nodes"].erase(nid) self._make_nid_available(did, nid) func get_dialog_property(did : int, property: String): return self._story[did][property] func get_dids(): return self._story.keys() func get_link_slots(did : int, nid : int): return self._story[did]["nodes"][nid]["links"].keys() func get_nid_link_from(did : int, nid: int, slot : int): return self._story[did]["nodes"][nid]["links"][slot] func get_nids(did : int): return self._story[did]["nodes"].keys() func get_node_property(did : int, nid : int, property: String): return self._story[did]["nodes"][nid][property] func set_dialog_property(did : int, property : String , data): self._story[did][property] = data func set_link(did : int, this_nid : int, slot : int, that_nid : int): self._story[did]["nodes"][this_nid]["links"][slot] = that_nid func set_node_property(did : int, nid : int, property : String , data): self._story[did]["nodes"][nid][property] = data func set_node_slot_count(did : int, nid : int, amount : int): self._story[did]["nodes"][nid]["slot_amount"] = amount #Private Methods func _add_group(): var new_group_name = self._New_Group_LineEdit.text if new_group_name == "" or self._groups.has(new_group_name): return self._groups.push_back(new_group_name) self._New_Group_LineEdit.text = "" self._Group_List.add_item(new_group_name) self._populate_filter_menu() var sort_list : Array for idx in range(self._Group_List.get_item_count()): var group = self._Group_List.get_item_text(idx) sort_list.push_back(group) sort_list.sort() self._Group_List.clear() for group in sort_list: self._Group_List.add_item(group) func _bake_data() : var baked_story = self._story.duplicate(true) for did in baked_story.keys(): baked_story[did].erase("name") baked_story[did].erase("groups") baked_story[did].erase("available_nid") baked_story[did].erase("human_readable_description") for nid in baked_story[did]["nodes"].keys(): baked_story[did]["nodes"][nid].erase("type") baked_story[did]["nodes"][nid].erase("graph_offset") baked_story[did]["nodes"][nid].erase("rect_size") baked_story[did]["nodes"][nid].erase("slot_amount") return baked_story.duplicate(true) func _bake_data_to(filename): var file_data if self._Dir.file_exists(filename): file_data = load(filename) if file_data.TYPE == "EXP_Baked_Story": file_data.story = self._bake_data() file_data.names = self._record_names.duplicate(true) ResourceSaver.save(filename, file_data) else: file_data = _EXP_Baked_Story.new() file_data.story = self._bake_data() file_data.names = self._record_names.duplicate(true) ResourceSaver.save(filename, file_data) func _clear_group_manager(): self._groups.clear() for idx in range(self._Group_List.get_item_count()): self._Group_List.remove_item(0) self._populate_group_selector() self._Filter_Menu.get_popup().clear() func _clear_story(): self._remove_all_records() self._clear_group_manager() self._populate_filter_menu() self._story.clear() self._available_dids.clear() self._checked_dialogs.clear() self._record_names.clear() self._Filename_LBL.text = "Unsaved Story" self.emit_signal("changed_story") func _create_dialog() -> int: var new_did = self._generate_did() var dialog_data = {"human_readable_description": "New Dialog - Enter Human Readable Description", "groups": [], "available_nid": [], "nodes": {}} self._story[new_did] = dialog_data return new_did func _create_dialog_record(): var new_did = self._create_dialog() var new_dialog_record = _Dialog_Record.instance() self._Dialog_Record_Root.add_child(new_dialog_record) new_dialog_record.set_story_editor(self) new_dialog_record.connect("checked", self, "_on_Dialog_checked") new_dialog_record.connect("unchecked", self, "_on_Dialog_unchecked") new_dialog_record.connect("changed_human_readable_text", self, "_on_Dialog_changed_human_readable_text") new_dialog_record.connect("edit_pressed", self, "_on_Dialog_edit_pressed") new_dialog_record.connect("rename_pressed", self, "_on_Record_Rename_pressed") new_dialog_record.set_did(new_did) new_dialog_record.update_human_readable_description( "New Dialog - Enter Human Readable Description.") func _create_rename_box(): self._Record_Rename_Box = _Record_Rename_Box_TSCN.instance() self._Record_Rename_Box.connect("rename_BTN_pressed", self, "_on_Rename_Box_Rename") self.add_child(self._Record_Rename_Box) func _delete_checked_dialogs(): for dialog in self._checked_dialogs: self._delete_dialog(dialog) self._checked_dialogs.clear() self.emit_signal("changed_story") func _delete_dialog(dialog): var did = dialog.get_did() self.erase_dialog(did) self._remove_record(dialog) func _delete_group(group): self._groups.erase(group) self._remove_group_from_story(group) func _dialog_apply_group(did : int, group : String): if not self._story[did]["groups"].has(group): self._story[did]["groups"].push_back(group) func _dialog_remove_group(did : int, group : String): if self._story[did]["groups"].has(group): self._story[did]["groups"].erase(group) func _generate_did() -> int: if not self._available_dids.empty(): return self._available_dids.pop_front() else: return self._story.size() + 1 func _generate_nid(did : int) -> int: if not self._story[did]["available_nid"].empty(): return self._story[did]["available_nid"].pop_front() else: return self._story[did]["nodes"].size() + 1 func _load_data_from(new_story): self._story = new_story.story.duplicate(true) self._available_dids = new_story.available_dids.duplicate(true) self._groups = new_story.groups.duplicate(true) self._record_names = new_story.names.duplicate(true) func _make_did_available(did : int): self._available_dids.push_front(did) self._available_dids.sort() func _make_nid_available(did : int, nid : int): self._story[did]["available_nid"].push_front(nid) self._story[did]["available_nid"].sort() func _make_records_visible(): var children = self._Dialog_Record_Root.get_children() for child in children: child.visible = true func _populate_filter_menu(): self._Filter_Menu.get_popup().clear() self._Filter_Menu.get_popup().add_check_item("-No Tags-") for group in self._groups: self._Filter_Menu.get_popup().add_check_item(group) for idx in range(self._Filter_Menu.get_popup().get_item_count()): self._Filter_Menu.get_popup().set_item_checked(idx, true) func _populate_group_selector(): self._Group_Selector.clear() self._Group_Selector.text = "Tags" for group in self._groups: self._Group_Selector.get_popup().add_item(group) func _populate_searchby_menu(): self._Search_Option_BTN.clear() self._Search_Option_BTN.get_popup().add_item("Human Readable LBL", 0) self._Search_Option_BTN.get_popup().add_item("DID", 1) self._Search_Option_BTN.get_popup().add_item("Record Name", 2) self._Search_Option_BTN.select(0) func _populate_story_menu(): self._Story_Menu.get_popup().clear() self._Story_Menu.get_popup().add_item("New Story", 0) self._Story_Menu.get_popup().add_item("Load Story", 1) self._Story_Menu.get_popup().add_item("Save Story As", 2) self._Story_Menu.get_popup().add_item("Bake Story As", 3) self._Story_Menu.get_popup().add_item("Save CSV As", 4) self._Story_Menu.get_popup().add_item("Load CSV", 5) self._Story_Menu.get_popup().connect("id_pressed", self, "_on_story_menu_option_pressed") func _remove_all_records(): var dialog_records = self._Dialog_Record_Root.get_children() for record in dialog_records: self._remove_record(record) func _remove_group_from_story(group : String): for did in self._story: if self._story[did]["groups"].has(group): self._story[did]["groups"].erase(group) func _remove_record(dialog_record): dialog_record.disconnect("checked", self, "_on_Dialog_checked") dialog_record.disconnect("unchecked", self, "_on_Dialog_unchecked") dialog_record.disconnect("changed_human_readable_text", self, "_on_Dialog_changed_human_readable_text") dialog_record.disconnect("rename_pressed", self, "_on_Record_Rename_pressed") var record_name = dialog_record.get_record_name() if not record_name == "NAME": self._record_names.erase(record_name) dialog_record.free() func _save_data_to(filename): var file_data if self._Dir.file_exists(filename): file_data = load(filename) if file_data.TYPE == "EXP_Story_editor": file_data.names = self._record_names.duplicate(true) file_data.story = self._story.duplicate(true) file_data.available_dids = self._available_dids.duplicate(true) file_data.groups = self._groups.duplicate(true) ResourceSaver.save(filename, file_data) else: file_data = _EXP_Story.new() file_data.names = self._record_names.duplicate(true) file_data.story = self._story.duplicate(true) file_data.available_dids = self._available_dids.duplicate(true) file_data.groups = self._groups.duplicate(true) ResourceSaver.save(filename, file_data) func _setup_dialogs(): self._Load_Story = EditorFileDialog.new() self._Load_Story.mode = EditorFileDialog.MODE_OPEN_FILE self._Load_Story.add_filter("*.tres ; Story files") self._Load_Story.resizable = true self._Load_Story.access = EditorFileDialog.ACCESS_RESOURCES self._Load_Story.current_dir = "res://" self._Load_Story.connect("file_selected", self, "_on_Load_Story_file_selected") self.add_child(self._Load_Story) self._Save_Story_As = EditorFileDialog.new() self._Save_Story_As.mode = EditorFileDialog.MODE_SAVE_FILE self._Save_Story_As.add_filter("*.tres ; Story files") self._Save_Story_As.resizable = true self._Save_Story_As.access = EditorFileDialog.ACCESS_RESOURCES self._Save_Story_As.current_dir = "res://" self._Save_Story_As.connect("file_selected", self, "_on_Save_Story_As_file_selected") self.add_child(self._Save_Story_As) self._Bake_Story_As = EditorFileDialog.new() self._Bake_Story_As.mode = EditorFileDialog.MODE_SAVE_FILE self._Bake_Story_As.add_filter("*.tres ; Baked Story files") self._Bake_Story_As.resizable = true self._Bake_Story_As.access = EditorFileDialog.ACCESS_RESOURCES self._Bake_Story_As.current_dir = "res://" self._Bake_Story_As.connect("file_selected", self, "_on_Bake_Story_As_file_selected") self.add_child(self._Bake_Story_As) self._Save_CSV_As = EditorFileDialog.new() self._Save_CSV_As.mode = EditorFileDialog.MODE_SAVE_FILE self._Save_CSV_As.add_filter("*.csv ; CSV files") self._Save_CSV_As.resizable = true self._Save_CSV_As.access = EditorFileDialog.ACCESS_FILESYSTEM self._Save_CSV_As.current_dir = "res://" self._Save_CSV_As.connect("file_selected", self, "_on_Save_CVS_As_file_selected") self.add_child(self._Save_CSV_As) self._Load_CSV = EditorFileDialog.new() self._Load_CSV .mode = EditorFileDialog.MODE_OPEN_FILE self._Load_CSV .add_filter("*.csv ; CSV files") self._Load_CSV .resizable = true self._Load_CSV .access = EditorFileDialog.ACCESS_FILESYSTEM self._Load_CSV .current_dir = "res://" self._Load_CSV .connect("file_selected", self, "_on_Load_CSV_file_selected") self.add_child(self._Load_CSV) func _update_filter(): var new_text = self._Search_LineEdit.text self._make_records_visible() var filter_groups : Array for idx in range(self._Filter_Menu.get_popup().get_item_count()): if self._Filter_Menu.get_popup().is_item_checked(idx): var group = self._Filter_Menu.get_popup().get_item_text(idx) filter_groups.push_back(group) var children = self._Dialog_Record_Root.get_children() var search_option = self._Search_Option_BTN.selected match search_option: 0: #Human Readable Search for child in children: var did = child.get_did() var human_readable_description = self.get_dialog_property(did, "human_readable_description") if human_readable_description.find(new_text) == -1 and not new_text.empty(): child.visible = false else: child.visible = false if self._Filter_Menu.get_popup().get_item_count() == 0: child.visible = true var dialog_groups = self.dialog_get_groups(did) if dialog_groups.empty() and filter_groups.has("-No Tags-"): child.visible = true for group in dialog_groups: if filter_groups.has(group): child.visible = true 1: #DID Search for child in children: var did = child.get_did() if not new_text == str(did) and not new_text.empty(): child.visible = false else: child.visible = false if self._Filter_Menu.get_popup().get_item_count() == 0: child.visible = true var dialog_groups = self.dialog_get_groups(did) if dialog_groups.empty() and filter_groups.has("-No Tags-"): child.visible = true for group in dialog_groups: if filter_groups.has(group): child.visible = true 2: #Record Name Search for child in children: var did = child.get_did() var record_name = child.get_record_name() if record_name.find(new_text) == -1 and not new_text.empty(): child.visible = false else: child.visible = false if self._Filter_Menu.get_popup().get_item_count() == 0: child.visible = true var dialog_groups = self.dialog_get_groups(did) if dialog_groups.empty() and filter_groups.has("-No Tags-"): child.visible = true for group in dialog_groups: if filter_groups.has(group): child.visible = true