@jgengine/core 0.6.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 +214 -2
- package/README.md +2 -0
- package/dist/ai/crowd.d.ts +36 -0
- package/dist/ai/crowd.js +213 -0
- package/dist/ai/jobBoard.d.ts +49 -0
- package/dist/ai/jobBoard.js +155 -0
- package/dist/ai/spawnDirector.d.ts +61 -0
- package/dist/ai/spawnDirector.js +162 -0
- package/dist/ai/threat.d.ts +25 -0
- package/dist/ai/threat.js +67 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/audio/audioFalloff.d.ts +38 -0
- package/dist/audio/audioFalloff.js +32 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/board/laneBoard.d.ts +63 -0
- package/dist/board/laneBoard.js +114 -0
- package/dist/board/timelineBoard.d.ts +36 -0
- package/dist/board/timelineBoard.js +70 -0
- package/dist/cards/cardPile.d.ts +64 -0
- package/dist/cards/cardPile.js +175 -0
- package/dist/cards/modifierPipeline.d.ts +33 -0
- package/dist/cards/modifierPipeline.js +48 -0
- package/dist/combat/abilityKit.d.ts +47 -0
- package/dist/combat/abilityKit.js +139 -0
- package/dist/combat/animationState.d.ts +47 -0
- package/dist/combat/animationState.js +101 -0
- package/dist/combat/attackTags.d.ts +14 -0
- package/dist/combat/attackTags.js +26 -0
- package/dist/combat/breakMeters.d.ts +32 -0
- package/dist/combat/breakMeters.js +51 -0
- package/dist/combat/comboString.d.ts +35 -0
- package/dist/combat/comboString.js +59 -0
- package/dist/combat/death.d.ts +8 -0
- package/dist/combat/death.js +5 -2
- package/dist/combat/defensiveWindow.d.ts +34 -0
- package/dist/combat/defensiveWindow.js +66 -0
- package/dist/combat/downed.d.ts +35 -0
- package/dist/combat/downed.js +104 -0
- package/dist/combat/effects.d.ts +12 -1
- package/dist/combat/effects.js +31 -19
- package/dist/combat/hitReaction.d.ts +23 -0
- package/dist/combat/hitReaction.js +20 -0
- package/dist/combat/projectiles.d.ts +42 -5
- package/dist/combat/projectiles.js +98 -12
- package/dist/combat/resistance.d.ts +13 -0
- package/dist/combat/resistance.js +46 -0
- package/dist/combat/telegraph.d.ts +31 -0
- package/dist/combat/telegraph.js +51 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/crop.d.ts +59 -0
- package/dist/crafting/crop.js +181 -0
- package/dist/crafting/production.d.ts +64 -0
- package/dist/crafting/production.js +130 -0
- package/dist/crafting/recipe.d.ts +62 -0
- package/dist/crafting/recipe.js +101 -0
- 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/economy/sharedWallet.d.ts +28 -0
- package/dist/economy/sharedWallet.js +62 -0
- package/dist/economy/techTree.d.ts +40 -0
- package/dist/economy/techTree.js +66 -0
- package/dist/game/chat.d.ts +66 -0
- package/dist/game/chat.js +165 -0
- package/dist/game/cosmetics.d.ts +25 -0
- package/dist/game/cosmetics.js +51 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +87 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootFilter.d.ts +33 -0
- package/dist/game/lootFilter.js +48 -0
- package/dist/game/ping.d.ts +67 -0
- package/dist/game/ping.js +86 -0
- package/dist/game/playableGame.d.ts +516 -8
- 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 +100 -0
- package/dist/game/race.js +236 -0
- package/dist/game/runDraft.d.ts +34 -0
- package/dist/game/runDraft.js +97 -0
- package/dist/game/social.d.ts +69 -0
- package/dist/game/social.js +138 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/worldItem.d.ts +95 -0
- package/dist/game/worldItem.js +138 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +43 -0
- package/dist/input/axisInput.js +70 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +69 -0
- package/dist/input/pointer.js +65 -0
- package/dist/input/touchScheme.d.ts +83 -0
- package/dist/input/touchScheme.js +115 -0
- package/dist/interaction/contextMenu.d.ts +30 -0
- package/dist/interaction/contextMenu.js +22 -0
- package/dist/interaction/qte.d.ts +20 -0
- package/dist/interaction/qte.js +24 -0
- package/dist/interaction/skillCheck.d.ts +20 -0
- package/dist/interaction/skillCheck.js +26 -0
- package/dist/inventory/shapedGrid.d.ts +56 -0
- package/dist/inventory/shapedGrid.js +176 -0
- package/dist/inventory/storageTier.d.ts +52 -0
- package/dist/inventory/storageTier.js +77 -0
- package/dist/item/affix.d.ts +54 -0
- package/dist/item/affix.js +115 -0
- package/dist/item/durability.d.ts +51 -0
- package/dist/item/durability.js +78 -0
- package/dist/item/modularItem.d.ts +46 -0
- package/dist/item/modularItem.js +75 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +71 -1
- package/dist/movement/dash.d.ts +37 -0
- package/dist/movement/dash.js +65 -0
- 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/combatSnapshot.d.ts +45 -0
- package/dist/multiplayer/combatSnapshot.js +89 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/lagCompensation.d.ts +43 -0
- package/dist/multiplayer/lagCompensation.js +154 -0
- package/dist/multiplayer/matchmaking.d.ts +33 -0
- package/dist/multiplayer/matchmaking.js +60 -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/simultaneousCommit.d.ts +34 -0
- package/dist/multiplayer/simultaneousCommit.js +53 -0
- 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 +56 -0
- package/dist/nav/navGrid.js +265 -0
- package/dist/nav/pathFollow.d.ts +31 -0
- package/dist/nav/pathFollow.js +69 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +49 -0
- package/dist/physics/buoyancy.js +112 -0
- package/dist/physics/carryable.d.ts +46 -0
- package/dist/physics/carryable.js +110 -0
- package/dist/physics/damageZones.d.ts +56 -0
- package/dist/physics/damageZones.js +107 -0
- package/dist/physics/forceVolume.d.ts +48 -0
- package/dist/physics/forceVolume.js +119 -0
- package/dist/physics/physicsWorld.d.ts +124 -2
- package/dist/physics/physicsWorld.js +493 -41
- package/dist/physics/ragdoll.d.ts +49 -0
- package/dist/physics/ragdoll.js +103 -0
- package/dist/physics/spatialGrid.d.ts +41 -0
- package/dist/physics/spatialGrid.js +143 -0
- package/dist/physics/structure.d.ts +83 -0
- package/dist/physics/structure.js +0 -0
- package/dist/physics/traversal.d.ts +75 -0
- package/dist/physics/traversal.js +138 -0
- package/dist/physics/vehicleBody.d.ts +99 -0
- package/dist/physics/vehicleBody.js +233 -0
- 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 +107 -3
- package/dist/runtime/gameContext.js +253 -7
- package/dist/runtime/hostPersistence.d.ts +39 -0
- package/dist/runtime/hostPersistence.js +54 -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/persistenceScope.d.ts +32 -0
- package/dist/runtime/persistenceScope.js +64 -0
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/scene/autoTarget.d.ts +15 -0
- package/dist/scene/autoTarget.js +54 -0
- package/dist/scene/behaviors.d.ts +13 -1
- package/dist/scene/behaviors.js +3 -0
- package/dist/scene/captureCheck.d.ts +7 -0
- package/dist/scene/captureCheck.js +11 -0
- package/dist/scene/entityStore.d.ts +9 -1
- package/dist/scene/entityStore.js +25 -2
- package/dist/scene/form.d.ts +40 -0
- package/dist/scene/form.js +56 -0
- package/dist/scene/mount.d.ts +62 -0
- package/dist/scene/mount.js +107 -0
- 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/possession.d.ts +30 -0
- package/dist/scene/possession.js +51 -0
- package/dist/scene/roster.d.ts +25 -0
- package/dist/scene/roster.js +57 -0
- package/dist/scene/selection.d.ts +30 -0
- package/dist/scene/selection.js +38 -0
- package/dist/scene/spatial.d.ts +21 -0
- package/dist/scene/spatial.js +63 -1
- package/dist/scene/stationClaim.d.ts +43 -0
- package/dist/scene/stationClaim.js +109 -0
- 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/frustumSensor.d.ts +60 -0
- package/dist/sensor/frustumSensor.js +104 -0
- package/dist/sensor/hiddenStateProbe.d.ts +31 -0
- package/dist/sensor/hiddenStateProbe.js +40 -0
- package/dist/sensor/recordingBuffer.d.ts +29 -0
- package/dist/sensor/recordingBuffer.js +61 -0
- package/dist/sensor/revealQuery.d.ts +22 -0
- package/dist/sensor/revealQuery.js +36 -0
- package/dist/session/contestedChannel.d.ts +36 -0
- package/dist/session/contestedChannel.js +100 -0
- package/dist/session/extraction.d.ts +54 -0
- package/dist/session/extraction.js +108 -0
- package/dist/session/ring.d.ts +36 -0
- package/dist/session/ring.js +88 -0
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +79 -0
- package/dist/session/roundState.js +158 -0
- package/dist/stats/accumulatorMeter.d.ts +31 -0
- package/dist/stats/accumulatorMeter.js +81 -0
- package/dist/stats/eventMeter.d.ts +34 -0
- package/dist/stats/eventMeter.js +76 -0
- package/dist/stats/rollCheck.d.ts +15 -0
- package/dist/stats/rollCheck.js +15 -0
- package/dist/survival/decayMeter.d.ts +59 -0
- package/dist/survival/decayMeter.js +95 -0
- package/dist/survival/moodle.d.ts +48 -0
- package/dist/survival/moodle.js +91 -0
- package/dist/survival/regionHealth.d.ts +72 -0
- package/dist/survival/regionHealth.js +129 -0
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/predictiveQuery.d.ts +34 -0
- package/dist/tactics/predictiveQuery.js +44 -0
- package/dist/tactics/snapshot.d.ts +18 -0
- package/dist/tactics/snapshot.js +65 -0
- package/dist/tactics/surface.d.ts +52 -0
- package/dist/tactics/surface.js +135 -0
- package/dist/tactics/tacticalGrid.d.ts +68 -0
- package/dist/tactics/tacticalGrid.js +261 -0
- package/dist/time/beatClock.d.ts +40 -0
- package/dist/time/beatClock.js +73 -0
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/turn/commit.d.ts +27 -0
- package/dist/turn/commit.js +72 -0
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +60 -0
- package/dist/turn/turnLoop.js +223 -0
- package/dist/world/buildPermissions.d.ts +47 -0
- package/dist/world/buildPermissions.js +134 -0
- package/dist/world/carve.d.ts +105 -0
- package/dist/world/carve.js +175 -0
- package/dist/world/connectors.d.ts +40 -0
- package/dist/world/connectors.js +67 -0
- package/dist/world/envField.d.ts +71 -0
- package/dist/world/envField.js +96 -0
- 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/fog.d.ts +40 -0
- package/dist/world/fog.js +137 -0
- package/dist/world/gridInstances.d.ts +13 -0
- package/dist/world/gridInstances.js +22 -0
- package/dist/world/markers.d.ts +52 -0
- package/dist/world/markers.js +96 -0
- package/dist/world/minimap.d.ts +42 -0
- package/dist/world/minimap.js +72 -0
- package/dist/world/placedStructureStore.d.ts +39 -0
- package/dist/world/placedStructureStore.js +95 -0
- package/dist/world/placementController.d.ts +69 -0
- package/dist/world/placementController.js +153 -0
- package/dist/world/realm.d.ts +61 -0
- package/dist/world/realm.js +66 -0
- package/dist/world/support.d.ts +27 -0
- package/dist/world/support.js +75 -0
- package/dist/world/terraform.d.ts +54 -0
- package/dist/world/terraform.js +186 -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/dist/world/walls.d.ts +61 -0
- package/dist/world/walls.js +221 -0
- package/dist/world/weather.d.ts +92 -0
- package/dist/world/weather.js +169 -0
- package/llms.txt +4102 -0
- package/package.json +3 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-finger play-surface gesture recognizer for touch controls: taps,
|
|
3
|
+
* directional flick swipes, and continuous step drags all resolve to action
|
|
4
|
+
* names from a TouchGestureBindings declaration. Drag steps stream out of
|
|
5
|
+
* move() as the finger travels; taps and swipes resolve on end(). Timestamps
|
|
6
|
+
* are injected so the state machine stays pure; the capture layer owns touch
|
|
7
|
+
* identifiers and pointer capture.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_GESTURE_TUNING = {
|
|
10
|
+
tapMoveThresholdPx: 12,
|
|
11
|
+
tapMaxMs: 280,
|
|
12
|
+
swipeMinPx: 48,
|
|
13
|
+
swipeMinVelocity: 0.45,
|
|
14
|
+
dragStepPx: 36,
|
|
15
|
+
};
|
|
16
|
+
export function createGestureSurfaceTracker(bindings, tuning = DEFAULT_GESTURE_TUNING) {
|
|
17
|
+
const drag = bindings.drag;
|
|
18
|
+
const dragStepPx = drag?.stepPx ?? tuning.dragStepPx;
|
|
19
|
+
const dragBound = {
|
|
20
|
+
horizontal: drag?.left !== undefined || drag?.right !== undefined,
|
|
21
|
+
vertical: drag?.up !== undefined || drag?.down !== undefined,
|
|
22
|
+
};
|
|
23
|
+
let active = false;
|
|
24
|
+
let startX = 0;
|
|
25
|
+
let startY = 0;
|
|
26
|
+
let lastX = 0;
|
|
27
|
+
let lastY = 0;
|
|
28
|
+
let startTime = 0;
|
|
29
|
+
let moved = false;
|
|
30
|
+
let axis = null;
|
|
31
|
+
let dragAccum = 0;
|
|
32
|
+
let dragStepsFired = 0;
|
|
33
|
+
const commitAxis = (totalDx, totalDy) => {
|
|
34
|
+
if (axis !== null)
|
|
35
|
+
return;
|
|
36
|
+
axis = Math.abs(totalDx) >= Math.abs(totalDy) ? "horizontal" : "vertical";
|
|
37
|
+
};
|
|
38
|
+
const swipeFor = (totalDx, totalDy) => {
|
|
39
|
+
if (axis === "horizontal") {
|
|
40
|
+
return totalDx >= 0 ? bindings.swipeRight : bindings.swipeLeft;
|
|
41
|
+
}
|
|
42
|
+
return totalDy >= 0 ? bindings.swipeDown : bindings.swipeUp;
|
|
43
|
+
};
|
|
44
|
+
const dragActionFor = (direction) => {
|
|
45
|
+
if (axis === "horizontal")
|
|
46
|
+
return direction >= 0 ? drag?.right : drag?.left;
|
|
47
|
+
return direction >= 0 ? drag?.down : drag?.up;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
begin(x, y, nowMs) {
|
|
51
|
+
active = true;
|
|
52
|
+
startX = lastX = x;
|
|
53
|
+
startY = lastY = y;
|
|
54
|
+
startTime = nowMs;
|
|
55
|
+
moved = false;
|
|
56
|
+
axis = null;
|
|
57
|
+
dragAccum = 0;
|
|
58
|
+
dragStepsFired = 0;
|
|
59
|
+
},
|
|
60
|
+
move(x, y) {
|
|
61
|
+
if (!active)
|
|
62
|
+
return [];
|
|
63
|
+
const totalDx = x - startX;
|
|
64
|
+
const totalDy = y - startY;
|
|
65
|
+
if (!moved && Math.hypot(totalDx, totalDy) > tuning.tapMoveThresholdPx) {
|
|
66
|
+
moved = true;
|
|
67
|
+
commitAxis(totalDx, totalDy);
|
|
68
|
+
}
|
|
69
|
+
const fired = [];
|
|
70
|
+
if (moved && axis !== null && dragBound[axis]) {
|
|
71
|
+
const delta = axis === "horizontal" ? x - lastX : y - lastY;
|
|
72
|
+
if (delta !== 0 && dragAccum !== 0 && Math.sign(delta) !== Math.sign(dragAccum))
|
|
73
|
+
dragAccum = 0;
|
|
74
|
+
dragAccum += delta;
|
|
75
|
+
while (Math.abs(dragAccum) >= dragStepPx) {
|
|
76
|
+
const direction = Math.sign(dragAccum);
|
|
77
|
+
const action = dragActionFor(direction);
|
|
78
|
+
dragAccum -= direction * dragStepPx;
|
|
79
|
+
if (action !== undefined) {
|
|
80
|
+
fired.push(action);
|
|
81
|
+
dragStepsFired += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
lastX = x;
|
|
86
|
+
lastY = y;
|
|
87
|
+
return fired;
|
|
88
|
+
},
|
|
89
|
+
end(x, y, nowMs) {
|
|
90
|
+
if (!active)
|
|
91
|
+
return [];
|
|
92
|
+
active = false;
|
|
93
|
+
const totalDx = x - startX;
|
|
94
|
+
const totalDy = y - startY;
|
|
95
|
+
const elapsed = nowMs - startTime;
|
|
96
|
+
if (!moved) {
|
|
97
|
+
if (elapsed <= tuning.tapMaxMs && bindings.tap !== undefined)
|
|
98
|
+
return [bindings.tap];
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const travel = Math.hypot(totalDx, totalDy);
|
|
102
|
+
const velocity = elapsed <= 0 ? Infinity : travel / elapsed;
|
|
103
|
+
if (travel < tuning.swipeMinPx || velocity < tuning.swipeMinVelocity)
|
|
104
|
+
return [];
|
|
105
|
+
const direction = axis === "horizontal" ? Math.sign(totalDx) : Math.sign(totalDy);
|
|
106
|
+
if (axis !== null && dragBound[axis] && dragActionFor(direction) !== undefined)
|
|
107
|
+
return [];
|
|
108
|
+
const swipe = swipeFor(totalDx, totalDy);
|
|
109
|
+
if (swipe === undefined || dragStepsFired > 0)
|
|
110
|
+
return [];
|
|
111
|
+
return [swipe];
|
|
112
|
+
},
|
|
113
|
+
cancel() {
|
|
114
|
+
active = false;
|
|
115
|
+
},
|
|
116
|
+
isActive() {
|
|
117
|
+
return active;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Aim } from "../scene/spatial.js";
|
|
2
|
+
export type PointerVec3 = readonly [number, number, number];
|
|
3
|
+
/**
|
|
4
|
+
* Renderer-free result of a screen→world raycast. The shell's pointer service
|
|
5
|
+
* produces this from the cursor; core-side gameplay (item.use aim, click-to-move,
|
|
6
|
+
* ground-target abilities, pings) consumes it without touching three.js.
|
|
7
|
+
*/
|
|
8
|
+
export interface PointerHit {
|
|
9
|
+
/** World-space point under the cursor (surface or ground plane). */
|
|
10
|
+
point: PointerVec3;
|
|
11
|
+
/** World-space surface normal at the hit; `[0, 1, 0]` for the ground plane. */
|
|
12
|
+
normal: PointerVec3;
|
|
13
|
+
/** Topmost scene-entity instance id under the cursor, or null over open ground. */
|
|
14
|
+
entity: string | null;
|
|
15
|
+
/** Topmost scene-object instance id under the cursor, or null. */
|
|
16
|
+
object: string | null;
|
|
17
|
+
/** Texture-space UV at the hit, when the intersected mesh carries UVs; absent for the ground-plane fallback. */
|
|
18
|
+
uv?: {
|
|
19
|
+
u: number;
|
|
20
|
+
v: number;
|
|
21
|
+
};
|
|
22
|
+
/** Sampled `#rrggbb` color + PBR params from the hit mesh's `MeshStandardMaterial` (#151.2); `null`/unset when the hit surface has no standard material (e.g. the ground plane). */
|
|
23
|
+
material?: {
|
|
24
|
+
color: string;
|
|
25
|
+
metalness?: number;
|
|
26
|
+
roughness?: number;
|
|
27
|
+
} | null;
|
|
28
|
+
/** Index of the hit instance when the intersected mesh is a `THREE.InstancedMesh`; absent otherwise. */
|
|
29
|
+
instanceId?: number;
|
|
30
|
+
}
|
|
31
|
+
export type PointerButton = "primary" | "secondary" | "middle";
|
|
32
|
+
/** Build an `origin → point` aim for `item.use` / projectiles, firing toward the cursor. */
|
|
33
|
+
export declare function aimToPoint(origin: PointerVec3, point: PointerVec3): Aim;
|
|
34
|
+
/** The move-to target of a pointer hit — sugar over `hit.point`. */
|
|
35
|
+
export declare function moveTargetFromHit(hit: PointerHit): PointerVec3;
|
|
36
|
+
/** Project a pointer hit onto the XZ plane for navmesh routing (`findPath` takes `[x, z]`). */
|
|
37
|
+
export declare function groundOf(hit: PointerHit): readonly [number, number];
|
|
38
|
+
/** Snapshot of an in-progress or just-released drag: origin/current cursor points plus the clamped pull vector. */
|
|
39
|
+
export interface DragState {
|
|
40
|
+
origin: PointerVec3;
|
|
41
|
+
current: PointerVec3;
|
|
42
|
+
/** `current - origin`, clamped to `maxPull` in length. */
|
|
43
|
+
pull: PointerVec3;
|
|
44
|
+
/** Length of `pull`, at most `maxPull`. */
|
|
45
|
+
magnitude: number;
|
|
46
|
+
/** `magnitude / maxPull`, in `[0, 1]`. `0` when `maxPull` is unset. */
|
|
47
|
+
fraction: number;
|
|
48
|
+
}
|
|
49
|
+
export type DragResult = DragState;
|
|
50
|
+
export interface DragCaptureConfig {
|
|
51
|
+
/** Clamp on pull-vector length. Unset (default) means unclamped. */
|
|
52
|
+
maxPull?: number;
|
|
53
|
+
/** Max distance from `origin` a `begin()` point may be to start a drag. Unset (default) means unrestricted. */
|
|
54
|
+
grabRadius?: number;
|
|
55
|
+
}
|
|
56
|
+
/** Renderer-agnostic drag-capture / pull-vector state machine for slingshot-style aiming, drawback abilities, and similar gestures. */
|
|
57
|
+
export interface DragCapture {
|
|
58
|
+
/** Start a drag at `origin` if `at` is within `grabRadius` of it. Returns whether the drag started. */
|
|
59
|
+
begin(origin: PointerVec3, at: PointerVec3): boolean;
|
|
60
|
+
/** Move the drag's current point. No-op when no drag is active. */
|
|
61
|
+
update(at: PointerVec3): void;
|
|
62
|
+
/** End the drag, returning its final state, or `null` if no drag was active. */
|
|
63
|
+
release(): DragResult | null;
|
|
64
|
+
/** Abort the drag without producing a result. */
|
|
65
|
+
cancel(): void;
|
|
66
|
+
/** The current drag state, or `null` when idle. */
|
|
67
|
+
state(): DragState | null;
|
|
68
|
+
}
|
|
69
|
+
export declare function createDragCapture(config?: DragCaptureConfig): DragCapture;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Build an `origin → point` aim for `item.use` / projectiles, firing toward the cursor. */
|
|
2
|
+
export function aimToPoint(origin, point) {
|
|
3
|
+
const dx = point[0] - origin[0];
|
|
4
|
+
const dy = point[1] - origin[1];
|
|
5
|
+
const dz = point[2] - origin[2];
|
|
6
|
+
const length = Math.hypot(dx, dy, dz);
|
|
7
|
+
const direction = length < 1e-9 ? [0, 0, 1] : [dx / length, dy / length, dz / length];
|
|
8
|
+
return { origin, direction };
|
|
9
|
+
}
|
|
10
|
+
/** The move-to target of a pointer hit — sugar over `hit.point`. */
|
|
11
|
+
export function moveTargetFromHit(hit) {
|
|
12
|
+
return hit.point;
|
|
13
|
+
}
|
|
14
|
+
/** Project a pointer hit onto the XZ plane for navmesh routing (`findPath` takes `[x, z]`). */
|
|
15
|
+
export function groundOf(hit) {
|
|
16
|
+
return [hit.point[0], hit.point[2]];
|
|
17
|
+
}
|
|
18
|
+
function subtractVec3(a, b) {
|
|
19
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
20
|
+
}
|
|
21
|
+
function lengthVec3(v) {
|
|
22
|
+
return Math.hypot(v[0], v[1], v[2]);
|
|
23
|
+
}
|
|
24
|
+
export function createDragCapture(config = {}) {
|
|
25
|
+
const maxPull = config.maxPull ?? Number.POSITIVE_INFINITY;
|
|
26
|
+
const grabRadius = config.grabRadius ?? Number.POSITIVE_INFINITY;
|
|
27
|
+
let origin = null;
|
|
28
|
+
let current = null;
|
|
29
|
+
function snapshot() {
|
|
30
|
+
if (origin === null || current === null)
|
|
31
|
+
return null;
|
|
32
|
+
const offset = subtractVec3(current, origin);
|
|
33
|
+
const rawLength = lengthVec3(offset);
|
|
34
|
+
const scale = rawLength > maxPull && rawLength > 0 ? maxPull / rawLength : 1;
|
|
35
|
+
const pull = [offset[0] * scale, offset[1] * scale, offset[2] * scale];
|
|
36
|
+
const magnitude = Math.min(rawLength, maxPull);
|
|
37
|
+
const fraction = Number.isFinite(maxPull) && maxPull > 0 ? magnitude / maxPull : 0;
|
|
38
|
+
return { origin, current, pull, magnitude, fraction };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
begin(dragOrigin, at) {
|
|
42
|
+
if (lengthVec3(subtractVec3(at, dragOrigin)) > grabRadius)
|
|
43
|
+
return false;
|
|
44
|
+
origin = dragOrigin;
|
|
45
|
+
current = at;
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
update(at) {
|
|
49
|
+
if (origin === null)
|
|
50
|
+
return;
|
|
51
|
+
current = at;
|
|
52
|
+
},
|
|
53
|
+
release() {
|
|
54
|
+
const result = snapshot();
|
|
55
|
+
origin = null;
|
|
56
|
+
current = null;
|
|
57
|
+
return result;
|
|
58
|
+
},
|
|
59
|
+
cancel() {
|
|
60
|
+
origin = null;
|
|
61
|
+
current = null;
|
|
62
|
+
},
|
|
63
|
+
state: snapshot,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Touch control scheme derived from a game's existing action bindings: the
|
|
3
|
+
* capture layer renders a virtual joystick, on-screen buttons, and a gesture
|
|
4
|
+
* surface, and feeds synthetic `touch:<action>` codes into the same
|
|
5
|
+
* ActionStateTracker the keyboard uses, so games gain touch play without any
|
|
6
|
+
* per-game input code. Games refine the derived defaults through
|
|
7
|
+
* TouchControlsConfig (gesture bindings, button curation) or opt out entirely
|
|
8
|
+
* with `touch: false`.
|
|
9
|
+
*/
|
|
10
|
+
import { type ActionCodesMap } from "./actionBindings.js";
|
|
11
|
+
export declare const TOUCH_CODE_PREFIX = "touch:";
|
|
12
|
+
export declare function touchCode(action: string): string;
|
|
13
|
+
/** Every action gains a synthetic touch code alongside its physical codes. */
|
|
14
|
+
export declare function withTouchCodes(map: ActionCodesMap | undefined): ActionCodesMap;
|
|
15
|
+
export interface TouchDragBinding {
|
|
16
|
+
/** Action fired once per `stepPx` of travel toward that side. */
|
|
17
|
+
left?: string;
|
|
18
|
+
right?: string;
|
|
19
|
+
up?: string;
|
|
20
|
+
down?: string;
|
|
21
|
+
stepPx?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface TouchGestureBindings {
|
|
24
|
+
/** Action fired by a quick still tap on the play surface. */
|
|
25
|
+
tap?: string;
|
|
26
|
+
/** Actions fired by a fast flick past the swipe threshold. */
|
|
27
|
+
swipeUp?: string;
|
|
28
|
+
swipeDown?: string;
|
|
29
|
+
swipeLeft?: string;
|
|
30
|
+
swipeRight?: string;
|
|
31
|
+
/** Continuous drag: repeats the directional action every `stepPx` of travel. Wins over swipes on its axis, except a flick can still fire the swipe binding for a direction the drag leaves unbound. */
|
|
32
|
+
drag?: TouchDragBinding;
|
|
33
|
+
}
|
|
34
|
+
export interface TouchButtonSpec {
|
|
35
|
+
action: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Glyph name from the capture layer's icon catalog (`@jgengine/react/gameIcons` `GameIconName`); unset derives one from the action name, `false` forces the text label. */
|
|
38
|
+
icon?: string | false;
|
|
39
|
+
}
|
|
40
|
+
export interface TouchControlsConfig {
|
|
41
|
+
/** `false` removes the virtual joystick even when movement actions are bound. */
|
|
42
|
+
movement?: false;
|
|
43
|
+
/** Curated on-screen buttons (order preserved); omit to derive one button per remaining action. */
|
|
44
|
+
buttons?: readonly (string | TouchButtonSpec)[];
|
|
45
|
+
/** Play-surface gestures; actions consumed here stop appearing as derived buttons. */
|
|
46
|
+
gestures?: TouchGestureBindings;
|
|
47
|
+
/** Drag-to-look on the play surface; defaults to true for first-person games. */
|
|
48
|
+
look?: boolean;
|
|
49
|
+
/** Radians of look rotation per pixel of touch travel. Default 0.005. */
|
|
50
|
+
lookSensitivity?: number;
|
|
51
|
+
/** Actions to leave off the derived button cluster. */
|
|
52
|
+
hidden?: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
export interface TouchJoystick {
|
|
55
|
+
up: string | null;
|
|
56
|
+
down: string | null;
|
|
57
|
+
left: string | null;
|
|
58
|
+
right: string | null;
|
|
59
|
+
}
|
|
60
|
+
export interface TouchButton {
|
|
61
|
+
action: string;
|
|
62
|
+
label: string;
|
|
63
|
+
/** Glyph hint for the capture layer: a name to render, `false` to force text, null to derive from the action name. */
|
|
64
|
+
icon: string | false | null;
|
|
65
|
+
}
|
|
66
|
+
export interface TouchScheme {
|
|
67
|
+
joystick: TouchJoystick | null;
|
|
68
|
+
buttons: readonly TouchButton[];
|
|
69
|
+
gestures: TouchGestureBindings | null;
|
|
70
|
+
look: boolean;
|
|
71
|
+
lookSensitivity: number;
|
|
72
|
+
}
|
|
73
|
+
export declare function touchActionLabel(action: string): string;
|
|
74
|
+
export interface DeriveTouchSchemeOptions {
|
|
75
|
+
reserved: ReadonlySet<string>;
|
|
76
|
+
firstPerson: boolean;
|
|
77
|
+
config?: TouchControlsConfig | false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Null means "render no touch controls" — either the game opted out or there
|
|
81
|
+
* is nothing to synthesize.
|
|
82
|
+
*/
|
|
83
|
+
export declare function deriveTouchScheme(input: ActionCodesMap | undefined, { reserved, firstPerson, config }: DeriveTouchSchemeOptions): TouchScheme | null;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Touch control scheme derived from a game's existing action bindings: the
|
|
3
|
+
* capture layer renders a virtual joystick, on-screen buttons, and a gesture
|
|
4
|
+
* surface, and feeds synthetic `touch:<action>` codes into the same
|
|
5
|
+
* ActionStateTracker the keyboard uses, so games gain touch play without any
|
|
6
|
+
* per-game input code. Games refine the derived defaults through
|
|
7
|
+
* TouchControlsConfig (gesture bindings, button curation) or opt out entirely
|
|
8
|
+
* with `touch: false`.
|
|
9
|
+
*/
|
|
10
|
+
import { isHotbarSlotAction } from "./actionBindings.js";
|
|
11
|
+
export const TOUCH_CODE_PREFIX = "touch:";
|
|
12
|
+
export function touchCode(action) {
|
|
13
|
+
return `${TOUCH_CODE_PREFIX}${action}`;
|
|
14
|
+
}
|
|
15
|
+
/** Every action gains a synthetic touch code alongside its physical codes. */
|
|
16
|
+
export function withTouchCodes(map) {
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const action of Object.keys(map ?? {})) {
|
|
19
|
+
const codes = map[action];
|
|
20
|
+
const synthetic = touchCode(action);
|
|
21
|
+
if (Array.isArray(codes)) {
|
|
22
|
+
result[action] = [...codes, synthetic];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const modes = codes;
|
|
26
|
+
const toggleOnly = (modes.toggle?.length ?? 0) > 0 && (modes.hold?.length ?? 0) === 0;
|
|
27
|
+
result[action] = toggleOnly
|
|
28
|
+
? { hold: modes.hold, toggle: [...(modes.toggle ?? []), synthetic] }
|
|
29
|
+
: { hold: [...(modes.hold ?? []), synthetic], toggle: modes.toggle };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
const DEFAULT_LOOK_SENSITIVITY = 0.005;
|
|
35
|
+
const JOYSTICK_AXES = {
|
|
36
|
+
up: ["moveForward"],
|
|
37
|
+
down: ["moveBack"],
|
|
38
|
+
left: ["moveLeft", "turnLeft"],
|
|
39
|
+
right: ["moveRight", "turnRight"],
|
|
40
|
+
};
|
|
41
|
+
const BUTTONABLE_RESERVED = new Set(["jump", "sprint", "interact", "useAbility"]);
|
|
42
|
+
export function touchActionLabel(action) {
|
|
43
|
+
const spaced = action.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
44
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
45
|
+
}
|
|
46
|
+
function gestureActions(gestures) {
|
|
47
|
+
const used = new Set();
|
|
48
|
+
if (gestures === undefined)
|
|
49
|
+
return used;
|
|
50
|
+
for (const action of [gestures.tap, gestures.swipeUp, gestures.swipeDown, gestures.swipeLeft, gestures.swipeRight]) {
|
|
51
|
+
if (action !== undefined)
|
|
52
|
+
used.add(action);
|
|
53
|
+
}
|
|
54
|
+
const drag = gestures.drag;
|
|
55
|
+
for (const action of [drag?.left, drag?.right, drag?.up, drag?.down]) {
|
|
56
|
+
if (action !== undefined)
|
|
57
|
+
used.add(action);
|
|
58
|
+
}
|
|
59
|
+
return used;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Null means "render no touch controls" — either the game opted out or there
|
|
63
|
+
* is nothing to synthesize.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveTouchScheme(input, { reserved, firstPerson, config }) {
|
|
66
|
+
if (config === false)
|
|
67
|
+
return null;
|
|
68
|
+
const actions = Object.keys(input ?? {});
|
|
69
|
+
const bound = new Set(actions);
|
|
70
|
+
const joystickMapped = new Set();
|
|
71
|
+
let joystick = null;
|
|
72
|
+
if (config?.movement !== false) {
|
|
73
|
+
const resolveAxis = (candidates) => {
|
|
74
|
+
const action = candidates.find((candidate) => bound.has(candidate)) ?? null;
|
|
75
|
+
if (action !== null)
|
|
76
|
+
joystickMapped.add(action);
|
|
77
|
+
return action;
|
|
78
|
+
};
|
|
79
|
+
const axes = {
|
|
80
|
+
up: resolveAxis(JOYSTICK_AXES.up),
|
|
81
|
+
down: resolveAxis(JOYSTICK_AXES.down),
|
|
82
|
+
left: resolveAxis(JOYSTICK_AXES.left),
|
|
83
|
+
right: resolveAxis(JOYSTICK_AXES.right),
|
|
84
|
+
};
|
|
85
|
+
joystick = axes.up === null && axes.down === null && axes.left === null && axes.right === null ? null : axes;
|
|
86
|
+
}
|
|
87
|
+
const gestures = config?.gestures ?? null;
|
|
88
|
+
const consumedByGestures = gestureActions(config?.gestures);
|
|
89
|
+
const hidden = new Set(config?.hidden ?? []);
|
|
90
|
+
let buttons;
|
|
91
|
+
if (config?.buttons !== undefined) {
|
|
92
|
+
buttons = config.buttons.map((spec) => typeof spec === "string"
|
|
93
|
+
? { action: spec, label: touchActionLabel(spec), icon: null }
|
|
94
|
+
: { action: spec.action, label: spec.label ?? touchActionLabel(spec.action), icon: spec.icon ?? null });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
buttons = actions
|
|
98
|
+
.filter((action) => !joystickMapped.has(action))
|
|
99
|
+
.filter((action) => !isHotbarSlotAction(action))
|
|
100
|
+
.filter((action) => !consumedByGestures.has(action))
|
|
101
|
+
.filter((action) => !hidden.has(action))
|
|
102
|
+
.filter((action) => !reserved.has(action) || BUTTONABLE_RESERVED.has(action))
|
|
103
|
+
.map((action) => ({ action, label: touchActionLabel(action), icon: null }));
|
|
104
|
+
}
|
|
105
|
+
const look = config?.look ?? firstPerson;
|
|
106
|
+
if (joystick === null && buttons.length === 0 && gestures === null && !look)
|
|
107
|
+
return null;
|
|
108
|
+
return {
|
|
109
|
+
joystick,
|
|
110
|
+
buttons,
|
|
111
|
+
gestures,
|
|
112
|
+
look,
|
|
113
|
+
lookSensitivity: config?.lookSensitivity ?? DEFAULT_LOOK_SENSITIVITY,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ContextTargetKind = "entity" | "object";
|
|
2
|
+
/** One right-click verb: a label plus the command it dispatches (walk-then-act supported by args). */
|
|
3
|
+
export interface ContextVerb {
|
|
4
|
+
label: string;
|
|
5
|
+
command: string;
|
|
6
|
+
args?: Record<string, unknown>;
|
|
7
|
+
/** Greyed-out and non-dispatchable when true (e.g. requirement unmet). */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function contextVerb(label: string, command: string, args?: Record<string, unknown>): ContextVerb;
|
|
11
|
+
export interface ContextMenu {
|
|
12
|
+
kind: ContextTargetKind;
|
|
13
|
+
targetId: string;
|
|
14
|
+
/** World point the click landed on, forwarded to dispatched commands for walk-then-act. */
|
|
15
|
+
point?: readonly [number, number, number];
|
|
16
|
+
verbs: readonly ContextVerb[];
|
|
17
|
+
}
|
|
18
|
+
export interface BuildContextMenuInput {
|
|
19
|
+
kind: ContextTargetKind;
|
|
20
|
+
targetId: string;
|
|
21
|
+
verbs: readonly ContextVerb[] | undefined;
|
|
22
|
+
point?: readonly [number, number, number];
|
|
23
|
+
}
|
|
24
|
+
/** Assemble a menu from a target's catalog verbs; null when the target lists none. */
|
|
25
|
+
export declare function buildContextMenu(input: BuildContextMenuInput): ContextMenu | null;
|
|
26
|
+
/**
|
|
27
|
+
* Command input a chosen verb dispatches: the verb's own args, plus the target id and
|
|
28
|
+
* the world point, so a single handler can walk the actor to the target then perform it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function contextVerbInput(menu: ContextMenu, verb: ContextVerb): Record<string, unknown>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function contextVerb(label, command, args) {
|
|
2
|
+
return args === undefined ? { label, command } : { label, command, args };
|
|
3
|
+
}
|
|
4
|
+
/** Assemble a menu from a target's catalog verbs; null when the target lists none. */
|
|
5
|
+
export function buildContextMenu(input) {
|
|
6
|
+
if (input.verbs === undefined || input.verbs.length === 0)
|
|
7
|
+
return null;
|
|
8
|
+
const menu = { kind: input.kind, targetId: input.targetId, verbs: input.verbs };
|
|
9
|
+
if (input.point !== undefined)
|
|
10
|
+
menu.point = input.point;
|
|
11
|
+
return menu;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Command input a chosen verb dispatches: the verb's own args, plus the target id and
|
|
15
|
+
* the world point, so a single handler can walk the actor to the target then perform it.
|
|
16
|
+
*/
|
|
17
|
+
export function contextVerbInput(menu, verb) {
|
|
18
|
+
const base = { ...verb.args, target: menu.targetId, targetKind: menu.kind };
|
|
19
|
+
if (menu.point !== undefined)
|
|
20
|
+
base.point = menu.point;
|
|
21
|
+
return base;
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface QteStep {
|
|
2
|
+
id: string;
|
|
3
|
+
action: string;
|
|
4
|
+
windowStart: number;
|
|
5
|
+
windowEnd: number;
|
|
6
|
+
}
|
|
7
|
+
export interface QteInputEvent {
|
|
8
|
+
action: string;
|
|
9
|
+
at: number;
|
|
10
|
+
}
|
|
11
|
+
export type QteOutcome = {
|
|
12
|
+
status: "success";
|
|
13
|
+
} | {
|
|
14
|
+
status: "fail";
|
|
15
|
+
atStep: string;
|
|
16
|
+
reason: "missed-window" | "wrong-action" | "too-early";
|
|
17
|
+
};
|
|
18
|
+
export declare function evaluateQteSequence(steps: readonly QteStep[], inputs: readonly QteInputEvent[]): QteOutcome;
|
|
19
|
+
export declare function pendingQteStep(steps: readonly QteStep[], elapsedSeconds: number): QteStep | null;
|
|
20
|
+
export declare function qteProgress(steps: readonly QteStep[], elapsedSeconds: number): number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function evaluateQteSequence(steps, inputs) {
|
|
2
|
+
for (let index = 0; index < steps.length; index += 1) {
|
|
3
|
+
const step = steps[index];
|
|
4
|
+
const input = inputs[index];
|
|
5
|
+
if (input === undefined)
|
|
6
|
+
return { status: "fail", atStep: step.id, reason: "missed-window" };
|
|
7
|
+
if (input.at < step.windowStart)
|
|
8
|
+
return { status: "fail", atStep: step.id, reason: "too-early" };
|
|
9
|
+
if (input.at > step.windowEnd)
|
|
10
|
+
return { status: "fail", atStep: step.id, reason: "missed-window" };
|
|
11
|
+
if (input.action !== step.action)
|
|
12
|
+
return { status: "fail", atStep: step.id, reason: "wrong-action" };
|
|
13
|
+
}
|
|
14
|
+
return { status: "success" };
|
|
15
|
+
}
|
|
16
|
+
export function pendingQteStep(steps, elapsedSeconds) {
|
|
17
|
+
return (steps.find((step) => elapsedSeconds >= step.windowStart && elapsedSeconds <= step.windowEnd) ?? null);
|
|
18
|
+
}
|
|
19
|
+
export function qteProgress(steps, elapsedSeconds) {
|
|
20
|
+
if (steps.length === 0)
|
|
21
|
+
return 1;
|
|
22
|
+
const passed = steps.filter((step) => elapsedSeconds > step.windowEnd).length;
|
|
23
|
+
return passed / steps.length;
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface SkillCheckZone {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
}
|
|
5
|
+
export interface SkillCheckConfig {
|
|
6
|
+
trackWidth: number;
|
|
7
|
+
zone: SkillCheckZone;
|
|
8
|
+
markerPeriod: number;
|
|
9
|
+
window: number;
|
|
10
|
+
zoneDriftPerSecond?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SkillCheckResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
timedOut: boolean;
|
|
15
|
+
markerPosition: number;
|
|
16
|
+
zone: SkillCheckZone;
|
|
17
|
+
}
|
|
18
|
+
export declare function skillCheckMarkerPosition(config: SkillCheckConfig, elapsedSeconds: number): number;
|
|
19
|
+
export declare function skillCheckZoneAt(config: SkillCheckConfig, elapsedSeconds: number): SkillCheckZone;
|
|
20
|
+
export declare function evaluateSkillCheck(config: SkillCheckConfig, elapsedSeconds: number): SkillCheckResult;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function bounce(t, period, max) {
|
|
2
|
+
if (period <= 0 || max <= 0)
|
|
3
|
+
return 0;
|
|
4
|
+
const cycle = max * 2;
|
|
5
|
+
const phase = ((t % period) / period) * cycle;
|
|
6
|
+
return phase <= max ? phase : cycle - phase;
|
|
7
|
+
}
|
|
8
|
+
export function skillCheckMarkerPosition(config, elapsedSeconds) {
|
|
9
|
+
return bounce(Math.max(0, elapsedSeconds), config.markerPeriod, config.trackWidth);
|
|
10
|
+
}
|
|
11
|
+
export function skillCheckZoneAt(config, elapsedSeconds) {
|
|
12
|
+
const drift = config.zoneDriftPerSecond ?? 0;
|
|
13
|
+
if (drift === 0)
|
|
14
|
+
return config.zone;
|
|
15
|
+
const width = config.zone.end - config.zone.start;
|
|
16
|
+
const travel = Math.max(0, config.trackWidth - width);
|
|
17
|
+
const start = bounce(Math.max(0, elapsedSeconds) * Math.abs(drift), config.markerPeriod, travel);
|
|
18
|
+
return { start, end: start + width };
|
|
19
|
+
}
|
|
20
|
+
export function evaluateSkillCheck(config, elapsedSeconds) {
|
|
21
|
+
const timedOut = elapsedSeconds > config.window;
|
|
22
|
+
const markerPosition = skillCheckMarkerPosition(config, elapsedSeconds);
|
|
23
|
+
const zone = skillCheckZoneAt(config, elapsedSeconds);
|
|
24
|
+
const inZone = markerPosition >= zone.start && markerPosition <= zone.end;
|
|
25
|
+
return { success: !timedOut && inZone, timedOut, markerPosition, zone };
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type Cell = readonly [number, number];
|
|
2
|
+
export type Footprint = readonly Cell[];
|
|
3
|
+
export type Rotation = 0 | 1 | 2 | 3;
|
|
4
|
+
export interface ShapedItem<T> {
|
|
5
|
+
id: string;
|
|
6
|
+
value: T;
|
|
7
|
+
footprint: Footprint;
|
|
8
|
+
}
|
|
9
|
+
export interface Placement<T> {
|
|
10
|
+
id: string;
|
|
11
|
+
value: T;
|
|
12
|
+
footprint: Footprint;
|
|
13
|
+
origin: Cell;
|
|
14
|
+
rotation: Rotation;
|
|
15
|
+
}
|
|
16
|
+
export interface ShapedGrid<T> {
|
|
17
|
+
readonly width: number;
|
|
18
|
+
readonly height: number;
|
|
19
|
+
readonly placements: readonly Placement<T>[];
|
|
20
|
+
}
|
|
21
|
+
export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id";
|
|
22
|
+
export interface ShapedPlaceResult<T> {
|
|
23
|
+
status: "ok";
|
|
24
|
+
grid: ShapedGrid<T>;
|
|
25
|
+
}
|
|
26
|
+
export interface ShapedRejected {
|
|
27
|
+
status: "rejected";
|
|
28
|
+
reason: ShapedRejection;
|
|
29
|
+
detail?: string;
|
|
30
|
+
}
|
|
31
|
+
export type ShapedResult<T> = ShapedPlaceResult<T> | ShapedRejected;
|
|
32
|
+
export declare function normalizeFootprint(footprint: Footprint): Footprint;
|
|
33
|
+
export declare function rotateFootprint(footprint: Footprint, rotation: Rotation): Footprint;
|
|
34
|
+
export declare function occupiedCells(footprint: Footprint, origin: Cell, rotation: Rotation): Cell[];
|
|
35
|
+
export declare function createShapedGrid<T>(width: number, height: number): ShapedGrid<T>;
|
|
36
|
+
export declare function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null;
|
|
37
|
+
export declare function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation?: Rotation): ShapedResult<T>;
|
|
38
|
+
export declare function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T>;
|
|
39
|
+
export declare function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T>;
|
|
40
|
+
export declare function cellOccupant<T>(grid: ShapedGrid<T>, cell: Cell): string | null;
|
|
41
|
+
export interface GridAdjacencyQuery {
|
|
42
|
+
cellsOf(id: string): readonly Cell[];
|
|
43
|
+
neighborsOf(id: string): readonly string[];
|
|
44
|
+
touching(idA: string, idB: string): boolean;
|
|
45
|
+
adjacentCells(cells: readonly Cell[]): readonly Cell[];
|
|
46
|
+
}
|
|
47
|
+
export declare function gridAdjacencyQuery<T>(grid: ShapedGrid<T>, options?: {
|
|
48
|
+
diagonal?: boolean;
|
|
49
|
+
}): GridAdjacencyQuery;
|
|
50
|
+
export declare function cellFromPoint(point: {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
}, cellSize: number, gridOrigin?: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
}): Cell;
|