@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.
- package/LICENSE +21 -0
- package/README.md +9 -16
- package/dist/actions.d.ts +30 -21
- package/dist/actions.js +793 -146
- package/dist/actions.js.map +1 -1
- package/dist/behavior/jumper-2d.d.ts +114 -0
- package/dist/behavior/jumper-2d.js +711 -0
- package/dist/behavior/jumper-2d.js.map +1 -0
- package/dist/behavior/platformer-3d.d.ts +296 -0
- package/dist/behavior/platformer-3d.js +761 -0
- package/dist/behavior/platformer-3d.js.map +1 -0
- package/dist/behavior/ricochet-2d.d.ts +275 -0
- package/dist/behavior/ricochet-2d.js +425 -0
- package/dist/behavior/ricochet-2d.js.map +1 -0
- package/dist/behavior/ricochet-3d.d.ts +117 -0
- package/dist/behavior/ricochet-3d.js +443 -0
- package/dist/behavior/ricochet-3d.js.map +1 -0
- package/dist/behavior/screen-visibility.d.ts +79 -0
- package/dist/behavior/screen-visibility.js +358 -0
- package/dist/behavior/screen-visibility.js.map +1 -0
- package/dist/behavior/screen-wrap.d.ts +87 -0
- package/dist/behavior/screen-wrap.js +246 -0
- package/dist/behavior/screen-wrap.js.map +1 -0
- package/dist/behavior/shooter-2d.d.ts +79 -0
- package/dist/behavior/shooter-2d.js +180 -0
- package/dist/behavior/shooter-2d.js.map +1 -0
- package/dist/behavior/thruster.d.ts +11 -0
- package/dist/behavior/thruster.js +292 -0
- package/dist/behavior/thruster.js.map +1 -0
- package/dist/behavior/top-down-movement.d.ts +56 -0
- package/dist/behavior/top-down-movement.js +125 -0
- package/dist/behavior/top-down-movement.js.map +1 -0
- package/dist/behavior/world-boundary-2d.d.ts +142 -0
- package/dist/behavior/world-boundary-2d.js +235 -0
- package/dist/behavior/world-boundary-2d.js.map +1 -0
- package/dist/behavior/world-boundary-3d.d.ts +76 -0
- package/dist/behavior/world-boundary-3d.js +274 -0
- package/dist/behavior/world-boundary-3d.js.map +1 -0
- package/dist/behavior-descriptor-BXnVR8Ki.d.ts +159 -0
- package/dist/{blueprints-Cq3Ko6_G.d.ts → blueprints-DmbK2dki.d.ts} +2 -2
- package/dist/camera-4XO5gbQH.d.ts +905 -0
- package/dist/camera.d.ts +3 -2
- package/dist/camera.js +1653 -377
- package/dist/camera.js.map +1 -1
- package/dist/composition-BASvMKrW.d.ts +218 -0
- package/dist/{core-bO8TzV7u.d.ts → core-CARRaS55.d.ts} +110 -69
- package/dist/core.d.ts +11 -6
- package/dist/core.js +10766 -5626
- package/dist/core.js.map +1 -1
- package/dist/{entities-DvByhMGU.d.ts → entities-ChFirVL9.d.ts} +133 -29
- package/dist/entities.d.ts +5 -3
- package/dist/entities.js +4679 -3202
- package/dist/entities.js.map +1 -1
- package/dist/entity-vj-HTjzU.d.ts +1169 -0
- package/dist/global-change-2JvMaz44.d.ts +25 -0
- package/dist/main.d.ts +1118 -16
- package/dist/main.js +17538 -8499
- package/dist/main.js.map +1 -1
- package/dist/physics-pose-DCc4oE44.d.ts +25 -0
- package/dist/physics-protocol-BDD3P5W2.d.ts +200 -0
- package/dist/physics-worker.d.ts +21 -0
- package/dist/physics-worker.js +306 -0
- package/dist/physics-worker.js.map +1 -0
- package/dist/physics.d.ts +205 -0
- package/dist/physics.js +577 -0
- package/dist/physics.js.map +1 -0
- package/dist/stage-types-C19IhuzA.d.ts +731 -0
- package/dist/stage.d.ts +11 -7
- package/dist/stage.js +8024 -3852
- package/dist/stage.js.map +1 -1
- package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
- package/dist/thruster-23lzoPZd.d.ts +180 -0
- package/dist/world-DfgxoNMt.d.ts +105 -0
- package/package.json +53 -13
- package/dist/behaviors.d.ts +0 -854
- package/dist/behaviors.js +0 -1209
- package/dist/behaviors.js.map +0 -1
- package/dist/camera-CeJPAgGg.d.ts +0 -116
- package/dist/moveable-B_vyA6cw.d.ts +0 -67
- package/dist/stage-types-Bd-KtcYT.d.ts +0 -375
- package/dist/transformable-CUhvyuYO.d.ts +0 -67
- package/dist/world-C8tQ7Plj.d.ts +0 -774
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// src/lib/behaviors/behavior-descriptor.ts
|
|
2
|
+
function defineBehavior(config) {
|
|
3
|
+
return {
|
|
4
|
+
key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
|
|
5
|
+
defaultOptions: config.defaultOptions,
|
|
6
|
+
systemFactory: config.systemFactory,
|
|
7
|
+
createHandle: config.createHandle
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/lib/core/utility/sync-state-machine.ts
|
|
12
|
+
import {
|
|
13
|
+
StateMachine as BaseStateMachine
|
|
14
|
+
} from "typescript-fsm";
|
|
15
|
+
import { t } from "typescript-fsm";
|
|
16
|
+
var SyncStateMachine = class extends BaseStateMachine {
|
|
17
|
+
constructor(init, transitions = [], logger = console) {
|
|
18
|
+
super(init, transitions, logger);
|
|
19
|
+
}
|
|
20
|
+
dispatch(_event, ..._args) {
|
|
21
|
+
throw new Error("SyncStateMachine does not support async dispatch.");
|
|
22
|
+
}
|
|
23
|
+
syncDispatch(event, ...args) {
|
|
24
|
+
const found = this.transitions.some((transition) => {
|
|
25
|
+
if (transition.fromState !== this._current || transition.event !== event) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const current = this._current;
|
|
29
|
+
this._current = transition.toState;
|
|
30
|
+
if (!transition.cb) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
transition.cb(...args);
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
this._current = current;
|
|
38
|
+
this.logger.error("Exception in callback", error);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (!found) {
|
|
43
|
+
const errorMessage = this.formatErr(this._current, event);
|
|
44
|
+
this.logger.error(errorMessage);
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts
|
|
51
|
+
var ScreenWrapState = /* @__PURE__ */ ((ScreenWrapState2) => {
|
|
52
|
+
ScreenWrapState2["Center"] = "center";
|
|
53
|
+
ScreenWrapState2["NearEdgeLeft"] = "near-edge-left";
|
|
54
|
+
ScreenWrapState2["NearEdgeRight"] = "near-edge-right";
|
|
55
|
+
ScreenWrapState2["NearEdgeTop"] = "near-edge-top";
|
|
56
|
+
ScreenWrapState2["NearEdgeBottom"] = "near-edge-bottom";
|
|
57
|
+
ScreenWrapState2["Wrapped"] = "wrapped";
|
|
58
|
+
return ScreenWrapState2;
|
|
59
|
+
})(ScreenWrapState || {});
|
|
60
|
+
var ScreenWrapEvent = /* @__PURE__ */ ((ScreenWrapEvent2) => {
|
|
61
|
+
ScreenWrapEvent2["EnterCenter"] = "enter-center";
|
|
62
|
+
ScreenWrapEvent2["ApproachLeft"] = "approach-left";
|
|
63
|
+
ScreenWrapEvent2["ApproachRight"] = "approach-right";
|
|
64
|
+
ScreenWrapEvent2["ApproachTop"] = "approach-top";
|
|
65
|
+
ScreenWrapEvent2["ApproachBottom"] = "approach-bottom";
|
|
66
|
+
ScreenWrapEvent2["Wrap"] = "wrap";
|
|
67
|
+
return ScreenWrapEvent2;
|
|
68
|
+
})(ScreenWrapEvent || {});
|
|
69
|
+
var ScreenWrapFSM = class {
|
|
70
|
+
machine;
|
|
71
|
+
constructor() {
|
|
72
|
+
this.machine = new SyncStateMachine(
|
|
73
|
+
"center" /* Center */,
|
|
74
|
+
[
|
|
75
|
+
// From Center
|
|
76
|
+
t("center" /* Center */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
|
|
77
|
+
t("center" /* Center */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
|
|
78
|
+
t("center" /* Center */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
|
|
79
|
+
t("center" /* Center */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
|
|
80
|
+
// From NearEdge to Wrapped
|
|
81
|
+
t("near-edge-left" /* NearEdgeLeft */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
|
|
82
|
+
t("near-edge-right" /* NearEdgeRight */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
|
|
83
|
+
t("near-edge-top" /* NearEdgeTop */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
|
|
84
|
+
t("near-edge-bottom" /* NearEdgeBottom */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
|
|
85
|
+
// From NearEdge back to Center
|
|
86
|
+
t("near-edge-left" /* NearEdgeLeft */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
87
|
+
t("near-edge-right" /* NearEdgeRight */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
88
|
+
t("near-edge-top" /* NearEdgeTop */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
89
|
+
t("near-edge-bottom" /* NearEdgeBottom */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
90
|
+
// From Wrapped back to Center
|
|
91
|
+
t("wrapped" /* Wrapped */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
92
|
+
// From Wrapped to NearEdge (landed near opposite edge)
|
|
93
|
+
t("wrapped" /* Wrapped */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
|
|
94
|
+
t("wrapped" /* Wrapped */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
|
|
95
|
+
t("wrapped" /* Wrapped */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
|
|
96
|
+
t("wrapped" /* Wrapped */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
|
|
97
|
+
// Self-transitions (no-ops for redundant events)
|
|
98
|
+
t("center" /* Center */, "enter-center" /* EnterCenter */, "center" /* Center */),
|
|
99
|
+
t("near-edge-left" /* NearEdgeLeft */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
|
|
100
|
+
t("near-edge-right" /* NearEdgeRight */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
|
|
101
|
+
t("near-edge-top" /* NearEdgeTop */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
|
|
102
|
+
t("near-edge-bottom" /* NearEdgeBottom */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */)
|
|
103
|
+
]
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
getState() {
|
|
107
|
+
return this.machine.getState();
|
|
108
|
+
}
|
|
109
|
+
dispatch(event) {
|
|
110
|
+
if (this.machine.can(event)) {
|
|
111
|
+
this.machine.syncDispatch(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Update FSM based on entity position relative to bounds
|
|
116
|
+
*/
|
|
117
|
+
update(position, bounds, wrapped) {
|
|
118
|
+
const { x, y } = position;
|
|
119
|
+
const { minX, maxX, minY, maxY, edgeThreshold } = bounds;
|
|
120
|
+
if (wrapped) {
|
|
121
|
+
this.dispatch("wrap" /* Wrap */);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const nearLeft = x < minX + edgeThreshold;
|
|
125
|
+
const nearRight = x > maxX - edgeThreshold;
|
|
126
|
+
const nearBottom = y < minY + edgeThreshold;
|
|
127
|
+
const nearTop = y > maxY - edgeThreshold;
|
|
128
|
+
if (nearLeft) {
|
|
129
|
+
this.dispatch("approach-left" /* ApproachLeft */);
|
|
130
|
+
} else if (nearRight) {
|
|
131
|
+
this.dispatch("approach-right" /* ApproachRight */);
|
|
132
|
+
} else if (nearTop) {
|
|
133
|
+
this.dispatch("approach-top" /* ApproachTop */);
|
|
134
|
+
} else if (nearBottom) {
|
|
135
|
+
this.dispatch("approach-bottom" /* ApproachBottom */);
|
|
136
|
+
} else {
|
|
137
|
+
this.dispatch("enter-center" /* EnterCenter */);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/lib/behaviors/shared/bounds-2d.ts
|
|
143
|
+
function createBounds2DRect({
|
|
144
|
+
width,
|
|
145
|
+
height,
|
|
146
|
+
centerX = 0,
|
|
147
|
+
centerY = 0
|
|
148
|
+
}) {
|
|
149
|
+
const halfWidth = width / 2;
|
|
150
|
+
const halfHeight = height / 2;
|
|
151
|
+
return {
|
|
152
|
+
minX: centerX - halfWidth,
|
|
153
|
+
maxX: centerX + halfWidth,
|
|
154
|
+
minY: centerY - halfHeight,
|
|
155
|
+
maxY: centerY + halfHeight
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function wrapPoint2D(position, bounds) {
|
|
159
|
+
let x = position.x;
|
|
160
|
+
let y = position.y;
|
|
161
|
+
let wrapped = false;
|
|
162
|
+
if (position.x < bounds.minX) {
|
|
163
|
+
x = bounds.maxX - (bounds.minX - position.x);
|
|
164
|
+
wrapped = true;
|
|
165
|
+
} else if (position.x > bounds.maxX) {
|
|
166
|
+
x = bounds.minX + (position.x - bounds.maxX);
|
|
167
|
+
wrapped = true;
|
|
168
|
+
}
|
|
169
|
+
if (position.y < bounds.minY) {
|
|
170
|
+
y = bounds.maxY - (bounds.minY - position.y);
|
|
171
|
+
wrapped = true;
|
|
172
|
+
} else if (position.y > bounds.maxY) {
|
|
173
|
+
y = bounds.minY + (position.y - bounds.maxY);
|
|
174
|
+
wrapped = true;
|
|
175
|
+
}
|
|
176
|
+
return { x, y, wrapped };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts
|
|
180
|
+
var defaultOptions = {
|
|
181
|
+
width: 20,
|
|
182
|
+
height: 15,
|
|
183
|
+
centerX: 0,
|
|
184
|
+
centerY: 0,
|
|
185
|
+
edgeThreshold: 2
|
|
186
|
+
};
|
|
187
|
+
var SCREEN_WRAP_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:screen-wrap");
|
|
188
|
+
var ScreenWrapSystem = class {
|
|
189
|
+
constructor(world, getBehaviorLinks) {
|
|
190
|
+
this.world = world;
|
|
191
|
+
this.getBehaviorLinks = getBehaviorLinks;
|
|
192
|
+
}
|
|
193
|
+
update(_ecs, delta) {
|
|
194
|
+
const links = this.getBehaviorLinks?.(SCREEN_WRAP_BEHAVIOR_KEY);
|
|
195
|
+
if (!links) return;
|
|
196
|
+
for (const link of links) {
|
|
197
|
+
const gameEntity = link.entity;
|
|
198
|
+
const wrapRef = link.ref;
|
|
199
|
+
if (!gameEntity.body) continue;
|
|
200
|
+
const options = wrapRef.options;
|
|
201
|
+
if (!wrapRef.fsm) {
|
|
202
|
+
wrapRef.fsm = new ScreenWrapFSM();
|
|
203
|
+
}
|
|
204
|
+
const wrapped = this.wrapEntity(gameEntity, options);
|
|
205
|
+
const pos = gameEntity.body.translation();
|
|
206
|
+
const { edgeThreshold } = options;
|
|
207
|
+
const bounds = createBounds2DRect(options);
|
|
208
|
+
wrapRef.fsm.update(
|
|
209
|
+
{ x: pos.x, y: pos.y },
|
|
210
|
+
{
|
|
211
|
+
minX: bounds.minX,
|
|
212
|
+
maxX: bounds.maxX,
|
|
213
|
+
minY: bounds.minY,
|
|
214
|
+
maxY: bounds.maxY,
|
|
215
|
+
edgeThreshold
|
|
216
|
+
},
|
|
217
|
+
wrapped
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
wrapEntity(entity, options) {
|
|
222
|
+
const body = entity.body;
|
|
223
|
+
if (!body) return false;
|
|
224
|
+
const pos = body.translation();
|
|
225
|
+
const bounds = createBounds2DRect(options);
|
|
226
|
+
const result = wrapPoint2D({ x: pos.x, y: pos.y }, bounds);
|
|
227
|
+
if (result.wrapped) {
|
|
228
|
+
body.setTranslation({ x: result.x, y: result.y, z: pos.z }, true);
|
|
229
|
+
}
|
|
230
|
+
return result.wrapped;
|
|
231
|
+
}
|
|
232
|
+
destroy(_ecs) {
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var ScreenWrapBehavior = defineBehavior({
|
|
236
|
+
name: "screen-wrap",
|
|
237
|
+
defaultOptions,
|
|
238
|
+
systemFactory: (ctx) => new ScreenWrapSystem(ctx.world, ctx.getBehaviorLinks)
|
|
239
|
+
});
|
|
240
|
+
export {
|
|
241
|
+
ScreenWrapBehavior,
|
|
242
|
+
ScreenWrapEvent,
|
|
243
|
+
ScreenWrapFSM,
|
|
244
|
+
ScreenWrapState
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=screen-wrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/behaviors/behavior-descriptor.ts","../../src/lib/core/utility/sync-state-machine.ts","../../src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts","../../src/lib/behaviors/shared/bounds-2d.ts","../../src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts"],"sourcesContent":["/**\n * BehaviorDescriptor\n *\n * Type-safe behavior descriptors that provide options inference.\n * Used with entity.use() to declaratively attach behaviors to entities.\n *\n * Each behavior can define its own handle type via `createHandle`,\n * providing behavior-specific methods with full type safety.\n */\n\nimport type { BehaviorSystemFactory } from './behavior-system';\n\n/**\n * Base handle returned by entity.use() for lazy access to behavior runtime.\n * FSM is null until entity is spawned and components are initialized.\n */\nexport interface BaseBehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** Get the FSM instance (null until entity is spawned) */\n getFSM(): any | null;\n /** Get the current options */\n getOptions(): O;\n /** Access the underlying behavior ref */\n readonly ref: BehaviorRef<O>;\n}\n\n/**\n * Reference to a behavior stored on an entity\n */\nexport interface BehaviorRef<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** The behavior descriptor */\n descriptor: BehaviorDescriptor<O, any>;\n /** Merged options (defaults + overrides) */\n options: O;\n /** Optional FSM instance - set lazily when entity is spawned */\n fsm?: any;\n}\n\n/**\n * A typed behavior descriptor that associates a symbol key with:\n * - Default options (providing type inference)\n * - A system factory to create the behavior system\n * - An optional handle factory for behavior-specific methods\n */\nexport interface BehaviorDescriptor<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Unique symbol identifying this behavior */\n readonly key: symbol;\n /** Default options (used for type inference) */\n readonly defaultOptions: O;\n /** Factory to create the behavior system */\n readonly systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * These methods are merged into the handle returned by entity.use().\n */\n readonly createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * The full handle type returned by entity.use().\n * Combines base handle with behavior-specific methods.\n */\nexport type BehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n> = BaseBehaviorHandle<O> & H;\n\n/**\n * Configuration for defining a new behavior\n */\nexport interface DefineBehaviorConfig<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Human-readable name for debugging */\n name: string;\n /** Default options - these define the type */\n defaultOptions: O;\n /** Factory function to create the system */\n systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * The returned object is merged into the handle returned by entity.use().\n *\n * @example\n * ```typescript\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX, moveY) => ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * ```\n */\n createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * Define a typed behavior descriptor.\n *\n * @example\n * ```typescript\n * export const WorldBoundary2DBehavior = defineBehavior({\n * name: 'world-boundary-2d',\n * defaultOptions: { boundaries: { top: 0, bottom: 0, left: 0, right: 0 } },\n * systemFactory: (ctx) => new WorldBoundary2DSystem(ctx.world),\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX: number, moveY: number) =>\n * ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * });\n *\n * // Usage - handle has getLastHits and getMovement with full types\n * const boundary = ship.use(WorldBoundary2DBehavior, { ... });\n * const hits = boundary.getLastHits(); // Fully typed!\n * ```\n */\nexport function defineBehavior<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n>(\n config: DefineBehaviorConfig<O, H, I>,\n): BehaviorDescriptor<O, H, I> {\n return {\n key: Symbol.for(`zylem:behavior:${config.name}`),\n defaultOptions: config.defaultOptions,\n systemFactory: config.systemFactory,\n createHandle: config.createHandle,\n };\n}\n","import {\n\tStateMachine as BaseStateMachine,\n\ttype ILogger,\n\ttype ITransition,\n\ttype SyncCallback,\n} from 'typescript-fsm';\n\nexport { t } from 'typescript-fsm';\nexport type { ILogger, ITransition, SyncCallback };\n\n/**\n * Local wrapper around typescript-fsm's SyncStateMachine.\n *\n * typescript-fsm@1.6.0 incorrectly reports callback-less transitions as\n * unhandled even after moving to the next state, which causes noisy\n * `No transition...` console errors for valid FSM dispatches.\n */\nexport class SyncStateMachine<\n\tSTATE extends string | number | symbol,\n\tEVENT extends string | number | symbol,\n\tCALLBACK extends Record<EVENT, SyncCallback> = Record<EVENT, SyncCallback>,\n> extends BaseStateMachine<STATE, EVENT, CALLBACK> {\n\tconstructor(\n\t\tinit: STATE,\n\t\ttransitions: ITransition<STATE, EVENT, CALLBACK[EVENT]>[] = [],\n\t\tlogger: ILogger = console,\n\t) {\n\t\tsuper(init, transitions, logger);\n\t}\n\n\tdispatch<E extends EVENT>(_event: E, ..._args: unknown[]): Promise<void> {\n\t\tthrow new Error('SyncStateMachine does not support async dispatch.');\n\t}\n\n\tsyncDispatch<E extends EVENT>(event: E, ...args: unknown[]): boolean {\n\t\tconst found = this.transitions.some((transition) => {\n\t\t\tif (transition.fromState !== this._current || transition.event !== event) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst current = this._current;\n\t\t\tthis._current = transition.toState;\n\n\t\t\tif (!transition.cb) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\ttransition.cb(...args);\n\t\t\t\treturn true;\n\t\t\t} catch (error) {\n\t\t\t\tthis._current = current;\n\t\t\t\tthis.logger.error('Exception in callback', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\n\t\tif (!found) {\n\t\t\tconst errorMessage = this.formatErr(this._current, event);\n\t\t\tthis.logger.error(errorMessage);\n\t\t}\n\n\t\treturn found;\n\t}\n}\n","/**\n * ScreenWrapFSM\n * \n * State machine for screen wrap behavior.\n * Reports position relative to bounds edges.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapState {\n\tCenter = 'center',\n\tNearEdgeLeft = 'near-edge-left',\n\tNearEdgeRight = 'near-edge-right',\n\tNearEdgeTop = 'near-edge-top',\n\tNearEdgeBottom = 'near-edge-bottom',\n\tWrapped = 'wrapped',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapEvent {\n\tEnterCenter = 'enter-center',\n\tApproachLeft = 'approach-left',\n\tApproachRight = 'approach-right',\n\tApproachTop = 'approach-top',\n\tApproachBottom = 'approach-bottom',\n\tWrap = 'wrap',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ScreenWrapFSM\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScreenWrapFSM {\n\tmachine: SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>(\n\t\t\tScreenWrapState.Center,\n\t\t\t[\n\t\t\t\t// From Center\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t\t\n\t\t\t\t// From NearEdge to Wrapped\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\n\t\t\t\t// From NearEdge back to Center\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped back to Center\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped to NearEdge (landed near opposite edge)\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): ScreenWrapState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: ScreenWrapEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on entity position relative to bounds\n\t */\n\tupdate(position: { x: number; y: number }, bounds: {\n\t\tminX: number; maxX: number; minY: number; maxY: number;\n\t\tedgeThreshold: number;\n\t}, wrapped: boolean): void {\n\t\tconst { x, y } = position;\n\t\tconst { minX, maxX, minY, maxY, edgeThreshold } = bounds;\n\n\t\tif (wrapped) {\n\t\t\tthis.dispatch(ScreenWrapEvent.Wrap);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if near edges\n\t\tconst nearLeft = x < minX + edgeThreshold;\n\t\tconst nearRight = x > maxX - edgeThreshold;\n\t\tconst nearBottom = y < minY + edgeThreshold;\n\t\tconst nearTop = y > maxY - edgeThreshold;\n\n\t\tif (nearLeft) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachLeft);\n\t\t} else if (nearRight) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachRight);\n\t\t} else if (nearTop) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachTop);\n\t\t} else if (nearBottom) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachBottom);\n\t\t} else {\n\t\t\tthis.dispatch(ScreenWrapEvent.EnterCenter);\n\t\t}\n\t}\n}\n","export interface Bounds2DPoint {\n\tx: number;\n\ty: number;\n}\n\nexport interface Bounds2DRect {\n\tminX: number;\n\tmaxX: number;\n\tminY: number;\n\tmaxY: number;\n}\n\nexport interface Bounds2DSize {\n\twidth: number;\n\theight: number;\n\tcenterX?: number;\n\tcenterY?: number;\n}\n\nexport interface Bounds2DHits {\n\tleft: boolean;\n\tright: boolean;\n\ttop: boolean;\n\tbottom: boolean;\n}\n\nexport interface Bounds2DWrapResult {\n\tx: number;\n\ty: number;\n\twrapped: boolean;\n}\n\nexport function createBounds2DRect({\n\twidth,\n\theight,\n\tcenterX = 0,\n\tcenterY = 0,\n}: Bounds2DSize): Bounds2DRect {\n\tconst halfWidth = width / 2;\n\tconst halfHeight = height / 2;\n\treturn {\n\t\tminX: centerX - halfWidth,\n\t\tmaxX: centerX + halfWidth,\n\t\tminY: centerY - halfHeight,\n\t\tmaxY: centerY + halfHeight,\n\t};\n}\n\nexport function computeBounds2DHits(\n\tposition: Bounds2DPoint,\n\tbounds: Bounds2DRect,\n): Bounds2DHits {\n\tconst hits: Bounds2DHits = {\n\t\ttop: false,\n\t\tbottom: false,\n\t\tleft: false,\n\t\tright: false,\n\t};\n\n\tif (position.x <= bounds.minX) hits.left = true;\n\telse if (position.x >= bounds.maxX) hits.right = true;\n\n\tif (position.y <= bounds.minY) hits.bottom = true;\n\telse if (position.y >= bounds.maxY) hits.top = true;\n\n\treturn hits;\n}\n\nexport function hasAnyBounds2DHit(hits: Bounds2DHits): boolean {\n\treturn hits.left || hits.right || hits.top || hits.bottom;\n}\n\nexport function constrainMovementToBounds2D(\n\thits: Bounds2DHits,\n\tmoveX: number,\n\tmoveY: number,\n): { moveX: number; moveY: number } {\n\tlet adjustedX = moveX;\n\tlet adjustedY = moveY;\n\n\tif ((hits.left && moveX < 0) || (hits.right && moveX > 0)) {\n\t\tadjustedX = 0;\n\t}\n\n\tif ((hits.bottom && moveY < 0) || (hits.top && moveY > 0)) {\n\t\tadjustedY = 0;\n\t}\n\n\treturn { moveX: adjustedX, moveY: adjustedY };\n}\n\nexport function wrapPoint2D(\n\tposition: Bounds2DPoint,\n\tbounds: Bounds2DRect,\n): Bounds2DWrapResult {\n\tlet x = position.x;\n\tlet y = position.y;\n\tlet wrapped = false;\n\n\tif (position.x < bounds.minX) {\n\t\tx = bounds.maxX - (bounds.minX - position.x);\n\t\twrapped = true;\n\t} else if (position.x > bounds.maxX) {\n\t\tx = bounds.minX + (position.x - bounds.maxX);\n\t\twrapped = true;\n\t}\n\n\tif (position.y < bounds.minY) {\n\t\ty = bounds.maxY - (bounds.minY - position.y);\n\t\twrapped = true;\n\t} else if (position.y > bounds.maxY) {\n\t\ty = bounds.minY + (position.y - bounds.maxY);\n\t\twrapped = true;\n\t}\n\n\treturn { x, y, wrapped };\n}\n\nexport function getBounds2DNormalFromHits(\n\thits: Bounds2DHits,\n): { x: number; y: number } {\n\tlet x = 0;\n\tlet y = 0;\n\n\tif (hits.left) x = 1;\n\tif (hits.right) x = -1;\n\tif (hits.bottom) y = 1;\n\tif (hits.top) y = -1;\n\n\treturn { x, y };\n}\n","/**\n * ScreenWrapBehavior\n * \n * When an entity exits the defined 2D bounds, it wraps around to the opposite edge.\n * Asteroids-style screen wrapping with FSM for edge detection.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ScreenWrapFSM } from './screen-wrap-fsm';\nimport { createBounds2DRect, wrapPoint2D } from '../shared/bounds-2d';\n\n/**\n * Screen wrap options (typed for entity.use() autocomplete)\n */\nexport interface ScreenWrapOptions {\n\t/** Width of the wrapping area (default: 20) */\n\twidth: number;\n\t/** Height of the wrapping area (default: 15) */\n\theight: number;\n\t/** Center X position (default: 0) */\n\tcenterX: number;\n\t/** Center Y position (default: 0) */\n\tcenterY: number;\n\t/** Distance from edge to trigger NearEdge state (default: 2) */\n\tedgeThreshold: number;\n}\n\nconst defaultOptions: ScreenWrapOptions = {\n\twidth: 20,\n\theight: 15,\n\tcenterX: 0,\n\tcenterY: 0,\n\tedgeThreshold: 2,\n};\n\nconst SCREEN_WRAP_BEHAVIOR_KEY = Symbol.for('zylem:behavior:screen-wrap');\n\n/**\n * ScreenWrapSystem - Wraps entities around 2D bounds\n */\nclass ScreenWrapSystem implements BehaviorSystem {\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: IWorld, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(SCREEN_WRAP_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst wrapRef = link.ref as any;\n\t\t\tif (!gameEntity.body) continue;\n\n\t\t\tconst options = wrapRef.options as ScreenWrapOptions;\n\n\t\t\t// Create FSM lazily\n\t\t\tif (!wrapRef.fsm) {\n\t\t\t\twrapRef.fsm = new ScreenWrapFSM();\n\t\t\t}\n\n\t\t\tconst wrapped = this.wrapEntity(gameEntity, options);\n\n\t\t\t// Update FSM with position and wrap state\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tconst { edgeThreshold } = options;\n\t\t\tconst bounds = createBounds2DRect(options);\n\n\t\t\twrapRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y },\n\t\t\t\t{\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tedgeThreshold,\n\t\t\t\t},\n\t\t\t\twrapped\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate wrapEntity(entity: any, options: ScreenWrapOptions): boolean {\n\t\tconst body = entity.body;\n\t\tif (!body) return false;\n\n\t\tconst pos = body.translation();\n\t\tconst bounds = createBounds2DRect(options);\n\t\tconst result = wrapPoint2D({ x: pos.x, y: pos.y }, bounds);\n\n\t\tif (result.wrapped) {\n\t\t\tbody.setTranslation({ x: result.x, y: result.y, z: pos.z }, true);\n\t\t}\n\n\t\treturn result.wrapped;\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// Cleanup if needed\n\t}\n}\n\n/**\n * ScreenWrapBehavior - Wraps entities around 2D bounds\n * \n * @example\n * ```typescript\n * import { ScreenWrapBehavior } from \"@zylem/game-lib\";\n * \n * const ship = createSprite({ ... });\n * const wrapRef = ship.use(ScreenWrapBehavior, { width: 20, height: 15 });\n * \n * // Access FSM to observe edge state\n * const fsm = wrapRef.getFSM();\n * console.log(fsm?.getState()); // 'center', 'near-edge-left', 'wrapped', etc.\n * ```\n */\nexport const ScreenWrapBehavior = defineBehavior({\n\tname: 'screen-wrap',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ScreenWrapSystem(ctx.world, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ScreenWrapState","ScreenWrapEvent"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { c as BehaviorDescriptor } from '../behavior-descriptor-BXnVR8Ki.js';
|
|
2
|
+
import 'bitecs';
|
|
3
|
+
|
|
4
|
+
interface Shooter2DStateComponent {
|
|
5
|
+
cooldownRemainingMs: number;
|
|
6
|
+
}
|
|
7
|
+
declare function createShooter2DStateComponent(): Shooter2DStateComponent;
|
|
8
|
+
|
|
9
|
+
interface Shooter2DTarget {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
interface Shooter2DStageLike {
|
|
14
|
+
add(...inputs: any[]): void;
|
|
15
|
+
}
|
|
16
|
+
interface Shooter2DProjectileEntity {
|
|
17
|
+
transformStore: {
|
|
18
|
+
velocity: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
z: number;
|
|
22
|
+
};
|
|
23
|
+
dirty: {
|
|
24
|
+
velocity: boolean;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
prependSetup(callback: (params: {
|
|
28
|
+
me: Shooter2DProjectileEntity;
|
|
29
|
+
}) => void): unknown;
|
|
30
|
+
setPosition(x: number, y: number, z: number): void;
|
|
31
|
+
setRotationZ(angle: number): void;
|
|
32
|
+
}
|
|
33
|
+
type Shooter2DProjectileFactory = () => Shooter2DProjectileEntity | Promise<Shooter2DProjectileEntity>;
|
|
34
|
+
interface Shooter2DBehaviorOptions {
|
|
35
|
+
projectileFactory: Shooter2DProjectileFactory;
|
|
36
|
+
projectileSpeed: number;
|
|
37
|
+
cooldownMs?: number;
|
|
38
|
+
spawnOffset?: Shooter2DTarget;
|
|
39
|
+
rotateProjectile?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface Shooter2DSourceEntity {
|
|
42
|
+
body?: {
|
|
43
|
+
translation(): {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
z: number;
|
|
47
|
+
};
|
|
48
|
+
rotation?: () => {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
z: number;
|
|
52
|
+
w: number;
|
|
53
|
+
};
|
|
54
|
+
} | null;
|
|
55
|
+
options?: {
|
|
56
|
+
position?: {
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
59
|
+
z?: number;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
topDownMovementState?: {
|
|
63
|
+
facingAngle: number;
|
|
64
|
+
};
|
|
65
|
+
shooter2dState?: Shooter2DStateComponent;
|
|
66
|
+
_rotation2DAngle?: number;
|
|
67
|
+
}
|
|
68
|
+
interface Shooter2DFireArgs {
|
|
69
|
+
source: Shooter2DSourceEntity;
|
|
70
|
+
stage: Shooter2DStageLike;
|
|
71
|
+
target: Shooter2DTarget;
|
|
72
|
+
}
|
|
73
|
+
interface Shooter2DHandle {
|
|
74
|
+
isReady(source: Shooter2DSourceEntity): boolean;
|
|
75
|
+
fire(args: Shooter2DFireArgs): Promise<Shooter2DProjectileEntity | null>;
|
|
76
|
+
}
|
|
77
|
+
declare const Shooter2DBehavior: BehaviorDescriptor<Shooter2DBehaviorOptions, Shooter2DHandle, Shooter2DSourceEntity>;
|
|
78
|
+
|
|
79
|
+
export { Shooter2DBehavior, type Shooter2DBehaviorOptions, type Shooter2DFireArgs, type Shooter2DHandle, type Shooter2DProjectileFactory, type Shooter2DSourceEntity, type Shooter2DStageLike, type Shooter2DStateComponent, type Shooter2DTarget, createShooter2DStateComponent };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/lib/behaviors/shooter-2d/components.ts
|
|
2
|
+
function createShooter2DStateComponent() {
|
|
3
|
+
return {
|
|
4
|
+
cooldownRemainingMs: 0
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/lib/behaviors/behavior-descriptor.ts
|
|
9
|
+
function defineBehavior(config) {
|
|
10
|
+
return {
|
|
11
|
+
key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
|
|
12
|
+
defaultOptions: config.defaultOptions,
|
|
13
|
+
systemFactory: config.systemFactory,
|
|
14
|
+
createHandle: config.createHandle
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/lib/behaviors/shared/direction-2d.ts
|
|
19
|
+
var DIRECTION_EPSILON = 1e-6;
|
|
20
|
+
function normalizeDirection2D(x, y) {
|
|
21
|
+
const length = Math.hypot(x, y);
|
|
22
|
+
if (length <= DIRECTION_EPSILON) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
x: x / length,
|
|
27
|
+
y: y / length
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function angleFromDirection2D(direction) {
|
|
31
|
+
return Math.atan2(-direction.x, direction.y);
|
|
32
|
+
}
|
|
33
|
+
function directionFromAngle2D(angle) {
|
|
34
|
+
return {
|
|
35
|
+
x: Math.sin(-angle),
|
|
36
|
+
y: Math.cos(-angle)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function rightFromAngle2D(angle) {
|
|
40
|
+
return {
|
|
41
|
+
x: Math.cos(angle),
|
|
42
|
+
y: Math.sin(angle)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function rotateLocalOffset2D(offset, angle) {
|
|
46
|
+
const right = rightFromAngle2D(angle);
|
|
47
|
+
const forward = directionFromAngle2D(angle);
|
|
48
|
+
return {
|
|
49
|
+
x: right.x * offset.x + forward.x * offset.y,
|
|
50
|
+
y: right.y * offset.x + forward.y * offset.y
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getRotationAngle2D(rotation) {
|
|
54
|
+
return Math.atan2(
|
|
55
|
+
2 * (rotation.w * rotation.z + rotation.x * rotation.y),
|
|
56
|
+
1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/lib/behaviors/shooter-2d/shooter-2d.descriptor.ts
|
|
61
|
+
var defaultOptions = {
|
|
62
|
+
projectileFactory: () => {
|
|
63
|
+
throw new Error("Shooter2DBehavior requires a projectileFactory.");
|
|
64
|
+
},
|
|
65
|
+
projectileSpeed: 12,
|
|
66
|
+
cooldownMs: 0,
|
|
67
|
+
spawnOffset: { x: 0, y: 1 },
|
|
68
|
+
rotateProjectile: true
|
|
69
|
+
};
|
|
70
|
+
var SHOOTER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:shooter-2d");
|
|
71
|
+
function ensureShooterState(entity) {
|
|
72
|
+
if (!entity.shooter2dState) {
|
|
73
|
+
entity.shooter2dState = createShooter2DStateComponent();
|
|
74
|
+
}
|
|
75
|
+
return entity.shooter2dState;
|
|
76
|
+
}
|
|
77
|
+
function getEntityPosition(entity) {
|
|
78
|
+
if (entity.body?.translation) {
|
|
79
|
+
return entity.body.translation();
|
|
80
|
+
}
|
|
81
|
+
const position = entity.options?.position;
|
|
82
|
+
if (!position) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
x: position.x,
|
|
87
|
+
y: position.y,
|
|
88
|
+
z: position.z ?? 0
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function getEntityFacingAngle(entity) {
|
|
92
|
+
if (entity.topDownMovementState) {
|
|
93
|
+
return entity.topDownMovementState.facingAngle;
|
|
94
|
+
}
|
|
95
|
+
if (entity.body?.rotation) {
|
|
96
|
+
return getRotationAngle2D(entity.body.rotation());
|
|
97
|
+
}
|
|
98
|
+
return entity._rotation2DAngle ?? 0;
|
|
99
|
+
}
|
|
100
|
+
function createShooter2DHandle(ref) {
|
|
101
|
+
return {
|
|
102
|
+
isReady: (source) => ensureShooterState(source).cooldownRemainingMs <= 0,
|
|
103
|
+
fire: async ({
|
|
104
|
+
source,
|
|
105
|
+
stage,
|
|
106
|
+
target
|
|
107
|
+
}) => {
|
|
108
|
+
const state = ensureShooterState(source);
|
|
109
|
+
if (state.cooldownRemainingMs > 0) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const sourcePosition = getEntityPosition(source);
|
|
113
|
+
if (!sourcePosition) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const options = ref.options;
|
|
117
|
+
const aimDirection = normalizeDirection2D(
|
|
118
|
+
target.x - sourcePosition.x,
|
|
119
|
+
target.y - sourcePosition.y
|
|
120
|
+
);
|
|
121
|
+
const angle = aimDirection ? angleFromDirection2D(aimDirection) : getEntityFacingAngle(source);
|
|
122
|
+
const direction = aimDirection ?? directionFromAngle2D(angle);
|
|
123
|
+
const offset = rotateLocalOffset2D(
|
|
124
|
+
options.spawnOffset ?? defaultOptions.spawnOffset,
|
|
125
|
+
angle
|
|
126
|
+
);
|
|
127
|
+
const spawnPosition = {
|
|
128
|
+
x: sourcePosition.x + offset.x,
|
|
129
|
+
y: sourcePosition.y + offset.y,
|
|
130
|
+
z: sourcePosition.z
|
|
131
|
+
};
|
|
132
|
+
const projectile = await Promise.resolve(options.projectileFactory());
|
|
133
|
+
projectile.prependSetup(({ me }) => {
|
|
134
|
+
me.setPosition(spawnPosition.x, spawnPosition.y, spawnPosition.z);
|
|
135
|
+
if (options.rotateProjectile ?? true) {
|
|
136
|
+
me.setRotationZ(angle);
|
|
137
|
+
}
|
|
138
|
+
me.transformStore.velocity.x = direction.x * options.projectileSpeed;
|
|
139
|
+
me.transformStore.velocity.y = direction.y * options.projectileSpeed;
|
|
140
|
+
me.transformStore.velocity.z = 0;
|
|
141
|
+
me.transformStore.dirty.velocity = true;
|
|
142
|
+
});
|
|
143
|
+
stage.add(projectile);
|
|
144
|
+
state.cooldownRemainingMs = Math.max(0, options.cooldownMs ?? 0);
|
|
145
|
+
return projectile;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
var Shooter2DBehaviorSystem = class {
|
|
150
|
+
constructor(getBehaviorLinks) {
|
|
151
|
+
this.getBehaviorLinks = getBehaviorLinks;
|
|
152
|
+
}
|
|
153
|
+
update(_ecs, delta) {
|
|
154
|
+
const links = this.getBehaviorLinks?.(SHOOTER_2D_BEHAVIOR_KEY);
|
|
155
|
+
if (!links) return;
|
|
156
|
+
for (const link of links) {
|
|
157
|
+
const entity = link.entity;
|
|
158
|
+
const ref = link.ref;
|
|
159
|
+
const state = ensureShooterState(entity);
|
|
160
|
+
state.cooldownRemainingMs = Math.max(
|
|
161
|
+
0,
|
|
162
|
+
state.cooldownRemainingMs - delta * 1e3
|
|
163
|
+
);
|
|
164
|
+
ref.__entity = entity;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
destroy(_ecs) {
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var Shooter2DBehavior = defineBehavior({
|
|
171
|
+
name: "shooter-2d",
|
|
172
|
+
defaultOptions,
|
|
173
|
+
systemFactory: (ctx) => new Shooter2DBehaviorSystem(ctx.getBehaviorLinks),
|
|
174
|
+
createHandle: createShooter2DHandle
|
|
175
|
+
});
|
|
176
|
+
export {
|
|
177
|
+
Shooter2DBehavior,
|
|
178
|
+
createShooter2DStateComponent
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=shooter-2d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/behaviors/shooter-2d/components.ts","../../src/lib/behaviors/behavior-descriptor.ts","../../src/lib/behaviors/shared/direction-2d.ts","../../src/lib/behaviors/shooter-2d/shooter-2d.descriptor.ts"],"sourcesContent":["export interface Shooter2DStateComponent {\n\tcooldownRemainingMs: number;\n}\n\nexport function createShooter2DStateComponent(): Shooter2DStateComponent {\n\treturn {\n\t\tcooldownRemainingMs: 0,\n\t};\n}\n","/**\n * BehaviorDescriptor\n *\n * Type-safe behavior descriptors that provide options inference.\n * Used with entity.use() to declaratively attach behaviors to entities.\n *\n * Each behavior can define its own handle type via `createHandle`,\n * providing behavior-specific methods with full type safety.\n */\n\nimport type { BehaviorSystemFactory } from './behavior-system';\n\n/**\n * Base handle returned by entity.use() for lazy access to behavior runtime.\n * FSM is null until entity is spawned and components are initialized.\n */\nexport interface BaseBehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** Get the FSM instance (null until entity is spawned) */\n getFSM(): any | null;\n /** Get the current options */\n getOptions(): O;\n /** Access the underlying behavior ref */\n readonly ref: BehaviorRef<O>;\n}\n\n/**\n * Reference to a behavior stored on an entity\n */\nexport interface BehaviorRef<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** The behavior descriptor */\n descriptor: BehaviorDescriptor<O, any>;\n /** Merged options (defaults + overrides) */\n options: O;\n /** Optional FSM instance - set lazily when entity is spawned */\n fsm?: any;\n}\n\n/**\n * A typed behavior descriptor that associates a symbol key with:\n * - Default options (providing type inference)\n * - A system factory to create the behavior system\n * - An optional handle factory for behavior-specific methods\n */\nexport interface BehaviorDescriptor<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Unique symbol identifying this behavior */\n readonly key: symbol;\n /** Default options (used for type inference) */\n readonly defaultOptions: O;\n /** Factory to create the behavior system */\n readonly systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * These methods are merged into the handle returned by entity.use().\n */\n readonly createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * The full handle type returned by entity.use().\n * Combines base handle with behavior-specific methods.\n */\nexport type BehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n> = BaseBehaviorHandle<O> & H;\n\n/**\n * Configuration for defining a new behavior\n */\nexport interface DefineBehaviorConfig<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Human-readable name for debugging */\n name: string;\n /** Default options - these define the type */\n defaultOptions: O;\n /** Factory function to create the system */\n systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * The returned object is merged into the handle returned by entity.use().\n *\n * @example\n * ```typescript\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX, moveY) => ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * ```\n */\n createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * Define a typed behavior descriptor.\n *\n * @example\n * ```typescript\n * export const WorldBoundary2DBehavior = defineBehavior({\n * name: 'world-boundary-2d',\n * defaultOptions: { boundaries: { top: 0, bottom: 0, left: 0, right: 0 } },\n * systemFactory: (ctx) => new WorldBoundary2DSystem(ctx.world),\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX: number, moveY: number) =>\n * ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * });\n *\n * // Usage - handle has getLastHits and getMovement with full types\n * const boundary = ship.use(WorldBoundary2DBehavior, { ... });\n * const hits = boundary.getLastHits(); // Fully typed!\n * ```\n */\nexport function defineBehavior<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n>(\n config: DefineBehaviorConfig<O, H, I>,\n): BehaviorDescriptor<O, H, I> {\n return {\n key: Symbol.for(`zylem:behavior:${config.name}`),\n defaultOptions: config.defaultOptions,\n systemFactory: config.systemFactory,\n createHandle: config.createHandle,\n };\n}\n","export interface Direction2D {\n\tx: number;\n\ty: number;\n}\n\nconst DIRECTION_EPSILON = 1e-6;\n\nexport function normalizeDirection2D(x: number, y: number): Direction2D | null {\n\tconst length = Math.hypot(x, y);\n\tif (length <= DIRECTION_EPSILON) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: x / length,\n\t\ty: y / length,\n\t};\n}\n\nexport function angleFromDirection2D(direction: Direction2D): number {\n\treturn Math.atan2(-direction.x, direction.y);\n}\n\nexport function directionFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.sin(-angle),\n\t\ty: Math.cos(-angle),\n\t};\n}\n\nexport function rightFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.cos(angle),\n\t\ty: Math.sin(angle),\n\t};\n}\n\nexport function rotateLocalOffset2D(\n\toffset: Direction2D,\n\tangle: number,\n): Direction2D {\n\tconst right = rightFromAngle2D(angle);\n\tconst forward = directionFromAngle2D(angle);\n\treturn {\n\t\tx: right.x * offset.x + forward.x * offset.y,\n\t\ty: right.y * offset.x + forward.y * offset.y,\n\t};\n}\n\nexport function getRotationAngle2D(\n\trotation: { x: number; y: number; z: number; w: number },\n): number {\n\treturn Math.atan2(\n\t\t2 * (rotation.w * rotation.z + rotation.x * rotation.y),\n\t\t1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z),\n\t);\n}\n","import type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tangleFromDirection2D,\n\tdirectionFromAngle2D,\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n\trotateLocalOffset2D,\n} from '../shared/direction-2d';\nimport {\n\tcreateShooter2DStateComponent,\n\ttype Shooter2DStateComponent,\n} from './components';\n\nexport interface Shooter2DTarget {\n\tx: number;\n\ty: number;\n}\n\nexport interface Shooter2DStageLike {\n\tadd(...inputs: any[]): void;\n}\n\nexport interface Shooter2DProjectileEntity {\n\ttransformStore: {\n\t\tvelocity: { x: number; y: number; z: number };\n\t\tdirty: { velocity: boolean };\n\t};\n\tprependSetup(\n\t\tcallback: (params: { me: Shooter2DProjectileEntity }) => void,\n\t): unknown;\n\tsetPosition(x: number, y: number, z: number): void;\n\tsetRotationZ(angle: number): void;\n}\n\nexport type Shooter2DProjectileFactory = () =>\n\t| Shooter2DProjectileEntity\n\t| Promise<Shooter2DProjectileEntity>;\n\nexport interface Shooter2DBehaviorOptions {\n\tprojectileFactory: Shooter2DProjectileFactory;\n\tprojectileSpeed: number;\n\tcooldownMs?: number;\n\tspawnOffset?: Shooter2DTarget;\n\trotateProjectile?: boolean;\n}\n\nexport interface Shooter2DSourceEntity {\n\tbody?: {\n\t\ttranslation(): { x: number; y: number; z: number };\n\t\trotation?: () => { x: number; y: number; z: number; w: number };\n\t} | null;\n\toptions?: {\n\t\tposition?: { x: number; y: number; z?: number };\n\t};\n\ttopDownMovementState?: {\n\t\tfacingAngle: number;\n\t};\n\tshooter2dState?: Shooter2DStateComponent;\n\t_rotation2DAngle?: number;\n}\n\nexport interface Shooter2DFireArgs {\n\tsource: Shooter2DSourceEntity;\n\tstage: Shooter2DStageLike;\n\ttarget: Shooter2DTarget;\n}\n\nexport interface Shooter2DHandle {\n\tisReady(source: Shooter2DSourceEntity): boolean;\n\tfire(args: Shooter2DFireArgs): Promise<Shooter2DProjectileEntity | null>;\n}\n\nconst defaultOptions: Shooter2DBehaviorOptions = {\n\tprojectileFactory: () => {\n\t\tthrow new Error('Shooter2DBehavior requires a projectileFactory.');\n\t},\n\tprojectileSpeed: 12,\n\tcooldownMs: 0,\n\tspawnOffset: { x: 0, y: 1 },\n\trotateProjectile: true,\n};\n\nconst SHOOTER_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:shooter-2d');\n\nfunction ensureShooterState(entity: Shooter2DSourceEntity): Shooter2DStateComponent {\n\tif (!entity.shooter2dState) {\n\t\tentity.shooter2dState = createShooter2DStateComponent();\n\t}\n\treturn entity.shooter2dState;\n}\n\nfunction getEntityPosition(\n\tentity: Shooter2DSourceEntity,\n): { x: number; y: number; z: number } | null {\n\tif (entity.body?.translation) {\n\t\treturn entity.body.translation();\n\t}\n\n\tconst position = entity.options?.position;\n\tif (!position) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: position.x,\n\t\ty: position.y,\n\t\tz: position.z ?? 0,\n\t};\n}\n\nfunction getEntityFacingAngle(entity: Shooter2DSourceEntity): number {\n\tif (entity.topDownMovementState) {\n\t\treturn entity.topDownMovementState.facingAngle;\n\t}\n\n\tif (entity.body?.rotation) {\n\t\treturn getRotationAngle2D(entity.body.rotation());\n\t}\n\n\treturn entity._rotation2DAngle ?? 0;\n}\n\nfunction createShooter2DHandle(\n\tref: BehaviorRef<Shooter2DBehaviorOptions>,\n): Shooter2DHandle {\n\treturn {\n\t\tisReady: (source: Shooter2DSourceEntity) =>\n\t\t\tensureShooterState(source).cooldownRemainingMs <= 0,\n\t\tfire: async ({\n\t\t\tsource,\n\t\t\tstage,\n\t\t\ttarget,\n\t\t}: Shooter2DFireArgs): Promise<Shooter2DProjectileEntity | null> => {\n\t\t\tconst state = ensureShooterState(source);\n\t\t\tif (state.cooldownRemainingMs > 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst sourcePosition = getEntityPosition(source);\n\t\t\tif (!sourcePosition) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst options = ref.options;\n\t\t\tconst aimDirection = normalizeDirection2D(\n\t\t\t\ttarget.x - sourcePosition.x,\n\t\t\t\ttarget.y - sourcePosition.y,\n\t\t\t);\n\t\t\tconst angle = aimDirection\n\t\t\t\t? angleFromDirection2D(aimDirection)\n\t\t\t\t: getEntityFacingAngle(source);\n\t\t\tconst direction = aimDirection ?? directionFromAngle2D(angle);\n\t\t\tconst offset = rotateLocalOffset2D(\n\t\t\t\toptions.spawnOffset ?? defaultOptions.spawnOffset!,\n\t\t\t\tangle,\n\t\t\t);\n\t\t\tconst spawnPosition = {\n\t\t\t\tx: sourcePosition.x + offset.x,\n\t\t\t\ty: sourcePosition.y + offset.y,\n\t\t\t\tz: sourcePosition.z,\n\t\t\t};\n\t\t\tconst projectile = await Promise.resolve(options.projectileFactory());\n\t\t\tprojectile.prependSetup(({ me }) => {\n\t\t\t\tme.setPosition(spawnPosition.x, spawnPosition.y, spawnPosition.z);\n\t\t\t\tif (options.rotateProjectile ?? true) {\n\t\t\t\t\tme.setRotationZ(angle);\n\t\t\t\t}\n\t\t\t\tme.transformStore.velocity.x = direction.x * options.projectileSpeed;\n\t\t\t\tme.transformStore.velocity.y = direction.y * options.projectileSpeed;\n\t\t\t\tme.transformStore.velocity.z = 0;\n\t\t\t\tme.transformStore.dirty.velocity = true;\n\t\t\t});\n\n\t\t\tstage.add(projectile);\n\t\t\tstate.cooldownRemainingMs = Math.max(0, options.cooldownMs ?? 0);\n\t\t\treturn projectile;\n\t\t},\n\t};\n}\n\nclass Shooter2DBehaviorSystem implements BehaviorSystem {\n\tconstructor(\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: IWorld, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(SHOOTER_2D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst entity = link.entity as Shooter2DSourceEntity;\n\t\t\tconst ref = link.ref as any;\n\t\t\tconst state = ensureShooterState(entity);\n\t\t\tstate.cooldownRemainingMs = Math.max(\n\t\t\t\t0,\n\t\t\t\tstate.cooldownRemainingMs - delta * 1000,\n\t\t\t);\n\t\t\tref.__entity = entity;\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {}\n}\n\nexport const Shooter2DBehavior = defineBehavior<\n\tShooter2DBehaviorOptions,\n\tShooter2DHandle,\n\tShooter2DSourceEntity\n>({\n\tname: 'shooter-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new Shooter2DBehaviorSystem(ctx.getBehaviorLinks),\n\tcreateHandle: createShooter2DHandle,\n});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { B as Behavior, m as PlayerInput, p as ThrusterBehavior, q as ThrusterBehaviorOptions, o as ThrusterEntity, j as ThrusterEvent, k as ThrusterFSM, l as ThrusterFSMContext, T as ThrusterInputComponent, n as ThrusterMovementBehavior, d as ThrusterMovementComponent, i as ThrusterState, e as ThrusterStateComponent, g as createThrusterInputComponent, f as createThrusterMovementComponent, h as createThrusterStateComponent } from '../thruster-23lzoPZd.js';
|
|
2
|
+
import '../sync-state-machine-CZyspBpj.js';
|
|
3
|
+
import 'typescript-fsm';
|
|
4
|
+
import '../world-DfgxoNMt.js';
|
|
5
|
+
import 'three';
|
|
6
|
+
import '@dimforge/rapier3d-compat';
|
|
7
|
+
import '../entity-Bq_eNEDI.js';
|
|
8
|
+
import '../entity-vj-HTjzU.js';
|
|
9
|
+
import 'bitecs';
|
|
10
|
+
import 'mitt';
|
|
11
|
+
import '../behavior-descriptor-BXnVR8Ki.js';
|