quake2ts 0.0.7 → 0.0.40
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/README.md +425 -0
- package/apps/viewer/dist/browser/index.global.js +1 -1
- package/apps/viewer/dist/browser/index.global.js.map +1 -1
- package/apps/viewer/dist/cjs/index.cjs +2097 -295
- package/apps/viewer/dist/cjs/index.cjs.map +1 -1
- package/apps/viewer/dist/esm/index.js +2097 -295
- package/apps/viewer/dist/esm/index.js.map +1 -1
- package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
- package/apps/viewer/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +1 -1
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +1200 -13
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +1186 -12
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/index.d.ts +14 -6
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/client/dist/types/input/bindings.d.ts +18 -0
- package/packages/client/dist/types/input/bindings.d.ts.map +1 -0
- package/packages/client/dist/types/input/command-buffer.d.ts +15 -0
- package/packages/client/dist/types/input/command-buffer.d.ts.map +1 -0
- package/packages/client/dist/types/input/controller.d.ts +125 -0
- package/packages/client/dist/types/input/controller.d.ts.map +1 -0
- package/packages/client/dist/types/prediction.d.ts +38 -0
- package/packages/client/dist/types/prediction.d.ts.map +1 -0
- package/packages/client/dist/types/view-effects.d.ts +41 -0
- package/packages/client/dist/types/view-effects.d.ts.map +1 -0
- package/packages/engine/dist/browser/index.global.js +257 -1
- package/packages/engine/dist/browser/index.global.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +2408 -2
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +2340 -2
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/assets/animation.d.ts +33 -0
- package/packages/engine/dist/types/assets/animation.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/audio.d.ts +21 -0
- package/packages/engine/dist/types/assets/audio.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/bsp.d.ts +1 -1
- package/packages/engine/dist/types/assets/bsp.d.ts.map +1 -1
- package/packages/engine/dist/types/assets/ingestion.d.ts +31 -0
- package/packages/engine/dist/types/assets/ingestion.d.ts.map +1 -1
- package/packages/engine/dist/types/assets/manager.d.ts +43 -0
- package/packages/engine/dist/types/assets/manager.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/md3.d.ts +69 -0
- package/packages/engine/dist/types/assets/md3.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/ogg.d.ts +12 -0
- package/packages/engine/dist/types/assets/ogg.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pakIndexStore.d.ts +19 -0
- package/packages/engine/dist/types/assets/pakIndexStore.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pakValidation.d.ts +28 -0
- package/packages/engine/dist/types/assets/pakValidation.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/pcx.d.ts +13 -0
- package/packages/engine/dist/types/assets/pcx.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/texture.d.ts +29 -0
- package/packages/engine/dist/types/assets/texture.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/wal.d.ts +21 -0
- package/packages/engine/dist/types/assets/wal.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/wav.d.ts +11 -0
- package/packages/engine/dist/types/assets/wav.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/api.d.ts +29 -0
- package/packages/engine/dist/types/audio/api.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/channels.d.ts +15 -0
- package/packages/engine/dist/types/audio/channels.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/constants.d.ts +24 -0
- package/packages/engine/dist/types/audio/constants.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/context.d.ts +67 -0
- package/packages/engine/dist/types/audio/context.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/music.d.ts +42 -0
- package/packages/engine/dist/types/audio/music.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/precache.d.ts +28 -0
- package/packages/engine/dist/types/audio/precache.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/registry.d.ts +13 -0
- package/packages/engine/dist/types/audio/registry.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/spatialization.d.ts +14 -0
- package/packages/engine/dist/types/audio/spatialization.d.ts.map +1 -0
- package/packages/engine/dist/types/audio/system.d.ts +101 -0
- package/packages/engine/dist/types/audio/system.d.ts.map +1 -0
- package/packages/engine/dist/types/configstrings.d.ts +1 -0
- package/packages/engine/dist/types/configstrings.d.ts.map +1 -1
- package/packages/engine/dist/types/index.d.ts +26 -1
- package/packages/engine/dist/types/index.d.ts.map +1 -1
- package/packages/engine/dist/types/render/bspPipeline.d.ts +42 -0
- package/packages/engine/dist/types/render/bspPipeline.d.ts.map +1 -0
- package/packages/engine/dist/types/render/bspTraversal.d.ts +11 -0
- package/packages/engine/dist/types/render/bspTraversal.d.ts.map +1 -0
- package/packages/engine/dist/types/render/culling.d.ts +8 -0
- package/packages/engine/dist/types/render/culling.d.ts.map +1 -0
- package/packages/engine/dist/types/render/md2Pipeline.d.ts +51 -0
- package/packages/engine/dist/types/render/md2Pipeline.d.ts.map +1 -0
- package/packages/engine/dist/types/render/resources.d.ts +10 -0
- package/packages/engine/dist/types/render/resources.d.ts.map +1 -1
- package/packages/engine/dist/types/render/skybox.d.ts +26 -0
- package/packages/engine/dist/types/render/skybox.d.ts.map +1 -0
- package/packages/game/dist/browser/index.global.js +1 -1
- package/packages/game/dist/browser/index.global.js.map +1 -1
- package/packages/game/dist/cjs/index.cjs +2926 -116
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +2863 -115
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/ai/constants.d.ts +13 -0
- package/packages/game/dist/types/ai/constants.d.ts.map +1 -0
- package/packages/game/dist/types/ai/index.d.ts +4 -0
- package/packages/game/dist/types/ai/index.d.ts.map +1 -0
- package/packages/game/dist/types/ai/movement.d.ts +20 -0
- package/packages/game/dist/types/ai/movement.d.ts.map +1 -0
- package/packages/game/dist/types/ai/perception.d.ts +21 -0
- package/packages/game/dist/types/ai/perception.d.ts.map +1 -0
- package/packages/game/dist/types/checksum.d.ts +3 -0
- package/packages/game/dist/types/checksum.d.ts.map +1 -0
- package/packages/game/dist/types/combat/armor.d.ts +39 -0
- package/packages/game/dist/types/combat/armor.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damage.d.ts +52 -0
- package/packages/game/dist/types/combat/damage.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damageFlags.d.ts +15 -0
- package/packages/game/dist/types/combat/damageFlags.d.ts.map +1 -0
- package/packages/game/dist/types/combat/damageMods.d.ts +79 -0
- package/packages/game/dist/types/combat/damageMods.d.ts.map +1 -0
- package/packages/game/dist/types/combat/index.d.ts +6 -0
- package/packages/game/dist/types/combat/index.d.ts.map +1 -0
- package/packages/game/dist/types/combat/specialDamage.d.ts +88 -0
- package/packages/game/dist/types/combat/specialDamage.d.ts.map +1 -0
- package/packages/game/dist/types/entities/entity.d.ts +46 -2
- package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
- package/packages/game/dist/types/entities/index.d.ts +6 -2
- package/packages/game/dist/types/entities/index.d.ts.map +1 -1
- package/packages/game/dist/types/entities/pool.d.ts +9 -0
- package/packages/game/dist/types/entities/pool.d.ts.map +1 -1
- package/packages/game/dist/types/entities/spawn.d.ts +27 -0
- package/packages/game/dist/types/entities/spawn.d.ts.map +1 -0
- package/packages/game/dist/types/entities/system.d.ts +32 -1
- package/packages/game/dist/types/entities/system.d.ts.map +1 -1
- package/packages/game/dist/types/entities/thinkScheduler.d.ts +6 -0
- package/packages/game/dist/types/entities/thinkScheduler.d.ts.map +1 -1
- package/packages/game/dist/types/entities/triggers.d.ts +3 -0
- package/packages/game/dist/types/entities/triggers.d.ts.map +1 -0
- package/packages/game/dist/types/entities/utils.d.ts +4 -0
- package/packages/game/dist/types/entities/utils.d.ts.map +1 -0
- package/packages/game/dist/types/index.d.ts +5 -0
- package/packages/game/dist/types/index.d.ts.map +1 -1
- package/packages/game/dist/types/inventory/ammo.d.ts +17 -0
- package/packages/game/dist/types/inventory/ammo.d.ts.map +1 -0
- package/packages/game/dist/types/inventory/index.d.ts +2 -0
- package/packages/game/dist/types/inventory/index.d.ts.map +1 -0
- package/packages/game/dist/types/level.d.ts +1 -0
- package/packages/game/dist/types/level.d.ts.map +1 -1
- package/packages/game/dist/types/save/index.d.ts +4 -0
- package/packages/game/dist/types/save/index.d.ts.map +1 -0
- package/packages/game/dist/types/save/rerelease.d.ts +25 -0
- package/packages/game/dist/types/save/rerelease.d.ts.map +1 -0
- package/packages/game/dist/types/save/save.d.ts +49 -0
- package/packages/game/dist/types/save/save.d.ts.map +1 -0
- package/packages/game/dist/types/save/storage.d.ts +37 -0
- package/packages/game/dist/types/save/storage.d.ts.map +1 -0
- package/packages/shared/dist/browser/index.global.js +1 -1
- package/packages/shared/dist/browser/index.global.js.map +1 -1
- package/packages/shared/dist/cjs/index.cjs +638 -9
- package/packages/shared/dist/cjs/index.cjs.map +1 -1
- package/packages/shared/dist/esm/index.js +616 -9
- package/packages/shared/dist/esm/index.js.map +1 -1
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/shared/dist/types/bsp/collision.d.ts +56 -0
- package/packages/shared/dist/types/bsp/collision.d.ts.map +1 -1
- package/packages/shared/dist/types/bsp/contents.d.ts +1 -0
- package/packages/shared/dist/types/bsp/contents.d.ts.map +1 -1
- package/packages/shared/dist/types/index.d.ts +2 -0
- package/packages/shared/dist/types/index.d.ts.map +1 -1
- package/packages/shared/dist/types/math/random.d.ts +11 -0
- package/packages/shared/dist/types/math/random.d.ts.map +1 -1
- package/packages/shared/dist/types/protocol/contracts.d.ts +17 -0
- package/packages/shared/dist/types/protocol/contracts.d.ts.map +1 -0
- package/packages/shared/dist/types/protocol/usercmd.d.ts +30 -0
- package/packages/shared/dist/types/protocol/usercmd.d.ts.map +1 -0
- package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# quake2ts
|
|
2
|
+
|
|
3
|
+
> A TypeScript/WebGL port of the Quake II re-release engine for creating interactive web-based game engine visualizations and experiments.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/GPL-2.0)
|
|
6
|
+
[](https://www.npmjs.com/package/quake2ts)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
**quake2ts** is a modular, browser-first implementation of the Quake II engine in TypeScript with WebGL2 rendering. It provides a complete game engine architecture broken into composable packages, making it ideal for:
|
|
11
|
+
|
|
12
|
+
- 🎮 **Interactive engine visualizations** - Build web apps that explore game engine internals
|
|
13
|
+
- 🔬 **Educational projects** - Learn game engine architecture through a well-structured codebase
|
|
14
|
+
- 🛠️ **Asset viewers** - Create BSP map viewers, MD2 model viewers, or PAK file explorers
|
|
15
|
+
- 🧪 **Physics experiments** - Test and visualize player movement, collision detection, and game mechanics
|
|
16
|
+
- 🎨 **Retro game development** - Build new games using classic Quake II technology
|
|
17
|
+
|
|
18
|
+
The library is organized as a **pnpm monorepo** with independent packages for different engine layers, allowing you to use only what you need.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install quake2ts
|
|
24
|
+
# or
|
|
25
|
+
pnpm add quake2ts
|
|
26
|
+
# or
|
|
27
|
+
yarn add quake2ts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Browser CDN
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script src="https://unpkg.com/@quake2ts/shared"></script>
|
|
34
|
+
<script src="https://unpkg.com/@quake2ts/engine"></script>
|
|
35
|
+
<script src="https://unpkg.com/@quake2ts/game"></script>
|
|
36
|
+
<script>
|
|
37
|
+
// Access via global: window.Quake2Shared, window.Quake2Engine, etc.
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Running the Complete Engine
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { bootstrapViewer } from '@quake2ts/viewer';
|
|
47
|
+
|
|
48
|
+
// Bootstrap the complete engine with game logic and rendering
|
|
49
|
+
const { engine, game, client, runtime } = bootstrapViewer();
|
|
50
|
+
// Runtime automatically starts at 40Hz simulation tick
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Building Custom Visualizations
|
|
54
|
+
|
|
55
|
+
For interactive engine exploration, import individual packages:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createEngineRuntime } from '@quake2ts/engine';
|
|
59
|
+
import { createGame } from '@quake2ts/game';
|
|
60
|
+
import { createClient } from '@quake2ts/client';
|
|
61
|
+
import { ingestPakFiles, wireFileInput } from '@quake2ts/engine';
|
|
62
|
+
|
|
63
|
+
// 1. Set up asset loading with progress tracking
|
|
64
|
+
const input = document.getElementById('pak-input') as HTMLInputElement;
|
|
65
|
+
wireFileInput(input, async (pakSources) => {
|
|
66
|
+
await ingestPakFiles(pakSources, (progress) => {
|
|
67
|
+
console.log(`Loading assets: ${progress.percent}%`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 2. Create engine, game, and client
|
|
71
|
+
const engine = createEngine({ /* trace callback */ });
|
|
72
|
+
const game = createGame({ /* trace callback */ });
|
|
73
|
+
const client = createClient({ engine });
|
|
74
|
+
|
|
75
|
+
// 3. Bootstrap runtime
|
|
76
|
+
const runtime = createEngineRuntime(engine, game, client);
|
|
77
|
+
runtime.start();
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Package Architecture
|
|
82
|
+
|
|
83
|
+
quake2ts is organized into **5 core packages** and a viewer app:
|
|
84
|
+
|
|
85
|
+
### 📦 `@quake2ts/shared` - Foundation Layer
|
|
86
|
+
|
|
87
|
+
Shared math, physics, and protocol utilities.
|
|
88
|
+
|
|
89
|
+
**Key Exports:**
|
|
90
|
+
- **Math utilities:** `Vec3` operations, angle conversions, color blending, random number generation
|
|
91
|
+
- **Collision detection:** `CONTENTS_*`, `SURF_*`, `MASK_*` constants
|
|
92
|
+
- **Player movement physics:** Complete `pmove` implementation with friction, acceleration, jumping, water/air movement, ducking, and stuck detection
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { Vec3, pmove, MASK_PLAYERSOLID } from '@quake2ts/shared';
|
|
96
|
+
|
|
97
|
+
// Use Quake II's player movement physics
|
|
98
|
+
const result = pmove(playerState, userCmd, traceFunction);
|
|
99
|
+
console.log('New position:', result.origin);
|
|
100
|
+
console.log('New velocity:', result.velocity);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 🎨 `@quake2ts/engine` - Platform & Rendering Layer
|
|
104
|
+
|
|
105
|
+
Web-facing services: WebGL2 rendering, asset loading, virtual filesystem, and engine runtime.
|
|
106
|
+
|
|
107
|
+
**Key Exports:**
|
|
108
|
+
|
|
109
|
+
**Asset System:**
|
|
110
|
+
- `PakArchive`, `VirtualFileSystem` - PAK file reading and virtual filesystem
|
|
111
|
+
- `ingestPaks`, `ingestPakFiles` - Asset ingestion pipeline with progress callbacks
|
|
112
|
+
- `wireFileInput`, `wireDropTarget` - Browser file handling helpers
|
|
113
|
+
- `Md2Loader`, `parseMd2` - MD2 model parsing with animation support
|
|
114
|
+
- `LruCache` - Asset caching system
|
|
115
|
+
|
|
116
|
+
**Rendering:**
|
|
117
|
+
- `createWebGLContext` - WebGL2 context initialization
|
|
118
|
+
- `ShaderProgram`, `VertexBuffer`, `IndexBuffer`, `Texture2D` - GPU resource wrappers
|
|
119
|
+
- `buildBspGeometry` - BSP geometry builder with lightmap atlas packing
|
|
120
|
+
|
|
121
|
+
**Engine Core:**
|
|
122
|
+
- `FixedTimestepLoop` - Deterministic 40Hz simulation loop with interpolation
|
|
123
|
+
- `EngineHost` - Game/client lifecycle manager
|
|
124
|
+
- `EngineRuntime`, `createEngineRuntime` - Complete runtime bootstrap
|
|
125
|
+
- `CvarRegistry`, `Cvar` - Configuration variable system
|
|
126
|
+
- `ConfigStringRegistry` - Deterministic asset indexing
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import {
|
|
130
|
+
ingestPakFiles,
|
|
131
|
+
buildBspGeometry,
|
|
132
|
+
createWebGLContext,
|
|
133
|
+
FixedTimestepLoop
|
|
134
|
+
} from '@quake2ts/engine';
|
|
135
|
+
|
|
136
|
+
// Create a BSP map viewer
|
|
137
|
+
const vfs = await ingestPakFiles(pakSources);
|
|
138
|
+
const bspData = vfs.readFile('maps/base1.bsp');
|
|
139
|
+
const geometry = buildBspGeometry(bspData);
|
|
140
|
+
|
|
141
|
+
// Set up WebGL rendering
|
|
142
|
+
const gl = createWebGLContext(canvas);
|
|
143
|
+
const loop = new FixedTimestepLoop(
|
|
144
|
+
(dt) => { /* simulate */ },
|
|
145
|
+
(alpha) => { /* render with interpolation */ }
|
|
146
|
+
);
|
|
147
|
+
loop.start();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 🎯 `@quake2ts/game` - Game Logic Layer
|
|
151
|
+
|
|
152
|
+
Authoritative simulation and entity system.
|
|
153
|
+
|
|
154
|
+
**Key Exports:**
|
|
155
|
+
- `createGame` - Main game factory function
|
|
156
|
+
- `EntitySystem` - Entity management with pooling
|
|
157
|
+
- `Entity` - Base entity class with `MoveType`, `Solid`, think callbacks, and field metadata
|
|
158
|
+
- `GameFrameLoop` - Frame timing with prep/simulate/post stages
|
|
159
|
+
- `LevelClock` - Level time tracking
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createGame, Entity, MoveType, Solid } from '@quake2ts/game';
|
|
163
|
+
|
|
164
|
+
const game = createGame(
|
|
165
|
+
{ trace: /* collision trace */ },
|
|
166
|
+
{ gravity: { x: 0, y: 0, z: -800 } }
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Create custom entities
|
|
170
|
+
const platform = new Entity();
|
|
171
|
+
platform.moveType = MoveType.Push;
|
|
172
|
+
platform.solid = Solid.Bsp;
|
|
173
|
+
platform.think = () => {
|
|
174
|
+
// Update logic
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
game.entitySystem.spawn(platform);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 🖥️ `@quake2ts/client` - Client & Prediction Layer
|
|
181
|
+
|
|
182
|
+
Client-side rendering interface and state prediction.
|
|
183
|
+
|
|
184
|
+
**Key Exports:**
|
|
185
|
+
- `createClient` - Client factory with prediction support
|
|
186
|
+
- `ClientRenderer` - Rendering interface
|
|
187
|
+
- `PredictionState` - Client-side state prediction
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { createClient } from '@quake2ts/client';
|
|
191
|
+
|
|
192
|
+
const client = createClient({ engine });
|
|
193
|
+
// Client handles HUD rendering and prediction
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 🔧 `@quake2ts/tools` - Asset Tools
|
|
197
|
+
|
|
198
|
+
Utilities for asset preparation and inspection.
|
|
199
|
+
|
|
200
|
+
**Key Exports:**
|
|
201
|
+
- `describeAsset` - Asset summary and metadata extraction
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { describeAsset } from '@quake2ts/tools';
|
|
205
|
+
|
|
206
|
+
const info = describeAsset(assetBuffer);
|
|
207
|
+
console.log(info);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 👁️ `@quake2ts/viewer` - Demo Application
|
|
211
|
+
|
|
212
|
+
Minimal viewer harness demonstrating complete engine integration.
|
|
213
|
+
|
|
214
|
+
**Key Exports:**
|
|
215
|
+
- `bootstrapViewer` - Complete engine/game/client bootstrap
|
|
216
|
+
|
|
217
|
+
## Interactive Visualization Examples
|
|
218
|
+
|
|
219
|
+
### Example 1: BSP Map Explorer
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { ingestPakFiles, buildBspGeometry, VirtualFileSystem } from '@quake2ts/engine';
|
|
223
|
+
|
|
224
|
+
// Load PAK files
|
|
225
|
+
const vfs = await ingestPakFiles(pakSources, (progress) => {
|
|
226
|
+
updateProgressBar(progress.percent);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// List all maps
|
|
230
|
+
const maps = vfs.listFiles('maps/*.bsp');
|
|
231
|
+
console.log('Available maps:', maps);
|
|
232
|
+
|
|
233
|
+
// Render a specific map
|
|
234
|
+
const bspData = vfs.readFile('maps/base1.bsp');
|
|
235
|
+
const geometry = buildBspGeometry(bspData);
|
|
236
|
+
|
|
237
|
+
// Display geometry stats
|
|
238
|
+
console.log(`Vertices: ${geometry.vertices.length}`);
|
|
239
|
+
console.log(`Faces: ${geometry.indices.length / 3}`);
|
|
240
|
+
console.log(`Lightmaps: ${geometry.lightmaps.length}`);
|
|
241
|
+
|
|
242
|
+
// Render with WebGL
|
|
243
|
+
renderBspGeometry(gl, geometry);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Example 2: MD2 Model Viewer with Animations
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { Md2Loader, parseMd2 } from '@quake2ts/engine';
|
|
250
|
+
|
|
251
|
+
// Load MD2 model
|
|
252
|
+
const modelData = vfs.readFile('models/monsters/soldier/tris.md2');
|
|
253
|
+
const model = parseMd2(modelData);
|
|
254
|
+
|
|
255
|
+
// Display model info
|
|
256
|
+
console.log(`Frames: ${model.header.numFrames}`);
|
|
257
|
+
console.log(`Animation groups:`, model.animations);
|
|
258
|
+
|
|
259
|
+
// Animate between frames
|
|
260
|
+
let currentFrame = 0;
|
|
261
|
+
const animationGroup = model.animations.find(a => a.name === 'run');
|
|
262
|
+
|
|
263
|
+
function animate() {
|
|
264
|
+
currentFrame = (currentFrame + 1) % animationGroup.frameCount;
|
|
265
|
+
const frame = model.frames[animationGroup.firstFrame + currentFrame];
|
|
266
|
+
renderMd2Frame(gl, frame, model.triangles);
|
|
267
|
+
requestAnimationFrame(animate);
|
|
268
|
+
}
|
|
269
|
+
animate();
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Example 3: Player Movement Physics Visualization
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { pmove, Vec3 } from '@quake2ts/shared';
|
|
276
|
+
import { createGame } from '@quake2ts/game';
|
|
277
|
+
|
|
278
|
+
// Set up player state
|
|
279
|
+
const playerState = {
|
|
280
|
+
origin: { x: 0, y: 0, z: 24 },
|
|
281
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
282
|
+
// ... other pmove fields
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Simulate user input
|
|
286
|
+
const userCmd = {
|
|
287
|
+
forwardmove: 400, // Move forward
|
|
288
|
+
sidemove: 0,
|
|
289
|
+
upmove: 0,
|
|
290
|
+
buttons: 0,
|
|
291
|
+
angles: { pitch: 0, yaw: 90, roll: 0 }
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Run one physics tick
|
|
295
|
+
const result = pmove(playerState, userCmd, traceFunction);
|
|
296
|
+
|
|
297
|
+
// Visualize the movement
|
|
298
|
+
drawPlayerPosition(result.origin);
|
|
299
|
+
drawVelocityVector(result.origin, result.velocity);
|
|
300
|
+
console.log(`Speed: ${Vec3.length(result.velocity)} units/sec`);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Example 4: Entity System Explorer
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { createGame, MoveType, Solid } from '@quake2ts/game';
|
|
307
|
+
|
|
308
|
+
const game = createGame({ trace }, { gravity: { x: 0, y: 0, z: -800 } });
|
|
309
|
+
|
|
310
|
+
// Spawn various entity types
|
|
311
|
+
const entities = [
|
|
312
|
+
{ type: 'static', moveType: MoveType.None, solid: Solid.Bsp },
|
|
313
|
+
{ type: 'physics', moveType: MoveType.Toss, solid: Solid.BoundingBox },
|
|
314
|
+
{ type: 'player', moveType: MoveType.Walk, solid: Solid.BoundingBox },
|
|
315
|
+
{ type: 'trigger', moveType: MoveType.None, solid: Solid.Trigger }
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
entities.forEach(config => {
|
|
319
|
+
const entity = new Entity();
|
|
320
|
+
entity.moveType = config.moveType;
|
|
321
|
+
entity.solid = config.solid;
|
|
322
|
+
entity.think = () => {
|
|
323
|
+
// Visualize entity state in real-time
|
|
324
|
+
highlightEntity(entity);
|
|
325
|
+
};
|
|
326
|
+
game.entitySystem.spawn(entity);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Run simulation and visualize
|
|
330
|
+
game.loop.start();
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Build Formats
|
|
334
|
+
|
|
335
|
+
Each package is published in **three formats**:
|
|
336
|
+
|
|
337
|
+
- **ESM** - `dist/esm/index.js` - Modern ES modules (recommended)
|
|
338
|
+
- **CJS** - `dist/cjs/index.cjs` - CommonJS for Node.js
|
|
339
|
+
- **Browser** - `dist/browser/index.js` - Minified IIFE for `<script>` tags
|
|
340
|
+
|
|
341
|
+
TypeScript declarations are included at `dist/types/index.d.ts`.
|
|
342
|
+
|
|
343
|
+
## Key Features
|
|
344
|
+
|
|
345
|
+
### ✅ Implemented
|
|
346
|
+
|
|
347
|
+
- ✅ **Complete player movement physics** - Full pmove with friction, acceleration, jumping, water/air movement
|
|
348
|
+
- ✅ **Asset loading pipeline** - PAK files, virtual filesystem, MD2 models, BSP geometry
|
|
349
|
+
- ✅ **WebGL2 rendering foundation** - Context management, shader compilation, GPU resources
|
|
350
|
+
- ✅ **Deterministic engine loop** - Fixed 40Hz simulation with frame interpolation
|
|
351
|
+
- ✅ **Entity system** - Component-based entities with pooling and think scheduler
|
|
352
|
+
- ✅ **Configuration system** - Cvars and ConfigStrings for runtime configuration
|
|
353
|
+
- ✅ **Lightmap atlas packing** - Efficient texture packing for BSP lightmaps
|
|
354
|
+
|
|
355
|
+
### 🚧 In Progress
|
|
356
|
+
|
|
357
|
+
- 🚧 BSP traversal and PVS (Potentially Visible Set) culling
|
|
358
|
+
- 🚧 Complete rendering pipeline (BSP surfaces, MD2/MD3 models, particles)
|
|
359
|
+
- 🚧 HUD and UI rendering
|
|
360
|
+
- 🚧 Audio system (WebAudio integration)
|
|
361
|
+
- 🚧 Combat and items
|
|
362
|
+
- 🚧 AI and monsters
|
|
363
|
+
- 🚧 Save/load serialization
|
|
364
|
+
|
|
365
|
+
## Documentation
|
|
366
|
+
|
|
367
|
+
Comprehensive documentation is available in the `quake2ts/docs` folder:
|
|
368
|
+
|
|
369
|
+
- **[overview.md](docs/overview.md)** - Architecture overview and package descriptions
|
|
370
|
+
- **[progress.md](docs/progress.md)** - Detailed progress log with completed features
|
|
371
|
+
- **[implementation.md](implementation.md)** - Detailed implementation plan and milestones
|
|
372
|
+
- **Section guides** - 10 detailed guides covering asset loading, rendering, physics, entities, combat, AI, audio, input, save/load, and testing
|
|
373
|
+
|
|
374
|
+
## Development
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
# Install dependencies
|
|
378
|
+
pnpm install
|
|
379
|
+
|
|
380
|
+
# Build all packages
|
|
381
|
+
pnpm run build
|
|
382
|
+
|
|
383
|
+
# Run tests
|
|
384
|
+
pnpm run test
|
|
385
|
+
|
|
386
|
+
# Watch mode for development
|
|
387
|
+
pnpm run dev
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Requirements
|
|
391
|
+
|
|
392
|
+
- **Node.js** 18+ (for development)
|
|
393
|
+
- **pnpm** 8.15.7+ (package manager)
|
|
394
|
+
- **Modern browser** with WebGL2 support (for runtime)
|
|
395
|
+
|
|
396
|
+
## Browser Compatibility
|
|
397
|
+
|
|
398
|
+
- Chrome 56+
|
|
399
|
+
- Firefox 51+
|
|
400
|
+
- Safari 15+
|
|
401
|
+
- Edge 79+
|
|
402
|
+
|
|
403
|
+
All browsers must support **WebGL2** and **ES6 modules**.
|
|
404
|
+
|
|
405
|
+
## License
|
|
406
|
+
|
|
407
|
+
**GPL-2.0** - This project is a port of Quake II, which is licensed under GPL-2.0. See [LICENSE](LICENSE) for details.
|
|
408
|
+
|
|
409
|
+
## Repository
|
|
410
|
+
|
|
411
|
+
- **GitHub:** https://github.com/jburnhams/quake2
|
|
412
|
+
- **Issues:** https://github.com/jburnhams/quake2/issues
|
|
413
|
+
- **npm:** https://www.npmjs.com/package/quake2ts
|
|
414
|
+
|
|
415
|
+
## Contributing
|
|
416
|
+
|
|
417
|
+
Contributions are welcome! Please see the [implementation.md](implementation.md) for the current roadmap and open issues.
|
|
418
|
+
|
|
419
|
+
## Credits
|
|
420
|
+
|
|
421
|
+
Based on the [Quake II re-release](https://github.com/id-Software/quake2-rerelease-dll) by id Software. This project is an independent TypeScript/WebGL port for educational and experimental purposes.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
**Happy hacking!** 🚀
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var Quake2=(()=>{var R=Object.defineProperty;var it=Object.getOwnPropertyDescriptor;var at=Object.getOwnPropertyNames;var ot=Object.prototype.hasOwnProperty;var ct=(t,e)=>{for(var r in e)R(t,r,{get:e[r],enumerable:!0})},lt=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of at(e))!ot.call(t,n)&&n!==r&&R(t,n,{get:()=>e[n],enumerable:!(s=it(e,n))||s.enumerable});return t};var ut=t=>lt(R({},"__esModule",{value:!0}),t);var Le={};ct(Le,{bootstrapViewer:()=>Ne});function I(t){let e;return{init(r){e=r,t.engine.trace({x:0,y:0,z:0},{x:1,y:0,z:0})},predict(r){let{origin:s,velocity:n}=r;return{origin:{x:s.x+n.x,y:s.y+n.y,z:s.z+n.z},velocity:n}},render(r){e=r.latest??e},shutdown(){e=void 0}}}var B=25,ht=5,ft=()=>typeof performance<"u"?performance.now():Date.now(),mt=t=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>t()):setTimeout(t,B)},V=class{constructor(t,e={}){this.callbacks=t,this.accumulatorMs=0,this.frame=0,this.running=!1,this.tick=()=>{if(!this.running)return;let n=this.options.now(),a=this.lastTimeMs===void 0?0:n-this.lastTimeMs;this.lastTimeMs=n,this.advance(a,n),this.running&&this.options.schedule(this.tick)};let r=e.fixedDeltaMs??B,s=e.maxSubSteps??ht;this.options={fixedDeltaMs:r,maxSubSteps:s,maxDeltaMs:e.maxDeltaMs??r*s,startTimeMs:e.startTimeMs,now:e.now??ft,schedule:e.schedule??mt}}start(){this.running||(this.running=!0,this.lastTimeMs=this.options.startTimeMs??this.options.now(),this.options.schedule(this.tick))}stop(){this.running=!1}pump(t){let e=(this.lastTimeMs??0)+t;this.lastTimeMs=e,this.advance(t,e)}isRunning(){return this.running}get frameNumber(){return this.frame}advance(t,e){let r=Math.min(Math.max(t,0),this.options.maxDeltaMs);this.accumulatorMs=Math.min(this.accumulatorMs+r,this.options.fixedDeltaMs*this.options.maxSubSteps);let s=0;for(;this.accumulatorMs>=this.options.fixedDeltaMs&&s<this.options.maxSubSteps;)this.frame+=1,this.callbacks.simulate({frame:this.frame,deltaMs:this.options.fixedDeltaMs,nowMs:e}),this.accumulatorMs-=this.options.fixedDeltaMs,s+=1;let n=this.options.fixedDeltaMs===0?0:this.accumulatorMs/this.options.fixedDeltaMs;this.callbacks.render?.({alpha:n,nowMs:e,accumulatorMs:this.accumulatorMs,frame:this.frame})}},dt=class{constructor(t,e,r={}){this.game=t,this.client=e,this.started=!1,this.stepSimulation=n=>{this.previousFrame=this.latestFrame,this.latestFrame=this.game.frame(n)},this.renderClient=n=>{this.client&&this.client.render({...n,previous:this.previousFrame,latest:this.latestFrame})};let s=r.loop?.now?.()??Date.now();this.startTimeMs=r.startTimeMs??r.loop?.startTimeMs??s,this.loop=new V({simulate:this.stepSimulation,render:this.renderClient},{...r.loop,startTimeMs:this.startTimeMs})}start(){this.started||(this.latestFrame=this.game.init(this.startTimeMs)??this.latestFrame,this.client?.init(this.latestFrame),this.started=!0,this.loop.start())}stop(){this.started&&(this.loop.stop(),this.client?.shutdown(),this.game.shutdown(),this.previousFrame=void 0,this.latestFrame=void 0,this.started=!1)}pump(t){this.loop.pump(t)}getLatestFrame(){return this.latestFrame}isRunning(){return this.loop.isRunning()}},Me=Math.PI/180,Oe=Math.PI/180,De=180/Math.PI,u=1,m=2;var D=8,k=16,yt=32;var vt=16384;var v=65536,H=1<<17,pt=1<<18,xt=1<<19,Tt=1<<20,Et=1<<21,zt=1<<22,_t=1<<23,ke=1<<24,p=1<<25,At=1<<26,Ce=1<<27,Pe=1<<28,Fe=1<<29,x=1<<30,Ue=1<<31;var be=1<<25,We=1<<28,Ge=1<<29,Ie=1<<30,Be=1<<31,Ve=u|m,He=u|v|m|p|x,Ke=u|v|m,Xe=u|H|m|p|x,Ye=yt|D|k,Je=u|k|D,St=u|p|x|m|At,$e=pt|xt|Tt|Et|zt|_t,qe=u|D|k|p|x,je=u|v|m,Ze=u|m,Qe=u|v|m|H,tr=St|vt;var K=256,wt=256,Nt=8192,Lt=2048,Rt=512,gt=256,Mt=K*2,Ot=256,g=32;var X=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Nt]="Sounds",t[t.Images=t.Sounds+Lt]="Images",t[t.Lights=t.Images+Rt]="Lights",t[t.ShadowLights=t.Lights+wt]="ShadowLights",t[t.Items=t.ShadowLights+Ot]="Items",t[t.PlayerSkins=t.Items+gt]="PlayerSkins",t[t.General=t.PlayerSkins+K]="General",t[t.WheelWeapons=t.General+Mt]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+g]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+g]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+g]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(X||{}),er=X.MaxConfigStrings;var Dt=class{constructor(t,e){this.engine=t,this.host=e,this.started=!1}start(){this.started||(this.engine.init(),this.host.start(),this.started=!0)}stop(){this.started&&(this.host.stop(),this.engine.shutdown(),this.started=!1)}pump(t){this.host.pump(t)}getLatestFrame(){return this.host.getLatestFrame()}isRunning(){return this.started&&this.host.isRunning()}};function Y(t,e,r,s){return new Dt(t,new dt(e,r,s))}function kt(){let t=new Uint32Array(256);for(let e=0;e<256;e+=1){let r=e;for(let s=0;s<8;s+=1)r=(r&1)!==0?3988292384^r>>>1:r>>>1;t[e]=r>>>0}return t}var rr=kt();var O=4,M=7*O,sr=[{index:0,size:3,type:5126,stride:M,offset:0},{index:1,size:2,type:5126,stride:M,offset:3*O},{index:2,size:2,type:5126,stride:M,offset:5*O}];function J(t){return{init(){t.trace({x:0,y:0,z:0},{x:0,y:0,z:0})},shutdown(){},createMainLoop(e,r){return new V(e,r)}}}var Ct={x:0,y:0,z:0},cr=Math.PI/180,lr=Math.PI/180,ur=180/Math.PI,h=1,d=2;var P=8,F=16,Pt=32;var Ft=16384;var z=65536,q=1<<17,Ut=1<<18,bt=1<<19,Wt=1<<20,Gt=1<<21,It=1<<22,Bt=1<<23,hr=1<<24,_=1<<25,Vt=1<<26,fr=1<<27,mr=1<<28,dr=1<<29,A=1<<30,yr=1<<31;var vr=1<<25,pr=1<<28,xr=1<<29,Tr=1<<30,Er=1<<31,zr=h|d,_r=h|z|d|_|A,Ar=h|z|d,Sr=h|q|d|_|A,wr=Pt|P|F,Nr=h|F|P,Ht=h|_|A|d|Vt,Lr=Ut|bt|Wt|Gt|It|Bt,Rr=h|P|F|_|A,gr=h|z|d,Mr=h|d,Or=h|z|d|q,Dr=Ht|Ft,j=256,Kt=256,Xt=8192,Yt=2048,Jt=512,$t=256,qt=j*2,jt=256,C=32,Z=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Xt]="Sounds",t[t.Images=t.Sounds+Yt]="Images",t[t.Lights=t.Images+Jt]="Lights",t[t.ShadowLights=t.Lights+Kt]="ShadowLights",t[t.Items=t.ShadowLights+jt]="Items",t[t.PlayerSkins=t.Items+$t]="PlayerSkins",t[t.General=t.PlayerSkins+j]="General",t[t.WheelWeapons=t.General+qt]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+C]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+C]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+C]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(Z||{}),kr=Z.MaxConfigStrings;var Zt={...Ct};function o(){return{...Zt}}var Qt=class{constructor(t){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.origin=o(),this.old_origin=o(),this.velocity=o(),this.avelocity=o(),this.angles=o(),this.mins=o(),this.maxs=o(),this.size=o(),this.mass=0,this.gravity=1,this.movetype=0,this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.deadflag=0,this.enemy=null,this.movetarget=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.solid=0,this.flags=0,this.svflags=0,this.index=t}reset(){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.target=void 0,this.targetname=void 0,this.team=void 0,this.message=void 0,this.origin=o(),this.old_origin=o(),this.velocity=o(),this.avelocity=o(),this.angles=o(),this.mins=o(),this.maxs=o(),this.size=o(),this.mass=0,this.gravity=1,this.movetype=0,this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.deadflag=0,this.enemy=null,this.movetarget=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.think=void 0,this.touch=void 0,this.use=void 0,this.pain=void 0,this.die=void 0,this.solid=0,this.flags=0,this.svflags=0}};var te=2048,T=0,ee=class{constructor(t=te){if(this.freeList=[],this.pendingFree=[],this.activeHead=null,t<1||!Number.isInteger(t))throw new Error("EntityPool requires a positive integer size");this.entities=new Array(t);for(let r=0;r<t;r+=1)this.entities[r]=new Qt(r),r!==T&&this.freeList.push(r);let e=this.entities[T];e.inUse=!0,e.classname="worldspawn",this.activeHead=e}get world(){return this.entities[T]}get capacity(){return this.entities.length}get activeCount(){let t=0;for(let e of this)t+=1;return t}[Symbol.iterator](){let t=this.activeHead;return{next:()=>{if(!t)return{done:!0,value:void 0};let e=t;return t=t.linkNext,{done:!1,value:e}}}}spawn(){let t=this.freeList.pop();if(t===void 0)throw new Error("No free entities available");let e=this.entities[t];return e.reset(),e.inUse=!0,this.link(e),e}deferFree(t){if(t.index===T)throw new Error("Cannot free world entity");!t.inUse||t.freePending||(this.unlink(t),t.inUse=!1,t.freePending=!0,this.pendingFree.push(t.index))}flushFreeList(){if(this.pendingFree.length!==0){for(let t of this.pendingFree)this.entities[t].reset(),this.freeList.push(t);this.pendingFree.length=0}}link(t){t.linkNext=this.activeHead,this.activeHead&&(this.activeHead.linkPrevious=t),this.activeHead=t,t.linkPrevious=null}unlink(t){t.linkPrevious&&(t.linkPrevious.linkNext=t.linkNext),t.linkNext&&(t.linkNext.linkPrevious=t.linkPrevious),this.activeHead===t&&(this.activeHead=t.linkNext),t.linkPrevious=null,t.linkNext=null}},re=class{constructor(){this.queue=[]}schedule(t,e){t.nextthink=e,this.queue.push({entity:t,time:e}),this.queue.sort((r,s)=>r.time===s.time?r.entity.index-s.entity.index:r.time-s.time)}cancel(t){if(this.queue.length!==0)for(let e=this.queue.length-1;e>=0;e-=1)this.queue[e].entity===t&&this.queue.splice(e,1)}runDueThinks(t){for(;this.queue.length>0;){let e=this.queue[0];if(e.time>t)break;this.queue.shift();let{entity:r,time:s}=e;!r.inUse||r.freePending||r.think&&r.nextthink===s&&r.think(r)}}};function $(t){return{min:{x:t.origin.x+t.mins.x,y:t.origin.y+t.mins.y,z:t.origin.z+t.mins.z},max:{x:t.origin.x+t.maxs.x,y:t.origin.y+t.maxs.y,z:t.origin.z+t.maxs.z}}}function se(t,e){return!(t.min.x>e.max.x||t.max.x<e.min.x||t.min.y>e.max.y||t.max.y<e.min.y||t.min.z>e.max.z||t.max.z<e.min.z)}var ne=class{constructor(t){this.currentTimeSeconds=0,this.pool=new ee(t),this.thinkScheduler=new re}get world(){return this.pool.world}get activeCount(){return this.pool.activeCount}get timeSeconds(){return this.currentTimeSeconds}forEachEntity(t){for(let e of this.pool)t(e)}spawn(){return this.pool.spawn()}free(t){this.thinkScheduler.cancel(t),this.pool.deferFree(t)}scheduleThink(t,e){this.thinkScheduler.schedule(t,e)}beginFrame(t){this.currentTimeSeconds=t}runFrame(){this.thinkScheduler.runDueThinks(this.currentTimeSeconds),this.runTouches(),this.pool.flushFreeList()}runTouches(){let t=this.pool.world,e=[];for(let r of this.pool)r!==t&&(!r.inUse||r.freePending||e.push(r));for(let r=0;r<e.length;r+=1){let s=e[r],n=null;for(let a=r+1;a<e.length;a+=1){let c=e[a];if(!s.touch&&!c.touch)continue;n||(n=$(s));let l=$(c);se(n,l)&&(s.touch&&s.touch(s,c),c.touch&&c.touch(c,s))}}}},ie=["prep","simulate","finish"],ae=class{constructor(t){if(this.timeMs=0,this.frame=0,this.stageHandlers={prep:[],simulate:[],finish:[]},this.stageCounts={prep:0,simulate:0,finish:0},this.stageCompactionNeeded={prep:!1,simulate:!1,finish:!1},t)for(let e of ie){let r=t[e];r&&this.addStage(e,r)}}addStage(t,e){let r=this.stageHandlers[t];return r.push(e),this.stageCounts[t]+=1,()=>{let s=r.indexOf(e);s>=0&&r[s]&&(r[s]=void 0,this.stageCounts[t]-=1,this.stageCompactionNeeded[t]=!0)}}reset(t){this.timeMs=t,this.frame=0}advance(t){let e=this.timeMs;this.timeMs=e+t.deltaMs,this.frame=t.frame;let r={...t,timeMs:this.timeMs,previousTimeMs:e,deltaSeconds:t.deltaMs/1e3};if(this.runStage("prep",r),this.stageCounts.simulate===0)throw new Error("GameFrameLoop requires at least one simulate stage");return this.runStage("simulate",r),this.runStage("finish",r),r}runStage(t,e){let r=this.stageHandlers[t];for(let s=0;s<r.length;s+=1){let n=r[s];n&&n(e)}this.stageCompactionNeeded[t]&&this.compactStageHandlers(t)}compactStageHandlers(t){let e=this.stageHandlers[t],r=0;for(let s=0;s<e.length;s+=1){let n=e[s];n&&(e[r]=n,r+=1)}e.length=r,this.stageCompactionNeeded[t]=!1}get time(){return this.timeMs}get frameNumber(){return this.frame}},oe={frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0},ce=class{constructor(){this.state=oe}start(t){let e=t/1e3;this.state={frameNumber:0,timeSeconds:e,previousTimeSeconds:e,deltaSeconds:0}}tick(t){return this.state={frameNumber:t.frame,timeSeconds:t.timeMs/1e3,previousTimeSeconds:t.previousTimeMs/1e3,deltaSeconds:t.deltaSeconds},this.state}get current(){return this.state}},E={x:0,y:0,z:0};function Q(t,e){let r=e.gravity,s=new ce,n=new ae,a=new ne;n.addStage("prep",i=>{s.tick(i),a.beginFrame(s.current.timeSeconds)}),n.addStage("simulate",({deltaSeconds:i})=>{l={x:l.x+r.x*i,y:l.y+r.y*i,z:l.z+r.z*i},c={x:c.x+l.x*i,y:c.y+l.y*i,z:c.z+l.z*i},a.runFrame()});let c={...E},l={...E},G=i=>({frame:i,timeMs:n.time,state:{gravity:{...r},origin:{...c},velocity:{...l},level:{...s.current},entities:{activeCount:a.activeCount,worldClassname:a.world.classname}}}),st=i=>{n.reset(i),s.start(i),c={...E},l={...E},a.beginFrame(i/1e3),a.runFrame()};return{init(i){return st(i),t.trace({x:0,y:0,z:0},r),G(0)},shutdown(){},spawnWorld(){},frame(i){let nt=n.advance(i);return G(nt.frame)},entities:a}}var S={x:0,y:0,z:0};var Ir=Math.PI/180;var Br=Math.PI/180,Vr=180/Math.PI;var f=1,y=2;var b=8,W=16,le=32;var ue=16384;var w=65536,tt=1<<17,he=1<<18,fe=1<<19,me=1<<20,de=1<<21,ye=1<<22,ve=1<<23,Hr=1<<24,N=1<<25,pe=1<<26,Kr=1<<27,Xr=1<<28,Yr=1<<29,L=1<<30,Jr=1<<31;var $r=1<<25,qr=1<<28,jr=1<<29,Zr=1<<30,Qr=1<<31;var ts=f|y,es=f|w|y|N|L,rs=f|w|y,ss=f|tt|y|N|L,ns=le|b|W,is=f|W|b,xe=f|N|L|y|pe,as=he|fe|me|de|ye|ve,os=f|b|W|N|L,cs=f|w|y,ls=f|y,us=f|w|y|tt,hs=xe|ue;var et=256;var Te=256,Ee=8192,ze=2048,_e=512,Ae=256,Se=et*2,we=256,U=32;var rt=(t=>(t[t.Name=0]="Name",t[t.CdTrack=1]="CdTrack",t[t.Sky=2]="Sky",t[t.SkyAxis=3]="SkyAxis",t[t.SkyRotate=4]="SkyRotate",t[t.StatusBar=5]="StatusBar",t[t.AirAccel=59]="AirAccel",t[t.MaxClients=60]="MaxClients",t[t.MapChecksum=61]="MapChecksum",t[t.Models=62]="Models",t[t.Sounds=62+Ee]="Sounds",t[t.Images=t.Sounds+ze]="Images",t[t.Lights=t.Images+_e]="Lights",t[t.ShadowLights=t.Lights+Te]="ShadowLights",t[t.Items=t.ShadowLights+we]="Items",t[t.PlayerSkins=t.Items+Ae]="PlayerSkins",t[t.General=t.PlayerSkins+et]="General",t[t.WheelWeapons=t.General+Se]="WheelWeapons",t[t.WheelAmmo=t.WheelWeapons+U]="WheelAmmo",t[t.WheelPowerups=t.WheelAmmo+U]="WheelPowerups",t[t.CdLoopCount=t.WheelPowerups+U]="CdLoopCount",t[t.GameStyle=t.CdLoopCount+1]="GameStyle",t[t.MaxConfigStrings=t.GameStyle+1]="MaxConfigStrings",t))(rt||{}),fs=rt.MaxConfigStrings;function Ne(){let t=J({trace(n,a){return{start:n,end:a,fraction:1}}}),e=Q({trace(n,a){return{start:n,end:a,fraction:1}}},{gravity:S}),r=I({engine:{trace:()=>({start:S,end:S,fraction:1})}}),s=Y(t,e,r);return s.start(),{engine:t,game:e,client:r,runtime:s}}return ut(Le);})();
|
|
1
|
+
"use strict";var Quake2=(()=>{var oe=Object.defineProperty;var ft=Object.getOwnPropertyDescriptor;var mt=Object.getOwnPropertyNames;var pt=Object.prototype.hasOwnProperty;var yt=(e,t)=>{for(var n in t)oe(e,n,{get:t[n],enumerable:!0})},vt=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of mt(t))!pt.call(e,s)&&s!==n&&oe(e,s,{get:()=>t[s],enumerable:!(r=ft(t,s))||r.enumerable});return e};var gt=e=>vt(oe({},"__esModule",{value:!0}),e);var Hr={};yt(Hr,{bootstrapViewer:()=>Vr});var xt={x:0,y:0,z:0},Xr=Math.PI/180;function I(e,t){return{x:e.x+t.x,y:e.y+t.y,z:e.z+t.z}}function D(e,t){return{x:e.x*t,y:e.y*t,z:e.z*t}}function P(e,t){return e.x*t.x+e.y*t.y+e.z*t.z}function Et(e){return P(e,e)}function G(e){return Math.sqrt(Et(e))}function be(e){let t=G(e);return t===0?e:D(e,1/t)}var ke=0,Ce=1,Pe=2,St=Math.PI/180,$r=180/Math.PI;function ae(e,t){switch(t){case ke:return e.x;case Ce:return e.y;case Pe:default:return e.z}}function ce(e){return e*St}function Le(e){let t=e%360;return t<0?360+t:t}function De(e){let t=ce(ae(e,Ce)),n=ce(ae(e,ke)),r=ce(ae(e,Pe)),s=Math.sin(t),o=Math.cos(t),i=Math.sin(n),a=Math.cos(n),u=Math.sin(r),l=Math.cos(r),c={x:a*o,y:a*s,z:-i},m={x:-u*i*o-l*-s,y:-u*i*s-l*o,z:-u*a},p={x:l*i*o-u*-s,y:l*i*s-u*o,z:l*a};return{forward:c,right:m,up:p}}var y=1,T=2;var he=8,de=16,Tt=32;var _t=16384;var W=65536,Ue=1<<17,wt=1<<18,At=1<<19,zt=1<<20,Nt=1<<21,Rt=1<<22,Lt=1<<23,Yr=1<<24,V=1<<25,Ot=1<<26,jr=1<<27,qr=1<<28,Jr=1<<29,H=1<<30,Zr=1<<31;var Qr=1<<25,es=1<<28,ts=1<<29,ns=1<<30,rs=1<<31,ss=y|T,is=y|W|T|V|H,os=y|W|T,as=y|Ue|T|V|H,cs=Tt|he|de,us=y|de|he,Mt=y|V|H|T|Ot,ls=wt|At|zt|Nt|Rt|Lt,hs=y|he|de|V|H,ds=y|W|T,fs=y|T,ms=y|W|T|Ue,ps=Mt|_t,ys=Number.MAX_SAFE_INTEGER-1,Fe=256,bt=256,kt=8192,Ct=2048,Pt=512,Dt=256,Ut=Fe*2,Ft=256,ue=32,Ie=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+kt]="Sounds",e[e.Images=e.Sounds+Ct]="Images",e[e.Lights=e.Images+Pt]="Lights",e[e.ShadowLights=e.Lights+bt]="ShadowLights",e[e.Items=e.ShadowLights+Ft]="Items",e[e.PlayerSkins=e.Items+Dt]="PlayerSkins",e[e.General=e.PlayerSkins+Fe]="General",e[e.WheelWeapons=e.General+Ut]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+ue]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+ue]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+ue]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(Ie||{}),vs=Ie.MaxConfigStrings,B=(e=>(e[e.None=0]="None",e[e.Feet=1]="Feet",e[e.Waist=2]="Waist",e[e.Under=3]="Under",e))(B||{}),S=(e=>(e[e.Ducked=1]="Ducked",e[e.JumpHeld=2]="JumpHeld",e[e.OnGround=4]="OnGround",e[e.TimeWaterJump=8]="TimeWaterJump",e[e.TimeLand=16]="TimeLand",e[e.TimeTeleport=32]="TimeTeleport",e[e.NoPositionalPrediction=64]="NoPositionalPrediction",e[e.OnLadder=128]="OnLadder",e[e.NoAngularPrediction=256]="NoAngularPrediction",e[e.IgnorePlayerCollision=512]="IgnorePlayerCollision",e[e.TimeTrick=1024]="TimeTrick",e))(S||{});function O(e,t){return(e&t)!==0}var Be=(e=>(e[e.Normal=0]="Normal",e[e.Grapple=1]="Grapple",e[e.NoClip=2]="NoClip",e[e.Spectator=3]="Spectator",e[e.Dead=4]="Dead",e[e.Gib=5]="Gib",e[e.Freeze=6]="Freeze",e))(Be||{}),R=(e=>(e[e.None=0]="None",e[e.Attack=1]="Attack",e[e.Use=2]="Use",e[e.Holster=4]="Holster",e[e.Jump=8]="Jump",e[e.Crouch=16]="Crouch",e[e.Any=128]="Any",e))(R||{});function It(e){let{velocity:t,frametime:n,onGround:r,groundIsSlick:s,onLadder:o,waterlevel:i,pmFriction:a,pmStopSpeed:u,pmWaterFriction:l}=e,c=G(t);if(c<1)return{x:0,y:0,z:t.z};let m=0;if(r&&!s||o){let k=c<u?u:c;m+=k*a*n}i>0&&!o&&(m+=c*l*i*n);let p=c-m;if(p<0&&(p=0),p===c)return t;let z=p/c;return D(t,z)}function Oe(e){let{velocity:t,wishdir:n,wishspeed:r,accel:s,frametime:o}=e,i=P(t,n),a=r-i;if(a<=0)return t;let u=s*o*r;return u>a&&(u=a),{x:t.x+n.x*u,y:t.y+n.y*u,z:t.z+n.z*u}}function Bt(e){let{velocity:t,wishdir:n,wishspeed:r,accel:s,frametime:o}=e,i=Math.min(r,30),a=P(t,n),u=i-a;if(u<=0)return t;let l=s*r*o;return l>u&&(l=u),{x:t.x+n.x*l,y:t.y+n.y*l,z:t.z+n.z*l}}function Gt(e){let{forward:t,right:n,cmd:r,maxSpeed:s}=e,o={x:t.x*r.forwardmove+n.x*r.sidemove,y:t.y*r.forwardmove+n.y*r.sidemove,z:0},i=G(o);if(i>s){let a=s/i;o=D(o,a),i=s}return{wishdir:i===0?o:be(o),wishspeed:i}}function Wt(e){let{forward:t,right:n,cmd:r,maxSpeed:s}=e,o={x:t.x*r.forwardmove+n.x*r.sidemove,y:t.y*r.forwardmove+n.y*r.sidemove,z:0};r.upmove>10?o=I(o,{x:0,y:0,z:r.upmove}):r.upmove<-10?o=I(o,{x:0,y:0,z:r.upmove}):o=I(o,{x:0,y:0,z:10});let i=G(o);if(i>s){let a=s/i;o=D(o,a),i=s}return i*=.5,{wishdir:i===0?o:be(o),wishspeed:i}}function Vt(e,t){return{x:e.x+t.x,y:e.y+t.y,z:e.z+t.z}}function Ht(e){return e>89&&e<180?89:e<271&&e>=180?271:e}function Ge(e){let{pmFlags:t,cmdAngles:n,deltaAngles:r}=e,s;(t&32)!==0?s={x:0,y:n.y+r.y,z:0}:(s=Vt(n,r),s={...s,x:Ht(s.x)});let o=De(s);return{viewangles:s,...o}}var Kt={pmFriction:6,pmStopSpeed:100,pmAccelerate:10,pmAirAccelerate:1,pmWaterAccelerate:4,pmWaterFriction:1,pmMaxSpeed:300,pmDuckSpeed:100,pmWaterSpeed:400,groundIsSlick:!1},Xt=800,L={x:0,y:0,z:0},$t=250;function C(){return{origin:L,velocity:L,viewangles:L,pmFlags:S.OnGround,pmType:Be.Normal,waterlevel:B.None,gravity:Xt,deltaAngles:L}}function Me(e){return e?{...C(),...e,origin:{...e.origin},velocity:{...e.velocity},viewangles:{...e.viewangles},deltaAngles:e.deltaAngles?{...e.deltaAngles}:L}:C()}function N(e,t,n){return e+(t-e)*n}function le(e,t,n){let r=Le(t-e);return r>180&&(r-=360),Le(e+r*n)}function Yt(e,t,n){let r=Math.max(0,Math.min(n,1));return{origin:{x:N(e.origin.x,t.origin.x,r),y:N(e.origin.y,t.origin.y,r),z:N(e.origin.z,t.origin.z,r)},velocity:{x:N(e.velocity.x,t.velocity.x,r),y:N(e.velocity.y,t.velocity.y,r),z:N(e.velocity.z,t.velocity.z,r)},viewangles:{x:le(e.viewangles.x,t.viewangles.x,r),y:le(e.viewangles.y,t.viewangles.y,r),z:le(e.viewangles.z,t.viewangles.z,r)},pmFlags:t.pmFlags,pmType:t.pmType,waterlevel:t.waterlevel,gravity:t.gravity,deltaAngles:t.deltaAngles}}function jt(e,t,n){let r=Math.min(Math.max(t.msec,0),$t)/1e3,s=O(e.pmFlags,S.OnGround),o=O(e.pmFlags,S.OnLadder),i=It({velocity:e.velocity,frametime:r,onGround:s,groundIsSlick:n.groundIsSlick,onLadder:o,waterlevel:e.waterlevel,pmFriction:n.pmFriction,pmStopSpeed:n.pmStopSpeed,pmWaterFriction:n.pmWaterFriction}),{viewangles:a,forward:u,right:l}=Ge({pmFlags:e.pmFlags,cmdAngles:t.angles,deltaAngles:e.deltaAngles??L}),c=e.waterlevel>B.None?Wt({forward:u,right:l,cmd:t,maxSpeed:n.pmWaterSpeed}):Gt({forward:u,right:l,cmd:t,maxSpeed:n.pmMaxSpeed});if(e.waterlevel>B.None)i=Oe({velocity:i,wishdir:c.wishdir,wishspeed:c.wishspeed,accel:n.pmWaterAccelerate,frametime:r});else if(s||o){let z=O(e.pmFlags,S.Ducked)?n.pmDuckSpeed:n.pmMaxSpeed,k=c.wishspeed>z?{wishdir:c.wishdir,wishspeed:z}:c;i=Oe({velocity:i,wishdir:k.wishdir,wishspeed:k.wishspeed,accel:n.pmAccelerate,frametime:r})}else i=Bt({velocity:i,wishdir:c.wishdir,wishspeed:c.wishspeed,accel:n.pmAirAccelerate,frametime:r}),i={...i,z:i.z-e.gravity*r};let m=D(i,r),p=I(e.origin,m);return{...e,origin:p,velocity:i,viewangles:a}}var qt=class{constructor(e={}){this.baseFrame={frame:0,timeMs:0,state:C()},this.commands=[],this.predicted=C(),this.settings={...Kt,...e},this.predicted=this.baseFrame.state??C()}setAuthoritative(e){let t=Me(e.state);return this.baseFrame={...e,state:t},this.commands=this.commands.filter(n=>(n.serverFrame??Number.MAX_SAFE_INTEGER)>e.frame),this.recompute()}enqueueCommand(e){return this.commands.push(e),this.recompute()}getPredictedState(){return this.predicted}recompute(){let e=Me(this.baseFrame.state);for(let t of this.commands)e=jt(e,t,this.settings);return this.predicted=e,e}},Jt={runPitch:.002,runRoll:.005,bobUp:.005,bobPitch:.002,bobRoll:.002,maxBobHeight:6,maxBobAngle:1.2};function Zt(e){return{x:Math.max(-14,Math.min(14,e.x)),y:Math.max(-14,Math.min(14,e.y)),z:Math.max(-22,Math.min(30,e.z))}}function Qt(e,t,n){return t?e>210?n/400:e>100?n/800:n/1600:0}function en(e,t,n,r,s){if(t<5)return{bobTime:0,bobCycle:0,bobCycleRun:0,bobFracSin:0};let o=Qt(t,r,s),i=e+o,u=O(n,S.Ducked)&&r?i*4:i;return{bobTime:i,bobCycle:Math.floor(u),bobCycleRun:Math.floor(i),bobFracSin:Math.abs(Math.sin(u*Math.PI))}}var tn=class{constructor(e={}){this.bobTime=0,this.bobCycle=0,this.bobCycleRun=0,this.bobFracSin=0,this.settings={...Jt,...e}}addKick(e){e.durationMs<=0||(this.kick={...e,remainingMs:e.durationMs})}get last(){return this.lastSample}sample(e,t){let{forward:n,right:r}=De(Ge({pmFlags:e.pmFlags,cmdAngles:e.viewangles,deltaAngles:e.deltaAngles??xt}).viewangles),s=Math.sqrt(e.velocity.x*e.velocity.x+e.velocity.y*e.velocity.y),o=O(e.pmFlags,S.OnGround),i=en(this.bobTime,s,e.pmFlags,o,t);this.bobTime=i.bobTime,this.bobCycle=i.bobCycle,this.bobCycleRun=i.bobCycleRun,this.bobFracSin=i.bobFracSin;let a=P(e.velocity,n)*this.settings.runPitch,u=P(e.velocity,r)*this.settings.runRoll,l=this.bobFracSin*this.settings.bobPitch*s,c=this.bobFracSin*this.settings.bobRoll*s;O(e.pmFlags,S.Ducked)&&o&&(l*=6,c*=6),a+=Math.min(l,this.settings.maxBobAngle),c=Math.min(c,this.settings.maxBobAngle),this.bobCycle&1&&(c=-c),u+=c;let m=Math.min(this.bobFracSin*s*this.settings.bobUp,this.settings.maxBobHeight),p=0,z=0;if(this.kick&&this.kick.remainingMs>0){let Re=Math.max(0,Math.min(1,this.kick.remainingMs/this.kick.durationMs));p+=Re*this.kick.pitch,z+=Re*this.kick.roll,this.kick.remainingMs=Math.max(0,this.kick.remainingMs-t),this.kick.remainingMs===0&&(this.kick=void 0)}let Ne={angles:{x:a+p,y:0,z:u+z},offset:Zt({x:0,y:0,z:m}),bobCycle:this.bobCycle,bobCycleRun:this.bobCycleRun,bobFracSin:this.bobFracSin,xyspeed:s};return this.lastSample=Ne,Ne}};function nn(e){return e.trim().toLowerCase()}var We=(e=>(e.Forward="+forward",e.Back="+back",e.MoveLeft="+moveleft",e.MoveRight="+moveright",e.MoveUp="+moveup",e.MoveDown="+movedown",e.Jump="+jump",e.Crouch="+crouch",e.Attack="+attack",e.Use="+use",e.Holster="+holster",e.TurnLeft="+left",e.TurnRight="+right",e.LookUp="+lookup",e.LookDown="+lookdown",e.SpeedModifier="+speed",e.Zoom="+zoom",e))(We||{}),gs={"+attack":R.Attack,"+use":R.Use,"+holster":R.Holster,"+jump":R.Jump,"+crouch":R.Crouch},xs=new Map(Object.values(We).map(e=>[nn(e),e]));function Ve(e){let t=new qt,n=new tn,r,s,o;return{init(i){r=i,i?.state&&t.setAuthoritative(i),e.engine.trace({x:0,y:0,z:0},{x:1,y:0,z:0})},predict(i){return t.enqueueCommand(i)},render(i){i.latest?.state&&(t.setAuthoritative(i.latest),r=i.latest),i.previous?.state&&i.latest?.state?s=Yt(i.previous.state,i.latest.state,i.alpha):s=i.latest?.state??i.previous?.state??t.getPredictedState();let a=i.latest&&i.previous?Math.max(0,i.latest.timeMs-i.previous.timeMs):0;o=n.sample(s,a)},shutdown(){r=void 0,s=void 0},get prediction(){return t},get lastRendered(){return s},get view(){return n},get lastView(){return o}}}var He=25,rn=5,sn=()=>typeof performance<"u"?performance.now():Date.now(),on=e=>{typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>e()):setTimeout(e,He)},Ke=class{constructor(e,t={}){this.callbacks=e,this.accumulatorMs=0,this.frame=0,this.running=!1,this.tick=()=>{if(!this.running)return;let s=this.options.now(),o=this.lastTimeMs===void 0?0:s-this.lastTimeMs;this.lastTimeMs=s,this.advance(o,s),this.running&&this.options.schedule(this.tick)};let n=t.fixedDeltaMs??He,r=t.maxSubSteps??rn;this.options={fixedDeltaMs:n,maxSubSteps:r,maxDeltaMs:t.maxDeltaMs??n*r,startTimeMs:t.startTimeMs,now:t.now??sn,schedule:t.schedule??on}}start(){this.running||(this.running=!0,this.lastTimeMs=this.options.startTimeMs??this.options.now(),this.options.schedule(this.tick))}stop(){this.running=!1}pump(e){let t=(this.lastTimeMs??0)+e;this.lastTimeMs=t,this.advance(e,t)}isRunning(){return this.running}get frameNumber(){return this.frame}advance(e,t){let n=Math.min(Math.max(e,0),this.options.maxDeltaMs);this.accumulatorMs=Math.min(this.accumulatorMs+n,this.options.fixedDeltaMs*this.options.maxSubSteps);let r=0;for(;this.accumulatorMs>=this.options.fixedDeltaMs&&r<this.options.maxSubSteps;)this.frame+=1,this.callbacks.simulate({frame:this.frame,deltaMs:this.options.fixedDeltaMs,nowMs:t}),this.accumulatorMs-=this.options.fixedDeltaMs,r+=1;let s=this.options.fixedDeltaMs===0?0:this.accumulatorMs/this.options.fixedDeltaMs;this.callbacks.render?.({alpha:s,nowMs:t,accumulatorMs:this.accumulatorMs,frame:this.frame})}},an=class{constructor(e,t,n={}){this.game=e,this.client=t,this.started=!1,this.stepSimulation=s=>{this.previousFrame=this.latestFrame,this.latestFrame=this.game.frame(s)},this.renderClient=s=>{this.client&&this.client.render({...s,previous:this.previousFrame,latest:this.latestFrame})};let r=n.loop?.now?.()??Date.now();this.startTimeMs=n.startTimeMs??n.loop?.startTimeMs??r,this.loop=new Ke({simulate:this.stepSimulation,render:this.renderClient},{...n.loop,startTimeMs:this.startTimeMs})}start(){this.started||(this.latestFrame=this.game.init(this.startTimeMs)??this.latestFrame,this.client?.init(this.latestFrame),this.started=!0,this.loop.start())}stop(){this.started&&(this.loop.stop(),this.client?.shutdown(),this.game.shutdown(),this.previousFrame=void 0,this.latestFrame=void 0,this.started=!1)}pump(e){this.loop.pump(e)}getLatestFrame(){return this.latestFrame}isRunning(){return this.loop.isRunning()}};var _s=Math.PI/180;var ws=Math.PI/180,As=180/Math.PI,v=1,_=2;var ye=8,ve=16,cn=32;var un=16384;var K=65536,Xe=1<<17,ln=1<<18,hn=1<<19,dn=1<<20,fn=1<<21,mn=1<<22,pn=1<<23,zs=1<<24,X=1<<25,yn=1<<26,Ns=1<<27,Rs=1<<28,Ls=1<<29,$=1<<30,Os=1<<31;var Ms=1<<25,bs=1<<28,ks=1<<29,Cs=1<<30,Ps=1<<31,Ds=v|_,Us=v|K|_|X|$,Fs=v|K|_,Is=v|Xe|_|X|$,Bs=cn|ye|ve,Gs=v|ve|ye,vn=v|X|$|_|yn,Ws=ln|hn|dn|fn|mn|pn,Vs=v|ye|ve|X|$,Hs=v|K|_,Ks=v|_,Xs=v|K|_|Xe,$s=vn|un,Ys=Number.MAX_SAFE_INTEGER-1;var $e=256,gn=256,xn=8192,En=2048,Sn=512,Tn=256,_n=$e*2,wn=256,fe=32;var Ye=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+xn]="Sounds",e[e.Images=e.Sounds+En]="Images",e[e.Lights=e.Images+Sn]="Lights",e[e.ShadowLights=e.Lights+gn]="ShadowLights",e[e.Items=e.ShadowLights+wn]="Items",e[e.PlayerSkins=e.Items+Tn]="PlayerSkins",e[e.General=e.PlayerSkins+$e]="General",e[e.WheelWeapons=e.General+_n]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+fe]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+fe]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+fe]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(Ye||{}),js=Ye.MaxConfigStrings;var An=class{constructor(e,t){this.engine=e,this.host=t,this.started=!1}start(){this.started||(this.engine.init(),this.host.start(),this.started=!0)}stop(){this.started&&(this.host.stop(),this.engine.shutdown(),this.started=!1)}pump(e){this.host.pump(e)}getLatestFrame(){return this.host.getLatestFrame()}isRunning(){return this.started&&this.host.isRunning()}};function je(e,t,n,r){return new An(e,new an(t,n,r))}function zn(){let e=new Uint32Array(256);for(let t=0;t<256;t+=1){let n=t;for(let r=0;r<8;r+=1)n=(n&1)!==0?3988292384^n>>>1:n>>>1;e[t]=n>>>0}return e}var qs=zn();var Js=Object.freeze([{name:"pak0.pak",checksum:2378051181,description:"Base game assets"},{name:"pak0.pak@baseq2",checksum:2378051181,description:"Base game assets (baseq2)"},{name:"pak0.pak@rogue",checksum:3373211245,description:"Ground Zero (rogue) mission pack"},{name:"pak0.pak@xatrix",checksum:1358269824,description:"The Reckoning (xatrix) mission pack"}]);var pe=4,me=7*pe,Zs=[{index:0,size:3,type:5126,stride:me,offset:0},{index:1,size:2,type:5126,stride:me,offset:3*pe},{index:2,size:2,type:5126,stride:me,offset:5*pe}];var Qs=new Float32Array([-1,-1,1,1,-1,1,1,1,1,-1,-1,1,1,1,1,-1,1,1,-1,-1,-1,-1,1,-1,1,1,-1,-1,-1,-1,1,1,-1,1,-1,-1,-1,-1,-1,-1,-1,1,-1,1,1,-1,-1,-1,-1,1,1,-1,1,-1,1,-1,-1,1,1,-1,1,1,1,1,-1,-1,1,1,1,1,-1,1,-1,1,-1,-1,1,1,1,1,1,-1,1,-1,1,1,1,1,1,-1,-1,-1,-1,1,-1,-1,1,-1,1,-1,-1,-1,1,-1,1,-1,-1,1]);function qe(e){return{init(){e.trace({x:0,y:0,z:0},{x:0,y:0,z:0})},shutdown(){},createMainLoop(t,n){return new Ke(t,n)}}}var Nn={x:0,y:0,z:0},si=Math.PI/180;var ii=Math.PI/180,oi=180/Math.PI;var g=624,Rn=397,Ln=2567483615,On=2147483648,Mn=2147483647,ge=4294967296,bn=class{constructor(e=5489){this.state=new Uint32Array(g),this.index=g,this.seed(e)}seed(e){this.state[0]=e>>>0;for(let t=1;t<g;t++){let n=this.state[t-1]^this.state[t-1]>>>30,r=Math.imul(n>>>0,1812433253)+t;this.state[t]=r>>>0}this.index=g}nextUint32(){this.index>=g&&this.twist();let e=this.state[this.index++];return e^=e>>>11,e^=e<<7&2636928640,e^=e<<15&4022730752,e^=e>>>18,e>>>0}twist(){for(let e=0;e<g;e++){let t=this.state[e]&On|this.state[(e+1)%g]&Mn,n=this.state[(e+Rn)%g]^t>>>1;(t&1)!==0&&(n^=Ln),this.state[e]=n>>>0}this.index=0}getState(){return{index:this.index,state:Array.from(this.state)}}setState(e){if(e.state.length!==g)throw new Error(`Expected ${g} MT state values, received ${e.state.length}`);this.index=e.index,this.state=Uint32Array.from(e.state,t=>t>>>0)}},et=class{constructor(e={}){this.mt=new bn(e.seed)}frandom(){return this.mt.nextUint32()/ge}frandomRange(e,t){return e+(t-e)*this.frandom()}frandomMax(e){return this.frandomRange(0,e)}crandom(){return this.frandomRange(-1,1)}crandomOpen(){let e=Number.EPSILON;return this.frandomRange(-1+e,1)}irandomUint32(){return this.mt.nextUint32()}irandomRange(e,t){if(t-e<=1)return e;let n=t-e,r=ge-ge%n,s;do s=this.mt.nextUint32();while(s>=r);return e+s%n}irandom(e){return e<=0?0:this.irandomRange(0,e)}randomTimeRange(e,t){return t<=e?e:this.irandomRange(e,t)}randomTime(e){return this.irandom(e)}randomIndex(e){return this.irandom(e.length)}getState(){return{mt:this.mt.getState()}}setState(e){this.mt.setState(e.mt)}};function kn(e){return new et(e)}var x=1,w=2;var Se=8,Te=16,Cn=32;var Pn=16384;var Q=65536,tt=1<<17,Dn=1<<18,Un=1<<19,Fn=1<<20,In=1<<21,Bn=1<<22,Gn=1<<23,ai=1<<24,ee=1<<25,Wn=1<<26,ci=1<<27,ui=1<<28,li=1<<29,te=1<<30,hi=1<<31;var di=1<<25,fi=1<<28,mi=1<<29,pi=1<<30,yi=1<<31,vi=x|w,gi=x|Q|w|ee|te,xi=x|Q|w,Ei=x|tt|w|ee|te,Si=Cn|Se|Te,Ti=x|Te|Se,Vn=x|ee|te|w|Wn,_i=Dn|Un|Fn|In|Bn|Gn,wi=x|Se|Te|ee|te,Ai=x|Q|w,zi=x|w,Ni=x|Q|w|tt,Ri=Vn|Pn,Li=Number.MAX_SAFE_INTEGER-1;var nt=256,Hn=256,Kn=8192,Xn=2048,$n=512,Yn=256,jn=nt*2,qn=256,xe=32,rt=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+Kn]="Sounds",e[e.Images=e.Sounds+Xn]="Images",e[e.Lights=e.Images+$n]="Lights",e[e.ShadowLights=e.Lights+Hn]="ShadowLights",e[e.Items=e.ShadowLights+qn]="Items",e[e.PlayerSkins=e.Items+Yn]="PlayerSkins",e[e.General=e.PlayerSkins+nt]="General",e[e.WheelWeapons=e.General+jn]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+xe]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+xe]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+xe]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(rt||{}),Oi=rt.MaxConfigStrings;var Jn={...Nn};function h(){return{...Jn}}var Je=Object.freeze({aiflags:0}),Zn=class{constructor(e){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.inventory={},this.origin=h(),this.old_origin=h(),this.velocity=h(),this.avelocity=h(),this.angles=h(),this.viewheight=0,this.mins=h(),this.maxs=h(),this.size=h(),this.mass=0,this.gravity=1,this.movetype=0,this.movedir=h(),this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.speed=0,this.deadflag=0,this.count=0,this.wait=0,this.delay=0,this.timestamp=0,this.sounds=0,this.noise_index=0,this.fly_sound_debounce_time=0,this.enemy=null,this.movetarget=null,this.target_ent=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.search_time=0,this.attack_finished_time=0,this.pain_finished_time=0,this.trail_time=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.activator=null,this.solid=0,this.flags=0,this.svflags=0,this.monsterinfo={...Je},this.index=e}reset(){this.inUse=!1,this.freePending=!1,this.linkPrevious=null,this.linkNext=null,this.classname="",this.spawnflags=0,this.target=void 0,this.targetname=void 0,this.killtarget=void 0,this.team=void 0,this.message=void 0,this.pathtarget=void 0,this.model=void 0,this.item=void 0,this.inventory={},this.origin=h(),this.old_origin=h(),this.velocity=h(),this.avelocity=h(),this.angles=h(),this.viewheight=0,this.mins=h(),this.maxs=h(),this.size=h(),this.mass=0,this.gravity=1,this.movetype=0,this.movedir=h(),this.modelindex=0,this.frame=0,this.skin=0,this.effects=0,this.renderfx=0,this.health=0,this.max_health=0,this.takedamage=!1,this.dmg=0,this.speed=0,this.deadflag=0,this.count=0,this.wait=0,this.delay=0,this.timestamp=0,this.sounds=0,this.noise_index=0,this.fly_sound_debounce_time=0,this.enemy=null,this.movetarget=null,this.target_ent=null,this.goalentity=null,this.ideal_yaw=0,this.yaw_speed=0,this.search_time=0,this.attack_finished_time=0,this.pain_finished_time=0,this.trail_time=0,this.groundentity=null,this.groundentity_linkcount=0,this.waterlevel=0,this.watertype=0,this.nextthink=0,this.think=void 0,this.touch=void 0,this.use=void 0,this.pain=void 0,this.die=void 0,this.activator=null,this.solid=0,this.flags=0,this.svflags=0,this.monsterinfo={...Je}}},st=[{name:"classname",type:"string",save:!0},{name:"spawnflags",type:"int",save:!0},{name:"target",type:"string",save:!0},{name:"targetname",type:"string",save:!0},{name:"killtarget",type:"string",save:!0},{name:"team",type:"string",save:!0},{name:"message",type:"string",save:!0},{name:"pathtarget",type:"string",save:!0},{name:"model",type:"string",save:!0},{name:"item",type:"string",save:!0},{name:"inventory",type:"inventory",save:!0},{name:"origin",type:"vec3",save:!0},{name:"old_origin",type:"vec3",save:!0},{name:"velocity",type:"vec3",save:!0},{name:"avelocity",type:"vec3",save:!0},{name:"angles",type:"vec3",save:!0},{name:"viewheight",type:"int",save:!0},{name:"mins",type:"vec3",save:!0},{name:"maxs",type:"vec3",save:!0},{name:"size",type:"vec3",save:!0},{name:"mass",type:"int",save:!0},{name:"gravity",type:"float",save:!0},{name:"movetype",type:"int",save:!0},{name:"movedir",type:"vec3",save:!0},{name:"modelindex",type:"int",save:!0},{name:"frame",type:"int",save:!0},{name:"skin",type:"int",save:!0},{name:"effects",type:"int",save:!0},{name:"renderfx",type:"int",save:!0},{name:"health",type:"int",save:!0},{name:"max_health",type:"int",save:!0},{name:"takedamage",type:"boolean",save:!0},{name:"dmg",type:"int",save:!0},{name:"speed",type:"float",save:!0},{name:"deadflag",type:"int",save:!0},{name:"count",type:"int",save:!0},{name:"wait",type:"float",save:!0},{name:"delay",type:"float",save:!0},{name:"timestamp",type:"float",save:!0},{name:"sounds",type:"int",save:!0},{name:"noise_index",type:"int",save:!0},{name:"fly_sound_debounce_time",type:"float",save:!0},{name:"enemy",type:"entity",save:!0},{name:"movetarget",type:"entity",save:!0},{name:"target_ent",type:"entity",save:!0},{name:"goalentity",type:"entity",save:!0},{name:"ideal_yaw",type:"float",save:!0},{name:"yaw_speed",type:"float",save:!0},{name:"search_time",type:"float",save:!0},{name:"attack_finished_time",type:"float",save:!0},{name:"pain_finished_time",type:"float",save:!0},{name:"trail_time",type:"float",save:!0},{name:"groundentity",type:"entity",save:!0},{name:"groundentity_linkcount",type:"int",save:!0},{name:"waterlevel",type:"int",save:!0},{name:"watertype",type:"int",save:!0},{name:"nextthink",type:"float",save:!0},{name:"solid",type:"int",save:!0},{name:"flags",type:"int",save:!0},{name:"svflags",type:"int",save:!0},{name:"think",type:"callback",save:!1},{name:"touch",type:"callback",save:!1},{name:"use",type:"callback",save:!1},{name:"pain",type:"callback",save:!1},{name:"die",type:"callback",save:!1}],Qn=2048,M=0,er=class{constructor(e=Qn){if(this.freeList=[],this.pendingFree=[],this.activeHead=null,e<1||!Number.isInteger(e))throw new Error("EntityPool requires a positive integer size");this.entities=new Array(e);for(let n=0;n<e;n+=1)this.entities[n]=new Zn(n),n!==M&&this.freeList.push(n);let t=this.entities[M];t.inUse=!0,t.classname="worldspawn",this.activeHead=t}get world(){return this.entities[M]}get capacity(){return this.entities.length}get activeCount(){let e=0;for(let t of this)e+=1;return e}[Symbol.iterator](){let e=this.activeHead;return{next:()=>{if(!e)return{done:!0,value:void 0};let t=e;return e=e.linkNext,{done:!1,value:t}}}}spawn(){let e=this.freeList.pop();if(e===void 0)throw new Error("No free entities available");let t=this.entities[e];return t.reset(),t.inUse=!0,this.link(t),t}deferFree(e){if(e.index===M)throw new Error("Cannot free world entity");!e.inUse||e.freePending||(this.unlink(e),e.inUse=!1,e.freePending=!0,this.pendingFree.push(e.index))}freeImmediate(e){if(e.index===M)throw new Error("Cannot free world entity");e.inUse&&(this.unlink(e),e.reset(),this.freeList.push(e.index))}flushFreeList(){if(this.pendingFree.length!==0){for(let e of this.pendingFree)this.entities[e].reset(),this.freeList.push(e);this.pendingFree.length=0}}createSnapshot(){let e=Array.from(this,t=>t.index);return{capacity:this.entities.length,activeOrder:e,freeList:[...this.freeList],pendingFree:[...this.pendingFree]}}restore(e){if(e.capacity!==this.entities.length)throw new Error(`Snapshot capacity ${e.capacity} does not match pool capacity ${this.entities.length}`);let t=new Set,n=(r,s)=>{if(r<0||r>=this.entities.length)throw new Error(`Invalid entity index ${r} in ${s}`);if(t.has(r))throw new Error(`Duplicate entity index ${r} in snapshot`);t.add(r)};for(let r of e.activeOrder)n(r,"activeOrder");for(let r of e.freeList)n(r,"freeList");for(let r of e.pendingFree)n(r,"pendingFree");this.activeHead=null,this.freeList.length=0,this.pendingFree.length=0;for(let r of this.entities)r.reset();for(let r=e.activeOrder.length-1;r>=0;r-=1){let s=this.entities[e.activeOrder[r]];s.inUse=!0,this.link(s)}for(let r of e.pendingFree){let s=this.entities[r];s.inUse=!1,s.freePending=!0,s.linkNext=null,s.linkPrevious=null,this.pendingFree.push(r)}for(let r of e.freeList){let s=this.entities[r];s.inUse=!1,s.freePending=!1,s.linkNext=null,s.linkPrevious=null,this.freeList.push(r)}if(!e.activeOrder.includes(M))throw new Error("Snapshot must include the world entity as active")}link(e){e.linkNext=this.activeHead,this.activeHead&&(this.activeHead.linkPrevious=e),this.activeHead=e,e.linkPrevious=null}unlink(e){e.linkPrevious&&(e.linkPrevious.linkNext=e.linkNext),e.linkNext&&(e.linkNext.linkPrevious=e.linkPrevious),this.activeHead===e&&(this.activeHead=e.linkNext),e.linkPrevious=null,e.linkNext=null}},tr=class{constructor(){this.queue=[]}schedule(e,t){e.nextthink=t,this.queue.push({entity:e,time:t}),this.queue.sort((n,r)=>n.time===r.time?n.entity.index-r.entity.index:n.time-r.time)}cancel(e){if(this.queue.length!==0)for(let t=this.queue.length-1;t>=0;t-=1)this.queue[t].entity===e&&this.queue.splice(t,1)}snapshot(){return this.queue.map(({time:e,entity:t})=>({time:e,entityIndex:t.index}))}restore(e,t){this.queue.length=0;for(let n of e){let r=t(n.entityIndex);r&&this.schedule(r,n.time)}}runDueThinks(e){for(;this.queue.length>0;){let t=this.queue[0];if(t.time>e)break;this.queue.shift();let{entity:n,time:r}=t;!n.inUse||n.freePending||n.think&&n.nextthink===r&&n.think(n)}}};function Y(e){return{min:{x:e.origin.x+e.mins.x,y:e.origin.y+e.mins.y,z:e.origin.z+e.mins.z},max:{x:e.origin.x+e.maxs.x,y:e.origin.y+e.maxs.y,z:e.origin.z+e.maxs.z}}}function Ze(e,t){return!(e.min.x>t.max.x||e.max.x<t.min.x||e.min.y>t.max.y||e.max.y<t.min.y||e.min.z>t.max.z||e.max.z<t.min.z)}var it=st.filter(e=>e.save),nr=new Map(it.map(e=>[e.name,e]));function rr(e){return[e.x,e.y,e.z]}function sr(e){let t=e;if(!Array.isArray(t)||t.length!==3)throw new Error("Invalid vec3 serialization");let[n,r,s]=t;return{x:n,y:r,z:s}}function U(e,t,n){e[t]=n}function ir(e){return{...e}}function or(e){if(e===null||typeof e!="object"||Array.isArray(e))throw new Error("Invalid inventory serialization");let t={};for(let[n,r]of Object.entries(e))t[n]=Number(r);return t}var ar=class{constructor(e){this.targetNameIndex=new Map,this.random=kn(),this.currentTimeSeconds=0,this.pool=new er(e),this.thinkScheduler=new tr}get world(){return this.pool.world}get activeCount(){return this.pool.activeCount}get timeSeconds(){return this.currentTimeSeconds}forEachEntity(e){for(let t of this.pool)e(t)}spawn(){return this.pool.spawn()}free(e){this.unregisterTarget(e),this.thinkScheduler.cancel(e),this.pool.deferFree(e)}freeImmediate(e){this.unregisterTarget(e),this.thinkScheduler.cancel(e),this.pool.freeImmediate(e)}scheduleThink(e,t){this.thinkScheduler.schedule(e,t)}beginFrame(e){this.currentTimeSeconds=e}finalizeSpawn(e){!e.inUse||e.freePending||this.registerTarget(e)}findByClassname(e){let t=[];for(let n of this.pool)n.classname===e&&n.inUse&&!n.freePending&&t.push(n);return t}findByTargetName(e){let t=this.targetNameIndex.get(e);return t?Array.from(t).filter(n=>n.inUse&&!n.freePending):[]}pickTarget(e){if(!e)return null;let t=this.findByTargetName(e);if(t.length===0)return null;let n=this.random.randomIndex(t);return t[n]??null}killBox(e){let t=Y(e);for(let n of this.pool)n===e||n===this.pool.world||!n.inUse||n.freePending||n.solid===0||n.svflags&2||Ze(t,Y(n))&&(n.health=0,n.deadflag=2,this.free(n))}useTargets(e,t=null){if(e.delay>0){let n=this.spawn();n.classname="DelayedUse",n.target=e.target,n.killtarget=e.killtarget,n.message=e.message,n.think=r=>{this.useTargetsImmediate(r,t??e),this.free(r)},this.scheduleThink(n,this.currentTimeSeconds+e.delay);return}this.useTargetsImmediate(e,t??e)}runFrame(){this.thinkScheduler.runDueThinks(this.currentTimeSeconds),this.runTouches(),this.pool.flushFreeList()}createSnapshot(){let e=[];for(let t of this.pool){let n={};for(let r of it){let s=t[r.name];switch(r.type){case"vec3":n[r.name]=rr(s);break;case"entity":n[r.name]=s?.index??null;break;case"inventory":n[r.name]=ir(s);break;default:n[r.name]=s??null;break}}e.push({index:t.index,fields:n})}return{timeSeconds:this.currentTimeSeconds,pool:this.pool.createSnapshot(),entities:e,thinks:this.thinkScheduler.snapshot()}}restore(e){this.currentTimeSeconds=e.timeSeconds,this.pool.restore(e.pool);let t=new Map;for(let r of this.pool)t.set(r.index,r);let n=[];for(let r of e.entities){let s=t.get(r.index);if(s)for(let[o,i]of Object.entries(r.fields)){let a=nr.get(o);if(!(!a||i===void 0))switch(a.type){case"vec3":U(s,o,sr(i));break;case"entity":n.push({entity:s,name:a.name,targetIndex:i});break;case"inventory":U(s,o,or(i));break;case"boolean":U(s,o,!!i);break;default:U(s,o,i);break}}}for(let r of n){let s=r.targetIndex===null?null:t.get(r.targetIndex)??null;U(r.entity,r.name,s)}this.thinkScheduler.restore(e.thinks,r=>t.get(r))}runTouches(){let e=this.pool.world,t=[];for(let n of this.pool)n!==e&&(!n.inUse||n.freePending||n.solid===0||t.push(n));for(let n=0;n<t.length;n+=1){let r=t[n],s=null;for(let o=n+1;o<t.length;o+=1){let i=t[o];if(!r.touch&&!i.touch)continue;s||(s=Y(r));let a=Y(i);Ze(s,a)&&(r.touch&&r.touch(r,i),i.touch&&i.touch(i,r))}}}registerTarget(e){if(!e.targetname)return;let t=this.targetNameIndex.get(e.targetname);t||(t=new Set,this.targetNameIndex.set(e.targetname,t)),t.add(e)}unregisterTarget(e){if(!e.targetname)return;let t=this.targetNameIndex.get(e.targetname);t&&(t.delete(e),t.size===0&&this.targetNameIndex.delete(e.targetname))}useTargetsImmediate(e,t){if(e.target)for(let n of this.findByTargetName(e.target))n!==e&&n.use?.(n,e,t);if(e.killtarget)for(let n of this.findByTargetName(e.killtarget))n!==e&&this.free(n)}};var Mi=1/40;var bi=new Map(st.map(e=>[e.name,e]));var cr=["prep","simulate","finish"],ur=class{constructor(e){if(this.timeMs=0,this.frame=0,this.stageHandlers={prep:[],simulate:[],finish:[]},this.stageCounts={prep:0,simulate:0,finish:0},this.stageCompactionNeeded={prep:!1,simulate:!1,finish:!1},e)for(let t of cr){let n=e[t];n&&this.addStage(t,n)}}addStage(e,t){let n=this.stageHandlers[e];return n.push(t),this.stageCounts[e]+=1,()=>{let r=n.indexOf(t);r>=0&&n[r]&&(n[r]=void 0,this.stageCounts[e]-=1,this.stageCompactionNeeded[e]=!0)}}reset(e){this.timeMs=e,this.frame=0}advance(e){let t=this.timeMs;this.timeMs=t+e.deltaMs,this.frame=e.frame;let n={...e,timeMs:this.timeMs,previousTimeMs:t,deltaSeconds:e.deltaMs/1e3};if(this.runStage("prep",n),this.stageCounts.simulate===0)throw new Error("GameFrameLoop requires at least one simulate stage");return this.runStage("simulate",n),this.runStage("finish",n),n}runStage(e,t){let n=this.stageHandlers[e];for(let r=0;r<n.length;r+=1){let s=n[r];s&&s(t)}this.stageCompactionNeeded[e]&&this.compactStageHandlers(e)}compactStageHandlers(e){let t=this.stageHandlers[e],n=0;for(let r=0;r<t.length;r+=1){let s=t[r];s&&(t[n]=s,n+=1)}t.length=n,this.stageCompactionNeeded[e]=!1}get time(){return this.timeMs}get frameNumber(){return this.frame}},lr={frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0},hr=class{constructor(){this.state=lr}start(e){let t=e/1e3;this.state={frameNumber:0,timeSeconds:t,previousTimeSeconds:t,deltaSeconds:0}}tick(e){return this.state={frameNumber:e.frame,timeSeconds:e.timeMs/1e3,previousTimeSeconds:e.previousTimeMs/1e3,deltaSeconds:e.deltaSeconds},this.state}get current(){return this.state}restore(e){this.state={...e}}};var ki=1<<24;var Ee=1,dr=1;function f(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be an object`);return e}function d(e,t){if(typeof e!="number"||!Number.isFinite(e))throw new Error(`${t} must be a finite number`);return e}function j(e,t,n){return e===void 0?n:d(e,t)}function Z(e,t){if(typeof e!="string")throw new Error(`${t} must be a string`);return e}function J(e,t){if(!Array.isArray(e))throw new Error(`${t} must be an array`);for(let n of e)d(n,`${t} element`);return e}function fr(e){if(e===void 0)return{frameNumber:0,timeSeconds:0,previousTimeSeconds:0,deltaSeconds:0};let t=f(e,"level");return{frameNumber:j(t.frameNumber,"level.frameNumber",0),timeSeconds:j(t.timeSeconds,"level.timeSeconds",0),previousTimeSeconds:j(t.previousTimeSeconds,"level.previousTimeSeconds",0),deltaSeconds:j(t.deltaSeconds,"level.deltaSeconds",0)}}function mr(e){if(e===void 0)return new et().getState();let t=f(e,"rng"),n=f(t.mt,"rng.mt"),r=J(n.state,"rng.mt.state");return{mt:{index:d(n.index,"rng.mt.index"),state:r}}}function pr(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("thinks must be an array");return e.map((t,n)=>{let r=f(t,`thinks[${n}]`);return{time:d(r.time,`thinks[${n}].time`),entityIndex:d(r.entityIndex,`thinks[${n}].entityIndex`)}})}function yr(e){if(e===void 0)return{};let t=f(e,"entity.fields"),n={};for(let[r,s]of Object.entries(t)){if(s===null){n[r]=null;continue}switch(typeof s){case"number":case"string":case"boolean":n[r]=s;break;default:{if(!Array.isArray(s)){let o=f(s,r),i={};for(let[a,u]of Object.entries(o))i[a]=d(u,`${r}.${a}`);n[r]=i;break}if(Array.isArray(s)&&s.length===3){let[o,i,a]=s;n[r]=[d(o,`${r}[0]`),d(i,`${r}[1]`),d(a,`${r}[2]`)];break}throw new Error(`Unsupported entity field value for ${r}`)}}}return n}function vr(e){if(!Array.isArray(e))throw new Error("entities must be an array");return e.map((t,n)=>{let r=f(t,`entities[${n}]`);return{index:d(r.index,`entities[${n}].index`),fields:yr(r.fields)}})}function gr(e){let t=f(e,"pool");return{capacity:d(t.capacity,"pool.capacity"),activeOrder:J(t.activeOrder,"pool.activeOrder"),freeList:J(t.freeList,"pool.freeList"),pendingFree:J(t.pendingFree,"pool.pendingFree")}}function xr(e){let t=f(e,"entities");return{timeSeconds:d(t.timeSeconds,"entities.timeSeconds"),pool:gr(t.pool),entities:vr(t.entities),thinks:pr(t.thinks)}}function Er(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("cvars must be an array");return e.map((t,n)=>{let r=f(t,`cvars[${n}]`);return{name:Z(r.name,`cvars[${n}].name`),value:Z(r.value,`cvars[${n}].value`),flags:d(r.flags,`cvars[${n}].flags`)}})}function Sr(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("configstrings must be an array");return e.map((t,n)=>Z(t,`configstrings[${n}]`))}function Tr(e){return e===void 0?{}:f(e,"gameState")}function ot(e,t={}){let{allowNewerVersion:n=!0}=t,r=typeof e=="string"?JSON.parse(e):e,s=f(r,"save"),o=s.version??Ee,i=d(o,"version");if(i<dr)throw new Error(`Unsupported save version ${i}`);if(i>Ee&&!n)throw new Error(`Save version ${i} is newer than supported ${Ee}`);return{version:i,timestamp:d(s.timestamp,"timestamp"),map:Z(s.map,"map"),difficulty:d(s.difficulty,"difficulty"),playtimeSeconds:d(s.playtimeSeconds,"playtimeSeconds"),gameState:Tr(s.gameState),level:fr(s.level),rng:mr(s.rng),entities:xr(s.entities),cvars:Er(s.cvars),configstrings:Sr(s.configstrings)}}var Qe=globalThis.TextEncoder;function b(e){return ot({...e},{allowNewerVersion:!0})}function _r(e){return Qe?new Qe().encode(JSON.stringify(e)).length:JSON.stringify(e).length}var wr=class{constructor(){this.records=new Map}async init(){return Promise.resolve()}async put(e){let t={id:e.id,metadata:{...e.metadata},save:b(e.save)};this.records.set(e.id,t)}async get(e){let t=this.records.get(e);return t?{id:t.id,metadata:{...t.metadata},save:b(t.save)}:null}async delete(e){return this.records.delete(e)}async list(){return Array.from(this.records.values()).map(e=>({id:e.id,metadata:{...e.metadata},save:b(e.save)}))}},Ar=class{constructor(e,t,n){this.indexedDB=e,this.dbName=t,this.storeName=n,this.db=null}async init(){this.db||(this.db=await new Promise((e,t)=>{let n=this.indexedDB.open(this.dbName,1);n.onupgradeneeded=()=>{n.result.createObjectStore(this.storeName,{keyPath:"id"})},n.onerror=()=>t(n.error??new Error("Failed to open IndexedDB")),n.onsuccess=()=>e(n.result)}))}async runTransaction(e,t){await this.init();let n=this.db;return new Promise((r,s)=>{let i=n.transaction(this.storeName,e).objectStore(this.storeName),a=t(i);a.onsuccess=()=>r(a.result),a.onerror=()=>s(a.error??new Error("IndexedDB request failed"))})}async put(e){await this.runTransaction("readwrite",t=>t.put(e))}async get(e){let t=await this.runTransaction("readonly",n=>n.get(e));return t?{id:t.id,metadata:{...t.metadata},save:b(t.save)}:null}async delete(e){return await this.get(e)?(await this.runTransaction("readwrite",n=>n.delete(e)),!0):!1}async list(){return(await this.runTransaction("readonly",t=>t.getAll())).map(t=>({id:t.id,metadata:{...t.metadata},save:b(t.save)}))}},_e=class F{constructor(t={}){let{dbName:n=F.DEFAULT_DB_NAME,storeName:r=F.DEFAULT_STORE}=t,s=t.indexedDB??globalThis.indexedDB;s?this.adapter=new Ar(s,n,r):this.adapter=new wr}async save(t,n,r={}){let s=b(n),o={id:t,name:r.name??t,map:s.map,difficulty:s.difficulty,playtimeSeconds:s.playtimeSeconds,timestamp:s.timestamp,version:s.version,bytes:_r(s)};return await this.adapter.init(),await this.adapter.put({id:t,metadata:o,save:s}),o}async load(t,n={}){await this.adapter.init();let r=await this.adapter.get(t);if(!r)throw new Error(`Save slot ${t} not found`);return ot(r.save,n)}async delete(t){return await this.adapter.init(),this.adapter.delete(t)}async list(){return await this.adapter.init(),(await this.adapter.list()).map(n=>({...n.metadata})).sort((n,r)=>r.timestamp-n.timestamp||n.id.localeCompare(r.id))}async quickSave(t){return this.save(F.QUICK_SLOT,t,{name:"Quick Save"})}async quickLoad(t={}){return this.load(F.QUICK_SLOT,t)}};_e.DEFAULT_DB_NAME="quake2ts-saves";_e.DEFAULT_STORE="saves";_e.QUICK_SLOT="quicksave";var at=(e=>(e[e.Bullets=0]="Bullets",e[e.Shells=1]="Shells",e[e.Rockets=2]="Rockets",e[e.Grenades=3]="Grenades",e[e.Cells=4]="Cells",e[e.Slugs=5]="Slugs",e))(at||{}),Ci=Object.keys(at).length/2;var q={x:0,y:0,z:0};function ct(e,t){let n=t.gravity,r=new hr,s=new ur,o=new ar;s.addStage("prep",c=>{r.tick(c),o.beginFrame(r.current.timeSeconds)}),s.addStage("simulate",({deltaSeconds:c})=>{a={x:a.x+n.x*c,y:a.y+n.y*c,z:a.z+n.z*c},i={x:i.x+a.x*c,y:i.y+a.y*c,z:i.z+a.z*c},o.runFrame()});let i={...q},a={...q},u=c=>({frame:c,timeMs:s.time,state:{gravity:{...n},origin:{...i},velocity:{...a},level:{...r.current},entities:{activeCount:o.activeCount,worldClassname:o.world.classname}}}),l=c=>{s.reset(c),r.start(c),i={...q},a={...q},o.beginFrame(c/1e3),o.runFrame()};return{init(c){return l(c),e.trace({x:0,y:0,z:0},n),u(0)},shutdown(){},spawnWorld(){},frame(c){let m=s.advance(c);return u(m.frame)},entities:o}}var ne={x:0,y:0,z:0};var Xi=Math.PI/180;var $i=Math.PI/180,Yi=180/Math.PI;var E=1,A=2;var Ae=8,ze=16,zr=32;var Nr=16384;var re=65536,ut=1<<17,Rr=1<<18,Lr=1<<19,Or=1<<20,Mr=1<<21,br=1<<22,kr=1<<23,ji=1<<24,se=1<<25,Cr=1<<26,qi=1<<27,Ji=1<<28,Zi=1<<29,ie=1<<30,Qi=1<<31;var eo=1<<25,to=1<<28,no=1<<29,ro=1<<30,so=1<<31;var io=E|A,oo=E|re|A|se|ie,ao=E|re|A,co=E|ut|A|se|ie,uo=zr|Ae|ze,lo=E|ze|Ae,Pr=E|se|ie|A|Cr,ho=Rr|Lr|Or|Mr|br|kr,fo=E|Ae|ze|se|ie,mo=E|re|A,po=E|A,yo=E|re|A|ut,vo=Pr|Nr;var go=Number.MAX_SAFE_INTEGER-1;var lt=256;var Dr=256,Ur=8192,Fr=2048,Ir=512,Br=256,Gr=lt*2,Wr=256,we=32;var ht=(e=>(e[e.Name=0]="Name",e[e.CdTrack=1]="CdTrack",e[e.Sky=2]="Sky",e[e.SkyAxis=3]="SkyAxis",e[e.SkyRotate=4]="SkyRotate",e[e.StatusBar=5]="StatusBar",e[e.AirAccel=59]="AirAccel",e[e.MaxClients=60]="MaxClients",e[e.MapChecksum=61]="MapChecksum",e[e.Models=62]="Models",e[e.Sounds=62+Ur]="Sounds",e[e.Images=e.Sounds+Fr]="Images",e[e.Lights=e.Images+Ir]="Lights",e[e.ShadowLights=e.Lights+Dr]="ShadowLights",e[e.Items=e.ShadowLights+Wr]="Items",e[e.PlayerSkins=e.Items+Br]="PlayerSkins",e[e.General=e.PlayerSkins+lt]="General",e[e.WheelWeapons=e.General+Gr]="WheelWeapons",e[e.WheelAmmo=e.WheelWeapons+we]="WheelAmmo",e[e.WheelPowerups=e.WheelAmmo+we]="WheelPowerups",e[e.CdLoopCount=e.WheelPowerups+we]="CdLoopCount",e[e.GameStyle=e.CdLoopCount+1]="GameStyle",e[e.MaxConfigStrings=e.GameStyle+1]="MaxConfigStrings",e))(ht||{}),xo=ht.MaxConfigStrings;function Vr(){let e=qe({trace(s,o){return{start:s,end:o,fraction:1}}}),t=ct({trace(s,o){return{start:s,end:o,fraction:1}}},{gravity:ne}),n=Ve({engine:{trace:()=>({start:ne,end:ne,fraction:1})}}),r=je(e,t,n);return r.start(),{engine:e,game:t,client:n,runtime:r}}return gt(Hr);})();
|
|
2
2
|
//# sourceMappingURL=index.global.js.map
|