@quake2ts/shared 0.0.1
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/dist/browser/index.global.js +2 -0
- package/dist/browser/index.global.js.map +1 -0
- package/dist/cjs/index.cjs +6569 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/index.js +6200 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/audio/constants.d.ts +24 -0
- package/dist/types/audio/constants.d.ts.map +1 -0
- package/dist/types/bsp/collision.d.ts +201 -0
- package/dist/types/bsp/collision.d.ts.map +1 -0
- package/dist/types/bsp/contents.d.ts +72 -0
- package/dist/types/bsp/contents.d.ts.map +1 -0
- package/dist/types/bsp/spatial.d.ts +13 -0
- package/dist/types/bsp/spatial.d.ts.map +1 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/inventory-helpers.d.ts +19 -0
- package/dist/types/inventory-helpers.d.ts.map +1 -0
- package/dist/types/io/binaryStream.d.ts +38 -0
- package/dist/types/io/binaryStream.d.ts.map +1 -0
- package/dist/types/io/binaryWriter.d.ts +26 -0
- package/dist/types/io/binaryWriter.d.ts.map +1 -0
- package/dist/types/io/index.d.ts +4 -0
- package/dist/types/io/index.d.ts.map +1 -0
- package/dist/types/io/messageBuilder.d.ts +21 -0
- package/dist/types/io/messageBuilder.d.ts.map +1 -0
- package/dist/types/items/ammo.d.ts +40 -0
- package/dist/types/items/ammo.d.ts.map +1 -0
- package/dist/types/items/index.d.ts +8 -0
- package/dist/types/items/index.d.ts.map +1 -0
- package/dist/types/items/powerups.d.ts +31 -0
- package/dist/types/items/powerups.d.ts.map +1 -0
- package/dist/types/items/weaponInfo.d.ts +5 -0
- package/dist/types/items/weaponInfo.d.ts.map +1 -0
- package/dist/types/items/weapons.d.ts +27 -0
- package/dist/types/items/weapons.d.ts.map +1 -0
- package/dist/types/math/angles.d.ts +19 -0
- package/dist/types/math/angles.d.ts.map +1 -0
- package/dist/types/math/anorms.d.ts +2 -0
- package/dist/types/math/anorms.d.ts.map +1 -0
- package/dist/types/math/color.d.ts +12 -0
- package/dist/types/math/color.d.ts.map +1 -0
- package/dist/types/math/mat4.d.ts +7 -0
- package/dist/types/math/mat4.d.ts.map +1 -0
- package/dist/types/math/random.d.ts +60 -0
- package/dist/types/math/random.d.ts.map +1 -0
- package/dist/types/math/vec3.d.ts +79 -0
- package/dist/types/math/vec3.d.ts.map +1 -0
- package/dist/types/net/driver.d.ts +10 -0
- package/dist/types/net/driver.d.ts.map +1 -0
- package/dist/types/net/index.d.ts +3 -0
- package/dist/types/net/index.d.ts.map +1 -0
- package/dist/types/net/netchan.d.ts +85 -0
- package/dist/types/net/netchan.d.ts.map +1 -0
- package/dist/types/pmove/apply.d.ts +5 -0
- package/dist/types/pmove/apply.d.ts.map +1 -0
- package/dist/types/pmove/categorize.d.ts +36 -0
- package/dist/types/pmove/categorize.d.ts.map +1 -0
- package/dist/types/pmove/config.d.ts +5 -0
- package/dist/types/pmove/config.d.ts.map +1 -0
- package/dist/types/pmove/constants.d.ts +76 -0
- package/dist/types/pmove/constants.d.ts.map +1 -0
- package/dist/types/pmove/currents.d.ts +58 -0
- package/dist/types/pmove/currents.d.ts.map +1 -0
- package/dist/types/pmove/dimensions.d.ts +14 -0
- package/dist/types/pmove/dimensions.d.ts.map +1 -0
- package/dist/types/pmove/duck.d.ts +39 -0
- package/dist/types/pmove/duck.d.ts.map +1 -0
- package/dist/types/pmove/fly.d.ts +34 -0
- package/dist/types/pmove/fly.d.ts.map +1 -0
- package/dist/types/pmove/index.d.ts +18 -0
- package/dist/types/pmove/index.d.ts.map +1 -0
- package/dist/types/pmove/jump.d.ts +28 -0
- package/dist/types/pmove/jump.d.ts.map +1 -0
- package/dist/types/pmove/move.d.ts +78 -0
- package/dist/types/pmove/move.d.ts.map +1 -0
- package/dist/types/pmove/pmove.d.ts +40 -0
- package/dist/types/pmove/pmove.d.ts.map +1 -0
- package/dist/types/pmove/slide.d.ts +63 -0
- package/dist/types/pmove/slide.d.ts.map +1 -0
- package/dist/types/pmove/snap.d.ts +40 -0
- package/dist/types/pmove/snap.d.ts.map +1 -0
- package/dist/types/pmove/special.d.ts +39 -0
- package/dist/types/pmove/special.d.ts.map +1 -0
- package/dist/types/pmove/stuck.d.ts +21 -0
- package/dist/types/pmove/stuck.d.ts.map +1 -0
- package/dist/types/pmove/types.d.ts +72 -0
- package/dist/types/pmove/types.d.ts.map +1 -0
- package/dist/types/pmove/view.d.ts +19 -0
- package/dist/types/pmove/view.d.ts.map +1 -0
- package/dist/types/pmove/water.d.ts +21 -0
- package/dist/types/pmove/water.d.ts.map +1 -0
- package/dist/types/protocol/bitpack.d.ts +17 -0
- package/dist/types/protocol/bitpack.d.ts.map +1 -0
- package/dist/types/protocol/configstrings.d.ts +73 -0
- package/dist/types/protocol/configstrings.d.ts.map +1 -0
- package/dist/types/protocol/constants.d.ts +36 -0
- package/dist/types/protocol/constants.d.ts.map +1 -0
- package/dist/types/protocol/contracts.d.ts +17 -0
- package/dist/types/protocol/contracts.d.ts.map +1 -0
- package/dist/types/protocol/crc.d.ts +5 -0
- package/dist/types/protocol/crc.d.ts.map +1 -0
- package/dist/types/protocol/cvar.d.ts +15 -0
- package/dist/types/protocol/cvar.d.ts.map +1 -0
- package/dist/types/protocol/effects.d.ts +33 -0
- package/dist/types/protocol/effects.d.ts.map +1 -0
- package/dist/types/protocol/entity.d.ts +46 -0
- package/dist/types/protocol/entity.d.ts.map +1 -0
- package/dist/types/protocol/entityEvent.d.ts +13 -0
- package/dist/types/protocol/entityEvent.d.ts.map +1 -0
- package/dist/types/protocol/entityState.d.ts +26 -0
- package/dist/types/protocol/entityState.d.ts.map +1 -0
- package/dist/types/protocol/index.d.ts +19 -0
- package/dist/types/protocol/index.d.ts.map +1 -0
- package/dist/types/protocol/layout.d.ts +9 -0
- package/dist/types/protocol/layout.d.ts.map +1 -0
- package/dist/types/protocol/ops.d.ts +44 -0
- package/dist/types/protocol/ops.d.ts.map +1 -0
- package/dist/types/protocol/player-state.d.ts +40 -0
- package/dist/types/protocol/player-state.d.ts.map +1 -0
- package/dist/types/protocol/player.d.ts +28 -0
- package/dist/types/protocol/player.d.ts.map +1 -0
- package/dist/types/protocol/renderFx.d.ts +23 -0
- package/dist/types/protocol/renderFx.d.ts.map +1 -0
- package/dist/types/protocol/stats.d.ts +61 -0
- package/dist/types/protocol/stats.d.ts.map +1 -0
- package/dist/types/protocol/tempEntity.d.ts +67 -0
- package/dist/types/protocol/tempEntity.d.ts.map +1 -0
- package/dist/types/protocol/usercmd.d.ts +33 -0
- package/dist/types/protocol/usercmd.d.ts.map +1 -0
- package/dist/types/protocol/writeUserCmd.d.ts +4 -0
- package/dist/types/protocol/writeUserCmd.d.ts.map +1 -0
- package/dist/types/replay/index.d.ts +3 -0
- package/dist/types/replay/index.d.ts.map +1 -0
- package/dist/types/replay/io.d.ts +7 -0
- package/dist/types/replay/io.d.ts.map +1 -0
- package/dist/types/replay/schema.d.ts +41 -0
- package/dist/types/replay/schema.d.ts.map +1 -0
- package/dist/types/testing.d.ts +6 -0
- package/dist/types/testing.d.ts.map +1 -0
- package/package.json +43 -0
- package/src/audio/constants.ts +35 -0
- package/src/bsp/collision.ts +1075 -0
- package/src/bsp/contents.ts +108 -0
- package/src/bsp/spatial.ts +116 -0
- package/src/index.ts +37 -0
- package/src/inventory-helpers.ts +81 -0
- package/src/io/binaryStream.ts +159 -0
- package/src/io/binaryWriter.ts +146 -0
- package/src/io/index.ts +3 -0
- package/src/io/messageBuilder.ts +117 -0
- package/src/items/ammo.ts +47 -0
- package/src/items/index.ts +8 -0
- package/src/items/powerups.ts +32 -0
- package/src/items/weaponInfo.ts +45 -0
- package/src/items/weapons.ts +28 -0
- package/src/math/angles.ts +135 -0
- package/src/math/anorms.ts +165 -0
- package/src/math/color.ts +42 -0
- package/src/math/mat4.ts +58 -0
- package/src/math/random.ts +182 -0
- package/src/math/vec3.ts +379 -0
- package/src/net/driver.ts +9 -0
- package/src/net/index.ts +2 -0
- package/src/net/netchan.ts +451 -0
- package/src/pmove/apply.ts +151 -0
- package/src/pmove/categorize.ts +162 -0
- package/src/pmove/config.ts +5 -0
- package/src/pmove/constants.ts +94 -0
- package/src/pmove/currents.ts +287 -0
- package/src/pmove/dimensions.ts +40 -0
- package/src/pmove/duck.ts +154 -0
- package/src/pmove/fly.ts +197 -0
- package/src/pmove/index.ts +18 -0
- package/src/pmove/jump.ts +92 -0
- package/src/pmove/move.ts +527 -0
- package/src/pmove/pmove.ts +446 -0
- package/src/pmove/slide.ts +267 -0
- package/src/pmove/snap.ts +89 -0
- package/src/pmove/special.ts +207 -0
- package/src/pmove/stuck.ts +258 -0
- package/src/pmove/types.ts +82 -0
- package/src/pmove/view.ts +57 -0
- package/src/pmove/water.ts +56 -0
- package/src/protocol/bitpack.ts +139 -0
- package/src/protocol/configstrings.ts +104 -0
- package/src/protocol/constants.ts +40 -0
- package/src/protocol/contracts.ts +149 -0
- package/src/protocol/crc.ts +32 -0
- package/src/protocol/cvar.ts +15 -0
- package/src/protocol/effects.ts +33 -0
- package/src/protocol/entity.ts +304 -0
- package/src/protocol/entityEvent.ts +14 -0
- package/src/protocol/entityState.ts +28 -0
- package/src/protocol/index.ts +19 -0
- package/src/protocol/layout.ts +9 -0
- package/src/protocol/ops.ts +49 -0
- package/src/protocol/player-state.ts +51 -0
- package/src/protocol/player.ts +165 -0
- package/src/protocol/renderFx.ts +22 -0
- package/src/protocol/stats.ts +161 -0
- package/src/protocol/tempEntity.ts +69 -0
- package/src/protocol/usercmd.ts +63 -0
- package/src/protocol/writeUserCmd.ts +30 -0
- package/src/replay/index.ts +2 -0
- package/src/replay/io.ts +37 -0
- package/src/replay/schema.ts +42 -0
- package/src/testing.ts +200 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Vec3 } from '../math/vec3.js';
|
|
2
|
+
import { fixStuckObjectGeneric } from './stuck.js';
|
|
3
|
+
import type { PmoveTraceFn } from './types.js';
|
|
4
|
+
|
|
5
|
+
const SNAP_OFFSETS = [0, -1, 1] as const;
|
|
6
|
+
|
|
7
|
+
export interface GoodPositionParams {
|
|
8
|
+
readonly origin: Vec3;
|
|
9
|
+
readonly mins: Vec3;
|
|
10
|
+
readonly maxs: Vec3;
|
|
11
|
+
readonly trace: PmoveTraceFn;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function goodPosition(params: GoodPositionParams): boolean {
|
|
15
|
+
const { origin, mins, maxs, trace } = params;
|
|
16
|
+
const result = trace(origin, origin, mins, maxs);
|
|
17
|
+
return result.allsolid ? false : true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SnapResolution = 'unchanged' | 'fixed' | 'reverted';
|
|
21
|
+
|
|
22
|
+
export interface SnapPositionParams extends GoodPositionParams {
|
|
23
|
+
readonly velocity: Vec3;
|
|
24
|
+
readonly previousOrigin: Vec3;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SnapPositionResult {
|
|
28
|
+
readonly origin: Vec3;
|
|
29
|
+
readonly velocity: Vec3;
|
|
30
|
+
readonly resolution: SnapResolution;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pure translation of PM_SnapPosition from rerelease `p_move.cpp`.
|
|
35
|
+
* Attempts to keep the caller's origin in a valid location by first
|
|
36
|
+
* checking the current origin against collision traces, then falling
|
|
37
|
+
* back to the shared `fixStuckObjectGeneric` helper before finally
|
|
38
|
+
* reverting to the provided previous origin when no fix is possible.
|
|
39
|
+
*/
|
|
40
|
+
export function snapPosition(params: SnapPositionParams): SnapPositionResult {
|
|
41
|
+
const { origin, velocity, mins, maxs, previousOrigin, trace } = params;
|
|
42
|
+
|
|
43
|
+
if (goodPosition({ origin, mins, maxs, trace })) {
|
|
44
|
+
return { origin: { ...origin }, velocity: { ...velocity }, resolution: 'unchanged' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fix = fixStuckObjectGeneric({
|
|
48
|
+
origin,
|
|
49
|
+
mins,
|
|
50
|
+
maxs,
|
|
51
|
+
trace: (start, localMins, localMaxs, end) => trace(start, end, localMins, localMaxs),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (fix.result === 'fixed' || fix.result === 'good-position') {
|
|
55
|
+
return { origin: fix.origin, velocity: { ...velocity }, resolution: 'fixed' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { origin: { ...previousOrigin }, velocity: { ...velocity }, resolution: 'reverted' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface InitialSnapPositionParams extends GoodPositionParams {}
|
|
62
|
+
|
|
63
|
+
export interface InitialSnapPositionResult {
|
|
64
|
+
readonly origin: Vec3;
|
|
65
|
+
readonly snapped: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Pure translation of PM_InitialSnapPosition from rerelease `p_move.cpp`.
|
|
70
|
+
* Tries a 3x3x3 grid of +/-1 unit offsets around the base origin to find
|
|
71
|
+
* a valid collision-free spot, mirroring the search order of the C++ code.
|
|
72
|
+
*/
|
|
73
|
+
export function initialSnapPosition(params: InitialSnapPositionParams): InitialSnapPositionResult {
|
|
74
|
+
const { origin, mins, maxs, trace } = params;
|
|
75
|
+
|
|
76
|
+
for (const oz of SNAP_OFFSETS) {
|
|
77
|
+
for (const oy of SNAP_OFFSETS) {
|
|
78
|
+
for (const ox of SNAP_OFFSETS) {
|
|
79
|
+
const candidate = { x: origin.x + ox, y: origin.y + oy, z: origin.z + oz } satisfies Vec3;
|
|
80
|
+
if (goodPosition({ origin: candidate, mins, maxs, trace })) {
|
|
81
|
+
const snapped = ox !== 0 || oy !== 0 || oz !== 0;
|
|
82
|
+
return { origin: candidate, snapped };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { origin: { ...origin }, snapped: false };
|
|
89
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { CONTENTS_LADDER, CONTENTS_NONE, CONTENTS_NO_WATERJUMP } from '../bsp/contents.js';
|
|
2
|
+
import { addVec3, lengthSquaredVec3, normalizeVec3, scaleVec3, type Vec3 } from '../math/vec3.js';
|
|
3
|
+
import { PlayerButton, PmFlag, type PmFlags, addPmFlag, removePmFlag, WaterLevel } from './constants.js';
|
|
4
|
+
import { stepSlideMove } from './slide.js';
|
|
5
|
+
import type { PmoveCmd, PmovePointContentsFn, PmoveTraceFn } from './types.js';
|
|
6
|
+
import { getWaterLevel } from './water.js';
|
|
7
|
+
|
|
8
|
+
const LADDER_TRACE_DISTANCE = 1;
|
|
9
|
+
const WATERJUMP_FORWARD_CHECK = 40;
|
|
10
|
+
const WATERJUMP_FORWARD_SPEED = 50;
|
|
11
|
+
const WATERJUMP_UPWARD_SPEED = 350;
|
|
12
|
+
const WATERJUMP_PM_TIME = 2048;
|
|
13
|
+
const WATERJUMP_SIM_STEP = 0.1;
|
|
14
|
+
const WATERJUMP_BASE_GRAVITY = 800;
|
|
15
|
+
const WATERJUMP_MAX_STEPS = 50;
|
|
16
|
+
const GROUND_NORMAL_THRESHOLD = 0.7;
|
|
17
|
+
const WATERJUMP_STEP_TOLERANCE = 18;
|
|
18
|
+
const DEFAULT_OVERBOUNCE = 1.01;
|
|
19
|
+
const WATERJUMP_DOWN_PROBE = 2;
|
|
20
|
+
|
|
21
|
+
export interface SpecialMovementParams {
|
|
22
|
+
readonly pmFlags: PmFlags;
|
|
23
|
+
readonly pmTime: number;
|
|
24
|
+
readonly waterlevel: WaterLevel;
|
|
25
|
+
readonly watertype: number;
|
|
26
|
+
readonly gravity: number;
|
|
27
|
+
readonly cmd: PmoveCmd;
|
|
28
|
+
readonly forward: Vec3;
|
|
29
|
+
readonly origin: Vec3;
|
|
30
|
+
readonly velocity: Vec3;
|
|
31
|
+
readonly mins: Vec3;
|
|
32
|
+
readonly maxs: Vec3;
|
|
33
|
+
readonly viewheight: number;
|
|
34
|
+
readonly trace: PmoveTraceFn;
|
|
35
|
+
readonly pointContents: PmovePointContentsFn;
|
|
36
|
+
readonly onGround: boolean;
|
|
37
|
+
readonly overbounce?: number;
|
|
38
|
+
readonly stepSize?: number;
|
|
39
|
+
readonly maxBumps?: number;
|
|
40
|
+
readonly maxClipPlanes?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SpecialMovementResult {
|
|
44
|
+
readonly pmFlags: PmFlags;
|
|
45
|
+
readonly pmTime: number;
|
|
46
|
+
readonly velocity: Vec3;
|
|
47
|
+
readonly performedWaterJump: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Mirrors the ladder detection and water-jump probing logic from
|
|
52
|
+
* `PM_CheckSpecialMovement` in `rerelease/p_move.cpp`. The helper clears and
|
|
53
|
+
* re-adds the ladder flag based on nearby CONTENTS_LADDER brushes, then
|
|
54
|
+
* simulates a potential water jump by firing the same 40-unit probe and
|
|
55
|
+
* step-slide loop the C++ uses before committing to the upward velocity.
|
|
56
|
+
*/
|
|
57
|
+
export function checkSpecialMovement(params: SpecialMovementParams): SpecialMovementResult {
|
|
58
|
+
const {
|
|
59
|
+
pmFlags: initialFlags,
|
|
60
|
+
pmTime: initialPmTime,
|
|
61
|
+
waterlevel,
|
|
62
|
+
watertype,
|
|
63
|
+
gravity,
|
|
64
|
+
cmd,
|
|
65
|
+
forward,
|
|
66
|
+
origin,
|
|
67
|
+
velocity: initialVelocity,
|
|
68
|
+
mins,
|
|
69
|
+
maxs,
|
|
70
|
+
viewheight,
|
|
71
|
+
trace,
|
|
72
|
+
pointContents,
|
|
73
|
+
onGround,
|
|
74
|
+
overbounce = DEFAULT_OVERBOUNCE,
|
|
75
|
+
stepSize = WATERJUMP_STEP_TOLERANCE,
|
|
76
|
+
maxBumps,
|
|
77
|
+
maxClipPlanes,
|
|
78
|
+
} = params;
|
|
79
|
+
|
|
80
|
+
if (initialPmTime > 0) {
|
|
81
|
+
return { pmFlags: initialFlags, pmTime: initialPmTime, velocity: initialVelocity, performedWaterJump: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let pmFlags = removePmFlag(initialFlags, PmFlag.OnLadder);
|
|
85
|
+
let pmTime = initialPmTime;
|
|
86
|
+
let velocity = initialVelocity;
|
|
87
|
+
|
|
88
|
+
const flatforward = normalizeVec3({ x: forward.x, y: forward.y, z: 0 });
|
|
89
|
+
const hasForward = lengthSquaredVec3(flatforward) > 0;
|
|
90
|
+
|
|
91
|
+
if (waterlevel < WaterLevel.Waist && hasForward) {
|
|
92
|
+
const ladderEnd = addVec3(origin, scaleVec3(flatforward, LADDER_TRACE_DISTANCE));
|
|
93
|
+
const ladderTrace = trace(origin, ladderEnd, mins, maxs);
|
|
94
|
+
const contents = ladderTrace.contents ?? CONTENTS_NONE;
|
|
95
|
+
|
|
96
|
+
if (ladderTrace.fraction < 1 && (contents & CONTENTS_LADDER) !== 0) {
|
|
97
|
+
pmFlags = addPmFlag(pmFlags, PmFlag.OnLadder);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (gravity === 0) {
|
|
102
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (((cmd.buttons ?? 0) & PlayerButton.Jump) === 0 && cmd.forwardmove <= 0) {
|
|
106
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (waterlevel !== WaterLevel.Waist) {
|
|
110
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if ((watertype & CONTENTS_NO_WATERJUMP) !== 0) {
|
|
114
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!hasForward) {
|
|
118
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const forwardCheckEnd = addVec3(origin, scaleVec3(flatforward, WATERJUMP_FORWARD_CHECK));
|
|
122
|
+
const forwardTrace = trace(origin, forwardCheckEnd, mins, maxs);
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
forwardTrace.fraction === 1 ||
|
|
126
|
+
!forwardTrace.planeNormal ||
|
|
127
|
+
forwardTrace.planeNormal.z >= GROUND_NORMAL_THRESHOLD
|
|
128
|
+
) {
|
|
129
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let simVelocity: Vec3 = {
|
|
133
|
+
x: flatforward.x * WATERJUMP_FORWARD_SPEED,
|
|
134
|
+
y: flatforward.y * WATERJUMP_FORWARD_SPEED,
|
|
135
|
+
z: WATERJUMP_UPWARD_SPEED,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let simOrigin = origin;
|
|
139
|
+
let hasTime = true;
|
|
140
|
+
const stepCount = computeWaterJumpSteps(gravity);
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < stepCount; i++) {
|
|
143
|
+
simVelocity = { x: simVelocity.x, y: simVelocity.y, z: simVelocity.z - gravity * WATERJUMP_SIM_STEP };
|
|
144
|
+
if (simVelocity.z < 0) {
|
|
145
|
+
hasTime = false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const move = stepSlideMove({
|
|
149
|
+
origin: simOrigin,
|
|
150
|
+
velocity: simVelocity,
|
|
151
|
+
frametime: WATERJUMP_SIM_STEP,
|
|
152
|
+
trace,
|
|
153
|
+
mins,
|
|
154
|
+
maxs,
|
|
155
|
+
overbounce,
|
|
156
|
+
stepSize,
|
|
157
|
+
maxBumps,
|
|
158
|
+
maxClipPlanes,
|
|
159
|
+
hasTime,
|
|
160
|
+
});
|
|
161
|
+
simOrigin = move.origin;
|
|
162
|
+
simVelocity = move.velocity;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const downEnd = addVec3(simOrigin, { x: 0, y: 0, z: -WATERJUMP_DOWN_PROBE });
|
|
166
|
+
const downTrace = trace(simOrigin, downEnd, mins, maxs);
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
downTrace.fraction === 1 ||
|
|
170
|
+
!downTrace.planeNormal ||
|
|
171
|
+
downTrace.planeNormal.z < GROUND_NORMAL_THRESHOLD ||
|
|
172
|
+
downTrace.endpos.z < origin.z
|
|
173
|
+
) {
|
|
174
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (onGround && Math.abs(origin.z - downTrace.endpos.z) <= stepSize) {
|
|
178
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const landingWater = getWaterLevel({ origin: downTrace.endpos, mins, viewheight, pointContents });
|
|
182
|
+
if (landingWater.waterlevel >= WaterLevel.Waist) {
|
|
183
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: false };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pmFlags = addPmFlag(pmFlags, PmFlag.TimeWaterJump);
|
|
187
|
+
pmTime = WATERJUMP_PM_TIME;
|
|
188
|
+
velocity = {
|
|
189
|
+
x: flatforward.x * WATERJUMP_FORWARD_SPEED,
|
|
190
|
+
y: flatforward.y * WATERJUMP_FORWARD_SPEED,
|
|
191
|
+
z: WATERJUMP_UPWARD_SPEED,
|
|
192
|
+
} satisfies Vec3;
|
|
193
|
+
|
|
194
|
+
return { pmFlags, pmTime, velocity, performedWaterJump: true };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function computeWaterJumpSteps(gravity: number): number {
|
|
198
|
+
if (gravity === 0) {
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const scaled = Math.floor(10 * (WATERJUMP_BASE_GRAVITY / gravity));
|
|
203
|
+
if (scaled <= 0) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
return Math.min(WATERJUMP_MAX_STEPS, scaled);
|
|
207
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addVec3,
|
|
3
|
+
lengthSquaredVec3,
|
|
4
|
+
scaleVec3,
|
|
5
|
+
subtractVec3,
|
|
6
|
+
type Vec3,
|
|
7
|
+
} from '../math/vec3.js';
|
|
8
|
+
import type { PmoveTraceResult } from './types.js';
|
|
9
|
+
|
|
10
|
+
const AXES = ['x', 'y', 'z'] as const;
|
|
11
|
+
type Axis = (typeof AXES)[number];
|
|
12
|
+
|
|
13
|
+
type AxisTuple = readonly [number, number, number];
|
|
14
|
+
type SideBoundCode = -1 | 0 | 1;
|
|
15
|
+
|
|
16
|
+
interface SideCheck {
|
|
17
|
+
readonly normal: AxisTuple;
|
|
18
|
+
readonly mins: readonly [SideBoundCode, SideBoundCode, SideBoundCode];
|
|
19
|
+
readonly maxs: readonly [SideBoundCode, SideBoundCode, SideBoundCode];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const SIDE_CHECKS: readonly SideCheck[] = [
|
|
23
|
+
{ normal: [0, 0, 1], mins: [-1, -1, 0], maxs: [1, 1, 0] },
|
|
24
|
+
{ normal: [0, 0, -1], mins: [-1, -1, 0], maxs: [1, 1, 0] },
|
|
25
|
+
{ normal: [1, 0, 0], mins: [0, -1, -1], maxs: [0, 1, 1] },
|
|
26
|
+
{ normal: [-1, 0, 0], mins: [0, -1, -1], maxs: [0, 1, 1] },
|
|
27
|
+
{ normal: [0, 1, 0], mins: [-1, 0, -1], maxs: [1, 0, 1] },
|
|
28
|
+
{ normal: [0, -1, 0], mins: [-1, 0, -1], maxs: [1, 0, 1] },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export interface FixStuckParams {
|
|
32
|
+
readonly origin: Vec3;
|
|
33
|
+
readonly mins: Vec3;
|
|
34
|
+
readonly maxs: Vec3;
|
|
35
|
+
readonly trace: FixStuckTraceFn;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type FixStuckResult = 'good-position' | 'fixed' | 'no-good-position';
|
|
39
|
+
|
|
40
|
+
export interface FixStuckOutcome {
|
|
41
|
+
readonly result: FixStuckResult;
|
|
42
|
+
readonly origin: Vec3;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type FixStuckTraceFn = (
|
|
46
|
+
start: Vec3,
|
|
47
|
+
mins: Vec3,
|
|
48
|
+
maxs: Vec3,
|
|
49
|
+
end: Vec3,
|
|
50
|
+
) => PmoveTraceResult;
|
|
51
|
+
|
|
52
|
+
interface CandidatePosition {
|
|
53
|
+
readonly distance: number;
|
|
54
|
+
readonly origin: Vec3;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ZERO_VEC: Vec3 = { x: 0, y: 0, z: 0 };
|
|
58
|
+
|
|
59
|
+
type MutableVec3 = { x: number; y: number; z: number };
|
|
60
|
+
|
|
61
|
+
function cloneMutable(vec: Vec3): MutableVec3 {
|
|
62
|
+
return { x: vec.x, y: vec.y, z: vec.z };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function tupleToVec3(tuple: AxisTuple): Vec3 {
|
|
66
|
+
return { x: tuple[0], y: tuple[1], z: tuple[2] };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function adjustAxis(vec: MutableVec3, axis: Axis, delta: number): void {
|
|
70
|
+
if (delta === 0) return;
|
|
71
|
+
switch (axis) {
|
|
72
|
+
case 'x':
|
|
73
|
+
vec.x += delta;
|
|
74
|
+
break;
|
|
75
|
+
case 'y':
|
|
76
|
+
vec.y += delta;
|
|
77
|
+
break;
|
|
78
|
+
case 'z':
|
|
79
|
+
vec.z += delta;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setAxis(vec: MutableVec3, axis: Axis, value: number): void {
|
|
85
|
+
switch (axis) {
|
|
86
|
+
case 'x':
|
|
87
|
+
vec.x = value;
|
|
88
|
+
break;
|
|
89
|
+
case 'y':
|
|
90
|
+
vec.y = value;
|
|
91
|
+
break;
|
|
92
|
+
case 'z':
|
|
93
|
+
vec.z = value;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function axisValue(vec: Vec3, axis: Axis): number {
|
|
99
|
+
switch (axis) {
|
|
100
|
+
case 'x':
|
|
101
|
+
return vec.x;
|
|
102
|
+
case 'y':
|
|
103
|
+
return vec.y;
|
|
104
|
+
case 'z':
|
|
105
|
+
default:
|
|
106
|
+
return vec.z;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function boundValue(code: SideBoundCode, axis: Axis, mins: Vec3, maxs: Vec3): number {
|
|
111
|
+
if (code === -1) {
|
|
112
|
+
return axisValue(mins, axis);
|
|
113
|
+
}
|
|
114
|
+
if (code === 1) {
|
|
115
|
+
return axisValue(maxs, axis);
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function applySideOffset(base: Vec3, side: SideCheck, mins: Vec3, maxs: Vec3): MutableVec3 {
|
|
121
|
+
const result = cloneMutable(base);
|
|
122
|
+
for (let i = 0; i < AXES.length; i++) {
|
|
123
|
+
const axis = AXES[i];
|
|
124
|
+
const normal = side.normal[i];
|
|
125
|
+
if (normal < 0) {
|
|
126
|
+
adjustAxis(result, axis, axisValue(mins, axis));
|
|
127
|
+
} else if (normal > 0) {
|
|
128
|
+
adjustAxis(result, axis, axisValue(maxs, axis));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildSideBounds(side: SideCheck, mins: Vec3, maxs: Vec3): { mins: MutableVec3; maxs: MutableVec3 } {
|
|
135
|
+
const localMins = cloneMutable(ZERO_VEC);
|
|
136
|
+
const localMaxs = cloneMutable(ZERO_VEC);
|
|
137
|
+
for (let i = 0; i < AXES.length; i++) {
|
|
138
|
+
const axis = AXES[i];
|
|
139
|
+
setAxis(localMins, axis, boundValue(side.mins[i], axis, mins, maxs));
|
|
140
|
+
setAxis(localMaxs, axis, boundValue(side.maxs[i], axis, mins, maxs));
|
|
141
|
+
}
|
|
142
|
+
return { mins: localMins, maxs: localMaxs };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function addEpsilon(
|
|
146
|
+
source: MutableVec3,
|
|
147
|
+
axis: Axis | undefined,
|
|
148
|
+
direction: number,
|
|
149
|
+
): MutableVec3 {
|
|
150
|
+
if (!axis || direction === 0) {
|
|
151
|
+
return source;
|
|
152
|
+
}
|
|
153
|
+
const clone = cloneMutable(source);
|
|
154
|
+
adjustAxis(clone, axis, direction);
|
|
155
|
+
return clone;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function addEpsilonImmutable(vec: Vec3, axis: Axis | undefined, direction: number): Vec3 {
|
|
159
|
+
if (!axis || direction === 0) {
|
|
160
|
+
return vec;
|
|
161
|
+
}
|
|
162
|
+
switch (axis) {
|
|
163
|
+
case 'x':
|
|
164
|
+
return { ...vec, x: vec.x + direction };
|
|
165
|
+
case 'y':
|
|
166
|
+
return { ...vec, y: vec.y + direction };
|
|
167
|
+
case 'z':
|
|
168
|
+
default:
|
|
169
|
+
return { ...vec, z: vec.z + direction };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* TypeScript port of G_FixStuckObject_Generic from rerelease p_move.cpp. Attempts to
|
|
175
|
+
* nudge a stuck bounding box out of solid space by probing the faces of the box and
|
|
176
|
+
* moving towards the opposite side, keeping the smallest successful displacement.
|
|
177
|
+
*/
|
|
178
|
+
export function fixStuckObjectGeneric(params: FixStuckParams): FixStuckOutcome {
|
|
179
|
+
const { origin, mins, maxs, trace } = params;
|
|
180
|
+
|
|
181
|
+
const initial = trace(origin, mins, maxs, origin);
|
|
182
|
+
if (!initial.startsolid) {
|
|
183
|
+
return { result: 'good-position', origin: { ...origin } };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const candidates: CandidatePosition[] = [];
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < SIDE_CHECKS.length; i++) {
|
|
189
|
+
const side = SIDE_CHECKS[i];
|
|
190
|
+
const { mins: localMins, maxs: localMaxs } = buildSideBounds(side, mins, maxs);
|
|
191
|
+
|
|
192
|
+
let start = applySideOffset(origin, side, mins, maxs);
|
|
193
|
+
let tr = trace(start, localMins, localMaxs, start);
|
|
194
|
+
|
|
195
|
+
let epsilonAxis: Axis | undefined;
|
|
196
|
+
let epsilonDir = 0;
|
|
197
|
+
|
|
198
|
+
if (tr.startsolid) {
|
|
199
|
+
for (let axisIndex = 0; axisIndex < AXES.length; axisIndex++) {
|
|
200
|
+
if (side.normal[axisIndex] !== 0) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const axis = AXES[axisIndex];
|
|
204
|
+
let epsilonStart = cloneMutable(start);
|
|
205
|
+
adjustAxis(epsilonStart, axis, 1);
|
|
206
|
+
tr = trace(epsilonStart, localMins, localMaxs, epsilonStart);
|
|
207
|
+
if (!tr.startsolid) {
|
|
208
|
+
start = epsilonStart;
|
|
209
|
+
epsilonAxis = axis;
|
|
210
|
+
epsilonDir = 1;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
epsilonStart = cloneMutable(start);
|
|
214
|
+
adjustAxis(epsilonStart, axis, -1);
|
|
215
|
+
tr = trace(epsilonStart, localMins, localMaxs, epsilonStart);
|
|
216
|
+
if (!tr.startsolid) {
|
|
217
|
+
start = epsilonStart;
|
|
218
|
+
epsilonAxis = axis;
|
|
219
|
+
epsilonDir = -1;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (tr.startsolid) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const otherSide = SIDE_CHECKS[i ^ 1];
|
|
230
|
+
let oppositeStart = applySideOffset(origin, otherSide, mins, maxs);
|
|
231
|
+
oppositeStart = addEpsilon(oppositeStart, epsilonAxis, epsilonDir);
|
|
232
|
+
|
|
233
|
+
tr = trace(start, localMins, localMaxs, oppositeStart);
|
|
234
|
+
if (tr.startsolid) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const normal = tupleToVec3(side.normal);
|
|
239
|
+
const end = addVec3(tr.endpos ?? oppositeStart, scaleVec3(normal, 0.125));
|
|
240
|
+
const delta = subtractVec3(end, oppositeStart);
|
|
241
|
+
let newOrigin = addVec3(origin, delta);
|
|
242
|
+
newOrigin = addEpsilonImmutable(newOrigin, epsilonAxis, epsilonDir);
|
|
243
|
+
|
|
244
|
+
const validation = trace(newOrigin, mins, maxs, newOrigin);
|
|
245
|
+
if (validation.startsolid) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
candidates.push({ origin: newOrigin, distance: lengthSquaredVec3(delta) });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (candidates.length === 0) {
|
|
253
|
+
return { result: 'no-good-position', origin: { ...origin } };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
257
|
+
return { result: 'fixed', origin: { ...candidates[0].origin } };
|
|
258
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ContentsFlag } from '../bsp/contents.js';
|
|
2
|
+
import type { Vec3 } from '../math/vec3.js';
|
|
3
|
+
import type { PmFlags, PmType, WaterLevel, PlayerButton } from './constants.js';
|
|
4
|
+
|
|
5
|
+
export interface PmoveFrictionParams {
|
|
6
|
+
readonly velocity: Vec3;
|
|
7
|
+
readonly frametime: number;
|
|
8
|
+
readonly onGround: boolean;
|
|
9
|
+
readonly groundIsSlick: boolean;
|
|
10
|
+
readonly onLadder: boolean;
|
|
11
|
+
readonly waterlevel: number;
|
|
12
|
+
readonly pmFriction: number;
|
|
13
|
+
readonly pmStopSpeed: number;
|
|
14
|
+
readonly pmWaterFriction: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PmoveAccelerateParams {
|
|
18
|
+
readonly velocity: Vec3;
|
|
19
|
+
readonly wishdir: Vec3;
|
|
20
|
+
readonly wishspeed: number;
|
|
21
|
+
readonly accel: number;
|
|
22
|
+
readonly frametime: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PmoveCmd {
|
|
26
|
+
readonly forwardmove: number;
|
|
27
|
+
readonly sidemove: number;
|
|
28
|
+
readonly upmove: number;
|
|
29
|
+
readonly buttons: PlayerButton;
|
|
30
|
+
readonly impulse?: number;
|
|
31
|
+
readonly lightlevel?: number;
|
|
32
|
+
readonly angles: Vec3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PmoveWishResult {
|
|
36
|
+
readonly wishdir: Vec3;
|
|
37
|
+
readonly wishspeed: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PmoveWishParams {
|
|
41
|
+
readonly forward: Vec3;
|
|
42
|
+
readonly right: Vec3;
|
|
43
|
+
readonly cmd: PmoveCmd;
|
|
44
|
+
readonly maxSpeed: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PmoveTraceResult {
|
|
48
|
+
readonly fraction: number;
|
|
49
|
+
readonly endpos: Vec3;
|
|
50
|
+
readonly planeNormal?: Vec3;
|
|
51
|
+
readonly allsolid: boolean;
|
|
52
|
+
readonly startsolid: boolean;
|
|
53
|
+
readonly contents?: ContentsFlag;
|
|
54
|
+
readonly surfaceFlags?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type PmoveTraceFn = (start: Vec3, end: Vec3, mins?: Vec3, maxs?: Vec3) => PmoveTraceResult;
|
|
58
|
+
|
|
59
|
+
export type PmovePointContentsFn = (point: Vec3) => ContentsFlag;
|
|
60
|
+
|
|
61
|
+
// Missing exports added for runPmove
|
|
62
|
+
export interface PmoveState {
|
|
63
|
+
pmType: PmType;
|
|
64
|
+
pmFlags: PmFlags;
|
|
65
|
+
origin: Vec3;
|
|
66
|
+
velocity: Vec3;
|
|
67
|
+
angles: Vec3;
|
|
68
|
+
viewAngles: Vec3;
|
|
69
|
+
viewHeight: number;
|
|
70
|
+
waterlevel: WaterLevel;
|
|
71
|
+
watertype: number;
|
|
72
|
+
cmd: PmoveCmd;
|
|
73
|
+
delta_angles: Vec3;
|
|
74
|
+
gravity: number;
|
|
75
|
+
mins: Vec3;
|
|
76
|
+
maxs: Vec3;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PmoveImports {
|
|
80
|
+
trace: PmoveTraceFn;
|
|
81
|
+
pointcontents: PmovePointContentsFn;
|
|
82
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Vec3 } from '../math/vec3.js';
|
|
2
|
+
import { angleVectors, type AngleVectorsResult } from '../math/angles.js';
|
|
3
|
+
import { PmFlag, type PmFlags } from './constants.js';
|
|
4
|
+
|
|
5
|
+
export interface ClampViewAnglesParams {
|
|
6
|
+
readonly pmFlags: PmFlags;
|
|
7
|
+
readonly cmdAngles: Vec3;
|
|
8
|
+
readonly deltaAngles: Vec3;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ClampViewAnglesResult extends AngleVectorsResult {
|
|
12
|
+
readonly viewangles: Vec3;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function addAngles(cmdAngles: Vec3, deltaAngles: Vec3): Vec3 {
|
|
16
|
+
return {
|
|
17
|
+
x: cmdAngles.x + deltaAngles.x,
|
|
18
|
+
y: cmdAngles.y + deltaAngles.y,
|
|
19
|
+
z: cmdAngles.z + deltaAngles.z,
|
|
20
|
+
} satisfies Vec3;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function clampPitch(pitch: number): number {
|
|
24
|
+
if (pitch > 89 && pitch < 180) {
|
|
25
|
+
return 89;
|
|
26
|
+
}
|
|
27
|
+
if (pitch < 271 && pitch >= 180) {
|
|
28
|
+
return 271;
|
|
29
|
+
}
|
|
30
|
+
return pitch;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Pure translation of `PM_ClampAngles` from `rerelease/p_move.cpp`. The helper
|
|
35
|
+
* fuses the latest command angles with the stored delta, applies the teleport
|
|
36
|
+
* pitch/roll reset, enforces the ±90° pitch window, and returns the resulting
|
|
37
|
+
* axis vectors that the C version stored in `pml.forward/right/up`.
|
|
38
|
+
*/
|
|
39
|
+
export function clampViewAngles(params: ClampViewAnglesParams): ClampViewAnglesResult {
|
|
40
|
+
const { pmFlags, cmdAngles, deltaAngles } = params;
|
|
41
|
+
|
|
42
|
+
let viewangles: Vec3;
|
|
43
|
+
|
|
44
|
+
if ((pmFlags & PmFlag.TimeTeleport) !== 0) {
|
|
45
|
+
viewangles = {
|
|
46
|
+
x: 0,
|
|
47
|
+
y: cmdAngles.y + deltaAngles.y,
|
|
48
|
+
z: 0,
|
|
49
|
+
} satisfies Vec3;
|
|
50
|
+
} else {
|
|
51
|
+
viewangles = addAngles(cmdAngles, deltaAngles);
|
|
52
|
+
viewangles = { ...viewangles, x: clampPitch(viewangles.x) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const vectors = angleVectors(viewangles);
|
|
56
|
+
return { viewangles, ...vectors };
|
|
57
|
+
}
|