@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,446 @@
1
+ import type { Vec3 } from '../math/vec3.js';
2
+ import { addVec3, dotVec3, lengthVec3, normalizeVec3, scaleVec3 } from '../math/vec3.js';
3
+ import { angleVectors } from '../math/angles.js';
4
+ import type {
5
+ PmoveAccelerateParams,
6
+ PmoveCmd,
7
+ PmoveFrictionParams,
8
+ PmoveWishParams,
9
+ PmoveWishResult,
10
+ PmoveState,
11
+ PmoveImports,
12
+ PmoveTraceResult
13
+ } from './types.js';
14
+ import { PlayerButton, PmFlag, PmType, addPmFlag, removePmFlag } from './constants.js';
15
+ import { checkJump } from './jump.js';
16
+ import { applyPmoveAirMove, applyPmoveWaterMove, applyPmoveWalkMove } from './move.js';
17
+ import { categorizePosition } from './categorize.js';
18
+ import { checkDuckState, DuckTraceParams } from './duck.js';
19
+ // import { updateViewOffsets } from './view.js';
20
+
21
+ const FRAMETIME = 0.025; // Define FRAMETIME here or import if available in constants? Using local definition for now as per previous context.
22
+
23
+ /**
24
+ * Pure version of PM_Friction from rerelease p_move.cpp.
25
+ * Handles ground and water friction and returns a new velocity.
26
+ */
27
+ export function applyPmoveFriction(params: PmoveFrictionParams): Vec3 {
28
+ const {
29
+ velocity,
30
+ frametime,
31
+ onGround,
32
+ groundIsSlick,
33
+ onLadder,
34
+ waterlevel,
35
+ pmFriction,
36
+ pmStopSpeed,
37
+ pmWaterFriction,
38
+ } = params;
39
+
40
+ const speed = lengthVec3(velocity);
41
+
42
+ // Matches the "if (speed < 1)" early-out: clears X/Y but preserves Z.
43
+ if (speed < 1) {
44
+ return { x: 0, y: 0, z: velocity.z };
45
+ }
46
+
47
+ let drop = 0;
48
+
49
+ // Ground friction (or ladder)
50
+ if ((onGround && !groundIsSlick) || onLadder) {
51
+ const control = speed < pmStopSpeed ? pmStopSpeed : speed;
52
+ const friction = pmFriction;
53
+ drop += control * friction * frametime;
54
+ }
55
+
56
+ // Water friction (only when not on ladder)
57
+ if (waterlevel > 0 && !onLadder) {
58
+ drop += speed * pmWaterFriction * waterlevel * frametime;
59
+ }
60
+
61
+ let newspeed = speed - drop;
62
+ if (newspeed < 0) {
63
+ newspeed = 0;
64
+ }
65
+
66
+ if (newspeed === speed) {
67
+ return velocity;
68
+ }
69
+
70
+ const scale = newspeed / speed;
71
+ return scaleVec3(velocity, scale);
72
+ }
73
+
74
+ /**
75
+ * Pure version of PM_Accelerate from rerelease p_move.cpp.
76
+ * Returns a new velocity with wishdir/wishspeed acceleration applied.
77
+ */
78
+ export function applyPmoveAccelerate(params: PmoveAccelerateParams): Vec3 {
79
+ const { velocity, wishdir, wishspeed, accel, frametime } = params;
80
+
81
+ const currentSpeed = dotVec3(velocity, wishdir);
82
+ const addSpeed = wishspeed - currentSpeed;
83
+
84
+ if (addSpeed <= 0) {
85
+ return velocity;
86
+ }
87
+
88
+ let accelSpeed = accel * frametime * wishspeed;
89
+ if (accelSpeed > addSpeed) {
90
+ accelSpeed = addSpeed;
91
+ }
92
+
93
+ return {
94
+ x: velocity.x + wishdir.x * accelSpeed,
95
+ y: velocity.y + wishdir.y * accelSpeed,
96
+ z: velocity.z + wishdir.z * accelSpeed,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Mirrors PM_AirAccelerate in rerelease `p_move.cpp` (lines ~612-636): wishspeed is clamped
102
+ * to 30 for the addspeed calculation but the acceleration magnitude still uses the full wishspeed.
103
+ */
104
+ export function applyPmoveAirAccelerate(params: PmoveAccelerateParams): Vec3 {
105
+ const { velocity, wishdir, wishspeed, accel, frametime } = params;
106
+
107
+ const wishspd = Math.min(wishspeed, 30);
108
+ const currentSpeed = dotVec3(velocity, wishdir);
109
+ const addSpeed = wishspd - currentSpeed;
110
+
111
+ if (addSpeed <= 0) {
112
+ return velocity;
113
+ }
114
+
115
+ let accelSpeed = accel * wishspeed * frametime;
116
+ if (accelSpeed > addSpeed) {
117
+ accelSpeed = addSpeed;
118
+ }
119
+
120
+ return {
121
+ x: velocity.x + wishdir.x * accelSpeed,
122
+ y: velocity.y + wishdir.y * accelSpeed,
123
+ z: velocity.z + wishdir.z * accelSpeed,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Pure mirror of PM_CmdScale from rerelease `p_move.cpp`. Computes the scalar applied to
129
+ * the command directional inputs so that the resulting wish velocity caps at `maxSpeed`
130
+ * regardless of the directional mix.
131
+ */
132
+ export function pmoveCmdScale(cmd: PmoveCmd, maxSpeed: number): number {
133
+ const forward = Math.abs(cmd.forwardmove);
134
+ const side = Math.abs(cmd.sidemove);
135
+ const up = Math.abs(cmd.upmove);
136
+
137
+ const max = Math.max(forward, side, up);
138
+ if (max === 0) {
139
+ return 0;
140
+ }
141
+
142
+ const total = Math.sqrt(cmd.forwardmove * cmd.forwardmove + cmd.sidemove * cmd.sidemove + cmd.upmove * cmd.upmove);
143
+ return (maxSpeed * max) / (127 * total);
144
+ }
145
+
146
+ /**
147
+ * Computes wishdir/wishspeed for ground/air movement as done in PM_AirMove and
148
+ * PM_GroundMove. Z is forced to zero and wishspeed is clamped to maxSpeed, matching
149
+ * the rerelease p_move.cpp helpers before they call PM_Accelerate/PM_AirAccelerate.
150
+ */
151
+ export function buildAirGroundWish(params: PmoveWishParams): PmoveWishResult {
152
+ const { forward, right, cmd, maxSpeed } = params;
153
+
154
+ let wishvel = {
155
+ x: forward.x * cmd.forwardmove + right.x * cmd.sidemove,
156
+ y: forward.y * cmd.forwardmove + right.y * cmd.sidemove,
157
+ z: 0,
158
+ } satisfies Vec3;
159
+
160
+ let wishspeed = lengthVec3(wishvel);
161
+ if (wishspeed > maxSpeed) {
162
+ const scale = maxSpeed / wishspeed;
163
+ wishvel = scaleVec3(wishvel, scale);
164
+ wishspeed = maxSpeed;
165
+ }
166
+
167
+ return {
168
+ wishdir: wishspeed === 0 ? wishvel : normalizeVec3(wishvel),
169
+ wishspeed,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Computes the wishdir/wishspeed mix for water movement, matching PM_WaterMove in
175
+ * rerelease p_move.cpp: includes the upward bias when no strong upmove is requested,
176
+ * clamps wishspeed to maxSpeed, and halves the returned wishspeed before acceleration.
177
+ */
178
+ export function buildWaterWish(params: PmoveWishParams): PmoveWishResult {
179
+ const { forward, right, cmd, maxSpeed } = params;
180
+
181
+ // Use full 3D components for water movement
182
+ let wishvel = {
183
+ x: forward.x * cmd.forwardmove + right.x * cmd.sidemove,
184
+ y: forward.y * cmd.forwardmove + right.y * cmd.sidemove,
185
+ z: forward.z * cmd.forwardmove + right.z * cmd.sidemove,
186
+ } satisfies Vec3;
187
+
188
+ if (cmd.upmove > 10) {
189
+ wishvel = addVec3(wishvel, { x: 0, y: 0, z: cmd.upmove });
190
+ } else if (cmd.upmove < -10) {
191
+ wishvel = addVec3(wishvel, { x: 0, y: 0, z: cmd.upmove });
192
+ } else if (Math.abs(cmd.forwardmove) < 10 && Math.abs(cmd.sidemove) < 10) {
193
+ // Standard drift down when no vertical input AND no significant horizontal input
194
+ // Matches Quake 2 rerelease behavior (sinking slowly)
195
+ wishvel = addVec3(wishvel, { x: 0, y: 0, z: -60 });
196
+ } else {
197
+ // When moving horizontally but not vertically, drift slightly up
198
+ // This matches the "else { wishvel[2] += 10 }" logic in PM_WaterMove
199
+ wishvel = addVec3(wishvel, { x: 0, y: 0, z: 10 });
200
+ }
201
+
202
+ let wishspeed = lengthVec3(wishvel);
203
+ if (wishspeed > maxSpeed) {
204
+ const scale = maxSpeed / wishspeed;
205
+ wishvel = scaleVec3(wishvel, scale);
206
+ wishspeed = maxSpeed;
207
+ }
208
+
209
+ wishspeed *= 0.5;
210
+
211
+ return {
212
+ wishdir: wishspeed === 0 ? wishvel : normalizeVec3(wishvel),
213
+ wishspeed,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Runs the full player movement simulation for a single frame.
219
+ */
220
+ export function runPmove(state: PmoveState, imports: PmoveImports): PmoveState {
221
+ if (state.pmType === PmType.Dead) {
222
+ return state;
223
+ }
224
+
225
+ let nextState = { ...state };
226
+
227
+ // Categorize Position
228
+ const catResult = categorizePosition({
229
+ pmType: nextState.pmType,
230
+ pmFlags: nextState.pmFlags,
231
+ pmTime: 0,
232
+ n64Physics: false,
233
+ velocity: nextState.velocity,
234
+ startVelocity: nextState.velocity,
235
+ origin: nextState.origin,
236
+ mins: nextState.mins,
237
+ maxs: nextState.maxs,
238
+ viewheight: nextState.viewHeight,
239
+ trace: imports.trace,
240
+ pointContents: imports.pointcontents
241
+ });
242
+
243
+ // Merge result back to state
244
+ nextState.pmFlags = catResult.pmFlags;
245
+ nextState.waterlevel = catResult.waterlevel;
246
+ nextState.watertype = catResult.watertype;
247
+
248
+ // Check Ducking (Before Jump)
249
+ const duckResult = checkDuckState({
250
+ pmType: nextState.pmType,
251
+ pmFlags: nextState.pmFlags,
252
+ buttons: nextState.cmd.buttons,
253
+ waterlevel: nextState.waterlevel,
254
+ hasGroundEntity: (nextState.pmFlags & PmFlag.OnGround) !== 0,
255
+ onLadder: false,
256
+ n64Physics: false,
257
+ origin: nextState.origin,
258
+ mins: nextState.mins,
259
+ maxs: nextState.maxs,
260
+ trace: (params: DuckTraceParams): PmoveTraceResult => {
261
+ // Adapter from DuckTraceFn (obj) to PmoveTraceFn (args)
262
+ return imports.trace(params.start, params.end, params.mins, params.maxs);
263
+ }
264
+ });
265
+
266
+ nextState.pmFlags = duckResult.pmFlags;
267
+ nextState.mins = duckResult.mins;
268
+ nextState.maxs = duckResult.maxs;
269
+ nextState.viewHeight = duckResult.viewheight;
270
+
271
+ // Check Jump
272
+ const jumpResult = checkJump({
273
+ pmFlags: nextState.pmFlags,
274
+ pmType: nextState.pmType,
275
+ buttons: nextState.cmd.buttons,
276
+ waterlevel: nextState.waterlevel,
277
+ onGround: (nextState.pmFlags & PmFlag.OnGround) !== 0,
278
+ velocity: nextState.velocity,
279
+ origin: nextState.origin
280
+ });
281
+
282
+ nextState.pmFlags = jumpResult.pmFlags;
283
+ nextState.velocity = jumpResult.velocity;
284
+ nextState.origin = jumpResult.origin;
285
+
286
+ if (jumpResult.onGround !== ((nextState.pmFlags & PmFlag.OnGround) !== 0)) {
287
+ if (jumpResult.onGround) {
288
+ nextState.pmFlags = addPmFlag(nextState.pmFlags, PmFlag.OnGround);
289
+ } else {
290
+ nextState.pmFlags = removePmFlag(nextState.pmFlags, PmFlag.OnGround);
291
+ }
292
+ }
293
+
294
+ // Frictional movement
295
+ const onGround = (nextState.pmFlags & PmFlag.OnGround) !== 0;
296
+
297
+ // Apply friction
298
+ const velocityBeforeFriction = nextState.velocity;
299
+ nextState.velocity = applyPmoveFriction({
300
+ velocity: nextState.velocity,
301
+ frametime: FRAMETIME,
302
+ onGround,
303
+ groundIsSlick: false,
304
+ onLadder: false, // Defaulting to false for now as ladder logic is complex
305
+ waterlevel: nextState.waterlevel,
306
+ pmFriction: 6, // Default
307
+ pmStopSpeed: 100, // Default
308
+ pmWaterFriction: 1 // Default
309
+ });
310
+
311
+ // Calculate view vectors from angles
312
+ const { forward, right } = angleVectors(nextState.viewAngles);
313
+
314
+ if (nextState.pmType === PmType.NoClip) {
315
+ // PM_NoclipMove
316
+ // Simplified noclip
317
+ const wishvel = {
318
+ x: forward.x * nextState.cmd.forwardmove + right.x * nextState.cmd.sidemove,
319
+ y: forward.y * nextState.cmd.forwardmove + right.y * nextState.cmd.sidemove,
320
+ z: nextState.cmd.upmove
321
+ };
322
+ const scale = FRAMETIME; // Just move by velocity
323
+ // Actually we need to apply velocity based on input
324
+ // But sticking to just what's needed for jumping/movement:
325
+ nextState.velocity = wishvel; // Simple override for noclip
326
+ nextState.origin = {
327
+ x: nextState.origin.x + wishvel.x * scale,
328
+ y: nextState.origin.y + wishvel.y * scale,
329
+ z: nextState.origin.z + wishvel.z * scale
330
+ };
331
+
332
+ } else if (nextState.waterlevel >= 2) {
333
+ const outcome = applyPmoveWaterMove({
334
+ origin: nextState.origin,
335
+ velocity: nextState.velocity,
336
+ frametime: FRAMETIME,
337
+ mins: nextState.mins,
338
+ maxs: nextState.maxs,
339
+ trace: imports.trace,
340
+ cmd: nextState.cmd,
341
+ forward,
342
+ right,
343
+ pmFlags: nextState.pmFlags,
344
+ onGround,
345
+ pmMaxSpeed: 300,
346
+ pmDuckSpeed: 100,
347
+ pmWaterAccelerate: 4,
348
+ pmWaterSpeed: 400,
349
+ onLadder: false,
350
+ watertype: nextState.watertype,
351
+ groundContents: 0, // Should be passed in?
352
+ waterlevel: nextState.waterlevel,
353
+ viewPitch: nextState.viewAngles.x,
354
+ ladderMod: 1,
355
+ stepSize: 18 // Added stepSize for consistency, though water move might not use it heavily
356
+ });
357
+ nextState.origin = outcome.origin;
358
+ nextState.velocity = outcome.velocity;
359
+
360
+ } else if ((nextState.pmFlags & PmFlag.OnGround) === 0) {
361
+ const outcome = applyPmoveAirMove({
362
+ origin: nextState.origin,
363
+ velocity: nextState.velocity,
364
+ frametime: FRAMETIME,
365
+ mins: nextState.mins,
366
+ maxs: nextState.maxs,
367
+ trace: imports.trace,
368
+ cmd: nextState.cmd,
369
+ forward,
370
+ right,
371
+ pmFlags: nextState.pmFlags,
372
+ onGround,
373
+ gravity: nextState.gravity,
374
+ pmType: nextState.pmType,
375
+ pmAccelerate: 10,
376
+ pmAirAccelerate: 1,
377
+ pmMaxSpeed: 300,
378
+ pmDuckSpeed: 100,
379
+ onLadder: false,
380
+ waterlevel: nextState.waterlevel,
381
+ watertype: nextState.watertype,
382
+ groundContents: 0,
383
+ viewPitch: nextState.viewAngles.x,
384
+ ladderMod: 1,
385
+ pmWaterSpeed: 400,
386
+ stepSize: 18 // Added stepSize
387
+ });
388
+ nextState.origin = outcome.origin;
389
+ nextState.velocity = outcome.velocity;
390
+
391
+ } else {
392
+ const outcome = applyPmoveWalkMove({
393
+ origin: nextState.origin,
394
+ velocity: nextState.velocity,
395
+ frametime: FRAMETIME,
396
+ mins: nextState.mins,
397
+ maxs: nextState.maxs,
398
+ trace: imports.trace,
399
+ cmd: nextState.cmd,
400
+ forward,
401
+ right,
402
+ pmFlags: nextState.pmFlags,
403
+ onGround,
404
+ gravity: nextState.gravity,
405
+ pmType: nextState.pmType,
406
+ pmAccelerate: 10,
407
+ pmMaxSpeed: 300,
408
+ pmDuckSpeed: 100,
409
+ onLadder: false,
410
+ waterlevel: nextState.waterlevel,
411
+ watertype: nextState.watertype,
412
+ groundContents: 0,
413
+ viewPitch: nextState.viewAngles.x,
414
+ ladderMod: 1,
415
+ pmWaterSpeed: 400,
416
+ stepSize: 18 // Added stepSize
417
+ });
418
+ nextState.origin = outcome.origin;
419
+ nextState.velocity = outcome.velocity;
420
+ }
421
+
422
+ // Categorize Position again at end of frame
423
+ const catResultEnd = categorizePosition({
424
+ pmType: nextState.pmType,
425
+ pmFlags: nextState.pmFlags,
426
+ pmTime: 0,
427
+ n64Physics: false,
428
+ velocity: nextState.velocity,
429
+ startVelocity: nextState.velocity,
430
+ origin: nextState.origin,
431
+ mins: nextState.mins,
432
+ maxs: nextState.maxs,
433
+ viewheight: nextState.viewHeight,
434
+ trace: imports.trace,
435
+ pointContents: imports.pointcontents
436
+ });
437
+
438
+ nextState.pmFlags = catResultEnd.pmFlags;
439
+ nextState.waterlevel = catResultEnd.waterlevel;
440
+ nextState.watertype = catResultEnd.watertype;
441
+
442
+ // Update view offsets (bobbing, etc)
443
+ // nextState = updateViewOffsets(nextState);
444
+
445
+ return nextState;
446
+ }
@@ -0,0 +1,267 @@
1
+ import { addVec3, ZERO_VEC3, clipVelocityVec3, crossVec3, dotVec3, scaleVec3, type Vec3 } from '../math/vec3.js';
2
+ import type { PmoveTraceFn } from './types.js';
3
+
4
+ const DEFAULT_MAX_CLIP_PLANES = 5;
5
+ const DEFAULT_MAX_BUMPS = 4;
6
+ const DEFAULT_STEP_SIZE = 18;
7
+ const MIN_STEP_NORMAL = 0.7;
8
+
9
+ export const SLIDEMOVE_BLOCKED_FLOOR = 1;
10
+ export const SLIDEMOVE_BLOCKED_WALL = 2;
11
+
12
+ export interface SlideMoveResult {
13
+ readonly velocity: Vec3;
14
+ readonly planes: readonly Vec3[];
15
+ readonly stopped: boolean;
16
+ }
17
+
18
+ export interface SlideMoveParams {
19
+ readonly origin: Vec3;
20
+ readonly velocity: Vec3;
21
+ readonly frametime: number;
22
+ readonly overbounce: number;
23
+ readonly trace: PmoveTraceFn;
24
+ readonly maxBumps?: number;
25
+ readonly maxClipPlanes?: number;
26
+ readonly mins?: Vec3;
27
+ readonly maxs?: Vec3;
28
+ /**
29
+ * Mirrors the pm->s.pm_time check in PM_StepSlideMove_Generic: if true, the
30
+ * returned velocity is reset to the primal velocity after collision
31
+ * resolution so time-based effects (like knockbacks) don't dampen.
32
+ */
33
+ readonly hasTime?: boolean;
34
+ }
35
+
36
+ export interface SlideMoveOutcome extends SlideMoveResult {
37
+ readonly origin: Vec3;
38
+ readonly blocked: number;
39
+ }
40
+
41
+ export interface StepSlideMoveParams extends SlideMoveParams {
42
+ readonly mins: Vec3;
43
+ readonly maxs: Vec3;
44
+ readonly stepSize?: number;
45
+ }
46
+
47
+ export interface StepSlideMoveOutcome extends SlideMoveOutcome {
48
+ readonly stepped: boolean;
49
+ readonly stepHeight: number;
50
+ readonly stepNormal?: Vec3;
51
+ }
52
+
53
+ /**
54
+ * Resolves a sequence of collision planes against a primal velocity using the same
55
+ * plane iteration logic seen in PM_StepSlideMove_Generic (rerelease p_move.cpp).
56
+ * The incoming planes should be ordered as they were encountered during traces;
57
+ * the function will accumulate them, clip the velocity to be parallel to all planes,
58
+ * and return zero velocity when three planes form an unresolvable corner or when
59
+ * the adjusted velocity would oppose the primal direction.
60
+ */
61
+ export function resolveSlideMove(
62
+ initialVelocity: Vec3,
63
+ planesEncountered: readonly Vec3[],
64
+ overbounce: number,
65
+ maxClipPlanes = DEFAULT_MAX_CLIP_PLANES,
66
+ primalVelocity: Vec3 = initialVelocity,
67
+ ): SlideMoveResult {
68
+ if (planesEncountered.length === 0) {
69
+ return { velocity: initialVelocity, planes: [], stopped: false };
70
+ }
71
+
72
+ const planes: Vec3[] = [];
73
+ let velocity: Vec3 = initialVelocity;
74
+
75
+ for (const plane of planesEncountered) {
76
+ if (planes.length >= maxClipPlanes) {
77
+ return { velocity: ZERO_VEC3, planes, stopped: true };
78
+ }
79
+
80
+ // Skip near-duplicate planes to mirror the epsilon guard in PM_StepSlideMove_Generic.
81
+ const duplicate = planes.find((existing) => dotVec3(existing, plane) > 0.99);
82
+ if (duplicate) {
83
+ continue;
84
+ }
85
+
86
+ planes.push(plane);
87
+
88
+ let clipped: Vec3 | undefined;
89
+ let i = 0;
90
+ for (; i < planes.length; i++) {
91
+ const candidate = clipVelocityVec3(velocity, planes[i], overbounce);
92
+
93
+ let j = 0;
94
+ for (; j < planes.length; j++) {
95
+ if (j === i) continue;
96
+ if (dotVec3(candidate, planes[j]) < 0) break;
97
+ }
98
+
99
+ if (j === planes.length) {
100
+ clipped = candidate;
101
+ break;
102
+ }
103
+ }
104
+
105
+ if (clipped) {
106
+ velocity = clipped;
107
+ } else {
108
+ if (planes.length !== 2) {
109
+ return { velocity: ZERO_VEC3, planes, stopped: true };
110
+ }
111
+
112
+ const dir = crossVec3(planes[0], planes[1]);
113
+ const d = dotVec3(dir, velocity);
114
+ velocity = scaleVec3(dir, d);
115
+ }
116
+
117
+ // If velocity reversed relative to the primal direction, stop to avoid oscillations.
118
+ if (dotVec3(velocity, primalVelocity) <= 0) {
119
+ return { velocity: ZERO_VEC3, planes, stopped: true };
120
+ }
121
+ }
122
+
123
+ const stopped = velocity.x === 0 && velocity.y === 0 && velocity.z === 0;
124
+ return { velocity, planes, stopped };
125
+ }
126
+
127
+ /**
128
+ * Pure mirror of PM_SlideMoveGeneric from rerelease `p_move.cpp` (minus gravity/step handling).
129
+ * Uses a caller-provided trace to collect collision planes, accumulates them through
130
+ * `resolveSlideMove`, and returns the resulting origin/velocity/blocking state.
131
+ */
132
+ export function slideMove(params: SlideMoveParams): SlideMoveOutcome {
133
+ const {
134
+ origin: initialOrigin,
135
+ velocity: initialVelocity,
136
+ frametime,
137
+ overbounce,
138
+ trace,
139
+ maxBumps = DEFAULT_MAX_BUMPS,
140
+ maxClipPlanes = DEFAULT_MAX_CLIP_PLANES,
141
+ mins,
142
+ maxs,
143
+ hasTime = false,
144
+ } = params;
145
+
146
+ let origin = initialOrigin;
147
+ let velocity = initialVelocity;
148
+ const planes: Vec3[] = [];
149
+ const primalVelocity = initialVelocity;
150
+ let timeLeft = frametime;
151
+ let blocked = 0;
152
+
153
+ for (let bump = 0; bump < maxBumps; bump++) {
154
+ if (velocity.x === 0 && velocity.y === 0 && velocity.z === 0) {
155
+ break;
156
+ }
157
+
158
+ const end = addVec3(origin, scaleVec3(velocity, timeLeft));
159
+ const tr = trace(origin, end, mins, maxs);
160
+
161
+ if (tr.allsolid) {
162
+ const velocity = hasTime ? primalVelocity : ZERO_VEC3;
163
+ return { origin: tr.endpos, velocity, planes, stopped: true, blocked };
164
+ }
165
+
166
+ if (tr.startsolid) {
167
+ const velocity = hasTime ? primalVelocity : ZERO_VEC3;
168
+ return { origin: tr.endpos, velocity, planes, stopped: true, blocked };
169
+ }
170
+
171
+ if (tr.fraction > 0) {
172
+ origin = tr.endpos;
173
+ }
174
+
175
+ if (tr.fraction === 1) {
176
+ break;
177
+ }
178
+
179
+ if (!tr.planeNormal) {
180
+ const velocity = hasTime ? primalVelocity : ZERO_VEC3;
181
+ return { origin, velocity, planes, stopped: true, blocked };
182
+ }
183
+
184
+ if (tr.planeNormal.z > 0.7) {
185
+ blocked |= SLIDEMOVE_BLOCKED_FLOOR;
186
+ }
187
+ if (tr.planeNormal.z === 0) {
188
+ blocked |= SLIDEMOVE_BLOCKED_WALL;
189
+ }
190
+
191
+ planes.push(tr.planeNormal);
192
+ timeLeft -= timeLeft * tr.fraction;
193
+
194
+ const resolved = resolveSlideMove(velocity, planes, overbounce, maxClipPlanes, primalVelocity);
195
+ velocity = resolved.velocity;
196
+ planes.splice(0, planes.length, ...resolved.planes);
197
+
198
+ if (primalVelocity.z > 0 && velocity.z < 0) {
199
+ velocity = { ...velocity, z: 0 };
200
+ }
201
+
202
+ if (resolved.stopped) {
203
+ const velocityOut = hasTime ? primalVelocity : velocity;
204
+ return { origin, velocity: velocityOut, planes, stopped: true, blocked };
205
+ }
206
+ }
207
+
208
+ const velocityOut = hasTime ? primalVelocity : velocity;
209
+ return { origin, velocity: velocityOut, planes, stopped: velocityOut.x === 0 && velocityOut.y === 0 && velocityOut.z === 0, blocked };
210
+ }
211
+
212
+ /**
213
+ * Mirrors PM_StepSlideMove (rerelease p_move.cpp) in a pure form: attempts a
214
+ * regular slide move, then retries from a stepped-up position when the first
215
+ * attempt was blocked. The function compares planar distance traveled and the
216
+ * steepness of the landing plane to decide whether to keep the step.
217
+ */
218
+ export function stepSlideMove(params: StepSlideMoveParams): StepSlideMoveOutcome {
219
+ const { mins, maxs, stepSize = DEFAULT_STEP_SIZE, ...rest } = params;
220
+
221
+ const startOrigin = params.origin;
222
+ const startVelocity = params.velocity;
223
+
224
+ const downResult = slideMove({ ...rest, mins, maxs });
225
+
226
+ const upTarget = addVec3(startOrigin, { x: 0, y: 0, z: stepSize });
227
+ const upTrace = rest.trace(startOrigin, upTarget, mins, maxs);
228
+ if (upTrace.allsolid) {
229
+ return { ...downResult, stepped: false, stepHeight: 0 };
230
+ }
231
+
232
+ const actualStep = upTrace.endpos.z - startOrigin.z;
233
+ const steppedResult = slideMove({ ...rest, origin: upTrace.endpos, velocity: startVelocity, mins, maxs });
234
+
235
+ const pushDownTarget = addVec3(steppedResult.origin, { x: 0, y: 0, z: -actualStep });
236
+ const downTrace = rest.trace(steppedResult.origin, pushDownTarget, mins, maxs);
237
+
238
+ let steppedOrigin = steppedResult.origin;
239
+ let stepNormal = downTrace.planeNormal;
240
+
241
+ if (!downTrace.allsolid) {
242
+ steppedOrigin = downTrace.endpos;
243
+ }
244
+
245
+ const planarDistanceSquared = (a: Vec3, b: Vec3) => (a.x - b.x) ** 2 + (a.y - b.y) ** 2;
246
+ const downDist = planarDistanceSquared(downResult.origin, startOrigin);
247
+ const upDist = planarDistanceSquared(steppedOrigin, startOrigin);
248
+
249
+ if (downDist > upDist || (stepNormal && stepNormal.z < MIN_STEP_NORMAL)) {
250
+ return { ...downResult, stepped: false, stepHeight: 0 };
251
+ }
252
+
253
+ const steppedVelocity = { ...steppedResult.velocity, z: downResult.velocity.z };
254
+ const steppedBlocked = steppedResult.blocked;
255
+ const stopped = steppedVelocity.x === 0 && steppedVelocity.y === 0 && steppedVelocity.z === 0;
256
+
257
+ return {
258
+ origin: steppedOrigin,
259
+ velocity: steppedVelocity,
260
+ planes: steppedResult.planes,
261
+ blocked: steppedBlocked,
262
+ stopped,
263
+ stepped: true,
264
+ stepHeight: actualStep,
265
+ stepNormal,
266
+ };
267
+ }