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.
- package/README.md +36 -286
- package/dist/bundle.js +13 -3
- package/dist/bundle.js.map +1 -1
- package/dist/style.css +1 -0
- package/package.json +43 -44
- package/src/components/WebGLView.jsx +318 -0
- package/src/css/pixos.css +372 -0
- package/src/engine/actions/animate.js +41 -0
- package/src/engine/actions/changezone.js +135 -0
- package/src/engine/actions/chat.js +109 -0
- package/src/engine/actions/dialogue.js +90 -0
- package/src/engine/actions/face.js +22 -0
- package/src/engine/actions/greeting.js +28 -0
- package/src/engine/actions/interact.js +86 -0
- package/src/engine/actions/move.js +67 -0
- package/src/engine/actions/patrol.js +109 -0
- package/src/engine/actions/prompt.js +185 -0
- package/src/engine/actions/script.js +42 -0
- package/src/engine/core/audio/AudioSystem.js +543 -0
- package/src/engine/core/cutscene/PxcPlayer.js +956 -0
- package/src/engine/core/cutscene/manager.js +243 -0
- package/src/engine/core/database/index.js +75 -0
- package/src/engine/core/debug/index.js +371 -0
- package/src/engine/core/hud/index.js +765 -0
- package/src/engine/core/index.js +540 -0
- package/src/engine/core/input/gamepad/Controller.js +71 -0
- package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
- package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
- package/src/engine/core/input/gamepad/index.js +592 -0
- package/src/engine/core/input/keyboard.js +196 -0
- package/src/engine/core/input/manager.js +485 -0
- package/src/engine/core/input/mouse.js +203 -0
- package/src/engine/core/input/touch.js +175 -0
- package/src/engine/core/mode/manager.js +199 -0
- package/src/engine/core/net/manager.js +535 -0
- package/src/engine/core/queue/action.js +83 -0
- package/src/engine/core/queue/event.js +82 -0
- package/src/engine/core/queue/index.js +44 -0
- package/src/engine/core/queue/loadable.js +33 -0
- package/src/engine/core/render/CameraEffects.js +494 -0
- package/src/engine/core/render/FrustumCuller.js +417 -0
- package/src/engine/core/render/LODManager.js +285 -0
- package/src/engine/core/render/ParticleManager.js +529 -0
- package/src/engine/core/render/TextureAtlas.js +465 -0
- package/src/engine/core/render/camera.js +338 -0
- package/src/engine/core/render/light.js +197 -0
- package/src/engine/core/render/manager.js +1079 -0
- package/src/engine/core/render/shaders.js +110 -0
- package/src/engine/core/render/skybox.js +342 -0
- package/src/engine/core/resource/manager.js +133 -0
- package/src/engine/core/resource/object.js +611 -0
- package/src/engine/core/resource/texture.js +103 -0
- package/src/engine/core/resource/tileset.js +177 -0
- package/src/engine/core/scene/avatar.js +215 -0
- package/src/engine/core/scene/speech.js +138 -0
- package/src/engine/core/scene/sprite.js +702 -0
- package/src/engine/core/scene/spritz.js +189 -0
- package/src/engine/core/scene/world.js +681 -0
- package/src/engine/core/scene/zone.js +1167 -0
- package/src/engine/core/store/index.js +110 -0
- package/src/engine/dynamic/animatedSprite.js +64 -0
- package/src/engine/dynamic/animatedTile.js +98 -0
- package/src/engine/dynamic/avatar.js +110 -0
- package/src/engine/dynamic/map.js +174 -0
- package/src/engine/dynamic/sprite.js +255 -0
- package/src/engine/dynamic/spritz.js +119 -0
- package/src/engine/events/EventSystem.js +609 -0
- package/src/engine/events/camera.js +142 -0
- package/src/engine/events/chat.js +75 -0
- package/src/engine/events/menu.js +186 -0
- package/src/engine/scripting/CallbackManager.js +514 -0
- package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
- package/src/engine/scripting/PixoScriptLibrary.js +704 -0
- package/src/engine/shaders/effects/index.js +450 -0
- package/src/engine/shaders/fs.js +222 -0
- package/src/engine/shaders/particles/fs.js +41 -0
- package/src/engine/shaders/particles/vs.js +61 -0
- package/src/engine/shaders/picker/fs.js +34 -0
- package/src/engine/shaders/picker/init.js +62 -0
- package/src/engine/shaders/picker/vs.js +42 -0
- package/src/engine/shaders/pxsl/README.md +250 -0
- package/src/engine/shaders/pxsl/index.js +25 -0
- package/src/engine/shaders/pxsl/library.js +608 -0
- package/src/engine/shaders/pxsl/manager.js +338 -0
- package/src/engine/shaders/pxsl/specification.js +363 -0
- package/src/engine/shaders/pxsl/transpiler.js +753 -0
- package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
- package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
- package/src/engine/shaders/skybox/matrix/fs.js +127 -0
- package/src/engine/shaders/skybox/matrix/vs.js +23 -0
- package/src/engine/shaders/skybox/morning/fs.js +109 -0
- package/src/engine/shaders/skybox/morning/vs.js +23 -0
- package/src/engine/shaders/skybox/neon/fs.js +119 -0
- package/src/engine/shaders/skybox/neon/vs.js +23 -0
- package/src/engine/shaders/skybox/sky/fs.js +114 -0
- package/src/engine/shaders/skybox/sky/vs.js +23 -0
- package/src/engine/shaders/skybox/sunset/fs.js +101 -0
- package/src/engine/shaders/skybox/sunset/vs.js +23 -0
- package/src/engine/shaders/transition/blur/fs.js +42 -0
- package/src/engine/shaders/transition/blur/vs.js +26 -0
- package/src/engine/shaders/transition/cross/fs.js +36 -0
- package/src/engine/shaders/transition/cross/vs.js +26 -0
- package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
- package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
- package/src/engine/shaders/transition/dissolve/fs.js +78 -0
- package/src/engine/shaders/transition/dissolve/vs.js +24 -0
- package/src/engine/shaders/transition/fade/fs.js +31 -0
- package/src/engine/shaders/transition/fade/vs.js +27 -0
- package/src/engine/shaders/transition/iris/fs.js +52 -0
- package/src/engine/shaders/transition/iris/vs.js +24 -0
- package/src/engine/shaders/transition/pixelate/fs.js +44 -0
- package/src/engine/shaders/transition/pixelate/vs.js +24 -0
- package/src/engine/shaders/transition/slide/fs.js +53 -0
- package/src/engine/shaders/transition/slide/vs.js +24 -0
- package/src/engine/shaders/transition/swirl/fs.js +39 -0
- package/src/engine/shaders/transition/swirl/vs.js +26 -0
- package/src/engine/shaders/transition/wipe/fs.js +50 -0
- package/src/engine/shaders/transition/wipe/vs.js +24 -0
- package/src/engine/shaders/vs.js +60 -0
- package/src/engine/utils/CameraController.js +506 -0
- package/src/engine/utils/ObjHelper.js +551 -0
- package/src/engine/utils/debug-logger.js +110 -0
- package/src/engine/utils/enums.js +305 -0
- package/src/engine/utils/generator.js +156 -0
- package/src/engine/utils/index.js +21 -0
- package/src/engine/utils/loaders/ActionLoader.js +77 -0
- package/src/engine/utils/loaders/AudioLoader.js +157 -0
- package/src/engine/utils/loaders/EventLoader.js +66 -0
- package/src/engine/utils/loaders/ObjectLoader.js +67 -0
- package/src/engine/utils/loaders/SpriteLoader.js +77 -0
- package/src/engine/utils/loaders/TilesetLoader.js +103 -0
- package/src/engine/utils/loaders/index.js +21 -0
- package/src/engine/utils/math/matrix4.js +367 -0
- package/src/engine/utils/math/vector.js +458 -0
- package/src/engine/utils/obj/_old_js/index.js +46 -0
- package/src/engine/utils/obj/_old_js/layout.js +308 -0
- package/src/engine/utils/obj/_old_js/material.js +711 -0
- package/src/engine/utils/obj/_old_js/mesh.js +761 -0
- package/src/engine/utils/obj/_old_js/utils.js +647 -0
- package/src/engine/utils/obj/index.js +24 -0
- package/src/engine/utils/obj/js/index.js +277 -0
- package/src/engine/utils/obj/js/loader.js +232 -0
- package/src/engine/utils/obj/layout.js +246 -0
- package/src/engine/utils/obj/material.js +665 -0
- package/src/engine/utils/obj/mesh.js +657 -0
- package/src/engine/utils/obj/ts/index.ts +72 -0
- package/src/engine/utils/obj/ts/layout.ts +265 -0
- package/src/engine/utils/obj/ts/material.ts +760 -0
- package/src/engine/utils/obj/ts/mesh.ts +785 -0
- package/src/engine/utils/obj/ts/utils.ts +501 -0
- package/src/engine/utils/obj/utils.js +428 -0
- package/src/engine/utils/resources.js +18 -0
- package/src/index.jsx +55 -0
- package/src/spritz/player.js +18 -0
- package/src/spritz/readme.md +18 -0
- package/LICENSE +0 -437
- package/dist/bundle.js.LICENSE.txt +0 -31
|
@@ -0,0 +1,704 @@
|
|
|
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 { EventLoader } from '@Engine/utils/loaders/index.js';
|
|
15
|
+
import PxcPlayer from '@Engine/core/cutscene/PxcPlayer.js';
|
|
16
|
+
import { debug } from '@Engine/utils/debug-logger.js';
|
|
17
|
+
|
|
18
|
+
export default class PixoScriptLibrary {
|
|
19
|
+
/**
|
|
20
|
+
* Constructor
|
|
21
|
+
* @param {pixoscript} pixoscript - PixoScript Library
|
|
22
|
+
* @constructor
|
|
23
|
+
*/
|
|
24
|
+
constructor(pixoscript) {
|
|
25
|
+
this.pixoscript = pixoscript;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create Script Environment
|
|
30
|
+
*/
|
|
31
|
+
getLibrary = (engine, envScope) => {
|
|
32
|
+
debug('PixoScriptLibrary', 'creating library', { envScope });
|
|
33
|
+
|
|
34
|
+
return new this.pixoscript.Table({
|
|
35
|
+
// passed in scope
|
|
36
|
+
...envScope,
|
|
37
|
+
// core functions
|
|
38
|
+
get_caller: () => {
|
|
39
|
+
return envScope._this;
|
|
40
|
+
},
|
|
41
|
+
get_subject: () => {
|
|
42
|
+
return envScope.subject;
|
|
43
|
+
},
|
|
44
|
+
get_map: () => {
|
|
45
|
+
return envScope.map || envScope.zone;
|
|
46
|
+
},
|
|
47
|
+
get_zone: () => {
|
|
48
|
+
return envScope.map || envScope.zone;
|
|
49
|
+
},
|
|
50
|
+
get_world: () => {
|
|
51
|
+
return engine.spritz.world;
|
|
52
|
+
},
|
|
53
|
+
// network functions
|
|
54
|
+
send_action: (action = false) => {
|
|
55
|
+
const { networkManager } = engine;
|
|
56
|
+
if (networkManager && networkManager.ws) {
|
|
57
|
+
const sent = networkManager.sendAction(action.toObject());
|
|
58
|
+
if (action)
|
|
59
|
+
return () => Promise.resolve(sent);
|
|
60
|
+
return sent;
|
|
61
|
+
}
|
|
62
|
+
if (action)
|
|
63
|
+
return () => Promise.resolve(false);
|
|
64
|
+
return false;
|
|
65
|
+
},
|
|
66
|
+
// flag functions
|
|
67
|
+
all_flags: (action = false) => {
|
|
68
|
+
const flags = engine.store.all();
|
|
69
|
+
if (action)
|
|
70
|
+
return () => Promise.resolve(flags);
|
|
71
|
+
return flags;
|
|
72
|
+
},
|
|
73
|
+
has_flag: (key, action = false) => {
|
|
74
|
+
debug('PixoScript', 'checking flag via lua', key, action);
|
|
75
|
+
const hasFlag = engine.store.keys().includes(key);
|
|
76
|
+
if (action)
|
|
77
|
+
return () => Promise.resolve(hasFlag);
|
|
78
|
+
return hasFlag;
|
|
79
|
+
},
|
|
80
|
+
set_flag: (key, value, action = false) => {
|
|
81
|
+
debug('PixoScript', 'setting flag via lua', key, action);
|
|
82
|
+
const flag = engine.store.set(key, value.toObject());
|
|
83
|
+
if (action)
|
|
84
|
+
return () => Promise.resolve(flag);
|
|
85
|
+
return flag;
|
|
86
|
+
},
|
|
87
|
+
add_flag: (key, value, action = false) => {
|
|
88
|
+
debug('PixoScript', 'adding flag via lua', key, action);
|
|
89
|
+
engine.store.add(key, value.toObject());
|
|
90
|
+
if (action)
|
|
91
|
+
return () => Promise.resolve(true);
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
get_flag: (key, action = false) => {
|
|
95
|
+
debug('PixoScript', 'getting flag via lua', key, action);
|
|
96
|
+
const flag = engine.store.get(key);
|
|
97
|
+
if (action)
|
|
98
|
+
return () => Promise.resolve(flag);
|
|
99
|
+
return flag;
|
|
100
|
+
},
|
|
101
|
+
// world functions
|
|
102
|
+
remove_all_zones: () => {
|
|
103
|
+
debug('PixoScript', 'removing all zones via lua');
|
|
104
|
+
return engine.spritz.world.removeAllZones();
|
|
105
|
+
},
|
|
106
|
+
load_zone_from_zip: (z, zip) => {
|
|
107
|
+
debug('PixoScript', 'loading zone from zip via lua', { world: engine.spritz.world, z, zip });
|
|
108
|
+
// When loading zones via Lua we allow the world to manage screen
|
|
109
|
+
// transitions. Passing `undefined` (or omitting the parameter) causes
|
|
110
|
+
// World.loadZoneFromZip() to use its default transition settings
|
|
111
|
+
// (typically a fade in/out). This keeps portal transitions consistent
|
|
112
|
+
// with other zone loads while still avoiding duplicate fades when the
|
|
113
|
+
// engine is already transitioning. The world implementation guards
|
|
114
|
+
// against overlapping transitions via the renderManager.isTransitioning
|
|
115
|
+
// flag and skips transitions if the zone is already cached.
|
|
116
|
+
return engine.spritz.world.loadZoneFromZip(z, zip, false);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Register a cutscene definition from Lua. The `steps` parameter
|
|
121
|
+
* should be a Lua table (array-like) whose entries are tables with
|
|
122
|
+
* step definitions (type, effect, direction, duration, zone, etc.).
|
|
123
|
+
* These tables are converted to plain JS objects and stored under
|
|
124
|
+
* the provided name. Once registered, call pixos.start_cutscene(name)
|
|
125
|
+
* to play it. If a cutscene with the same name already exists it
|
|
126
|
+
* will be replaced.
|
|
127
|
+
*
|
|
128
|
+
* Example Lua:
|
|
129
|
+
* pixos.register_cutscene("intro", {
|
|
130
|
+
* { type = 'transition', effect = 'fade', direction = 'out', duration = 500 },
|
|
131
|
+
* { type = 'load_zone', zone = 'village', effect = 'fade', duration = 500 },
|
|
132
|
+
* { type = 'transition', effect = 'fade', direction = 'in', duration = 500 },
|
|
133
|
+
* })
|
|
134
|
+
* @param {string} name
|
|
135
|
+
* @param {table} steps
|
|
136
|
+
*/
|
|
137
|
+
register_cutscene: (name, steps) => {
|
|
138
|
+
try {
|
|
139
|
+
// Convert Lua table to JS array of step objects
|
|
140
|
+
const arr = this.pixoscript.utils.ensureArray(steps.toObject());
|
|
141
|
+
const jsSteps = arr.map((item) => {
|
|
142
|
+
// `item` may be a Lua Table; convert to JS object
|
|
143
|
+
return item && typeof item.toObject === 'function' ? item.toObject() : item;
|
|
144
|
+
});
|
|
145
|
+
engine.cutsceneManager.register(name, jsSteps);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
console.warn('Failed to register cutscene from Lua', e);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Start a pre-registered cutscene by name. Returns immediately. The
|
|
153
|
+
* cutscene will run asynchronously. Use pixos.skip_cutscene() to
|
|
154
|
+
* cancel the currently playing cutscene.
|
|
155
|
+
* @param {string} name
|
|
156
|
+
*/
|
|
157
|
+
start_cutscene: (name) => {
|
|
158
|
+
try {
|
|
159
|
+
engine.cutsceneManager.start(name);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.warn('Failed to start cutscene', name, e);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Skip the currently active cutscene, if any. Clears the cutscene
|
|
167
|
+
* queue immediately. Safe to call even if no cutscene is running.
|
|
168
|
+
*/
|
|
169
|
+
skip_cutscene: () => {
|
|
170
|
+
try {
|
|
171
|
+
engine.cutsceneManager.skip();
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.warn('Failed to skip cutscene', e);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Set the backdrop for the current cutscene.
|
|
179
|
+
* @param {string} backdrop - The backdrop label to set.
|
|
180
|
+
*/
|
|
181
|
+
set_backdrop: (backdrop) => {
|
|
182
|
+
try {
|
|
183
|
+
engine.cutsceneManager.setBackdrop({ backdrop });
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.warn('Failed to set backdrop', e);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Show a cutout in the current cutscene.
|
|
191
|
+
* @param {string} sprite - The sprite ID.
|
|
192
|
+
* @param {string} cutout - The cutout label.
|
|
193
|
+
* @param {string} [position='left'] - The position ('left' or 'right').
|
|
194
|
+
*/
|
|
195
|
+
show_cutout: (sprite, cutout, position = 'left') => {
|
|
196
|
+
try {
|
|
197
|
+
engine.cutsceneManager.showCutout({ sprite, cutout, position });
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.warn('Failed to show cutout', e);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Play a cutscene by name. Supports both:
|
|
205
|
+
* 1. Pre-registered cutscene names (registered via register_cutscene)
|
|
206
|
+
* 2. File paths to .pxc cutscene files
|
|
207
|
+
*
|
|
208
|
+
* Returns a function that can be yielded in a Lua script.
|
|
209
|
+
*
|
|
210
|
+
* Example Lua:
|
|
211
|
+
* pixos.sync({ pixos.play_cutscene('intro') })
|
|
212
|
+
* pixos.sync({ pixos.play_cutscene('cutscenes/opening.pxc') })
|
|
213
|
+
*
|
|
214
|
+
* @param {string} cutscene - Cutscene name or file path
|
|
215
|
+
* @returns {function} Async function that resolves when cutscene completes
|
|
216
|
+
*/
|
|
217
|
+
play_cutscene: (cutscene) => {
|
|
218
|
+
return () =>
|
|
219
|
+
new Promise(async (resolve) => {
|
|
220
|
+
try {
|
|
221
|
+
// Check if this is a file path (ends with .pxc) or a registered cutscene name
|
|
222
|
+
if (typeof cutscene === 'string' && cutscene.endsWith('.pxc')) {
|
|
223
|
+
// Load and play .pxc file
|
|
224
|
+
const scriptText = await engine.assetLoader.load(cutscene);
|
|
225
|
+
if (scriptText) {
|
|
226
|
+
const player = new PxcPlayer(engine, {
|
|
227
|
+
onEnd: () => resolve()
|
|
228
|
+
});
|
|
229
|
+
await player.playCutscene(scriptText);
|
|
230
|
+
} else {
|
|
231
|
+
console.warn('[play_cutscene] Failed to load cutscene file:', cutscene);
|
|
232
|
+
resolve();
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
// Try registered cutscene first
|
|
236
|
+
if (engine.cutsceneManager && engine.cutsceneManager.isRegistered?.(cutscene)) {
|
|
237
|
+
engine.cutsceneManager.start(cutscene);
|
|
238
|
+
// Poll until cutscene finishes
|
|
239
|
+
const poll = () => {
|
|
240
|
+
if (!engine.cutsceneManager.isRunning()) {
|
|
241
|
+
resolve();
|
|
242
|
+
} else {
|
|
243
|
+
setTimeout(poll, 30);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
poll();
|
|
247
|
+
} else if (envScope.zone?.playCutscene) {
|
|
248
|
+
// Fallback to zone's playCutscene method
|
|
249
|
+
await envScope.zone.playCutscene(cutscene);
|
|
250
|
+
resolve();
|
|
251
|
+
} else {
|
|
252
|
+
console.warn('[play_cutscene] Cutscene not found:', cutscene);
|
|
253
|
+
resolve();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch (e) {
|
|
257
|
+
console.warn('[play_cutscene] Error playing cutscene:', e);
|
|
258
|
+
resolve();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* TODO - This is working well - but I need to fix up older cutscene methods,
|
|
265
|
+
* and consolidate everything together. Zones, etc, should all work using the
|
|
266
|
+
* same system.
|
|
267
|
+
*
|
|
268
|
+
* Run an ad-hoc cutscene defined by a Lua table of steps. Returns a
|
|
269
|
+
* function that can be yielded in a Lua script and executed via
|
|
270
|
+
* pixos.sync. The returned function resolves when the cutscene
|
|
271
|
+
* completes. Steps are converted to plain JS objects. A unique
|
|
272
|
+
* temporary name is generated for each call to avoid collisions.
|
|
273
|
+
*
|
|
274
|
+
* Example Lua:
|
|
275
|
+
* local steps = {
|
|
276
|
+
* { type = 'transition', effect = 'cross', direction = 'out', duration = 500 },
|
|
277
|
+
* { type = 'load_zone', zone = 'cave', effect = 'cross', duration = 500 },
|
|
278
|
+
* { type = 'transition', effect = 'cross', direction = 'in', duration = 500 },
|
|
279
|
+
* }
|
|
280
|
+
* pixos.sync({ pixos.run_cutscene(steps) })
|
|
281
|
+
*/
|
|
282
|
+
run_cutscene: (steps) => {
|
|
283
|
+
// Return an async function that the Lua runtime will call
|
|
284
|
+
return () =>
|
|
285
|
+
new Promise((resolve) => {
|
|
286
|
+
try {
|
|
287
|
+
const arr = this.pixoscript.utils.ensureArray(steps.toObject());
|
|
288
|
+
const jsSteps = arr.map((item) => {
|
|
289
|
+
return item && typeof item.toObject === 'function' ? item.toObject() : item;
|
|
290
|
+
});
|
|
291
|
+
// Generate a unique name for this temporary cutscene
|
|
292
|
+
const name = '__lua_cutscene_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
|
|
293
|
+
engine.cutsceneManager.register(name, jsSteps);
|
|
294
|
+
engine.cutsceneManager.start(name);
|
|
295
|
+
// Poll until cutscene finishes
|
|
296
|
+
const poll = () => {
|
|
297
|
+
if (!engine.cutsceneManager.isRunning()) {
|
|
298
|
+
resolve();
|
|
299
|
+
} else {
|
|
300
|
+
setTimeout(poll, 30);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
poll();
|
|
304
|
+
} catch (e) {
|
|
305
|
+
console.warn('Failed to run cutscene', e);
|
|
306
|
+
resolve();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
run_transition: (effect = 'fade', direction = 'out', duration = 500) => {
|
|
311
|
+
return () => {
|
|
312
|
+
new Promise((resolve) => {
|
|
313
|
+
const rm = engine.renderManager;
|
|
314
|
+
if (!rm) resolve();
|
|
315
|
+
return rm.startTransition({ effect, direction, duration }).then(() => resolve());
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
sprite_dialogue: (spriteId, dialogue, options = {}) => {
|
|
320
|
+
return () =>
|
|
321
|
+
new Promise((resolve) => {
|
|
322
|
+
debug('PixoScript', 'playing dialogue via lua', { zone: envScope.zone, spriteId, dialogue });
|
|
323
|
+
options.onClose = () => resolve();
|
|
324
|
+
return envScope.zone.spriteDialogue(spriteId, dialogue, options).then(() => {
|
|
325
|
+
debug('PixoScript', 'played dialogue via lua', { zone: envScope.zone, spriteId, dialogue });
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
move_sprite: (spriteId, location, running) => {
|
|
330
|
+
return () =>
|
|
331
|
+
new Promise((resolve) => {
|
|
332
|
+
debug('PixoScript', 'moving sprite via lua', { zone: envScope.zone, spriteId, location, running });
|
|
333
|
+
return envScope.zone.moveSprite(spriteId, this.pixoscript.utils.ensureArray(location.toObject()), running).then(() => {
|
|
334
|
+
debug('PixoScript', 'moved sprite via lua', { zone: envScope.zone, spriteId, location, running });
|
|
335
|
+
resolve();
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
load_scripts: (scripts) => {
|
|
340
|
+
debug('PixoScript', 'loading scripts via lua', { scripts, envScope });
|
|
341
|
+
return envScope.zone.loadScripts(scripts);
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Play a .pxc cutscene file
|
|
346
|
+
* Returns a function that resolves when cutscene completes
|
|
347
|
+
*
|
|
348
|
+
* Example:
|
|
349
|
+
* pixos.sync({ pixos.play_pxc_cutscene('cutscenes/intro.pxc') })
|
|
350
|
+
*/
|
|
351
|
+
play_pxc_cutscene: (filePath, options = {}) => {
|
|
352
|
+
return () =>
|
|
353
|
+
new Promise(async (resolve) => {
|
|
354
|
+
try {
|
|
355
|
+
// Load the .pxc file from asset loader
|
|
356
|
+
const scriptText = await engine.assetLoader.load(filePath);
|
|
357
|
+
|
|
358
|
+
if (!scriptText) {
|
|
359
|
+
console.error('[PixoScript] Failed to load cutscene:', filePath);
|
|
360
|
+
resolve();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Create PxcPlayer instance with callbacks
|
|
365
|
+
const callbacks = {
|
|
366
|
+
onDialogueShow: options.onDialogueShow || ((data) => {
|
|
367
|
+
debug('PxcPlayer', 'Dialogue:', data.actor, data.text);
|
|
368
|
+
}),
|
|
369
|
+
onBackdropChange: options.onBackdropChange || ((url, opts) => {
|
|
370
|
+
debug('PxcPlayer', 'Backdrop:', url);
|
|
371
|
+
}),
|
|
372
|
+
onEnd: () => {
|
|
373
|
+
debug('PxcPlayer', 'Cutscene ended');
|
|
374
|
+
if (options.onEnd) options.onEnd();
|
|
375
|
+
resolve();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const player = new PxcPlayer(engine, callbacks);
|
|
380
|
+
|
|
381
|
+
// Play the cutscene
|
|
382
|
+
await player.playCutscene(scriptText);
|
|
383
|
+
|
|
384
|
+
} catch (e) {
|
|
385
|
+
console.error('[PixoScript] Error playing .pxc cutscene:', e);
|
|
386
|
+
resolve();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Play inline .pxc cutscene script
|
|
393
|
+
* Returns a function that resolves when cutscene completes
|
|
394
|
+
*
|
|
395
|
+
* Example:
|
|
396
|
+
* local script = [[
|
|
397
|
+
* @backdrop textures/room.gif
|
|
398
|
+
* HERO: [expression=smile] Hello!
|
|
399
|
+
* waitInput
|
|
400
|
+
* @end
|
|
401
|
+
* ]]
|
|
402
|
+
* pixos.sync({ pixos.play_pxc_script(script) })
|
|
403
|
+
*/
|
|
404
|
+
play_pxc_script: (scriptText, options = {}) => {
|
|
405
|
+
return () =>
|
|
406
|
+
new Promise(async (resolve) => {
|
|
407
|
+
try {
|
|
408
|
+
debug('PixoScript', 'Playing inline .pxc script');
|
|
409
|
+
|
|
410
|
+
// Create PxcPlayer instance with callbacks
|
|
411
|
+
const callbacks = {
|
|
412
|
+
onDialogueShow: options.onDialogueShow || ((data) => {
|
|
413
|
+
debug('PxcPlayer', 'Dialogue:', data.actor, data.text);
|
|
414
|
+
}),
|
|
415
|
+
onBackdropChange: options.onBackdropChange || ((url, opts) => {
|
|
416
|
+
debug('PxcPlayer', 'Backdrop:', url);
|
|
417
|
+
}),
|
|
418
|
+
onEnd: () => {
|
|
419
|
+
debug('PxcPlayer', 'Cutscene ended');
|
|
420
|
+
if (options.onEnd) options.onEnd();
|
|
421
|
+
resolve();
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const player = new PxcPlayer(engine, callbacks);
|
|
426
|
+
|
|
427
|
+
// Play the cutscene
|
|
428
|
+
await player.playCutscene(scriptText);
|
|
429
|
+
|
|
430
|
+
} catch (e) {
|
|
431
|
+
console.error('[PixoScript] Error playing inline .pxc script:', e);
|
|
432
|
+
resolve();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
|
|
437
|
+
// camera functions
|
|
438
|
+
// ...
|
|
439
|
+
set_camera: () => {
|
|
440
|
+
engine.renderManager.camera.setCamera();
|
|
441
|
+
},
|
|
442
|
+
get_camera_vector: () => {
|
|
443
|
+
return engine.renderManager.camera.cameraTarget;
|
|
444
|
+
},
|
|
445
|
+
look_at: (pos, trgt, up) => {
|
|
446
|
+
let position = this.pixoscript.utils.ensureArray(pos.toObject());
|
|
447
|
+
let target = this.pixoscript.utils.ensureArray(trgt.toObject());
|
|
448
|
+
let upDir = this.pixoscript.utils.ensureArray(up.toObject());
|
|
449
|
+
engine.renderManager.camera.lookAt(position, target, upDir);
|
|
450
|
+
},
|
|
451
|
+
pan_camera: (from, to, duration) => {
|
|
452
|
+
debug('PixoScript', 'panning camera via lua', { from, to, duration });
|
|
453
|
+
return () =>
|
|
454
|
+
new Promise((resolve) => {
|
|
455
|
+
engine.spritz.world.addEvent(
|
|
456
|
+
new EventLoader(
|
|
457
|
+
engine,
|
|
458
|
+
'camera',
|
|
459
|
+
[
|
|
460
|
+
'pan',
|
|
461
|
+
{
|
|
462
|
+
from: from,
|
|
463
|
+
to: to,
|
|
464
|
+
duration: duration,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
engine.spritz.world,
|
|
468
|
+
async () => {
|
|
469
|
+
resolve();
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
);
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
_pan: (direction, radians = Math.PI / 4) => {
|
|
477
|
+
if (direction === 'CCW') {
|
|
478
|
+
engine.renderManager.camera.panCCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
479
|
+
} else {
|
|
480
|
+
engine.renderManager.camera.panCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
pitch: (direction, radians = Math.PI / 4) => {
|
|
484
|
+
if (direction === 'CCW') {
|
|
485
|
+
engine.renderManager.camera.pitchCCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
486
|
+
} else {
|
|
487
|
+
engine.renderManager.camera.pitchCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
tilt: (direction, radians = Math.PI / 4) => {
|
|
491
|
+
if (direction === 'CCW') {
|
|
492
|
+
engine.renderManager.camera.tiltCCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
493
|
+
} else {
|
|
494
|
+
engine.renderManager.camera.tiltCW(this.pixoscript.utils.coerceToNumber(radians));
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// input functions
|
|
499
|
+
bind_action: (action, inputType, inputValue) => {
|
|
500
|
+
try {
|
|
501
|
+
if (engine.inputManager) {
|
|
502
|
+
engine.inputManager.bindAction(action, inputType, inputValue);
|
|
503
|
+
}
|
|
504
|
+
} catch (e) {
|
|
505
|
+
console.warn('bind_action failed', e);
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
unbind_action: (action, inputType) => {
|
|
509
|
+
try {
|
|
510
|
+
if (engine.inputManager) {
|
|
511
|
+
engine.inputManager.unbindAction(action, inputType);
|
|
512
|
+
}
|
|
513
|
+
} catch (e) {
|
|
514
|
+
console.warn('unbind_action failed', e);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
register_action_hook: (action, hook) => {
|
|
518
|
+
try {
|
|
519
|
+
if (engine.inputManager) {
|
|
520
|
+
engine.inputManager.registerActionHook(action, hook);
|
|
521
|
+
}
|
|
522
|
+
} catch (e) {
|
|
523
|
+
console.warn('register_action_hook failed', e);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
is_action_active: (action) => {
|
|
527
|
+
try {
|
|
528
|
+
return engine.inputManager ? engine.inputManager.isActionActive(action) : false;
|
|
529
|
+
} catch (e) {
|
|
530
|
+
console.warn('is_action_active failed', e);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
get_action_input: (action) => {
|
|
535
|
+
try {
|
|
536
|
+
return engine.inputManager ? engine.inputManager.getActionInput(action) : null;
|
|
537
|
+
} catch (e) {
|
|
538
|
+
console.warn('get_action_input failed', e);
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
// audio functions
|
|
544
|
+
// ...
|
|
545
|
+
|
|
546
|
+
// sprite functions
|
|
547
|
+
// ...
|
|
548
|
+
|
|
549
|
+
// math functions
|
|
550
|
+
vector: (tbl) => {
|
|
551
|
+
let [x, y, z] = this.pixoscript.utils.ensureArray(tbl.toObject());
|
|
552
|
+
return new engine.utils.Vector(x, y, z);
|
|
553
|
+
},
|
|
554
|
+
vec_sub: (a, b) => {
|
|
555
|
+
return a.sub(b);
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
// misc utils & functions
|
|
559
|
+
sync: async (p) => {
|
|
560
|
+
for (const a of p.toObject()) {
|
|
561
|
+
await a();
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
as_obj: (tbl) => {
|
|
565
|
+
return tbl.toObject();
|
|
566
|
+
},
|
|
567
|
+
as_array: (tbl) => {
|
|
568
|
+
return this.pixoscript.utils.ensureArray(tbl.toObject());
|
|
569
|
+
},
|
|
570
|
+
as_table: (obj) => {
|
|
571
|
+
const table = new this.pixoscript.Table();
|
|
572
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
573
|
+
table.set(key, value);
|
|
574
|
+
}
|
|
575
|
+
return table;
|
|
576
|
+
},
|
|
577
|
+
log: (msg) => {
|
|
578
|
+
debug('PixoScript', msg);
|
|
579
|
+
},
|
|
580
|
+
to: (obj, tbl) => {
|
|
581
|
+
for (const [key, value] of Object.entries(tbl.toObject())) {
|
|
582
|
+
obj[key] = value;
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
/** Mode API - allow Lua scripts to change or query current mode */
|
|
586
|
+
set_mode: (name, params) => {
|
|
587
|
+
try {
|
|
588
|
+
debug('PixoScript', 'pixos.set_mode called ->', name, params);
|
|
589
|
+
const world = engine.spritz.world;
|
|
590
|
+
if (world && world.modeManager) {
|
|
591
|
+
// params may be a Lua table - convert if necessary
|
|
592
|
+
const p = params && typeof params.toObject === 'function' ? params.toObject() : params;
|
|
593
|
+
world.modeManager.set(name, p);
|
|
594
|
+
}
|
|
595
|
+
} catch (e) {
|
|
596
|
+
console.warn('set_mode failed', e);
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
get_mode: () => {
|
|
600
|
+
try { return engine.spritz.world.modeManager.getMode(); } catch (e) { return null; }
|
|
601
|
+
},
|
|
602
|
+
set_mode_mappings: (name, params) => {
|
|
603
|
+
try {
|
|
604
|
+
debug('PixoScript', 'pixos.set_mode_mappings called ->', name, params);
|
|
605
|
+
if (engine && engine.inputManager) {
|
|
606
|
+
// params may be a Lua table - convert if necessary
|
|
607
|
+
const p = params && typeof params.toObject === 'function' ? params.toObject() : params;
|
|
608
|
+
engine.inputManager.setModeMappings(name, p);
|
|
609
|
+
}
|
|
610
|
+
} catch (e) {
|
|
611
|
+
console.warn('set_mode_mappings failed', e);
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
register_mode: (name, handlers) => {
|
|
615
|
+
try {
|
|
616
|
+
if (!name) {
|
|
617
|
+
console.warn('pixos.register_mode called with undefined name');
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
debug('PixoScript', 'pixos.register_mode called ->', name);
|
|
621
|
+
const world = engine.spritz.world;
|
|
622
|
+
if (!world || !world.modeManager) return;
|
|
623
|
+
// handlers may be a Lua table; convert to JS object safely
|
|
624
|
+
const h = {};
|
|
625
|
+
const asObj = handlers && typeof handlers.toObject === 'function' ? handlers.toObject() : handlers || {};
|
|
626
|
+
if (asObj.setup) h.setup = asObj.setup;
|
|
627
|
+
if (asObj.update) h.update = asObj.update;
|
|
628
|
+
if (asObj.teardown) h.teardown = asObj.teardown;
|
|
629
|
+
if (asObj.check_input) h.check_input = asObj.check_input;
|
|
630
|
+
if (asObj.on_select) h.on_select = asObj.on_select;
|
|
631
|
+
if (asObj.picker !== undefined) h.picker = asObj.picker;
|
|
632
|
+
world.modeManager.register(name, h);
|
|
633
|
+
} catch (e) { console.warn('register_mode failed', e); }
|
|
634
|
+
},
|
|
635
|
+
from: (obj, key) => {
|
|
636
|
+
return obj[key];
|
|
637
|
+
},
|
|
638
|
+
length: (tbl) => {
|
|
639
|
+
return tbl.length || 0;
|
|
640
|
+
},
|
|
641
|
+
callback_finish: (success) => {
|
|
642
|
+
debug('PixoScript', 'callback finish', { success });
|
|
643
|
+
if (envScope.finish) {
|
|
644
|
+
envScope.finish(success > 0);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
// skybox shader switching
|
|
648
|
+
set_skybox_shader: async (shaderName) => {
|
|
649
|
+
if (engine.renderManager?.skyboxManager?.setSkyboxShader) {
|
|
650
|
+
await engine.renderManager.skyboxManager.setSkyboxShader(shaderName);
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
// particle system
|
|
654
|
+
emit_particles: (posTbl, cfgTbl) => {
|
|
655
|
+
try {
|
|
656
|
+
const pos = posTbl && typeof posTbl.toObject === 'function' ? posTbl.toObject() : posTbl || [0, 0, 0];
|
|
657
|
+
const cfg = cfgTbl && typeof cfgTbl.toObject === 'function' ? cfgTbl.toObject() : cfgTbl || {};
|
|
658
|
+
if (engine.renderManager && engine.renderManager.particleManager) {
|
|
659
|
+
// allow shorthand preset names
|
|
660
|
+
if (cfg.preset) {
|
|
661
|
+
const presetCfg = engine.renderManager.particleManager.preset(cfg.preset);
|
|
662
|
+
if (presetCfg) Object.assign(cfg, presetCfg);
|
|
663
|
+
}
|
|
664
|
+
engine.renderManager.particleManager.emit(pos, cfg);
|
|
665
|
+
}
|
|
666
|
+
} catch (e) {
|
|
667
|
+
console.warn('emit_particles failed', e);
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
create_particles: (posTbl, presetName) => {
|
|
671
|
+
try {
|
|
672
|
+
const pos = posTbl && typeof posTbl.toObject === 'function' ? posTbl.toObject() : posTbl || [0, 0, 0];
|
|
673
|
+
const preset = presetName || null;
|
|
674
|
+
if (engine.renderManager && engine.renderManager.particleManager) {
|
|
675
|
+
const cfg = preset ? engine.renderManager.particleManager.preset(preset) : {};
|
|
676
|
+
engine.renderManager.particleManager.emit(pos, cfg || {});
|
|
677
|
+
}
|
|
678
|
+
} catch (e) {
|
|
679
|
+
console.warn('create_particles failed', e);
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
clear_particles: () => {
|
|
683
|
+
try {
|
|
684
|
+
if (engine.renderManager && engine.renderManager.particleManager) {
|
|
685
|
+
engine.renderManager.particleManager.particles = [];
|
|
686
|
+
}
|
|
687
|
+
} catch (e) {
|
|
688
|
+
console.warn('clear_particles failed', e);
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
get_particle_count: () => {
|
|
692
|
+
try {
|
|
693
|
+
if (engine.renderManager && engine.renderManager.particleManager) {
|
|
694
|
+
return engine.renderManager.particleManager.particles.length;
|
|
695
|
+
}
|
|
696
|
+
return 0;
|
|
697
|
+
} catch (e) {
|
|
698
|
+
console.warn('get_particle_count failed', e);
|
|
699
|
+
return 0;
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
};
|
|
704
|
+
}
|