@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,56 @@
1
+ import { MASK_WATER, CONTENTS_NONE, type ContentsFlag } from '../bsp/contents.js';
2
+ import type { Vec3 } from '../math/vec3.js';
3
+ import { WaterLevel } from './constants.js';
4
+ import type { PmovePointContentsFn } from './types.js';
5
+
6
+ export interface WaterLevelParams {
7
+ readonly origin: Vec3;
8
+ readonly mins: Vec3;
9
+ readonly viewheight: number;
10
+ readonly pointContents: PmovePointContentsFn;
11
+ }
12
+
13
+ export interface WaterLevelResult {
14
+ readonly waterlevel: WaterLevel;
15
+ readonly watertype: ContentsFlag;
16
+ }
17
+
18
+ /**
19
+ * Mirrors the rerelease `PM_GetWaterLevel` helper: probes the player's feet,
20
+ * waist, and viewheight to determine how submerged they are and returns both
21
+ * the enum level plus the contents bits encountered at the lowest sample.
22
+ */
23
+ export function getWaterLevel(params: WaterLevelParams): WaterLevelResult {
24
+ const { origin, mins, viewheight, pointContents } = params;
25
+
26
+ const sample2 = viewheight - mins.z;
27
+ const sample1 = sample2 / 2;
28
+
29
+ const point: Vec3 = {
30
+ x: origin.x,
31
+ y: origin.y,
32
+ z: origin.z + mins.z + 1,
33
+ };
34
+
35
+ let contents = pointContents(point);
36
+ if ((contents & MASK_WATER) === 0) {
37
+ return { waterlevel: WaterLevel.None, watertype: CONTENTS_NONE };
38
+ }
39
+
40
+ const watertype = contents;
41
+ let waterlevel = WaterLevel.Feet;
42
+
43
+ let point2: Vec3 = { x: point.x, y: point.y, z: origin.z + mins.z + sample1 };
44
+ contents = pointContents(point2);
45
+ if ((contents & MASK_WATER) !== 0) {
46
+ waterlevel = WaterLevel.Waist;
47
+
48
+ let point3: Vec3 = { x: point.x, y: point.y, z: origin.z + mins.z + sample2 };
49
+ contents = pointContents(point3);
50
+ if ((contents & MASK_WATER) !== 0) {
51
+ waterlevel = WaterLevel.Under;
52
+ }
53
+ }
54
+
55
+ return { waterlevel, watertype };
56
+ }
@@ -0,0 +1,139 @@
1
+
2
+ /**
3
+ * Helper to force a number into a signed 16-bit integer range (-32768 to 32767).
4
+ * This mimics the behavior of casting to `short` in C++.
5
+ */
6
+ function toSigned16(val: number): number {
7
+ return (val << 16) >> 16;
8
+ }
9
+
10
+ /**
11
+ * Reads a 16-bit integer (unsigned) from the stats array at the given byte offset.
12
+ * Mimics reading `*(uint16_t*)((uint8_t*)stats + byteOffset)` in Little Endian.
13
+ */
14
+ function readUint16LE(stats: number[], startIndex: number, byteOffset: number): number {
15
+ // Determine which element(s) of the array we are accessing
16
+ // stats is int16[], so each element is 2 bytes.
17
+ // absolute byte offset from stats[startIndex]
18
+ const elementIndex = Math.floor(byteOffset / 2);
19
+ const isOdd = (byteOffset % 2) !== 0;
20
+
21
+ // Access the array at the calculated index relative to startIndex
22
+ const index = startIndex + elementIndex;
23
+
24
+ // Read the primary element
25
+ const val0 = stats[index] || 0; // Handle potentially undefined/uninitialized slots as 0
26
+
27
+ if (!isOdd) {
28
+ // Aligned access: just return the element as uint16
29
+ return val0 & 0xFFFF;
30
+ } else {
31
+ // Unaligned access: High byte of val0 + Low byte of val1
32
+ const val1 = stats[index + 1] || 0;
33
+
34
+ // Low byte of result comes from High byte of val0
35
+ const low = (val0 >>> 8) & 0xFF;
36
+ // High byte of result comes from Low byte of val1
37
+ const high = val1 & 0xFF;
38
+
39
+ return (high << 8) | low;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Writes a 16-bit integer to the stats array at the given byte offset.
45
+ * Mimics writing `*(uint16_t*)((uint8_t*)stats + byteOffset) = value` in Little Endian.
46
+ */
47
+ function writeUint16LE(stats: number[], startIndex: number, byteOffset: number, value: number): void {
48
+ const elementIndex = Math.floor(byteOffset / 2);
49
+ const isOdd = (byteOffset % 2) !== 0;
50
+ const index = startIndex + elementIndex;
51
+
52
+ // Ensure array has values at these indices to avoid NaN math
53
+ if (stats[index] === undefined) stats[index] = 0;
54
+
55
+ if (!isOdd) {
56
+ // Aligned access: overwrite the element
57
+ stats[index] = toSigned16(value);
58
+ } else {
59
+ // Unaligned access
60
+ if (stats[index + 1] === undefined) stats[index + 1] = 0;
61
+
62
+ const val0 = stats[index];
63
+ const val1 = stats[index + 1];
64
+
65
+ // We want to write `value` (which is Low byte `L_v` and High byte `H_v`)
66
+ // into the bytes at `byteOffset` and `byteOffset + 1`.
67
+
68
+ // Byte at `byteOffset` corresponds to High byte of `stats[index]`.
69
+ // It should become `value & 0xFF` (L_v).
70
+ // So `stats[index]` becomes `(Old_Low) | (L_v << 8)`.
71
+ const newHigh0 = value & 0xFF;
72
+ const newVal0 = (val0 & 0xFF) | (newHigh0 << 8);
73
+ stats[index] = toSigned16(newVal0);
74
+
75
+ // Byte at `byteOffset + 1` corresponds to Low byte of `stats[index+1]`.
76
+ // It should become `(value >> 8) & 0xFF` (H_v).
77
+ // So `stats[index+1]` becomes `(H_v) | (Old_High << 8)`.
78
+ const newLow1 = (value >>> 8) & 0xFF;
79
+ const newVal1 = newLow1 | (val1 & 0xFF00);
80
+ stats[index + 1] = toSigned16(newVal1);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Packs a value into the stats array using a specific bit width.
86
+ * Equivalent to C++ `set_compressed_integer`.
87
+ *
88
+ * @param stats The stats array (number[] representing int16s)
89
+ * @param startIndex The index in the stats array where the packed region begins (e.g. STAT_AMMO_INFO_START)
90
+ * @param id The ID of the item to set (0-based index within the packed region)
91
+ * @param count The value to set
92
+ * @param bitsPerValue Number of bits per item (e.g. 9 for ammo, 2 for powerups)
93
+ */
94
+ export function setCompressedInteger(
95
+ stats: number[],
96
+ startIndex: number,
97
+ id: number,
98
+ count: number,
99
+ bitsPerValue: number
100
+ ): void {
101
+ const bitOffset = bitsPerValue * id;
102
+ const byteOffset = Math.floor(bitOffset / 8);
103
+ const bitShift = bitOffset % 8;
104
+ const mask = ((1 << bitsPerValue) - 1) << bitShift;
105
+
106
+ // Read the 16-bit word at the target byte address
107
+ let base = readUint16LE(stats, startIndex, byteOffset);
108
+
109
+ // Apply the mask and value
110
+ // Note: (count << bitShift) might overflow 16 bits if we aren't careful,
111
+ // but the mask will handle the high bits.
112
+ // However, in JS, bitwise ops are 32-bit.
113
+ // We need to ensure we only write back 16 bits.
114
+
115
+ const valueToWrite = (base & ~mask) | ((count << bitShift) & mask);
116
+
117
+ // Write the modified 16-bit word back
118
+ writeUint16LE(stats, startIndex, byteOffset, valueToWrite & 0xFFFF);
119
+ }
120
+
121
+ /**
122
+ * Unpacks a value from the stats array.
123
+ * Equivalent to C++ `get_compressed_integer`.
124
+ */
125
+ export function getCompressedInteger(
126
+ stats: number[],
127
+ startIndex: number,
128
+ id: number,
129
+ bitsPerValue: number
130
+ ): number {
131
+ const bitOffset = bitsPerValue * id;
132
+ const byteOffset = Math.floor(bitOffset / 8);
133
+ const bitShift = bitOffset % 8;
134
+ const mask = ((1 << bitsPerValue) - 1) << bitShift;
135
+
136
+ const base = readUint16LE(stats, startIndex, byteOffset);
137
+
138
+ return (base & mask) >>> bitShift;
139
+ }
@@ -0,0 +1,104 @@
1
+ // Mirrors the Quake II rerelease configstring/index layout from `game.h`.
2
+ // These constants intentionally track the numeric values used in the C++
3
+ // game and client modules so the TypeScript engine/game/client layers can
4
+ // share deterministic indices for precaches and HUD parsing.
5
+
6
+ export const MAX_STRING_CHARS = 1024;
7
+ export const MAX_STRING_TOKENS = 80;
8
+ export const MAX_TOKEN_CHARS = 512;
9
+
10
+ export const MAX_QPATH = 64;
11
+ export const MAX_OSPATH = 128;
12
+
13
+ export const MAX_CLIENTS = 256;
14
+ export const MAX_EDICTS = 8192;
15
+ export const MAX_LIGHTSTYLES = 256;
16
+ export const MAX_MODELS = 8192;
17
+ export const MAX_SOUNDS = 2048;
18
+ export const MAX_IMAGES = 512;
19
+ export const MAX_ITEMS = 256;
20
+ export const MAX_GENERAL = MAX_CLIENTS * 2;
21
+ export const MAX_SHADOW_LIGHTS = 256;
22
+ export const MAX_WHEEL_ITEMS = 32;
23
+
24
+ export const CS_MAX_STRING_LENGTH = 96;
25
+ export const CS_MAX_STRING_LENGTH_OLD = 64;
26
+
27
+ // Enum-style numeric constants that mirror the C++ `configstrings` enum. Only
28
+ // the explicitly numbered entries are re-stated here; everything else follows
29
+ // sequentially to keep the arithmetic (e.g., CS_SOUNDS = CS_MODELS +
30
+ // MAX_MODELS) intact.
31
+ export enum ConfigStringIndex {
32
+ Name = 0,
33
+ CdTrack = 1,
34
+ Sky = 2,
35
+ SkyAxis = 3,
36
+ SkyRotate = 4,
37
+ StatusBar = 5,
38
+
39
+ // Matching bg_local.h:55-76
40
+ HealthBarName = 55,
41
+ CONFIG_N64_PHYSICS = 56,
42
+ CONFIG_CTF_TEAMS = 57,
43
+ CONFIG_COOP_RESPAWN_STRING = 58,
44
+ Story = 54, // Arbitrarily placed in the gap for now
45
+
46
+ AirAccel = 59,
47
+ MaxClients = 60,
48
+ MapChecksum = 61,
49
+
50
+ Models = 62,
51
+ Sounds = Models + MAX_MODELS,
52
+ Images = Sounds + MAX_SOUNDS,
53
+ Lights = Images + MAX_IMAGES,
54
+ ShadowLights = Lights + MAX_LIGHTSTYLES,
55
+ Items = ShadowLights + MAX_SHADOW_LIGHTS,
56
+ Players = Items + MAX_ITEMS, // CS_PLAYERS (contains userinfo with name, skin, etc.)
57
+ PlayerSkins = Players, // Alias for legacy code compatibility
58
+ General = Players + MAX_CLIENTS,
59
+ WheelWeapons = General + MAX_GENERAL,
60
+ WheelAmmo = WheelWeapons + MAX_WHEEL_ITEMS,
61
+ WheelPowerups = WheelAmmo + MAX_WHEEL_ITEMS,
62
+ CdLoopCount = WheelPowerups + MAX_WHEEL_ITEMS,
63
+ GameStyle = CdLoopCount + 1,
64
+ MaxConfigStrings = GameStyle + 1,
65
+ }
66
+
67
+ // Mirror the C++ MAX_CONFIGSTRINGS value for consumers that prefer a standalone constant.
68
+ export const MAX_CONFIGSTRINGS = ConfigStringIndex.MaxConfigStrings;
69
+
70
+ /**
71
+ * Returns the maximum string length permitted for the given configstring index,
72
+ * mirroring the `CS_SIZE` helper in the rerelease. Statusbar and general ranges
73
+ * can legally occupy multiple 96-character slots; everything else is capped at
74
+ * `CS_MAX_STRING_LENGTH`.
75
+ */
76
+ export function configStringSize(index: number): number {
77
+ if (index >= ConfigStringIndex.StatusBar && index < ConfigStringIndex.AirAccel) {
78
+ return CS_MAX_STRING_LENGTH * (ConfigStringIndex.AirAccel - index);
79
+ }
80
+
81
+ if (index >= ConfigStringIndex.General && index < ConfigStringIndex.WheelWeapons) {
82
+ return CS_MAX_STRING_LENGTH * (ConfigStringIndex.MaxConfigStrings - index);
83
+ }
84
+
85
+ return CS_MAX_STRING_LENGTH;
86
+ }
87
+
88
+ // Legacy constants
89
+ export const CS_NAME = ConfigStringIndex.Name;
90
+ export const CS_CDTRACK = ConfigStringIndex.CdTrack;
91
+ export const CS_SKY = ConfigStringIndex.Sky;
92
+ export const CS_SKYAXIS = ConfigStringIndex.SkyAxis;
93
+ export const CS_SKYROTATE = ConfigStringIndex.SkyRotate;
94
+ export const CS_STATUSBAR = ConfigStringIndex.StatusBar;
95
+ export const CS_AIRACCEL = ConfigStringIndex.AirAccel;
96
+ export const CS_MAXCLIENTS = ConfigStringIndex.MaxClients;
97
+ export const CS_MAPCHECKSUM = ConfigStringIndex.MapChecksum;
98
+ export const CS_MODELS = ConfigStringIndex.Models;
99
+ export const CS_SOUNDS = ConfigStringIndex.Sounds;
100
+ export const CS_IMAGES = ConfigStringIndex.Images;
101
+ export const CS_LIGHTS = ConfigStringIndex.Lights;
102
+ export const CS_ITEMS = ConfigStringIndex.Items;
103
+ export const CS_PLAYERS = ConfigStringIndex.Players;
104
+ export const CS_GENERAL = ConfigStringIndex.General;
@@ -0,0 +1,40 @@
1
+
2
+ export const MAX_CHALLENGES = 1024;
3
+ export const MAX_PACKET_ENTITIES = 256; // Standard Q2 value
4
+ export const UPDATE_BACKUP = 16;
5
+ export const CMD_BACKUP = 64;
6
+ export const MAX_INFO_STRING = 512;
7
+ export const MAX_MSGLEN = 1400; // MTU safe limit
8
+
9
+ // Muzzle Flash Constants
10
+ export const MZ_BLASTER = 0;
11
+ export const MZ_MACHINEGUN = 1;
12
+ export const MZ_SHOTGUN = 2;
13
+ export const MZ_CHAINGUN1 = 3;
14
+ export const MZ_CHAINGUN2 = 4;
15
+ export const MZ_CHAINGUN3 = 5;
16
+ export const MZ_RAILGUN = 6;
17
+ export const MZ_ROCKET = 7;
18
+ export const MZ_GRENADE = 8;
19
+ export const MZ_LOGIN = 9;
20
+ export const MZ_LOGOUT = 10;
21
+ export const MZ_SSHOTGUN = 11;
22
+ export const MZ_BFG = 12;
23
+ export const MZ_HYPERBLASTER = 13;
24
+
25
+ // Xatrix / Rogue Extensions
26
+ export const MZ_IONRIPPER = 16;
27
+ export const MZ_BLUEHYPERBLASTER = 17;
28
+ export const MZ_PHALANX = 18;
29
+ export const MZ_BFG2 = 19;
30
+ export const MZ_PHALANX2 = 20;
31
+ export const MZ_ETF_RIFLE = 30;
32
+ export const MZ_PROX = 31;
33
+ export const MZ_ETF_RIFLE_2 = 32;
34
+ export const MZ_HEATBEAM = 33;
35
+ export const MZ_BLASTER2 = 34;
36
+ export const MZ_TRACKER = 35;
37
+ export const MZ_NUKE1 = 36;
38
+ export const MZ_NUKE2 = 37;
39
+ export const MZ_NUKE4 = 38;
40
+ export const MZ_NUKE8 = 39;
@@ -0,0 +1,149 @@
1
+ export interface ContractValidationResult {
2
+ missing: string[];
3
+ nonFunctions: string[];
4
+ extras: string[];
5
+ }
6
+
7
+ export interface ContractValidationOptions {
8
+ readonly name?: string;
9
+ readonly allowExtra?: boolean;
10
+ }
11
+
12
+ export type ContractFunctionMap<Keys extends readonly string[]> = Record<Keys[number], (...args: unknown[]) => unknown>;
13
+
14
+ function normalize(object: Record<string, unknown> | undefined): Record<string, unknown> {
15
+ return object ?? {};
16
+ }
17
+
18
+ export function validateContract<Keys extends readonly string[]>(
19
+ table: Record<string, unknown> | undefined,
20
+ requiredKeys: Keys,
21
+ options: ContractValidationOptions = {},
22
+ ): ContractValidationResult {
23
+ const normalized = normalize(table);
24
+ const missing: string[] = [];
25
+ const nonFunctions: string[] = [];
26
+
27
+ for (const key of requiredKeys) {
28
+ if (!(key in normalized)) {
29
+ missing.push(key);
30
+ continue;
31
+ }
32
+
33
+ if (typeof normalized[key] !== 'function') {
34
+ nonFunctions.push(key);
35
+ }
36
+ }
37
+
38
+ const extras = options.allowExtra === false ? Object.keys(normalized).filter((key) => !requiredKeys.includes(key)) : [];
39
+
40
+ return { missing, nonFunctions, extras } satisfies ContractValidationResult;
41
+ }
42
+
43
+ export function assertContract<Keys extends readonly string[]>(
44
+ table: Record<string, unknown> | undefined,
45
+ requiredKeys: Keys,
46
+ options: ContractValidationOptions = {},
47
+ ): asserts table is ContractFunctionMap<Keys> {
48
+ const { missing, nonFunctions, extras } = validateContract(table, requiredKeys, options);
49
+ if (missing.length === 0 && nonFunctions.length === 0 && extras.length === 0) {
50
+ return;
51
+ }
52
+
53
+ const pieces: string[] = [];
54
+ if (missing.length > 0) {
55
+ pieces.push(`missing: ${missing.join(', ')}`);
56
+ }
57
+ if (nonFunctions.length > 0) {
58
+ pieces.push(`non-functions: ${nonFunctions.join(', ')}`);
59
+ }
60
+ if (extras.length > 0) {
61
+ pieces.push(`extras: ${extras.join(', ')}`);
62
+ }
63
+
64
+ const label = options.name ?? 'contract';
65
+ throw new Error(`${label} validation failed (${pieces.join('; ')})`);
66
+ }
67
+
68
+ export const GAME_IMPORT_KEYS = [
69
+ 'Broadcast_Print',
70
+ 'Com_Print',
71
+ 'Client_Print',
72
+ 'Center_Print',
73
+ 'sound',
74
+ 'positioned_sound',
75
+ 'local_sound',
76
+ 'configstring',
77
+ 'get_configstring',
78
+ 'Com_Error',
79
+ 'modelindex',
80
+ 'soundindex',
81
+ 'imageindex',
82
+ 'setmodel',
83
+ 'trace',
84
+ 'clip',
85
+ 'pointcontents',
86
+ 'inPVS',
87
+ 'inPHS',
88
+ 'SetAreaPortalState',
89
+ 'AreasConnected',
90
+ 'linkentity',
91
+ 'unlinkentity',
92
+ 'BoxEdicts',
93
+ 'multicast',
94
+ 'unicast',
95
+ ] as const;
96
+
97
+ export const GAME_EXPORT_KEYS = [
98
+ 'PreInit',
99
+ 'Init',
100
+ 'Shutdown',
101
+ 'SpawnEntities',
102
+ 'WriteGameJson',
103
+ 'ReadGameJson',
104
+ 'WriteLevelJson',
105
+ 'ReadLevelJson',
106
+ 'CanSave',
107
+ 'ClientConnect',
108
+ 'ClientThink',
109
+ 'RunFrame',
110
+ 'Pmove',
111
+ ] as const;
112
+
113
+ export const CGAME_IMPORT_KEYS = [
114
+ 'Com_Print',
115
+ 'get_configstring',
116
+ 'Com_Error',
117
+ 'TagMalloc',
118
+ 'TagFree',
119
+ 'AddCommandString',
120
+ 'CL_FrameValid',
121
+ 'CL_FrameTime',
122
+ 'CL_ClientTime',
123
+ 'CL_ServerFrame',
124
+ 'Draw_RegisterPic',
125
+ 'Draw_GetPicSize',
126
+ 'SCR_DrawChar',
127
+ 'SCR_DrawPic',
128
+ 'SCR_DrawColorPic',
129
+ ] as const;
130
+
131
+ export const CGAME_EXPORT_KEYS = [
132
+ 'Init',
133
+ 'Shutdown',
134
+ 'DrawHUD',
135
+ 'TouchPics',
136
+ 'LayoutFlags',
137
+ 'GetActiveWeaponWheelWeapon',
138
+ 'GetOwnedWeaponWheelWeapons',
139
+ 'GetWeaponWheelAmmoCount',
140
+ 'GetPowerupWheelCount',
141
+ 'GetHitMarkerDamage',
142
+ 'Pmove',
143
+ 'ParseConfigString',
144
+ 'ParseCenterPrint',
145
+ 'ClearNotify',
146
+ 'ClearCenterprint',
147
+ 'NotifyMessage',
148
+ 'GetMonsterFlashOffset',
149
+ ] as const;
@@ -0,0 +1,32 @@
1
+ // Quake 2 CRC implementation
2
+ // Ported from qcommon/crc.c
3
+
4
+ const crc_table: number[] = [
5
+ 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
6
+ 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
7
+ 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
8
+ 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
9
+ 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
10
+ 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
11
+ 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
12
+ 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
13
+ 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
14
+ 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
15
+ 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
16
+ 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
17
+ 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
18
+ 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
19
+ 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
20
+ 0xb4, 0x25, 0x56, 0xc7, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
21
+ ];
22
+
23
+ /**
24
+ * Calculates 8-bit CRC for the given data
25
+ */
26
+ export function crc8(data: Uint8Array): number {
27
+ let crc = 0;
28
+ for (let i = 0; i < data.length; i++) {
29
+ crc = crc_table[(crc ^ data[i]) & 0xff];
30
+ }
31
+ return crc;
32
+ }
@@ -0,0 +1,15 @@
1
+ export const enum CvarFlags {
2
+ None = 0,
3
+ Archive = 1 << 0,
4
+ UserInfo = 1 << 1,
5
+ ServerInfo = 1 << 2,
6
+ Latch = 1 << 3,
7
+ Cheat = 1 << 4,
8
+ }
9
+
10
+ export interface CvarDefinition {
11
+ readonly name: string;
12
+ readonly defaultValue: string;
13
+ readonly description?: string;
14
+ readonly flags?: CvarFlags;
15
+ }
@@ -0,0 +1,33 @@
1
+ // Source: game.h (Quake 2)
2
+ export enum EntityEffects {
3
+ Rotate = 0x00000004,
4
+ Gib = 0x00000008,
5
+ RotateScript = 0x00000010,
6
+ Blaster = 0x00000020,
7
+ Rocket = 0x00000040,
8
+ Grenade = 0x00000080,
9
+ HyperBlaster = 0x00000100,
10
+ Bfg = 0x00000200,
11
+ ColorShell = 0x00000400,
12
+ Powerscreen = 0x00000800,
13
+ Anim01 = 0x00001000,
14
+ Anim23 = 0x00002000,
15
+ AnimAll = 0x00004000,
16
+ AnimAllFast = 0x00008000,
17
+ Quad = 0x00010000,
18
+ Pent = 0x00020000,
19
+ Explosion = 0x00040000,
20
+ Teleport = 0x00080000,
21
+ Flag1 = 0x00100000,
22
+ Flag2 = 0x00200000,
23
+ Boomerang = 0x00400000,
24
+ Greengibs = 0x00800000,
25
+ Bluehyperblaster = 0x01000000,
26
+ Spinning = 0x02000000,
27
+ Plasma = 0x04000000,
28
+ Trap = 0x08000000,
29
+ Tracker = 0x10000000,
30
+ Double = 0x20000000,
31
+ Sphinx = 0x40000000,
32
+ TagTrail = 0x80000000,
33
+ }