From e46e53db1a3b3434b39e699d4792e0fca605aff1 Mon Sep 17 00:00:00 2001 From: Non0w Date: Tue, 8 Sep 2020 23:52:26 +0200 Subject: [PATCH] Basic yarn parsing and running --- ...n.png-6587814821ca75f730cf7dfe8a5167cb.md5 | 3 + ....png-6587814821ca75f730cf7dfe8a5167cb.stex | Bin 0 -> 696 bytes ...e.txt-05f23fb6780d7fb8747b6c25d01e926c.md5 | 3 + ....yarn-bfaf9553976186f49f9681f51669695e.md5 | 3 + ....yarn-642d0590cee80e9b0bd554c3bdc33727.md5 | 3 + ....yarn-642d0590cee80e9b0bd554c3bdc33727.res | Bin 0 -> 2396 bytes addons/yarn_spinner/icon.png | Bin 0 -> 2112 bytes addons/yarn_spinner/icon.png.import | 34 +++ addons/yarn_spinner/plugin.cfg | 7 + addons/yarn_spinner/yarn_runner.gd | 51 ++++ addons/yarn_spinner/yarn_script.gd | 3 + addons/yarn_spinner/yarn_spinner_importer.gd | 232 ++++++++++++++++++ addons/yarn_spinner/yarn_spinner_plugin.gd | 16 ++ main/dialogues/test.yarn | 5 +- main/dialogues/test.yarn.import | 14 ++ main/scenes/test_viteuf.tscn | 21 ++ main/scripts/test_viteuf.gd | 24 ++ project.godot | 10 + yarn/scripts/yarn-importer.gd | 41 +++- 19 files changed, 457 insertions(+), 13 deletions(-) create mode 100644 .import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5 create mode 100644 .import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex create mode 100644 .import/test - Copie.txt-05f23fb6780d7fb8747b6c25d01e926c.md5 create mode 100644 .import/test - Copie.yarn-bfaf9553976186f49f9681f51669695e.md5 create mode 100644 .import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.md5 create mode 100644 .import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.res create mode 100644 addons/yarn_spinner/icon.png create mode 100644 addons/yarn_spinner/icon.png.import create mode 100644 addons/yarn_spinner/plugin.cfg create mode 100644 addons/yarn_spinner/yarn_runner.gd create mode 100644 addons/yarn_spinner/yarn_script.gd create mode 100644 addons/yarn_spinner/yarn_spinner_importer.gd create mode 100644 addons/yarn_spinner/yarn_spinner_plugin.gd create mode 100644 main/dialogues/test.yarn.import create mode 100644 main/scenes/test_viteuf.tscn create mode 100644 main/scripts/test_viteuf.gd diff --git a/.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5 b/.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5 new file mode 100644 index 0000000..725781a --- /dev/null +++ b/.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5 @@ -0,0 +1,3 @@ +source_md5="10b14a4273e3c8eb3d990fd5ce59afed" +dest_md5="06821280489269c54a81298e6d9b3949" + diff --git a/.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex b/.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex new file mode 100644 index 0000000000000000000000000000000000000000..56ae2be6c0d8a87190a04220fbea1dfa559b95e1 GIT binary patch literal 696 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 qk Jump to " + next_node["title"]) + var next_node_key = run_body(next_node["body"]) + if (yarnScript.nodes.has(next_node_key)): + next_node = yarnScript.nodes[next_node_key] + else: + next_node = null + +func run_body(body): + for element in body: + if element["type"] == "jump": + return element["node"] + elif element["type"] == "choice_blocks": + var block = decide_choice_block(element["blocks"]) + return block["node"] # todo change later with "-> choices" + elif element["type"] == "condition_blocks": + var block = decide_condition_block(element["blocks"]) + var next_node_key = run_body(block["body"]) + if next_node_key != "": + return next_node_key + else: + if say_func == null or !say_func.is_valid(): + print(element) + else: + say_func.call_func(element) + + return "" + +export var choice = 0 + +func decide_choice_block(blocks): + return blocks[choice] + +func decide_condition_block(blocks): + for block in blocks: + var expr := Expression.new() + var err := expr.parse(block["condition"]) + + if err == OK and expr.execute(): + return block + return null diff --git a/addons/yarn_spinner/yarn_script.gd b/addons/yarn_spinner/yarn_script.gd new file mode 100644 index 0000000..9b5965f --- /dev/null +++ b/addons/yarn_spinner/yarn_script.gd @@ -0,0 +1,3 @@ +extends Resource + +export var nodes = {} diff --git a/addons/yarn_spinner/yarn_spinner_importer.gd b/addons/yarn_spinner/yarn_spinner_importer.gd new file mode 100644 index 0000000..8f32876 --- /dev/null +++ b/addons/yarn_spinner/yarn_spinner_importer.gd @@ -0,0 +1,232 @@ +tool +extends EditorImportPlugin + +var YarnScript = preload("yarn_script.gd") + +enum Presets { DEFAULT } + +# types +const EMPTY = "empty" +const NODE_BODY_END = "node_body_end" +const NODE_BODY_START = "node_body_start" +const NODE_TAG = "node_tag" +const JUMP = "jump" +const TEXT = "text" +const CHOICE = "choice" +const CHOICE_BLOCKS = "choice_blocks" +const CONDITION_IF = "if" +const CONDITION_ELSE = "else" +const CONDITION_ELSEIF = "elseif" +const CONDITION_ENDIF = "endif" +const CONDITION_BLOCKS = "condition_blocks" +const COMMAND = "command" + +# Entry point + +func import(source_file, save_path, options, r_platform_variants, r_gen_files): + var file : File = File.new() + var err := file.open(source_file, File.READ) + if err != OK: + return err + + var yarn = (YarnScript).new() + yarn.nodes = {} + + while not file.eof_reached(): + var node = parse_header(file) + node["body"] = parse_body(file) + if node["title"] != "": + yarn.nodes[node["title"]] = node + + file.close() + + return ResourceSaver.save("%s.%s" % [save_path, get_save_extension()], yarn) + +# Nodes parsing + +func parse_header(file : File): + var new_node = {"title": "", "tags": "", "body":[]} + + while not file.eof_reached(): + var parsed_line = parse_line_header(file.get_line()) + var type = parsed_line["type"] + +# print("[" + type + "]\n\t" + str(parsed_line)) + + if type == EMPTY: + pass + elif type == NODE_BODY_START: + break + elif type == NODE_TAG: + new_node[parsed_line["key"]] = parsed_line["value"] + + return new_node + +func parse_body(file): + var body = [] + + while not file.eof_reached(): + var parsed_line = parse_line_body(file.get_line()) + var type = parsed_line["type"] + +# print("[" + type + "]\n\t" + str(parsed_line)) + + if type == EMPTY: + pass + elif type == NODE_BODY_END: + break + elif type == CONDITION_IF: + body.push_back(parse_condition_blocks(file, parsed_line)) + elif type == CHOICE: + if body.size() == 0 or body[body.size() - 1]["type"] != CHOICE_BLOCKS: + body.push_back({"type":CHOICE_BLOCKS, "blocks":[]}) + body[body.size() -1]["blocks"].push_back(parsed_line) + else: + body.push_back(parsed_line) + + return body + +func parse_condition_blocks(file, parsed_line): + var condition_blocks = {"type":CONDITION_BLOCKS, "blocks":[]} + + var current_block = {"condition":parsed_line["expression"], "body":[]} + + while not file.eof_reached(): + parsed_line = parse_line_body(file.get_line()) + var type = parsed_line["type"] + +# print("[" + type + "]\n\t" + str(parsed_line)) + + if type == EMPTY: + pass + elif type == CONDITION_IF: + current_block["body"].push_back(parse_condition_blocks(file, parsed_line)) + elif type == CONDITION_ELSEIF: + condition_blocks["blocks"].push_back(current_block) + current_block = {"condition":parsed_line["expression"], "body":[]} + elif type == CONDITION_ELSE: + condition_blocks["blocks"].push_back(current_block) + current_block = {"condition":parsed_line["expression"], "body":[]} + elif type == CONDITION_ENDIF: + condition_blocks["blocks"].push_back(current_block) + break + else: + current_block["body"].push_back(parsed_line) + + return condition_blocks + +# Lines Parsing + +func parse_line_header(raw_line : String): + var parsed_line = {"type":"????", "raw":raw_line} + + if raw_line.strip_edges() == "": + parsed_line["type"] = EMPTY + elif raw_line.begins_with("---"): + parsed_line["type"] = NODE_BODY_START + else: + var split = raw_line.split(':') + if split.size() >= 2: + parsed_line["type"] = NODE_TAG + parsed_line["key"] = split[0] + parsed_line["value"] = split[1].strip_edges() + + return parsed_line + +func parse_line_body(raw_line : String): + #todo later, use indentation + raw_line = raw_line.strip_edges() + + var parsed_line = {"type":"????", "raw":raw_line} + + if raw_line.strip_edges() == "": + parsed_line["type"] = EMPTY + elif raw_line.begins_with("==="): + parsed_line["type"] = NODE_BODY_END + elif raw_line.begins_with("[["): + var split := raw_line.replace("[[", "").replace("]]", "").split("|") + if split.size() > 1: + parsed_line["type"] = CHOICE + parsed_line["text"] = split[0] + parsed_line["node"] = split[1] + else: + parsed_line["type"] = JUMP + parsed_line["node"] = split[0] + elif raw_line.begins_with("<<"): + var split := raw_line.replace("<<", "").replace(">>", "").strip_edges().split(' ') + if split.size() > 0: + if split[0] == "if": + parsed_line["type"] = CONDITION_IF + var expr := "" + for token in Array(split).slice(1, split.size() -1): + expr += token + " " + parsed_line["expression"] = expr + elif split[0] == "elseif": + parsed_line["type"] = CONDITION_ELSEIF + var expr := "" + for token in Array(split).slice(1, split.size() -1): + expr += token + " " + parsed_line["expression"] = expr + elif split[0] == "else": + parsed_line["type"] = CONDITION_ELSE + parsed_line["expression"] = "true" # add default expression always true + elif split[0] == "endif": + parsed_line["type"] = CONDITION_ENDIF + else: + parsed_line["type"] = COMMAND + parsed_line["command"] = split[0] + parsed_line["args"] = Array(split).slice(1, split.size() - 1) + else: + parsed_line["type"] = TEXT + # use regex ? + var separator_idx = raw_line.find(':') + if separator_idx != -1: + var chara = raw_line.substr(0, separator_idx).strip_edges() + var text = raw_line.substr(separator_idx + 1 , -1).strip_edges() + parsed_line["chara"] = chara + parsed_line["text"] = text + else: + parsed_line["chara"] = "" + parsed_line["text"] = raw_line.strip_edges() + + return parsed_line + +# Required editor functions + +func get_importer_name(): + return "yarn.script" + +func get_visible_name(): + return "Yarn Script" + +func get_recognized_extensions(): + return ["yarn", "txt"] + +func get_save_extension(): + return "res" + +func get_resource_type(): + return "Resource" + +func get_preset_count(): + return Presets.size() + +func get_preset_name(preset): + match preset: + Presets.DEFAULT: + return "Default" + _: + return "Unknown" + +func get_import_options(preset): + match preset: + Presets.DEFAULT: + return [{ + "name": "use_red_anyway", + "default_value": false + }] + _: + return [] + +func get_option_visibility(option, options): + return true diff --git a/addons/yarn_spinner/yarn_spinner_plugin.gd b/addons/yarn_spinner/yarn_spinner_plugin.gd new file mode 100644 index 0000000..4cf15a3 --- /dev/null +++ b/addons/yarn_spinner/yarn_spinner_plugin.gd @@ -0,0 +1,16 @@ +tool +extends EditorPlugin + +var yarn_spinner_importer + +func _enter_tree(): + yarn_spinner_importer = preload("yarn_spinner_importer.gd").new() + add_custom_type("YarnScript", "Resource", preload("yarn_script.gd"), preload("icon.png")) + add_custom_type("YarnRunner", "Node", preload("yarn_runner.gd"), preload("icon.png")) + add_import_plugin(yarn_spinner_importer) + +func _exit_tree(): + remove_custom_type("YarnScript") + remove_custom_type("YarnRunner") + remove_import_plugin(yarn_spinner_importer) + yarn_spinner_importer = null diff --git a/main/dialogues/test.yarn b/main/dialogues/test.yarn index aacda39..3ed5583 100644 --- a/main/dialogues/test.yarn +++ b/main/dialogues/test.yarn @@ -12,8 +12,9 @@ tags: position: 1032.2125854492188,730.3896484375 --- Huhuhu -<> - OK ! +< 2 >> + OK !! + [[zboub]] <> Pas ok. <> diff --git a/main/dialogues/test.yarn.import b/main/dialogues/test.yarn.import new file mode 100644 index 0000000..3712340 --- /dev/null +++ b/main/dialogues/test.yarn.import @@ -0,0 +1,14 @@ +[remap] + +importer="yarn.script" +type="Resource" +path="res://.import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.res" + +[deps] + +source_file="res://main/dialogues/test.yarn" +dest_files=[ "res://.import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.res" ] + +[params] + +use_red_anyway=false diff --git a/main/scenes/test_viteuf.tscn b/main/scenes/test_viteuf.tscn new file mode 100644 index 0000000..299333b --- /dev/null +++ b/main/scenes/test_viteuf.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://main/scripts/test_viteuf.gd" type="Script" id=1] +[ext_resource path="res://main/dialogues/test.yarn" type="Resource" id=2] +[ext_resource path="res://addons/yarn_spinner/yarn_runner.gd" type="Script" id=3] + +[node name="Node" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="YarnRunner" type="Node" parent="."] +script = ExtResource( 3 ) +yarnScript = ExtResource( 2 ) + +[node name="RichTextLabel" type="RichTextLabel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 diff --git a/main/scripts/test_viteuf.gd b/main/scripts/test_viteuf.gd new file mode 100644 index 0000000..97570b9 --- /dev/null +++ b/main/scripts/test_viteuf.gd @@ -0,0 +1,24 @@ +extends Control + +func _ready(): + $YarnRunner.say_func = funcref(self, "say") + $YarnRunner.run_all() + +func say(element): + $RichTextLabel.text += str(element["raw"]) + "\n" + +func exec(stri): + var expr := Expression.new() + print("input : " + stri) + var err := expr.parse(stri) + if err == OK: + var res = expr.execute() + print("res : " + str(res)) + else: + print("err : " + str(err)) + +func jajoujaj(): + print("jaaaja") + +var current_node = "Start" +var current_line = 0 diff --git a/project.godot b/project.godot index 564cf45..d7b1c9a 100644 --- a/project.godot +++ b/project.godot @@ -35,6 +35,16 @@ _global_script_class_icons={ config/name="Chepa" config/icon="res://icon.png" +[editor_plugins] + +enabled=PoolStringArray( "yarn_spinner" ) + +[importer_defaults] + +yarn.script={ +"use_red_anyway": false +} + [rendering] quality/driver/driver_name="GLES2" diff --git a/yarn/scripts/yarn-importer.gd b/yarn/scripts/yarn-importer.gd index 9e95ca5..7d8b313 100644 --- a/yarn/scripts/yarn-importer.gd +++ b/yarn/scripts/yarn-importer.gd @@ -80,8 +80,21 @@ func new_yarn_thread(): thread['fibres'] = [] return thread +func exec(stri): + var expr := Expression.new() + print("input : " + stri) + var err := expr.parse(stri) + if err == OK: + var res = expr.execute() + print("res : " + str(res)) + else: + print("err : " + str(err)) + +func jajoujaj(): + print("jaaaja") + # Internally create a new fibre (during loading) -func new_yarn_fibre(line): +func new_yarn_fibre(line:String): # choice fibre if line.substr(0,2) == '[[': if line.find('|') != -1: @@ -102,16 +115,22 @@ func new_yarn_fibre(line): return fibre # logic instruction (not part of official Yarn standard) elif line.substr(0,2) == '<<': - if line.find(':') != -1: - var fibre = {} - fibre['kind'] = 'logic' - line = line.replace('<<', '') - line = line.replace('>>', '') - var split = line.split(':') - fibre['instruction'] = split[0] - fibre['command'] = split[1] - #print(line, split[0], split[1]) - return fibre + line = line.replace('<<', '') + line = line.replace('>>', '') + var line_split = line.split(' ') + print (line_split) + var command = line.substr(line.find(' ')) + exec(command) + +# if line.find(':') != -1: +# fibre['kind'] = 'logic' +# line = line.replace('<<', '') +# line = line.replace('>>', '') +# var split = line.split(':') +# fibre['instruction'] = split[0] +# fibre['command'] = split[1] +# #print(line, split[0], split[1]) +# return fibre # text fibre var fibre = {} fibre['kind'] = 'text'