You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

356 lines
9.3 KiB

  1. extends Node
  2. class_name YarnImporter
  3. #
  4. # A YARN Importer for Godot
  5. #
  6. # Credits:
  7. # - Dave Kerr (http://www.naturallyintelligent.com)
  8. #
  9. # Latest: https://github.com/naturally-intelligent/godot-yarn-importer
  10. #
  11. # Yarn: https://github.com/InfiniteAmmoInc/Yarn
  12. # Twine: http://twinery.org
  13. #
  14. # Yarn: a ball of threads (Yarn file)
  15. # Thread: a series of fibres (Yarn node)
  16. # Fibre: a text or choice or logic (Yarn line)
  17. var yarn = {}
  18. # OVERRIDE METHODS
  19. #
  20. # called to request new dialog
  21. func on_new_line(text):
  22. pass
  23. # called to request new choice button
  24. func on_choices(choices_list):
  25. pass
  26. # called to request internal logic handling
  27. func logic(instruction, command):
  28. pass
  29. # called for each line of text
  30. func yarn_text_variables(text):
  31. return text
  32. # called when "settings" node parsed
  33. func story_setting(setting, value):
  34. pass
  35. # called for each node name
  36. func on_node_start(to):
  37. pass
  38. yield(get_tree(), "idle_frame")
  39. # called for each node name (after)
  40. func on_node_end(to):
  41. pass
  42. yield(get_tree(), "idle_frame")
  43. # START SPINNING YOUR YARN
  44. #
  45. func spin_yarn(file, start_thread = false):
  46. yarn = load_yarn(file)
  47. # Find the starting thread...
  48. if not start_thread:
  49. start_thread = yarn['start']
  50. # Load any scene-specific settings
  51. # (Not part of official Yarn standard)
  52. if 'settings' in yarn['threads']:
  53. var settings = yarn['threads']['settings']
  54. for fibre in settings['fibres']:
  55. var line = fibre['text']
  56. var split = line.split('=')
  57. var setting = split[0].strip_edges(true, true)
  58. var value = split[1].strip_edges(true, true)
  59. story_setting(setting, value)
  60. # First thread unravel...
  61. yield(on_dialogue_start(), "completed")
  62. yield(yarn_unravel(start_thread), "completed")
  63. # Internally create a new thread (during loading)
  64. func new_yarn_thread():
  65. var thread = {}
  66. thread['title'] = ''
  67. thread['kind'] = 'branch' # 'branch' for standard dialog, 'code' for gdscript
  68. thread['tags'] = [] # unused
  69. thread['fibres'] = []
  70. return thread
  71. func exec(stri):
  72. var expr := Expression.new()
  73. print("input : " + stri)
  74. var err := expr.parse(stri)
  75. if err == OK:
  76. var res = expr.execute()
  77. print("res : " + str(res))
  78. else:
  79. print("err : " + str(err))
  80. func jajoujaj():
  81. print("jaaaja")
  82. # Internally create a new fibre (during loading)
  83. func new_yarn_fibre(line:String):
  84. # choice fibre
  85. if line.substr(0,2) == '[[':
  86. if line.find('|') != -1:
  87. var fibre = {}
  88. fibre['kind'] = 'choice'
  89. line = line.replace('[[', '')
  90. line = line.replace(']]', '')
  91. var split = line.split('|')
  92. fibre['text'] = split[0]
  93. fibre['marker'] = split[1]
  94. return fibre
  95. else:
  96. var fibre = {}
  97. fibre['kind'] = 'jump'
  98. line = line.replace('[[', '')
  99. line = line.replace(']]', '')
  100. fibre['marker'] = line
  101. return fibre
  102. # logic instruction (not part of official Yarn standard)
  103. elif line.substr(0,2) == '<<':
  104. line = line.replace('<<', '')
  105. line = line.replace('>>', '')
  106. var line_split = line.split(' ')
  107. print (line_split)
  108. var command = line.substr(line.find(' '))
  109. exec(command)
  110. # if line.find(':') != -1:
  111. # fibre['kind'] = 'logic'
  112. # line = line.replace('<<', '')
  113. # line = line.replace('>>', '')
  114. # var split = line.split(':')
  115. # fibre['instruction'] = split[0]
  116. # fibre['command'] = split[1]
  117. # #print(line, split[0], split[1])
  118. # return fibre
  119. # text fibre
  120. var fibre = {}
  121. fibre['kind'] = 'text'
  122. fibre['text'] = line
  123. return fibre
  124. # Create Yarn data structure from file (must be *.yarn.txt Yarn format)
  125. func load_yarn(path):
  126. var yarn = {}
  127. yarn['threads'] = {}
  128. yarn['start'] = false
  129. yarn['file'] = path
  130. var file = File.new()
  131. file.open(path, file.READ)
  132. if file.is_open():
  133. # yarn reading flags
  134. var start = false
  135. var header = true
  136. var thread = new_yarn_thread()
  137. # loop
  138. while !file.eof_reached():
  139. # read a line
  140. var line = file.get_line()
  141. # header read mode
  142. if header:
  143. if line == '---':
  144. header = false
  145. else:
  146. var split = line.split(': ')
  147. if split[0] == 'title':
  148. var title_split = split[1].split(':')
  149. var thread_title = ''
  150. var thread_kind = 'branch'
  151. if len(title_split) == 1:
  152. thread_title = split[1]
  153. else:
  154. thread_title = title_split[1]
  155. thread_kind = title_split[0]
  156. thread['title'] = thread_title
  157. thread['kind'] = thread_kind
  158. if not yarn['start']:
  159. yarn['start'] = thread_title
  160. # end of thread
  161. elif line == '===':
  162. header = true
  163. yarn['threads'][thread['title']] = thread
  164. thread = new_yarn_thread()
  165. # fibre read mode
  166. else:
  167. var fibre = new_yarn_fibre(line)
  168. if fibre:
  169. thread['fibres'].append(fibre)
  170. else:
  171. print('ERROR: Yarn file missing: ', filename)
  172. return yarn
  173. # Main logic for node handling
  174. #
  175. func yarn_unravel(to, from=false):
  176. if not to in yarn['threads']:
  177. print('WARNING: Missing Yarn thread: ', to, ' in file ',yarn['file'])
  178. return
  179. while to != null and to in yarn['threads']:
  180. yield (on_node_start(to), "completed")
  181. if to in yarn['threads']:
  182. var thread = yarn['threads'][to]
  183. to = null
  184. match thread['kind']:
  185. 'branch':
  186. var i = 0
  187. while i < thread['fibres'].size():
  188. match thread['fibres'][i]['kind']:
  189. 'text':
  190. var fibre = thread['fibres'][i]
  191. var text = yarn_text_variables(fibre['text'])
  192. yield(on_new_line(text), "completed")
  193. 'choice':
  194. var choices = []
  195. while i < thread['fibres'].size() and thread['fibres'][i]['kind'] == 'choice' :
  196. var fibre = thread['fibres'][i]
  197. var text = yarn_text_variables(fibre['text'])
  198. choices.push_back({"text": text, "marker": fibre['marker']})
  199. i += 1
  200. to = yield(on_choices(choices), "completed")
  201. break
  202. 'logic':
  203. var fibre = thread['fibres'][i]
  204. var instruction = fibre['instruction']
  205. var command = fibre['command']
  206. yield(logic(instruction, command), "completed")
  207. 'jump':
  208. var fibre = thread['fibres'][i]
  209. to = fibre['marker']
  210. break
  211. i += 1
  212. 'code':
  213. yarn_code(to)
  214. yield(on_node_end(to), "completed")
  215. yield (on_dialogue_end(), "completed")
  216. func on_dialogue_start():
  217. pass
  218. yield(get_tree(), "idle_frame")
  219. func on_dialogue_end():
  220. pass
  221. yield(get_tree(), "idle_frame")
  222. #
  223. # RUN GDSCRIPT CODE FROM YARN NODE - Special node = code:title
  224. # - Not part of official Yarn standard
  225. #
  226. func yarn_code(title, run=true, parent='parent.', tabs="\t", next_func="yarn_unravel"):
  227. if title in yarn['threads']:
  228. var thread = yarn['threads'][title]
  229. var code = ''
  230. for fibre in thread['fibres']:
  231. match fibre['kind']:
  232. 'text':
  233. var line = yarn_text_variables(fibre['text'])
  234. line = yarn_code_replace(line, parent, next_func)
  235. code += tabs + line + "\n"
  236. 'choice':
  237. var line = parent+next_func+"('"+fibre['marker']+"')"
  238. print(line)
  239. code += tabs + line + "\n"
  240. if run:
  241. run_yarn_code(code)
  242. else:
  243. return code
  244. else:
  245. print('WARNING: Title missing in yarn ball: ', title)
  246. # override to replace convenience variables
  247. func yarn_code_replace(code, parent='parent.', next_func="yarn_unravel"):
  248. if code.find("[[") != -1:
  249. code = code.replace("[[", parent+next_func+"('")
  250. code = code.replace("]]", "')")
  251. code = code.replace("say(", parent+"say(")
  252. code = code.replace("choice(", parent+"choice(")
  253. return code
  254. func run_yarn_code(code):
  255. var front = "extends Node\n"
  256. front += "func dynamic_code():\n"
  257. front += "\tvar parent = get_parent()\n\n"
  258. code = front + code
  259. #print("CODE BLOCK: \n", code)
  260. var script = GDScript.new()
  261. script.set_source_code(code)
  262. script.reload()
  263. #print("Executing code...")
  264. var node = Node.new()
  265. node.set_script(script)
  266. add_child(node)
  267. var result = node.dynamic_code()
  268. remove_child(node)
  269. return result
  270. # EXPORTING TO GDSCRIPT
  271. #
  272. # This code may not be directly usable
  273. # Use if you need an exit from Yarn
  274. func export_to_gdscript():
  275. var script = ''
  276. script += "func start_story():\n\n"
  277. if 'settings' in yarn['threads']:
  278. var settings = yarn['threads']['settings']
  279. for fibre in settings['fibres']:
  280. var line = fibre['text']
  281. var split = line.split('=')
  282. var setting = split[0].strip_edges(true, true)
  283. var value = split[1].strip_edges(true, true)
  284. script += "\t" + 'story_setting("' + setting + '", "' + value + '")' + "\n"
  285. script += "\tstory_logic('" + yarn['start'] + "')\n\n"
  286. # story logic choice/press event
  287. script += "func story_logic(marker):\n\n"
  288. script += "\tmatch marker:\n"
  289. for title in yarn['threads']:
  290. var thread = yarn['threads'][title]
  291. match thread['kind']:
  292. 'branch':
  293. var code = "\n\t\t'" + thread['title'] + "':"
  294. var tabs = "\n\t\t\t"
  295. for fibre in thread['fibres']:
  296. match fibre['kind']:
  297. 'text':
  298. code += tabs + 'say("' + fibre['text'] + '")'
  299. 'choice':
  300. code += tabs + 'choice("' + fibre['text'] + '", "' + fibre['marker'] + '")'
  301. 'logic':
  302. code += tabs + 'logic("' + fibre['instruction'] + '", "' + fibre['command'] + '")'
  303. script += code + "\n"
  304. 'code':
  305. var code = "\n\t\t'" + thread['title'] + "':"
  306. var tabs = "\n\t\t\t"
  307. code += "\n"
  308. code += yarn_code(thread['title'], false, '', "\t\t\t", "story_logic")
  309. script += code + "\n"
  310. # done
  311. return script
  312. func print_gdscript_to_console():
  313. print(export_to_gdscript())
  314. func save_to_gdscript(filename):
  315. var script = export_to_gdscript()
  316. # write to file
  317. var file = File.new()
  318. file.open(filename, file.WRITE)
  319. if not file.is_open():
  320. print('ERROR: Cant open file ', filename)
  321. return false
  322. file.store_string(script)
  323. file.close()