├── DialogPlugin ├── ... ├── ChoiceNode.gd ├── ChoiceNode.tscn ├── ConditionNode.gd ├── ConditionNode.tscn ├── DialogNode.gd ├── DialogNode.tscn ├── Dialogs.gd ├── Mainpanel.gd ├── Mainpanel.tscn ├── SignalNode.gd ├── SignalNode.tscn ├── UiidGenerator.gd ├── nodes.stylebox ├── optionbutton.stylebox ├── plugin.cfg └── textedit.stylebox ├── DialogueManager.gd ├── LICENSE └── README.md /DialogPlugin/...: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /DialogPlugin/ChoiceNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | signal nodechanged 4 | 5 | var nodeid:String="" # stores unique ID of node 6 | var nodecontent:Dictionary={"en":"", "fr":""} # stores content of node in all languages 7 | onready var Editor:Object= get_parent().get_parent().get_parent() 8 | 9 | func _ready(): 10 | updatenode() 11 | 12 | # connect("nodechanged",Editor, "refreshjson") 13 | 14 | func _close_request(): 15 | var connections:Array=Editor.get_node("VBox/Panel").get_connection_list() 16 | for item in connections: 17 | if item["to"]==self.name or item["from"]==self.name: 18 | Editor.get_node("VBox/Panel").disconnect_node(item["from"],0,item["to"],0) 19 | queue_free() 20 | 21 | func _on_resize_request(new_minsize): 22 | rect_size=new_minsize 23 | 24 | func _on_values_changed(): # refresh the nodecontent variable 25 | nodecontent[Editor.language]=$VBox/Content.text 26 | 27 | # emit_signal("nodechanged") # refresh json file 28 | 29 | func updatenode(): 30 | $VBox/Content.text = nodecontent[Editor.language] 31 | _on_values_changed() 32 | 33 | func _on_Singlenode_clicked(): 34 | print(nodecontent) 35 | -------------------------------------------------------------------------------- /DialogPlugin/ChoiceNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/Dialogs/ChoiceNode.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/Dialogs/textedit.stylebox" type="StyleBox" id=2] 5 | [ext_resource path="res://addons/Dialogs/nodes.stylebox" type="StyleBox" id=3] 6 | 7 | [node name="ChoiceNode" type="GraphNode" groups=[ 8 | "dialognode", 9 | ]] 10 | margin_right = 270.0 11 | margin_bottom = 210.0 12 | rect_min_size = Vector2( 200, 200 ) 13 | rect_clip_content = true 14 | custom_styles/frame = ExtResource( 3 ) 15 | custom_styles/selectedframe = ExtResource( 3 ) 16 | title = "Choice" 17 | show_close = true 18 | resizable = true 19 | slot/0/left_enabled = false 20 | slot/0/left_type = 0 21 | slot/0/left_color = Color( 1, 1, 1, 1 ) 22 | slot/0/right_enabled = false 23 | slot/0/right_type = 0 24 | slot/0/right_color = Color( 1, 1, 1, 1 ) 25 | script = ExtResource( 1 ) 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="VBox" type="VBoxContainer" parent="."] 31 | margin_left = 30.0 32 | margin_top = 30.0 33 | margin_right = 240.0 34 | margin_bottom = 160.0 35 | 36 | [node name="KeyBox" type="HBoxContainer" parent="VBox"] 37 | margin_right = 210.0 38 | margin_bottom = 14.0 39 | 40 | [node name="Label" type="Label" parent="VBox/KeyBox"] 41 | margin_right = 27.0 42 | margin_bottom = 14.0 43 | text = "Key:" 44 | 45 | [node name="Keylabel" type="Label" parent="VBox/KeyBox"] 46 | margin_left = 31.0 47 | margin_right = 31.0 48 | margin_bottom = 14.0 49 | 50 | [node name="HSeparator2" type="HSeparator" parent="VBox"] 51 | margin_top = 18.0 52 | margin_right = 210.0 53 | margin_bottom = 22.0 54 | 55 | [node name="Content" type="TextEdit" parent="VBox"] 56 | margin_top = 26.0 57 | margin_right = 210.0 58 | margin_bottom = 86.0 59 | rect_min_size = Vector2( 200, 60 ) 60 | size_flags_horizontal = 3 61 | size_flags_vertical = 3 62 | custom_styles/normal = ExtResource( 2 ) 63 | 64 | [node name="HSeparator" type="HSeparator" parent="VBox"] 65 | margin_top = 90.0 66 | margin_right = 210.0 67 | margin_bottom = 94.0 68 | 69 | [node name="Nexttitle" type="Label" parent="VBox"] 70 | margin_top = 98.0 71 | margin_right = 210.0 72 | margin_bottom = 112.0 73 | text = "Next node:" 74 | 75 | [node name="Nextnode" type="Label" parent="VBox"] 76 | margin_top = 116.0 77 | margin_right = 210.0 78 | margin_bottom = 130.0 79 | 80 | [connection signal="close_request" from="." to="." method="_close_request"] 81 | [connection signal="raise_request" from="." to="." method="_on_Singlenode_clicked"] 82 | [connection signal="resize_request" from="." to="." method="_on_resize_request"] 83 | [connection signal="text_changed" from="VBox/Content" to="." method="_on_values_changed"] 84 | -------------------------------------------------------------------------------- /DialogPlugin/ConditionNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | signal nodechanged 4 | 5 | var nodeid:String="" # stores unique ID of node 6 | var nodecontent:Dictionary={} # stores content of node in all languages 7 | var variable="" 8 | var type="boolean" 9 | var operator="equal" 10 | var value="" 11 | onready var Editor:Object= get_parent().get_parent().get_parent() 12 | 13 | func _ready(): 14 | updatenode() 15 | $VBox/HBoxType/Content.select(0) 16 | $VBox/HBoxOper/Content.select(0) 17 | # connect("nodechanged",Editor, "refreshjson") 18 | 19 | func _close_request(): 20 | var connections:Array=Editor.get_node("VBox/Panel").get_connection_list() 21 | for item in connections: 22 | if item["to"]==self.name or item["from"]==self.name: 23 | Editor.get_node("VBox/Panel").disconnect_node(item["from"],0,item["to"],0) 24 | queue_free() 25 | 26 | func _on_resize_request(new_minsize): 27 | rect_size=new_minsize 28 | 29 | func _on_values_changed(): # refresh the nodecontent variable 30 | nodecontent.clear() 31 | variable =$VBox/HBoxVar/Content.text 32 | type=$VBox/HBoxType/Content.text 33 | operator=$VBox/HBoxOper/Content.text 34 | value=$VBox/HBoxValue/Content.text 35 | nodecontent[variable]={"type":type,"operator":operator,"value":value} 36 | 37 | # emit_signal("nodechanged") # refresh json file 38 | 39 | func updatenode(): 40 | $VBox/HBoxVar/Content.text=variable 41 | $VBox/HBoxType/Content.text=type 42 | $VBox/HBoxOper/Content.text=operator 43 | $VBox/HBoxValue/Content.text=value 44 | _on_values_changed() # rebuild nodecontent variable 45 | 46 | func _on_Singlenode_clicked(): 47 | print(nodecontent) 48 | 49 | func _on_type_selected(index): 50 | if index==0:type="boolean" 51 | if index==1:type="number" 52 | update() 53 | 54 | func _on_operator_selected(index): 55 | if index==0:operator="equal" 56 | if index==1:operator="different" 57 | if index==2:operator="greater" 58 | if index==3:operator="lower" 59 | update() 60 | -------------------------------------------------------------------------------- /DialogPlugin/ConditionNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/Dialogs/ConditionNode.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/Dialogs/textedit.stylebox" type="StyleBox" id=2] 5 | [ext_resource path="res://addons/Dialogs/nodes.stylebox" type="StyleBox" id=3] 6 | [ext_resource path="res://addons/Dialogs/optionbutton.stylebox" type="StyleBox" id=4] 7 | 8 | [node name="ConditionNode" type="GraphNode" groups=[ 9 | "dialognode", 10 | ]] 11 | margin_right = 145.0 12 | margin_bottom = 122.0 13 | rect_min_size = Vector2( 200, 200 ) 14 | rect_clip_content = true 15 | custom_styles/frame = ExtResource( 3 ) 16 | custom_styles/selectedframe = ExtResource( 3 ) 17 | title = "Condition" 18 | show_close = true 19 | resizable = true 20 | slot/0/left_enabled = false 21 | slot/0/left_type = 0 22 | slot/0/left_color = Color( 1, 1, 1, 1 ) 23 | slot/0/right_enabled = false 24 | slot/0/right_type = 0 25 | slot/0/right_color = Color( 1, 1, 1, 1 ) 26 | script = ExtResource( 1 ) 27 | __meta__ = { 28 | "_edit_use_anchors_": false 29 | } 30 | 31 | [node name="VBox" type="VBoxContainer" parent="."] 32 | margin_left = 30.0 33 | margin_top = 30.0 34 | margin_right = 289.0 35 | margin_bottom = 184.0 36 | 37 | [node name="KeyBox" type="HBoxContainer" parent="VBox"] 38 | margin_right = 259.0 39 | margin_bottom = 14.0 40 | 41 | [node name="Label" type="Label" parent="VBox/KeyBox"] 42 | margin_right = 27.0 43 | margin_bottom = 14.0 44 | text = "Key:" 45 | 46 | [node name="Keylabel" type="Label" parent="VBox/KeyBox"] 47 | margin_left = 31.0 48 | margin_right = 31.0 49 | margin_bottom = 14.0 50 | 51 | [node name="HSeparator2" type="HSeparator" parent="VBox"] 52 | margin_top = 18.0 53 | margin_right = 259.0 54 | margin_bottom = 22.0 55 | 56 | [node name="HBoxVar" type="HBoxContainer" parent="VBox"] 57 | margin_top = 26.0 58 | margin_right = 259.0 59 | margin_bottom = 46.0 60 | size_flags_horizontal = 3 61 | 62 | [node name="Label" type="Label" parent="VBox/HBoxVar"] 63 | margin_top = 3.0 64 | margin_right = 55.0 65 | margin_bottom = 17.0 66 | text = "Variable:" 67 | 68 | [node name="Content" type="TextEdit" parent="VBox/HBoxVar"] 69 | margin_left = 59.0 70 | margin_right = 259.0 71 | margin_bottom = 20.0 72 | rect_min_size = Vector2( 200, 20 ) 73 | size_flags_horizontal = 3 74 | size_flags_vertical = 3 75 | custom_styles/normal = ExtResource( 2 ) 76 | 77 | [node name="HBoxType" type="HBoxContainer" parent="VBox"] 78 | margin_top = 50.0 79 | margin_right = 259.0 80 | margin_bottom = 66.0 81 | size_flags_horizontal = 3 82 | 83 | [node name="Label" type="Label" parent="VBox/HBoxType"] 84 | margin_top = 1.0 85 | margin_right = 34.0 86 | margin_bottom = 15.0 87 | text = "Type:" 88 | 89 | [node name="Content" type="OptionButton" parent="VBox/HBoxType"] 90 | margin_left = 38.0 91 | margin_right = 259.0 92 | margin_bottom = 16.0 93 | size_flags_horizontal = 3 94 | custom_styles/hover = ExtResource( 4 ) 95 | custom_styles/pressed = ExtResource( 4 ) 96 | custom_styles/normal = ExtResource( 4 ) 97 | items = [ "boolean", null, false, 0, null, "number", null, false, 1, null ] 98 | selected = 0 99 | 100 | [node name="HBoxOper" type="HBoxContainer" parent="VBox"] 101 | margin_top = 70.0 102 | margin_right = 259.0 103 | margin_bottom = 86.0 104 | size_flags_horizontal = 3 105 | 106 | [node name="Label" type="Label" parent="VBox/HBoxOper"] 107 | margin_top = 1.0 108 | margin_right = 57.0 109 | margin_bottom = 15.0 110 | text = "Operator" 111 | 112 | [node name="Content" type="OptionButton" parent="VBox/HBoxOper"] 113 | margin_left = 61.0 114 | margin_right = 259.0 115 | margin_bottom = 16.0 116 | size_flags_horizontal = 3 117 | custom_styles/hover = ExtResource( 4 ) 118 | custom_styles/pressed = ExtResource( 4 ) 119 | custom_styles/normal = ExtResource( 4 ) 120 | items = [ "equal", null, false, 0, null, "different", null, false, 1, null, "greater", null, false, 2, null, "lower", null, false, 3, null ] 121 | selected = 0 122 | 123 | [node name="HBoxValue" type="HBoxContainer" parent="VBox"] 124 | margin_top = 90.0 125 | margin_right = 259.0 126 | margin_bottom = 110.0 127 | size_flags_horizontal = 3 128 | 129 | [node name="Label" type="Label" parent="VBox/HBoxValue"] 130 | margin_top = 3.0 131 | margin_right = 35.0 132 | margin_bottom = 17.0 133 | text = "Value" 134 | 135 | [node name="Content" type="TextEdit" parent="VBox/HBoxValue"] 136 | margin_left = 39.0 137 | margin_right = 259.0 138 | margin_bottom = 20.0 139 | rect_min_size = Vector2( 200, 20 ) 140 | size_flags_horizontal = 3 141 | size_flags_vertical = 3 142 | custom_styles/normal = ExtResource( 2 ) 143 | 144 | [node name="HSeparator" type="HSeparator" parent="VBox"] 145 | margin_top = 114.0 146 | margin_right = 259.0 147 | margin_bottom = 118.0 148 | 149 | [node name="Nexttitle" type="Label" parent="VBox"] 150 | margin_top = 122.0 151 | margin_right = 259.0 152 | margin_bottom = 136.0 153 | text = "Next node:" 154 | 155 | [node name="Nextnode" type="Label" parent="VBox"] 156 | margin_top = 140.0 157 | margin_right = 259.0 158 | margin_bottom = 154.0 159 | 160 | [connection signal="close_request" from="." to="." method="_close_request"] 161 | [connection signal="raise_request" from="." to="." method="_on_Singlenode_clicked"] 162 | [connection signal="resize_request" from="." to="." method="_on_resize_request"] 163 | [connection signal="text_changed" from="VBox/HBoxVar/Content" to="." method="_on_values_changed"] 164 | [connection signal="item_selected" from="VBox/HBoxType/Content" to="." method="_on_type_selected"] 165 | [connection signal="item_selected" from="VBox/HBoxOper/Content" to="." method="_on_operator_selected"] 166 | [connection signal="text_changed" from="VBox/HBoxValue/Content" to="." method="_on_values_changed"] 167 | -------------------------------------------------------------------------------- /DialogPlugin/DialogNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | signal nodechanged 4 | 5 | var nodeid:String="" # stores unique ID of node 6 | var nodecontent:Dictionary={"en":"", "fr":""} # stores content of node in all languages 7 | onready var Editor:Object= get_parent().get_parent().get_parent() 8 | 9 | #func _ready(): 10 | # connect("nodechanged",Editor, "refreshjson") 11 | 12 | func _close_request(): 13 | var connections:Array=Editor.get_node("VBox/Panel").get_connection_list() 14 | for item in connections: 15 | if item["to"]==self.name or item["from"]==self.name: 16 | Editor.get_node("VBox/Panel").disconnect_node(item["from"],0,item["to"],0) 17 | queue_free() 18 | 19 | func _on_resize_request(new_minsize): 20 | rect_size=new_minsize 21 | 22 | func _on_Content_text_changed(): 23 | nodecontent[Editor.language]=$VBox/Content.text 24 | 25 | # emit_signal("nodechanged") # refresh json file 26 | 27 | func _on_Singlenode_clicked(): 28 | print(nodecontent) 29 | -------------------------------------------------------------------------------- /DialogPlugin/DialogNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/Dialogs/DialogNode.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/Dialogs/nodes.stylebox" type="StyleBox" id=2] 5 | [ext_resource path="res://addons/Dialogs/textedit.stylebox" type="StyleBox" id=3] 6 | 7 | [node name="Dialognode" type="GraphNode" groups=[ 8 | "dialognode", 9 | ]] 10 | margin_right = 275.0 11 | margin_bottom = 260.0 12 | rect_min_size = Vector2( 300, 200 ) 13 | rect_clip_content = true 14 | custom_styles/frame = ExtResource( 2 ) 15 | custom_styles/selectedframe = ExtResource( 2 ) 16 | title = "Dialogue" 17 | show_close = true 18 | resizable = true 19 | slot/0/left_enabled = false 20 | slot/0/left_type = 0 21 | slot/0/left_color = Color( 1, 1, 1, 1 ) 22 | slot/0/right_enabled = false 23 | slot/0/right_type = 0 24 | slot/0/right_color = Color( 1, 1, 1, 1 ) 25 | script = ExtResource( 1 ) 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="VBox" type="VBoxContainer" parent="."] 31 | margin_left = 30.0 32 | margin_top = 30.0 33 | margin_right = 270.0 34 | margin_bottom = 250.0 35 | 36 | [node name="KeyBox" type="HBoxContainer" parent="VBox"] 37 | margin_right = 240.0 38 | margin_bottom = 14.0 39 | 40 | [node name="Label" type="Label" parent="VBox/KeyBox"] 41 | margin_right = 27.0 42 | margin_bottom = 14.0 43 | text = "Key:" 44 | 45 | [node name="Keylabel" type="Label" parent="VBox/KeyBox"] 46 | margin_left = 31.0 47 | margin_right = 31.0 48 | margin_bottom = 14.0 49 | 50 | [node name="HSeparator2" type="HSeparator" parent="VBox"] 51 | margin_top = 18.0 52 | margin_right = 240.0 53 | margin_bottom = 22.0 54 | 55 | [node name="Content" type="TextEdit" parent="VBox"] 56 | margin_top = 26.0 57 | margin_right = 240.0 58 | margin_bottom = 176.0 59 | rect_min_size = Vector2( 100, 150 ) 60 | size_flags_vertical = 3 61 | custom_styles/normal = ExtResource( 3 ) 62 | wrap_enabled = true 63 | 64 | [node name="HSeparator" type="HSeparator" parent="VBox"] 65 | margin_top = 180.0 66 | margin_right = 240.0 67 | margin_bottom = 184.0 68 | 69 | [node name="Nexttitle" type="Label" parent="VBox"] 70 | margin_top = 188.0 71 | margin_right = 240.0 72 | margin_bottom = 202.0 73 | text = "Next node:" 74 | 75 | [node name="Nextnode" type="Label" parent="VBox"] 76 | margin_top = 206.0 77 | margin_right = 240.0 78 | margin_bottom = 220.0 79 | 80 | [connection signal="close_request" from="." to="." method="_close_request"] 81 | [connection signal="raise_request" from="." to="." method="_on_Singlenode_clicked"] 82 | [connection signal="resize_request" from="." to="." method="_on_resize_request"] 83 | [connection signal="text_changed" from="VBox/Content" to="." method="_on_Content_text_changed"] 84 | -------------------------------------------------------------------------------- /DialogPlugin/Dialogs.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | const MainPanel = preload("res://addons/Dialogs/Mainpanel.tscn") 5 | 6 | var main_panel_instance 7 | 8 | func _enter_tree(): 9 | main_panel_instance = MainPanel.instance() 10 | # Add the main panel to the editor's main viewport. 11 | get_editor_interface().get_editor_viewport().add_child(main_panel_instance) 12 | # Hide the main panel. Very much required. 13 | make_visible(false) 14 | 15 | 16 | func _exit_tree(): 17 | if main_panel_instance: 18 | main_panel_instance.queue_free() 19 | 20 | 21 | func has_main_screen(): 22 | return true 23 | 24 | 25 | func make_visible(visible): 26 | if main_panel_instance: 27 | main_panel_instance.visible = visible 28 | 29 | 30 | func get_plugin_name(): 31 | return "Dialogs" 32 | 33 | 34 | func get_plugin_icon(): 35 | # Must return some kind of Texture for the icon. 36 | return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons") 37 | -------------------------------------------------------------------------------- /DialogPlugin/Mainpanel.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Panel 3 | 4 | # Json structure: 5 | # level 1) dictionary with "root", "__editor" and "uuid" (uuid = unique dialogue key) 6 | # level 2) dictionary with "text", "next", "choices", "conditions", "signals", "name", "portrait" 7 | # level 3): 8 | # "text" contains a dictionary of type {"en":"blabla"} 9 | # "signals" contains a dictionaries of type {"signalname":{"Number":"2"}} 10 | # "choices" contains an array with dictionaries of type {"text":"blabla","next":"uuid"} 11 | # "conditions" contains an array with dictionaries of type {"variable":{"value":false,"operator":"equal","type":"boolean"},"next":"uuid"} 12 | # "__editor" contains data for editor display only 13 | 14 | var open_dialog=null 15 | var save_dialog=null 16 | var jsonfile:Dictionary 17 | var dialognode=preload("res://addons/Dialogs/DialogNode.tscn") 18 | var ConditionNode=preload("res://addons/Dialogs/ConditionNode.tscn") 19 | var ChoiceNode=preload("res://addons/Dialogs/ChoiceNode.tscn") 20 | var SignalNode=preload("res://addons/Dialogs/SignalNode.tscn") 21 | var language:String="en" 22 | 23 | onready var nodepanel=$VBox/Panel 24 | 25 | signal jsonupdated 26 | signal graphcleared 27 | 28 | func _ready(): 29 | 30 | # create open file menu 31 | open_dialog = FileDialog.new() 32 | open_dialog.window_title = "Open json" 33 | open_dialog.add_filter("*.json ; json files") 34 | open_dialog.mode = FileDialog.MODE_OPEN_FILE 35 | open_dialog.connect("file_selected", self, "_on_file_selected") 36 | add_child(open_dialog) 37 | 38 | # create save file menu 39 | save_dialog = FileDialog.new() 40 | save_dialog.window_title = "Save json" 41 | save_dialog.add_filter("*.json ; json files") 42 | save_dialog.mode = FileDialog.MODE_SAVE_FILE 43 | save_dialog.connect("file_selected", self, "_on_savefile_selected") 44 | add_child(save_dialog) 45 | 46 | showlang() 47 | 48 | #func _exit_tree(): 49 | # if open_dialog : open_dialog.queue_free() 50 | # if save_dialog : save_dialog.queue_free() 51 | 52 | # ------------------------- 53 | # --- File dialogs --- 54 | # ------------------------- 55 | 56 | # open file button => popup load file dialog 57 | func _on_Openbutton_pressed(): 58 | open_dialog.popup_centered_ratio() 59 | 60 | # load file and put content intoo jsonfile variable 61 | func _on_file_selected(path): 62 | $VBox/HBoxMenu/Filename.text=str(path) 63 | cleargraph() 64 | jsonfile=get_json(path) 65 | displaynodes() 66 | 67 | # save file button => popup save file dialog 68 | func _on_Savebutton_pressed(): 69 | save_dialog.popup_centered_ratio() 70 | 71 | # load file and put content intoo jsonfile variable 72 | func _on_savefile_selected(path): 73 | $VBox/HBoxMenu/Filename.text=str(path) 74 | refreshjson() 75 | savejson(path) 76 | 77 | func savejson(path): 78 | var file = File.new() 79 | file.open(path, File.WRITE) 80 | var savefile=JSON.print(jsonfile) 81 | file.store_string(savefile) 82 | file.close() 83 | 84 | # ------------------------- 85 | # --- New file Creation --- 86 | # ------------------------- 87 | 88 | func Newjson(): 89 | jsonfile.clear() 90 | 91 | # Clear editor 92 | cleargraph() 93 | $VBox/HBoxMenu/Filename.text="[unsaved]" 94 | 95 | # Create and place a root node 96 | var newnode:GraphNode=dialognode.instance() 97 | $VBox/Panel.add_child(newnode) 98 | newnode.title="Root" 99 | newnode.nodeid="root" 100 | newnode.get_node("VBox/KeyBox").visible=false 101 | newnode.resizable=false 102 | newnode.show_close=false 103 | newnode.get_node("VBox/Content").visible=false 104 | newnode.set_slot(0,false,0,Color.turquoise,true,0,Color.turquoise) 105 | newnode.offset=Vector2(50,50) 106 | 107 | # Create the json based on current status : refreshjson() 108 | refreshjson() 109 | 110 | # --------------------- 111 | # --- Node Creation --- 112 | # --------------------- 113 | 114 | func checkifroot(): 115 | var nodesingraph:Array 116 | for child in $VBox/Panel.get_children(): 117 | if child.is_in_group("dialognode"): nodesingraph.push_back(child.title) 118 | if not nodesingraph.has("Root"): Newjson() 119 | 120 | func NewDialog(): # Create and place a dialogue node 121 | checkifroot() 122 | yield(get_tree().create_timer(0.2),"timeout") 123 | var uiid=IDGen.v4() 124 | var newnode:GraphNode=dialognode.instance() 125 | $VBox/Panel.add_child(newnode) 126 | newnode.title="Dialogue" 127 | newnode.nodeid=uiid 128 | newnode.nodecontent={"en":"","fr":""} 129 | newnode.get_node("VBox/KeyBox/Keylabel").text=uiid 130 | newnode.resizable=false 131 | newnode.show_close=true 132 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 133 | newnode.offset=$VBox/Panel.scroll_offset+Vector2(100,50) 134 | refreshjson() 135 | 136 | func NewCondition(): # Create and place a condition node 137 | checkifroot() 138 | yield(get_tree().create_timer(0.2),"timeout") 139 | var uiid=IDGen.v4() 140 | var newnode:GraphNode=ConditionNode.instance() 141 | $VBox/Panel.add_child(newnode) 142 | newnode.nodeid=uiid 143 | newnode.get_node("VBox/KeyBox/Keylabel").text=uiid 144 | newnode.resizable=false 145 | newnode.show_close=true 146 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 147 | newnode.offset=$VBox/Panel.scroll_offset+Vector2(50,50) 148 | refreshjson() 149 | 150 | func NewChoice(): # Create and place a choice node 151 | checkifroot() 152 | yield(get_tree().create_timer(0.2),"timeout") 153 | var uiid=IDGen.v4() 154 | var newnode:GraphNode=ChoiceNode.instance() 155 | $VBox/Panel.add_child(newnode) 156 | newnode.nodeid=uiid 157 | newnode.get_node("VBox/KeyBox/Keylabel").text=uiid 158 | newnode.resizable=false 159 | newnode.show_close=true 160 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 161 | newnode.offset=$VBox/Panel.scroll_offset+Vector2(50,50) 162 | refreshjson() 163 | 164 | func NewSignal(): # Create and place a condition node 165 | checkifroot() 166 | yield(get_tree().create_timer(0.2),"timeout") 167 | var uiid=IDGen.v4() 168 | var newnode:GraphNode=SignalNode.instance() 169 | $VBox/Panel.add_child(newnode) 170 | newnode.nodeid=uiid 171 | newnode.get_node("VBox/KeyBox/Keylabel").text=uiid 172 | newnode.resizable=false 173 | newnode.show_close=true 174 | newnode.set_slot(0,true,0,Color.turquoise,false,0,Color.turquoise) 175 | newnode.offset=$VBox/Panel.scroll_offset+Vector2(50,50) 176 | refreshjson() 177 | 178 | # -------------------- 179 | # --- Json Update ---- 180 | # -------------------- 181 | 182 | func refreshjson(): # json structure : https://github.com/Levrault/LE-dialogue-editor/wiki/Json 183 | 184 | if $VBox/Panel.get_node(findnodewithID("root"))==null:return # no root node 185 | var newjson:Dictionary 186 | var nodecode # for "__editor" data 187 | 188 | # create "__editor" key 189 | var editorstructure:Dictionary 190 | var rootoffset= [$VBox/Panel.get_node(findnodewithID("root")).offset.x, $VBox/Panel.get_node(findnodewithID("root")).offset.y] 191 | editorstructure["root"]={"uuid":"root","offset":rootoffset} 192 | editorstructure["dialogues"]=[] 193 | editorstructure["signals"]=[] 194 | editorstructure["conditions"]=[] 195 | editorstructure["choices"]=[] 196 | 197 | # get each root & dialogue node and store its content 198 | for node in $VBox/Panel.get_children(): 199 | if node.is_in_group("dialognode"): # only select nodes in the graphedit 200 | # take care of root node 201 | if node.title=="Root": 202 | var nodecontent:Dictionary 203 | var connectedcontent:Dictionary 204 | var nextnode={"next":getnext(node)} 205 | if nextnode.next !="": newjson["root"]={"next":getnext(node)} 206 | else: # check for conditions 207 | var connections:Array=$VBox/Panel.get_connection_list() 208 | for item in connections: 209 | if item["from"]==findnodewithID(node.nodeid): 210 | var attachednode:Object=$VBox/Panel.get_node(item["to"]) 211 | if attachednode.title=="Condition": 212 | if nodecontent.has("conditions"):nodecontent["conditions"].push_back(getconditions(attachednode)) 213 | else: nodecontent["conditions"]=[getconditions(attachednode)] 214 | nodecode={"uuid":attachednode.nodeid,"offset":getoffset(attachednode)} 215 | nodecode["parent"]=node.nodeid 216 | nodecode["data"]=attachednode.nodecontent 217 | nodecode["next"]=getnext(attachednode) 218 | editorstructure.conditions.push_back(nodecode) 219 | newjson["root"]=nodecontent 220 | 221 | # take care of dialogue nodes and their attachements 222 | if node.title=="Dialogue": 223 | var nodecontent:Dictionary 224 | var connectedcontent:Dictionary 225 | # determine dialogue text and insert it into json 226 | nodecontent["text"]=node.nodecontent 227 | nodecode={"uuid":node.nodeid,"offset":getoffset(node)} 228 | nodecode["parent"]=getparent(node) 229 | editorstructure.dialogues.push_back(nodecode) 230 | 231 | # determine if there are conditions / choices / signals attached 232 | var connections:Array=$VBox/Panel.get_connection_list() 233 | for item in connections: 234 | if item["from"]==findnodewithID(node.nodeid): 235 | var attachednode:Object=$VBox/Panel.get_node(item["to"]) 236 | # get type of attached node 237 | var connectedtype=attachednode.title 238 | 239 | # create the content of the attached node 240 | if connectedtype=="Dialogue": 241 | nodecontent["next"]=getnext(node) 242 | nodecode={"uuid":attachednode.nodeid,"offset":getoffset(attachednode)} 243 | nodecode["parent"]=node.nodeid 244 | editorstructure.dialogues.push_back(nodecode) 245 | 246 | if connectedtype=="Condition": 247 | if nodecontent.has("conditions"):nodecontent["conditions"].push_back(getconditions(attachednode)) 248 | else: nodecontent["conditions"]=[getconditions(attachednode)] 249 | nodecode={"uuid":attachednode.nodeid,"offset":getoffset(attachednode)} 250 | nodecode["parent"]=getparent(attachednode) 251 | nodecode["data"]=attachednode.nodecontent 252 | nodecode["next"]=getnext(attachednode) 253 | editorstructure.conditions.push_back(nodecode) 254 | 255 | if connectedtype=="Choice": 256 | if nodecontent.has("choices"):nodecontent["choices"].push_back(getchoices(attachednode)) 257 | else: nodecontent["choices"]=[getchoices(attachednode)] 258 | nodecode={"uuid":attachednode.nodeid,"offset":getoffset(attachednode)} 259 | nodecode["parent"]=getparent(attachednode) 260 | nodecode["text"]=attachednode.nodecontent # contains all languages 261 | nodecode["next"]=getnext(attachednode) 262 | editorstructure.choices.push_back(nodecode) 263 | 264 | if connectedtype=="Signal": 265 | nodecontent["signals"]=getsignal(attachednode) 266 | nodecode={"uuid":attachednode.nodeid,"offset":getoffset(attachednode)} 267 | nodecode["parent"]=getparent(attachednode) 268 | nodecode["data"]=attachednode.nodecontent 269 | # nodecode["next"]=getnext(attachednode) 270 | editorstructure.signals.push_back(nodecode) 271 | 272 | newjson[node.nodeid]=nodecontent 273 | 274 | newjson["__editor"]=editorstructure # TO DO : track and delete doubles 275 | 276 | jsonfile=newjson 277 | 278 | emit_signal("jsonupdated") 279 | 280 | func getconditions(node)->Dictionary: # get condition dictionary in a node 281 | var condition:Dictionary={} 282 | if node.title=="Condition": 283 | condition[node.variable]={"type":node.type,"operator":node.operator,"value":node.value} 284 | condition["next"]=getnext(node) 285 | return condition 286 | 287 | func getchoices(node)->Dictionary: # get choice dictionary in a node 288 | var choice:Dictionary={} 289 | if node.title=="Choice": 290 | choice["text"]=node.nodecontent 291 | choice["next"]=getnext(node) 292 | return choice 293 | 294 | func getsignal(node)->Dictionary: # get signal dictionary in a node 295 | var signalcode:Dictionary={} 296 | if node.title=="Signal": 297 | signalcode[node.variable]={"type":node.type,"value":node.value} 298 | # signalcode["next"]=getnext(node) 299 | return signalcode 300 | 301 | func getoffset(node): # get attached node's offset 302 | var offset=$VBox/Panel.get_node(findnodewithID(node.nodeid)).offset 303 | var offsetarray=[offset.x,offset.y] 304 | return (offsetarray) 305 | 306 | func getparent(node): # get a node's parent id 307 | var connections:Array=$VBox/Panel.get_connection_list() 308 | for item in connections: 309 | if item["to"]==findnodewithID(node.nodeid): 310 | var attachednode:Object=$VBox/Panel.get_node(item["from"]) 311 | return attachednode.nodeid 312 | 313 | func getnext(node): # get next node's id 314 | var connections:Array=$VBox/Panel.get_connection_list() 315 | for item in connections: 316 | if item["from"]==findnodewithID(node.nodeid): 317 | var attachednode:Object=$VBox/Panel.get_node(item["to"]) 318 | if attachednode.title=="Dialogue":return attachednode.nodeid 319 | return "" 320 | 321 | # ----------------------- 322 | # --- Change Language --- 323 | # ----------------------- 324 | 325 | # highlight selected language 326 | func showlang(): 327 | if language =="fr": 328 | $"VBox/HBoxMenu/Lang-fr".set("custom_colors/font_color",Color.white) 329 | $"VBox/HBoxMenu/Lang-en".set("custom_colors/font_color",Color.darkgray) 330 | if language =="en": 331 | $"VBox/HBoxMenu/Lang-fr".set("custom_colors/font_color",Color.darkgray) 332 | $"VBox/HBoxMenu/Lang-en".set("custom_colors/font_color",Color.white) 333 | 334 | func langchange(lang): 335 | language=lang 336 | showlang() 337 | yield(get_tree().create_timer(0.5),"timeout") 338 | refreshjson() 339 | yield(get_tree().create_timer(0.5),"timeout") 340 | cleargraph() 341 | yield(get_tree().create_timer(0.5),"timeout") 342 | displaynodes() 343 | 344 | # -------------------- 345 | # --- Node Display --- 346 | # -------------------- 347 | 348 | func displaynodes(): # NB: jsonfile contains the whole file 349 | 350 | var jsonstruct:Dictionary=jsonfile["__editor"] # contains editor information 351 | 352 | # create root node 353 | var newnode:GraphNode=dialognode.instance() 354 | $VBox/Panel.add_child(newnode) 355 | newnode.title="Root" 356 | newnode.nodeid="root" 357 | newnode.get_node("VBox/KeyBox").visible=false 358 | newnode.resizable=false 359 | newnode.show_close=false 360 | newnode.get_node("VBox/Content").visible=false 361 | newnode.set_slot(0,false,0,Color.turquoise,true,0,Color.turquoise) 362 | var offset=Vector2(jsonstruct["root"]["offset"][0],jsonstruct["root"]["offset"][1]) 363 | newnode.offset=offset 364 | 365 | # create dialogue nodes 366 | for dialog in jsonstruct["dialogues"]: 367 | newnode=dialognode.instance() 368 | $VBox/Panel.add_child(newnode) 369 | var uuid=dialog["uuid"] 370 | newnode.nodeid=uuid 371 | newnode.nodecontent["fr"]=jsonfile[uuid]["text"]["fr"] # contains all languages 372 | newnode.nodecontent["en"]=jsonfile[uuid]["text"]["en"] # contains all languages 373 | newnode.title="Dialogue" 374 | newnode.get_node("VBox/KeyBox/Keylabel").text=uuid 375 | newnode.offset=Vector2(dialog["offset"][0],dialog["offset"][1]) 376 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 377 | newnode.get_node("VBox/Content").set_text(jsonfile[uuid]["text"][language]) 378 | # check if there is a "next" node and display its uiid 379 | if jsonfile[uuid].has("next"):newnode.get_node("VBox/Nextnode").text=str(jsonfile[uuid]["next"]) 380 | else: newnode.get_node("VBox/Nextnode").text="" 381 | 382 | # connect root node 383 | var rootnode=$VBox/Panel.get_node(findnodewithID("root")) 384 | if jsonfile["root"].has("next"): 385 | connectroot() # connect root to dialogue node 386 | rootnode.get_node("VBox/Nextnode").text=jsonfile["root"]["next"] 387 | 388 | # create condition nodes 389 | for condition in jsonstruct["conditions"]: 390 | newnode=ConditionNode.instance() 391 | $VBox/Panel.add_child(newnode) 392 | newnode.nodeid=condition["uuid"] 393 | newnode.get_node("VBox/KeyBox/Keylabel").text=condition["uuid"] 394 | newnode.offset=Vector2(condition["offset"][0],condition["offset"][1]) 395 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 396 | if condition.has("data"): 397 | for variable in condition["data"]: 398 | newnode.variable = variable 399 | newnode.type = condition.data[variable].type 400 | newnode.operator = condition.data[variable].operator 401 | newnode.value = condition.data[variable].value 402 | newnode.updatenode() 403 | # connect to its parent node 404 | if condition.has("parent"): 405 | var nodefrom=findnodewithID(condition["parent"]) 406 | var nodeto=findnodewithID(condition["uuid"]) 407 | if nodefrom=="" or nodeto=="": print("nodes are not recognized: nodefrom="+str(nodefrom)+" - nodeto=" + str(nodeto)) 408 | else: 409 | $VBox/Panel.connect_node(nodefrom,0,nodeto,0) 410 | # display next node and connect it 411 | if condition.has("next"): 412 | newnode.get_node("VBox/Nextnode").text=condition["next"] 413 | var nodefrom=findnodewithID(condition["uuid"]) 414 | var nodeto=findnodewithID(condition["next"]) 415 | if nodefrom=="" or nodeto=="": print("nodes are not recognized: nodefrom="+str(nodefrom)+" - nodeto=" + str(nodeto)) 416 | else: 417 | $VBox/Panel.connect_node(nodefrom,0,nodeto,0) 418 | 419 | # create choice nodes 420 | for choice in jsonstruct["choices"]: 421 | newnode=ChoiceNode.instance() 422 | $VBox/Panel.add_child(newnode) 423 | newnode.nodeid=choice["uuid"] 424 | newnode.get_node("VBox/KeyBox/Keylabel").text=choice["uuid"] 425 | newnode.offset=Vector2(choice["offset"][0],choice["offset"][1]) 426 | newnode.set_slot(0,true,0,Color.turquoise,true,0,Color.turquoise) 427 | newnode.nodecontent["fr"]=choice["text"]["fr"] # contains fr language 428 | newnode.nodecontent["en"]=choice["text"]["en"] # contains en language 429 | newnode.updatenode() 430 | # connect to its parent node 431 | if choice.has("parent"): 432 | var nodefrom=findnodewithID(choice["parent"]) 433 | var nodeto=findnodewithID(choice["uuid"]) 434 | if nodefrom=="" or nodeto=="": print("nodes are not recognized: nodefrom="+str(nodefrom)+" - nodeto=" + str(nodeto)) 435 | else: 436 | $VBox/Panel.connect_node(nodefrom,0,nodeto,0) 437 | # display next node 438 | if choice["next"]:newnode.get_node("VBox/Nextnode").text=choice["next"] 439 | 440 | # create signal nodes 441 | for _signal in jsonstruct["signals"]: 442 | newnode=SignalNode.instance() 443 | $VBox/Panel.add_child(newnode) 444 | newnode.nodeid=_signal["uuid"] 445 | newnode.get_node("VBox/KeyBox/Keylabel").text=_signal["uuid"] 446 | newnode.offset=Vector2(_signal["offset"][0],_signal["offset"][1]) 447 | newnode.set_slot(0,true,0,Color.turquoise,false,0,Color.turquoise) 448 | for variable in _signal["data"]: 449 | newnode.variable = variable 450 | newnode.type = _signal.data[variable].type 451 | newnode.value = _signal.data[variable].value 452 | newnode.updatenode() 453 | # connect to its parent node 454 | if _signal.has("parent"): 455 | var nodefrom=findnodewithID(_signal["parent"]) 456 | var nodeto=findnodewithID(_signal["uuid"]) 457 | if nodefrom=="" or nodeto=="": print("nodes are not recognized: nodefrom="+str(nodefrom)+" - nodeto=" + str(nodeto)) 458 | else: 459 | $VBox/Panel.connect_node(nodefrom,0,nodeto,0) 460 | # display next node 461 | # if condition["next"]:newnode.get_node("VBox/Nextnode").text=condition["next"] 462 | 463 | # For each dialog node identify "next" nodes and connect them 464 | for node in jsonfile.keys(): 465 | if node!="__editor": # only use dialogue nodes and root node 466 | if jsonfile[node].has("next"): 467 | var nodefrom=findnodewithID(node) 468 | var nodeto=findnodewithID(jsonfile[node]["next"]) 469 | if nodefrom=="" or nodeto=="":print("node to connect not found") 470 | else:nodepanel.connect_node(nodefrom,0,nodeto,0) 471 | 472 | # For each dialog node identify "parent" nodes and connect them 473 | for node in jsonfile.keys(): 474 | if node=="__editor": 475 | # if jsonfile[node].dialogues.size()>0: 476 | for i in jsonfile["__editor"]["dialogues"]: # dialogues contains an array 477 | var nodefrom=findnodewithID(i["parent"]) 478 | var nodeto=findnodewithID(i["uuid"]) 479 | if nodefrom=="" or nodeto=="":print("node to connect not found") 480 | else:nodepanel.connect_node(nodefrom,0,nodeto,0) 481 | 482 | func connectroot(): 483 | var nodefrom=findnodewithID("root") 484 | var nodeto=findnodewithID(jsonfile["root"]["next"]) 485 | if nodefrom=="" or nodeto=="": print("nodes are not recognized: nodefrom="+str(nodefrom)+" - nodeto=" + str(nodeto)) 486 | else: 487 | nodepanel.connect_node(nodefrom,0,nodeto,0) 488 | # $VBox/Panel.update() 489 | 490 | func findnodewithID(ID): 491 | for node in $VBox/Panel.get_children(): 492 | if node.is_in_group("dialognode"): 493 | if node.nodeid==ID: return node.name 494 | return "" 495 | 496 | # ------------------- 497 | # --- JSON parser --- 498 | # ------------------- 499 | 500 | func get_json(file_path: String) -> Dictionary: 501 | var file := File.new() 502 | 503 | if file.open(file_path, file.READ) != OK: 504 | print("get_json: file cannot been read") 505 | return {} 506 | 507 | var text_content := file.get_as_text() 508 | file.close() 509 | var data_parse = JSON.parse(text_content) 510 | if data_parse.error != OK: 511 | print("get_json: error while parsing") 512 | return {} 513 | 514 | # data_parse.result.erase("__editor") 515 | return data_parse.result 516 | 517 | # ------------------------------ 518 | # --- Graph Editor Functions --- 519 | # ------------------------------ 520 | 521 | func _on_Panel_connection_request(from, from_slot, to, to_slot): 522 | $VBox/Panel.connect_node(from, from_slot, to, to_slot) 523 | if $VBox/Panel.get_node(to).title=="Dialogue" or $VBox/Panel.get_node(to).title=="Signal": 524 | $VBox/Panel.get_node(from+"/VBox/Nextnode").text=$VBox/Panel.get_node(to).nodeid 525 | refreshjson() 526 | 527 | func _on_Panel_disconnection_request(from, from_slot, to, to_slot): 528 | $VBox/Panel.disconnect_node(from, from_slot, to, to_slot) 529 | $VBox/Panel.get_node(from+"/VBox/Nextnode").text="" 530 | refreshjson() 531 | 532 | func cleargraph(): 533 | $VBox/Panel.clear_connections() 534 | if $VBox/Panel.get_child_count()>0: # clear all existing nodes 535 | for child in $VBox/Panel.get_children(): 536 | if child.is_in_group("dialognode"): 537 | child.queue_free() 538 | emit_signal("graphcleared") 539 | 540 | func printjson(): 541 | print(JSON.print(jsonfile)) 542 | # for node in $VBox/Panel.get_children(): 543 | # if node.is_in_group("dialognode"): 544 | # print (node.name) 545 | print("----") 546 | -------------------------------------------------------------------------------- /DialogPlugin/Mainpanel.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/Dialogs/Mainpanel.gd" type="Script" id=1] 4 | 5 | [sub_resource type="StyleBoxLine" id=1] 6 | color = Color( 1, 0, 0, 1 ) 7 | grow_begin = 0.0 8 | grow_end = 5.0 9 | thickness = 3 10 | vertical = true 11 | 12 | [sub_resource type="StyleBoxLine" id=2] 13 | color = Color( 1, 0, 0, 1 ) 14 | thickness = 3 15 | 16 | [node name="DialogEditor" type="Panel"] 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | rect_min_size = Vector2( 1280, 720 ) 20 | size_flags_vertical = 3 21 | script = ExtResource( 1 ) 22 | __meta__ = { 23 | "_edit_lock_": true, 24 | "_edit_use_anchors_": false 25 | } 26 | 27 | [node name="VBox" type="VBoxContainer" parent="."] 28 | anchor_right = 1.0 29 | anchor_bottom = 1.0 30 | __meta__ = { 31 | "_edit_use_anchors_": false 32 | } 33 | 34 | [node name="HBoxMenu" type="HBoxContainer" parent="VBox"] 35 | margin_right = 1280.0 36 | margin_bottom = 20.0 37 | 38 | [node name="Title" type="Label" parent="VBox/HBoxMenu"] 39 | margin_top = 3.0 40 | margin_right = 104.0 41 | margin_bottom = 17.0 42 | text = "DIALOG EDITOR" 43 | __meta__ = { 44 | "_edit_use_anchors_": false 45 | } 46 | 47 | [node name="VSeparator" type="VSeparator" parent="VBox/HBoxMenu"] 48 | margin_left = 108.0 49 | margin_right = 112.0 50 | margin_bottom = 20.0 51 | custom_styles/separator = SubResource( 1 ) 52 | 53 | [node name="Openbutton" type="Button" parent="VBox/HBoxMenu"] 54 | margin_left = 116.0 55 | margin_right = 187.0 56 | margin_bottom = 20.0 57 | text = "Open file" 58 | 59 | [node name="Savebutton" type="Button" parent="VBox/HBoxMenu"] 60 | margin_left = 191.0 61 | margin_right = 256.0 62 | margin_bottom = 20.0 63 | text = "Save file" 64 | 65 | [node name="Newjson" type="Button" parent="VBox/HBoxMenu"] 66 | margin_left = 260.0 67 | margin_right = 324.0 68 | margin_bottom = 20.0 69 | text = "New file" 70 | 71 | [node name="CurrentFile" type="Label" parent="VBox/HBoxMenu"] 72 | margin_left = 328.0 73 | margin_top = 3.0 74 | margin_right = 407.0 75 | margin_bottom = 17.0 76 | text = "Current file: " 77 | 78 | [node name="Filename" type="Label" parent="VBox/HBoxMenu"] 79 | margin_left = 411.0 80 | margin_top = 3.0 81 | margin_right = 451.0 82 | margin_bottom = 17.0 83 | text = "[none]" 84 | 85 | [node name="VSeparator2" type="VSeparator" parent="VBox/HBoxMenu"] 86 | margin_left = 455.0 87 | margin_right = 459.0 88 | margin_bottom = 20.0 89 | custom_styles/separator = SubResource( 1 ) 90 | 91 | [node name="Language" type="Label" parent="VBox/HBoxMenu"] 92 | margin_left = 463.0 93 | margin_top = 3.0 94 | margin_right = 526.0 95 | margin_bottom = 17.0 96 | text = "Language:" 97 | 98 | [node name="Lang-en" type="Button" parent="VBox/HBoxMenu"] 99 | margin_left = 530.0 100 | margin_right = 558.0 101 | margin_bottom = 20.0 102 | custom_colors/font_color = Color( 1, 1, 1, 1 ) 103 | text = "en" 104 | 105 | [node name="Lang-fr" type="Button" parent="VBox/HBoxMenu"] 106 | margin_left = 562.0 107 | margin_right = 583.0 108 | margin_bottom = 20.0 109 | custom_colors/font_color = Color( 0.66, 0.66, 0.66, 1 ) 110 | text = "fr" 111 | 112 | [node name="VSeparator3" type="VSeparator" parent="VBox/HBoxMenu"] 113 | margin_left = 587.0 114 | margin_right = 591.0 115 | margin_bottom = 20.0 116 | custom_styles/separator = SubResource( 1 ) 117 | 118 | [node name="Dialog" type="Button" parent="VBox/HBoxMenu"] 119 | margin_left = 595.0 120 | margin_right = 647.0 121 | margin_bottom = 20.0 122 | text = "Dialog" 123 | 124 | [node name="Condition" type="Button" parent="VBox/HBoxMenu"] 125 | margin_left = 651.0 126 | margin_right = 724.0 127 | margin_bottom = 20.0 128 | text = "Condition" 129 | 130 | [node name="Choice" type="Button" parent="VBox/HBoxMenu"] 131 | margin_left = 728.0 132 | margin_right = 783.0 133 | margin_bottom = 20.0 134 | text = "Choice" 135 | 136 | [node name="Signal" type="Button" parent="VBox/HBoxMenu"] 137 | margin_left = 787.0 138 | margin_right = 836.0 139 | margin_bottom = 20.0 140 | text = "Signal" 141 | 142 | [node name="VSeparator4" type="VSeparator" parent="VBox/HBoxMenu"] 143 | margin_left = 840.0 144 | margin_right = 844.0 145 | margin_bottom = 20.0 146 | custom_styles/separator = SubResource( 1 ) 147 | 148 | [node name="Print" type="Button" parent="VBox/HBoxMenu"] 149 | margin_left = 848.0 150 | margin_right = 890.0 151 | margin_bottom = 20.0 152 | text = "Print" 153 | 154 | [node name="Refresh" type="Button" parent="VBox/HBoxMenu"] 155 | margin_left = 894.0 156 | margin_right = 954.0 157 | margin_bottom = 20.0 158 | text = "Refresh" 159 | 160 | [node name="Clear" type="Button" parent="VBox/HBoxMenu"] 161 | margin_left = 958.0 162 | margin_right = 1002.0 163 | margin_bottom = 20.0 164 | text = "Clear" 165 | 166 | [node name="HSeparator" type="HSeparator" parent="VBox"] 167 | margin_top = 24.0 168 | margin_right = 1280.0 169 | margin_bottom = 28.0 170 | custom_styles/separator = SubResource( 2 ) 171 | 172 | [node name="Panel" type="GraphEdit" parent="VBox"] 173 | margin_top = 32.0 174 | margin_right = 1280.0 175 | margin_bottom = 720.0 176 | size_flags_horizontal = 3 177 | size_flags_vertical = 3 178 | right_disconnects = true 179 | scroll_offset = Vector2( 0, -11 ) 180 | use_snap = false 181 | 182 | [connection signal="pressed" from="VBox/HBoxMenu/Openbutton" to="." method="_on_Openbutton_pressed"] 183 | [connection signal="pressed" from="VBox/HBoxMenu/Savebutton" to="." method="_on_Savebutton_pressed"] 184 | [connection signal="pressed" from="VBox/HBoxMenu/Newjson" to="." method="Newjson"] 185 | [connection signal="pressed" from="VBox/HBoxMenu/Lang-en" to="." method="langchange" binds= [ "en" ]] 186 | [connection signal="pressed" from="VBox/HBoxMenu/Lang-fr" to="." method="langchange" binds= [ "fr" ]] 187 | [connection signal="pressed" from="VBox/HBoxMenu/Dialog" to="." method="NewDialog"] 188 | [connection signal="pressed" from="VBox/HBoxMenu/Condition" to="." method="NewCondition"] 189 | [connection signal="pressed" from="VBox/HBoxMenu/Choice" to="." method="NewChoice"] 190 | [connection signal="pressed" from="VBox/HBoxMenu/Signal" to="." method="NewSignal"] 191 | [connection signal="pressed" from="VBox/HBoxMenu/Print" to="." method="printjson"] 192 | [connection signal="pressed" from="VBox/HBoxMenu/Refresh" to="." method="refreshjson"] 193 | [connection signal="pressed" from="VBox/HBoxMenu/Clear" to="." method="cleargraph"] 194 | [connection signal="connection_request" from="VBox/Panel" to="." method="_on_Panel_connection_request"] 195 | [connection signal="disconnection_request" from="VBox/Panel" to="." method="_on_Panel_disconnection_request"] 196 | -------------------------------------------------------------------------------- /DialogPlugin/SignalNode.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GraphNode 3 | signal nodechanged 4 | 5 | var nodeid:String="" # stores unique ID of node 6 | var nodecontent:Dictionary={} # stores content of node in all languages 7 | var variable="" 8 | var type="string" 9 | var value="" 10 | onready var Editor:Object= get_parent().get_parent().get_parent() 11 | 12 | func _ready(): 13 | updatenode() 14 | $VBox/HBoxType/Content._select_int(0) 15 | $VBox/HBoxType/Content.text="string" 16 | # connect("nodechanged",Editor, "refreshjson") 17 | 18 | func _close_request(): 19 | var connections:Array=Editor.get_node("VBox/Panel").get_connection_list() 20 | for item in connections: 21 | if item["to"]==self.name or item["from"]==self.name: 22 | Editor.get_node("VBox/Panel").disconnect_node(item["from"],0,item["to"],0) 23 | queue_free() 24 | 25 | func _on_resize_request(new_minsize): 26 | rect_size=new_minsize 27 | 28 | func _on_values_changed(): # refresh the nodecontent variable 29 | nodecontent.clear() 30 | variable =$VBox/HBoxVar/Content.text 31 | type=$VBox/HBoxType/Content.text 32 | value=$VBox/HBoxValue/Content.text 33 | nodecontent[variable]={"type":type,"value":value} 34 | 35 | # emit_signal("nodechanged") # refresh json file 36 | 37 | func updatenode(): 38 | $VBox/HBoxVar/Content.text=variable 39 | $VBox/HBoxType/Content.text=type 40 | $VBox/HBoxValue/Content.text=value 41 | _on_values_changed() # rebuild nodecontent variable 42 | 43 | func _on_Singlenode_clicked(): 44 | print(nodecontent) 45 | print(selected) 46 | 47 | func _on_type_selected(index): 48 | if index==0:type="string" 49 | if index==1:type="number" 50 | update() 51 | -------------------------------------------------------------------------------- /DialogPlugin/SignalNode.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/Dialogs/SignalNode.gd" type="Script" id=1] 4 | [ext_resource path="res://addons/Dialogs/textedit.stylebox" type="StyleBox" id=2] 5 | [ext_resource path="res://addons/Dialogs/nodes.stylebox" type="StyleBox" id=3] 6 | [ext_resource path="res://addons/Dialogs/optionbutton.stylebox" type="StyleBox" id=4] 7 | 8 | [node name="SignalNode" type="GraphNode" groups=[ 9 | "dialognode", 10 | ]] 11 | margin_right = 145.0 12 | margin_bottom = 122.0 13 | rect_min_size = Vector2( 200, 200 ) 14 | rect_clip_content = true 15 | custom_styles/frame = ExtResource( 3 ) 16 | custom_styles/selectedframe = ExtResource( 3 ) 17 | title = "Signal" 18 | show_close = true 19 | resizable = true 20 | slot/0/left_enabled = false 21 | slot/0/left_type = 0 22 | slot/0/left_color = Color( 1, 1, 1, 1 ) 23 | slot/0/right_enabled = false 24 | slot/0/right_type = 0 25 | slot/0/right_color = Color( 1, 1, 1, 1 ) 26 | script = ExtResource( 1 ) 27 | __meta__ = { 28 | "_edit_use_anchors_": false 29 | } 30 | 31 | [node name="VBox" type="VBoxContainer" parent="."] 32 | margin_left = 30.0 33 | margin_top = 30.0 34 | margin_right = 275.0 35 | margin_bottom = 164.0 36 | 37 | [node name="KeyBox" type="HBoxContainer" parent="VBox"] 38 | margin_right = 245.0 39 | margin_bottom = 14.0 40 | 41 | [node name="Label" type="Label" parent="VBox/KeyBox"] 42 | margin_right = 27.0 43 | margin_bottom = 14.0 44 | text = "Key:" 45 | 46 | [node name="Keylabel" type="Label" parent="VBox/KeyBox"] 47 | margin_left = 31.0 48 | margin_right = 31.0 49 | margin_bottom = 14.0 50 | 51 | [node name="HSeparator2" type="HSeparator" parent="VBox"] 52 | margin_top = 18.0 53 | margin_right = 245.0 54 | margin_bottom = 22.0 55 | 56 | [node name="HBoxVar" type="HBoxContainer" parent="VBox"] 57 | margin_top = 26.0 58 | margin_right = 245.0 59 | margin_bottom = 46.0 60 | size_flags_horizontal = 3 61 | 62 | [node name="Label" type="Label" parent="VBox/HBoxVar"] 63 | margin_top = 3.0 64 | margin_right = 41.0 65 | margin_bottom = 17.0 66 | text = "Signal:" 67 | 68 | [node name="Content" type="TextEdit" parent="VBox/HBoxVar"] 69 | margin_left = 45.0 70 | margin_right = 245.0 71 | margin_bottom = 20.0 72 | rect_min_size = Vector2( 200, 20 ) 73 | size_flags_horizontal = 3 74 | size_flags_vertical = 3 75 | custom_styles/normal = ExtResource( 2 ) 76 | 77 | [node name="HBoxType" type="HBoxContainer" parent="VBox"] 78 | margin_top = 50.0 79 | margin_right = 245.0 80 | margin_bottom = 66.0 81 | size_flags_horizontal = 3 82 | 83 | [node name="Label" type="Label" parent="VBox/HBoxType"] 84 | margin_top = 1.0 85 | margin_right = 34.0 86 | margin_bottom = 15.0 87 | text = "Type:" 88 | 89 | [node name="Content" type="OptionButton" parent="VBox/HBoxType"] 90 | margin_left = 38.0 91 | margin_right = 245.0 92 | margin_bottom = 16.0 93 | size_flags_horizontal = 3 94 | custom_styles/hover = ExtResource( 4 ) 95 | custom_styles/pressed = ExtResource( 4 ) 96 | custom_styles/normal = ExtResource( 4 ) 97 | text = "string" 98 | items = [ "string", null, false, 0, null, "number", null, false, 1, null ] 99 | selected = 0 100 | 101 | [node name="HBoxValue" type="HBoxContainer" parent="VBox"] 102 | margin_top = 70.0 103 | margin_right = 245.0 104 | margin_bottom = 90.0 105 | size_flags_horizontal = 3 106 | 107 | [node name="Label" type="Label" parent="VBox/HBoxValue"] 108 | margin_top = 3.0 109 | margin_right = 35.0 110 | margin_bottom = 17.0 111 | text = "Value" 112 | 113 | [node name="Content" type="TextEdit" parent="VBox/HBoxValue"] 114 | margin_left = 39.0 115 | margin_right = 245.0 116 | margin_bottom = 20.0 117 | rect_min_size = Vector2( 200, 20 ) 118 | size_flags_horizontal = 3 119 | size_flags_vertical = 3 120 | custom_styles/normal = ExtResource( 2 ) 121 | 122 | [node name="HSeparator" type="HSeparator" parent="VBox"] 123 | margin_top = 94.0 124 | margin_right = 245.0 125 | margin_bottom = 98.0 126 | 127 | [node name="Nexttitle" type="Label" parent="VBox"] 128 | margin_top = 102.0 129 | margin_right = 245.0 130 | margin_bottom = 116.0 131 | text = "Next node:" 132 | 133 | [node name="Nextnode" type="Label" parent="VBox"] 134 | margin_top = 120.0 135 | margin_right = 245.0 136 | margin_bottom = 134.0 137 | 138 | [connection signal="close_request" from="." to="." method="_close_request"] 139 | [connection signal="raise_request" from="." to="." method="_on_Singlenode_clicked"] 140 | [connection signal="resize_request" from="." to="." method="_on_resize_request"] 141 | [connection signal="text_changed" from="VBox/HBoxVar/Content" to="." method="_on_values_changed"] 142 | [connection signal="item_selected" from="VBox/HBoxType/Content" to="." method="_on_type_selected"] 143 | [connection signal="text_changed" from="VBox/HBoxValue/Content" to="." method="_on_values_changed"] 144 | -------------------------------------------------------------------------------- /DialogPlugin/UiidGenerator.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name IDGen 3 | 4 | static func getRandomInt(max_value): 5 | randomize() 6 | return randi() % max_value 7 | 8 | static func randomBytes(n): 9 | var r = [] 10 | 11 | for index in range(0, n): 12 | r.append(getRandomInt(256)) 13 | return r 14 | 15 | static func uuidbin(): 16 | var b = randomBytes(16) 17 | 18 | b[6] = (b[6] & 0x0f) | 0x40 19 | b[8] = (b[8] & 0x3f) | 0x80 20 | return b 21 | 22 | static func v4(): 23 | var b = uuidbin() 24 | 25 | var low = '%02x%02x%02x%02x' % [b[0], b[1], b[2], b[3]] 26 | var mid = '%02x%02x' % [b[4], b[5]] 27 | var hi = '%02x%02x' % [b[6], b[7]] 28 | var clock = '%02x%02x' % [b[8], b[9]] 29 | var node = '%02x%02x%02x%02x%02x%02x' % [b[10], b[11], b[12], b[13], b[14], b[15]] 30 | 31 | return '%s-%s-%s-%s-%s' % [low, mid, hi, clock, node] 32 | -------------------------------------------------------------------------------- /DialogPlugin/nodes.stylebox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlooRabbit/DialogPlugin/26100245875d76e8f9ca47ea2c96153339d50caf/DialogPlugin/nodes.stylebox -------------------------------------------------------------------------------- /DialogPlugin/optionbutton.stylebox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlooRabbit/DialogPlugin/26100245875d76e8f9ca47ea2c96153339d50caf/DialogPlugin/optionbutton.stylebox -------------------------------------------------------------------------------- /DialogPlugin/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Dialogs" 4 | description="Blubbits dialog maker, based on Levrault's LeDialog" 5 | author="Blubbits" 6 | version="" 7 | script="Dialogs.gd" 8 | -------------------------------------------------------------------------------- /DialogPlugin/textedit.stylebox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlooRabbit/DialogPlugin/26100245875d76e8f9ca47ea2c96153339d50caf/DialogPlugin/textedit.stylebox -------------------------------------------------------------------------------- /DialogueManager.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | # Stand-alone node for the display of dialogues based on json files made with a Godot plugin 4 | # The plugin was made based on Levrault's dialogue editor (there may be some compatibility issues) 5 | # Plugin can be found here: https://github.com/BlooRabbit/DialogPlugin 6 | # Lebvault's editor can be found here: https://github.com/Levrault/LE-dialogue-editor 7 | # Use it to make and save your json file in the "Dialogue" folder 8 | 9 | # Load .json by triggering loaddialogue(path) from your game script, where path is the name without extension in a folder called Dialogue 10 | # Start dialogue with start(originnode,position, campos, camtarget) from your game script(s) 11 | 12 | # Choices in this example are only based on "OK" or "NO" triggering a yes/no button - change this based on your own project 13 | # => requires a $OKbutton and a $NObutton button node to work 14 | # Conditions based on variables work using variables defined in this script or as autoload.variable 15 | 16 | var position : Vector2 = Vector2.ZERO 17 | 18 | var _dialogues := {} 19 | var _conditions := {} 20 | var _current_dialogue := {} 21 | var dialogue_json 22 | var OK_node : String ="" 23 | var NO_node : String ="" 24 | var originnode:Object = self 25 | 26 | var _is_last_dialogue : bool= false 27 | var _has_timer : bool = false 28 | var _is_displaying : bool = false 29 | var _is_executing : bool = false 30 | 31 | # change the below to allocate the various panels based on your own project 32 | onready var _text = $Dialoguetext # message 33 | onready var _choices_panel = null # panel for choices 34 | onready var _next = $OKbutton # next button 35 | onready var _end = $OKbutton # end of dialogue button 36 | onready var _timer = $Timer # timer for timed messages 37 | 38 | signal dialogue_finished 39 | signal timer(seconds) 40 | signal codexec(codetoexecute) 41 | 42 | # -------------------------- 43 | 44 | func _ready(): 45 | 46 | $OKbutton.visible=false 47 | $NObutton.visible=false 48 | 49 | func _physics_process(delta): 50 | if movecamera: # get camera to dialog fixed place 51 | get_tree().root.get_node("Game/Camera").adjustcam(delta,campos) 52 | get_tree().root.get_node("Game/Camera").look_at(camtarget, Vector3.UP) 53 | # signal when camera arrived 54 | if (get_tree().root.get_node("Game/Camera").global_transform.origin-campos).length()<0.5: 55 | emit_signal("cameraOK") 56 | 57 | # ---------------------------------------- 58 | # Start functions triggered by game code 59 | # ---------------------------------------- 60 | 61 | # Load dialogue 62 | func loaddialogue(path) -> void: # path is the name of the file without extension 63 | dialogue_json = get_json("Dialogue/"+path+".json") 64 | 65 | # starts the dialogue 66 | func start(origin:Object=self,pos:Object=self, cpos:Vector3=Vector3.ZERO, ctarget:Vector3=Vector3.ZERO) -> void: 67 | 68 | # launch first dialogue 69 | originnode=origin 70 | _current_dialogue = dialogue_json["root"] 71 | if _current_dialogue.has("conditions"): # in case dialogue starts with condition branches 72 | executeconditions() # displays next dialogue based on conditions 73 | else: # in case dialogue starts with a text dialogue 74 | _current_dialogue = dialogue_json[dialogue_json.root["next"]] 75 | displaydialogue() # start the first text box 76 | 77 | # --------------------------------------------- 78 | # Specific game events triggered by json signal 79 | # --------------------------------------------- 80 | 81 | # Execute some game specific signals 82 | 83 | # put you functions here, that will executed as signals from th dialog 84 | # example: func signallight(): $light.visible = toggle($light.visible) 85 | 86 | # ---------------------------------------- 87 | # Dialogue system 88 | # ---------------------------------------- 89 | 90 | # This function displays the dialogues 91 | func displaydialogue() -> void: 92 | 93 | if _is_displaying : return 94 | _is_displaying = true 95 | 96 | if _current_dialogue.empty() or not _current_dialogue.has("text"): Dialogue_finished() # avoid game breaks 97 | 98 | # if there is no linked dialogue, no choice and no condition => last dialogue 99 | if not _current_dialogue.has("conditions") and not _current_dialogue.has("next") and not _current_dialogue.has("choices"): 100 | _is_last_dialogue=true 101 | 102 | # reset choice display 103 | $OKbutton.visible=false 104 | $NObutton.visible=false 105 | 106 | # parse dialog text 107 | var text: String = _current_dialogue["text"][TranslationServer.get_locale()] 108 | var message= texttoimagepath(text) # transform in-dialog image tags 109 | _text.parse_bbcode(message) 110 | 111 | # resize dialog box based on message size 112 | # do your own stuff here if needed ! 113 | 114 | # make letters appear progressively 115 | var textsize=message.length() 116 | $Tween.interpolate_property(_text,"visible_characters",0,textsize,textsize*0.02,Tween.TRANS_LINEAR,Tween.EASE_IN_OUT) 117 | $Tween.start() 118 | yield(get_tree().create_timer(textsize*0.01),"timeout") 119 | 120 | # check if player can make some choices and have them displayed 121 | # careful: in this example, choices are only "OK" or "NO" - adapt depending on your project 122 | if _current_dialogue.has("choices"): 123 | var conditions: Array = ( 124 | _current_dialogue.get("conditions") 125 | if _current_dialogue.has("conditions") 126 | else [] 127 | ) 128 | displaychoices(get_choices(_current_dialogue.choices, conditions)) 129 | else: # just display a next button 130 | $OKbutton.visible=true 131 | $OKbutton.grab_focus() 132 | 133 | _is_displaying = false 134 | 135 | # displays choices 136 | # careful: in this example, choices are only "OK" or "NO" - adapt depending on your project 137 | func displaychoices(choices): 138 | OK_node="" 139 | NO_node="" 140 | for choice in choices: 141 | # the below checks if it is a OK or NO button 142 | var choosefrom:String = choice["text"][TranslationServer.get_locale()] 143 | if choosefrom=="OK": 144 | OK_node=choice["next"] # store OK choice 145 | $OKbutton.visible=true 146 | $OKbutton.grab_focus() 147 | if choosefrom=="NO": 148 | NO_node=choice["next"] # store NO choice 149 | $NObutton.visible=true 150 | 151 | # implements the conditions (conditions can follow root or dialogue nodes) 152 | # condition may be a variable of the dialog script or an autoload.variable 153 | func executeconditions(): 154 | if _is_displaying or _is_executing : return 155 | _is_executing==true 156 | 157 | var chosendialog : Dictionary 158 | var conditions=_current_dialogue.conditions 159 | for condition in conditions: 160 | for key in condition.keys(): # key is either the condition or its "next" node 161 | if key != "next": 162 | if checkiftrue(key, condition[key]): 163 | chosendialog=dialogue_json[condition["next"]] 164 | if chosendialog.empty(): 165 | Dialogue_finished() # avoid dialog issues 166 | return 167 | _current_dialogue=chosendialog 168 | displaydialogue() 169 | 170 | _is_executing=false 171 | 172 | # checks if a given condition is true and returns a boolean 173 | func checkiftrue(variable, condition) -> bool: 174 | var vartocheck=get(variable) 175 | # ------ 176 | if "autoload" in variable: # variable for conditions can be an autoload 177 | variable=variable.replace("autoload.","") 178 | vartocheck=autoload.get(variable) 179 | # ------ 180 | if condition ["type"] == "boolean": 181 | var valtocheck :bool 182 | if condition["value"]=="true":valtocheck=true 183 | if condition["value"]=="false":valtocheck=false 184 | if vartocheck == valtocheck: return true 185 | else: return false 186 | elif condition ["type"] == "number": 187 | var value=int(condition["value"]) 188 | if condition ["operator"] == "equal": 189 | if vartocheck == value: return true 190 | else: return false 191 | if condition ["operator"] == "greater": 192 | if vartocheck > value: return true 193 | else: return false 194 | if condition ["operator"] == "lower": 195 | if vartocheck < value: return true 196 | else: return false 197 | if condition ["operator"] == "different": 198 | if vartocheck != value: return true 199 | else: return false 200 | return false 201 | 202 | # OK button 203 | func _OKbutton_pressed(): 204 | if _current_dialogue.has("signals"): 205 | _emit_dialogue_signal(_current_dialogue["signals"]) 206 | if _is_last_dialogue: Dialogue_finished() # OK to end dialogue 207 | elif _current_dialogue.has("conditions"): 208 | executeconditions() # next dialogue based on conditions 209 | else: # not last dialogue and no conditions, so "OK" means "next" or "choice" 210 | if OK_node !="": # OK is a choice and the next key is stored in OK_node 211 | _current_dialogue = dialogue_json[OK_node] 212 | displaydialogue() 213 | else: # OK means next 214 | if _current_dialogue.has("next"): 215 | _current_dialogue = dialogue_json[_current_dialogue["next"]] 216 | displaydialogue() 217 | else: Dialogue_finished() # avoid dead ends 218 | 219 | # NO button 220 | func _NObutton_pressed(): 221 | if NO_node !="": # go to node refered to in NO_node 222 | _current_dialogue = dialogue_json[NO_node] 223 | displaydialogue() 224 | 225 | # Emit signals - this requires that the signals are "declared" somewhere ("signal string(value)") 226 | # For example timed dialog works with a signal called "timer(seconds)" to created a timed box 227 | # Another signal is the "execode(blabla)" signal which executes specific code 228 | func _emit_dialogue_signal(signals: Dictionary) -> void: 229 | for key in signals: 230 | if not signals[key] is Dictionary: 231 | if signals[key] == null: 232 | connect(key, self, key) 233 | emit_signal(key) 234 | continue 235 | connect(key, self, key) 236 | emit_signal(key, signals[key]) 237 | continue 238 | var multi_values_signal: Dictionary = signals[key] 239 | for type in multi_values_signal: 240 | var value = _convert_value_to_type(type, multi_values_signal[type]) 241 | connect(key, self, key) 242 | emit_signal(key, value) 243 | 244 | # Function used for signals 245 | func _convert_value_to_type(type: String, value): 246 | match type: 247 | "vector2": 248 | return Vector2(value["x"], value["y"]) 249 | "number": 250 | if "." in type: return float(value) 251 | else: return int(value) 252 | "string": 253 | return str(value) 254 | return value 255 | 256 | # ---------------- 257 | # End of dialogue 258 | # ---------------- 259 | 260 | # close box at end of dialogue 261 | func closebox(): 262 | yield(get_tree().create_timer(0.1), "timeout") 263 | $Tween.interpolate_property(self,"rect_scale",Vector2(1.0,1.0),Vector2(0.0,0.0),0.3, Tween.TRANS_SINE,Tween.EASE_IN_OUT) 264 | $Tween.start() #tween the scale of the dialogue box back to zero 265 | yield($Tween,"tween_completed") 266 | self.visible=false 267 | self.set("rect_size", Vector2(470,290)) # reset standard box size 268 | 269 | # Reset dialogue when finished 270 | func Dialogue_finished() -> void: 271 | emit_signal("dialogue_finished") # can be captured by yield from origin node 272 | $OKbutton.visible=false 273 | $NObutton.visible=false 274 | _text.text="" 275 | _text.bbcode_text="" 276 | closebox() 277 | clear() 278 | _timer.stop() 279 | _is_last_dialogue = false 280 | _has_timer = false 281 | 282 | # clear dialogue variables 283 | func clear() -> void: 284 | NO_node="" 285 | OK_node="" 286 | _dialogues = {} 287 | _current_dialogue = {} 288 | _conditions = {} 289 | 290 | # Function to get choices in case of multiple choices 291 | func get_choices(choices: Array, conditions: Array = []) -> Array: 292 | if conditions.empty(): 293 | return choices 294 | 295 | var result := [] 296 | var conditional_choices := {} 297 | 298 | for choice in choices: 299 | if choice.has("uuid"): 300 | conditional_choices[choice.uuid] = choice 301 | else: 302 | result.append(choice) 303 | 304 | if conditional_choices.empty(): 305 | return choices 306 | 307 | var matching_condition := 0 308 | for condition in conditions: 309 | var current_matching_condition := 0 310 | for key in condition: 311 | var predicated_next: String = condition.next 312 | condition.erase("next") 313 | 314 | if _conditions.has(key): 315 | # conditions will never match 316 | if _conditions.size() < condition.size(): 317 | continue 318 | 319 | if condition.empty(): 320 | result.append(conditional_choices[predicated_next]) 321 | 322 | if _check_conditions(condition, key): 323 | current_matching_condition += 1 324 | 325 | if current_matching_condition > matching_condition: 326 | result.append(conditional_choices[predicated_next]) 327 | 328 | # dialogue json file was badly configuarated since it doesn't have a default choice 329 | assert(not result.empty()) 330 | return result 331 | 332 | # Check conditions 333 | # @returns {bool} 334 | func _check_conditions(condition: Dictionary, key: String) -> bool: 335 | match condition[key].operator: 336 | "lower": 337 | return condition[key].value > _conditions[key] 338 | "greater": 339 | return condition[key].value < _conditions[key] 340 | "different": 341 | return condition[key].value != _conditions[key] 342 | _: 343 | return condition[key].value == _conditions[key] 344 | 345 | # -------------------------- 346 | # Read and parse Json file 347 | # -------------------------- 348 | 349 | func get_json(file_path: String) -> Dictionary: 350 | var file := File.new() 351 | 352 | if file.open(file_path, file.READ) != OK: 353 | print("get_json: file cannot been read") 354 | return {} 355 | 356 | var text_content := file.get_as_text() 357 | file.close() 358 | var data_parse = JSON.parse(text_content) 359 | if data_parse.error != OK: 360 | print("get_json: error while parsing") 361 | return {} 362 | 363 | data_parse.result.erase("__editor") 364 | return data_parse.result 365 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Blue Rabbit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DialogPlugin 2 | Godot plugin for creating dialog jsons with Godot 3.x. 3 | 4 | This Godot plugin is designed to create json files containing dialogs, based on a visual nodes system. 5 | 6 | The json files are in the format designed by Levrault for his standalone editor, which can be found here, along with a lot of explanations: https://github.com/Levrault/LE-dialogue-editor. Please note that there may be some compatibility issues with his editor, though, as I added some stuff to the json files. 7 | 8 | **Use** 9 | 10 | The visual node system allows for dialog nodes, conditional nodes, choice nodes and signals nodes. 11 | 12 | Start by creating a "new file", which will add a root node. Then plugin whatever other nodes you want. "print" prints out the json to your console. 13 | Don't forget to save your file ! 14 | 15 | The plugin currently supports two languages, but it is pretty easy to add other languages, once you understand the way it works. 16 | 17 | The json files can be read, for example, by the gdscript called "DialogueManager.gd" I am providing in this github folder. This reader is based on simplified choices "OK" and "NO", but you can easily change this. 18 | 19 | **Install** 20 | 21 | Just install the plugin in the addon folder and enable it in the Godot editor settings. Use and customize the DialogManager.gd script to read the files, or make your own. 22 | 23 | The plugin is work in progress. It is still pretty raw and I will probably add more features in the future. 24 | 25 | https://user-images.githubusercontent.com/56018661/127719492-557f85c4-ca9a-445c-8da9-7116be51db3c.mp4 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------