Browse Source

Basic yarn parsing and running

master
Non0w 3 years ago
parent
commit
e46e53db1a
19 changed files with 457 additions and 13 deletions
  1. +3
    -0
      .import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5
  2. BIN
      .import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex
  3. +3
    -0
      .import/test - Copie.txt-05f23fb6780d7fb8747b6c25d01e926c.md5
  4. +3
    -0
      .import/test - Copie.yarn-bfaf9553976186f49f9681f51669695e.md5
  5. +3
    -0
      .import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.md5
  6. BIN
      .import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.res
  7. BIN
      addons/yarn_spinner/icon.png
  8. +34
    -0
      addons/yarn_spinner/icon.png.import
  9. +7
    -0
      addons/yarn_spinner/plugin.cfg
  10. +51
    -0
      addons/yarn_spinner/yarn_runner.gd
  11. +3
    -0
      addons/yarn_spinner/yarn_script.gd
  12. +232
    -0
      addons/yarn_spinner/yarn_spinner_importer.gd
  13. +16
    -0
      addons/yarn_spinner/yarn_spinner_plugin.gd
  14. +3
    -2
      main/dialogues/test.yarn
  15. +14
    -0
      main/dialogues/test.yarn.import
  16. +21
    -0
      main/scenes/test_viteuf.tscn
  17. +24
    -0
      main/scripts/test_viteuf.gd
  18. +10
    -0
      project.godot
  19. +30
    -11
      yarn/scripts/yarn-importer.gd

+ 3
- 0
.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.md5 View File

@@ -0,0 +1,3 @@
source_md5="10b14a4273e3c8eb3d990fd5ce59afed"
dest_md5="06821280489269c54a81298e6d9b3949"


BIN
.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex View File


+ 3
- 0
.import/test - Copie.txt-05f23fb6780d7fb8747b6c25d01e926c.md5 View File

@@ -0,0 +1,3 @@
source_md5="52422b01794998083b364be682eb840a"
dest_md5="d41d8cd98f00b204e9800998ecf8427e"


+ 3
- 0
.import/test - Copie.yarn-bfaf9553976186f49f9681f51669695e.md5 View File

@@ -0,0 +1,3 @@
source_md5="52422b01794998083b364be682eb840a"
dest_md5="d41d8cd98f00b204e9800998ecf8427e"


+ 3
- 0
.import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.md5 View File

@@ -0,0 +1,3 @@
source_md5="9da48ae0e2c8858122bdf1a8b8dd698e"
dest_md5="348cbedcb0148126d176d7cbcee8e36c"


BIN
.import/test.yarn-642d0590cee80e9b0bd554c3bdc33727.res View File


BIN
addons/yarn_spinner/icon.png View File

Before After
Width: 16  |  Height: 16  |  Size: 2.1 KiB

+ 34
- 0
addons/yarn_spinner/icon.png.import View File

@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="StreamTexture"
path="res://.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/yarn_spinner/icon.png"
dest_files=[ "res://.import/icon.png-6587814821ca75f730cf7dfe8a5167cb.stex" ]

[params]

compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

+ 7
- 0
addons/yarn_spinner/plugin.cfg View File

@@ -0,0 +1,7 @@
[plugin]

name="Yarn Spinner"
description="A Yarn Spinner importer and runner for Godot"
author="Arnaud Cojez"
version="0.0.1"
script="yarn_spinner_plugin.gd"

+ 51
- 0
addons/yarn_spinner/yarn_runner.gd View File

@@ -0,0 +1,51 @@
extends Node

var YarnScript = preload("yarn_script.gd")

export (Resource) var yarnScript

var say_func : FuncRef = null

func run_all():
var next_node = yarnScript.nodes["Start"]
while next_node != null:
print("-> 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

+ 3
- 0
addons/yarn_spinner/yarn_script.gd View File

@@ -0,0 +1,3 @@
extends Resource

export var nodes = {}

+ 232
- 0
addons/yarn_spinner/yarn_spinner_importer.gd View File

@@ -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

+ 16
- 0
addons/yarn_spinner/yarn_spinner_plugin.gd View File

@@ -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

+ 3
- 2
main/dialogues/test.yarn View File

@@ -12,8 +12,9 @@ tags:
position: 1032.2125854492188,730.3896484375
---
Huhuhu
<<if $truc is True>>
OK !
<<if 1 > 2 >>
OK !!
[[zboub]]
<<else>>
Pas ok.
<<endif>>


+ 14
- 0
main/dialogues/test.yarn.import View File

@@ -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

+ 21
- 0
main/scenes/test_viteuf.tscn View File

@@ -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

+ 24
- 0
main/scripts/test_viteuf.gd View File

@@ -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

+ 10
- 0
project.godot View File

@@ -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"


+ 30
- 11
yarn/scripts/yarn-importer.gd View File

@@ -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'


Loading…
Cancel
Save