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