pixospritz-core 0.10.1 → 1.0.1

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 (157) hide show
  1. package/README.md +36 -286
  2. package/dist/bundle.js +13 -3
  3. package/dist/bundle.js.map +1 -1
  4. package/dist/style.css +1 -0
  5. package/package.json +43 -44
  6. package/src/components/WebGLView.jsx +318 -0
  7. package/src/css/pixos.css +372 -0
  8. package/src/engine/actions/animate.js +41 -0
  9. package/src/engine/actions/changezone.js +135 -0
  10. package/src/engine/actions/chat.js +109 -0
  11. package/src/engine/actions/dialogue.js +90 -0
  12. package/src/engine/actions/face.js +22 -0
  13. package/src/engine/actions/greeting.js +28 -0
  14. package/src/engine/actions/interact.js +86 -0
  15. package/src/engine/actions/move.js +67 -0
  16. package/src/engine/actions/patrol.js +109 -0
  17. package/src/engine/actions/prompt.js +185 -0
  18. package/src/engine/actions/script.js +42 -0
  19. package/src/engine/core/audio/AudioSystem.js +543 -0
  20. package/src/engine/core/cutscene/PxcPlayer.js +956 -0
  21. package/src/engine/core/cutscene/manager.js +243 -0
  22. package/src/engine/core/database/index.js +75 -0
  23. package/src/engine/core/debug/index.js +371 -0
  24. package/src/engine/core/hud/index.js +765 -0
  25. package/src/engine/core/index.js +540 -0
  26. package/src/engine/core/input/gamepad/Controller.js +71 -0
  27. package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
  28. package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
  29. package/src/engine/core/input/gamepad/index.js +592 -0
  30. package/src/engine/core/input/keyboard.js +196 -0
  31. package/src/engine/core/input/manager.js +485 -0
  32. package/src/engine/core/input/mouse.js +203 -0
  33. package/src/engine/core/input/touch.js +175 -0
  34. package/src/engine/core/mode/manager.js +199 -0
  35. package/src/engine/core/net/manager.js +535 -0
  36. package/src/engine/core/queue/action.js +83 -0
  37. package/src/engine/core/queue/event.js +82 -0
  38. package/src/engine/core/queue/index.js +44 -0
  39. package/src/engine/core/queue/loadable.js +33 -0
  40. package/src/engine/core/render/CameraEffects.js +494 -0
  41. package/src/engine/core/render/FrustumCuller.js +417 -0
  42. package/src/engine/core/render/LODManager.js +285 -0
  43. package/src/engine/core/render/ParticleManager.js +529 -0
  44. package/src/engine/core/render/TextureAtlas.js +465 -0
  45. package/src/engine/core/render/camera.js +338 -0
  46. package/src/engine/core/render/light.js +197 -0
  47. package/src/engine/core/render/manager.js +1079 -0
  48. package/src/engine/core/render/shaders.js +110 -0
  49. package/src/engine/core/render/skybox.js +342 -0
  50. package/src/engine/core/resource/manager.js +133 -0
  51. package/src/engine/core/resource/object.js +611 -0
  52. package/src/engine/core/resource/texture.js +103 -0
  53. package/src/engine/core/resource/tileset.js +177 -0
  54. package/src/engine/core/scene/avatar.js +215 -0
  55. package/src/engine/core/scene/speech.js +138 -0
  56. package/src/engine/core/scene/sprite.js +702 -0
  57. package/src/engine/core/scene/spritz.js +189 -0
  58. package/src/engine/core/scene/world.js +681 -0
  59. package/src/engine/core/scene/zone.js +1167 -0
  60. package/src/engine/core/store/index.js +110 -0
  61. package/src/engine/dynamic/animatedSprite.js +64 -0
  62. package/src/engine/dynamic/animatedTile.js +98 -0
  63. package/src/engine/dynamic/avatar.js +110 -0
  64. package/src/engine/dynamic/map.js +174 -0
  65. package/src/engine/dynamic/sprite.js +255 -0
  66. package/src/engine/dynamic/spritz.js +119 -0
  67. package/src/engine/events/EventSystem.js +609 -0
  68. package/src/engine/events/camera.js +142 -0
  69. package/src/engine/events/chat.js +75 -0
  70. package/src/engine/events/menu.js +186 -0
  71. package/src/engine/scripting/CallbackManager.js +514 -0
  72. package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
  73. package/src/engine/scripting/PixoScriptLibrary.js +704 -0
  74. package/src/engine/shaders/effects/index.js +450 -0
  75. package/src/engine/shaders/fs.js +222 -0
  76. package/src/engine/shaders/particles/fs.js +41 -0
  77. package/src/engine/shaders/particles/vs.js +61 -0
  78. package/src/engine/shaders/picker/fs.js +34 -0
  79. package/src/engine/shaders/picker/init.js +62 -0
  80. package/src/engine/shaders/picker/vs.js +42 -0
  81. package/src/engine/shaders/pxsl/README.md +250 -0
  82. package/src/engine/shaders/pxsl/index.js +25 -0
  83. package/src/engine/shaders/pxsl/library.js +608 -0
  84. package/src/engine/shaders/pxsl/manager.js +338 -0
  85. package/src/engine/shaders/pxsl/specification.js +363 -0
  86. package/src/engine/shaders/pxsl/transpiler.js +753 -0
  87. package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
  88. package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
  89. package/src/engine/shaders/skybox/matrix/fs.js +127 -0
  90. package/src/engine/shaders/skybox/matrix/vs.js +23 -0
  91. package/src/engine/shaders/skybox/morning/fs.js +109 -0
  92. package/src/engine/shaders/skybox/morning/vs.js +23 -0
  93. package/src/engine/shaders/skybox/neon/fs.js +119 -0
  94. package/src/engine/shaders/skybox/neon/vs.js +23 -0
  95. package/src/engine/shaders/skybox/sky/fs.js +114 -0
  96. package/src/engine/shaders/skybox/sky/vs.js +23 -0
  97. package/src/engine/shaders/skybox/sunset/fs.js +101 -0
  98. package/src/engine/shaders/skybox/sunset/vs.js +23 -0
  99. package/src/engine/shaders/transition/blur/fs.js +42 -0
  100. package/src/engine/shaders/transition/blur/vs.js +26 -0
  101. package/src/engine/shaders/transition/cross/fs.js +36 -0
  102. package/src/engine/shaders/transition/cross/vs.js +26 -0
  103. package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
  104. package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
  105. package/src/engine/shaders/transition/dissolve/fs.js +78 -0
  106. package/src/engine/shaders/transition/dissolve/vs.js +24 -0
  107. package/src/engine/shaders/transition/fade/fs.js +31 -0
  108. package/src/engine/shaders/transition/fade/vs.js +27 -0
  109. package/src/engine/shaders/transition/iris/fs.js +52 -0
  110. package/src/engine/shaders/transition/iris/vs.js +24 -0
  111. package/src/engine/shaders/transition/pixelate/fs.js +44 -0
  112. package/src/engine/shaders/transition/pixelate/vs.js +24 -0
  113. package/src/engine/shaders/transition/slide/fs.js +53 -0
  114. package/src/engine/shaders/transition/slide/vs.js +24 -0
  115. package/src/engine/shaders/transition/swirl/fs.js +39 -0
  116. package/src/engine/shaders/transition/swirl/vs.js +26 -0
  117. package/src/engine/shaders/transition/wipe/fs.js +50 -0
  118. package/src/engine/shaders/transition/wipe/vs.js +24 -0
  119. package/src/engine/shaders/vs.js +60 -0
  120. package/src/engine/utils/CameraController.js +506 -0
  121. package/src/engine/utils/ObjHelper.js +551 -0
  122. package/src/engine/utils/debug-logger.js +110 -0
  123. package/src/engine/utils/enums.js +305 -0
  124. package/src/engine/utils/generator.js +156 -0
  125. package/src/engine/utils/index.js +21 -0
  126. package/src/engine/utils/loaders/ActionLoader.js +77 -0
  127. package/src/engine/utils/loaders/AudioLoader.js +157 -0
  128. package/src/engine/utils/loaders/EventLoader.js +66 -0
  129. package/src/engine/utils/loaders/ObjectLoader.js +67 -0
  130. package/src/engine/utils/loaders/SpriteLoader.js +77 -0
  131. package/src/engine/utils/loaders/TilesetLoader.js +103 -0
  132. package/src/engine/utils/loaders/index.js +21 -0
  133. package/src/engine/utils/math/matrix4.js +367 -0
  134. package/src/engine/utils/math/vector.js +458 -0
  135. package/src/engine/utils/obj/_old_js/index.js +46 -0
  136. package/src/engine/utils/obj/_old_js/layout.js +308 -0
  137. package/src/engine/utils/obj/_old_js/material.js +711 -0
  138. package/src/engine/utils/obj/_old_js/mesh.js +761 -0
  139. package/src/engine/utils/obj/_old_js/utils.js +647 -0
  140. package/src/engine/utils/obj/index.js +24 -0
  141. package/src/engine/utils/obj/js/index.js +277 -0
  142. package/src/engine/utils/obj/js/loader.js +232 -0
  143. package/src/engine/utils/obj/layout.js +246 -0
  144. package/src/engine/utils/obj/material.js +665 -0
  145. package/src/engine/utils/obj/mesh.js +657 -0
  146. package/src/engine/utils/obj/ts/index.ts +72 -0
  147. package/src/engine/utils/obj/ts/layout.ts +265 -0
  148. package/src/engine/utils/obj/ts/material.ts +760 -0
  149. package/src/engine/utils/obj/ts/mesh.ts +785 -0
  150. package/src/engine/utils/obj/ts/utils.ts +501 -0
  151. package/src/engine/utils/obj/utils.js +428 -0
  152. package/src/engine/utils/resources.js +18 -0
  153. package/src/index.jsx +55 -0
  154. package/src/spritz/player.js +18 -0
  155. package/src/spritz/readme.md +18 -0
  156. package/LICENSE +0 -437
  157. package/dist/bundle.js.LICENSE.txt +0 -31
@@ -0,0 +1,514 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import { debug } from '../utils/debug-logger.js';
15
+
16
+ /**
17
+ * @typedef {object} CallbackRegistration
18
+ * @property {string} id - Unique callback ID.
19
+ * @property {string} event - Event name (supports wildcards).
20
+ * @property {Function} handler - Callback function.
21
+ * @property {object} [context] - 'this' context for handler.
22
+ * @property {number} [priority=0] - Execution priority (higher = earlier).
23
+ * @property {boolean} [once=false] - Remove after first invocation.
24
+ * @property {object} [filter] - Optional event data filter.
25
+ */
26
+
27
+ /**
28
+ * @typedef {object} CallbackEvent
29
+ * @property {string} type - Event type name.
30
+ * @property {object} data - Event payload.
31
+ * @property {number} timestamp - Event timestamp.
32
+ * @property {boolean} defaultPrevented - Whether default was prevented.
33
+ * @property {boolean} propagationStopped - Whether propagation was stopped.
34
+ * @property {string} [target] - Target entity ID.
35
+ * @property {string} [source] - Source entity ID.
36
+ */
37
+
38
+ /**
39
+ * CallbackManager - Manages engine event callbacks for scripting integration.
40
+ * Supports zone/sprite/trigger events with priority and filtering.
41
+ */
42
+ export default class CallbackManager {
43
+ /**
44
+ * Creates an instance of CallbackManager.
45
+ * @param {import('../core/index.js').default} engine - The engine instance.
46
+ */
47
+ constructor(engine) {
48
+ /** @type {import('../core/index.js').default} */
49
+ this.engine = engine;
50
+
51
+ /** @type {Map<string, CallbackRegistration[]>} Event name -> registrations */
52
+ this.callbacks = new Map();
53
+
54
+ /** @type {Map<string, CallbackRegistration[]>} Wildcard patterns */
55
+ this.wildcardCallbacks = new Map();
56
+
57
+ /** @type {number} Unique ID counter */
58
+ this.idCounter = 0;
59
+
60
+ /** @type {Set<string>} IDs to remove after current emission */
61
+ this.pendingRemovals = new Set();
62
+
63
+ /** @type {boolean} Currently emitting an event */
64
+ this.isEmitting = false;
65
+
66
+ /** @type {CallbackEvent[]} Event queue for deferred processing */
67
+ this.eventQueue = [];
68
+
69
+ /** @type {boolean} Process events immediately or queue them */
70
+ this.deferredMode = false;
71
+
72
+ // Register built-in event types
73
+ this.builtInEvents = [
74
+ 'zone:enter', 'zone:exit', 'zone:load', 'zone:unload',
75
+ 'sprite:click', 'sprite:hover', 'sprite:collide', 'sprite:spawn', 'sprite:destroy',
76
+ 'trigger:enter', 'trigger:exit', 'trigger:activate',
77
+ 'action:start', 'action:complete', 'action:cancel',
78
+ 'player:move', 'player:interact', 'player:damage', 'player:heal',
79
+ 'game:start', 'game:pause', 'game:resume', 'game:stop',
80
+ 'cutscene:start', 'cutscene:end', 'cutscene:skip',
81
+ 'menu:open', 'menu:close', 'menu:select',
82
+ 'input:key', 'input:gamepad', 'input:mouse',
83
+ 'update', 'render', 'physics'
84
+ ];
85
+ }
86
+
87
+ /**
88
+ * Register a callback for an event.
89
+ * @param {string} event - Event name (supports wildcards like 'sprite:*').
90
+ * @param {Function} handler - Callback function.
91
+ * @param {object} [options={}] - Registration options.
92
+ * @returns {string} Registration ID for unregistering.
93
+ */
94
+ on(event, handler, options = {}) {
95
+ const id = `cb_${++this.idCounter}`;
96
+ const registration = {
97
+ id,
98
+ event,
99
+ handler,
100
+ context: options.context || null,
101
+ priority: options.priority || 0,
102
+ once: options.once || false,
103
+ filter: options.filter || null
104
+ };
105
+
106
+ if (event.includes('*')) {
107
+ // Wildcard registration
108
+ if (!this.wildcardCallbacks.has(event)) {
109
+ this.wildcardCallbacks.set(event, []);
110
+ }
111
+ this.wildcardCallbacks.get(event).push(registration);
112
+ this.sortByPriority(this.wildcardCallbacks.get(event));
113
+ } else {
114
+ // Direct registration
115
+ if (!this.callbacks.has(event)) {
116
+ this.callbacks.set(event, []);
117
+ }
118
+ this.callbacks.get(event).push(registration);
119
+ this.sortByPriority(this.callbacks.get(event));
120
+ }
121
+
122
+ debug('CallbackManager', `Registered callback ${id} for event: ${event}`);
123
+ return id;
124
+ }
125
+
126
+ /**
127
+ * Register a one-time callback.
128
+ * @param {string} event - Event name.
129
+ * @param {Function} handler - Callback function.
130
+ * @param {object} [options={}] - Registration options.
131
+ * @returns {string} Registration ID.
132
+ */
133
+ once(event, handler, options = {}) {
134
+ return this.on(event, handler, { ...options, once: true });
135
+ }
136
+
137
+ /**
138
+ * Unregister a callback by ID.
139
+ * @param {string} id - Registration ID.
140
+ * @returns {boolean} Whether the callback was found and removed.
141
+ */
142
+ off(id) {
143
+ if (this.isEmitting) {
144
+ // Defer removal until after emission
145
+ this.pendingRemovals.add(id);
146
+ return true;
147
+ }
148
+
149
+ return this.removeById(id);
150
+ }
151
+
152
+ /**
153
+ * Remove a callback by ID from all maps.
154
+ * @param {string} id - Registration ID.
155
+ * @returns {boolean} Whether found and removed.
156
+ */
157
+ removeById(id) {
158
+ for (const [event, registrations] of this.callbacks) {
159
+ const idx = registrations.findIndex(r => r.id === id);
160
+ if (idx >= 0) {
161
+ registrations.splice(idx, 1);
162
+ if (registrations.length === 0) this.callbacks.delete(event);
163
+ debug('CallbackManager', `Removed callback ${id}`);
164
+ return true;
165
+ }
166
+ }
167
+
168
+ for (const [pattern, registrations] of this.wildcardCallbacks) {
169
+ const idx = registrations.findIndex(r => r.id === id);
170
+ if (idx >= 0) {
171
+ registrations.splice(idx, 1);
172
+ if (registrations.length === 0) this.wildcardCallbacks.delete(pattern);
173
+ return true;
174
+ }
175
+ }
176
+
177
+ return false;
178
+ }
179
+
180
+ /**
181
+ * Remove all callbacks for an event.
182
+ * @param {string} event - Event name.
183
+ */
184
+ offAll(event) {
185
+ this.callbacks.delete(event);
186
+ this.wildcardCallbacks.delete(event);
187
+ }
188
+
189
+ /**
190
+ * Emit an event to all registered callbacks.
191
+ * @param {string} event - Event name.
192
+ * @param {object} [data={}] - Event data payload.
193
+ * @returns {CallbackEvent} The event object (check defaultPrevented).
194
+ */
195
+ emit(event, data = {}) {
196
+ const callbackEvent = {
197
+ type: event,
198
+ data,
199
+ timestamp: Date.now(),
200
+ defaultPrevented: false,
201
+ propagationStopped: false,
202
+ target: data.target || null,
203
+ source: data.source || null,
204
+
205
+ preventDefault() {
206
+ this.defaultPrevented = true;
207
+ },
208
+
209
+ stopPropagation() {
210
+ this.propagationStopped = true;
211
+ }
212
+ };
213
+
214
+ if (this.deferredMode) {
215
+ this.eventQueue.push(callbackEvent);
216
+ return callbackEvent;
217
+ }
218
+
219
+ this.processEvent(callbackEvent);
220
+ return callbackEvent;
221
+ }
222
+
223
+ /**
224
+ * Process a single event.
225
+ * @param {CallbackEvent} event - Event to process.
226
+ */
227
+ processEvent(event) {
228
+ this.isEmitting = true;
229
+ const toRemove = [];
230
+
231
+ // Get direct callbacks
232
+ const directCallbacks = this.callbacks.get(event.type) || [];
233
+
234
+ // Get wildcard callbacks
235
+ const wildcardMatches = this.getWildcardMatches(event.type);
236
+
237
+ // Combine and sort by priority
238
+ const allCallbacks = [...directCallbacks, ...wildcardMatches];
239
+ this.sortByPriority(allCallbacks);
240
+
241
+ for (const registration of allCallbacks) {
242
+ if (event.propagationStopped) break;
243
+
244
+ // Check filter
245
+ if (registration.filter && !this.matchesFilter(event.data, registration.filter)) {
246
+ continue;
247
+ }
248
+
249
+ try {
250
+ if (registration.context) {
251
+ registration.handler.call(registration.context, event);
252
+ } else {
253
+ registration.handler(event);
254
+ }
255
+
256
+ if (registration.once) {
257
+ toRemove.push(registration.id);
258
+ }
259
+ } catch (error) {
260
+ console.error(`CallbackManager: Error in callback for ${event.type}:`, error);
261
+ }
262
+ }
263
+
264
+ this.isEmitting = false;
265
+
266
+ // Clean up once callbacks and pending removals
267
+ for (const id of toRemove) {
268
+ this.removeById(id);
269
+ }
270
+
271
+ for (const id of this.pendingRemovals) {
272
+ this.removeById(id);
273
+ }
274
+ this.pendingRemovals.clear();
275
+ }
276
+
277
+ /**
278
+ * Get all wildcard registrations matching an event.
279
+ * @param {string} event - Event name.
280
+ * @returns {CallbackRegistration[]} Matching registrations.
281
+ */
282
+ getWildcardMatches(event) {
283
+ const matches = [];
284
+
285
+ for (const [pattern, registrations] of this.wildcardCallbacks) {
286
+ if (this.matchesPattern(event, pattern)) {
287
+ matches.push(...registrations);
288
+ }
289
+ }
290
+
291
+ return matches;
292
+ }
293
+
294
+ /**
295
+ * Check if an event matches a wildcard pattern.
296
+ * @param {string} event - Event name.
297
+ * @param {string} pattern - Pattern with wildcards.
298
+ * @returns {boolean} Whether it matches.
299
+ */
300
+ matchesPattern(event, pattern) {
301
+ // Convert pattern to regex
302
+ const regexStr = '^' + pattern
303
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
304
+ .replace(/\*/g, '.*')
305
+ .replace(/\?/g, '.') + '$';
306
+
307
+ return new RegExp(regexStr).test(event);
308
+ }
309
+
310
+ /**
311
+ * Check if event data matches a filter.
312
+ * @param {object} data - Event data.
313
+ * @param {object} filter - Filter criteria.
314
+ * @returns {boolean} Whether data matches filter.
315
+ */
316
+ matchesFilter(data, filter) {
317
+ for (const [key, value] of Object.entries(filter)) {
318
+ if (typeof value === 'function') {
319
+ if (!value(data[key])) return false;
320
+ } else if (data[key] !== value) {
321
+ return false;
322
+ }
323
+ }
324
+ return true;
325
+ }
326
+
327
+ /**
328
+ * Sort registrations by priority (descending).
329
+ * @param {CallbackRegistration[]} registrations - Array to sort.
330
+ */
331
+ sortByPriority(registrations) {
332
+ registrations.sort((a, b) => b.priority - a.priority);
333
+ }
334
+
335
+ /**
336
+ * Process all queued events (when in deferred mode).
337
+ */
338
+ processQueue() {
339
+ while (this.eventQueue.length > 0) {
340
+ const event = this.eventQueue.shift();
341
+ this.processEvent(event);
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Enable or disable deferred event processing.
347
+ * @param {boolean} enabled - Whether to defer events.
348
+ */
349
+ setDeferredMode(enabled) {
350
+ this.deferredMode = enabled;
351
+ if (!enabled) {
352
+ this.processQueue();
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Create callback hooks for zone events.
358
+ * @param {string} zoneId - Zone identifier.
359
+ * @param {object} callbacks - Callback definitions.
360
+ * @returns {string[]} Registration IDs.
361
+ */
362
+ registerZoneCallbacks(zoneId, callbacks) {
363
+ const ids = [];
364
+
365
+ if (callbacks.onEnter) {
366
+ ids.push(this.on('zone:enter', callbacks.onEnter, {
367
+ filter: { zoneId }
368
+ }));
369
+ }
370
+
371
+ if (callbacks.onExit) {
372
+ ids.push(this.on('zone:exit', callbacks.onExit, {
373
+ filter: { zoneId }
374
+ }));
375
+ }
376
+
377
+ if (callbacks.onLoad) {
378
+ ids.push(this.on('zone:load', callbacks.onLoad, {
379
+ filter: { zoneId }
380
+ }));
381
+ }
382
+
383
+ return ids;
384
+ }
385
+
386
+ /**
387
+ * Create callback hooks for sprite events.
388
+ * @param {string} spriteId - Sprite identifier.
389
+ * @param {object} callbacks - Callback definitions.
390
+ * @returns {string[]} Registration IDs.
391
+ */
392
+ registerSpriteCallbacks(spriteId, callbacks) {
393
+ const ids = [];
394
+
395
+ if (callbacks.onClick) {
396
+ ids.push(this.on('sprite:click', callbacks.onClick, {
397
+ filter: { spriteId }
398
+ }));
399
+ }
400
+
401
+ if (callbacks.onHover) {
402
+ ids.push(this.on('sprite:hover', callbacks.onHover, {
403
+ filter: { spriteId }
404
+ }));
405
+ }
406
+
407
+ if (callbacks.onCollide) {
408
+ ids.push(this.on('sprite:collide', callbacks.onCollide, {
409
+ filter: { spriteId }
410
+ }));
411
+ }
412
+
413
+ return ids;
414
+ }
415
+
416
+ /**
417
+ * Create callback hooks for trigger events.
418
+ * @param {string} triggerId - Trigger identifier.
419
+ * @param {object} callbacks - Callback definitions.
420
+ * @returns {string[]} Registration IDs.
421
+ */
422
+ registerTriggerCallbacks(triggerId, callbacks) {
423
+ const ids = [];
424
+
425
+ if (callbacks.onEnter) {
426
+ ids.push(this.on('trigger:enter', callbacks.onEnter, {
427
+ filter: { triggerId }
428
+ }));
429
+ }
430
+
431
+ if (callbacks.onExit) {
432
+ ids.push(this.on('trigger:exit', callbacks.onExit, {
433
+ filter: { triggerId }
434
+ }));
435
+ }
436
+
437
+ if (callbacks.onActivate) {
438
+ ids.push(this.on('trigger:activate', callbacks.onActivate, {
439
+ filter: { triggerId }
440
+ }));
441
+ }
442
+
443
+ return ids;
444
+ }
445
+
446
+ /**
447
+ * Register an update callback (called every frame).
448
+ * @param {Function} handler - Update function receiving delta time.
449
+ * @param {number} [priority=0] - Priority.
450
+ * @returns {string} Registration ID.
451
+ */
452
+ onUpdate(handler, priority = 0) {
453
+ return this.on('update', (event) => {
454
+ handler(event.data.dt, event.data.time);
455
+ }, { priority });
456
+ }
457
+
458
+ /**
459
+ * Get all registered event names.
460
+ * @returns {string[]} List of event names.
461
+ */
462
+ getRegisteredEvents() {
463
+ return [
464
+ ...this.callbacks.keys(),
465
+ ...this.wildcardCallbacks.keys()
466
+ ];
467
+ }
468
+
469
+ /**
470
+ * Get count of registrations for an event.
471
+ * @param {string} event - Event name.
472
+ * @returns {number} Count.
473
+ */
474
+ getListenerCount(event) {
475
+ const direct = this.callbacks.get(event)?.length || 0;
476
+ const wildcard = this.getWildcardMatches(event).length;
477
+ return direct + wildcard;
478
+ }
479
+
480
+ /**
481
+ * Clear all callbacks.
482
+ */
483
+ clear() {
484
+ this.callbacks.clear();
485
+ this.wildcardCallbacks.clear();
486
+ this.eventQueue.length = 0;
487
+ this.pendingRemovals.clear();
488
+ }
489
+
490
+ /**
491
+ * Create a Lua-compatible callback table for PixoScript.
492
+ * @returns {object} Lua table with callback functions.
493
+ */
494
+ getLuaBindings() {
495
+ const manager = this;
496
+
497
+ return {
498
+ on: (event, handler) => manager.on(event, handler),
499
+ once: (event, handler) => manager.once(event, handler),
500
+ off: (id) => manager.off(id),
501
+ emit: (event, data) => manager.emit(event, data?.toObject?.() || data),
502
+
503
+ // Convenience methods
504
+ on_zone_enter: (zoneId, handler) => manager.on('zone:enter', handler, { filter: { zoneId } }),
505
+ on_zone_exit: (zoneId, handler) => manager.on('zone:exit', handler, { filter: { zoneId } }),
506
+ on_sprite_click: (spriteId, handler) => manager.on('sprite:click', handler, { filter: { spriteId } }),
507
+ on_trigger_enter: (triggerId, handler) => manager.on('trigger:enter', handler, { filter: { triggerId } }),
508
+ on_action_start: (handler) => manager.on('action:start', handler),
509
+ on_action_complete: (handler) => manager.on('action:complete', handler),
510
+ on_update: (handler) => manager.onUpdate(handler),
511
+ on_event: (pattern, handler) => manager.on(pattern, handler)
512
+ };
513
+ }
514
+ }
@@ -0,0 +1,81 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import PixoScriptLibrary from '@Engine/scripting/PixoScriptLibrary.js';
15
+ import * as pixoscript from 'pixoscript';
16
+
17
+ export default class PixoScriptInterpreter {
18
+ constructor(engine) {
19
+ this.engine = engine;
20
+ this.pixoscript = pixoscript;
21
+ this.pixosLib = new PixoScriptLibrary(this.pixoscript);
22
+ this.scope = {};
23
+ this.env = null;
24
+ this.library = null;
25
+ // Cache for loaded scripts (simulated filesystem)
26
+ this._scriptCache = new Map();
27
+ }
28
+
29
+ setScope = (scope) => {
30
+ this.scope = scope;
31
+ };
32
+
33
+ getScope = () => {
34
+ return this.scope;
35
+ };
36
+
37
+ /**
38
+ * Register a script in the virtual filesystem for require() support
39
+ * @param {string} path - Virtual path like "mymodule" or "lib/utils"
40
+ * @param {string} content - The script content
41
+ */
42
+ registerScript = (path, content) => {
43
+ this._scriptCache.set(path, content);
44
+ this._scriptCache.set(path + '.pxs', content);
45
+ };
46
+
47
+ createEnv = () => {
48
+ // Create config with virtual filesystem handlers
49
+ const config = {
50
+ PIXOSCRIPT_PATH: './?.pxs;./?/init.pxs',
51
+ fileExists: (path) => {
52
+ // Check if path exists in our script cache
53
+ const normalizedPath = path.replace(/^\.\//, '');
54
+ return this._scriptCache.has(normalizedPath);
55
+ },
56
+ loadFile: (path) => {
57
+ const normalizedPath = path.replace(/^\.\//, '');
58
+ const content = this._scriptCache.get(normalizedPath);
59
+ if (!content) {
60
+ throw new Error(`Script not found: ${path}`);
61
+ }
62
+ return content;
63
+ }
64
+ };
65
+
66
+ this.env = this.pixoscript.createEnv(config);
67
+ return this.env;
68
+ };
69
+
70
+ initLibrary = () => {
71
+ if (!this.env) this.createEnv();
72
+ this.library = this.pixosLib.getLibrary(this.engine, this.scope);
73
+ this.env.loadLib('pixos', this.library);
74
+ };
75
+
76
+ run = async (script) => {
77
+ if (!this.env) this.createEnv();
78
+ if (!this.library) this.initLibrary();
79
+ return this.env.parse(script).exec();
80
+ };
81
+ }