@jgengine/core 0.7.0 → 0.9.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 +63 -0
- package/README.md +2 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +14 -2
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- 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/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +22 -2
- package/dist/combat/abilityKit.js +72 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +64 -6
- package/dist/combat/projectiles.js +232 -74
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +40 -0
- package/dist/combat/shotOrigin.js +57 -0
- package/dist/combat/telegraph.d.ts +30 -1
- package/dist/combat/telegraph.js +61 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/recipe.js +4 -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 +205 -0
- package/dist/devtools/devtools.js +832 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +68 -0
- package/dist/game/chat.js +173 -0
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +35 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +27 -7
- package/dist/game/playableGame.d.ts +286 -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/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +44 -1
- package/dist/game/race.js +116 -11
- package/dist/game/recordBook.d.ts +29 -0
- package/dist/game/recordBook.js +86 -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/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +26 -3
- package/dist/input/axisInput.js +44 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -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/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +89 -0
- package/dist/input/touchScheme.js +153 -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 +59 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- 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/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -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 +17 -2
- package/dist/multiplayer/presenceModel.js +30 -4
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -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 +48 -5
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +43 -5
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +54 -8
- package/dist/physics/physicsWorld.js +400 -79
- package/dist/physics/ragdoll.js +1 -1
- 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 +18 -18
- 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/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +15 -0
- package/dist/runtime/cameraDirector.js +31 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +65 -2
- package/dist/runtime/gameContext.js +280 -9
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +30 -1
- package/dist/runtime/hostPersistence.js +70 -5
- package/dist/runtime/inputSnapshot.d.ts +12 -0
- package/dist/runtime/inputSnapshot.js +17 -0
- package/dist/runtime/motionIntents.d.ts +33 -0
- package/dist/runtime/motionIntents.js +43 -0
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/scene/colliders.d.ts +44 -0
- package/dist/scene/colliders.js +77 -0
- package/dist/scene/entityStore.d.ts +30 -5
- package/dist/scene/entityStore.js +83 -13
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +29 -0
- package/dist/scene/objectQuery.js +140 -0
- package/dist/scene/objectStore.d.ts +13 -0
- package/dist/scene/objectStore.js +130 -6
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +17 -1
- package/dist/scene/spatial.js +140 -17
- 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/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- 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/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.js +8 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- 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/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- 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/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +198 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +61 -0
- package/dist/world/environmentSummary.js +138 -0
- package/dist/world/features.d.ts +138 -9
- package/dist/world/features.js +73 -2
- package/dist/world/gridInstances.d.ts +12 -0
- package/dist/world/gridInstances.js +160 -0
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +62 -1
- package/dist/world/terrain.js +253 -9
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +4983 -0
- package/package.json +14 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type FactionRelation = "hostile" | "neutral" | "friendly";
|
|
2
|
+
export interface FactionDef {
|
|
3
|
+
id: string;
|
|
4
|
+
relations?: Readonly<Record<string, FactionRelation>>;
|
|
5
|
+
towardSelf?: FactionRelation;
|
|
6
|
+
towardOthers?: FactionRelation;
|
|
7
|
+
}
|
|
8
|
+
export interface FactionGraphConfig {
|
|
9
|
+
factions: readonly FactionDef[];
|
|
10
|
+
symmetric?: boolean;
|
|
11
|
+
unaligned?: FactionRelation;
|
|
12
|
+
}
|
|
13
|
+
export interface FactionGraph {
|
|
14
|
+
relationBetween(observer: string | null | undefined, other: string | null | undefined): FactionRelation;
|
|
15
|
+
isHostile(observer: string | null | undefined, other: string | null | undefined): boolean;
|
|
16
|
+
isFriendly(observer: string | null | undefined, other: string | null | undefined): boolean;
|
|
17
|
+
isNeutral(observer: string | null | undefined, other: string | null | undefined): boolean;
|
|
18
|
+
has(factionId: string): boolean;
|
|
19
|
+
ids(): string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function createFactionGraph(config: FactionGraphConfig): FactionGraph;
|
|
22
|
+
export interface FactionRoster {
|
|
23
|
+
assign(entityId: string, factionId: string | null): void;
|
|
24
|
+
factionOf(entityId: string): string | null;
|
|
25
|
+
relationBetweenEntities(observer: string, other: string): FactionRelation;
|
|
26
|
+
isHostile(observer: string, other: string): boolean;
|
|
27
|
+
isFriendly(observer: string, other: string): boolean;
|
|
28
|
+
members(factionId: string): string[];
|
|
29
|
+
hostilesOf(observer: string, candidates: Iterable<string>): string[];
|
|
30
|
+
remove(entityId: string): void;
|
|
31
|
+
clear(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createFactionRoster(graph: FactionGraph): FactionRoster;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function createFactionGraph(config) {
|
|
2
|
+
const symmetric = config.symmetric ?? true;
|
|
3
|
+
const unaligned = config.unaligned ?? "neutral";
|
|
4
|
+
const defs = new Map();
|
|
5
|
+
for (const def of config.factions)
|
|
6
|
+
defs.set(def.id, def);
|
|
7
|
+
function relationBetween(observer, other) {
|
|
8
|
+
if (observer == null || other == null)
|
|
9
|
+
return unaligned;
|
|
10
|
+
const from = defs.get(observer);
|
|
11
|
+
const to = defs.get(other);
|
|
12
|
+
if (from === undefined || to === undefined)
|
|
13
|
+
return unaligned;
|
|
14
|
+
if (observer === other)
|
|
15
|
+
return from.towardSelf ?? "friendly";
|
|
16
|
+
const direct = from.relations?.[other];
|
|
17
|
+
if (direct !== undefined)
|
|
18
|
+
return direct;
|
|
19
|
+
if (symmetric) {
|
|
20
|
+
const mirrored = to.relations?.[observer];
|
|
21
|
+
if (mirrored !== undefined)
|
|
22
|
+
return mirrored;
|
|
23
|
+
}
|
|
24
|
+
return from.towardOthers ?? "neutral";
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
relationBetween,
|
|
28
|
+
isHostile: (observer, other) => relationBetween(observer, other) === "hostile",
|
|
29
|
+
isFriendly: (observer, other) => relationBetween(observer, other) === "friendly",
|
|
30
|
+
isNeutral: (observer, other) => relationBetween(observer, other) === "neutral",
|
|
31
|
+
has: (factionId) => defs.has(factionId),
|
|
32
|
+
ids: () => [...defs.keys()],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function createFactionRoster(graph) {
|
|
36
|
+
const membership = new Map();
|
|
37
|
+
function factionOf(entityId) {
|
|
38
|
+
return membership.get(entityId) ?? null;
|
|
39
|
+
}
|
|
40
|
+
function relationBetweenEntities(observer, other) {
|
|
41
|
+
return graph.relationBetween(factionOf(observer), factionOf(other));
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
assign(entityId, factionId) {
|
|
45
|
+
if (factionId === null)
|
|
46
|
+
membership.delete(entityId);
|
|
47
|
+
else
|
|
48
|
+
membership.set(entityId, factionId);
|
|
49
|
+
},
|
|
50
|
+
factionOf,
|
|
51
|
+
relationBetweenEntities,
|
|
52
|
+
isHostile: (observer, other) => relationBetweenEntities(observer, other) === "hostile",
|
|
53
|
+
isFriendly: (observer, other) => relationBetweenEntities(observer, other) === "friendly",
|
|
54
|
+
members(factionId) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const [entityId, id] of membership)
|
|
57
|
+
if (id === factionId)
|
|
58
|
+
out.push(entityId);
|
|
59
|
+
return out;
|
|
60
|
+
},
|
|
61
|
+
hostilesOf(observer, candidates) {
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const id of candidates) {
|
|
64
|
+
if (id !== observer && relationBetweenEntities(observer, id) === "hostile")
|
|
65
|
+
out.push(id);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
},
|
|
69
|
+
remove(entityId) {
|
|
70
|
+
membership.delete(entityId);
|
|
71
|
+
},
|
|
72
|
+
clear() {
|
|
73
|
+
membership.clear();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FactionRelation } from "./factions.js";
|
|
2
|
+
export interface ReputationTier {
|
|
3
|
+
id: string;
|
|
4
|
+
min: number;
|
|
5
|
+
relation: FactionRelation;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_REPUTATION_TIERS: readonly ReputationTier[];
|
|
8
|
+
export declare function tierForStanding(tiers: readonly ReputationTier[], standing: number): ReputationTier;
|
|
9
|
+
export interface ReputationLedgerConfig {
|
|
10
|
+
tiers?: readonly ReputationTier[];
|
|
11
|
+
initial?: Readonly<Record<string, number>>;
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ReputationLedger {
|
|
16
|
+
standing(actorId: string, factionId: string): number;
|
|
17
|
+
hasStanding(actorId: string, factionId: string): boolean;
|
|
18
|
+
gain(actorId: string, factionId: string, amount: number): number;
|
|
19
|
+
set(actorId: string, factionId: string, standing: number): number;
|
|
20
|
+
tier(actorId: string, factionId: string): ReputationTier;
|
|
21
|
+
relation(actorId: string, factionId: string): FactionRelation;
|
|
22
|
+
standings(actorId: string): Record<string, number>;
|
|
23
|
+
reset(actorId: string, factionId?: string): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createReputationLedger(config?: ReputationLedgerConfig): ReputationLedger;
|
|
26
|
+
export interface EffectiveRelationInput {
|
|
27
|
+
base: FactionRelation;
|
|
28
|
+
ledger: ReputationLedger;
|
|
29
|
+
actorId: string;
|
|
30
|
+
factionId: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function effectiveRelation(input: EffectiveRelationInput): FactionRelation;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const DEFAULT_REPUTATION_TIERS = [
|
|
2
|
+
{ id: "hated", min: -42000, relation: "hostile" },
|
|
3
|
+
{ id: "hostile", min: -6000, relation: "hostile" },
|
|
4
|
+
{ id: "unfriendly", min: -3000, relation: "neutral" },
|
|
5
|
+
{ id: "neutral", min: 0, relation: "neutral" },
|
|
6
|
+
{ id: "friendly", min: 3000, relation: "friendly" },
|
|
7
|
+
{ id: "honored", min: 9000, relation: "friendly" },
|
|
8
|
+
{ id: "revered", min: 21000, relation: "friendly" },
|
|
9
|
+
{ id: "exalted", min: 42000, relation: "friendly" },
|
|
10
|
+
];
|
|
11
|
+
export function tierForStanding(tiers, standing) {
|
|
12
|
+
let match = tiers[0];
|
|
13
|
+
for (const tier of tiers) {
|
|
14
|
+
if (standing >= tier.min && tier.min >= match.min)
|
|
15
|
+
match = tier;
|
|
16
|
+
}
|
|
17
|
+
return match;
|
|
18
|
+
}
|
|
19
|
+
export function createReputationLedger(config = {}) {
|
|
20
|
+
const tiers = [...(config.tiers ?? DEFAULT_REPUTATION_TIERS)].sort((a, b) => a.min - b.min);
|
|
21
|
+
const initial = config.initial ?? {};
|
|
22
|
+
const min = config.min ?? Number.NEGATIVE_INFINITY;
|
|
23
|
+
const max = config.max ?? Number.POSITIVE_INFINITY;
|
|
24
|
+
const records = new Map();
|
|
25
|
+
function clamp(value) {
|
|
26
|
+
return Math.min(max, Math.max(min, value));
|
|
27
|
+
}
|
|
28
|
+
function standing(actorId, factionId) {
|
|
29
|
+
const stored = records.get(actorId)?.get(factionId);
|
|
30
|
+
if (stored !== undefined)
|
|
31
|
+
return stored;
|
|
32
|
+
return initial[factionId] ?? 0;
|
|
33
|
+
}
|
|
34
|
+
function store(actorId, factionId, value) {
|
|
35
|
+
const clamped = clamp(value);
|
|
36
|
+
let row = records.get(actorId);
|
|
37
|
+
if (row === undefined) {
|
|
38
|
+
row = new Map();
|
|
39
|
+
records.set(actorId, row);
|
|
40
|
+
}
|
|
41
|
+
row.set(factionId, clamped);
|
|
42
|
+
return clamped;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
standing,
|
|
46
|
+
hasStanding(actorId, factionId) {
|
|
47
|
+
return records.get(actorId)?.has(factionId) === true || initial[factionId] !== undefined;
|
|
48
|
+
},
|
|
49
|
+
gain(actorId, factionId, amount) {
|
|
50
|
+
return store(actorId, factionId, standing(actorId, factionId) + amount);
|
|
51
|
+
},
|
|
52
|
+
set(actorId, factionId, value) {
|
|
53
|
+
return store(actorId, factionId, value);
|
|
54
|
+
},
|
|
55
|
+
tier(actorId, factionId) {
|
|
56
|
+
return tierForStanding(tiers, standing(actorId, factionId));
|
|
57
|
+
},
|
|
58
|
+
relation(actorId, factionId) {
|
|
59
|
+
return tierForStanding(tiers, standing(actorId, factionId)).relation;
|
|
60
|
+
},
|
|
61
|
+
standings(actorId) {
|
|
62
|
+
const out = { ...initial };
|
|
63
|
+
const row = records.get(actorId);
|
|
64
|
+
if (row !== undefined)
|
|
65
|
+
for (const [factionId, value] of row)
|
|
66
|
+
out[factionId] = value;
|
|
67
|
+
return out;
|
|
68
|
+
},
|
|
69
|
+
reset(actorId, factionId) {
|
|
70
|
+
if (factionId === undefined)
|
|
71
|
+
records.delete(actorId);
|
|
72
|
+
else
|
|
73
|
+
records.get(actorId)?.delete(factionId);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function effectiveRelation(input) {
|
|
78
|
+
if (input.ledger.hasStanding(input.actorId, input.factionId)) {
|
|
79
|
+
return input.ledger.relation(input.actorId, input.factionId);
|
|
80
|
+
}
|
|
81
|
+
return input.base;
|
|
82
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { GameEvents } from "./events.js";
|
|
2
|
+
import type { ChatFilter } from "./chatFilter.js";
|
|
3
|
+
import type { EmotesDeps } from "./social.js";
|
|
4
|
+
export type ChatChannelKind = "global" | "party" | "proximity";
|
|
5
|
+
export interface ChatRateLimit {
|
|
6
|
+
count: number;
|
|
7
|
+
perMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ChatChannelDef {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: ChatChannelKind;
|
|
12
|
+
radius?: number;
|
|
13
|
+
historyLimit?: number;
|
|
14
|
+
rateLimit?: ChatRateLimit;
|
|
15
|
+
}
|
|
16
|
+
export interface ChatMessage {
|
|
17
|
+
id: string;
|
|
18
|
+
channelId: string;
|
|
19
|
+
fromUserId: string;
|
|
20
|
+
body: string;
|
|
21
|
+
at: number;
|
|
22
|
+
}
|
|
23
|
+
export type ChatRecipients = readonly string[] | "all";
|
|
24
|
+
export type ChatSendResult = {
|
|
25
|
+
message: ChatMessage;
|
|
26
|
+
recipients: ChatRecipients;
|
|
27
|
+
} | {
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
export interface ChatSnapshot {
|
|
31
|
+
messages: Record<string, ChatMessage[]>;
|
|
32
|
+
counter: number;
|
|
33
|
+
}
|
|
34
|
+
export interface Chat {
|
|
35
|
+
register(def: ChatChannelDef): void;
|
|
36
|
+
channels(): ChatChannelDef[];
|
|
37
|
+
send(fromUserId: string, channelId: string, body: string): ChatSendResult;
|
|
38
|
+
whisper(fromUserId: string, toUserId: string, body: string): ChatSendResult;
|
|
39
|
+
history(channelId: string, options?: {
|
|
40
|
+
limit?: number;
|
|
41
|
+
viewerUserId?: string;
|
|
42
|
+
}): ChatMessage[];
|
|
43
|
+
snapshot(): ChatSnapshot;
|
|
44
|
+
hydrate(data: ChatSnapshot): void;
|
|
45
|
+
}
|
|
46
|
+
export interface ChatDeps {
|
|
47
|
+
events: GameEvents;
|
|
48
|
+
now?: () => number;
|
|
49
|
+
party?: {
|
|
50
|
+
membersOf(userId: string): string[];
|
|
51
|
+
};
|
|
52
|
+
proximity?: EmotesDeps;
|
|
53
|
+
blockedBy?: (userId: string) => readonly string[];
|
|
54
|
+
maxBodyLength?: number;
|
|
55
|
+
defaultRateLimit?: ChatRateLimit;
|
|
56
|
+
filter?: ChatFilter;
|
|
57
|
+
}
|
|
58
|
+
export declare const DEFAULT_CHAT_RATE_LIMIT: ChatRateLimit;
|
|
59
|
+
export declare const DEFAULT_CHAT_HISTORY_LIMIT = 100;
|
|
60
|
+
export declare const DEFAULT_CHAT_BODY_LENGTH = 500;
|
|
61
|
+
export declare const DEFAULT_PROXIMITY_CHAT_RADIUS = 20;
|
|
62
|
+
export declare const WHISPER_CHANNEL_PREFIX = "whisper:";
|
|
63
|
+
export declare function whisperChannelId(a: string, b: string): string;
|
|
64
|
+
export interface ChatRateLimiter {
|
|
65
|
+
allow(key: string, atMs: number): boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare function createChatRateLimiter(limit: ChatRateLimit): ChatRateLimiter;
|
|
68
|
+
export declare function createChat(deps: ChatDeps): Chat;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export const DEFAULT_CHAT_RATE_LIMIT = { count: 10, perMs: 10_000 };
|
|
2
|
+
export const DEFAULT_CHAT_HISTORY_LIMIT = 100;
|
|
3
|
+
export const DEFAULT_CHAT_BODY_LENGTH = 500;
|
|
4
|
+
export const DEFAULT_PROXIMITY_CHAT_RADIUS = 20;
|
|
5
|
+
export const WHISPER_CHANNEL_PREFIX = "whisper:";
|
|
6
|
+
export function whisperChannelId(a, b) {
|
|
7
|
+
const [first, second] = a < b ? [a, b] : [b, a];
|
|
8
|
+
return `${WHISPER_CHANNEL_PREFIX}${first}:${second}`;
|
|
9
|
+
}
|
|
10
|
+
export function createChatRateLimiter(limit) {
|
|
11
|
+
const windows = new Map();
|
|
12
|
+
return {
|
|
13
|
+
allow(key, atMs) {
|
|
14
|
+
const cutoff = atMs - limit.perMs;
|
|
15
|
+
const stamps = (windows.get(key) ?? []).filter((stamp) => stamp > cutoff);
|
|
16
|
+
if (stamps.length >= limit.count) {
|
|
17
|
+
windows.set(key, stamps);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
stamps.push(atMs);
|
|
21
|
+
windows.set(key, stamps);
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const BUILTIN_CHANNELS = [
|
|
27
|
+
{ id: "global", kind: "global" },
|
|
28
|
+
{ id: "party", kind: "party" },
|
|
29
|
+
{ id: "proximity", kind: "proximity", radius: DEFAULT_PROXIMITY_CHAT_RADIUS },
|
|
30
|
+
];
|
|
31
|
+
export function createChat(deps) {
|
|
32
|
+
const now = deps.now ?? Date.now;
|
|
33
|
+
const events = deps.events;
|
|
34
|
+
const maxBodyLength = deps.maxBodyLength ?? DEFAULT_CHAT_BODY_LENGTH;
|
|
35
|
+
const defaultRateLimit = deps.defaultRateLimit ?? DEFAULT_CHAT_RATE_LIMIT;
|
|
36
|
+
const blockedBy = deps.blockedBy ?? (() => []);
|
|
37
|
+
const filter = deps.filter;
|
|
38
|
+
const channelDefs = new Map();
|
|
39
|
+
for (const def of BUILTIN_CHANNELS)
|
|
40
|
+
channelDefs.set(def.id, { ...def });
|
|
41
|
+
const messagesByChannel = new Map();
|
|
42
|
+
const limiters = new Map();
|
|
43
|
+
let counter = 0;
|
|
44
|
+
function limiterFor(channelId, rateLimit) {
|
|
45
|
+
let limiter = limiters.get(channelId);
|
|
46
|
+
if (limiter === undefined) {
|
|
47
|
+
limiter = createChatRateLimiter(rateLimit);
|
|
48
|
+
limiters.set(channelId, limiter);
|
|
49
|
+
}
|
|
50
|
+
return limiter;
|
|
51
|
+
}
|
|
52
|
+
function hasBlocked(userId, targetUserId) {
|
|
53
|
+
return blockedBy(userId).includes(targetUserId);
|
|
54
|
+
}
|
|
55
|
+
function resolveRecipients(fromUserId, channelId, def) {
|
|
56
|
+
if (channelId.startsWith(WHISPER_CHANNEL_PREFIX)) {
|
|
57
|
+
const pair = channelId.slice(WHISPER_CHANNEL_PREFIX.length).split(":");
|
|
58
|
+
const toUserId = pair.find((userId) => userId !== fromUserId);
|
|
59
|
+
if (pair.length !== 2 || toUserId === undefined || !pair.includes(fromUserId)) {
|
|
60
|
+
return { reason: "not a participant of this whisper" };
|
|
61
|
+
}
|
|
62
|
+
if (hasBlocked(toUserId, fromUserId) || hasBlocked(fromUserId, toUserId)) {
|
|
63
|
+
return { reason: "blocked" };
|
|
64
|
+
}
|
|
65
|
+
return { recipients: [toUserId] };
|
|
66
|
+
}
|
|
67
|
+
if (def === null)
|
|
68
|
+
return { reason: `unknown channel "${channelId}"` };
|
|
69
|
+
if (def.kind === "global")
|
|
70
|
+
return { recipients: "all" };
|
|
71
|
+
if (def.kind === "party") {
|
|
72
|
+
const members = deps.party?.membersOf(fromUserId) ?? [];
|
|
73
|
+
if (!members.includes(fromUserId))
|
|
74
|
+
return { reason: "not in a party" };
|
|
75
|
+
return {
|
|
76
|
+
recipients: members.filter((userId) => userId !== fromUserId && !hasBlocked(userId, fromUserId)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const proximity = deps.proximity;
|
|
80
|
+
if (proximity === undefined)
|
|
81
|
+
return { reason: "proximity chat not configured" };
|
|
82
|
+
const origin = proximity.entities.get(fromUserId);
|
|
83
|
+
if (origin === null)
|
|
84
|
+
return { reason: `entity "${fromUserId}" is not spawned` };
|
|
85
|
+
const nearby = proximity.spatial.inRadius(origin.position, def.radius ?? DEFAULT_PROXIMITY_CHAT_RADIUS, (id) => id !== fromUserId && proximity.entities.get(id)?.role === "player");
|
|
86
|
+
return { recipients: nearby.filter((userId) => !hasBlocked(userId, fromUserId)) };
|
|
87
|
+
}
|
|
88
|
+
function append(channelId, message, historyLimit) {
|
|
89
|
+
const ring = messagesByChannel.get(channelId) ?? [];
|
|
90
|
+
ring.push(message);
|
|
91
|
+
if (ring.length > historyLimit)
|
|
92
|
+
ring.splice(0, ring.length - historyLimit);
|
|
93
|
+
messagesByChannel.set(channelId, ring);
|
|
94
|
+
}
|
|
95
|
+
function sendResolved(fromUserId, channelId, body, def) {
|
|
96
|
+
const trimmed = body.trim();
|
|
97
|
+
if (trimmed.length === 0)
|
|
98
|
+
return { reason: "empty message" };
|
|
99
|
+
if (trimmed.length > maxBodyLength)
|
|
100
|
+
return { reason: "message too long" };
|
|
101
|
+
let filteredBody = trimmed;
|
|
102
|
+
if (filter !== undefined) {
|
|
103
|
+
const filtered = filter.apply(trimmed);
|
|
104
|
+
if (!filtered.ok)
|
|
105
|
+
return { reason: "filtered" };
|
|
106
|
+
filteredBody = filtered.body;
|
|
107
|
+
}
|
|
108
|
+
const resolved = resolveRecipients(fromUserId, channelId, def);
|
|
109
|
+
if ("reason" in resolved)
|
|
110
|
+
return resolved;
|
|
111
|
+
const at = now();
|
|
112
|
+
const rateLimit = def?.rateLimit ?? defaultRateLimit;
|
|
113
|
+
if (!limiterFor(channelId, rateLimit).allow(`${fromUserId}|${channelId}`, at)) {
|
|
114
|
+
return { reason: "rate limited" };
|
|
115
|
+
}
|
|
116
|
+
counter += 1;
|
|
117
|
+
const message = {
|
|
118
|
+
id: `msg_${counter}`,
|
|
119
|
+
channelId,
|
|
120
|
+
fromUserId,
|
|
121
|
+
body: filteredBody,
|
|
122
|
+
at,
|
|
123
|
+
};
|
|
124
|
+
append(channelId, message, def?.historyLimit ?? DEFAULT_CHAT_HISTORY_LIMIT);
|
|
125
|
+
const event = { ...message };
|
|
126
|
+
if (resolved.recipients !== "all")
|
|
127
|
+
event.recipients = resolved.recipients;
|
|
128
|
+
events.emit("chat.message", event);
|
|
129
|
+
return { message, recipients: resolved.recipients };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
register(def) {
|
|
133
|
+
channelDefs.set(def.id, { ...def });
|
|
134
|
+
limiters.delete(def.id);
|
|
135
|
+
},
|
|
136
|
+
channels() {
|
|
137
|
+
return Array.from(channelDefs.values(), (def) => ({ ...def }));
|
|
138
|
+
},
|
|
139
|
+
send(fromUserId, channelId, body) {
|
|
140
|
+
const def = channelDefs.get(channelId) ?? null;
|
|
141
|
+
return sendResolved(fromUserId, channelId, body, def);
|
|
142
|
+
},
|
|
143
|
+
whisper(fromUserId, toUserId, body) {
|
|
144
|
+
if (fromUserId === toUserId)
|
|
145
|
+
return { reason: "cannot whisper yourself" };
|
|
146
|
+
const channelId = whisperChannelId(fromUserId, toUserId);
|
|
147
|
+
return sendResolved(fromUserId, channelId, body, channelDefs.get(channelId) ?? null);
|
|
148
|
+
},
|
|
149
|
+
history(channelId, options) {
|
|
150
|
+
const ring = messagesByChannel.get(channelId) ?? [];
|
|
151
|
+
const viewerUserId = options?.viewerUserId;
|
|
152
|
+
const visible = viewerUserId === undefined
|
|
153
|
+
? ring
|
|
154
|
+
: ring.filter((message) => !hasBlocked(viewerUserId, message.fromUserId));
|
|
155
|
+
const limit = options?.limit;
|
|
156
|
+
return limit === undefined ? visible.slice() : visible.slice(-limit);
|
|
157
|
+
},
|
|
158
|
+
snapshot() {
|
|
159
|
+
const messages = {};
|
|
160
|
+
for (const [channelId, ring] of messagesByChannel) {
|
|
161
|
+
messages[channelId] = ring.map((message) => ({ ...message }));
|
|
162
|
+
}
|
|
163
|
+
return { messages, counter };
|
|
164
|
+
},
|
|
165
|
+
hydrate(data) {
|
|
166
|
+
messagesByChannel.clear();
|
|
167
|
+
for (const [channelId, ring] of Object.entries(data.messages)) {
|
|
168
|
+
messagesByChannel.set(channelId, ring.map((message) => ({ ...message })));
|
|
169
|
+
}
|
|
170
|
+
counter = data.counter;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ChatFilterConfig {
|
|
2
|
+
blockedWords: readonly string[];
|
|
3
|
+
mode?: "mask" | "reject";
|
|
4
|
+
mask?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ChatFilterResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
body: string;
|
|
9
|
+
matched: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface ChatFilter {
|
|
12
|
+
apply(body: string): ChatFilterResult;
|
|
13
|
+
}
|
|
14
|
+
export declare function normalizeChatText(text: string): string;
|
|
15
|
+
export declare function createChatFilter(config: ChatFilterConfig): ChatFilter;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const LEET_SUBSTITUTIONS = {
|
|
2
|
+
"0": "o",
|
|
3
|
+
"1": "i",
|
|
4
|
+
"3": "e",
|
|
5
|
+
"4": "a",
|
|
6
|
+
"5": "s",
|
|
7
|
+
"7": "t",
|
|
8
|
+
"@": "a",
|
|
9
|
+
$: "s",
|
|
10
|
+
"!": "i",
|
|
11
|
+
};
|
|
12
|
+
const TOKEN_PATTERN = /[\p{L}\p{N}@$!]+/gu;
|
|
13
|
+
export function normalizeChatText(text) {
|
|
14
|
+
let normalized = "";
|
|
15
|
+
for (const char of text.toLowerCase()) {
|
|
16
|
+
normalized += LEET_SUBSTITUTIONS[char] ?? char;
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
export function createChatFilter(config) {
|
|
21
|
+
const mode = config.mode ?? "mask";
|
|
22
|
+
const mask = config.mask ?? "*";
|
|
23
|
+
const blockedByNormalized = new Map();
|
|
24
|
+
for (const word of config.blockedWords) {
|
|
25
|
+
blockedByNormalized.set(normalizeChatText(word), word);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
apply(body) {
|
|
29
|
+
if (blockedByNormalized.size === 0)
|
|
30
|
+
return { ok: true, body, matched: [] };
|
|
31
|
+
const hit = new Set();
|
|
32
|
+
let masked = "";
|
|
33
|
+
let cursor = 0;
|
|
34
|
+
for (const token of body.matchAll(TOKEN_PATTERN)) {
|
|
35
|
+
const word = token[0];
|
|
36
|
+
const start = token.index;
|
|
37
|
+
const blockedWord = blockedByNormalized.get(normalizeChatText(word));
|
|
38
|
+
if (blockedWord === undefined)
|
|
39
|
+
continue;
|
|
40
|
+
hit.add(blockedWord);
|
|
41
|
+
if (mode === "reject")
|
|
42
|
+
continue;
|
|
43
|
+
masked += body.slice(cursor, start) + mask.repeat(word.length);
|
|
44
|
+
cursor = start + word.length;
|
|
45
|
+
}
|
|
46
|
+
if (hit.size === 0)
|
|
47
|
+
return { ok: true, body, matched: [] };
|
|
48
|
+
const matched = [...new Set(config.blockedWords)].filter((word) => hit.has(word));
|
|
49
|
+
if (mode === "reject")
|
|
50
|
+
return { ok: false, body, matched };
|
|
51
|
+
masked += body.slice(cursor);
|
|
52
|
+
return { ok: true, body: masked, matched };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GameContext } from "../runtime/gameContext.js";
|
|
2
|
+
export declare const PLAY_CONTROLS_STORE_KEY = "jg.playControls";
|
|
3
|
+
export declare function setPlayControlsActive(ctx: GameContext, active: boolean): void;
|
|
4
|
+
export declare function playControlsActive(ctx: GameContext): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const PLAY_CONTROLS_STORE_KEY = "jg.playControls";
|
|
2
|
+
export function setPlayControlsActive(ctx, active) {
|
|
3
|
+
ctx.game.store.set(PLAY_CONTROLS_STORE_KEY, active);
|
|
4
|
+
}
|
|
5
|
+
export function playControlsActive(ctx) {
|
|
6
|
+
return ctx.game.store.get(PLAY_CONTROLS_STORE_KEY) !== false;
|
|
7
|
+
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { ActionCodesMap } from "../input/actionBindings.js";
|
|
2
|
+
import type { GameFeedOptions } from "./feed.js";
|
|
2
3
|
import type { ItemTraits } from "../inventory/inventoryModel.js";
|
|
3
4
|
import type { StorageTier } from "../inventory/storageTier.js";
|
|
4
5
|
import type { SaveConfig } from "../runtime/save.js";
|
|
5
|
-
import type
|
|
6
|
+
import { type AssetCatalog, type ModelAssetRef } from "../scene/assetCatalog.js";
|
|
6
7
|
import { type EntityStore } from "../scene/entityStore.js";
|
|
7
8
|
import type { TimeConfig } from "../time/simClock.js";
|
|
8
9
|
import type { WorldFeature } from "../world/features.js";
|
|
9
10
|
export interface PhysicsConfig {
|
|
10
11
|
gravity?: number;
|
|
12
|
+
jumpVelocity?: number;
|
|
13
|
+
/** Opt placed scene objects into projectile raycasts as blocking cover AABBs. Default false — enabling changes where shots land. */
|
|
14
|
+
projectileObstacles?: boolean;
|
|
11
15
|
}
|
|
12
16
|
export interface InventoryDeclaration {
|
|
13
17
|
slots: number;
|
|
@@ -35,6 +39,8 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
|
|
|
35
39
|
physics?: PhysicsConfig;
|
|
36
40
|
/** Simulation clock: real→game time scale, selectable speeds, calendar. Exposed as `ctx.time`; the shell feeds its scaled dt to `loop.onTick`. */
|
|
37
41
|
time?: TimeConfig;
|
|
42
|
+
/** Per-action ring-buffer capacity for `ctx.game.feed`. Default 20. */
|
|
43
|
+
feed?: GameFeedOptions;
|
|
38
44
|
inventories?: Record<string, InventoryDeclaration>;
|
|
39
45
|
input?: ActionCodesMap;
|
|
40
46
|
server?: GameServerConfig;
|
|
@@ -42,5 +48,7 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
|
|
|
42
48
|
ui?: unknown;
|
|
43
49
|
loop?: GameLoop<any>;
|
|
44
50
|
}
|
|
45
|
-
export type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene"
|
|
51
|
+
export type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & {
|
|
52
|
+
assets?: AssetCatalog<TAssetRef>;
|
|
53
|
+
};
|
|
46
54
|
export declare function defineGame<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer>;
|
package/dist/game/defineGame.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createAssetCatalog } from "../scene/assetCatalog.js";
|
|
1
2
|
import { createEntityStore } from "../scene/entityStore.js";
|
|
2
3
|
export function defineGame(config) {
|
|
3
4
|
if (config.name.trim().length === 0) {
|
|
4
5
|
throw new Error("defineGame: name must be non-empty");
|
|
5
6
|
}
|
|
6
|
-
return { ...config, scene: createEntityStore() };
|
|
7
|
+
return { ...config, scene: createEntityStore(), assets: config.assets ?? createAssetCatalog() };
|
|
7
8
|
}
|
package/dist/game/events.d.ts
CHANGED
|
@@ -63,6 +63,28 @@ export interface SocialPartyLeftEvent {
|
|
|
63
63
|
userId: string;
|
|
64
64
|
partyId: string;
|
|
65
65
|
}
|
|
66
|
+
export interface SocialWorldInvitedEvent {
|
|
67
|
+
inviteId: string;
|
|
68
|
+
fromUserId: string;
|
|
69
|
+
toUserId: string;
|
|
70
|
+
serverId: string;
|
|
71
|
+
joinCode?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface SocialWorldAcceptedEvent {
|
|
74
|
+
inviteId: string;
|
|
75
|
+
userId: string;
|
|
76
|
+
fromUserId: string;
|
|
77
|
+
serverId: string;
|
|
78
|
+
joinCode?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface ChatMessageEvent {
|
|
81
|
+
id: string;
|
|
82
|
+
channelId: string;
|
|
83
|
+
fromUserId: string;
|
|
84
|
+
body: string;
|
|
85
|
+
at: number;
|
|
86
|
+
recipients?: readonly string[];
|
|
87
|
+
}
|
|
66
88
|
export interface StatLevelUpEvent {
|
|
67
89
|
userId: string;
|
|
68
90
|
stat: string;
|
|
@@ -87,6 +109,10 @@ export interface CombatTelegraphEvent {
|
|
|
87
109
|
windupMs: number;
|
|
88
110
|
kind: string;
|
|
89
111
|
}
|
|
112
|
+
export interface CombatTelegraphCancelledEvent {
|
|
113
|
+
/** The `combat.telegraph` event id whose decal should disappear early. */
|
|
114
|
+
id: number;
|
|
115
|
+
}
|
|
90
116
|
export interface CombatHitReactionEvent {
|
|
91
117
|
instanceId?: string;
|
|
92
118
|
position: [number, number, number];
|
|
@@ -134,10 +160,15 @@ export interface FormChangedEvent {
|
|
|
134
160
|
instanceId: string;
|
|
135
161
|
formId: string | null;
|
|
136
162
|
}
|
|
163
|
+
export interface AudioPlayEvent {
|
|
164
|
+
sound: string;
|
|
165
|
+
at?: readonly [number, number, number];
|
|
166
|
+
}
|
|
137
167
|
export interface GameEventMap {
|
|
138
168
|
"entity.died": EntityDiedEvent;
|
|
139
169
|
"entity.floatText": EntityFloatTextEvent;
|
|
140
170
|
"combat.telegraph": CombatTelegraphEvent;
|
|
171
|
+
"combat.telegraphCancelled": CombatTelegraphCancelledEvent;
|
|
141
172
|
"combat.hitReaction": CombatHitReactionEvent;
|
|
142
173
|
"loot.granted": LootGrantedEvent;
|
|
143
174
|
"inventory.added": InventoryAddedEvent;
|
|
@@ -147,6 +178,9 @@ export interface GameEventMap {
|
|
|
147
178
|
"social.friend.added": SocialFriendAddedEvent;
|
|
148
179
|
"social.party.joined": SocialPartyJoinedEvent;
|
|
149
180
|
"social.party.left": SocialPartyLeftEvent;
|
|
181
|
+
"social.world.invited": SocialWorldInvitedEvent;
|
|
182
|
+
"social.world.accepted": SocialWorldAcceptedEvent;
|
|
183
|
+
"chat.message": ChatMessageEvent;
|
|
150
184
|
"stat.levelUp": StatLevelUpEvent;
|
|
151
185
|
"projectile.settled": ProjectileSettledEvent;
|
|
152
186
|
"worldItem.dropped": WorldItemDroppedEvent;
|
|
@@ -155,6 +189,7 @@ export interface GameEventMap {
|
|
|
155
189
|
"emote.played": EmotePlayedEvent;
|
|
156
190
|
"possession.swapped": PossessionSwappedEvent;
|
|
157
191
|
"form.changed": FormChangedEvent;
|
|
192
|
+
"audio.play": AudioPlayEvent;
|
|
158
193
|
}
|
|
159
194
|
export type GameEventHandler<TPayload> = (payload: TPayload) => void;
|
|
160
195
|
export interface GameEvents<TMap extends GameEventMap = GameEventMap> {
|