@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.
Files changed (209) hide show
  1. package/dist/browser/index.global.js +2 -0
  2. package/dist/browser/index.global.js.map +1 -0
  3. package/dist/cjs/index.cjs +6569 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/esm/index.js +6200 -0
  6. package/dist/esm/index.js.map +1 -0
  7. package/dist/tsconfig.tsbuildinfo +1 -0
  8. package/dist/types/audio/constants.d.ts +24 -0
  9. package/dist/types/audio/constants.d.ts.map +1 -0
  10. package/dist/types/bsp/collision.d.ts +201 -0
  11. package/dist/types/bsp/collision.d.ts.map +1 -0
  12. package/dist/types/bsp/contents.d.ts +72 -0
  13. package/dist/types/bsp/contents.d.ts.map +1 -0
  14. package/dist/types/bsp/spatial.d.ts +13 -0
  15. package/dist/types/bsp/spatial.d.ts.map +1 -0
  16. package/dist/types/index.d.ts +38 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/inventory-helpers.d.ts +19 -0
  19. package/dist/types/inventory-helpers.d.ts.map +1 -0
  20. package/dist/types/io/binaryStream.d.ts +38 -0
  21. package/dist/types/io/binaryStream.d.ts.map +1 -0
  22. package/dist/types/io/binaryWriter.d.ts +26 -0
  23. package/dist/types/io/binaryWriter.d.ts.map +1 -0
  24. package/dist/types/io/index.d.ts +4 -0
  25. package/dist/types/io/index.d.ts.map +1 -0
  26. package/dist/types/io/messageBuilder.d.ts +21 -0
  27. package/dist/types/io/messageBuilder.d.ts.map +1 -0
  28. package/dist/types/items/ammo.d.ts +40 -0
  29. package/dist/types/items/ammo.d.ts.map +1 -0
  30. package/dist/types/items/index.d.ts +8 -0
  31. package/dist/types/items/index.d.ts.map +1 -0
  32. package/dist/types/items/powerups.d.ts +31 -0
  33. package/dist/types/items/powerups.d.ts.map +1 -0
  34. package/dist/types/items/weaponInfo.d.ts +5 -0
  35. package/dist/types/items/weaponInfo.d.ts.map +1 -0
  36. package/dist/types/items/weapons.d.ts +27 -0
  37. package/dist/types/items/weapons.d.ts.map +1 -0
  38. package/dist/types/math/angles.d.ts +19 -0
  39. package/dist/types/math/angles.d.ts.map +1 -0
  40. package/dist/types/math/anorms.d.ts +2 -0
  41. package/dist/types/math/anorms.d.ts.map +1 -0
  42. package/dist/types/math/color.d.ts +12 -0
  43. package/dist/types/math/color.d.ts.map +1 -0
  44. package/dist/types/math/mat4.d.ts +7 -0
  45. package/dist/types/math/mat4.d.ts.map +1 -0
  46. package/dist/types/math/random.d.ts +60 -0
  47. package/dist/types/math/random.d.ts.map +1 -0
  48. package/dist/types/math/vec3.d.ts +79 -0
  49. package/dist/types/math/vec3.d.ts.map +1 -0
  50. package/dist/types/net/driver.d.ts +10 -0
  51. package/dist/types/net/driver.d.ts.map +1 -0
  52. package/dist/types/net/index.d.ts +3 -0
  53. package/dist/types/net/index.d.ts.map +1 -0
  54. package/dist/types/net/netchan.d.ts +85 -0
  55. package/dist/types/net/netchan.d.ts.map +1 -0
  56. package/dist/types/pmove/apply.d.ts +5 -0
  57. package/dist/types/pmove/apply.d.ts.map +1 -0
  58. package/dist/types/pmove/categorize.d.ts +36 -0
  59. package/dist/types/pmove/categorize.d.ts.map +1 -0
  60. package/dist/types/pmove/config.d.ts +5 -0
  61. package/dist/types/pmove/config.d.ts.map +1 -0
  62. package/dist/types/pmove/constants.d.ts +76 -0
  63. package/dist/types/pmove/constants.d.ts.map +1 -0
  64. package/dist/types/pmove/currents.d.ts +58 -0
  65. package/dist/types/pmove/currents.d.ts.map +1 -0
  66. package/dist/types/pmove/dimensions.d.ts +14 -0
  67. package/dist/types/pmove/dimensions.d.ts.map +1 -0
  68. package/dist/types/pmove/duck.d.ts +39 -0
  69. package/dist/types/pmove/duck.d.ts.map +1 -0
  70. package/dist/types/pmove/fly.d.ts +34 -0
  71. package/dist/types/pmove/fly.d.ts.map +1 -0
  72. package/dist/types/pmove/index.d.ts +18 -0
  73. package/dist/types/pmove/index.d.ts.map +1 -0
  74. package/dist/types/pmove/jump.d.ts +28 -0
  75. package/dist/types/pmove/jump.d.ts.map +1 -0
  76. package/dist/types/pmove/move.d.ts +78 -0
  77. package/dist/types/pmove/move.d.ts.map +1 -0
  78. package/dist/types/pmove/pmove.d.ts +40 -0
  79. package/dist/types/pmove/pmove.d.ts.map +1 -0
  80. package/dist/types/pmove/slide.d.ts +63 -0
  81. package/dist/types/pmove/slide.d.ts.map +1 -0
  82. package/dist/types/pmove/snap.d.ts +40 -0
  83. package/dist/types/pmove/snap.d.ts.map +1 -0
  84. package/dist/types/pmove/special.d.ts +39 -0
  85. package/dist/types/pmove/special.d.ts.map +1 -0
  86. package/dist/types/pmove/stuck.d.ts +21 -0
  87. package/dist/types/pmove/stuck.d.ts.map +1 -0
  88. package/dist/types/pmove/types.d.ts +72 -0
  89. package/dist/types/pmove/types.d.ts.map +1 -0
  90. package/dist/types/pmove/view.d.ts +19 -0
  91. package/dist/types/pmove/view.d.ts.map +1 -0
  92. package/dist/types/pmove/water.d.ts +21 -0
  93. package/dist/types/pmove/water.d.ts.map +1 -0
  94. package/dist/types/protocol/bitpack.d.ts +17 -0
  95. package/dist/types/protocol/bitpack.d.ts.map +1 -0
  96. package/dist/types/protocol/configstrings.d.ts +73 -0
  97. package/dist/types/protocol/configstrings.d.ts.map +1 -0
  98. package/dist/types/protocol/constants.d.ts +36 -0
  99. package/dist/types/protocol/constants.d.ts.map +1 -0
  100. package/dist/types/protocol/contracts.d.ts +17 -0
  101. package/dist/types/protocol/contracts.d.ts.map +1 -0
  102. package/dist/types/protocol/crc.d.ts +5 -0
  103. package/dist/types/protocol/crc.d.ts.map +1 -0
  104. package/dist/types/protocol/cvar.d.ts +15 -0
  105. package/dist/types/protocol/cvar.d.ts.map +1 -0
  106. package/dist/types/protocol/effects.d.ts +33 -0
  107. package/dist/types/protocol/effects.d.ts.map +1 -0
  108. package/dist/types/protocol/entity.d.ts +46 -0
  109. package/dist/types/protocol/entity.d.ts.map +1 -0
  110. package/dist/types/protocol/entityEvent.d.ts +13 -0
  111. package/dist/types/protocol/entityEvent.d.ts.map +1 -0
  112. package/dist/types/protocol/entityState.d.ts +26 -0
  113. package/dist/types/protocol/entityState.d.ts.map +1 -0
  114. package/dist/types/protocol/index.d.ts +19 -0
  115. package/dist/types/protocol/index.d.ts.map +1 -0
  116. package/dist/types/protocol/layout.d.ts +9 -0
  117. package/dist/types/protocol/layout.d.ts.map +1 -0
  118. package/dist/types/protocol/ops.d.ts +44 -0
  119. package/dist/types/protocol/ops.d.ts.map +1 -0
  120. package/dist/types/protocol/player-state.d.ts +40 -0
  121. package/dist/types/protocol/player-state.d.ts.map +1 -0
  122. package/dist/types/protocol/player.d.ts +28 -0
  123. package/dist/types/protocol/player.d.ts.map +1 -0
  124. package/dist/types/protocol/renderFx.d.ts +23 -0
  125. package/dist/types/protocol/renderFx.d.ts.map +1 -0
  126. package/dist/types/protocol/stats.d.ts +61 -0
  127. package/dist/types/protocol/stats.d.ts.map +1 -0
  128. package/dist/types/protocol/tempEntity.d.ts +67 -0
  129. package/dist/types/protocol/tempEntity.d.ts.map +1 -0
  130. package/dist/types/protocol/usercmd.d.ts +33 -0
  131. package/dist/types/protocol/usercmd.d.ts.map +1 -0
  132. package/dist/types/protocol/writeUserCmd.d.ts +4 -0
  133. package/dist/types/protocol/writeUserCmd.d.ts.map +1 -0
  134. package/dist/types/replay/index.d.ts +3 -0
  135. package/dist/types/replay/index.d.ts.map +1 -0
  136. package/dist/types/replay/io.d.ts +7 -0
  137. package/dist/types/replay/io.d.ts.map +1 -0
  138. package/dist/types/replay/schema.d.ts +41 -0
  139. package/dist/types/replay/schema.d.ts.map +1 -0
  140. package/dist/types/testing.d.ts +6 -0
  141. package/dist/types/testing.d.ts.map +1 -0
  142. package/package.json +43 -0
  143. package/src/audio/constants.ts +35 -0
  144. package/src/bsp/collision.ts +1075 -0
  145. package/src/bsp/contents.ts +108 -0
  146. package/src/bsp/spatial.ts +116 -0
  147. package/src/index.ts +37 -0
  148. package/src/inventory-helpers.ts +81 -0
  149. package/src/io/binaryStream.ts +159 -0
  150. package/src/io/binaryWriter.ts +146 -0
  151. package/src/io/index.ts +3 -0
  152. package/src/io/messageBuilder.ts +117 -0
  153. package/src/items/ammo.ts +47 -0
  154. package/src/items/index.ts +8 -0
  155. package/src/items/powerups.ts +32 -0
  156. package/src/items/weaponInfo.ts +45 -0
  157. package/src/items/weapons.ts +28 -0
  158. package/src/math/angles.ts +135 -0
  159. package/src/math/anorms.ts +165 -0
  160. package/src/math/color.ts +42 -0
  161. package/src/math/mat4.ts +58 -0
  162. package/src/math/random.ts +182 -0
  163. package/src/math/vec3.ts +379 -0
  164. package/src/net/driver.ts +9 -0
  165. package/src/net/index.ts +2 -0
  166. package/src/net/netchan.ts +451 -0
  167. package/src/pmove/apply.ts +151 -0
  168. package/src/pmove/categorize.ts +162 -0
  169. package/src/pmove/config.ts +5 -0
  170. package/src/pmove/constants.ts +94 -0
  171. package/src/pmove/currents.ts +287 -0
  172. package/src/pmove/dimensions.ts +40 -0
  173. package/src/pmove/duck.ts +154 -0
  174. package/src/pmove/fly.ts +197 -0
  175. package/src/pmove/index.ts +18 -0
  176. package/src/pmove/jump.ts +92 -0
  177. package/src/pmove/move.ts +527 -0
  178. package/src/pmove/pmove.ts +446 -0
  179. package/src/pmove/slide.ts +267 -0
  180. package/src/pmove/snap.ts +89 -0
  181. package/src/pmove/special.ts +207 -0
  182. package/src/pmove/stuck.ts +258 -0
  183. package/src/pmove/types.ts +82 -0
  184. package/src/pmove/view.ts +57 -0
  185. package/src/pmove/water.ts +56 -0
  186. package/src/protocol/bitpack.ts +139 -0
  187. package/src/protocol/configstrings.ts +104 -0
  188. package/src/protocol/constants.ts +40 -0
  189. package/src/protocol/contracts.ts +149 -0
  190. package/src/protocol/crc.ts +32 -0
  191. package/src/protocol/cvar.ts +15 -0
  192. package/src/protocol/effects.ts +33 -0
  193. package/src/protocol/entity.ts +304 -0
  194. package/src/protocol/entityEvent.ts +14 -0
  195. package/src/protocol/entityState.ts +28 -0
  196. package/src/protocol/index.ts +19 -0
  197. package/src/protocol/layout.ts +9 -0
  198. package/src/protocol/ops.ts +49 -0
  199. package/src/protocol/player-state.ts +51 -0
  200. package/src/protocol/player.ts +165 -0
  201. package/src/protocol/renderFx.ts +22 -0
  202. package/src/protocol/stats.ts +161 -0
  203. package/src/protocol/tempEntity.ts +69 -0
  204. package/src/protocol/usercmd.ts +63 -0
  205. package/src/protocol/writeUserCmd.ts +30 -0
  206. package/src/replay/index.ts +2 -0
  207. package/src/replay/io.ts +37 -0
  208. package/src/replay/schema.ts +42 -0
  209. package/src/testing.ts +200 -0
@@ -0,0 +1,162 @@
1
+ import { CONTENTS_NONE, type ContentsFlag } from '../bsp/contents.js';
2
+ import { addVec3, clipVelocityVec3, type Vec3 } from '../math/vec3.js';
3
+ import {
4
+ PmFlag,
5
+ type PmFlags,
6
+ PmType,
7
+ addPmFlag,
8
+ hasPmFlag,
9
+ removePmFlag,
10
+ } from './constants.js';
11
+ import { getWaterLevel } from './water.js';
12
+ import type { PmovePointContentsFn, PmoveTraceFn, PmoveTraceResult } from './types.js';
13
+
14
+ const GROUND_PROBE_DISTANCE = 0.25;
15
+ const LADDER_BYPASS_VELOCITY = 180;
16
+ const TRICK_VELOCITY_THRESHOLD = 100;
17
+ const SLANTED_NORMAL_THRESHOLD = 0.7;
18
+ const TRICK_NORMAL_THRESHOLD = 0.9;
19
+ const TRICK_PM_TIME = 64;
20
+ const LAND_PM_TIME = 128;
21
+ const IMPACT_CLIP_OVERBOUNCE = 1.01;
22
+
23
+ const WATERJUMP_CLEAR =
24
+ PmFlag.TimeWaterJump | PmFlag.TimeLand | PmFlag.TimeTeleport | PmFlag.TimeTrick;
25
+
26
+ export interface CategorizePositionParams {
27
+ readonly pmType: PmType;
28
+ readonly pmFlags: PmFlags;
29
+ readonly pmTime: number;
30
+ readonly n64Physics: boolean;
31
+ readonly velocity: Vec3;
32
+ readonly startVelocity: Vec3;
33
+ readonly origin: Vec3;
34
+ readonly mins: Vec3;
35
+ readonly maxs: Vec3;
36
+ readonly viewheight: number;
37
+ readonly trace: PmoveTraceFn;
38
+ readonly pointContents: PmovePointContentsFn;
39
+ }
40
+
41
+ export interface CategorizePositionResult {
42
+ readonly pmFlags: PmFlags;
43
+ readonly pmTime: number;
44
+ readonly onGround: boolean;
45
+ readonly groundTrace?: PmoveTraceResult;
46
+ readonly groundContents: ContentsFlag;
47
+ readonly waterlevel: number;
48
+ readonly watertype: ContentsFlag;
49
+ readonly impactDelta?: number;
50
+ }
51
+
52
+ /**
53
+ * Pure mirror of PM_CatagorizePosition from `rerelease/p_move.cpp`: traces a quarter-unit
54
+ * below the player bounds to determine whether they stand on solid ground, updates timers
55
+ * and pmflags accordingly, records the latest ground plane data, and recalculates waterlevel
56
+ * by probing feet/waist/viewheight samples.
57
+ */
58
+ export function categorizePosition(params: CategorizePositionParams): CategorizePositionResult {
59
+ const {
60
+ pmType,
61
+ n64Physics,
62
+ velocity,
63
+ startVelocity,
64
+ origin,
65
+ mins,
66
+ maxs,
67
+ viewheight,
68
+ trace,
69
+ pointContents,
70
+ } = params;
71
+
72
+ let pmFlags = params.pmFlags;
73
+ let pmTime = params.pmTime;
74
+ let impactDelta: number | undefined;
75
+ let onGround = hasPmFlag(pmFlags, PmFlag.OnGround);
76
+
77
+ let groundTrace: PmoveTraceResult | undefined;
78
+ let groundContents: ContentsFlag = CONTENTS_NONE;
79
+
80
+ const forceAirborne = velocity.z > LADDER_BYPASS_VELOCITY || pmType === PmType.Grapple;
81
+
82
+ if (forceAirborne) {
83
+ pmFlags = removePmFlag(pmFlags, PmFlag.OnGround);
84
+ onGround = false;
85
+ } else {
86
+ const end: Vec3 = { x: origin.x, y: origin.y, z: origin.z - GROUND_PROBE_DISTANCE };
87
+ const traceResult = trace(origin, end, mins, maxs);
88
+ groundTrace = traceResult;
89
+ groundContents = traceResult.contents ?? CONTENTS_NONE;
90
+
91
+ const planeNormal = traceResult.planeNormal;
92
+
93
+ let slantedGround =
94
+ traceResult.fraction < 1 && !!planeNormal && planeNormal.z < SLANTED_NORMAL_THRESHOLD;
95
+
96
+ if (slantedGround && planeNormal) {
97
+ const slantEnd = addVec3(origin, planeNormal);
98
+ const slantTrace = trace(origin, slantEnd, mins, maxs);
99
+ if (slantTrace.fraction < 1 && !slantTrace.startsolid) {
100
+ slantedGround = false;
101
+ }
102
+ }
103
+
104
+ if (
105
+ traceResult.fraction === 1 ||
106
+ !planeNormal ||
107
+ (slantedGround && !traceResult.startsolid)
108
+ ) {
109
+ pmFlags = removePmFlag(pmFlags, PmFlag.OnGround);
110
+ onGround = false;
111
+ } else {
112
+ onGround = true;
113
+
114
+ if (hasPmFlag(pmFlags, PmFlag.TimeWaterJump)) {
115
+ pmFlags &= ~WATERJUMP_CLEAR;
116
+ pmTime = 0;
117
+ }
118
+
119
+ const wasOnGround = hasPmFlag(pmFlags, PmFlag.OnGround);
120
+
121
+ if (!wasOnGround) {
122
+ if (
123
+ !n64Physics &&
124
+ velocity.z >= TRICK_VELOCITY_THRESHOLD &&
125
+ planeNormal.z >= TRICK_NORMAL_THRESHOLD &&
126
+ !hasPmFlag(pmFlags, PmFlag.Ducked)
127
+ ) {
128
+ pmFlags = addPmFlag(pmFlags, PmFlag.TimeTrick);
129
+ pmTime = TRICK_PM_TIME;
130
+ }
131
+
132
+ const clipped = clipVelocityVec3(velocity, planeNormal, IMPACT_CLIP_OVERBOUNCE);
133
+ impactDelta = startVelocity.z - clipped.z;
134
+ }
135
+
136
+ pmFlags = addPmFlag(pmFlags, PmFlag.OnGround);
137
+
138
+ if (!wasOnGround && (n64Physics || hasPmFlag(pmFlags, PmFlag.Ducked))) {
139
+ pmFlags = addPmFlag(pmFlags, PmFlag.TimeLand);
140
+ pmTime = LAND_PM_TIME;
141
+ }
142
+ }
143
+ }
144
+
145
+ const { waterlevel, watertype } = getWaterLevel({
146
+ origin,
147
+ mins,
148
+ viewheight,
149
+ pointContents,
150
+ });
151
+
152
+ return {
153
+ pmFlags,
154
+ pmTime,
155
+ onGround: hasPmFlag(pmFlags, PmFlag.OnGround),
156
+ groundTrace,
157
+ groundContents,
158
+ waterlevel,
159
+ watertype,
160
+ impactDelta,
161
+ };
162
+ }
@@ -0,0 +1,5 @@
1
+ // Matching rerelease/bg_local.h:18-24
2
+ export interface PmConfig {
3
+ airaccel: number;
4
+ n64_physics: boolean;
5
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Mirrors the Quake II rerelease `water_level_t` enumeration from `game.h`
3
+ * (lines 443-449). These numeric values are relied upon throughout the
4
+ * movement code when checking how submerged a player is, so we keep the same
5
+ * ordering to make future porting work straightforward.
6
+ */
7
+ export enum WaterLevel {
8
+ None = 0,
9
+ Feet = 1,
10
+ Waist = 2,
11
+ Under = 3,
12
+ }
13
+
14
+ /**
15
+ * Utility that matches the common rerelease checks that treat any level at or
16
+ * above the `WATER_WAIST` constant as "significantly submerged" for friction
17
+ * and current calculations.
18
+ */
19
+ export function isAtLeastWaistDeep(level: WaterLevel): boolean {
20
+ return level >= WaterLevel.Waist;
21
+ }
22
+
23
+ /**
24
+ * Returns true when the player is considered underwater (the `WATER_UNDER`
25
+ * case in the rerelease). This mirrors the places in `p_move.cpp` that gate
26
+ * effects such as breath timers and screen warping.
27
+ */
28
+ export function isUnderwater(level: WaterLevel): boolean {
29
+ return level === WaterLevel.Under;
30
+ }
31
+
32
+ /**
33
+ * Matches the Quake II rerelease `pmflags_t` bit layout from `game.h` so the
34
+ * shared helpers can manipulate the same flag words as the authoritative game
35
+ * and the client prediction layer.
36
+ */
37
+ export const enum PmFlag {
38
+ Ducked = 1 << 0,
39
+ JumpHeld = 1 << 1,
40
+ OnGround = 1 << 2,
41
+ TimeWaterJump = 1 << 3,
42
+ TimeLand = 1 << 4,
43
+ TimeTeleport = 1 << 5,
44
+ NoPositionalPrediction = 1 << 6,
45
+ OnLadder = 1 << 7,
46
+ NoAngularPrediction = 1 << 8,
47
+ IgnorePlayerCollision = 1 << 9,
48
+ TimeTrick = 1 << 10,
49
+ }
50
+
51
+ export type PmFlags = number;
52
+
53
+ export function hasPmFlag(flags: PmFlags, flag: PmFlag): boolean {
54
+ return (flags & flag) !== 0;
55
+ }
56
+
57
+ export function addPmFlag(flags: PmFlags, flag: PmFlag): PmFlags {
58
+ return flags | flag;
59
+ }
60
+
61
+ export function removePmFlag(flags: PmFlags, flag: PmFlag): PmFlags {
62
+ return flags & ~flag;
63
+ }
64
+
65
+ /**
66
+ * Player movement types mirrored from the rerelease `pmtype_t` enumeration.
67
+ * The exact numeric values matter when syncing pmove state across the network
68
+ * so we keep the same order as the C++ definition.
69
+ */
70
+ export enum PmType {
71
+ Normal = 0,
72
+ Grapple = 1,
73
+ NoClip = 2,
74
+ Spectator = 3,
75
+ Dead = 4,
76
+ Gib = 5,
77
+ Freeze = 6,
78
+ }
79
+
80
+ /**
81
+ * Bitmask constants for the `buttons` field on the Quake II player command
82
+ * structure. These mirror the rerelease `BUTTON_*` definitions so logic such as
83
+ * jump/crouch checks can be shared between the server and client.
84
+ */
85
+ export const enum PlayerButton {
86
+ None = 0,
87
+ Attack = 1 << 0,
88
+ Use = 1 << 1,
89
+ Holster = 1 << 2,
90
+ Jump = 1 << 3,
91
+ Crouch = 1 << 4,
92
+ Attack2 = 1 << 5,
93
+ Any = 1 << 7,
94
+ }
@@ -0,0 +1,287 @@
1
+ import type { ContentsFlag } from '../bsp/contents.js';
2
+ import {
3
+ CONTENTS_CURRENT_0,
4
+ CONTENTS_CURRENT_180,
5
+ CONTENTS_CURRENT_270,
6
+ CONTENTS_CURRENT_90,
7
+ CONTENTS_CURRENT_DOWN,
8
+ CONTENTS_CURRENT_UP,
9
+ CONTENTS_LADDER,
10
+ MASK_CURRENT,
11
+ } from '../bsp/contents.js';
12
+ import { addVec3, crossVec3, normalizeVec3, scaleVec3, ZERO_VEC3, type Vec3 } from '../math/vec3.js';
13
+ import { PlayerButton, WaterLevel, isAtLeastWaistDeep } from './constants.js';
14
+ import type { PmoveCmd, PmoveTraceFn } from './types.js';
15
+
16
+ export interface WaterCurrentParams {
17
+ readonly watertype: ContentsFlag;
18
+ readonly waterlevel: WaterLevel;
19
+ readonly onGround: boolean;
20
+ readonly waterSpeed: number;
21
+ }
22
+
23
+ export interface GroundCurrentParams {
24
+ readonly groundContents: ContentsFlag;
25
+ readonly scale?: number;
26
+ }
27
+
28
+ export interface AddCurrentsParams {
29
+ readonly wishVelocity: Vec3;
30
+ readonly onLadder: boolean;
31
+ readonly onGround: boolean;
32
+ readonly waterlevel: WaterLevel;
33
+ readonly watertype: ContentsFlag;
34
+ readonly groundContents: ContentsFlag;
35
+ readonly cmd: PmoveCmd;
36
+ readonly viewPitch: number;
37
+ readonly maxSpeed: number;
38
+ readonly ladderMod: number;
39
+ readonly waterSpeed: number;
40
+ readonly forward: Vec3;
41
+ readonly origin: Vec3;
42
+ readonly mins: Vec3;
43
+ readonly maxs: Vec3;
44
+ readonly trace?: PmoveTraceFn;
45
+ }
46
+
47
+ const DEFAULT_GROUND_CURRENT_SCALE = 100;
48
+ const DEFAULT_FORWARD_LADDER_CLAMP = 200;
49
+ const DEFAULT_SIDE_LADDER_CLAMP = 150;
50
+ const LADDER_HORIZONTAL_CAP = 25;
51
+ const LADDER_ASCEND_PITCH_THRESHOLD = 15;
52
+ const LADDER_TRACE_DISTANCE = 1;
53
+ const UP_VECTOR: Vec3 = { x: 0, y: 0, z: 1 };
54
+
55
+ const DEFAULT_TRACE: PmoveTraceFn = (_, end) => ({
56
+ fraction: 1,
57
+ endpos: end,
58
+ allsolid: false,
59
+ startsolid: false,
60
+ });
61
+
62
+ /**
63
+ * Mirrors the rerelease pattern in `p_move.cpp` (lines 730-765) that turns the
64
+ * directional CONTENTS_CURRENT_* flags into a unit-ish direction vector.
65
+ */
66
+ export function currentVectorFromContents(contents: ContentsFlag): Vec3 {
67
+ let x = 0;
68
+ let y = 0;
69
+ let z = 0;
70
+
71
+ if (contents & CONTENTS_CURRENT_0) {
72
+ x += 1;
73
+ }
74
+ if (contents & CONTENTS_CURRENT_90) {
75
+ y += 1;
76
+ }
77
+ if (contents & CONTENTS_CURRENT_180) {
78
+ x -= 1;
79
+ }
80
+ if (contents & CONTENTS_CURRENT_270) {
81
+ y -= 1;
82
+ }
83
+ if (contents & CONTENTS_CURRENT_UP) {
84
+ z += 1;
85
+ }
86
+ if (contents & CONTENTS_CURRENT_DOWN) {
87
+ z -= 1;
88
+ }
89
+
90
+ if (x === 0 && y === 0 && z === 0) {
91
+ return ZERO_VEC3;
92
+ }
93
+
94
+ return { x, y, z };
95
+ }
96
+
97
+ /**
98
+ * Computes the velocity contribution from water currents using the same rules
99
+ * as `PM_WaterMove`: the CONTENTS_CURRENT_* bits are turned into a direction
100
+ * vector, scaled by `pm_waterspeed`, and halved when the player only has their
101
+ * feet submerged while standing on solid ground.
102
+ */
103
+ export function waterCurrentVelocity(params: WaterCurrentParams): Vec3 {
104
+ const { watertype, waterlevel, onGround, waterSpeed } = params;
105
+
106
+ if ((watertype & MASK_CURRENT) === 0) {
107
+ return ZERO_VEC3;
108
+ }
109
+
110
+ const direction = currentVectorFromContents(watertype);
111
+ if (direction === ZERO_VEC3) {
112
+ return ZERO_VEC3;
113
+ }
114
+
115
+ let scale = waterSpeed;
116
+ if (waterlevel === WaterLevel.Feet && onGround) {
117
+ scale *= 0.5;
118
+ }
119
+
120
+ return scaleVec3(direction, scale);
121
+ }
122
+
123
+ /**
124
+ * Computes the conveyor-style velocity that should be applied while touching a
125
+ * ground plane that carries CONTENTS_CURRENT_* bits. The rerelease multiplies
126
+ * the direction vector by 100 units per second, so we expose the same default
127
+ * while allowing callers to override the scalar for tests.
128
+ */
129
+ export function groundCurrentVelocity(params: GroundCurrentParams): Vec3 {
130
+ const { groundContents, scale = DEFAULT_GROUND_CURRENT_SCALE } = params;
131
+
132
+ const direction = currentVectorFromContents(groundContents);
133
+ if (direction === ZERO_VEC3) {
134
+ return ZERO_VEC3;
135
+ }
136
+
137
+ return scaleVec3(direction, scale);
138
+ }
139
+
140
+ /**
141
+ * Pure mirror of PM_AddCurrents from rerelease `p_move.cpp`: handles ladder
142
+ * specific motion tweaks, water currents, and conveyor-style ground currents
143
+ * before pmove acceleration is applied.
144
+ */
145
+ export function applyPmoveAddCurrents(params: AddCurrentsParams): Vec3 {
146
+ const {
147
+ wishVelocity,
148
+ onLadder,
149
+ onGround,
150
+ waterlevel,
151
+ watertype,
152
+ groundContents,
153
+ cmd,
154
+ viewPitch,
155
+ maxSpeed,
156
+ ladderMod,
157
+ waterSpeed,
158
+ forward,
159
+ origin,
160
+ mins,
161
+ maxs,
162
+ trace = DEFAULT_TRACE,
163
+ } = params;
164
+
165
+ let adjusted = wishVelocity;
166
+
167
+ if (onLadder) {
168
+ adjusted = applyLadderAdjustments({
169
+ wishVelocity: adjusted,
170
+ cmd,
171
+ waterlevel,
172
+ viewPitch,
173
+ maxSpeed,
174
+ ladderMod,
175
+ onGround,
176
+ forward,
177
+ origin,
178
+ mins,
179
+ maxs,
180
+ trace,
181
+ });
182
+ }
183
+
184
+ const waterVelocity = waterCurrentVelocity({ watertype, waterlevel, onGround, waterSpeed });
185
+ if (waterVelocity !== ZERO_VEC3) {
186
+ adjusted = addVec3(adjusted, waterVelocity);
187
+ }
188
+
189
+ if (onGround) {
190
+ const groundVelocity = groundCurrentVelocity({ groundContents });
191
+ if (groundVelocity !== ZERO_VEC3) {
192
+ adjusted = addVec3(adjusted, groundVelocity);
193
+ }
194
+ }
195
+
196
+ return adjusted;
197
+ }
198
+
199
+ interface LadderAdjustParams {
200
+ readonly wishVelocity: Vec3;
201
+ readonly cmd: PmoveCmd;
202
+ readonly waterlevel: WaterLevel;
203
+ readonly viewPitch: number;
204
+ readonly maxSpeed: number;
205
+ readonly ladderMod: number;
206
+ readonly onGround: boolean;
207
+ readonly forward: Vec3;
208
+ readonly origin: Vec3;
209
+ readonly mins: Vec3;
210
+ readonly maxs: Vec3;
211
+ readonly trace: PmoveTraceFn;
212
+ }
213
+
214
+ function applyLadderAdjustments(params: LadderAdjustParams): Vec3 {
215
+ const { wishVelocity, cmd, waterlevel, viewPitch, maxSpeed, ladderMod, onGround, forward, origin, mins, maxs, trace } = params;
216
+ const buttons = cmd.buttons ?? 0;
217
+ let adjusted = { ...wishVelocity };
218
+
219
+ if ((buttons & (PlayerButton.Jump | PlayerButton.Crouch)) !== 0) {
220
+ const ladderSpeed = isAtLeastWaistDeep(waterlevel) ? maxSpeed : DEFAULT_FORWARD_LADDER_CLAMP;
221
+ adjusted = {
222
+ ...adjusted,
223
+ z: buttons & PlayerButton.Jump ? ladderSpeed : -ladderSpeed,
224
+ };
225
+ } else if (cmd.forwardmove) {
226
+ const clamped = clamp(cmd.forwardmove, -DEFAULT_FORWARD_LADDER_CLAMP, DEFAULT_FORWARD_LADDER_CLAMP);
227
+ if (cmd.forwardmove > 0) {
228
+ const climb = viewPitch < LADDER_ASCEND_PITCH_THRESHOLD ? clamped : -clamped;
229
+ adjusted = { ...adjusted, z: climb };
230
+ } else {
231
+ if (!onGround) {
232
+ adjusted = { ...adjusted, x: 0, y: 0 };
233
+ }
234
+ adjusted = { ...adjusted, z: clamped };
235
+ }
236
+ } else {
237
+ adjusted = { ...adjusted, z: 0 };
238
+ }
239
+
240
+ if (!onGround) {
241
+ if (cmd.sidemove) {
242
+ let sideSpeed = clamp(cmd.sidemove, -DEFAULT_SIDE_LADDER_CLAMP, DEFAULT_SIDE_LADDER_CLAMP);
243
+ if (waterlevel < WaterLevel.Waist) {
244
+ sideSpeed *= ladderMod;
245
+ }
246
+
247
+ const flatForward = normalizeVec3({ x: forward.x, y: forward.y, z: 0 });
248
+ if (flatForward.x !== 0 || flatForward.y !== 0) {
249
+ const spot = addVec3(origin, scaleVec3(flatForward, LADDER_TRACE_DISTANCE));
250
+ const tr = trace(origin, spot, mins, maxs);
251
+ if (
252
+ tr.fraction !== 1 &&
253
+ !tr.allsolid &&
254
+ tr.contents !== undefined &&
255
+ (tr.contents & CONTENTS_LADDER) !== 0 &&
256
+ tr.planeNormal
257
+ ) {
258
+ const right = crossVec3(tr.planeNormal, UP_VECTOR);
259
+ adjusted = { ...adjusted, x: 0, y: 0 };
260
+ adjusted = addVec3(adjusted, scaleVec3(right, -sideSpeed));
261
+ }
262
+ }
263
+ } else {
264
+ adjusted = {
265
+ ...adjusted,
266
+ x: clampHorizontal(adjusted.x),
267
+ y: clampHorizontal(adjusted.y),
268
+ };
269
+ }
270
+ }
271
+
272
+ return adjusted;
273
+ }
274
+
275
+ function clamp(value: number, min: number, max: number): number {
276
+ return Math.max(min, Math.min(max, value));
277
+ }
278
+
279
+ function clampHorizontal(value: number): number {
280
+ if (value < -LADDER_HORIZONTAL_CAP) {
281
+ return -LADDER_HORIZONTAL_CAP;
282
+ }
283
+ if (value > LADDER_HORIZONTAL_CAP) {
284
+ return LADDER_HORIZONTAL_CAP;
285
+ }
286
+ return value;
287
+ }
@@ -0,0 +1,40 @@
1
+ import type { Vec3 } from '../math/vec3.js';
2
+ import { PmFlag, type PmFlags, PmType } from './constants.js';
3
+
4
+ export interface PlayerDimensions {
5
+ readonly mins: Vec3;
6
+ readonly maxs: Vec3;
7
+ readonly viewheight: number;
8
+ }
9
+
10
+ function createVec3(x: number, y: number, z: number): Vec3 {
11
+ return { x, y, z } satisfies Vec3;
12
+ }
13
+
14
+ /**
15
+ * Pure mirror of PM_SetDimensions from rerelease `p_move.cpp`.
16
+ * Computes the mins/maxs/viewheight triplet for a player based on
17
+ * their movement type and ducked flag without mutating inputs.
18
+ */
19
+ export function computePlayerDimensions(pmType: PmType, pmFlags: PmFlags): PlayerDimensions {
20
+ const minsBase = createVec3(-16, -16, 0);
21
+ const maxsBase = createVec3(16, 16, 16);
22
+
23
+ if (pmType === PmType.Gib) {
24
+ return {
25
+ mins: minsBase,
26
+ maxs: maxsBase,
27
+ viewheight: 8,
28
+ } satisfies PlayerDimensions;
29
+ }
30
+
31
+ const ducked = pmType === PmType.Dead || (pmFlags & PmFlag.Ducked) !== 0;
32
+ const mins = createVec3(minsBase.x, minsBase.y, -24);
33
+ const maxs = createVec3(maxsBase.x, maxsBase.y, ducked ? 4 : 32);
34
+
35
+ return {
36
+ mins,
37
+ maxs,
38
+ viewheight: ducked ? -2 : 22,
39
+ } satisfies PlayerDimensions;
40
+ }