@quake2ts/shared 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.global.js +2 -0
- package/dist/browser/index.global.js.map +1 -0
- package/dist/cjs/index.cjs +6569 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/index.js +6200 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/audio/constants.d.ts +24 -0
- package/dist/types/audio/constants.d.ts.map +1 -0
- package/dist/types/bsp/collision.d.ts +201 -0
- package/dist/types/bsp/collision.d.ts.map +1 -0
- package/dist/types/bsp/contents.d.ts +72 -0
- package/dist/types/bsp/contents.d.ts.map +1 -0
- package/dist/types/bsp/spatial.d.ts +13 -0
- package/dist/types/bsp/spatial.d.ts.map +1 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/inventory-helpers.d.ts +19 -0
- package/dist/types/inventory-helpers.d.ts.map +1 -0
- package/dist/types/io/binaryStream.d.ts +38 -0
- package/dist/types/io/binaryStream.d.ts.map +1 -0
- package/dist/types/io/binaryWriter.d.ts +26 -0
- package/dist/types/io/binaryWriter.d.ts.map +1 -0
- package/dist/types/io/index.d.ts +4 -0
- package/dist/types/io/index.d.ts.map +1 -0
- package/dist/types/io/messageBuilder.d.ts +21 -0
- package/dist/types/io/messageBuilder.d.ts.map +1 -0
- package/dist/types/items/ammo.d.ts +40 -0
- package/dist/types/items/ammo.d.ts.map +1 -0
- package/dist/types/items/index.d.ts +8 -0
- package/dist/types/items/index.d.ts.map +1 -0
- package/dist/types/items/powerups.d.ts +31 -0
- package/dist/types/items/powerups.d.ts.map +1 -0
- package/dist/types/items/weaponInfo.d.ts +5 -0
- package/dist/types/items/weaponInfo.d.ts.map +1 -0
- package/dist/types/items/weapons.d.ts +27 -0
- package/dist/types/items/weapons.d.ts.map +1 -0
- package/dist/types/math/angles.d.ts +19 -0
- package/dist/types/math/angles.d.ts.map +1 -0
- package/dist/types/math/anorms.d.ts +2 -0
- package/dist/types/math/anorms.d.ts.map +1 -0
- package/dist/types/math/color.d.ts +12 -0
- package/dist/types/math/color.d.ts.map +1 -0
- package/dist/types/math/mat4.d.ts +7 -0
- package/dist/types/math/mat4.d.ts.map +1 -0
- package/dist/types/math/random.d.ts +60 -0
- package/dist/types/math/random.d.ts.map +1 -0
- package/dist/types/math/vec3.d.ts +79 -0
- package/dist/types/math/vec3.d.ts.map +1 -0
- package/dist/types/net/driver.d.ts +10 -0
- package/dist/types/net/driver.d.ts.map +1 -0
- package/dist/types/net/index.d.ts +3 -0
- package/dist/types/net/index.d.ts.map +1 -0
- package/dist/types/net/netchan.d.ts +85 -0
- package/dist/types/net/netchan.d.ts.map +1 -0
- package/dist/types/pmove/apply.d.ts +5 -0
- package/dist/types/pmove/apply.d.ts.map +1 -0
- package/dist/types/pmove/categorize.d.ts +36 -0
- package/dist/types/pmove/categorize.d.ts.map +1 -0
- package/dist/types/pmove/config.d.ts +5 -0
- package/dist/types/pmove/config.d.ts.map +1 -0
- package/dist/types/pmove/constants.d.ts +76 -0
- package/dist/types/pmove/constants.d.ts.map +1 -0
- package/dist/types/pmove/currents.d.ts +58 -0
- package/dist/types/pmove/currents.d.ts.map +1 -0
- package/dist/types/pmove/dimensions.d.ts +14 -0
- package/dist/types/pmove/dimensions.d.ts.map +1 -0
- package/dist/types/pmove/duck.d.ts +39 -0
- package/dist/types/pmove/duck.d.ts.map +1 -0
- package/dist/types/pmove/fly.d.ts +34 -0
- package/dist/types/pmove/fly.d.ts.map +1 -0
- package/dist/types/pmove/index.d.ts +18 -0
- package/dist/types/pmove/index.d.ts.map +1 -0
- package/dist/types/pmove/jump.d.ts +28 -0
- package/dist/types/pmove/jump.d.ts.map +1 -0
- package/dist/types/pmove/move.d.ts +78 -0
- package/dist/types/pmove/move.d.ts.map +1 -0
- package/dist/types/pmove/pmove.d.ts +40 -0
- package/dist/types/pmove/pmove.d.ts.map +1 -0
- package/dist/types/pmove/slide.d.ts +63 -0
- package/dist/types/pmove/slide.d.ts.map +1 -0
- package/dist/types/pmove/snap.d.ts +40 -0
- package/dist/types/pmove/snap.d.ts.map +1 -0
- package/dist/types/pmove/special.d.ts +39 -0
- package/dist/types/pmove/special.d.ts.map +1 -0
- package/dist/types/pmove/stuck.d.ts +21 -0
- package/dist/types/pmove/stuck.d.ts.map +1 -0
- package/dist/types/pmove/types.d.ts +72 -0
- package/dist/types/pmove/types.d.ts.map +1 -0
- package/dist/types/pmove/view.d.ts +19 -0
- package/dist/types/pmove/view.d.ts.map +1 -0
- package/dist/types/pmove/water.d.ts +21 -0
- package/dist/types/pmove/water.d.ts.map +1 -0
- package/dist/types/protocol/bitpack.d.ts +17 -0
- package/dist/types/protocol/bitpack.d.ts.map +1 -0
- package/dist/types/protocol/configstrings.d.ts +73 -0
- package/dist/types/protocol/configstrings.d.ts.map +1 -0
- package/dist/types/protocol/constants.d.ts +36 -0
- package/dist/types/protocol/constants.d.ts.map +1 -0
- package/dist/types/protocol/contracts.d.ts +17 -0
- package/dist/types/protocol/contracts.d.ts.map +1 -0
- package/dist/types/protocol/crc.d.ts +5 -0
- package/dist/types/protocol/crc.d.ts.map +1 -0
- package/dist/types/protocol/cvar.d.ts +15 -0
- package/dist/types/protocol/cvar.d.ts.map +1 -0
- package/dist/types/protocol/effects.d.ts +33 -0
- package/dist/types/protocol/effects.d.ts.map +1 -0
- package/dist/types/protocol/entity.d.ts +46 -0
- package/dist/types/protocol/entity.d.ts.map +1 -0
- package/dist/types/protocol/entityEvent.d.ts +13 -0
- package/dist/types/protocol/entityEvent.d.ts.map +1 -0
- package/dist/types/protocol/entityState.d.ts +26 -0
- package/dist/types/protocol/entityState.d.ts.map +1 -0
- package/dist/types/protocol/index.d.ts +19 -0
- package/dist/types/protocol/index.d.ts.map +1 -0
- package/dist/types/protocol/layout.d.ts +9 -0
- package/dist/types/protocol/layout.d.ts.map +1 -0
- package/dist/types/protocol/ops.d.ts +44 -0
- package/dist/types/protocol/ops.d.ts.map +1 -0
- package/dist/types/protocol/player-state.d.ts +40 -0
- package/dist/types/protocol/player-state.d.ts.map +1 -0
- package/dist/types/protocol/player.d.ts +28 -0
- package/dist/types/protocol/player.d.ts.map +1 -0
- package/dist/types/protocol/renderFx.d.ts +23 -0
- package/dist/types/protocol/renderFx.d.ts.map +1 -0
- package/dist/types/protocol/stats.d.ts +61 -0
- package/dist/types/protocol/stats.d.ts.map +1 -0
- package/dist/types/protocol/tempEntity.d.ts +67 -0
- package/dist/types/protocol/tempEntity.d.ts.map +1 -0
- package/dist/types/protocol/usercmd.d.ts +33 -0
- package/dist/types/protocol/usercmd.d.ts.map +1 -0
- package/dist/types/protocol/writeUserCmd.d.ts +4 -0
- package/dist/types/protocol/writeUserCmd.d.ts.map +1 -0
- package/dist/types/replay/index.d.ts +3 -0
- package/dist/types/replay/index.d.ts.map +1 -0
- package/dist/types/replay/io.d.ts +7 -0
- package/dist/types/replay/io.d.ts.map +1 -0
- package/dist/types/replay/schema.d.ts +41 -0
- package/dist/types/replay/schema.d.ts.map +1 -0
- package/dist/types/testing.d.ts +6 -0
- package/dist/types/testing.d.ts.map +1 -0
- package/package.json +43 -0
- package/src/audio/constants.ts +35 -0
- package/src/bsp/collision.ts +1075 -0
- package/src/bsp/contents.ts +108 -0
- package/src/bsp/spatial.ts +116 -0
- package/src/index.ts +37 -0
- package/src/inventory-helpers.ts +81 -0
- package/src/io/binaryStream.ts +159 -0
- package/src/io/binaryWriter.ts +146 -0
- package/src/io/index.ts +3 -0
- package/src/io/messageBuilder.ts +117 -0
- package/src/items/ammo.ts +47 -0
- package/src/items/index.ts +8 -0
- package/src/items/powerups.ts +32 -0
- package/src/items/weaponInfo.ts +45 -0
- package/src/items/weapons.ts +28 -0
- package/src/math/angles.ts +135 -0
- package/src/math/anorms.ts +165 -0
- package/src/math/color.ts +42 -0
- package/src/math/mat4.ts +58 -0
- package/src/math/random.ts +182 -0
- package/src/math/vec3.ts +379 -0
- package/src/net/driver.ts +9 -0
- package/src/net/index.ts +2 -0
- package/src/net/netchan.ts +451 -0
- package/src/pmove/apply.ts +151 -0
- package/src/pmove/categorize.ts +162 -0
- package/src/pmove/config.ts +5 -0
- package/src/pmove/constants.ts +94 -0
- package/src/pmove/currents.ts +287 -0
- package/src/pmove/dimensions.ts +40 -0
- package/src/pmove/duck.ts +154 -0
- package/src/pmove/fly.ts +197 -0
- package/src/pmove/index.ts +18 -0
- package/src/pmove/jump.ts +92 -0
- package/src/pmove/move.ts +527 -0
- package/src/pmove/pmove.ts +446 -0
- package/src/pmove/slide.ts +267 -0
- package/src/pmove/snap.ts +89 -0
- package/src/pmove/special.ts +207 -0
- package/src/pmove/stuck.ts +258 -0
- package/src/pmove/types.ts +82 -0
- package/src/pmove/view.ts +57 -0
- package/src/pmove/water.ts +56 -0
- package/src/protocol/bitpack.ts +139 -0
- package/src/protocol/configstrings.ts +104 -0
- package/src/protocol/constants.ts +40 -0
- package/src/protocol/contracts.ts +149 -0
- package/src/protocol/crc.ts +32 -0
- package/src/protocol/cvar.ts +15 -0
- package/src/protocol/effects.ts +33 -0
- package/src/protocol/entity.ts +304 -0
- package/src/protocol/entityEvent.ts +14 -0
- package/src/protocol/entityState.ts +28 -0
- package/src/protocol/index.ts +19 -0
- package/src/protocol/layout.ts +9 -0
- package/src/protocol/ops.ts +49 -0
- package/src/protocol/player-state.ts +51 -0
- package/src/protocol/player.ts +165 -0
- package/src/protocol/renderFx.ts +22 -0
- package/src/protocol/stats.ts +161 -0
- package/src/protocol/tempEntity.ts +69 -0
- package/src/protocol/usercmd.ts +63 -0
- package/src/protocol/writeUserCmd.ts +30 -0
- package/src/replay/index.ts +2 -0
- package/src/replay/io.ts +37 -0
- package/src/replay/schema.ts +42 -0
- package/src/testing.ts +200 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { setCompressedInteger, getCompressedInteger } from './bitpack.js';
|
|
2
|
+
import { PowerupId } from '../items/powerups.js';
|
|
3
|
+
|
|
4
|
+
// Matching rerelease/bg_local.h:196-262
|
|
5
|
+
export enum PlayerStat {
|
|
6
|
+
STAT_HEALTH_ICON = 0,
|
|
7
|
+
STAT_HEALTH,
|
|
8
|
+
STAT_AMMO_ICON,
|
|
9
|
+
STAT_AMMO,
|
|
10
|
+
STAT_ARMOR_ICON,
|
|
11
|
+
STAT_ARMOR,
|
|
12
|
+
STAT_SELECTED_ICON,
|
|
13
|
+
STAT_PICKUP_ICON,
|
|
14
|
+
STAT_PICKUP_STRING,
|
|
15
|
+
STAT_TIMER_ICON,
|
|
16
|
+
STAT_TIMER,
|
|
17
|
+
STAT_HELPICON,
|
|
18
|
+
STAT_SELECTED_ITEM,
|
|
19
|
+
STAT_LAYOUTS,
|
|
20
|
+
STAT_FRAGS,
|
|
21
|
+
STAT_FLASHES,
|
|
22
|
+
STAT_CHASE,
|
|
23
|
+
STAT_SPECTATOR,
|
|
24
|
+
|
|
25
|
+
// CTF Stats (Rerelease/KEX)
|
|
26
|
+
STAT_CTF_TEAM1_PIC = 18,
|
|
27
|
+
STAT_CTF_TEAM1_CAPS = 19,
|
|
28
|
+
STAT_CTF_TEAM2_PIC = 20,
|
|
29
|
+
STAT_CTF_TEAM2_CAPS = 21,
|
|
30
|
+
STAT_CTF_FLAG_PIC = 22,
|
|
31
|
+
STAT_CTF_JOINED_TEAM1_PIC = 23,
|
|
32
|
+
STAT_CTF_JOINED_TEAM2_PIC = 24,
|
|
33
|
+
STAT_CTF_TEAM1_HEADER = 25,
|
|
34
|
+
STAT_CTF_TEAM2_HEADER = 26,
|
|
35
|
+
STAT_CTF_TECH = 27,
|
|
36
|
+
STAT_CTF_ID_VIEW = 28,
|
|
37
|
+
STAT_CTF_MATCH = 29,
|
|
38
|
+
STAT_CTF_ID_VIEW_COLOR = 30,
|
|
39
|
+
STAT_CTF_TEAMINFO = 31,
|
|
40
|
+
|
|
41
|
+
// Rerelease additions
|
|
42
|
+
STAT_WEAPONS_OWNED_1 = 32,
|
|
43
|
+
STAT_WEAPONS_OWNED_2 = 33,
|
|
44
|
+
|
|
45
|
+
// Ammo counts (start index)
|
|
46
|
+
STAT_AMMO_INFO_START = 34,
|
|
47
|
+
// Calculated below, but enum needs literal or constant if we want to use it as type.
|
|
48
|
+
// However, for TS Enum, we can just define start.
|
|
49
|
+
|
|
50
|
+
// Powerups start after Ammo.
|
|
51
|
+
// AMMO_MAX=12, 9 bits each -> 108 bits -> 7 int16s.
|
|
52
|
+
// 34 + 7 = 41.
|
|
53
|
+
STAT_POWERUP_INFO_START = 41,
|
|
54
|
+
|
|
55
|
+
// Keys and other KEX stats (Start after Powerups)
|
|
56
|
+
// POWERUP_MAX=23, 2 bits each -> 46 bits -> 3 int16s.
|
|
57
|
+
// 41 + 3 = 44.
|
|
58
|
+
STAT_KEY_A = 44,
|
|
59
|
+
STAT_KEY_B = 45,
|
|
60
|
+
STAT_KEY_C = 46,
|
|
61
|
+
|
|
62
|
+
STAT_ACTIVE_WHEEL_WEAPON = 47,
|
|
63
|
+
STAT_COOP_RESPAWN = 48,
|
|
64
|
+
STAT_LIVES = 49,
|
|
65
|
+
STAT_HIT_MARKER = 50,
|
|
66
|
+
STAT_SELECTED_ITEM_NAME = 51,
|
|
67
|
+
STAT_HEALTH_BARS = 52,
|
|
68
|
+
STAT_ACTIVE_WEAPON = 53,
|
|
69
|
+
|
|
70
|
+
STAT_LAST
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Constants for bit packing logic
|
|
74
|
+
export const AMMO_MAX = 12;
|
|
75
|
+
export const NUM_BITS_FOR_AMMO = 9;
|
|
76
|
+
export const NUM_AMMO_STATS = Math.ceil((AMMO_MAX * NUM_BITS_FOR_AMMO) / 16); // 7
|
|
77
|
+
|
|
78
|
+
export const POWERUP_MAX = 23; // Adjusted to include TechRegeneration (index 22)
|
|
79
|
+
export const NUM_BITS_FOR_POWERUP = 2;
|
|
80
|
+
export const NUM_POWERUP_STATS = Math.ceil((POWERUP_MAX * NUM_BITS_FOR_POWERUP) / 16); // 3
|
|
81
|
+
|
|
82
|
+
// Powerup ID mapping from string to C++ integer index (powerup_t in bg_local.h)
|
|
83
|
+
const POWERUP_STAT_MAP: Partial<Record<PowerupId, number>> = {
|
|
84
|
+
[PowerupId.PowerScreen]: 0,
|
|
85
|
+
[PowerupId.PowerShield]: 1,
|
|
86
|
+
// 2 is POWERUP_AM_BOMB (not in PowerupId?)
|
|
87
|
+
[PowerupId.QuadDamage]: 3,
|
|
88
|
+
[PowerupId.QuadFire]: 4,
|
|
89
|
+
[PowerupId.Invulnerability]: 5,
|
|
90
|
+
[PowerupId.Invisibility]: 6,
|
|
91
|
+
[PowerupId.Silencer]: 7,
|
|
92
|
+
[PowerupId.Rebreather]: 8,
|
|
93
|
+
[PowerupId.EnviroSuit]: 9,
|
|
94
|
+
[PowerupId.Bandolier]: 10, // Placeholder/Map mismatch handling?
|
|
95
|
+
[PowerupId.AmmoPack]: 10, // Original reused indices or had gaps?
|
|
96
|
+
[PowerupId.IRGoggles]: 11,
|
|
97
|
+
[PowerupId.DoubleDamage]: 12,
|
|
98
|
+
[PowerupId.SphereVengeance]: 13,
|
|
99
|
+
[PowerupId.SphereHunter]: 14,
|
|
100
|
+
[PowerupId.SphereDefender]: 15,
|
|
101
|
+
[PowerupId.Doppelganger]: 16,
|
|
102
|
+
[PowerupId.Flashlight]: 17,
|
|
103
|
+
[PowerupId.Compass]: 18,
|
|
104
|
+
[PowerupId.TechResistance]: 19,
|
|
105
|
+
[PowerupId.TechStrength]: 20,
|
|
106
|
+
[PowerupId.TechHaste]: 21,
|
|
107
|
+
[PowerupId.TechRegeneration]: 22,
|
|
108
|
+
// Add missing mappings to avoid runtime lookups failing for new types
|
|
109
|
+
[PowerupId.TagToken]: -1,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// 9 bits for ammo count
|
|
113
|
+
export function G_SetAmmoStat(stats: number[], ammoId: number, count: number): void {
|
|
114
|
+
if (ammoId < 0 || ammoId >= AMMO_MAX) return;
|
|
115
|
+
|
|
116
|
+
// Clamp count to 9 bits (0-511)
|
|
117
|
+
let val = count;
|
|
118
|
+
if (val > 511) val = 511;
|
|
119
|
+
if (val < 0) val = 0;
|
|
120
|
+
|
|
121
|
+
setCompressedInteger(stats, PlayerStat.STAT_AMMO_INFO_START, ammoId, val, NUM_BITS_FOR_AMMO);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function G_GetAmmoStat(stats: number[], ammoId: number): number {
|
|
125
|
+
if (ammoId < 0 || ammoId >= AMMO_MAX) return 0;
|
|
126
|
+
return getCompressedInteger(stats, PlayerStat.STAT_AMMO_INFO_START, ammoId, NUM_BITS_FOR_AMMO);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2 bits for powerup active/inactive state
|
|
130
|
+
export function G_SetPowerupStat(stats: number[], powerupId: PowerupId | number, val: number): void {
|
|
131
|
+
let index: number | undefined;
|
|
132
|
+
|
|
133
|
+
if (typeof powerupId === 'number') {
|
|
134
|
+
index = powerupId;
|
|
135
|
+
} else {
|
|
136
|
+
index = POWERUP_STAT_MAP[powerupId];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (index === undefined || index < 0 || index >= POWERUP_MAX) return;
|
|
140
|
+
|
|
141
|
+
// Clamp value to 2 bits (0-3)
|
|
142
|
+
let safeVal = val;
|
|
143
|
+
if (safeVal > 3) safeVal = 3;
|
|
144
|
+
if (safeVal < 0) safeVal = 0;
|
|
145
|
+
|
|
146
|
+
setCompressedInteger(stats, PlayerStat.STAT_POWERUP_INFO_START, index, safeVal, NUM_BITS_FOR_POWERUP);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function G_GetPowerupStat(stats: number[], powerupId: PowerupId | number): number {
|
|
150
|
+
let index: number | undefined;
|
|
151
|
+
|
|
152
|
+
if (typeof powerupId === 'number') {
|
|
153
|
+
index = powerupId;
|
|
154
|
+
} else {
|
|
155
|
+
index = POWERUP_STAT_MAP[powerupId];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (index === undefined || index < 0 || index >= POWERUP_MAX) return 0;
|
|
159
|
+
|
|
160
|
+
return getCompressedInteger(stats, PlayerStat.STAT_POWERUP_INFO_START, index, NUM_BITS_FOR_POWERUP);
|
|
161
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
// Temp entity constants from Quake 2
|
|
3
|
+
export enum TempEntity {
|
|
4
|
+
GUNSHOT = 0,
|
|
5
|
+
BLOOD = 1,
|
|
6
|
+
BLASTER = 2,
|
|
7
|
+
RAILTRAIL = 3,
|
|
8
|
+
SHOTGUN = 4,
|
|
9
|
+
EXPLOSION1 = 5,
|
|
10
|
+
EXPLOSION2 = 6,
|
|
11
|
+
ROCKET_EXPLOSION = 7,
|
|
12
|
+
GRENADE_EXPLOSION = 8,
|
|
13
|
+
SPARKS = 9,
|
|
14
|
+
SPLASH = 10,
|
|
15
|
+
BUBBLETRAIL = 11,
|
|
16
|
+
SCREEN_SPARKS = 12,
|
|
17
|
+
SHIELD_SPARKS = 13,
|
|
18
|
+
BULLET_SPARKS = 14,
|
|
19
|
+
LASER_SPARKS = 15,
|
|
20
|
+
PARASITE_ATTACK = 16,
|
|
21
|
+
ROCKET_EXPLOSION_WATER = 17,
|
|
22
|
+
GRENADE_EXPLOSION_WATER = 18,
|
|
23
|
+
MEDIC_CABLE_ATTACK = 19,
|
|
24
|
+
BFG_EXPLOSION = 20,
|
|
25
|
+
BFG_BIGEXPLOSION = 21,
|
|
26
|
+
BOSSTPORT = 22,
|
|
27
|
+
BFG_LASER = 23,
|
|
28
|
+
GRAPPLE_CABLE = 24,
|
|
29
|
+
WELDING_SPARKS = 25,
|
|
30
|
+
GREENBLOOD = 26,
|
|
31
|
+
BLUEHYPERBLASTER = 27,
|
|
32
|
+
PLASMA_EXPLOSION = 28,
|
|
33
|
+
TUNNEL_SPARKS = 29,
|
|
34
|
+
// ROGUE
|
|
35
|
+
BLASTER2 = 30,
|
|
36
|
+
RAILTRAIL2 = 31,
|
|
37
|
+
FLAME = 32,
|
|
38
|
+
LIGHTNING = 33,
|
|
39
|
+
DEBUGTRAIL = 34,
|
|
40
|
+
PLAIN_EXPLOSION = 35,
|
|
41
|
+
FLASHLIGHT = 36,
|
|
42
|
+
FORCEWALL = 37,
|
|
43
|
+
HEATBEAM = 38,
|
|
44
|
+
MONSTER_HEATBEAM = 39,
|
|
45
|
+
STEAM = 40,
|
|
46
|
+
BUBBLETRAIL2 = 41,
|
|
47
|
+
MOREBLOOD = 42,
|
|
48
|
+
HEATBEAM_SPARKS = 43,
|
|
49
|
+
HEATBEAM_STEAM = 44,
|
|
50
|
+
CHAINFIST_SMOKE = 45,
|
|
51
|
+
ELECTRIC_SPARKS = 46,
|
|
52
|
+
TRACKER_EXPLOSION = 47,
|
|
53
|
+
TELEPORT_EFFECT = 48,
|
|
54
|
+
DBALL_GOAL = 49,
|
|
55
|
+
WIDOWBEAMOUT = 50,
|
|
56
|
+
NUKEBLAST = 51,
|
|
57
|
+
WIDOWSPLASH = 52,
|
|
58
|
+
EXPLOSION1_BIG = 53,
|
|
59
|
+
EXPLOSION1_NP = 54,
|
|
60
|
+
FLECHETTE = 55,
|
|
61
|
+
BLUEHYPERBLASTER_KEX = 56,
|
|
62
|
+
BFG_ZAP = 57,
|
|
63
|
+
BERSERK_SLAM = 58,
|
|
64
|
+
GRAPPLE_CABLE_2 = 59,
|
|
65
|
+
POWER_SPLASH = 60,
|
|
66
|
+
LIGHTNING_BEAM = 61,
|
|
67
|
+
EXPLOSION1_NL = 62,
|
|
68
|
+
EXPLOSION2_NL = 63
|
|
69
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { angleMod } from '../math/angles.js';
|
|
2
|
+
import type { Vec3 } from '../math/vec3.js';
|
|
3
|
+
import { PlayerButton } from '../pmove/constants.js';
|
|
4
|
+
|
|
5
|
+
export interface UserCommand {
|
|
6
|
+
readonly msec: number;
|
|
7
|
+
readonly buttons: PlayerButton;
|
|
8
|
+
readonly angles: Vec3;
|
|
9
|
+
readonly forwardmove: number;
|
|
10
|
+
readonly sidemove: number;
|
|
11
|
+
readonly upmove: number;
|
|
12
|
+
readonly serverFrame?: number;
|
|
13
|
+
readonly sequence: number;
|
|
14
|
+
readonly lightlevel: number;
|
|
15
|
+
readonly impulse: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MouseDelta {
|
|
19
|
+
readonly deltaX: number;
|
|
20
|
+
readonly deltaY: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface MouseLookOptions {
|
|
24
|
+
readonly sensitivity: number;
|
|
25
|
+
readonly invertY: boolean;
|
|
26
|
+
readonly sensitivityX?: number;
|
|
27
|
+
readonly sensitivityY?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_FORWARD_SPEED = 200;
|
|
31
|
+
export const DEFAULT_SIDE_SPEED = 200;
|
|
32
|
+
export const DEFAULT_UP_SPEED = 200;
|
|
33
|
+
export const DEFAULT_YAW_SPEED = 140;
|
|
34
|
+
export const DEFAULT_PITCH_SPEED = 150;
|
|
35
|
+
export const DEFAULT_MOUSE_SENSITIVITY = 3;
|
|
36
|
+
|
|
37
|
+
function clampPitch(pitch: number): number {
|
|
38
|
+
const normalized = angleMod(pitch);
|
|
39
|
+
|
|
40
|
+
if (normalized > 89 && normalized < 180) return 89;
|
|
41
|
+
if (normalized < 271 && normalized >= 180) return 271;
|
|
42
|
+
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function addViewAngles(current: Vec3, delta: Vec3): Vec3 {
|
|
47
|
+
return {
|
|
48
|
+
x: clampPitch(current.x + delta.x),
|
|
49
|
+
y: angleMod(current.y + delta.y),
|
|
50
|
+
z: angleMod(current.z + delta.z),
|
|
51
|
+
} satisfies Vec3;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function mouseDeltaToViewDelta(delta: MouseDelta, options: MouseLookOptions): Vec3 {
|
|
55
|
+
const yawScale = options.sensitivityX ?? options.sensitivity;
|
|
56
|
+
const pitchScale = (options.sensitivityY ?? options.sensitivity) * (options.invertY ? -1 : 1);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
x: delta.deltaY * pitchScale,
|
|
60
|
+
y: delta.deltaX * yawScale,
|
|
61
|
+
z: 0,
|
|
62
|
+
} satisfies Vec3;
|
|
63
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BinaryWriter } from '../io/binaryWriter.js';
|
|
2
|
+
import { UserCommand } from './usercmd.js';
|
|
3
|
+
|
|
4
|
+
export function writeUserCommand(writer: BinaryWriter, cmd: UserCommand): void {
|
|
5
|
+
// msec (byte)
|
|
6
|
+
writer.writeByte(cmd.msec);
|
|
7
|
+
|
|
8
|
+
// buttons (byte)
|
|
9
|
+
writer.writeByte(cmd.buttons);
|
|
10
|
+
|
|
11
|
+
// angles (short * 3) - Scaled 360 -> 65536
|
|
12
|
+
writer.writeAngle16(cmd.angles.x);
|
|
13
|
+
writer.writeAngle16(cmd.angles.y);
|
|
14
|
+
writer.writeAngle16(cmd.angles.z);
|
|
15
|
+
|
|
16
|
+
// forwardmove (short)
|
|
17
|
+
writer.writeShort(cmd.forwardmove);
|
|
18
|
+
|
|
19
|
+
// sidemove (short)
|
|
20
|
+
writer.writeShort(cmd.sidemove);
|
|
21
|
+
|
|
22
|
+
// upmove (short)
|
|
23
|
+
writer.writeShort(cmd.upmove);
|
|
24
|
+
|
|
25
|
+
// impulse (byte)
|
|
26
|
+
writer.writeByte(cmd.impulse);
|
|
27
|
+
|
|
28
|
+
// lightlevel (byte)
|
|
29
|
+
writer.writeByte(0); // TODO: Lightlevel
|
|
30
|
+
}
|
package/src/replay/io.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ReplaySession, ReplayFrame } from './schema.js';
|
|
2
|
+
import { UserCommand } from '../protocol/usercmd.js';
|
|
3
|
+
|
|
4
|
+
export function serializeReplay(session: ReplaySession): string {
|
|
5
|
+
return JSON.stringify(session, null, 2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function deserializeReplay(json: string): ReplaySession {
|
|
9
|
+
const session = JSON.parse(json);
|
|
10
|
+
|
|
11
|
+
// Validate structure lightly
|
|
12
|
+
if (!session.metadata || !Array.isArray(session.frames)) {
|
|
13
|
+
throw new Error('Invalid replay format: missing metadata or frames');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return session as ReplaySession;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createReplaySession(map: string, seed?: number): ReplaySession {
|
|
20
|
+
return {
|
|
21
|
+
metadata: {
|
|
22
|
+
map,
|
|
23
|
+
date: new Date().toISOString(),
|
|
24
|
+
version: '1.0',
|
|
25
|
+
seed
|
|
26
|
+
},
|
|
27
|
+
frames: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function addReplayFrame(session: ReplaySession, cmd: UserCommand, serverFrame: number, startTime: number) {
|
|
32
|
+
session.frames.push({
|
|
33
|
+
serverFrame,
|
|
34
|
+
cmd,
|
|
35
|
+
timestamp: Date.now() - startTime
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { UserCommand } from '../protocol/usercmd.js';
|
|
2
|
+
|
|
3
|
+
export interface ReplayFrame {
|
|
4
|
+
/**
|
|
5
|
+
* The server frame number this command was executed on.
|
|
6
|
+
*/
|
|
7
|
+
serverFrame: number;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The command sent by the client.
|
|
11
|
+
*/
|
|
12
|
+
cmd: UserCommand;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The timestamp (in ms) relative to start of recording.
|
|
16
|
+
*/
|
|
17
|
+
timestamp: number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The hash of the game state AFTER this frame was executed.
|
|
21
|
+
* Optional for backward compatibility or lightweight recording.
|
|
22
|
+
*/
|
|
23
|
+
stateHash?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ReplaySession {
|
|
27
|
+
/**
|
|
28
|
+
* Metadata about the recording.
|
|
29
|
+
*/
|
|
30
|
+
metadata: {
|
|
31
|
+
map: string;
|
|
32
|
+
date: string;
|
|
33
|
+
version: string;
|
|
34
|
+
seed?: number; // Random seed
|
|
35
|
+
gravity?: { x: number, y: number, z: number }; // Capture gravity
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The sequence of inputs.
|
|
40
|
+
*/
|
|
41
|
+
frames: ReplayFrame[];
|
|
42
|
+
}
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { PmoveTraceFn, PmoveTraceResult } from './pmove/types.js';
|
|
2
|
+
import type { Vec3 } from './math/vec3.js';
|
|
3
|
+
import { CONTENTS_LADDER } from './bsp/contents.js';
|
|
4
|
+
|
|
5
|
+
export const intersects = (end: Vec3, maxs: Vec3, mins: Vec3, boxMins: Vec3, boxMaxs: Vec3): boolean => {
|
|
6
|
+
return (
|
|
7
|
+
end.x + maxs.x > boxMins.x &&
|
|
8
|
+
end.x + mins.x < boxMaxs.x &&
|
|
9
|
+
end.y + maxs.y > boxMins.y &&
|
|
10
|
+
end.y + mins.y < boxMaxs.y &&
|
|
11
|
+
end.z + maxs.z > boxMins.z &&
|
|
12
|
+
end.z + mins.z < boxMaxs.z
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const stairTrace: PmoveTraceFn = (start: Vec3, end: Vec3, mins?: Vec3, maxs?: Vec3): PmoveTraceResult => {
|
|
17
|
+
// Default bbox if not provided
|
|
18
|
+
const useMins = mins ?? { x: -16, y: -16, z: -24 };
|
|
19
|
+
const useMaxs = maxs ?? { x: 16, y: 16, z: 32 };
|
|
20
|
+
|
|
21
|
+
// Step: x from 0 forward, z from 0 to 8
|
|
22
|
+
const STEP_HEIGHT = 8;
|
|
23
|
+
const STEP_X_START = 0;
|
|
24
|
+
|
|
25
|
+
const isHorizontal = Math.abs(end.z - start.z) < 1;
|
|
26
|
+
const isMovingDown = end.z < start.z;
|
|
27
|
+
|
|
28
|
+
// Check if trying to go below the floor
|
|
29
|
+
const endMinZ = end.z + useMins.z;
|
|
30
|
+
const startMinZ = start.z + useMins.z;
|
|
31
|
+
const endMaxX = end.x + useMaxs.x;
|
|
32
|
+
|
|
33
|
+
// If moving horizontally, check if we'd hit the vertical face of the step
|
|
34
|
+
// The step only blocks if the player's origin is below the step height
|
|
35
|
+
if (isHorizontal && end.z < STEP_HEIGHT && endMaxX > STEP_X_START) {
|
|
36
|
+
// Check if we're crossing into the step area
|
|
37
|
+
const startMaxX = start.x + useMaxs.x;
|
|
38
|
+
if (startMaxX <= STEP_X_START) {
|
|
39
|
+
// We're moving from before the step to past it, block
|
|
40
|
+
return {
|
|
41
|
+
allsolid: false,
|
|
42
|
+
startsolid: false,
|
|
43
|
+
fraction: 0,
|
|
44
|
+
endpos: start,
|
|
45
|
+
planeNormal: { x: -1, y: 0, z: 0 },
|
|
46
|
+
contents: 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If moving down and over the step area, land on the step surface
|
|
52
|
+
if (isMovingDown && end.x >= STEP_X_START) {
|
|
53
|
+
// The step surface is at z=STEP_HEIGHT in world space
|
|
54
|
+
// The player's bbox bottom reaches this plane when origin.z + mins.z = STEP_HEIGHT
|
|
55
|
+
// So the player's origin should be at z = STEP_HEIGHT - mins.z
|
|
56
|
+
const landZ = STEP_HEIGHT - useMins.z;
|
|
57
|
+
|
|
58
|
+
// Check if we'd pass through the step surface
|
|
59
|
+
// We cross the plane if start is above it and end would be below it
|
|
60
|
+
if (startMinZ > STEP_HEIGHT && endMinZ < STEP_HEIGHT) {
|
|
61
|
+
// Calculate the fraction along the ray where we intersect the plane
|
|
62
|
+
// The bbox bottom is at: start.z + useMins.z + t * (end.z - start.z + 0) = STEP_HEIGHT
|
|
63
|
+
// Solving for t: t = (STEP_HEIGHT - (start.z + useMins.z)) / ((end.z + useMins.z) - (start.z + useMins.z))
|
|
64
|
+
const fraction = (STEP_HEIGHT - startMinZ) / (endMinZ - startMinZ);
|
|
65
|
+
|
|
66
|
+
// Clamp to valid range [0, 1]
|
|
67
|
+
const clampedFraction = Math.max(0, Math.min(1, fraction));
|
|
68
|
+
|
|
69
|
+
// Calculate the endpos along the ray at this fraction
|
|
70
|
+
const finalX = start.x + clampedFraction * (end.x - start.x);
|
|
71
|
+
const finalY = start.y + clampedFraction * (end.y - start.y);
|
|
72
|
+
const finalZ = start.z + clampedFraction * (end.z - start.z);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
allsolid: false,
|
|
76
|
+
startsolid: false,
|
|
77
|
+
fraction: clampedFraction,
|
|
78
|
+
endpos: { x: finalX, y: finalY, z: finalZ },
|
|
79
|
+
planeNormal: { x: 0, y: 0, z: 1 },
|
|
80
|
+
contents: 1,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If moving down and would go below floor level, block at floor
|
|
86
|
+
if (isMovingDown && endMinZ < 0) {
|
|
87
|
+
// Floor is at z=0, so player origin should be at z = -mins.z when landing
|
|
88
|
+
const landZ = -useMins.z;
|
|
89
|
+
|
|
90
|
+
// Only apply if we're crossing the floor plane
|
|
91
|
+
if (startMinZ >= 0) {
|
|
92
|
+
// Calculate fraction where bbox bottom hits z=0
|
|
93
|
+
const fraction = (0 - startMinZ) / (endMinZ - startMinZ);
|
|
94
|
+
const clampedFraction = Math.max(0, Math.min(1, fraction));
|
|
95
|
+
|
|
96
|
+
const finalX = start.x + clampedFraction * (end.x - start.x);
|
|
97
|
+
const finalY = start.y + clampedFraction * (end.y - start.y);
|
|
98
|
+
const finalZ = start.z + clampedFraction * (end.z - start.z);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
allsolid: false,
|
|
102
|
+
startsolid: false,
|
|
103
|
+
fraction: clampedFraction,
|
|
104
|
+
endpos: { x: finalX, y: finalY, z: finalZ },
|
|
105
|
+
planeNormal: { x: 0, y: 0, z: 1 },
|
|
106
|
+
contents: 1,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Already below floor, block immediately
|
|
111
|
+
return {
|
|
112
|
+
allsolid: false,
|
|
113
|
+
startsolid: false,
|
|
114
|
+
fraction: 0,
|
|
115
|
+
endpos: start,
|
|
116
|
+
planeNormal: { x: 0, y: 0, z: 1 },
|
|
117
|
+
contents: 1,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Free movement
|
|
122
|
+
return {
|
|
123
|
+
allsolid: false,
|
|
124
|
+
startsolid: false,
|
|
125
|
+
fraction: 1.0,
|
|
126
|
+
endpos: end,
|
|
127
|
+
contents: 0,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const ladderTrace: PmoveTraceFn = (start: Vec3, end: Vec3, mins?: Vec3, maxs?: Vec3): PmoveTraceResult => {
|
|
132
|
+
// Default bbox if not provided
|
|
133
|
+
const useMins = mins ?? { x: -16, y: -16, z: -24 };
|
|
134
|
+
const useMaxs = maxs ?? { x: 16, y: 16, z: 32 };
|
|
135
|
+
|
|
136
|
+
// Define the ladder volume (x=0 to x=8, y=-16 to y=16, z=0 to z=100)
|
|
137
|
+
const LADDER_X_MIN = 0;
|
|
138
|
+
const LADDER_X_MAX = 8;
|
|
139
|
+
const LADDER_Y_MIN = -16;
|
|
140
|
+
const LADDER_Y_MAX = 16;
|
|
141
|
+
const LADDER_Z_MIN = 0;
|
|
142
|
+
const LADDER_Z_MAX = 100;
|
|
143
|
+
|
|
144
|
+
// Check if end position is within the ladder volume
|
|
145
|
+
const endInLadder =
|
|
146
|
+
end.x + useMins.x < LADDER_X_MAX &&
|
|
147
|
+
end.x + useMaxs.x > LADDER_X_MIN &&
|
|
148
|
+
end.y + useMins.y < LADDER_Y_MAX &&
|
|
149
|
+
end.y + useMaxs.y > LADDER_Y_MIN &&
|
|
150
|
+
end.z + useMins.z < LADDER_Z_MAX &&
|
|
151
|
+
end.z + useMaxs.z > LADDER_Z_MIN;
|
|
152
|
+
|
|
153
|
+
// If moving into the ladder from outside (moving forward into it)
|
|
154
|
+
const movingIntoLadder = start.x < LADDER_X_MIN && end.x >= LADDER_X_MIN;
|
|
155
|
+
|
|
156
|
+
// If moving horizontally into the ladder front face, block with ladder surface
|
|
157
|
+
if (movingIntoLadder && Math.abs(end.z - start.z) < 0.1) {
|
|
158
|
+
return {
|
|
159
|
+
allsolid: false,
|
|
160
|
+
startsolid: false,
|
|
161
|
+
fraction: 0,
|
|
162
|
+
endpos: start,
|
|
163
|
+
planeNormal: { x: -1, y: 0, z: 0 },
|
|
164
|
+
contents: CONTENTS_LADDER,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// If we're in the ladder volume, return success but with CONTENTS_LADDER
|
|
169
|
+
// This allows the player to detect they're on a ladder without blocking movement
|
|
170
|
+
if (endInLadder) {
|
|
171
|
+
return {
|
|
172
|
+
allsolid: false,
|
|
173
|
+
startsolid: false,
|
|
174
|
+
fraction: 1.0,
|
|
175
|
+
endpos: end,
|
|
176
|
+
contents: CONTENTS_LADDER,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Floor at z=0
|
|
181
|
+
if (end.z + useMins.z <= 0) {
|
|
182
|
+
return {
|
|
183
|
+
allsolid: false,
|
|
184
|
+
startsolid: false,
|
|
185
|
+
fraction: 0,
|
|
186
|
+
endpos: start,
|
|
187
|
+
planeNormal: { x: 0, y: 0, z: 1 },
|
|
188
|
+
contents: 1,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// No collision - free movement
|
|
193
|
+
return {
|
|
194
|
+
allsolid: false,
|
|
195
|
+
startsolid: false,
|
|
196
|
+
fraction: 1.0,
|
|
197
|
+
endpos: end,
|
|
198
|
+
contents: 0,
|
|
199
|
+
};
|
|
200
|
+
};
|