@magicborn/dialogue-forge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +233 -0
  2. package/bin/dialogue-forge.js +78 -0
  3. package/demo/app/layout.tsx +36 -0
  4. package/demo/app/page.tsx +440 -0
  5. package/demo/components/ThemeSwitcher.tsx +611 -0
  6. package/demo/next.config.mjs +7 -0
  7. package/demo/package.json +29 -0
  8. package/demo/postcss.config.mjs +7 -0
  9. package/demo/public/logo.svg +1 -0
  10. package/demo/styles/globals.css +19 -0
  11. package/demo/tailwind.config.ts +90 -0
  12. package/demo/tsconfig.json +42 -0
  13. package/dist/components/ChoiceEdgeV2.d.ts +3 -0
  14. package/dist/components/ChoiceEdgeV2.js +103 -0
  15. package/dist/components/CodeBlock.d.ts +8 -0
  16. package/dist/components/CodeBlock.js +24 -0
  17. package/dist/components/ConditionAutocomplete.d.ts +14 -0
  18. package/dist/components/ConditionAutocomplete.js +284 -0
  19. package/dist/components/ConditionalNodeV2.d.ts +16 -0
  20. package/dist/components/ConditionalNodeV2.js +147 -0
  21. package/dist/components/DialogueEditorV2.d.ts +22 -0
  22. package/dist/components/DialogueEditorV2.js +1170 -0
  23. package/dist/components/EdgeIcon.d.ts +8 -0
  24. package/dist/components/EdgeIcon.js +13 -0
  25. package/dist/components/ExampleLoader.d.ts +11 -0
  26. package/dist/components/ExampleLoader.js +52 -0
  27. package/dist/components/ExampleLoaderButton.d.ts +15 -0
  28. package/dist/components/ExampleLoaderButton.js +102 -0
  29. package/dist/components/FlagManager.d.ts +11 -0
  30. package/dist/components/FlagManager.js +282 -0
  31. package/dist/components/FlagSelector.d.ts +11 -0
  32. package/dist/components/FlagSelector.js +235 -0
  33. package/dist/components/GuidePanel.d.ts +7 -0
  34. package/dist/components/GuidePanel.js +1176 -0
  35. package/dist/components/Minimap.d.ts +16 -0
  36. package/dist/components/Minimap.js +93 -0
  37. package/dist/components/NPCEdgeV2.d.ts +3 -0
  38. package/dist/components/NPCEdgeV2.js +104 -0
  39. package/dist/components/NPCNodeV2.d.ts +26 -0
  40. package/dist/components/NPCNodeV2.js +86 -0
  41. package/dist/components/NodeEditor.d.ts +18 -0
  42. package/dist/components/NodeEditor.js +1025 -0
  43. package/dist/components/PlayView.d.ts +12 -0
  44. package/dist/components/PlayView.js +307 -0
  45. package/dist/components/PlayerNodeV2.d.ts +16 -0
  46. package/dist/components/PlayerNodeV2.js +139 -0
  47. package/dist/components/ReactFlowPOC.d.ts +61 -0
  48. package/dist/components/ReactFlowPOC.js +312 -0
  49. package/dist/components/ScenePlayer.d.ts +18 -0
  50. package/dist/components/ScenePlayer.js +196 -0
  51. package/dist/components/YarnView.d.ts +9 -0
  52. package/dist/components/YarnView.js +45 -0
  53. package/dist/components/ZoomControls.d.ts +11 -0
  54. package/dist/components/ZoomControls.js +34 -0
  55. package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
  56. package/dist/esm/components/ChoiceEdgeV2.js +67 -0
  57. package/dist/esm/components/CodeBlock.d.ts +8 -0
  58. package/dist/esm/components/CodeBlock.js +18 -0
  59. package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
  60. package/dist/esm/components/ConditionAutocomplete.js +248 -0
  61. package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
  62. package/dist/esm/components/ConditionalNodeV2.js +111 -0
  63. package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
  64. package/dist/esm/components/DialogueEditorV2.js +1134 -0
  65. package/dist/esm/components/EdgeIcon.d.ts +8 -0
  66. package/dist/esm/components/EdgeIcon.js +7 -0
  67. package/dist/esm/components/ExampleLoader.d.ts +11 -0
  68. package/dist/esm/components/ExampleLoader.js +46 -0
  69. package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
  70. package/dist/esm/components/ExampleLoaderButton.js +66 -0
  71. package/dist/esm/components/FlagManager.d.ts +11 -0
  72. package/dist/esm/components/FlagManager.js +246 -0
  73. package/dist/esm/components/FlagSelector.d.ts +11 -0
  74. package/dist/esm/components/FlagSelector.js +199 -0
  75. package/dist/esm/components/GuidePanel.d.ts +7 -0
  76. package/dist/esm/components/GuidePanel.js +1140 -0
  77. package/dist/esm/components/Minimap.d.ts +16 -0
  78. package/dist/esm/components/Minimap.js +57 -0
  79. package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
  80. package/dist/esm/components/NPCEdgeV2.js +68 -0
  81. package/dist/esm/components/NPCNodeV2.d.ts +26 -0
  82. package/dist/esm/components/NPCNodeV2.js +80 -0
  83. package/dist/esm/components/NodeEditor.d.ts +18 -0
  84. package/dist/esm/components/NodeEditor.js +989 -0
  85. package/dist/esm/components/PlayView.d.ts +12 -0
  86. package/dist/esm/components/PlayView.js +271 -0
  87. package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
  88. package/dist/esm/components/PlayerNodeV2.js +103 -0
  89. package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
  90. package/dist/esm/components/ReactFlowPOC.js +275 -0
  91. package/dist/esm/components/ScenePlayer.d.ts +18 -0
  92. package/dist/esm/components/ScenePlayer.js +160 -0
  93. package/dist/esm/components/YarnView.d.ts +9 -0
  94. package/dist/esm/components/YarnView.js +39 -0
  95. package/dist/esm/components/ZoomControls.d.ts +11 -0
  96. package/dist/esm/components/ZoomControls.js +28 -0
  97. package/dist/esm/examples/example-loader.d.ts +29 -0
  98. package/dist/esm/examples/example-loader.js +103 -0
  99. package/dist/esm/examples/examples-registry.d.ts +38 -0
  100. package/dist/esm/examples/examples-registry.js +153 -0
  101. package/dist/esm/examples/index.d.ts +26 -0
  102. package/dist/esm/examples/index.js +50 -0
  103. package/dist/esm/examples/legacy-examples.d.ts +9 -0
  104. package/dist/esm/examples/legacy-examples.js +814 -0
  105. package/dist/esm/examples/yarn-examples.d.ts +35 -0
  106. package/dist/esm/examples/yarn-examples.js +181 -0
  107. package/dist/esm/index.d.ts +21 -0
  108. package/dist/esm/index.js +26 -0
  109. package/dist/esm/lib/flag-manager.d.ts +21 -0
  110. package/dist/esm/lib/flag-manager.js +93 -0
  111. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  112. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
  113. package/dist/esm/lib/yarn-converter.d.ts +17 -0
  114. package/dist/esm/lib/yarn-converter.js +521 -0
  115. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  116. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
  117. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  118. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
  119. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  120. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
  121. package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  122. package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
  123. package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
  124. package/dist/esm/lib/yarn-runner/index.js +11 -0
  125. package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
  126. package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
  127. package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
  128. package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
  129. package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
  130. package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
  131. package/dist/esm/types/conditionals.d.ts +29 -0
  132. package/dist/esm/types/conditionals.js +1 -0
  133. package/dist/esm/types/constants.d.ts +59 -0
  134. package/dist/esm/types/constants.js +55 -0
  135. package/dist/esm/types/flags.d.ts +49 -0
  136. package/dist/esm/types/flags.js +49 -0
  137. package/dist/esm/types/game-state.d.ts +62 -0
  138. package/dist/esm/types/game-state.js +6 -0
  139. package/dist/esm/types/index.d.ts +77 -0
  140. package/dist/esm/types/index.js +1 -0
  141. package/dist/esm/utils/constants.d.ts +5 -0
  142. package/dist/esm/utils/constants.js +5 -0
  143. package/dist/esm/utils/feature-flags.d.ts +11 -0
  144. package/dist/esm/utils/feature-flags.js +11 -0
  145. package/dist/esm/utils/game-state-flattener.d.ts +41 -0
  146. package/dist/esm/utils/game-state-flattener.js +135 -0
  147. package/dist/esm/utils/layout/collision.d.ts +27 -0
  148. package/dist/esm/utils/layout/collision.js +74 -0
  149. package/dist/esm/utils/layout/index.d.ts +82 -0
  150. package/dist/esm/utils/layout/index.js +98 -0
  151. package/dist/esm/utils/layout/registry.d.ts +91 -0
  152. package/dist/esm/utils/layout/registry.js +148 -0
  153. package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
  154. package/dist/esm/utils/layout/strategies/dagre.js +182 -0
  155. package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
  156. package/dist/esm/utils/layout/strategies/force.js +178 -0
  157. package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
  158. package/dist/esm/utils/layout/strategies/grid.js +91 -0
  159. package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
  160. package/dist/esm/utils/layout/strategies/index.js +8 -0
  161. package/dist/esm/utils/layout/types.d.ts +100 -0
  162. package/dist/esm/utils/layout/types.js +7 -0
  163. package/dist/esm/utils/layout.d.ts +9 -0
  164. package/dist/esm/utils/layout.js +17 -0
  165. package/dist/esm/utils/node-helpers.d.ts +7 -0
  166. package/dist/esm/utils/node-helpers.js +94 -0
  167. package/dist/esm/utils/reactflow-converter.d.ts +42 -0
  168. package/dist/esm/utils/reactflow-converter.js +217 -0
  169. package/dist/examples/example-loader.d.ts +29 -0
  170. package/dist/examples/example-loader.js +109 -0
  171. package/dist/examples/examples-registry.d.ts +38 -0
  172. package/dist/examples/examples-registry.js +160 -0
  173. package/dist/examples/index.d.ts +26 -0
  174. package/dist/examples/index.js +63 -0
  175. package/dist/examples/legacy-examples.d.ts +9 -0
  176. package/dist/examples/legacy-examples.js +817 -0
  177. package/dist/examples/yarn-examples.d.ts +35 -0
  178. package/dist/examples/yarn-examples.js +189 -0
  179. package/dist/index.d.ts +21 -0
  180. package/dist/index.js +66 -0
  181. package/dist/lib/flag-manager.d.ts +21 -0
  182. package/dist/lib/flag-manager.js +99 -0
  183. package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  184. package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
  185. package/dist/lib/yarn-converter.d.ts +17 -0
  186. package/dist/lib/yarn-converter.js +525 -0
  187. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  188. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
  189. package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  190. package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
  191. package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  192. package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
  193. package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  194. package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
  195. package/dist/lib/yarn-runner/index.d.ts +12 -0
  196. package/dist/lib/yarn-runner/index.js +21 -0
  197. package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
  198. package/dist/lib/yarn-runner/node-processor.js +133 -0
  199. package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
  200. package/dist/lib/yarn-runner/variable-manager.js +124 -0
  201. package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
  202. package/dist/lib/yarn-runner/variable-operations.js +92 -0
  203. package/dist/types/conditionals.d.ts +29 -0
  204. package/dist/types/conditionals.js +2 -0
  205. package/dist/types/constants.d.ts +59 -0
  206. package/dist/types/constants.js +58 -0
  207. package/dist/types/flags.d.ts +49 -0
  208. package/dist/types/flags.js +52 -0
  209. package/dist/types/game-state.d.ts +62 -0
  210. package/dist/types/game-state.js +7 -0
  211. package/dist/types/index.d.ts +77 -0
  212. package/dist/types/index.js +2 -0
  213. package/dist/utils/constants.d.ts +5 -0
  214. package/dist/utils/constants.js +8 -0
  215. package/dist/utils/feature-flags.d.ts +11 -0
  216. package/dist/utils/feature-flags.js +14 -0
  217. package/dist/utils/game-state-flattener.d.ts +41 -0
  218. package/dist/utils/game-state-flattener.js +140 -0
  219. package/dist/utils/layout/collision.d.ts +27 -0
  220. package/dist/utils/layout/collision.js +77 -0
  221. package/dist/utils/layout/index.d.ts +82 -0
  222. package/dist/utils/layout/index.js +109 -0
  223. package/dist/utils/layout/registry.d.ts +91 -0
  224. package/dist/utils/layout/registry.js +151 -0
  225. package/dist/utils/layout/strategies/dagre.d.ts +19 -0
  226. package/dist/utils/layout/strategies/dagre.js +189 -0
  227. package/dist/utils/layout/strategies/force.d.ts +21 -0
  228. package/dist/utils/layout/strategies/force.js +182 -0
  229. package/dist/utils/layout/strategies/grid.d.ts +17 -0
  230. package/dist/utils/layout/strategies/grid.js +95 -0
  231. package/dist/utils/layout/strategies/index.d.ts +8 -0
  232. package/dist/utils/layout/strategies/index.js +14 -0
  233. package/dist/utils/layout/types.d.ts +100 -0
  234. package/dist/utils/layout/types.js +8 -0
  235. package/dist/utils/layout.d.ts +9 -0
  236. package/dist/utils/layout.js +25 -0
  237. package/dist/utils/node-helpers.d.ts +7 -0
  238. package/dist/utils/node-helpers.js +101 -0
  239. package/dist/utils/reactflow-converter.d.ts +42 -0
  240. package/dist/utils/reactflow-converter.js +223 -0
  241. package/package.json +70 -0
@@ -0,0 +1,525 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportToYarn = exportToYarn;
4
+ exports.importFromYarn = importFromYarn;
5
+ /**
6
+ * Convert DialogueTree to Yarn Spinner format
7
+ *
8
+ * Flags are converted to Yarn variables ($variable).
9
+ * Variables are NOT stored in the .yarn file - they're managed by
10
+ * Yarn Spinner's Variable Storage at runtime.
11
+ *
12
+ * The .yarn file contains commands like:
13
+ * - <<set $flag_name = value>> - Sets variable in Variable Storage
14
+ * - <<if $flag_name>> - Checks variable in Variable Storage
15
+ */
16
+ function exportToYarn(tree) {
17
+ let yarn = '';
18
+ Object.values(tree.nodes).forEach(node => {
19
+ yarn += `title: ${node.id}\n`;
20
+ yarn += `---\n`;
21
+ if (node.type === 'npc') {
22
+ // Export conditional blocks if present
23
+ if (node.conditionalBlocks && node.conditionalBlocks.length > 0) {
24
+ node.conditionalBlocks.forEach(block => {
25
+ if (block.type === 'if' || block.type === 'elseif') {
26
+ // Build condition string
27
+ const conditions = block.condition?.map(cond => {
28
+ const varName = `$${cond.flag}`;
29
+ if (cond.operator === 'is_set') {
30
+ return varName;
31
+ }
32
+ else if (cond.operator === 'is_not_set') {
33
+ return `not ${varName}`;
34
+ }
35
+ else if (cond.value !== undefined) {
36
+ const op = cond.operator === 'equals' ? '==' :
37
+ cond.operator === 'not_equals' ? '!=' :
38
+ cond.operator === 'greater_than' ? '>' :
39
+ cond.operator === 'less_than' ? '<' :
40
+ cond.operator === 'greater_equal' ? '>=' :
41
+ cond.operator === 'less_equal' ? '<=' : '==';
42
+ const value = typeof cond.value === 'string' ? `"${cond.value}"` : cond.value;
43
+ return `${varName} ${op} ${value}`;
44
+ }
45
+ return '';
46
+ }).filter(c => c).join(' and ') || '';
47
+ yarn += `<<${block.type} ${conditions}>>\n`;
48
+ }
49
+ else if (block.type === 'else') {
50
+ yarn += `<<else>>\n`;
51
+ }
52
+ // Export block content (remove set commands from content, they'll be exported separately)
53
+ let blockContent = block.content;
54
+ const setCommandsInBlock = blockContent.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
55
+ if (setCommandsInBlock) {
56
+ // Remove set commands from content
57
+ setCommandsInBlock.forEach(cmd => {
58
+ blockContent = blockContent.replace(cmd, '').trim();
59
+ });
60
+ }
61
+ if (block.speaker) {
62
+ yarn += `${block.speaker}: ${blockContent.replace(/\n/g, '\n' + block.speaker + ': ')}\n`;
63
+ }
64
+ else {
65
+ yarn += `${blockContent}\n`;
66
+ }
67
+ // Export variable operations from this block
68
+ if (setCommandsInBlock) {
69
+ setCommandsInBlock.forEach(cmd => {
70
+ yarn += `${cmd}\n`;
71
+ });
72
+ }
73
+ });
74
+ yarn += `<<endif>>\n`;
75
+ }
76
+ else {
77
+ // Regular content (no conditionals)
78
+ let content = node.content;
79
+ const setCommandsInContent = content.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
80
+ if (setCommandsInContent) {
81
+ // Remove set commands from content
82
+ setCommandsInContent.forEach(cmd => {
83
+ content = content.replace(cmd, '').trim();
84
+ });
85
+ }
86
+ if (node.speaker) {
87
+ yarn += `${node.speaker}: ${content.replace(/\n/g, '\n' + node.speaker + ': ')}\n`;
88
+ }
89
+ else {
90
+ yarn += `${content}\n`;
91
+ }
92
+ }
93
+ // Export flags as Yarn variable commands
94
+ // Check if content contains variable operations first
95
+ const setCommandsInContent = node.content.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
96
+ if (setCommandsInContent) {
97
+ // Export the operations found in content
98
+ setCommandsInContent.forEach(cmd => {
99
+ yarn += `${cmd}\n`;
100
+ });
101
+ }
102
+ else if (node.setFlags?.length) {
103
+ // Fallback: export as boolean flags
104
+ node.setFlags.forEach(flag => {
105
+ yarn += `<<set $${flag} = true>>\n`;
106
+ });
107
+ }
108
+ if (node.nextNodeId) {
109
+ yarn += `<<jump ${node.nextNodeId}>>\n`;
110
+ }
111
+ }
112
+ else if (node.type === 'conditional' && node.conditionalBlocks) {
113
+ // Export conditional node blocks
114
+ node.conditionalBlocks.forEach(block => {
115
+ if (block.type === 'if' || block.type === 'elseif') {
116
+ // Build condition string
117
+ const conditions = block.condition?.map(cond => {
118
+ const varName = `$${cond.flag}`;
119
+ if (cond.operator === 'is_set') {
120
+ return varName;
121
+ }
122
+ else if (cond.operator === 'is_not_set') {
123
+ return `not ${varName}`;
124
+ }
125
+ else if (cond.value !== undefined) {
126
+ const op = cond.operator === 'equals' ? '==' :
127
+ cond.operator === 'not_equals' ? '!=' :
128
+ cond.operator === 'greater_than' ? '>' :
129
+ cond.operator === 'less_than' ? '<' :
130
+ cond.operator === 'greater_equal' ? '>=' :
131
+ cond.operator === 'less_equal' ? '<=' : '==';
132
+ const value = typeof cond.value === 'string' ? `"${cond.value}"` : cond.value;
133
+ return `${varName} ${op} ${value}`;
134
+ }
135
+ return '';
136
+ }).filter(c => c).join(' and ') || '';
137
+ yarn += `<<${block.type} ${conditions}>>\n`;
138
+ }
139
+ else if (block.type === 'else') {
140
+ yarn += `<<else>>\n`;
141
+ }
142
+ // Export block content (remove set commands from content)
143
+ let blockContent = block.content;
144
+ const setCommandsInBlock = blockContent.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
145
+ if (setCommandsInBlock) {
146
+ // Remove set commands from content
147
+ setCommandsInBlock.forEach(cmd => {
148
+ blockContent = blockContent.replace(cmd, '').trim();
149
+ });
150
+ }
151
+ if (block.speaker) {
152
+ yarn += `${block.speaker}: ${blockContent.replace(/\n/g, '\n' + block.speaker + ': ')}\n`;
153
+ }
154
+ else {
155
+ yarn += `${blockContent}\n`;
156
+ }
157
+ // Export variable operations from this block
158
+ if (setCommandsInBlock) {
159
+ setCommandsInBlock.forEach(cmd => {
160
+ yarn += `${cmd}\n`;
161
+ });
162
+ }
163
+ // Export block's nextNodeId if present
164
+ if (block.nextNodeId) {
165
+ yarn += `<<jump ${block.nextNodeId}>>\n`;
166
+ }
167
+ });
168
+ yarn += `<<endif>>\n`;
169
+ // Export flags as Yarn variable commands
170
+ const setCommandsInNode = node.content?.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
171
+ if (setCommandsInNode) {
172
+ setCommandsInNode.forEach(cmd => {
173
+ yarn += `${cmd}\n`;
174
+ });
175
+ }
176
+ else if (node.setFlags?.length) {
177
+ node.setFlags.forEach(flag => {
178
+ yarn += `<<set $${flag} = true>>\n`;
179
+ });
180
+ }
181
+ // Conditional nodes don't have a main nextNodeId (each block has its own)
182
+ }
183
+ else if (node.type === 'player' && node.choices) {
184
+ node.choices.forEach(choice => {
185
+ // Export conditions as Yarn if statements (wrap the choice)
186
+ if (choice.conditions && choice.conditions.length > 0) {
187
+ // Combine multiple conditions with AND logic
188
+ const conditions = choice.conditions.map(cond => {
189
+ const varName = `$${cond.flag}`;
190
+ if (cond.operator === 'is_set') {
191
+ return varName;
192
+ }
193
+ else if (cond.operator === 'is_not_set') {
194
+ return `not ${varName}`;
195
+ }
196
+ else if (cond.value !== undefined) {
197
+ // Comparison operators
198
+ const op = cond.operator === 'equals' ? '==' :
199
+ cond.operator === 'not_equals' ? '!=' :
200
+ cond.operator === 'greater_than' ? '>' :
201
+ cond.operator === 'less_than' ? '<' :
202
+ cond.operator === 'greater_equal' ? '>=' :
203
+ cond.operator === 'less_equal' ? '<=' : '==';
204
+ const value = typeof cond.value === 'string' ? `"${cond.value}"` : cond.value;
205
+ return `${varName} ${op} ${value}`;
206
+ }
207
+ return '';
208
+ }).filter(c => c).join(' and ');
209
+ if (conditions) {
210
+ yarn += `<<if ${conditions}>>\n`;
211
+ }
212
+ }
213
+ // Remove set commands from choice text before exporting
214
+ let choiceText = choice.text;
215
+ const setCommandsInText = choiceText.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/g);
216
+ if (setCommandsInText) {
217
+ // Remove set commands from text
218
+ setCommandsInText.forEach(cmd => {
219
+ choiceText = choiceText.replace(cmd, '').trim();
220
+ });
221
+ }
222
+ yarn += `-> ${choiceText}\n`;
223
+ // Export flags set by this choice
224
+ if (setCommandsInText) {
225
+ // Export the operations found in text
226
+ setCommandsInText.forEach(cmd => {
227
+ yarn += ` ${cmd}\n`;
228
+ });
229
+ }
230
+ else if (choice.setFlags?.length) {
231
+ // Fallback: export as boolean flags
232
+ choice.setFlags.forEach(flag => {
233
+ yarn += ` <<set $${flag} = true>>\n`;
234
+ });
235
+ }
236
+ if (choice.nextNodeId) {
237
+ yarn += ` <<jump ${choice.nextNodeId}>>\n`;
238
+ }
239
+ if (choice.conditions && choice.conditions.length > 0) {
240
+ yarn += `<<endif>>\n`;
241
+ }
242
+ });
243
+ }
244
+ yarn += `===\n\n`;
245
+ });
246
+ return yarn;
247
+ }
248
+ /**
249
+ * Parse Yarn Spinner format to DialogueTree
250
+ */
251
+ function importFromYarn(yarnContent, title = 'Imported Dialogue') {
252
+ const nodes = {};
253
+ const nodeBlocks = yarnContent.split('===').filter(b => b.trim());
254
+ let y = 50;
255
+ nodeBlocks.forEach((block, idx) => {
256
+ const titleMatch = block.match(/title:\s*(\S+)/);
257
+ if (!titleMatch)
258
+ return;
259
+ const nodeId = titleMatch[1];
260
+ const contentStart = block.indexOf('---');
261
+ if (contentStart === -1)
262
+ return;
263
+ const content = block.slice(contentStart + 3).trim();
264
+ const lines = content.split('\n').filter(l => l.trim());
265
+ const choices = [];
266
+ let dialogueContent = '';
267
+ let speaker = '';
268
+ const setFlags = [];
269
+ let nextNodeId = '';
270
+ const conditionalBlocks = [];
271
+ // Track conditional block state
272
+ let inConditionalBlock = false;
273
+ let currentBlock = null;
274
+ let blockContent = [];
275
+ let blockSpeaker = '';
276
+ // Track conditional choice state (for <<if>> blocks that wrap choices)
277
+ let inConditionalChoice = false;
278
+ let currentChoiceCondition = [];
279
+ const parseCondition = (conditionStr) => {
280
+ // Parse condition string like "$flag", "not $flag", "$flag == 5", etc.
281
+ const conditions = [];
282
+ // Split by 'and' for multiple conditions
283
+ const parts = conditionStr.split(/\s+and\s+/i);
284
+ parts.forEach(part => {
285
+ part = part.trim();
286
+ if (part.startsWith('not ')) {
287
+ const varMatch = part.match(/not\s+\$(\w+)/);
288
+ if (varMatch) {
289
+ conditions.push({ flag: varMatch[1], operator: 'is_not_set' });
290
+ }
291
+ }
292
+ else if (part.includes('==')) {
293
+ const match = part.match(/\$(\w+)\s*==\s*(.+)/);
294
+ if (match) {
295
+ const value = match[2].trim().replace(/^["']|["']$/g, '');
296
+ conditions.push({ flag: match[1], operator: 'equals', value });
297
+ }
298
+ }
299
+ else if (part.includes('!=')) {
300
+ const match = part.match(/\$(\w+)\s*!=\s*(.+)/);
301
+ if (match) {
302
+ const value = match[2].trim().replace(/^["']|["']$/g, '');
303
+ conditions.push({ flag: match[1], operator: 'not_equals', value });
304
+ }
305
+ }
306
+ else if (part.includes('>=')) {
307
+ const match = part.match(/\$(\w+)\s*>=\s*(.+)/);
308
+ if (match) {
309
+ conditions.push({ flag: match[1], operator: 'greater_equal', value: parseFloat(match[2]) });
310
+ }
311
+ }
312
+ else if (part.includes('<=')) {
313
+ const match = part.match(/\$(\w+)\s*<=\s*(.+)/);
314
+ if (match) {
315
+ conditions.push({ flag: match[1], operator: 'less_equal', value: parseFloat(match[2]) });
316
+ }
317
+ }
318
+ else if (part.includes('>')) {
319
+ const match = part.match(/\$(\w+)\s*>\s*(.+)/);
320
+ if (match) {
321
+ conditions.push({ flag: match[1], operator: 'greater_than', value: parseFloat(match[2]) });
322
+ }
323
+ }
324
+ else if (part.includes('<')) {
325
+ const match = part.match(/\$(\w+)\s*<\s*(.+)/);
326
+ if (match) {
327
+ conditions.push({ flag: match[1], operator: 'less_than', value: parseFloat(match[2]) });
328
+ }
329
+ }
330
+ else {
331
+ // Simple flag check
332
+ const varMatch = part.match(/\$(\w+)/);
333
+ if (varMatch) {
334
+ conditions.push({ flag: varMatch[1], operator: 'is_set' });
335
+ }
336
+ }
337
+ });
338
+ return conditions;
339
+ };
340
+ lines.forEach(line => {
341
+ const trimmed = line.trim();
342
+ // Skip empty lines
343
+ if (!trimmed)
344
+ return;
345
+ if (trimmed.startsWith('->')) {
346
+ const choiceText = trimmed.slice(2).trim();
347
+ const choice = {
348
+ id: `c_${Date.now()}_${choices.length}`,
349
+ text: choiceText,
350
+ nextNodeId: ''
351
+ };
352
+ // If we're in a conditional choice context, apply the condition
353
+ if (inConditionalChoice && currentChoiceCondition.length > 0) {
354
+ choice.conditions = currentChoiceCondition;
355
+ // Reset for next choice (if any)
356
+ inConditionalChoice = false;
357
+ currentChoiceCondition = [];
358
+ }
359
+ choices.push(choice);
360
+ }
361
+ else if (trimmed.startsWith('<<jump')) {
362
+ const jumpMatch = trimmed.match(/<<jump\s+(\S+)>>/);
363
+ if (jumpMatch) {
364
+ if (choices.length > 0) {
365
+ choices[choices.length - 1].nextNodeId = jumpMatch[1];
366
+ }
367
+ else {
368
+ nextNodeId = jumpMatch[1];
369
+ }
370
+ }
371
+ }
372
+ else if (trimmed.startsWith('<<set')) {
373
+ // Match: <<set $var = value>>, <<set $var += value>>, etc.
374
+ // Full regex to capture variable name, operator, and value
375
+ const setMatch = trimmed.match(/<<set\s+\$(\w+)\s*([+\-*/=]+)\s*(.+?)>>/);
376
+ if (setMatch) {
377
+ const varName = setMatch[1];
378
+ const operator = setMatch[2].trim();
379
+ const valueStr = setMatch[3].trim();
380
+ // For now, we store the flag name. The operation will be preserved in content
381
+ // when we re-export. In the future, we could extend types to store operations.
382
+ if (choices.length > 0) {
383
+ if (!choices[choices.length - 1].setFlags) {
384
+ choices[choices.length - 1].setFlags = [];
385
+ }
386
+ choices[choices.length - 1].setFlags.push(varName);
387
+ // Store the full command in content for re-export
388
+ if (!choices[choices.length - 1].text.includes('<<set')) {
389
+ choices[choices.length - 1].text += ` ${trimmed}`;
390
+ }
391
+ }
392
+ else {
393
+ setFlags.push(varName);
394
+ // Store the full command in content for re-export
395
+ if (!dialogueContent.includes(trimmed)) {
396
+ dialogueContent += trimmed + '\n';
397
+ }
398
+ }
399
+ }
400
+ }
401
+ else if (trimmed.startsWith('<<if')) {
402
+ // Check if this is a conditional choice (appears before a -> choice)
403
+ // or a conditional block (appears before dialogue content)
404
+ const conditionStr = trimmed.replace(/<<if\s+/, '').replace(/>>/, '').trim();
405
+ const parsedConditions = parseCondition(conditionStr);
406
+ // If we're in a player node context (have choices) or this looks like it's before a choice,
407
+ // treat it as a conditional choice
408
+ if (choices.length > 0 || trimmed.match(/<<if.*>>\s*$/)) {
409
+ inConditionalChoice = true;
410
+ currentChoiceCondition = parsedConditions;
411
+ }
412
+ else {
413
+ // Otherwise, treat it as a conditional block for NPC nodes
414
+ if (inConditionalBlock && currentBlock) {
415
+ // Save previous block
416
+ currentBlock.content = blockContent.join('\n').trim();
417
+ currentBlock.speaker = blockSpeaker || undefined;
418
+ conditionalBlocks.push(currentBlock);
419
+ }
420
+ inConditionalBlock = true;
421
+ currentBlock = {
422
+ id: `block_${Date.now()}_${conditionalBlocks.length}`,
423
+ type: 'if',
424
+ condition: parsedConditions,
425
+ content: '',
426
+ speaker: undefined
427
+ };
428
+ blockContent = [];
429
+ blockSpeaker = '';
430
+ }
431
+ }
432
+ else if (trimmed.startsWith('<<elseif')) {
433
+ // Elseif block
434
+ if (currentBlock) {
435
+ currentBlock.content = blockContent.join('\n').trim();
436
+ currentBlock.speaker = blockSpeaker || undefined;
437
+ conditionalBlocks.push(currentBlock);
438
+ }
439
+ const conditionStr = trimmed.replace(/<<elseif\s+/, '').replace(/>>/, '').trim();
440
+ currentBlock = {
441
+ id: `block_${Date.now()}_${conditionalBlocks.length}`,
442
+ type: 'elseif',
443
+ condition: parseCondition(conditionStr),
444
+ content: '',
445
+ speaker: undefined
446
+ };
447
+ blockContent = [];
448
+ blockSpeaker = '';
449
+ }
450
+ else if (trimmed.startsWith('<<else')) {
451
+ // Else block
452
+ if (currentBlock) {
453
+ currentBlock.content = blockContent.join('\n').trim();
454
+ currentBlock.speaker = blockSpeaker || undefined;
455
+ conditionalBlocks.push(currentBlock);
456
+ }
457
+ currentBlock = {
458
+ id: `block_${Date.now()}_${conditionalBlocks.length}`,
459
+ type: 'else',
460
+ condition: undefined,
461
+ content: '',
462
+ speaker: undefined
463
+ };
464
+ blockContent = [];
465
+ blockSpeaker = '';
466
+ }
467
+ else if (trimmed.startsWith('<<endif')) {
468
+ // End of conditional block or conditional choice
469
+ if (inConditionalChoice) {
470
+ // End of conditional choice - the condition is already applied to the last choice
471
+ inConditionalChoice = false;
472
+ currentChoiceCondition = [];
473
+ }
474
+ else if (currentBlock) {
475
+ // End of conditional block
476
+ currentBlock.content = blockContent.join('\n').trim();
477
+ currentBlock.speaker = blockSpeaker || undefined;
478
+ conditionalBlocks.push(currentBlock);
479
+ inConditionalBlock = false;
480
+ currentBlock = null;
481
+ blockContent = [];
482
+ blockSpeaker = '';
483
+ }
484
+ }
485
+ else if (inConditionalBlock) {
486
+ // Content within conditional block
487
+ if (trimmed.includes(':') && !trimmed.startsWith('<<')) {
488
+ const [spk, ...rest] = trimmed.split(':');
489
+ blockSpeaker = spk.trim();
490
+ blockContent.push(rest.join(':').trim());
491
+ }
492
+ else if (!trimmed.startsWith('<<')) {
493
+ blockContent.push(trimmed);
494
+ }
495
+ }
496
+ else if (trimmed.includes(':') && !trimmed.startsWith('<<')) {
497
+ const [spk, ...rest] = trimmed.split(':');
498
+ speaker = spk.trim();
499
+ dialogueContent += rest.join(':').trim() + '\n';
500
+ }
501
+ else if (!trimmed.startsWith('<<')) {
502
+ dialogueContent += trimmed + '\n';
503
+ }
504
+ });
505
+ nodes[nodeId] = {
506
+ id: nodeId,
507
+ type: choices.length > 0 ? 'player' : 'npc',
508
+ speaker: speaker || undefined,
509
+ content: dialogueContent.trim(),
510
+ choices: choices.length > 0 ? choices : undefined,
511
+ nextNodeId: nextNodeId || undefined,
512
+ setFlags: setFlags.length > 0 ? setFlags : undefined,
513
+ conditionalBlocks: conditionalBlocks.length > 0 ? conditionalBlocks : undefined,
514
+ x: (idx % 3) * 250,
515
+ y: y + Math.floor(idx / 3) * 180
516
+ };
517
+ });
518
+ const startNodeId = Object.keys(nodes)[0] || 'start';
519
+ return {
520
+ id: 'imported',
521
+ title,
522
+ startNodeId,
523
+ nodes
524
+ };
525
+ }