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":[]} # print("[" + parsed_line["type"] + "]\n\t" + str(parsed_line)) 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): if token == "False": token = "false" elif token == "True": token = "true" 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: _: return [] func get_option_visibility(option, options): return true