@nice2dev/game-engine 0.1.0 → 1.0.2

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 (112) hide show
  1. package/CHANGELOG.md +193 -1
  2. package/dist/cjs/audio/AudioBridge.js +454 -0
  3. package/dist/cjs/audio/AudioBridge.js.map +1 -0
  4. package/dist/cjs/devtools/GameplayAnalytics.js +651 -0
  5. package/dist/cjs/devtools/GameplayAnalytics.js.map +1 -0
  6. package/dist/cjs/dialogue/DialogueSystem.js +1023 -0
  7. package/dist/cjs/dialogue/DialogueSystem.js.map +1 -0
  8. package/dist/cjs/editor/NiceGameEditor.js +569 -71
  9. package/dist/cjs/editor/NiceGameEditor.js.map +1 -1
  10. package/dist/cjs/engine/SaveSystemV2.js +494 -0
  11. package/dist/cjs/engine/SaveSystemV2.js.map +1 -0
  12. package/dist/cjs/i18n/useTranslation.js +11 -11
  13. package/dist/cjs/index.js +90 -1
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/input/GamepadNavigation.js +21 -21
  16. package/dist/cjs/input/useGamepads.js +6 -6
  17. package/dist/cjs/integration/IconSprite.js +281 -0
  18. package/dist/cjs/integration/IconSprite.js.map +1 -0
  19. package/dist/cjs/inventory/InventorySystem.js +930 -0
  20. package/dist/cjs/inventory/InventorySystem.js.map +1 -0
  21. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AbortController.js.map +1 -1
  22. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js.map +1 -1
  23. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js.map +1 -1
  24. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js.map +1 -1
  25. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Errors.js.map +1 -1
  26. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js.map +1 -1
  27. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js.map +1 -1
  28. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js.map +1 -1
  29. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpClient.js.map +1 -1
  30. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js.map +1 -1
  31. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnection.js.map +1 -1
  32. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js.map +1 -1
  33. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js.map +1 -1
  34. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ILogger.js.map +1 -1
  35. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ITransport.js.map +1 -1
  36. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js.map +1 -1
  37. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Loggers.js.map +1 -1
  38. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js.map +1 -1
  39. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js.map +1 -1
  40. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js.map +1 -1
  41. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Subject.js.map +1 -1
  42. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js.map +1 -1
  43. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/Utils.js.map +1 -1
  44. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js.map +1 -1
  45. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js.map +1 -1
  46. package/dist/cjs/node_modules/@microsoft/signalr/dist/esm/pkg-version.js.map +1 -1
  47. package/dist/cjs/quest/QuestSystem.js +924 -0
  48. package/dist/cjs/quest/QuestSystem.js.map +1 -0
  49. package/dist/cjs/rendering/WebGPURenderPipeline.js +658 -0
  50. package/dist/cjs/rendering/WebGPURenderPipeline.js.map +1 -0
  51. package/dist/cjs/xr/ARVR.js.map +1 -1
  52. package/dist/esm/audio/AudioBridge.js +446 -0
  53. package/dist/esm/audio/AudioBridge.js.map +1 -0
  54. package/dist/esm/devtools/GameplayAnalytics.js +639 -0
  55. package/dist/esm/devtools/GameplayAnalytics.js.map +1 -0
  56. package/dist/esm/dialogue/DialogueSystem.js +1008 -0
  57. package/dist/esm/dialogue/DialogueSystem.js.map +1 -0
  58. package/dist/esm/editor/NiceGameEditor.js +556 -58
  59. package/dist/esm/editor/NiceGameEditor.js.map +1 -1
  60. package/dist/esm/engine/SaveSystemV2.js +487 -0
  61. package/dist/esm/engine/SaveSystemV2.js.map +1 -0
  62. package/dist/esm/index.js +11 -3
  63. package/dist/esm/index.js.map +1 -1
  64. package/dist/esm/integration/IconSprite.js +266 -0
  65. package/dist/esm/integration/IconSprite.js.map +1 -0
  66. package/dist/esm/inventory/InventorySystem.js +924 -0
  67. package/dist/esm/inventory/InventorySystem.js.map +1 -0
  68. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AbortController.js.map +1 -1
  69. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/AccessTokenHttpClient.js.map +1 -1
  70. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultHttpClient.js.map +1 -1
  71. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/DefaultReconnectPolicy.js.map +1 -1
  72. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Errors.js.map +1 -1
  73. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/FetchHttpClient.js.map +1 -1
  74. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HandshakeProtocol.js.map +1 -1
  75. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HeaderNames.js.map +1 -1
  76. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpClient.js.map +1 -1
  77. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HttpConnection.js.map +1 -1
  78. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnection.js.map +1 -1
  79. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/HubConnectionBuilder.js.map +1 -1
  80. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/IHubProtocol.js.map +1 -1
  81. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ILogger.js.map +1 -1
  82. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ITransport.js.map +1 -1
  83. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/JsonHubProtocol.js.map +1 -1
  84. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Loggers.js.map +1 -1
  85. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/LongPollingTransport.js.map +1 -1
  86. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/MessageBuffer.js.map +1 -1
  87. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/ServerSentEventsTransport.js.map +1 -1
  88. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Subject.js.map +1 -1
  89. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/TextMessageFormat.js.map +1 -1
  90. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/Utils.js.map +1 -1
  91. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/WebSocketTransport.js.map +1 -1
  92. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/XhrHttpClient.js.map +1 -1
  93. package/dist/esm/node_modules/@microsoft/signalr/dist/esm/pkg-version.js.map +1 -1
  94. package/dist/esm/quest/QuestSystem.js +916 -0
  95. package/dist/esm/quest/QuestSystem.js.map +1 -0
  96. package/dist/esm/rendering/WebGPURenderPipeline.js +642 -0
  97. package/dist/esm/rendering/WebGPURenderPipeline.js.map +1 -0
  98. package/dist/esm/xr/ARVR.js.map +1 -1
  99. package/dist/types/__tests__/setup.d.ts +1 -1
  100. package/dist/types/audio/AudioBridge.d.ts +199 -0
  101. package/dist/types/devtools/GameplayAnalytics.d.ts +279 -0
  102. package/dist/types/dialogue/DialogueSystem.d.ts +326 -0
  103. package/dist/types/dialogue/index.d.ts +2 -0
  104. package/dist/types/editor/NiceGameEditor.d.ts +12 -1
  105. package/dist/types/engine/SaveSystemV2.d.ts +155 -0
  106. package/dist/types/index.d.ts +19 -3
  107. package/dist/types/integration/IconSprite.d.ts +196 -0
  108. package/dist/types/inventory/InventorySystem.d.ts +336 -0
  109. package/dist/types/performance/WebGPUCompute.d.ts +0 -10
  110. package/dist/types/quest/QuestSystem.d.ts +287 -0
  111. package/dist/types/rendering/WebGPURenderPipeline.d.ts +255 -0
  112. package/package.json +7 -1
@@ -0,0 +1,1008 @@
1
+ import { EventBus } from '../core/EventBus.js';
2
+ import React from 'react';
3
+
4
+ /* ────────────────────────────────────────────────────────────────
5
+ Dialogue System — Visual Dialogue Tree Editor
6
+
7
+ A comprehensive dialogue system with:
8
+ - Visual node-based dialogue tree editor
9
+ - Variables and conditions support
10
+ - Character portraits and emotions
11
+ - Branching conversations
12
+ - Localization support
13
+ ──────────────────────────────────────────────────────────────── */
14
+ /* ═══════════════════════════════════════════════════════════════
15
+ DIALOGUE ENGINE
16
+ ══════════════════════════════════════════════════════════════════ */
17
+ class DialogueEngine {
18
+ constructor(events) {
19
+ this.trees = new Map();
20
+ this.globalVariables = new Map();
21
+ this.currentState = null;
22
+ this.events = events !== null && events !== void 0 ? events : new EventBus();
23
+ }
24
+ /* ── Tree Management ──────────────────────────────────────────── */
25
+ registerTree(tree) {
26
+ this.trees.set(tree.id, tree);
27
+ }
28
+ unregisterTree(treeId) {
29
+ this.trees.delete(treeId);
30
+ }
31
+ getTree(treeId) {
32
+ return this.trees.get(treeId);
33
+ }
34
+ getAllTrees() {
35
+ return Array.from(this.trees.values());
36
+ }
37
+ /* ── Variable Management ──────────────────────────────────────── */
38
+ setGlobalVariable(name, value) {
39
+ this.globalVariables.set(name, value);
40
+ this.emitEvent('dialogue:variable-changed', { name, value, scope: 'global' });
41
+ }
42
+ getGlobalVariable(name) {
43
+ return this.globalVariables.get(name);
44
+ }
45
+ getVariable(variableId) {
46
+ var _a;
47
+ if (!this.currentState)
48
+ return this.globalVariables.get(variableId);
49
+ return (_a = this.currentState.variables[variableId]) !== null && _a !== void 0 ? _a : this.globalVariables.get(variableId);
50
+ }
51
+ setVariable(variableId, value, scope) {
52
+ if (scope === 'global') {
53
+ this.globalVariables.set(variableId, value);
54
+ }
55
+ else if (this.currentState) {
56
+ this.currentState.variables[variableId] = value;
57
+ }
58
+ this.emitEvent('dialogue:variable-changed', { variableId, value, scope });
59
+ }
60
+ /* ── Dialogue Flow ────────────────────────────────────────────── */
61
+ startDialogue(treeId, startLabel) {
62
+ const tree = this.trees.get(treeId);
63
+ if (!tree)
64
+ return false;
65
+ // Find start node
66
+ let startNode;
67
+ if (startLabel) {
68
+ startNode = tree.nodes.find((n) => n.type === 'start' && n.label === startLabel);
69
+ }
70
+ if (!startNode) {
71
+ startNode = tree.nodes.find((n) => n.id === tree.startNodeId);
72
+ }
73
+ if (!startNode)
74
+ return false;
75
+ // Initialize state
76
+ const variables = {};
77
+ for (const v of tree.variables) {
78
+ if (v.scope === 'conversation') {
79
+ variables[v.id] = v.defaultValue;
80
+ }
81
+ }
82
+ this.currentState = {
83
+ treeId,
84
+ currentNodeId: startNode.id,
85
+ variables,
86
+ history: [startNode.id],
87
+ choicesMade: [],
88
+ startTime: Date.now(),
89
+ isActive: true,
90
+ isPaused: false,
91
+ };
92
+ this.emitEvent('dialogue:started', { treeId, startNodeId: startNode.id });
93
+ // Advance to first content node
94
+ if (startNode.nextNode) {
95
+ this.goToNode(startNode.nextNode);
96
+ }
97
+ return true;
98
+ }
99
+ endDialogue() {
100
+ if (!this.currentState)
101
+ return;
102
+ const treeId = this.currentState.treeId;
103
+ this.currentState.isActive = false;
104
+ this.emitEvent('dialogue:ended', { treeId, state: { ...this.currentState } });
105
+ this.currentState = null;
106
+ }
107
+ pauseDialogue() {
108
+ if (!this.currentState || !this.currentState.isActive)
109
+ return;
110
+ this.currentState.isPaused = true;
111
+ this.emitEvent('dialogue:paused', {});
112
+ }
113
+ resumeDialogue() {
114
+ if (!this.currentState || !this.currentState.isActive)
115
+ return;
116
+ this.currentState.isPaused = false;
117
+ this.emitEvent('dialogue:resumed', {});
118
+ }
119
+ getState() {
120
+ return this.currentState ? { ...this.currentState } : null;
121
+ }
122
+ isActive() {
123
+ var _a, _b;
124
+ return (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.isActive) !== null && _b !== void 0 ? _b : false;
125
+ }
126
+ /* ── Node Navigation ──────────────────────────────────────────── */
127
+ goToNode(nodeId) {
128
+ if (!this.currentState)
129
+ return;
130
+ const tree = this.trees.get(this.currentState.treeId);
131
+ if (!tree)
132
+ return;
133
+ const node = tree.nodes.find(n => n.id === nodeId);
134
+ if (!node)
135
+ return;
136
+ this.currentState.currentNodeId = nodeId;
137
+ this.currentState.history.push(nodeId);
138
+ this.emitEvent('dialogue:node-entered', { nodeId, nodeType: node.type });
139
+ this.processNode(node, tree);
140
+ }
141
+ processNode(node, tree) {
142
+ switch (node.type) {
143
+ case 'start':
144
+ if (node.nextNode)
145
+ this.goToNode(node.nextNode);
146
+ break;
147
+ case 'text':
148
+ this.processTextNode(node, tree);
149
+ break;
150
+ case 'choice':
151
+ this.processChoiceNode(node, tree);
152
+ break;
153
+ case 'branch':
154
+ case 'check_variable':
155
+ this.processBranchNode(node);
156
+ break;
157
+ case 'set_variable':
158
+ this.processSetVariableNode(node, tree);
159
+ break;
160
+ case 'event':
161
+ this.processEventNode(node);
162
+ break;
163
+ case 'random':
164
+ this.processRandomNode(node);
165
+ break;
166
+ case 'end':
167
+ this.processEndNode(node);
168
+ break;
169
+ }
170
+ }
171
+ processTextNode(node, tree) {
172
+ const character = tree.characters.find(c => c.id === node.characterId);
173
+ if (character && this.onTextCallback) {
174
+ this.onTextCallback(node, character);
175
+ }
176
+ this.emitEvent('dialogue:text-displayed', {
177
+ nodeId: node.id,
178
+ characterId: node.characterId,
179
+ text: node.text,
180
+ emotion: node.emotion,
181
+ });
182
+ }
183
+ processChoiceNode(node, tree) {
184
+ // Filter valid options based on conditions
185
+ const validOptions = node.options.filter(opt => {
186
+ if (opt.disabled)
187
+ return false;
188
+ if (!opt.conditions || opt.conditions.length === 0)
189
+ return true;
190
+ return opt.conditions.every(c => this.evaluateCondition(c));
191
+ });
192
+ if (this.onChoiceCallback) {
193
+ this.onChoiceCallback(node, validOptions);
194
+ }
195
+ }
196
+ processBranchNode(node) {
197
+ const result = this.evaluateConditions(node.conditions, node.logic);
198
+ const nextNode = result ? node.trueNode : node.falseNode;
199
+ if (nextNode)
200
+ this.goToNode(nextNode);
201
+ }
202
+ processSetVariableNode(node, tree) {
203
+ var _a;
204
+ const variable = tree.variables.find(v => v.id === node.variableId);
205
+ if (!variable) {
206
+ if (node.nextNode)
207
+ this.goToNode(node.nextNode);
208
+ return;
209
+ }
210
+ const currentValue = (_a = this.getVariable(node.variableId)) !== null && _a !== void 0 ? _a : variable.defaultValue;
211
+ let newValue;
212
+ switch (node.operation) {
213
+ case 'set':
214
+ newValue = node.value;
215
+ break;
216
+ case 'add':
217
+ newValue = currentValue + node.value;
218
+ break;
219
+ case 'subtract':
220
+ newValue = currentValue - node.value;
221
+ break;
222
+ case 'multiply':
223
+ newValue = currentValue * node.value;
224
+ break;
225
+ case 'toggle':
226
+ newValue = !currentValue;
227
+ break;
228
+ case 'append':
229
+ newValue = currentValue + node.value;
230
+ break;
231
+ default:
232
+ newValue = node.value;
233
+ }
234
+ this.setVariable(node.variableId, newValue, variable.scope);
235
+ if (node.nextNode)
236
+ this.goToNode(node.nextNode);
237
+ }
238
+ processEventNode(node) {
239
+ this.events.emit(node.eventType, node.eventData);
240
+ this.emitEvent('dialogue:event-triggered', {
241
+ eventType: node.eventType,
242
+ eventData: node.eventData,
243
+ });
244
+ if (node.nextNode)
245
+ this.goToNode(node.nextNode);
246
+ }
247
+ processRandomNode(node) {
248
+ const totalWeight = node.outputs.reduce((sum, out) => sum + out.weight, 0);
249
+ let random = Math.random() * totalWeight;
250
+ for (const output of node.outputs) {
251
+ random -= output.weight;
252
+ if (random <= 0 && output.nextNode) {
253
+ this.goToNode(output.nextNode);
254
+ return;
255
+ }
256
+ }
257
+ // Fallback to first output
258
+ const first = node.outputs[0];
259
+ if (first === null || first === void 0 ? void 0 : first.nextNode)
260
+ this.goToNode(first.nextNode);
261
+ }
262
+ processEndNode(node) {
263
+ // Apply final variable changes
264
+ if (node.setVariables && this.currentState) {
265
+ const tree = this.trees.get(this.currentState.treeId);
266
+ if (tree) {
267
+ for (const sv of node.setVariables) {
268
+ const variable = tree.variables.find(v => v.id === sv.variableId);
269
+ if (variable) {
270
+ this.setVariable(sv.variableId, sv.value, variable.scope);
271
+ }
272
+ }
273
+ }
274
+ }
275
+ if (this.onEndCallback && this.currentState) {
276
+ this.onEndCallback(node, this.currentState);
277
+ }
278
+ this.endDialogue();
279
+ }
280
+ /* ── Choice Selection ─────────────────────────────────────────── */
281
+ selectChoice(optionId) {
282
+ if (!this.currentState || !this.currentState.isActive)
283
+ return false;
284
+ const tree = this.trees.get(this.currentState.treeId);
285
+ if (!tree)
286
+ return false;
287
+ const node = tree.nodes.find(n => n.id === this.currentState.currentNodeId);
288
+ if (!node || node.type !== 'choice')
289
+ return false;
290
+ const choice = node.options.find(o => o.id === optionId);
291
+ if (!choice)
292
+ return false;
293
+ // Apply variables from choice
294
+ if (choice.setVariables) {
295
+ for (const sv of choice.setVariables) {
296
+ const variable = tree.variables.find(v => v.id === sv.variableId);
297
+ if (variable) {
298
+ this.setVariable(sv.variableId, sv.value, variable.scope);
299
+ }
300
+ }
301
+ }
302
+ this.currentState.choicesMade.push({
303
+ nodeId: node.id,
304
+ optionId,
305
+ timestamp: Date.now(),
306
+ });
307
+ this.emitEvent('dialogue:choice-selected', {
308
+ nodeId: node.id,
309
+ optionId,
310
+ text: choice.text,
311
+ });
312
+ if (choice.nextNode) {
313
+ this.goToNode(choice.nextNode);
314
+ }
315
+ return true;
316
+ }
317
+ /** Advance to next node (for text nodes) */
318
+ advance() {
319
+ if (!this.currentState || !this.currentState.isActive)
320
+ return false;
321
+ const tree = this.trees.get(this.currentState.treeId);
322
+ if (!tree)
323
+ return false;
324
+ const node = tree.nodes.find(n => n.id === this.currentState.currentNodeId);
325
+ if (!node)
326
+ return false;
327
+ if (node.type === 'text' && node.nextNode) {
328
+ this.goToNode(node.nextNode);
329
+ return true;
330
+ }
331
+ return false;
332
+ }
333
+ /* ── Condition Evaluation ─────────────────────────────────────── */
334
+ evaluateConditions(conditions, logic) {
335
+ if (conditions.length === 0)
336
+ return true;
337
+ if (logic === 'and') {
338
+ return conditions.every(c => this.evaluateCondition(c));
339
+ }
340
+ else {
341
+ return conditions.some(c => this.evaluateCondition(c));
342
+ }
343
+ }
344
+ evaluateCondition(condition) {
345
+ const varValue = this.getVariable(condition.variableId);
346
+ if (varValue === undefined)
347
+ return false;
348
+ const { operator, value } = condition;
349
+ switch (operator) {
350
+ case 'eq':
351
+ return varValue === value;
352
+ case 'neq':
353
+ return varValue !== value;
354
+ case 'gt':
355
+ return varValue > value;
356
+ case 'lt':
357
+ return varValue < value;
358
+ case 'gte':
359
+ return varValue >= value;
360
+ case 'lte':
361
+ return varValue <= value;
362
+ case 'contains':
363
+ return String(varValue).includes(String(value));
364
+ case 'startsWith':
365
+ return String(varValue).startsWith(String(value));
366
+ case 'endsWith':
367
+ return String(varValue).endsWith(String(value));
368
+ default:
369
+ return false;
370
+ }
371
+ }
372
+ /* ── Callbacks ────────────────────────────────────────────────── */
373
+ onText(callback) {
374
+ this.onTextCallback = callback;
375
+ }
376
+ onChoice(callback) {
377
+ this.onChoiceCallback = callback;
378
+ }
379
+ onEnd(callback) {
380
+ this.onEndCallback = callback;
381
+ }
382
+ /* ── Events ───────────────────────────────────────────────────── */
383
+ emitEvent(type, data) {
384
+ var _a, _b, _c, _d;
385
+ this.events.emit(type, {
386
+ type,
387
+ treeId: (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.treeId) !== null && _b !== void 0 ? _b : '',
388
+ nodeId: (_d = (_c = this.currentState) === null || _c === void 0 ? void 0 : _c.currentNodeId) !== null && _d !== void 0 ? _d : undefined,
389
+ data,
390
+ timestamp: Date.now(),
391
+ });
392
+ }
393
+ subscribe(eventType, handler) {
394
+ return this.events.on(eventType, handler);
395
+ }
396
+ }
397
+ /* ═══════════════════════════════════════════════════════════════
398
+ DIALOGUE TREE BUILDER (Fluent API)
399
+ ══════════════════════════════════════════════════════════════════ */
400
+ class DialogueTreeBuilder {
401
+ constructor(id, name) {
402
+ this.nodeCounter = 0;
403
+ this.tree = {
404
+ id,
405
+ name,
406
+ version: '1.0.0',
407
+ characters: [],
408
+ variables: [],
409
+ nodes: [],
410
+ startNodeId: '',
411
+ metadata: {
412
+ created: new Date().toISOString(),
413
+ modified: new Date().toISOString(),
414
+ locale: 'en',
415
+ },
416
+ };
417
+ }
418
+ generateId() {
419
+ return `node_${++this.nodeCounter}`;
420
+ }
421
+ /* ── Metadata ─────────────────────────────────────────────────── */
422
+ description(desc) {
423
+ this.tree.description = desc;
424
+ return this;
425
+ }
426
+ author(author) {
427
+ this.tree.metadata.author = author;
428
+ return this;
429
+ }
430
+ locale(locale) {
431
+ this.tree.metadata.locale = locale;
432
+ return this;
433
+ }
434
+ tags(tags) {
435
+ this.tree.metadata.tags = tags;
436
+ return this;
437
+ }
438
+ /* ── Characters ───────────────────────────────────────────────── */
439
+ addCharacter(character) {
440
+ this.tree.characters.push(character);
441
+ return this;
442
+ }
443
+ /* ── Variables ────────────────────────────────────────────────── */
444
+ addVariable(variable) {
445
+ this.tree.variables.push(variable);
446
+ return this;
447
+ }
448
+ boolVar(id, name, defaultValue = false, scope = 'conversation') {
449
+ return this.addVariable({ id, name, type: 'boolean', defaultValue, scope });
450
+ }
451
+ numVar(id, name, defaultValue = 0, scope = 'conversation') {
452
+ return this.addVariable({ id, name, type: 'number', defaultValue, scope });
453
+ }
454
+ strVar(id, name, defaultValue = '', scope = 'conversation') {
455
+ return this.addVariable({ id, name, type: 'string', defaultValue, scope });
456
+ }
457
+ /* ── Nodes ────────────────────────────────────────────────────── */
458
+ addStart(label, nextNode = null, position = { x: 0, y: 0 }) {
459
+ const id = this.generateId();
460
+ const node = { id, type: 'start', label, nextNode, position };
461
+ this.tree.nodes.push(node);
462
+ if (!this.tree.startNodeId)
463
+ this.tree.startNodeId = id;
464
+ return this;
465
+ }
466
+ addText(characterId, text, nextNode = null, options = {}) {
467
+ var _a;
468
+ const id = this.generateId();
469
+ const node = {
470
+ id,
471
+ type: 'text',
472
+ characterId,
473
+ text,
474
+ nextNode,
475
+ position: (_a = options.position) !== null && _a !== void 0 ? _a : { x: 100, y: 0 },
476
+ ...options,
477
+ };
478
+ this.tree.nodes.push(node);
479
+ return this;
480
+ }
481
+ addChoice(options, settings = {}) {
482
+ var _a;
483
+ const id = this.generateId();
484
+ const node = {
485
+ id,
486
+ type: 'choice',
487
+ options,
488
+ position: (_a = settings.position) !== null && _a !== void 0 ? _a : { x: 200, y: 0 },
489
+ ...settings,
490
+ };
491
+ this.tree.nodes.push(node);
492
+ return this;
493
+ }
494
+ addBranch(conditions, trueNode, falseNode, logic = 'and', position = { x: 150, y: 0 }) {
495
+ const id = this.generateId();
496
+ const node = {
497
+ id,
498
+ type: 'branch',
499
+ conditions,
500
+ logic,
501
+ trueNode,
502
+ falseNode,
503
+ position,
504
+ };
505
+ this.tree.nodes.push(node);
506
+ return this;
507
+ }
508
+ addSetVariable(variableId, operation, value, nextNode = null, position = { x: 150, y: 0 }) {
509
+ const id = this.generateId();
510
+ const node = {
511
+ id,
512
+ type: 'set_variable',
513
+ variableId,
514
+ operation,
515
+ value,
516
+ nextNode,
517
+ position,
518
+ };
519
+ this.tree.nodes.push(node);
520
+ return this;
521
+ }
522
+ addEvent(eventType, eventData, nextNode = null, position = { x: 150, y: 0 }) {
523
+ const id = this.generateId();
524
+ const node = {
525
+ id,
526
+ type: 'event',
527
+ eventType,
528
+ eventData,
529
+ nextNode,
530
+ position,
531
+ };
532
+ this.tree.nodes.push(node);
533
+ return this;
534
+ }
535
+ addRandom(outputs, position = { x: 150, y: 0 }) {
536
+ const id = this.generateId();
537
+ const node = {
538
+ id,
539
+ type: 'random',
540
+ outputs: outputs.map((o, i) => ({ ...o, id: `out_${i}` })),
541
+ position,
542
+ };
543
+ this.tree.nodes.push(node);
544
+ return this;
545
+ }
546
+ addEnd(result = 'success', setVariables, position = { x: 300, y: 0 }) {
547
+ const id = this.generateId();
548
+ const node = {
549
+ id,
550
+ type: 'end',
551
+ result,
552
+ setVariables,
553
+ position,
554
+ };
555
+ this.tree.nodes.push(node);
556
+ return this;
557
+ }
558
+ /* ── Build ────────────────────────────────────────────────────── */
559
+ build() {
560
+ this.tree.metadata.modified = new Date().toISOString();
561
+ return { ...this.tree };
562
+ }
563
+ /** Get the ID of the last added node */
564
+ lastNodeId() {
565
+ var _a;
566
+ const lastNode = this.tree.nodes[this.tree.nodes.length - 1];
567
+ return (_a = lastNode === null || lastNode === void 0 ? void 0 : lastNode.id) !== null && _a !== void 0 ? _a : '';
568
+ }
569
+ /** Get node by index (1-based for readability) */
570
+ nodeId(index) {
571
+ return `node_${index}`;
572
+ }
573
+ }
574
+ /**
575
+ * React hook for typewriter text effect
576
+ */
577
+ function useTypewriter(text, speed = 50, enabled = true) {
578
+ const [displayedText, setDisplayedText] = React.useState('');
579
+ const [isComplete, setIsComplete] = React.useState(!enabled);
580
+ const indexRef = React.useRef(0);
581
+ React.useEffect(() => {
582
+ if (!enabled) {
583
+ setDisplayedText(text);
584
+ setIsComplete(true);
585
+ return;
586
+ }
587
+ setDisplayedText('');
588
+ setIsComplete(false);
589
+ indexRef.current = 0;
590
+ const interval = setInterval(() => {
591
+ if (indexRef.current >= text.length) {
592
+ setIsComplete(true);
593
+ clearInterval(interval);
594
+ return;
595
+ }
596
+ indexRef.current++;
597
+ setDisplayedText(text.slice(0, indexRef.current));
598
+ }, 1000 / speed);
599
+ return () => clearInterval(interval);
600
+ }, [text, speed, enabled]);
601
+ const skip = React.useCallback(() => {
602
+ setDisplayedText(text);
603
+ setIsComplete(true);
604
+ }, [text]);
605
+ return { displayedText, isComplete, skip };
606
+ }
607
+ /**
608
+ * DialogueBox component for displaying dialogue text
609
+ */
610
+ const DialogueBox = ({ text, characterName, portrait, emotion = 'neutral', textEffect = 'typewriter', textSpeed = 50, onComplete, onAdvance, className = '', style, }) => {
611
+ const { displayedText, isComplete, skip } = useTypewriter(text, textSpeed, textEffect === 'typewriter');
612
+ React.useEffect(() => {
613
+ if (isComplete && onComplete) {
614
+ onComplete();
615
+ }
616
+ }, [isComplete, onComplete]);
617
+ const handleClick = () => {
618
+ if (!isComplete) {
619
+ skip();
620
+ }
621
+ else if (onAdvance) {
622
+ onAdvance();
623
+ }
624
+ };
625
+ return React.createElement('div', {
626
+ className: `nice-dialogue-box ${className}`,
627
+ style: {
628
+ display: 'flex',
629
+ gap: '16px',
630
+ padding: '16px',
631
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
632
+ borderRadius: '8px',
633
+ color: 'white',
634
+ cursor: 'pointer',
635
+ ...style,
636
+ },
637
+ onClick: handleClick,
638
+ 'data-emotion': emotion,
639
+ }, [
640
+ portrait && React.createElement('div', {
641
+ key: 'portrait',
642
+ className: 'nice-dialogue-portrait',
643
+ style: {
644
+ width: '80px',
645
+ height: '80px',
646
+ flexShrink: 0,
647
+ borderRadius: '8px',
648
+ overflow: 'hidden',
649
+ },
650
+ }, React.createElement('img', {
651
+ src: portrait,
652
+ alt: characterName !== null && characterName !== void 0 ? characterName : 'Character',
653
+ style: { width: '100%', height: '100%', objectFit: 'cover' },
654
+ })),
655
+ React.createElement('div', {
656
+ key: 'content',
657
+ className: 'nice-dialogue-content',
658
+ style: { flex: 1 },
659
+ }, [
660
+ characterName && React.createElement('div', {
661
+ key: 'name',
662
+ className: 'nice-dialogue-name',
663
+ style: {
664
+ fontWeight: 'bold',
665
+ marginBottom: '8px',
666
+ color: '#64b5f6',
667
+ },
668
+ }, characterName),
669
+ React.createElement('div', {
670
+ key: 'text',
671
+ className: 'nice-dialogue-text',
672
+ style: { lineHeight: 1.6 },
673
+ }, displayedText),
674
+ React.createElement('div', {
675
+ key: 'indicator',
676
+ className: 'nice-dialogue-indicator',
677
+ style: {
678
+ marginTop: '8px',
679
+ textAlign: 'right',
680
+ opacity: isComplete ? 1 : 0.5,
681
+ fontSize: '12px',
682
+ },
683
+ }, isComplete ? '▼ Click to continue' : '...'),
684
+ ]),
685
+ ]);
686
+ };
687
+ /**
688
+ * ChoiceList component for displaying dialogue choices
689
+ */
690
+ const ChoiceList = ({ options, prompt, onSelect, timeout, className = '', style, }) => {
691
+ const [timeLeft, setTimeLeft] = React.useState(timeout !== null && timeout !== void 0 ? timeout : 0);
692
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
693
+ // Timeout countdown
694
+ React.useEffect(() => {
695
+ if (!timeout)
696
+ return;
697
+ setTimeLeft(timeout);
698
+ const interval = setInterval(() => {
699
+ setTimeLeft(prev => {
700
+ if (prev <= 100) {
701
+ // Auto-select first option
702
+ const firstValid = options.find(o => !o.disabled);
703
+ if (firstValid)
704
+ onSelect(firstValid.id);
705
+ return 0;
706
+ }
707
+ return prev - 100;
708
+ });
709
+ }, 100);
710
+ return () => clearInterval(interval);
711
+ }, [timeout, options, onSelect]);
712
+ // Keyboard navigation
713
+ React.useEffect(() => {
714
+ const handleKeyDown = (e) => {
715
+ const validOptions = options.filter(o => !o.disabled);
716
+ switch (e.key) {
717
+ case 'ArrowUp':
718
+ e.preventDefault();
719
+ setSelectedIndex(prev => (prev - 1 + validOptions.length) % validOptions.length);
720
+ break;
721
+ case 'ArrowDown':
722
+ e.preventDefault();
723
+ setSelectedIndex(prev => (prev + 1) % validOptions.length);
724
+ break;
725
+ case 'Enter':
726
+ case ' ':
727
+ e.preventDefault();
728
+ if (validOptions[selectedIndex]) {
729
+ onSelect(validOptions[selectedIndex].id);
730
+ }
731
+ break;
732
+ }
733
+ };
734
+ window.addEventListener('keydown', handleKeyDown);
735
+ return () => window.removeEventListener('keydown', handleKeyDown);
736
+ }, [options, selectedIndex, onSelect]);
737
+ const validOptions = options.filter(o => !o.disabled);
738
+ return React.createElement('div', {
739
+ className: `nice-choice-list ${className}`,
740
+ style: {
741
+ padding: '16px',
742
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
743
+ borderRadius: '8px',
744
+ color: 'white',
745
+ ...style,
746
+ },
747
+ }, [
748
+ prompt && React.createElement('div', {
749
+ key: 'prompt',
750
+ className: 'nice-choice-prompt',
751
+ style: {
752
+ marginBottom: '16px',
753
+ fontStyle: 'italic',
754
+ opacity: 0.9,
755
+ },
756
+ }, prompt),
757
+ timeout && timeLeft > 0 && React.createElement('div', {
758
+ key: 'timer',
759
+ className: 'nice-choice-timer',
760
+ style: {
761
+ marginBottom: '8px',
762
+ height: '4px',
763
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
764
+ borderRadius: '2px',
765
+ overflow: 'hidden',
766
+ },
767
+ }, React.createElement('div', {
768
+ style: {
769
+ width: `${(timeLeft / timeout) * 100}%`,
770
+ height: '100%',
771
+ backgroundColor: timeLeft < 3000 ? '#f44336' : '#4caf50',
772
+ transition: 'width 0.1s linear',
773
+ },
774
+ })),
775
+ React.createElement('div', {
776
+ key: 'options',
777
+ className: 'nice-choice-options',
778
+ style: { display: 'flex', flexDirection: 'column', gap: '8px' },
779
+ }, validOptions.map((option, index) => React.createElement('button', {
780
+ key: option.id,
781
+ className: `nice-choice-option ${index === selectedIndex ? 'selected' : ''}`,
782
+ onClick: () => onSelect(option.id),
783
+ title: option.tooltip,
784
+ style: {
785
+ padding: '12px 16px',
786
+ backgroundColor: index === selectedIndex ? 'rgba(100, 181, 246, 0.3)' : 'rgba(255, 255, 255, 0.1)',
787
+ border: index === selectedIndex ? '2px solid #64b5f6' : '2px solid transparent',
788
+ borderRadius: '4px',
789
+ color: 'white',
790
+ cursor: 'pointer',
791
+ textAlign: 'left',
792
+ transition: 'all 0.2s ease',
793
+ },
794
+ }, option.text))),
795
+ ]);
796
+ };
797
+ /* ═══════════════════════════════════════════════════════════════
798
+ SERIALIZATION & VALIDATION
799
+ ══════════════════════════════════════════════════════════════════ */
800
+ /**
801
+ * Serialize dialogue tree to JSON string
802
+ */
803
+ function serializeDialogueTree(tree) {
804
+ return JSON.stringify(tree, null, 2);
805
+ }
806
+ /**
807
+ * Deserialize dialogue tree from JSON string
808
+ */
809
+ function deserializeDialogueTree(json) {
810
+ const parsed = JSON.parse(json);
811
+ return validateDialogueTree(parsed);
812
+ }
813
+ /**
814
+ * Validate dialogue tree structure
815
+ */
816
+ function validateDialogueTree(tree) {
817
+ if (!tree || typeof tree !== 'object') {
818
+ throw new Error('Invalid dialogue tree: must be an object');
819
+ }
820
+ const t = tree;
821
+ if (typeof t.id !== 'string' || !t.id) {
822
+ throw new Error('Invalid dialogue tree: missing id');
823
+ }
824
+ if (typeof t.name !== 'string' || !t.name) {
825
+ throw new Error('Invalid dialogue tree: missing name');
826
+ }
827
+ if (!Array.isArray(t.nodes)) {
828
+ throw new Error('Invalid dialogue tree: nodes must be an array');
829
+ }
830
+ if (!Array.isArray(t.characters)) {
831
+ throw new Error('Invalid dialogue tree: characters must be an array');
832
+ }
833
+ if (!Array.isArray(t.variables)) {
834
+ throw new Error('Invalid dialogue tree: variables must be an array');
835
+ }
836
+ if (typeof t.startNodeId !== 'string') {
837
+ throw new Error('Invalid dialogue tree: missing startNodeId');
838
+ }
839
+ // Validate all node connections
840
+ const nodeIds = new Set(t.nodes.map(n => n.id));
841
+ for (const node of t.nodes) {
842
+ validateNodeConnections(node, nodeIds);
843
+ }
844
+ return t;
845
+ }
846
+ function validateNodeConnections(node, validIds) {
847
+ const checkId = (id, context) => {
848
+ if (id !== null && !validIds.has(id)) {
849
+ throw new Error(`Invalid node reference "${id}" in ${context} of node "${node.id}"`);
850
+ }
851
+ };
852
+ switch (node.type) {
853
+ case 'start':
854
+ case 'text':
855
+ case 'set_variable':
856
+ case 'event':
857
+ checkId(node.nextNode, 'nextNode');
858
+ break;
859
+ case 'choice':
860
+ for (const opt of node.options) {
861
+ checkId(opt.nextNode, `option "${opt.id}"`);
862
+ }
863
+ break;
864
+ case 'branch':
865
+ case 'check_variable':
866
+ checkId(node.trueNode, 'trueNode');
867
+ checkId(node.falseNode, 'falseNode');
868
+ break;
869
+ case 'random':
870
+ for (const out of node.outputs) {
871
+ checkId(out.nextNode, `output "${out.id}"`);
872
+ }
873
+ break;
874
+ }
875
+ }
876
+ /**
877
+ * Find unreachable nodes in dialogue tree
878
+ */
879
+ function findUnreachableNodes(tree) {
880
+ const reachable = new Set();
881
+ const toVisit = [tree.startNodeId];
882
+ while (toVisit.length > 0) {
883
+ const nodeId = toVisit.pop();
884
+ if (reachable.has(nodeId))
885
+ continue;
886
+ reachable.add(nodeId);
887
+ const node = tree.nodes.find(n => n.id === nodeId);
888
+ if (!node)
889
+ continue;
890
+ const nextIds = getNextNodeIds(node);
891
+ for (const id of nextIds) {
892
+ if (id && !reachable.has(id)) {
893
+ toVisit.push(id);
894
+ }
895
+ }
896
+ }
897
+ return tree.nodes
898
+ .filter(n => !reachable.has(n.id) && n.type !== 'start')
899
+ .map(n => n.id);
900
+ }
901
+ function getNextNodeIds(node) {
902
+ switch (node.type) {
903
+ case 'start':
904
+ case 'text':
905
+ case 'set_variable':
906
+ case 'event':
907
+ return [node.nextNode];
908
+ case 'choice':
909
+ return node.options.map(o => o.nextNode);
910
+ case 'branch':
911
+ case 'check_variable':
912
+ return [node.trueNode, node.falseNode];
913
+ case 'random':
914
+ return node.outputs.map(o => o.nextNode);
915
+ case 'end':
916
+ return [];
917
+ default:
918
+ return [];
919
+ }
920
+ }
921
+ /**
922
+ * Extract all translatable strings from a dialogue tree
923
+ */
924
+ function extractTranslatableStrings(tree) {
925
+ const nodeTexts = {};
926
+ const characterNames = {};
927
+ for (const node of tree.nodes) {
928
+ if (node.type === 'text') {
929
+ nodeTexts[node.id] = node.text;
930
+ }
931
+ else if (node.type === 'choice') {
932
+ const choiceNode = node;
933
+ if (choiceNode.prompt) {
934
+ nodeTexts[`${node.id}_prompt`] = choiceNode.prompt;
935
+ }
936
+ for (const opt of choiceNode.options) {
937
+ nodeTexts[`${node.id}_${opt.id}`] = opt.text;
938
+ if (opt.tooltip) {
939
+ nodeTexts[`${node.id}_${opt.id}_tooltip`] = opt.tooltip;
940
+ }
941
+ }
942
+ }
943
+ }
944
+ for (const char of tree.characters) {
945
+ characterNames[char.id] = char.displayName;
946
+ }
947
+ return { nodeTexts, characterNames };
948
+ }
949
+ /**
950
+ * Apply localization to a dialogue tree (creates a copy)
951
+ */
952
+ function applyLocalization(tree, localization) {
953
+ const localized = JSON.parse(JSON.stringify(tree));
954
+ localized.metadata.locale = localization.locale;
955
+ for (const node of localized.nodes) {
956
+ if (node.type === 'text' && localization.texts[node.id]) {
957
+ node.text = localization.texts[node.id];
958
+ }
959
+ else if (node.type === 'choice') {
960
+ const choiceNode = node;
961
+ if (localization.texts[`${node.id}_prompt`]) {
962
+ choiceNode.prompt = localization.texts[`${node.id}_prompt`];
963
+ }
964
+ for (const opt of choiceNode.options) {
965
+ if (localization.texts[`${node.id}_${opt.id}`]) {
966
+ opt.text = localization.texts[`${node.id}_${opt.id}`];
967
+ }
968
+ if (localization.texts[`${node.id}_${opt.id}_tooltip`]) {
969
+ opt.tooltip = localization.texts[`${node.id}_${opt.id}_tooltip`];
970
+ }
971
+ }
972
+ }
973
+ }
974
+ for (const char of localized.characters) {
975
+ if (localization.characterNames[char.id]) {
976
+ char.displayName = localization.characterNames[char.id];
977
+ }
978
+ }
979
+ return localized;
980
+ }
981
+ /* ═══════════════════════════════════════════════════════════════
982
+ DEFAULT EXPORTS
983
+ ══════════════════════════════════════════════════════════════════ */
984
+ const DEFAULT_TEXT_SPEED = 50; // characters per second
985
+ const EMOTION_COLORS = {
986
+ neutral: '#9e9e9e',
987
+ happy: '#4caf50',
988
+ sad: '#2196f3',
989
+ angry: '#f44336',
990
+ surprised: '#ff9800',
991
+ scared: '#9c27b0',
992
+ disgusted: '#795548',
993
+ confused: '#607d8b',
994
+ thoughtful: '#3f51b5',
995
+ excited: '#ffeb3b',
996
+ tired: '#455a64',
997
+ love: '#e91e63',
998
+ custom: '#ffffff',
999
+ };
1000
+ function createEmptyDialogueTree(id, name) {
1001
+ return new DialogueTreeBuilder(id, name)
1002
+ .addStart('main', null)
1003
+ .addEnd('success')
1004
+ .build();
1005
+ }
1006
+
1007
+ export { ChoiceList, DEFAULT_TEXT_SPEED, DialogueBox, DialogueEngine, DialogueTreeBuilder, EMOTION_COLORS, applyLocalization, createEmptyDialogueTree, deserializeDialogueTree, extractTranslatableStrings, findUnreachableNodes, serializeDialogueTree, useTypewriter, validateDialogueTree };
1008
+ //# sourceMappingURL=DialogueSystem.js.map