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