@jgengine/core 0.7.0 → 0.8.0
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/CHANGELOG.md +47 -0
- package/README.md +2 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +13 -1
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/combat/abilityKit.d.ts +5 -0
- package/dist/combat/abilityKit.js +10 -0
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/telegraph.d.ts +5 -1
- package/dist/combat/telegraph.js +14 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +157 -0
- package/dist/devtools/devtools.js +484 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/defineGame.d.ts +8 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +25 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/playableGame.d.ts +237 -6
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/race.d.ts +5 -0
- package/dist/game/race.js +20 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +37 -1
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +3 -0
- package/dist/multiplayer/presenceModel.js +15 -1
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +10 -0
- package/dist/nav/navGrid.js +18 -1
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/forceVolume.js +5 -5
- package/dist/physics/physicsWorld.d.ts +41 -2
- package/dist/physics/physicsWorld.js +232 -40
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +16 -17
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +10 -0
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/gameContext.d.ts +44 -2
- package/dist/runtime/gameContext.js +100 -4
- package/dist/runtime/hostPersistence.d.ts +21 -0
- package/dist/runtime/hostPersistence.js +50 -0
- package/dist/runtime/inputSnapshot.d.ts +7 -0
- package/dist/runtime/inputSnapshot.js +12 -0
- package/dist/runtime/motionIntents.d.ts +25 -0
- package/dist/runtime/motionIntents.js +25 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/entityStore.d.ts +8 -0
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +18 -0
- package/dist/scene/objectQuery.js +108 -0
- package/dist/scene/objectStore.d.ts +11 -0
- package/dist/scene/objectStore.js +109 -4
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- package/dist/world/environmentSummary.d.ts +53 -0
- package/dist/world/environmentSummary.js +129 -0
- package/dist/world/features.d.ts +87 -6
- package/dist/world/features.js +52 -2
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +23 -1
- package/dist/world/terrain.js +64 -1
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cellAt } from "./cellGrid.js";
|
|
2
|
+
export function pieceCells(table, piece) {
|
|
3
|
+
const rotations = table[piece.shape];
|
|
4
|
+
const index = ((piece.rotation % rotations.length) + rotations.length) % rotations.length;
|
|
5
|
+
const state = rotations[index];
|
|
6
|
+
return state.map(([ox, oy]) => [piece.x + ox, piece.y + oy]);
|
|
7
|
+
}
|
|
8
|
+
export function pieceCollides(grid, table, piece) {
|
|
9
|
+
for (const [x, y] of pieceCells(table, piece)) {
|
|
10
|
+
if (x < 0 || x >= grid.width || y >= grid.height)
|
|
11
|
+
return true;
|
|
12
|
+
if (y >= 0 && cellAt(grid, x, y) !== null)
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
export function mergePiece(grid, table, piece, value) {
|
|
18
|
+
const cells = grid.cells.slice();
|
|
19
|
+
for (const [x, y] of pieceCells(table, piece)) {
|
|
20
|
+
if (x >= 0 && x < grid.width && y >= 0 && y < grid.height)
|
|
21
|
+
cells[y * grid.width + x] = value;
|
|
22
|
+
}
|
|
23
|
+
return { width: grid.width, height: grid.height, cells };
|
|
24
|
+
}
|
|
25
|
+
export function dropDistance(grid, table, piece) {
|
|
26
|
+
let distance = 0;
|
|
27
|
+
while (!pieceCollides(grid, table, { ...piece, y: piece.y + distance + 1 }))
|
|
28
|
+
distance += 1;
|
|
29
|
+
return distance;
|
|
30
|
+
}
|
|
31
|
+
export function gravityInterval(level, base = 0.8, perLevel = 0.07, min = 0.05) {
|
|
32
|
+
return Math.max(min, base - level * perLevel);
|
|
33
|
+
}
|
|
34
|
+
export function levelForLines(lines, linesPerLevel = 10) {
|
|
35
|
+
return Math.floor(lines / linesPerLevel);
|
|
36
|
+
}
|
|
37
|
+
const CLASSIC_LINE_SCORE = [0, 40, 100, 300, 1200];
|
|
38
|
+
export function lineScore(cleared, level, table = CLASSIC_LINE_SCORE) {
|
|
39
|
+
return (table[cleared] ?? 0) * (level + 1);
|
|
40
|
+
}
|
|
41
|
+
export function createLockDelay(delaySeconds) {
|
|
42
|
+
return { delaySeconds, elapsed: 0 };
|
|
43
|
+
}
|
|
44
|
+
export function stepLockDelay(state, grounded, dt) {
|
|
45
|
+
if (!grounded)
|
|
46
|
+
return { state: { ...state, elapsed: 0 }, locked: false };
|
|
47
|
+
const elapsed = state.elapsed + dt;
|
|
48
|
+
if (elapsed >= state.delaySeconds)
|
|
49
|
+
return { state: { ...state, elapsed: 0 }, locked: true };
|
|
50
|
+
return { state: { ...state, elapsed }, locked: false };
|
|
51
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SyllableBank {
|
|
2
|
+
onset?: readonly string[];
|
|
3
|
+
nucleus: readonly string[];
|
|
4
|
+
coda?: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface NameGeneratorOptions {
|
|
7
|
+
rng: () => number;
|
|
8
|
+
syllables: SyllableBank;
|
|
9
|
+
minSyllables?: number;
|
|
10
|
+
maxSyllables?: number;
|
|
11
|
+
capitalize?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface NameGenerator {
|
|
14
|
+
name(): string;
|
|
15
|
+
}
|
|
16
|
+
export declare function pickFrom(rng: () => number, bank: readonly string[]): string;
|
|
17
|
+
export declare function createNameGenerator(options: NameGeneratorOptions): NameGenerator;
|
|
18
|
+
export declare function fillTemplate(template: string, vars: Record<string, () => string> | Record<string, string>): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function pickFrom(rng, bank) {
|
|
2
|
+
if (bank.length === 0)
|
|
3
|
+
throw new Error("pickFrom: bank must not be empty");
|
|
4
|
+
return bank[Math.floor(rng() * bank.length)];
|
|
5
|
+
}
|
|
6
|
+
export function createNameGenerator(options) {
|
|
7
|
+
const { rng, syllables } = options;
|
|
8
|
+
const minSyllables = Math.max(1, options.minSyllables ?? 2);
|
|
9
|
+
const maxSyllables = Math.max(minSyllables, options.maxSyllables ?? minSyllables);
|
|
10
|
+
const capitalize = options.capitalize ?? true;
|
|
11
|
+
function syllable() {
|
|
12
|
+
const onset = syllables.onset === undefined || syllables.onset.length === 0 ? "" : pickFrom(rng, syllables.onset);
|
|
13
|
+
const nucleus = pickFrom(rng, syllables.nucleus);
|
|
14
|
+
const coda = syllables.coda === undefined || syllables.coda.length === 0 ? "" : pickFrom(rng, syllables.coda);
|
|
15
|
+
return `${onset}${nucleus}${coda}`;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
name() {
|
|
19
|
+
const span = maxSyllables - minSyllables;
|
|
20
|
+
const count = minSyllables + (span === 0 ? 0 : Math.floor(rng() * (span + 1)));
|
|
21
|
+
let result = "";
|
|
22
|
+
for (let i = 0; i < count; i++)
|
|
23
|
+
result += syllable();
|
|
24
|
+
return capitalize && result.length > 0 ? result.charAt(0).toUpperCase() + result.slice(1) : result;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function fillTemplate(template, vars) {
|
|
29
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
30
|
+
if (!(key in vars))
|
|
31
|
+
return match;
|
|
32
|
+
const value = vars[key];
|
|
33
|
+
return typeof value === "function" ? value() : value;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function hashSeed(seed) {
|
|
2
|
+
const text = typeof seed === "number" ? seed.toString() : seed;
|
|
3
|
+
let h = 2166136261 >>> 0;
|
|
4
|
+
for (let i = 0; i < text.length; i++) {
|
|
5
|
+
h ^= text.charCodeAt(i);
|
|
6
|
+
h = Math.imul(h, 16777619);
|
|
7
|
+
}
|
|
8
|
+
return h >>> 0;
|
|
9
|
+
}
|
|
10
|
+
export function seededRng(seed) {
|
|
11
|
+
let a = hashSeed(seed) >>> 0;
|
|
12
|
+
return () => {
|
|
13
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
14
|
+
let t = a;
|
|
15
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
16
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
17
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function seededStreams(seed) {
|
|
21
|
+
const base = typeof seed === "number" ? seed.toString() : seed;
|
|
22
|
+
return (stream) => seededRng(`${base}:${stream}`);
|
|
23
|
+
}
|
|
@@ -5,6 +5,20 @@ export type MultiplayerAdapterConfig = {
|
|
|
5
5
|
} | {
|
|
6
6
|
kind: "ws";
|
|
7
7
|
topology?: MultiplayerTopology;
|
|
8
|
+
url?: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "socketio";
|
|
11
|
+
topology?: MultiplayerTopology;
|
|
12
|
+
url?: string;
|
|
13
|
+
} | {
|
|
14
|
+
kind: "p2p";
|
|
15
|
+
topology?: MultiplayerTopology;
|
|
16
|
+
room?: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "lan";
|
|
19
|
+
topology?: MultiplayerTopology;
|
|
20
|
+
port?: number;
|
|
21
|
+
path?: string;
|
|
8
22
|
} | {
|
|
9
23
|
kind: "offline";
|
|
10
24
|
};
|
|
@@ -13,8 +27,29 @@ export declare function convex(config?: {
|
|
|
13
27
|
}): MultiplayerAdapterConfig;
|
|
14
28
|
export declare function ws(config?: {
|
|
15
29
|
topology?: MultiplayerTopology;
|
|
30
|
+
url?: string;
|
|
31
|
+
}): MultiplayerAdapterConfig;
|
|
32
|
+
export declare function fly(config: {
|
|
33
|
+
app: string;
|
|
34
|
+
topology?: MultiplayerTopology;
|
|
35
|
+
path?: string;
|
|
36
|
+
}): MultiplayerAdapterConfig;
|
|
37
|
+
export declare function socketIo(config?: {
|
|
38
|
+
topology?: MultiplayerTopology;
|
|
39
|
+
url?: string;
|
|
40
|
+
}): MultiplayerAdapterConfig;
|
|
41
|
+
export declare function p2p(config?: {
|
|
42
|
+
topology?: MultiplayerTopology;
|
|
43
|
+
room?: string;
|
|
44
|
+
}): MultiplayerAdapterConfig;
|
|
45
|
+
export declare function lan(config?: {
|
|
46
|
+
topology?: MultiplayerTopology;
|
|
47
|
+
port?: number;
|
|
48
|
+
path?: string;
|
|
16
49
|
}): MultiplayerAdapterConfig;
|
|
17
50
|
export declare function offline(): MultiplayerAdapterConfig;
|
|
51
|
+
export declare function adapterOf(multiplayer: unknown): MultiplayerAdapterConfig | null;
|
|
52
|
+
export declare function multiplayerAdapterKind(multiplayer: unknown): string | null;
|
|
18
53
|
export type ServersPoolConfig = {
|
|
19
54
|
maxServers: number;
|
|
20
55
|
slotsPerServer: number;
|
package/dist/runtime/adapter.js
CHANGED
|
@@ -2,11 +2,47 @@ export function convex(config) {
|
|
|
2
2
|
return { kind: "convex", topology: config?.topology ?? "shared" };
|
|
3
3
|
}
|
|
4
4
|
export function ws(config) {
|
|
5
|
-
return { kind: "ws", topology: config?.topology ?? "shared" };
|
|
5
|
+
return { kind: "ws", topology: config?.topology ?? "shared", url: config?.url };
|
|
6
|
+
}
|
|
7
|
+
export function fly(config) {
|
|
8
|
+
return {
|
|
9
|
+
kind: "ws",
|
|
10
|
+
topology: config.topology ?? "shared",
|
|
11
|
+
url: `wss://${config.app}.fly.dev${config.path ?? "/ws"}`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function socketIo(config) {
|
|
15
|
+
return { kind: "socketio", topology: config?.topology ?? "shared", url: config?.url };
|
|
16
|
+
}
|
|
17
|
+
export function p2p(config) {
|
|
18
|
+
return { kind: "p2p", topology: config?.topology ?? "private", room: config?.room };
|
|
19
|
+
}
|
|
20
|
+
export function lan(config) {
|
|
21
|
+
return { kind: "lan", topology: config?.topology ?? "shared", port: config?.port, path: config?.path };
|
|
6
22
|
}
|
|
7
23
|
export function offline() {
|
|
8
24
|
return { kind: "offline" };
|
|
9
25
|
}
|
|
26
|
+
const ADAPTER_KINDS = new Set(["convex", "ws", "socketio", "p2p", "lan", "offline"]);
|
|
27
|
+
function isAdapterConfig(value) {
|
|
28
|
+
if (typeof value !== "object" || value === null)
|
|
29
|
+
return false;
|
|
30
|
+
const kind = value.kind;
|
|
31
|
+
return typeof kind === "string" && ADAPTER_KINDS.has(kind);
|
|
32
|
+
}
|
|
33
|
+
export function adapterOf(multiplayer) {
|
|
34
|
+
if (isAdapterConfig(multiplayer))
|
|
35
|
+
return multiplayer;
|
|
36
|
+
if (typeof multiplayer === "object" && multiplayer !== null) {
|
|
37
|
+
const nested = multiplayer.adapter;
|
|
38
|
+
if (isAdapterConfig(nested))
|
|
39
|
+
return nested;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
export function multiplayerAdapterKind(multiplayer) {
|
|
44
|
+
return adapterOf(multiplayer)?.kind ?? null;
|
|
45
|
+
}
|
|
10
46
|
export function servers(config) {
|
|
11
47
|
return config;
|
|
12
48
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CinematicCameraConfig } from "../game/playableGame.js";
|
|
2
|
+
export interface CameraDirector {
|
|
3
|
+
follow(entityId: string | null): void;
|
|
4
|
+
/** `undefined` means no runtime override — the shell falls back to the static `playable.camera.followEntityId`. `null` means explicitly follow nothing. */
|
|
5
|
+
followedEntityId(): string | null | undefined;
|
|
6
|
+
setCinematic(config: CinematicCameraConfig | null): void;
|
|
7
|
+
cinematic(): CinematicCameraConfig | null;
|
|
8
|
+
subscribe(listener: () => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createCameraDirector(): CameraDirector;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function createCameraDirector() {
|
|
2
|
+
const listeners = new Set();
|
|
3
|
+
let followEntity = undefined;
|
|
4
|
+
let cinematicConfig = null;
|
|
5
|
+
function notify() {
|
|
6
|
+
for (const listener of listeners)
|
|
7
|
+
listener();
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
follow(entityId) {
|
|
11
|
+
followEntity = entityId;
|
|
12
|
+
notify();
|
|
13
|
+
},
|
|
14
|
+
followedEntityId: () => followEntity,
|
|
15
|
+
setCinematic(config) {
|
|
16
|
+
cinematicConfig = config;
|
|
17
|
+
notify();
|
|
18
|
+
},
|
|
19
|
+
cinematic: () => cinematicConfig,
|
|
20
|
+
subscribe(listener) {
|
|
21
|
+
listeners.add(listener);
|
|
22
|
+
return () => listeners.delete(listener);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CardPile, type CardPileConfig } from "../cards/cardPile.js";
|
|
1
2
|
import { type OnDeathSpec } from "../combat/death.js";
|
|
2
3
|
import { type EffectInput, type EffectResult, type EffectVia, type ReceiveMap, type SingleTargetEffectInput } from "../combat/effects.js";
|
|
3
4
|
import { type ProjectileSystem } from "../combat/projectiles.js";
|
|
@@ -6,6 +7,7 @@ import { type TelegraphShape } from "../combat/telegraph.js";
|
|
|
6
7
|
import { type CommandDefinition, type CommandResult } from "../commands/commandRegistry.js";
|
|
7
8
|
import { type Cosmetics } from "../game/cosmetics.js";
|
|
8
9
|
import type { GameDefinition } from "../game/defineGame.js";
|
|
10
|
+
import { type TerrainField } from "../world/terrain.js";
|
|
9
11
|
import { type GameEventMap, type GameEvents } from "../game/events.js";
|
|
10
12
|
import { type GameFeed } from "../game/feed.js";
|
|
11
13
|
import { type Leaderboard } from "../game/leaderboard.js";
|
|
@@ -13,6 +15,7 @@ import { type Loadouts } from "../game/loadout.js";
|
|
|
13
15
|
import { type Drop, type LootTableDef } from "../game/lootTable.js";
|
|
14
16
|
import { type QuestJournal } from "../game/quest.js";
|
|
15
17
|
import { type WorldItemRecord, type WorldItemSpawnInput } from "../game/worldItem.js";
|
|
18
|
+
import { type Chat } from "../game/chat.js";
|
|
16
19
|
import { type Social } from "../game/social.js";
|
|
17
20
|
import { type TradeField, type TradeSystem } from "../game/trade.js";
|
|
18
21
|
import { type Unlocks } from "../game/unlocks.js";
|
|
@@ -23,16 +26,23 @@ import { type ItemUseHandler, type ItemUseInput, type ItemUseRejection, type Ite
|
|
|
23
26
|
import { type WeaponStats } from "../item/weapon.js";
|
|
24
27
|
import { type PoseAllowedStates, type PoseState } from "../movement/poseState.js";
|
|
25
28
|
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
29
|
+
import { type PaintLayer } from "../scene/paintLayer.js";
|
|
26
30
|
import { type EntityStatsApi, type StatCatalog } from "../scene/entityStats.js";
|
|
27
|
-
import type { EntityPose, EntityPosition, SceneEntity, SpawnOptions } from "../scene/entityStore.js";
|
|
31
|
+
import type { EntityPose, EntityPosition, EntityStore, SceneEntity, SpawnOptions, SpawnPose } from "../scene/entityStore.js";
|
|
28
32
|
import { type Forms } from "../scene/form.js";
|
|
33
|
+
import { type ObjectRaycastHit, type ObjectRaycastInput } from "../scene/objectQuery.js";
|
|
29
34
|
import { type ObjectStore } from "../scene/objectStore.js";
|
|
30
35
|
import { type Roster } from "../scene/roster.js";
|
|
31
36
|
import { type Possession } from "../scene/possession.js";
|
|
32
37
|
import { type SpatialApi } from "../scene/spatial.js";
|
|
33
38
|
import { type CycleTargetOptions } from "../scene/targeting.js";
|
|
34
39
|
import { type Stats } from "../stats/statModifiers.js";
|
|
40
|
+
import { type ObservableKeyedStore } from "../store/observableKeyedStore.js";
|
|
35
41
|
import { type SimClock } from "../time/simClock.js";
|
|
42
|
+
import { type TurnLoop, type TurnLoopConfig } from "../turn/turnLoop.js";
|
|
43
|
+
import { type CameraDirector } from "./cameraDirector.js";
|
|
44
|
+
import { type InputSnapshot } from "./inputSnapshot.js";
|
|
45
|
+
import { type MotionIntents } from "./motionIntents.js";
|
|
36
46
|
export interface GameContextItemEntry {
|
|
37
47
|
use?: string;
|
|
38
48
|
weapon?: Record<string, unknown>;
|
|
@@ -79,6 +89,8 @@ export interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAsset
|
|
|
79
89
|
}
|
|
80
90
|
export interface SceneObjectContext extends ObjectStore {
|
|
81
91
|
catalog(instanceId: string): GameContextObjectEntry | null;
|
|
92
|
+
raycast(input: ObjectRaycastInput): ObjectRaycastHit | null;
|
|
93
|
+
raycastAll(input: ObjectRaycastInput): readonly ObjectRaycastHit[];
|
|
82
94
|
}
|
|
83
95
|
export interface FloatTextInput {
|
|
84
96
|
instanceId?: string;
|
|
@@ -115,9 +127,13 @@ export interface HitReactionInput {
|
|
|
115
127
|
export interface SceneEntityContext {
|
|
116
128
|
spawn(name: string, options?: SpawnOptions): string;
|
|
117
129
|
despawn(instanceId: string): boolean;
|
|
130
|
+
update: EntityStore["update"];
|
|
118
131
|
setPose(instanceId: string, pose: EntityPose): boolean;
|
|
119
132
|
get(instanceId: string): SceneEntity | null;
|
|
120
133
|
list(): readonly SceneEntity[];
|
|
134
|
+
spawnPoseOf(instanceId: string): SpawnPose | null;
|
|
135
|
+
resetToSpawn(instanceId: string): boolean;
|
|
136
|
+
resetAllToSpawn(filter?: (entity: SceneEntity) => boolean): number;
|
|
121
137
|
stats: EntityStatsApi;
|
|
122
138
|
floatText(input: FloatTextInput): void;
|
|
123
139
|
telegraph(input: TelegraphInput): () => void;
|
|
@@ -125,7 +141,7 @@ export interface SceneEntityContext {
|
|
|
125
141
|
setTarget(fromId: string, toId: string | null): void;
|
|
126
142
|
getTarget(fromId: string): string | null;
|
|
127
143
|
cycleTarget(fromId: string, options?: CycleTargetOptions): string | null;
|
|
128
|
-
canReceive(instanceId: string, effect: string): string | null;
|
|
144
|
+
canReceive(instanceId: string, effect: string, magnitude?: number): string | null;
|
|
129
145
|
preview(input: SingleTargetEffectInput): number;
|
|
130
146
|
effect(input: EffectInput): EffectResult[];
|
|
131
147
|
willHitProjectile: ProjectileSystem["willHitProjectile"];
|
|
@@ -137,6 +153,7 @@ export interface SceneEntityContext {
|
|
|
137
153
|
queryArc: SpatialApi["queryArc"];
|
|
138
154
|
moveToward: SpatialApi["moveToward"];
|
|
139
155
|
form: Forms;
|
|
156
|
+
paint: PaintLayer;
|
|
140
157
|
}
|
|
141
158
|
export type WorldItemPickupResult = {
|
|
142
159
|
status: "ok";
|
|
@@ -151,6 +168,7 @@ export interface SceneWorldItemContext {
|
|
|
151
168
|
list(): readonly WorldItemRecord[];
|
|
152
169
|
nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
|
|
153
170
|
pickup(instanceId: string, userId: string): WorldItemPickupResult;
|
|
171
|
+
consume(instanceId: string): WorldItemRecord | null;
|
|
154
172
|
}
|
|
155
173
|
export interface GameContextCommands {
|
|
156
174
|
define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void;
|
|
@@ -180,12 +198,25 @@ export interface GameContextItemUse {
|
|
|
180
198
|
can(input: ItemUseInput): ItemUseRejection | null;
|
|
181
199
|
use(input: ItemUseInput): ItemUseResult<GameContext>;
|
|
182
200
|
}
|
|
201
|
+
export interface GameContextWorld {
|
|
202
|
+
ground: TerrainField;
|
|
203
|
+
groundHeightAt(x: number, z: number): number;
|
|
204
|
+
}
|
|
205
|
+
export interface GameContextCards {
|
|
206
|
+
/** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped pile for `id`. */
|
|
207
|
+
pile(id: string, config?: CardPileConfig): CardPile;
|
|
208
|
+
}
|
|
209
|
+
export interface GameContextTurn {
|
|
210
|
+
/** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped turn loop for `id`. */
|
|
211
|
+
loop(id: string, config?: TurnLoopConfig): TurnLoop;
|
|
212
|
+
}
|
|
183
213
|
export interface GameContext {
|
|
184
214
|
scene: {
|
|
185
215
|
object: SceneObjectContext;
|
|
186
216
|
entity: SceneEntityContext;
|
|
187
217
|
worldItem: SceneWorldItemContext;
|
|
188
218
|
};
|
|
219
|
+
world: GameContextWorld;
|
|
189
220
|
game: {
|
|
190
221
|
commands: GameContextCommands;
|
|
191
222
|
events: GameEvents;
|
|
@@ -194,10 +225,15 @@ export interface GameContext {
|
|
|
194
225
|
trade: TradeSystem;
|
|
195
226
|
quest: QuestJournal;
|
|
196
227
|
social: Social;
|
|
228
|
+
chat: Chat;
|
|
197
229
|
unlocks: Unlocks;
|
|
198
230
|
economy: GameContextEconomy;
|
|
199
231
|
leaderboard: Leaderboard;
|
|
200
232
|
roster: Roster;
|
|
233
|
+
/** Game-defined keyed reactive store slot (#163.1); mutations bump `ctx.version()`/notify `ctx.subscribe`. */
|
|
234
|
+
store: ObservableKeyedStore<unknown>;
|
|
235
|
+
cards: GameContextCards;
|
|
236
|
+
turn: GameContextTurn;
|
|
201
237
|
};
|
|
202
238
|
player: {
|
|
203
239
|
userId: string;
|
|
@@ -211,12 +247,18 @@ export interface GameContext {
|
|
|
211
247
|
movement: PoseState;
|
|
212
248
|
possession: Possession;
|
|
213
249
|
cosmetics: Cosmetics;
|
|
250
|
+
/** Vertical-motion seam into the shell-owned FrameDriver (#162.4); see `MotionIntents`. */
|
|
251
|
+
motion: MotionIntents;
|
|
214
252
|
};
|
|
215
253
|
item: {
|
|
216
254
|
use: GameContextItemUse;
|
|
217
255
|
weapon: WeaponStats;
|
|
218
256
|
};
|
|
219
257
|
time: SimClock;
|
|
258
|
+
/** Runtime camera-follow/cinematic override (#196.2); the shell reads `followedEntityId()` each frame. */
|
|
259
|
+
camera: CameraDirector;
|
|
260
|
+
/** Held-input snapshot published by the shell each frame before `onTick` (#164.1). */
|
|
261
|
+
input: InputSnapshot;
|
|
220
262
|
subscribe(listener: () => void): () => void;
|
|
221
263
|
version(): number;
|
|
222
264
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCardPile } from "../cards/cardPile.js";
|
|
1
2
|
import { createDeathSystem, deathReasonFromEffect, normalizeOnDeath } from "../combat/death.js";
|
|
2
3
|
import { createEffectSystem, } from "../combat/effects.js";
|
|
3
4
|
import { createProjectileSystem } from "../combat/projectiles.js";
|
|
@@ -6,6 +7,7 @@ import { pointInTelegraph, } from "../combat/telegraph.js";
|
|
|
6
7
|
import { createCommandRegistry, } from "../commands/commandRegistry.js";
|
|
7
8
|
import { balance as walletBalance, canAfford as walletCanAfford, charge as walletCharge, chargeAll as walletChargeAll, createEmptyWallet, grant as walletGrant, } from "../economy/wallet.js";
|
|
8
9
|
import { createCosmetics } from "../game/cosmetics.js";
|
|
10
|
+
import { groundFieldFor } from "../world/terrain.js";
|
|
9
11
|
import { createGameEvents } from "../game/events.js";
|
|
10
12
|
import { createGameFeed } from "../game/feed.js";
|
|
11
13
|
import { createLeaderboard } from "../game/leaderboard.js";
|
|
@@ -13,6 +15,7 @@ import { createLoadouts } from "../game/loadout.js";
|
|
|
13
15
|
import { createLootRegistry, grantDrops } from "../game/lootTable.js";
|
|
14
16
|
import { createQuestJournal } from "../game/quest.js";
|
|
15
17
|
import { createWorldItemStore, resolveDeathDrops, DEFAULT_RARITY, WORLD_ITEM_ENTITY_NAME, } from "../game/worldItem.js";
|
|
18
|
+
import { createChat } from "../game/chat.js";
|
|
16
19
|
import { createSocial } from "../game/social.js";
|
|
17
20
|
import { createTradeSystem } from "../game/trade.js";
|
|
18
21
|
import { createUnlocks } from "../game/unlocks.js";
|
|
@@ -20,8 +23,10 @@ import { createInventorySet, putItem, } from "../inventory/inventoryModel.js";
|
|
|
20
23
|
import { createItemUse, } from "../item/use.js";
|
|
21
24
|
import { createWeaponStats } from "../item/weapon.js";
|
|
22
25
|
import { createPoseState } from "../movement/poseState.js";
|
|
26
|
+
import { createPaintLayer } from "../scene/paintLayer.js";
|
|
23
27
|
import { createEntityStatsApi, seedStatValues, setStatValue, } from "../scene/entityStats.js";
|
|
24
28
|
import { createForms } from "../scene/form.js";
|
|
29
|
+
import { raycastObjects, raycastObjectsAll } from "../scene/objectQuery.js";
|
|
25
30
|
import { createObjectStore } from "../scene/objectStore.js";
|
|
26
31
|
import { createRoster } from "../scene/roster.js";
|
|
27
32
|
import { createPossession } from "../scene/possession.js";
|
|
@@ -29,12 +34,18 @@ import { createSpatialApi } from "../scene/spatial.js";
|
|
|
29
34
|
import { createTargeting } from "../scene/targeting.js";
|
|
30
35
|
import { createStats } from "../stats/statModifiers.js";
|
|
31
36
|
import { createChangeSignal, notifyAfter } from "../store/changeSignal.js";
|
|
37
|
+
import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
|
|
32
38
|
import { createSimClock } from "../time/simClock.js";
|
|
39
|
+
import { createTurnLoop } from "../turn/turnLoop.js";
|
|
40
|
+
import { createCameraDirector } from "./cameraDirector.js";
|
|
41
|
+
import { createInputSnapshot } from "./inputSnapshot.js";
|
|
42
|
+
import { createMotionIntents } from "./motionIntents.js";
|
|
33
43
|
export function createGameContext(options) {
|
|
34
44
|
const { definition, content, player } = options;
|
|
35
45
|
const now = options.now ?? Date.now;
|
|
36
46
|
const signal = createChangeSignal();
|
|
37
47
|
const time = createSimClock({ config: definition.time, onChange: signal.notify });
|
|
48
|
+
const ground = groundFieldFor(definition.world);
|
|
38
49
|
const entities = definition.scene;
|
|
39
50
|
const objects = createObjectStore();
|
|
40
51
|
entities.subscribe(signal.notify);
|
|
@@ -84,7 +95,7 @@ export function createGameContext(options) {
|
|
|
84
95
|
signal.notify();
|
|
85
96
|
},
|
|
86
97
|
};
|
|
87
|
-
const feed = createGameFeed();
|
|
98
|
+
const feed = createGameFeed(definition.feed);
|
|
88
99
|
const lootRegistry = createLootRegistry();
|
|
89
100
|
const unlocks = notifyAfter(createUnlocks(), ["grant", "hydrate"], signal.notify);
|
|
90
101
|
const rawSocial = createSocial({
|
|
@@ -96,11 +107,22 @@ export function createGameContext(options) {
|
|
|
96
107
|
},
|
|
97
108
|
});
|
|
98
109
|
const social = {
|
|
99
|
-
friends: notifyAfter(rawSocial.friends, ["request", "accept", "remove", "block", "hydrate"], signal.notify),
|
|
100
|
-
party: notifyAfter(rawSocial.party, ["invite", "accept", "kick", "leave", "promote"], signal.notify),
|
|
110
|
+
friends: notifyAfter(rawSocial.friends, ["request", "accept", "decline", "remove", "block", "hydrate"], signal.notify),
|
|
111
|
+
party: notifyAfter(rawSocial.party, ["invite", "accept", "decline", "kick", "leave", "promote"], signal.notify),
|
|
101
112
|
presence: rawSocial.presence,
|
|
102
113
|
emotes: rawSocial.emotes,
|
|
114
|
+
worldInvites: notifyAfter(rawSocial.worldInvites, ["invite", "accept", "decline"], signal.notify),
|
|
103
115
|
};
|
|
116
|
+
const chat = notifyAfter(createChat({
|
|
117
|
+
events,
|
|
118
|
+
now,
|
|
119
|
+
party: rawSocial.party,
|
|
120
|
+
proximity: {
|
|
121
|
+
entities: { get: (id) => entities.get(id) },
|
|
122
|
+
spatial: { inRadius: (center, radius, filter) => spatial.inRadius(center, radius, filter) },
|
|
123
|
+
},
|
|
124
|
+
blockedBy: (userId) => rawSocial.friends.snapshot(userId).blocked,
|
|
125
|
+
}), ["register", "send", "whisper", "hydrate"], signal.notify);
|
|
104
126
|
const leaderboard = notifyAfter(createLeaderboard(), ["increment", "hydrate"], signal.notify);
|
|
105
127
|
const roster = notifyAfter(createRoster({ now }), ["capture", "release", "setEquipped", "hydrate"], signal.notify);
|
|
106
128
|
const playerStats = createStats({});
|
|
@@ -110,6 +132,7 @@ export function createGameContext(options) {
|
|
|
110
132
|
const possession = notifyAfter(createPossession({ entities, events }), ["possess", "own", "disown"], signal.notify);
|
|
111
133
|
const forms = notifyAfter(createForms({ entities, time, events }), ["shapeshift", "revert"], signal.notify);
|
|
112
134
|
const cosmetics = notifyAfter(createCosmetics({ events }), ["apply", "equip", "hydrate"], signal.notify);
|
|
135
|
+
const paintLayer = notifyAfter(createPaintLayer(), ["paint", "clear"], signal.notify);
|
|
113
136
|
const inventoryDeclarations = definition.inventories ?? {};
|
|
114
137
|
const inventoryIds = Object.keys(inventoryDeclarations);
|
|
115
138
|
const layouts = {};
|
|
@@ -261,11 +284,21 @@ export function createGameContext(options) {
|
|
|
261
284
|
pose.clear(instanceId);
|
|
262
285
|
return existed;
|
|
263
286
|
}
|
|
287
|
+
function resetAllToSpawn(filter) {
|
|
288
|
+
let count = 0;
|
|
289
|
+
for (const entity of entities.list()) {
|
|
290
|
+
if (filter !== undefined && !filter(entity))
|
|
291
|
+
continue;
|
|
292
|
+
if (entities.resetToSpawn(entity.id))
|
|
293
|
+
count += 1;
|
|
294
|
+
}
|
|
295
|
+
return count;
|
|
296
|
+
}
|
|
264
297
|
const worldItems = notifyAfter(createWorldItemStore({
|
|
265
298
|
spawnEntity: (position) => entities.spawn(WORLD_ITEM_ENTITY_NAME, { position, role: "prop" }),
|
|
266
299
|
despawnEntity,
|
|
267
300
|
resolvePosition: (instanceId) => entities.get(instanceId)?.position,
|
|
268
|
-
}), ["spawn", "take"], signal.notify);
|
|
301
|
+
}), ["spawn", "take", "remove"], signal.notify);
|
|
269
302
|
function spawnWorldItem(input) {
|
|
270
303
|
const record = worldItems.spawn(input);
|
|
271
304
|
events.emit("worldItem.dropped", {
|
|
@@ -484,16 +517,66 @@ export function createGameContext(options) {
|
|
|
484
517
|
const sceneObjects = {
|
|
485
518
|
...objects,
|
|
486
519
|
catalog: (instanceId) => catalogObject(instanceId) ?? null,
|
|
520
|
+
raycast: (input) => raycastObjects(objects.list(), input),
|
|
521
|
+
raycastAll: (input) => raycastObjectsAll(objects.list(), input),
|
|
487
522
|
};
|
|
523
|
+
const store = notifyAfter(createObservableKeyedStore(), ["set", "delete"], signal.notify);
|
|
524
|
+
const cardPiles = new Map();
|
|
525
|
+
function pile(id, config) {
|
|
526
|
+
const existing = cardPiles.get(id);
|
|
527
|
+
if (existing !== undefined)
|
|
528
|
+
return existing;
|
|
529
|
+
if (config === undefined) {
|
|
530
|
+
throw new Error(`cardPile "${id}" has not been created yet; pass a config on first access`);
|
|
531
|
+
}
|
|
532
|
+
const created = notifyAfter(createCardPile(config), ["shuffle", "draw", "discard", "exhaust", "move", "reset"], signal.notify);
|
|
533
|
+
cardPiles.set(id, created);
|
|
534
|
+
return created;
|
|
535
|
+
}
|
|
536
|
+
const turnLoops = new Map();
|
|
537
|
+
function loop(id, config) {
|
|
538
|
+
const existing = turnLoops.get(id);
|
|
539
|
+
if (existing !== undefined)
|
|
540
|
+
return existing;
|
|
541
|
+
if (config === undefined) {
|
|
542
|
+
throw new Error(`turn loop "${id}" has not been created yet; pass a config on first access`);
|
|
543
|
+
}
|
|
544
|
+
const raw = createTurnLoop(config);
|
|
545
|
+
const wrappedCommit = notifyAfter(raw.commit, ["submit", "expected", "commit", "rewind", "clear"], signal.notify);
|
|
546
|
+
const wrapped = {
|
|
547
|
+
...notifyAfter(raw, [
|
|
548
|
+
"setOrder",
|
|
549
|
+
"addParticipant",
|
|
550
|
+
"removeParticipant",
|
|
551
|
+
"advancePhase",
|
|
552
|
+
"advanceTurn",
|
|
553
|
+
"advanceRound",
|
|
554
|
+
"spend",
|
|
555
|
+
"gain",
|
|
556
|
+
"refill",
|
|
557
|
+
"restore",
|
|
558
|
+
], signal.notify),
|
|
559
|
+
commit: wrappedCommit,
|
|
560
|
+
};
|
|
561
|
+
turnLoops.set(id, wrapped);
|
|
562
|
+
return wrapped;
|
|
563
|
+
}
|
|
564
|
+
const camera = notifyAfter(createCameraDirector(), ["follow", "setCinematic"], signal.notify);
|
|
565
|
+
const input = createInputSnapshot();
|
|
566
|
+
const motion = createMotionIntents();
|
|
488
567
|
const ctx = {
|
|
489
568
|
scene: {
|
|
490
569
|
object: sceneObjects,
|
|
491
570
|
entity: {
|
|
492
571
|
spawn: spawnEntity,
|
|
493
572
|
despawn: despawnEntity,
|
|
573
|
+
update: entities.update,
|
|
494
574
|
setPose: entities.setPose,
|
|
495
575
|
get: entities.get,
|
|
496
576
|
list: entities.list,
|
|
577
|
+
spawnPoseOf: entities.spawnPoseOf,
|
|
578
|
+
resetToSpawn: entities.resetToSpawn,
|
|
579
|
+
resetAllToSpawn,
|
|
497
580
|
stats: entityStats,
|
|
498
581
|
floatText: emitFloatText,
|
|
499
582
|
telegraph: fireTelegraph,
|
|
@@ -513,6 +596,7 @@ export function createGameContext(options) {
|
|
|
513
596
|
queryArc: spatial.queryArc,
|
|
514
597
|
moveToward: spatial.moveToward,
|
|
515
598
|
form: forms,
|
|
599
|
+
paint: paintLayer,
|
|
516
600
|
},
|
|
517
601
|
worldItem: {
|
|
518
602
|
spawn: spawnWorldItem,
|
|
@@ -520,8 +604,13 @@ export function createGameContext(options) {
|
|
|
520
604
|
list: worldItems.list,
|
|
521
605
|
nearestInRadius: worldItems.nearestInRadius,
|
|
522
606
|
pickup: pickupWorldItem,
|
|
607
|
+
consume: worldItems.remove,
|
|
523
608
|
},
|
|
524
609
|
},
|
|
610
|
+
world: {
|
|
611
|
+
ground,
|
|
612
|
+
groundHeightAt: ground.sampleHeight,
|
|
613
|
+
},
|
|
525
614
|
game: {
|
|
526
615
|
commands: {
|
|
527
616
|
define: commandRegistry.define,
|
|
@@ -552,10 +641,14 @@ export function createGameContext(options) {
|
|
|
552
641
|
trade,
|
|
553
642
|
quest,
|
|
554
643
|
social,
|
|
644
|
+
chat,
|
|
555
645
|
unlocks,
|
|
556
646
|
economy,
|
|
557
647
|
leaderboard,
|
|
558
648
|
roster,
|
|
649
|
+
store,
|
|
650
|
+
cards: { pile },
|
|
651
|
+
turn: { loop },
|
|
559
652
|
},
|
|
560
653
|
player: {
|
|
561
654
|
userId: player.userId,
|
|
@@ -567,6 +660,7 @@ export function createGameContext(options) {
|
|
|
567
660
|
movement: pose,
|
|
568
661
|
possession,
|
|
569
662
|
cosmetics,
|
|
663
|
+
motion,
|
|
570
664
|
},
|
|
571
665
|
item: {
|
|
572
666
|
use: {
|
|
@@ -582,6 +676,8 @@ export function createGameContext(options) {
|
|
|
582
676
|
weapon,
|
|
583
677
|
},
|
|
584
678
|
time,
|
|
679
|
+
camera,
|
|
680
|
+
input,
|
|
585
681
|
subscribe: signal.subscribe,
|
|
586
682
|
version: signal.version,
|
|
587
683
|
};
|