@zylem/game-lib 0.6.2 → 0.6.4

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -16
  3. package/dist/actions.d.ts +30 -21
  4. package/dist/actions.js +793 -146
  5. package/dist/actions.js.map +1 -1
  6. package/dist/behavior/jumper-2d.d.ts +114 -0
  7. package/dist/behavior/jumper-2d.js +711 -0
  8. package/dist/behavior/jumper-2d.js.map +1 -0
  9. package/dist/behavior/platformer-3d.d.ts +296 -0
  10. package/dist/behavior/platformer-3d.js +761 -0
  11. package/dist/behavior/platformer-3d.js.map +1 -0
  12. package/dist/behavior/ricochet-2d.d.ts +275 -0
  13. package/dist/behavior/ricochet-2d.js +425 -0
  14. package/dist/behavior/ricochet-2d.js.map +1 -0
  15. package/dist/behavior/ricochet-3d.d.ts +117 -0
  16. package/dist/behavior/ricochet-3d.js +443 -0
  17. package/dist/behavior/ricochet-3d.js.map +1 -0
  18. package/dist/behavior/screen-visibility.d.ts +79 -0
  19. package/dist/behavior/screen-visibility.js +358 -0
  20. package/dist/behavior/screen-visibility.js.map +1 -0
  21. package/dist/behavior/screen-wrap.d.ts +87 -0
  22. package/dist/behavior/screen-wrap.js +246 -0
  23. package/dist/behavior/screen-wrap.js.map +1 -0
  24. package/dist/behavior/shooter-2d.d.ts +79 -0
  25. package/dist/behavior/shooter-2d.js +180 -0
  26. package/dist/behavior/shooter-2d.js.map +1 -0
  27. package/dist/behavior/thruster.d.ts +11 -0
  28. package/dist/behavior/thruster.js +292 -0
  29. package/dist/behavior/thruster.js.map +1 -0
  30. package/dist/behavior/top-down-movement.d.ts +56 -0
  31. package/dist/behavior/top-down-movement.js +125 -0
  32. package/dist/behavior/top-down-movement.js.map +1 -0
  33. package/dist/behavior/world-boundary-2d.d.ts +142 -0
  34. package/dist/behavior/world-boundary-2d.js +235 -0
  35. package/dist/behavior/world-boundary-2d.js.map +1 -0
  36. package/dist/behavior/world-boundary-3d.d.ts +76 -0
  37. package/dist/behavior/world-boundary-3d.js +274 -0
  38. package/dist/behavior/world-boundary-3d.js.map +1 -0
  39. package/dist/behavior-descriptor-BXnVR8Ki.d.ts +159 -0
  40. package/dist/{blueprints-Cq3Ko6_G.d.ts → blueprints-DmbK2dki.d.ts} +2 -2
  41. package/dist/camera-4XO5gbQH.d.ts +905 -0
  42. package/dist/camera.d.ts +3 -2
  43. package/dist/camera.js +1653 -377
  44. package/dist/camera.js.map +1 -1
  45. package/dist/composition-BASvMKrW.d.ts +218 -0
  46. package/dist/{core-bO8TzV7u.d.ts → core-CARRaS55.d.ts} +110 -69
  47. package/dist/core.d.ts +11 -6
  48. package/dist/core.js +10766 -5626
  49. package/dist/core.js.map +1 -1
  50. package/dist/{entities-DvByhMGU.d.ts → entities-ChFirVL9.d.ts} +133 -29
  51. package/dist/entities.d.ts +5 -3
  52. package/dist/entities.js +4679 -3202
  53. package/dist/entities.js.map +1 -1
  54. package/dist/entity-vj-HTjzU.d.ts +1169 -0
  55. package/dist/global-change-2JvMaz44.d.ts +25 -0
  56. package/dist/main.d.ts +1118 -16
  57. package/dist/main.js +17538 -8499
  58. package/dist/main.js.map +1 -1
  59. package/dist/physics-pose-DCc4oE44.d.ts +25 -0
  60. package/dist/physics-protocol-BDD3P5W2.d.ts +200 -0
  61. package/dist/physics-worker.d.ts +21 -0
  62. package/dist/physics-worker.js +306 -0
  63. package/dist/physics-worker.js.map +1 -0
  64. package/dist/physics.d.ts +205 -0
  65. package/dist/physics.js +577 -0
  66. package/dist/physics.js.map +1 -0
  67. package/dist/stage-types-C19IhuzA.d.ts +731 -0
  68. package/dist/stage.d.ts +11 -7
  69. package/dist/stage.js +8024 -3852
  70. package/dist/stage.js.map +1 -1
  71. package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
  72. package/dist/thruster-23lzoPZd.d.ts +180 -0
  73. package/dist/world-DfgxoNMt.d.ts +105 -0
  74. package/package.json +53 -13
  75. package/dist/behaviors.d.ts +0 -854
  76. package/dist/behaviors.js +0 -1209
  77. package/dist/behaviors.js.map +0 -1
  78. package/dist/camera-CeJPAgGg.d.ts +0 -116
  79. package/dist/moveable-B_vyA6cw.d.ts +0 -67
  80. package/dist/stage-types-Bd-KtcYT.d.ts +0 -375
  81. package/dist/transformable-CUhvyuYO.d.ts +0 -67
  82. package/dist/world-C8tQ7Plj.d.ts +0 -774
@@ -0,0 +1,711 @@
1
+ // src/lib/behaviors/jumper-2d/components.ts
2
+ function createJumpConfig2D(options = {}) {
3
+ return {
4
+ jumpHeight: options.jumpHeight ?? 2.5,
5
+ gravity: options.gravity ?? 20,
6
+ maxFallSpeed: options.maxFallSpeed,
7
+ maxJumps: options.maxJumps ?? 1,
8
+ resetJumpsOnGround: options.resetJumpsOnGround ?? true,
9
+ coyoteTimeMs: options.coyoteTimeMs ?? 100,
10
+ jumpBufferMs: options.jumpBufferMs ?? 80,
11
+ minTimeBetweenJumpsMs: options.minTimeBetweenJumpsMs,
12
+ variableJump: options.variableJump
13
+ };
14
+ }
15
+ function createJumpInput2D() {
16
+ return {
17
+ jumpPressed: false,
18
+ jumpHeld: false,
19
+ jumpReleased: false,
20
+ fastFall: false
21
+ };
22
+ }
23
+ function createJumpState2D() {
24
+ return {
25
+ jumpsUsed: 0,
26
+ lastJumpTimeMs: 0,
27
+ bufferedJumpMs: 0,
28
+ coyoteMs: 0,
29
+ isJumping: false,
30
+ jumpHoldMs: 0,
31
+ jumpCutApplied: false
32
+ };
33
+ }
34
+
35
+ // src/lib/behaviors/jumper-2d/jumper-2d.behavior.ts
36
+ function jumpVelocityFromHeight(gravity, height) {
37
+ return Math.sqrt(2 * gravity * height);
38
+ }
39
+ var Jumper2DTickEvent = /* @__PURE__ */ ((Jumper2DTickEvent3) => {
40
+ Jumper2DTickEvent3["None"] = "none";
41
+ Jumper2DTickEvent3["Jump"] = "jump";
42
+ Jumper2DTickEvent3["Fall"] = "fall";
43
+ Jumper2DTickEvent3["Land"] = "land";
44
+ return Jumper2DTickEvent3;
45
+ })(Jumper2DTickEvent || {});
46
+ var Jumper2DBehavior = class {
47
+ tick(config, input, ctx, state) {
48
+ const dtMs = ctx.dt * 1e3;
49
+ const now = performance.now();
50
+ let event = "none" /* None */;
51
+ let jumpedThisFrame = false;
52
+ if (ctx.isGrounded) {
53
+ state.coyoteMs = config.coyoteTimeMs;
54
+ if (config.resetJumpsOnGround) {
55
+ state.jumpsUsed = 0;
56
+ }
57
+ if (state.isJumping) {
58
+ state.isJumping = false;
59
+ state.jumpCutApplied = false;
60
+ event = "land" /* Land */;
61
+ }
62
+ } else {
63
+ state.coyoteMs = Math.max(0, state.coyoteMs - dtMs);
64
+ }
65
+ if (input.jumpPressed) {
66
+ state.bufferedJumpMs = config.jumpBufferMs;
67
+ } else {
68
+ state.bufferedJumpMs = Math.max(0, state.bufferedJumpMs - dtMs);
69
+ }
70
+ if (state.isJumping && input.jumpHeld) {
71
+ state.jumpHoldMs += dtMs;
72
+ }
73
+ const wantsJump = state.bufferedJumpMs > 0;
74
+ const isFirstJump = ctx.isGrounded || state.coyoteMs > 0 && state.jumpsUsed === 0;
75
+ const hasAirJumps = state.jumpsUsed > 0 && state.jumpsUsed < config.maxJumps;
76
+ const canJump = isFirstJump || hasAirJumps;
77
+ const cooldownOk = config.minTimeBetweenJumpsMs == null || now - state.lastJumpTimeMs >= config.minTimeBetweenJumpsMs;
78
+ if (wantsJump && canJump && cooldownOk) {
79
+ ctx.setVerticalVelocity(jumpVelocityFromHeight(config.gravity, config.jumpHeight));
80
+ jumpedThisFrame = true;
81
+ state.jumpsUsed++;
82
+ state.lastJumpTimeMs = now;
83
+ state.bufferedJumpMs = 0;
84
+ state.coyoteMs = 0;
85
+ state.isJumping = true;
86
+ state.jumpHoldMs = 0;
87
+ state.jumpCutApplied = false;
88
+ event = "jump" /* Jump */;
89
+ }
90
+ if (!ctx.isGrounded || jumpedThisFrame) {
91
+ let gravityMul = 1;
92
+ const isFalling = ctx.velocityY < 0;
93
+ if (isFalling) {
94
+ gravityMul = input.fastFall ? 1.75 : 1;
95
+ if (event !== "land" /* Land */) {
96
+ event = "fall" /* Fall */;
97
+ }
98
+ } else if (state.isJumping) {
99
+ const variableJump = config.variableJump;
100
+ if (variableJump?.enabled && !state.jumpCutApplied) {
101
+ const holdExpired = variableJump.maxHoldMs != null && state.jumpHoldMs >= variableJump.maxHoldMs;
102
+ if (!input.jumpHeld || holdExpired) {
103
+ gravityMul = variableJump.cutGravityMultiplier;
104
+ state.jumpCutApplied = true;
105
+ }
106
+ }
107
+ }
108
+ let newVelocityY = ctx.velocityY - config.gravity * gravityMul * ctx.dt;
109
+ if (config.maxFallSpeed != null) {
110
+ newVelocityY = Math.max(-config.maxFallSpeed, newVelocityY);
111
+ }
112
+ ctx.setVerticalVelocity(newVelocityY);
113
+ } else {
114
+ ctx.setVerticalVelocity(0);
115
+ }
116
+ return { event };
117
+ }
118
+ };
119
+
120
+ // src/lib/core/utility/sync-state-machine.ts
121
+ import {
122
+ StateMachine as BaseStateMachine
123
+ } from "typescript-fsm";
124
+ import { t } from "typescript-fsm";
125
+ var SyncStateMachine = class extends BaseStateMachine {
126
+ constructor(init, transitions = [], logger = console) {
127
+ super(init, transitions, logger);
128
+ }
129
+ dispatch(_event, ..._args) {
130
+ throw new Error("SyncStateMachine does not support async dispatch.");
131
+ }
132
+ syncDispatch(event, ...args) {
133
+ const found = this.transitions.some((transition) => {
134
+ if (transition.fromState !== this._current || transition.event !== event) {
135
+ return false;
136
+ }
137
+ const current = this._current;
138
+ this._current = transition.toState;
139
+ if (!transition.cb) {
140
+ return true;
141
+ }
142
+ try {
143
+ transition.cb(...args);
144
+ return true;
145
+ } catch (error) {
146
+ this._current = current;
147
+ this.logger.error("Exception in callback", error);
148
+ throw error;
149
+ }
150
+ });
151
+ if (!found) {
152
+ const errorMessage = this.formatErr(this._current, event);
153
+ this.logger.error(errorMessage);
154
+ }
155
+ return found;
156
+ }
157
+ };
158
+
159
+ // src/lib/behaviors/jumper-2d/jumper-2d-fsm.ts
160
+ var Jumper2DState = /* @__PURE__ */ ((Jumper2DState2) => {
161
+ Jumper2DState2["Grounded"] = "grounded";
162
+ Jumper2DState2["Jumping"] = "jumping";
163
+ Jumper2DState2["Falling"] = "falling";
164
+ return Jumper2DState2;
165
+ })(Jumper2DState || {});
166
+ var Jumper2DEvent = /* @__PURE__ */ ((Jumper2DEvent2) => {
167
+ Jumper2DEvent2["Jump"] = "jump";
168
+ Jumper2DEvent2["Fall"] = "fall";
169
+ Jumper2DEvent2["Land"] = "land";
170
+ return Jumper2DEvent2;
171
+ })(Jumper2DEvent || {});
172
+ var Jumper2DFSM = class {
173
+ constructor(ctx) {
174
+ this.ctx = ctx;
175
+ this.machine = new SyncStateMachine(
176
+ "grounded" /* Grounded */,
177
+ [
178
+ t("grounded" /* Grounded */, "jump" /* Jump */, "jumping" /* Jumping */),
179
+ t("grounded" /* Grounded */, "fall" /* Fall */, "falling" /* Falling */),
180
+ t("grounded" /* Grounded */, "land" /* Land */, "grounded" /* Grounded */),
181
+ t("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
182
+ t("jumping" /* Jumping */, "land" /* Land */, "grounded" /* Grounded */),
183
+ t("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
184
+ t("falling" /* Falling */, "land" /* Land */, "grounded" /* Grounded */),
185
+ t("falling" /* Falling */, "jump" /* Jump */, "jumping" /* Jumping */),
186
+ t("falling" /* Falling */, "fall" /* Fall */, "falling" /* Falling */)
187
+ ]
188
+ );
189
+ }
190
+ machine;
191
+ getState() {
192
+ return this.machine.getState();
193
+ }
194
+ dispatch(event) {
195
+ if (this.machine.can(event)) {
196
+ this.machine.syncDispatch(event);
197
+ }
198
+ }
199
+ isJumping() {
200
+ return this.getState() === "jumping" /* Jumping */;
201
+ }
202
+ isGrounded() {
203
+ return this.getState() === "grounded" /* Grounded */;
204
+ }
205
+ getJumpsUsed() {
206
+ return this.ctx.state.jumpsUsed;
207
+ }
208
+ applyTickEvent(tickEvent, isGrounded) {
209
+ switch (tickEvent) {
210
+ case "jump":
211
+ this.dispatch("jump" /* Jump */);
212
+ break;
213
+ case "fall":
214
+ this.dispatch("fall" /* Fall */);
215
+ break;
216
+ case "land":
217
+ this.dispatch("land" /* Land */);
218
+ break;
219
+ default:
220
+ if (!isGrounded && this.getState() === "grounded" /* Grounded */) {
221
+ this.dispatch("fall" /* Fall */);
222
+ }
223
+ if (isGrounded && this.getState() !== "grounded" /* Grounded */) {
224
+ this.dispatch("land" /* Land */);
225
+ }
226
+ break;
227
+ }
228
+ }
229
+ };
230
+
231
+ // src/lib/behaviors/behavior-descriptor.ts
232
+ function defineBehavior(config) {
233
+ return {
234
+ key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
235
+ defaultOptions: config.defaultOptions,
236
+ systemFactory: config.systemFactory,
237
+ createHandle: config.createHandle
238
+ };
239
+ }
240
+
241
+ // src/lib/actions/capabilities/velocity-intents.ts
242
+ function setVelocityIntent(store, sourceId, vector, options = {}) {
243
+ const prev = store.velocityChannels[sourceId];
244
+ const mode = options.mode ?? prev?.mode ?? "replace";
245
+ const priority = options.priority ?? prev?.priority ?? 0;
246
+ const isAdditiveMerge = mode === "add" && prev?.mode === "add";
247
+ const x = vector.x == null ? prev?.x : isAdditiveMerge ? (prev?.x ?? 0) + vector.x : vector.x;
248
+ const y = vector.y == null ? prev?.y : isAdditiveMerge ? (prev?.y ?? 0) + vector.y : vector.y;
249
+ const z = vector.z == null ? prev?.z : isAdditiveMerge ? (prev?.z ?? 0) + vector.z : vector.z;
250
+ store.velocityChannels[sourceId] = {
251
+ x,
252
+ y,
253
+ z,
254
+ mode,
255
+ priority
256
+ };
257
+ store.dirty.velocityChannels = true;
258
+ }
259
+
260
+ // src/lib/behaviors/shared/ground-probe-3d.ts
261
+ import { Ray } from "@dimforge/rapier3d-compat";
262
+ import { BufferGeometry, Line, LineBasicMaterial, Vector3 } from "three";
263
+
264
+ // src/lib/physics/serialize-descriptors.ts
265
+ import { RigidBodyType } from "@dimforge/rapier3d-compat";
266
+ function serializeColliderDesc(desc) {
267
+ const internal = desc;
268
+ const customShapeData = internal.__zylemShapeData;
269
+ if (customShapeData?.shape === "trimesh") {
270
+ const result2 = {
271
+ shape: "trimesh",
272
+ dimensions: [],
273
+ vertices: [...customShapeData.vertices],
274
+ indices: [...customShapeData.indices]
275
+ };
276
+ const translation = internal.translation;
277
+ if (translation && (translation.x !== 0 || translation.y !== 0 || translation.z !== 0)) {
278
+ result2.translation = [translation.x, translation.y, translation.z];
279
+ }
280
+ if (internal.isSensor) {
281
+ result2.sensor = true;
282
+ }
283
+ if (internal.collisionGroups !== void 0 && internal.collisionGroups !== 4294967295) {
284
+ result2.collisionGroups = internal.collisionGroups;
285
+ }
286
+ if (internal.activeCollisionTypes !== void 0) {
287
+ result2.activeCollisionTypes = internal.activeCollisionTypes;
288
+ }
289
+ return result2;
290
+ }
291
+ const shapeType = internal.shape?.type ?? internal.shapeType ?? 0;
292
+ const { shape, dimensions, heightfieldMeta } = extractShapeData(shapeType, internal);
293
+ const result = {
294
+ shape,
295
+ dimensions
296
+ };
297
+ const t2 = internal.translation;
298
+ if (t2 && (t2.x !== 0 || t2.y !== 0 || t2.z !== 0)) {
299
+ result.translation = [t2.x, t2.y, t2.z];
300
+ }
301
+ if (internal.isSensor) {
302
+ result.sensor = true;
303
+ }
304
+ if (internal.collisionGroups !== void 0 && internal.collisionGroups !== 4294967295) {
305
+ result.collisionGroups = internal.collisionGroups;
306
+ }
307
+ if (internal.activeCollisionTypes !== void 0) {
308
+ result.activeCollisionTypes = internal.activeCollisionTypes;
309
+ }
310
+ if (heightfieldMeta) {
311
+ result.heightfieldMeta = heightfieldMeta;
312
+ }
313
+ return result;
314
+ }
315
+ function extractShapeData(shapeType, internal) {
316
+ switch (shapeType) {
317
+ case 0:
318
+ return {
319
+ shape: "ball",
320
+ dimensions: [internal.shape?.radius ?? internal.halfExtents?.x ?? 1]
321
+ };
322
+ case 1:
323
+ return {
324
+ shape: "cuboid",
325
+ dimensions: [
326
+ internal.shape?.halfExtents?.x ?? internal.halfExtents?.x ?? 0.5,
327
+ internal.shape?.halfExtents?.y ?? internal.halfExtents?.y ?? 0.5,
328
+ internal.shape?.halfExtents?.z ?? internal.halfExtents?.z ?? 0.5
329
+ ]
330
+ };
331
+ case 2:
332
+ return {
333
+ shape: "capsule",
334
+ dimensions: [
335
+ internal.shape?.halfHeight ?? 0.5,
336
+ internal.shape?.radius ?? 0.5
337
+ ]
338
+ };
339
+ case 6:
340
+ return {
341
+ shape: "cone",
342
+ dimensions: [
343
+ internal.shape?.halfHeight ?? 1,
344
+ internal.shape?.radius ?? 1
345
+ ]
346
+ };
347
+ case 7:
348
+ return {
349
+ shape: "cylinder",
350
+ dimensions: [
351
+ internal.shape?.halfHeight ?? 1,
352
+ internal.shape?.radius ?? 1
353
+ ]
354
+ };
355
+ case 11: {
356
+ const nrows = internal.shape?.nrows ?? 10;
357
+ const ncols = internal.shape?.ncols ?? 10;
358
+ const heights = internal.shape?.heights;
359
+ return {
360
+ shape: "heightfield",
361
+ dimensions: heights ? Array.from(heights) : [],
362
+ heightfieldMeta: { nrows, ncols }
363
+ };
364
+ }
365
+ default:
366
+ return { shape: "cuboid", dimensions: [0.5, 0.5, 0.5] };
367
+ }
368
+ }
369
+
370
+ // src/lib/behaviors/shared/ground-probe-3d.ts
371
+ var GROUND_SNAP_EPSILON = 1e-3;
372
+ var DEFAULT_OFFSETS = [
373
+ { x: 0, z: 0 },
374
+ { x: 0.4, z: 0.4 },
375
+ { x: -0.4, z: 0.4 },
376
+ { x: 0.4, z: -0.4 },
377
+ { x: -0.4, z: -0.4 }
378
+ ];
379
+ var GroundProbe3D = class {
380
+ constructor(world) {
381
+ this.world = world;
382
+ }
383
+ rays = /* @__PURE__ */ new Map();
384
+ debugLines = /* @__PURE__ */ new Map();
385
+ probeSupport(entity, options) {
386
+ if (!this.world?.world || !entity.body) return null;
387
+ const mode = options.mode ?? "any";
388
+ const offsets = mode === "center" ? (options.offsets ?? DEFAULT_OFFSETS).slice(0, 1) : options.offsets ?? DEFAULT_OFFSETS;
389
+ const translation = entity.body.translation();
390
+ const rays = this.getOrCreateRays(entity.uuid, offsets.length);
391
+ const originYOffset = options.originYOffset ?? 0;
392
+ let support = null;
393
+ for (let index = 0; index < offsets.length; index++) {
394
+ const offset = offsets[index];
395
+ const ray = rays[index];
396
+ ray.origin = {
397
+ x: translation.x + offset.x,
398
+ y: translation.y + originYOffset,
399
+ z: translation.z + offset.z
400
+ };
401
+ ray.dir = { x: 0, y: -1, z: 0 };
402
+ const hit = this.world.world.castRay(
403
+ ray,
404
+ options.rayLength,
405
+ true,
406
+ void 0,
407
+ void 0,
408
+ void 0,
409
+ entity.body
410
+ );
411
+ if (!hit) continue;
412
+ const nextSupport = {
413
+ toi: hit.toi,
414
+ point: {
415
+ x: ray.origin.x + ray.dir.x * hit.toi,
416
+ y: ray.origin.y + ray.dir.y * hit.toi,
417
+ z: ray.origin.z + ray.dir.z * hit.toi
418
+ },
419
+ origin: {
420
+ x: ray.origin.x,
421
+ y: ray.origin.y,
422
+ z: ray.origin.z
423
+ },
424
+ rayIndex: index,
425
+ colliderUuid: hit.collider?._parent?.userData?.uuid
426
+ };
427
+ if (mode === "center") {
428
+ support = nextSupport;
429
+ break;
430
+ }
431
+ if (!support || nextSupport.toi < support.toi) {
432
+ support = nextSupport;
433
+ }
434
+ }
435
+ if (options.debug && options.scene) {
436
+ this.updateDebugLines(
437
+ entity.uuid,
438
+ rays,
439
+ Boolean(support),
440
+ options.rayLength,
441
+ options.scene
442
+ );
443
+ } else {
444
+ this.disposeDebugLines(entity.uuid);
445
+ }
446
+ return support;
447
+ }
448
+ detect(entity, options) {
449
+ return this.probeSupport(entity, options) != null;
450
+ }
451
+ destroyEntity(uuid) {
452
+ this.rays.delete(uuid);
453
+ this.disposeDebugLines(uuid);
454
+ }
455
+ destroy() {
456
+ this.rays.clear();
457
+ for (const uuid of this.debugLines.keys()) {
458
+ this.disposeDebugLines(uuid);
459
+ }
460
+ this.debugLines.clear();
461
+ }
462
+ getOrCreateRays(uuid, count) {
463
+ let rays = this.rays.get(uuid);
464
+ if (!rays || rays.length !== count) {
465
+ rays = Array.from(
466
+ { length: count },
467
+ () => new Ray({ x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 })
468
+ );
469
+ this.rays.set(uuid, rays);
470
+ }
471
+ return rays;
472
+ }
473
+ updateDebugLines(uuid, rays, hasGround, length, scene) {
474
+ let lines = this.debugLines.get(uuid);
475
+ if (!lines) {
476
+ lines = rays.map(() => {
477
+ const geometry = new BufferGeometry().setFromPoints([
478
+ new Vector3(),
479
+ new Vector3()
480
+ ]);
481
+ const material = new LineBasicMaterial({ color: 16711680 });
482
+ const line = new Line(geometry, material);
483
+ scene.add(line);
484
+ return line;
485
+ });
486
+ this.debugLines.set(uuid, lines);
487
+ }
488
+ rays.forEach((ray, index) => {
489
+ const line = lines[index];
490
+ const start = new Vector3(ray.origin.x, ray.origin.y, ray.origin.z);
491
+ const end = new Vector3(
492
+ ray.origin.x + ray.dir.x * length,
493
+ ray.origin.y + ray.dir.y * length,
494
+ ray.origin.z + ray.dir.z * length
495
+ );
496
+ line.visible = true;
497
+ line.geometry.setFromPoints([start, end]);
498
+ line.material.color.setHex(
499
+ hasGround ? 65280 : 16711680
500
+ );
501
+ });
502
+ }
503
+ disposeDebugLines(uuid) {
504
+ const lines = this.debugLines.get(uuid);
505
+ if (!lines) return;
506
+ for (const line of lines) {
507
+ line.removeFromParent();
508
+ line.geometry.dispose();
509
+ line.material.dispose();
510
+ }
511
+ this.debugLines.delete(uuid);
512
+ }
513
+ };
514
+ function getGroundAnchorOffsetY(entity) {
515
+ const runtimeColliderDesc = entity?.colliderDesc;
516
+ if (runtimeColliderDesc) {
517
+ const serialized = serializeColliderDesc(runtimeColliderDesc);
518
+ const centerY2 = serialized.translation?.[1] ?? 0;
519
+ if (serialized.shape === "capsule" && serialized.dimensions.length >= 2) {
520
+ const halfCylinder = serialized.dimensions[0] ?? 0;
521
+ const radius = serialized.dimensions[1] ?? 0;
522
+ return halfCylinder + radius - centerY2;
523
+ }
524
+ if (serialized.shape === "cuboid" && serialized.dimensions.length >= 2) {
525
+ const halfHeight = serialized.dimensions[1] ?? 0;
526
+ return halfHeight - centerY2;
527
+ }
528
+ }
529
+ const collisionSize = entity?.options?.collision?.size ?? entity?.options?.collisionSize ?? entity?.options?.size;
530
+ const height = collisionSize?.y ?? 0;
531
+ if (height <= 0) {
532
+ return 0;
533
+ }
534
+ const collisionPosition = entity?.options?.collision?.position ?? entity?.options?.collisionPosition;
535
+ const centerY = collisionPosition?.y ?? height / 2;
536
+ return height / 2 - centerY;
537
+ }
538
+ function getGroundSnapTargetY(entity, support, epsilon = GROUND_SNAP_EPSILON) {
539
+ return support.point.y + getGroundAnchorOffsetY(entity) + epsilon;
540
+ }
541
+
542
+ // src/lib/behaviors/jumper-2d/jumper-2d.descriptor.ts
543
+ var defaultOptions = {
544
+ jumpHeight: 2.5,
545
+ gravity: 20,
546
+ maxJumps: 1,
547
+ resetJumpsOnGround: true,
548
+ coyoteTimeMs: 100,
549
+ jumpBufferMs: 80,
550
+ groundRayLength: 1,
551
+ debugGroundProbe: false
552
+ };
553
+ var JUMPER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:jumper-2d");
554
+ var JUMPER_2D_OFFSETS = [
555
+ { x: 0, z: 0 },
556
+ { x: 0.35, z: 0 },
557
+ { x: -0.35, z: 0 }
558
+ ];
559
+ function isJumper2DGrounded(params) {
560
+ if (params.velocityY > 0) {
561
+ return false;
562
+ }
563
+ if (params.snapToGroundDistance != null) {
564
+ return params.supportToi != null && params.supportToi <= params.snapToGroundDistance;
565
+ }
566
+ return params.nearGround === true;
567
+ }
568
+ function shouldSnapJumper2DToGround(support, options, velocityY) {
569
+ return support != null && options.snapToGroundDistance != null && velocityY <= 0 && support.toi <= options.snapToGroundDistance;
570
+ }
571
+ var Jumper2DBehaviorSystem = class {
572
+ constructor(world, scene, getBehaviorLinks) {
573
+ this.world = world;
574
+ this.scene = scene;
575
+ this.getBehaviorLinks = getBehaviorLinks;
576
+ this.groundProbe = new GroundProbe3D(world);
577
+ }
578
+ behavior = new Jumper2DBehavior();
579
+ groundProbe;
580
+ initializedEntities = /* @__PURE__ */ new Set();
581
+ timeSinceGroundedMs = /* @__PURE__ */ new Map();
582
+ wasJumpHeld = /* @__PURE__ */ new Map();
583
+ update(_ecs, delta) {
584
+ const links = this.getBehaviorLinks?.(JUMPER_2D_BEHAVIOR_KEY);
585
+ if (!links) return;
586
+ for (const link of links) {
587
+ const gameEntity = link.entity;
588
+ const jumperRef = link.ref;
589
+ if (!gameEntity.body) continue;
590
+ const options = jumperRef.options;
591
+ if (!gameEntity.jumper2d) {
592
+ gameEntity.jumper2d = createJumpConfig2D(options);
593
+ }
594
+ if (!gameEntity.$jumper2d) {
595
+ gameEntity.$jumper2d = createJumpInput2D();
596
+ }
597
+ if (!gameEntity.jumper2dState) {
598
+ gameEntity.jumper2dState = createJumpState2D();
599
+ }
600
+ if (!this.initializedEntities.has(gameEntity.uuid)) {
601
+ this.initializedEntities.add(gameEntity.uuid);
602
+ gameEntity.body.setGravityScale(0, true);
603
+ }
604
+ if (!jumperRef.fsm && gameEntity.jumper2dState) {
605
+ jumperRef.fsm = new Jumper2DFSM({
606
+ state: gameEntity.jumper2dState
607
+ });
608
+ }
609
+ const rayLength = options.groundRayLength ?? 1;
610
+ const bodyVelocity = gameEntity.body.linvel();
611
+ const probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);
612
+ const support = this.groundProbe.probeSupport(gameEntity, {
613
+ rayLength,
614
+ offsets: JUMPER_2D_OFFSETS,
615
+ mode: "any",
616
+ debug: options.debugGroundProbe ?? false,
617
+ scene: this.scene,
618
+ originYOffset: probeOriginYOffset
619
+ });
620
+ const nearGround = support != null;
621
+ const isGrounded = isJumper2DGrounded({
622
+ nearGround,
623
+ velocityY: bodyVelocity.y,
624
+ supportToi: support?.toi ?? null,
625
+ snapToGroundDistance: options.snapToGroundDistance
626
+ });
627
+ if (shouldSnapJumper2DToGround(support, options, bodyVelocity.y)) {
628
+ const currentPosition = gameEntity.body.translation();
629
+ gameEntity.body.setTranslation(
630
+ {
631
+ x: currentPosition.x,
632
+ y: getGroundSnapTargetY(gameEntity, support),
633
+ z: currentPosition.z
634
+ },
635
+ true
636
+ );
637
+ }
638
+ let timeSinceGroundedMs = this.timeSinceGroundedMs.get(gameEntity.uuid) ?? 0;
639
+ if (isGrounded) {
640
+ timeSinceGroundedMs = 0;
641
+ } else {
642
+ timeSinceGroundedMs += delta * 1e3;
643
+ }
644
+ this.timeSinceGroundedMs.set(gameEntity.uuid, timeSinceGroundedMs);
645
+ const config = gameEntity.jumper2d;
646
+ const input = gameEntity.$jumper2d;
647
+ const state = gameEntity.jumper2dState;
648
+ const previousHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;
649
+ const held = input.jumpHeld === true;
650
+ const effectiveInput = {
651
+ ...input,
652
+ jumpPressed: input.jumpPressed || held && !previousHeld,
653
+ jumpReleased: input.jumpReleased || !held && previousHeld
654
+ };
655
+ const store = gameEntity.transformStore;
656
+ const ctx = {
657
+ dt: delta,
658
+ velocityY: bodyVelocity.y,
659
+ isGrounded,
660
+ timeSinceGroundedMs,
661
+ setVerticalVelocity: (y) => {
662
+ setVelocityIntent(
663
+ store,
664
+ "jumper-2d",
665
+ { y },
666
+ { mode: "replace", priority: 20 }
667
+ );
668
+ ctx.velocityY = y;
669
+ }
670
+ };
671
+ const result = this.behavior.tick(config, effectiveInput, ctx, state);
672
+ this.wasJumpHeld.set(gameEntity.uuid, held);
673
+ if (jumperRef.fsm) {
674
+ jumperRef.fsm.applyTickEvent(result.event, isGrounded);
675
+ }
676
+ }
677
+ }
678
+ destroy(_ecs) {
679
+ this.groundProbe.destroy();
680
+ this.initializedEntities.clear();
681
+ this.timeSinceGroundedMs.clear();
682
+ this.wasJumpHeld.clear();
683
+ }
684
+ };
685
+ var Jumper2D = defineBehavior({
686
+ name: "jumper-2d",
687
+ defaultOptions,
688
+ systemFactory: (ctx) => new Jumper2DBehaviorSystem(ctx.world, ctx.scene, ctx.getBehaviorLinks),
689
+ createHandle: (ref) => ({
690
+ getState: () => ref.fsm?.getState() ?? "grounded" /* Grounded */,
691
+ isJumping: () => ref.fsm?.isJumping() ?? false,
692
+ getJumpsUsed: () => ref.fsm?.getJumpsUsed() ?? 0,
693
+ getJumpsRemaining: () => {
694
+ const maxJumps = ref.options.maxJumps ?? 1;
695
+ const used = ref.fsm?.getJumpsUsed() ?? 0;
696
+ return maxJumps - used;
697
+ }
698
+ })
699
+ });
700
+ export {
701
+ Jumper2D,
702
+ Jumper2DBehavior,
703
+ Jumper2DEvent,
704
+ Jumper2DFSM,
705
+ Jumper2DState,
706
+ Jumper2DTickEvent,
707
+ createJumpConfig2D,
708
+ createJumpInput2D,
709
+ createJumpState2D
710
+ };
711
+ //# sourceMappingURL=jumper-2d.js.map