quake2ts 0.0.512 → 0.0.513
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/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +17 -17
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +168 -38
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +168 -38
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/demo/handler.d.ts +0 -1
- package/packages/client/dist/types/demo/handler.d.ts.map +1 -1
- package/packages/engine/dist/browser/index.global.js +17 -17
- package/packages/engine/dist/browser/index.global.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +461 -9
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +456 -9
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/assets/manager.d.ts +3 -0
- package/packages/engine/dist/types/assets/manager.d.ts.map +1 -1
- package/packages/engine/dist/types/assets/pakWriter.d.ts +8 -0
- package/packages/engine/dist/types/assets/pakWriter.d.ts.map +1 -0
- package/packages/engine/dist/types/assets/resourceTracker.d.ts +33 -0
- package/packages/engine/dist/types/assets/resourceTracker.d.ts.map +1 -0
- package/packages/engine/dist/types/demo/clipper.d.ts +22 -0
- package/packages/engine/dist/types/demo/clipper.d.ts.map +1 -0
- package/packages/engine/dist/types/demo/delta.d.ts +3 -0
- package/packages/engine/dist/types/demo/delta.d.ts.map +1 -0
- package/packages/engine/dist/types/demo/playback.d.ts +40 -0
- package/packages/engine/dist/types/demo/playback.d.ts.map +1 -1
- package/packages/engine/dist/types/index.d.ts +4 -0
- package/packages/engine/dist/types/index.d.ts.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
CvarRegistry: () => CvarRegistry,
|
|
50
50
|
DemoAnalyzer: () => DemoAnalyzer,
|
|
51
51
|
DemoCameraMode: () => DemoCameraMode,
|
|
52
|
+
DemoClipper: () => DemoClipper,
|
|
52
53
|
DemoEventType: () => DemoEventType,
|
|
53
54
|
DemoPlaybackController: () => DemoPlaybackController,
|
|
54
55
|
DemoReader: () => DemoReader,
|
|
@@ -88,10 +89,13 @@ __export(index_exports, {
|
|
|
88
89
|
PakParseError: () => PakParseError,
|
|
89
90
|
PakValidationError: () => PakValidationError,
|
|
90
91
|
PakValidator: () => PakValidator,
|
|
92
|
+
PakWriter: () => PakWriter,
|
|
91
93
|
ParticleRenderer: () => ParticleRenderer,
|
|
92
94
|
ParticleSystem: () => ParticleSystem,
|
|
93
95
|
PlaybackState: () => PlaybackState,
|
|
94
96
|
RERELEASE_KNOWN_PAKS: () => RERELEASE_KNOWN_PAKS,
|
|
97
|
+
ResourceLoadTracker: () => ResourceLoadTracker,
|
|
98
|
+
ResourceType: () => ResourceType,
|
|
95
99
|
SKYBOX_FRAGMENT_SHADER: () => SKYBOX_FRAGMENT_SHADER,
|
|
96
100
|
SKYBOX_VERTEX_SHADER: () => SKYBOX_VERTEX_SHADER,
|
|
97
101
|
SOUND_FULLVOLUME: () => SOUND_FULLVOLUME,
|
|
@@ -141,6 +145,7 @@ __export(index_exports, {
|
|
|
141
145
|
VertexBuffer: () => VertexBuffer,
|
|
142
146
|
VirtualFileSystem: () => VirtualFileSystem,
|
|
143
147
|
advanceAnimation: () => advanceAnimation,
|
|
148
|
+
applyEntityDelta: () => applyEntityDelta,
|
|
144
149
|
applySurfaceState: () => applySurfaceState,
|
|
145
150
|
attenuationToDistanceMultiplier: () => attenuationToDistanceMultiplier,
|
|
146
151
|
boxIntersectsFrustum: () => boxIntersectsFrustum,
|
|
@@ -2012,7 +2017,7 @@ var PakArchive = class _PakArchive {
|
|
|
2012
2017
|
if (dirOffset < HEADER_SIZE) {
|
|
2013
2018
|
throw new PakParseError(`Invalid directory offset: ${dirOffset}`);
|
|
2014
2019
|
}
|
|
2015
|
-
if (dirLength
|
|
2020
|
+
if (dirLength < 0 || dirLength % DIRECTORY_ENTRY_SIZE !== 0) {
|
|
2016
2021
|
throw new PakParseError(`Invalid directory length: ${dirLength}`);
|
|
2017
2022
|
}
|
|
2018
2023
|
const dirEnd = dirOffset + dirLength;
|
|
@@ -2066,6 +2071,136 @@ function calculatePakChecksum(buffer) {
|
|
|
2066
2071
|
return crc32(new Uint8Array(buffer));
|
|
2067
2072
|
}
|
|
2068
2073
|
|
|
2074
|
+
// src/assets/pakWriter.ts
|
|
2075
|
+
var HEADER_SIZE2 = 12;
|
|
2076
|
+
var DIRECTORY_ENTRY_SIZE2 = 64;
|
|
2077
|
+
var PakWriter = class _PakWriter {
|
|
2078
|
+
constructor() {
|
|
2079
|
+
this.files = /* @__PURE__ */ new Map();
|
|
2080
|
+
}
|
|
2081
|
+
addFile(path, data) {
|
|
2082
|
+
const normalized = normalizePath(path);
|
|
2083
|
+
if (!normalized) {
|
|
2084
|
+
throw new Error(`Invalid path: ${path}`);
|
|
2085
|
+
}
|
|
2086
|
+
if (normalized.length > 56) {
|
|
2087
|
+
throw new Error(`Path too long: ${path} (max 56 chars)`);
|
|
2088
|
+
}
|
|
2089
|
+
this.files.set(normalized, data);
|
|
2090
|
+
}
|
|
2091
|
+
removeFile(path) {
|
|
2092
|
+
const normalized = normalizePath(path);
|
|
2093
|
+
return this.files.delete(normalized);
|
|
2094
|
+
}
|
|
2095
|
+
build() {
|
|
2096
|
+
let currentOffset = HEADER_SIZE2;
|
|
2097
|
+
const sortedPaths = Array.from(this.files.keys()).sort();
|
|
2098
|
+
const directorySize = sortedPaths.length * DIRECTORY_ENTRY_SIZE2;
|
|
2099
|
+
let fileDataSize = 0;
|
|
2100
|
+
for (const path of sortedPaths) {
|
|
2101
|
+
fileDataSize += this.files.get(path).byteLength;
|
|
2102
|
+
}
|
|
2103
|
+
const totalSize = HEADER_SIZE2 + fileDataSize + directorySize;
|
|
2104
|
+
const buffer = new Uint8Array(totalSize);
|
|
2105
|
+
const view = new DataView(buffer.buffer);
|
|
2106
|
+
view.setUint8(0, "P".charCodeAt(0));
|
|
2107
|
+
view.setUint8(1, "A".charCodeAt(0));
|
|
2108
|
+
view.setUint8(2, "C".charCodeAt(0));
|
|
2109
|
+
view.setUint8(3, "K".charCodeAt(0));
|
|
2110
|
+
const directoryOffset = HEADER_SIZE2 + fileDataSize;
|
|
2111
|
+
view.setInt32(4, directoryOffset, true);
|
|
2112
|
+
view.setInt32(8, directorySize, true);
|
|
2113
|
+
let fileOffset = HEADER_SIZE2;
|
|
2114
|
+
let dirEntryOffset = directoryOffset;
|
|
2115
|
+
for (const path of sortedPaths) {
|
|
2116
|
+
const data = this.files.get(path);
|
|
2117
|
+
buffer.set(data, fileOffset);
|
|
2118
|
+
for (let i = 0; i < 56; i++) {
|
|
2119
|
+
if (i < path.length) {
|
|
2120
|
+
view.setUint8(dirEntryOffset + i, path.charCodeAt(i));
|
|
2121
|
+
} else {
|
|
2122
|
+
view.setUint8(dirEntryOffset + i, 0);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
view.setInt32(dirEntryOffset + 56, fileOffset, true);
|
|
2126
|
+
view.setInt32(dirEntryOffset + 60, data.byteLength, true);
|
|
2127
|
+
fileOffset += data.byteLength;
|
|
2128
|
+
dirEntryOffset += DIRECTORY_ENTRY_SIZE2;
|
|
2129
|
+
}
|
|
2130
|
+
return buffer;
|
|
2131
|
+
}
|
|
2132
|
+
static buildFromEntries(entries) {
|
|
2133
|
+
const writer = new _PakWriter();
|
|
2134
|
+
for (const [path, data] of entries) {
|
|
2135
|
+
writer.addFile(path, data);
|
|
2136
|
+
}
|
|
2137
|
+
return writer.build();
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
|
|
2141
|
+
// src/assets/resourceTracker.ts
|
|
2142
|
+
var ResourceType = /* @__PURE__ */ ((ResourceType2) => {
|
|
2143
|
+
ResourceType2["Texture"] = "texture";
|
|
2144
|
+
ResourceType2["Sound"] = "sound";
|
|
2145
|
+
ResourceType2["Model"] = "model";
|
|
2146
|
+
ResourceType2["Map"] = "map";
|
|
2147
|
+
ResourceType2["Sprite"] = "sprite";
|
|
2148
|
+
ResourceType2["ConfigString"] = "configString";
|
|
2149
|
+
return ResourceType2;
|
|
2150
|
+
})(ResourceType || {});
|
|
2151
|
+
var ResourceLoadTracker = class {
|
|
2152
|
+
constructor() {
|
|
2153
|
+
this.tracking = false;
|
|
2154
|
+
this.entries = [];
|
|
2155
|
+
this.currentFrame = 0;
|
|
2156
|
+
this.currentTime = 0;
|
|
2157
|
+
}
|
|
2158
|
+
startTracking() {
|
|
2159
|
+
this.tracking = true;
|
|
2160
|
+
this.entries = [];
|
|
2161
|
+
}
|
|
2162
|
+
stopTracking() {
|
|
2163
|
+
this.tracking = false;
|
|
2164
|
+
const log = {
|
|
2165
|
+
byFrame: /* @__PURE__ */ new Map(),
|
|
2166
|
+
byTime: /* @__PURE__ */ new Map(),
|
|
2167
|
+
uniqueResources: /* @__PURE__ */ new Map()
|
|
2168
|
+
};
|
|
2169
|
+
for (const entry of this.entries) {
|
|
2170
|
+
if (!log.byFrame.has(entry.frame)) {
|
|
2171
|
+
log.byFrame.set(entry.frame, []);
|
|
2172
|
+
}
|
|
2173
|
+
log.byFrame.get(entry.frame).push(entry);
|
|
2174
|
+
if (!log.byTime.has(entry.timestamp)) {
|
|
2175
|
+
log.byTime.set(entry.timestamp, []);
|
|
2176
|
+
}
|
|
2177
|
+
log.byTime.get(entry.timestamp).push(entry);
|
|
2178
|
+
const key = `${entry.type}:${entry.path}`;
|
|
2179
|
+
if (!log.uniqueResources.has(key)) {
|
|
2180
|
+
log.uniqueResources.set(key, entry);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return log;
|
|
2184
|
+
}
|
|
2185
|
+
recordLoad(type, path, size, pakSource) {
|
|
2186
|
+
if (!this.tracking) return;
|
|
2187
|
+
this.entries.push({
|
|
2188
|
+
type,
|
|
2189
|
+
path,
|
|
2190
|
+
timestamp: this.currentTime,
|
|
2191
|
+
frame: this.currentFrame,
|
|
2192
|
+
size,
|
|
2193
|
+
pakSource
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
setCurrentFrame(frame) {
|
|
2197
|
+
this.currentFrame = frame;
|
|
2198
|
+
}
|
|
2199
|
+
setCurrentTime(time) {
|
|
2200
|
+
this.currentTime = time;
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2069
2204
|
// src/assets/vfs.ts
|
|
2070
2205
|
var VirtualFileSystem = class {
|
|
2071
2206
|
constructor(archives = []) {
|
|
@@ -2590,7 +2725,7 @@ function wireFileInput(input, handler) {
|
|
|
2590
2725
|
var BSP_MAGIC = "IBSP";
|
|
2591
2726
|
var BSP_VERSION = 38;
|
|
2592
2727
|
var HEADER_LUMPS = 19;
|
|
2593
|
-
var
|
|
2728
|
+
var HEADER_SIZE3 = 4 + 4 + HEADER_LUMPS * 8;
|
|
2594
2729
|
var BspParseError = class extends Error {
|
|
2595
2730
|
};
|
|
2596
2731
|
var BspLoader = class {
|
|
@@ -2605,7 +2740,7 @@ var BspLoader = class {
|
|
|
2605
2740
|
}
|
|
2606
2741
|
};
|
|
2607
2742
|
function parseBsp(buffer) {
|
|
2608
|
-
if (buffer.byteLength <
|
|
2743
|
+
if (buffer.byteLength < HEADER_SIZE3) {
|
|
2609
2744
|
throw new BspParseError("BSP too small to contain header");
|
|
2610
2745
|
}
|
|
2611
2746
|
const view = new DataView(buffer);
|
|
@@ -3058,7 +3193,7 @@ function createFaceLightmap(face, lightMaps, info) {
|
|
|
3058
3193
|
// src/assets/md2.ts
|
|
3059
3194
|
var MD2_MAGIC = 844121161;
|
|
3060
3195
|
var MD2_VERSION = 8;
|
|
3061
|
-
var
|
|
3196
|
+
var HEADER_SIZE4 = 17 * 4;
|
|
3062
3197
|
var MD2_NORMALS = [
|
|
3063
3198
|
{ x: -0.525731, y: 0, z: 0.850651 },
|
|
3064
3199
|
{ x: -0.442863, y: 0.238856, z: 0.864188 },
|
|
@@ -3256,12 +3391,12 @@ function readCString2(view, offset, maxLength) {
|
|
|
3256
3391
|
}
|
|
3257
3392
|
function validateSection(buffer, offset, length, label) {
|
|
3258
3393
|
if (length === 0) return;
|
|
3259
|
-
if (offset <
|
|
3394
|
+
if (offset < HEADER_SIZE4 || offset + length > buffer.byteLength) {
|
|
3260
3395
|
throw new Md2ParseError(`${label} section is out of bounds`);
|
|
3261
3396
|
}
|
|
3262
3397
|
}
|
|
3263
3398
|
function parseHeader(buffer) {
|
|
3264
|
-
if (buffer.byteLength <
|
|
3399
|
+
if (buffer.byteLength < HEADER_SIZE4) {
|
|
3265
3400
|
throw new Md2ParseError("MD2 buffer too small to contain header");
|
|
3266
3401
|
}
|
|
3267
3402
|
const view = new DataView(buffer);
|
|
@@ -3698,7 +3833,7 @@ var Md3Loader = class {
|
|
|
3698
3833
|
var IDSPRITEHEADER = 844317769;
|
|
3699
3834
|
var SPRITE_VERSION = 2;
|
|
3700
3835
|
var MAX_SKINNAME = 64;
|
|
3701
|
-
var
|
|
3836
|
+
var HEADER_SIZE5 = 12;
|
|
3702
3837
|
var SpriteParseError = class extends Error {
|
|
3703
3838
|
};
|
|
3704
3839
|
function readCString3(view, offset, maxLength) {
|
|
@@ -3711,7 +3846,7 @@ function readCString3(view, offset, maxLength) {
|
|
|
3711
3846
|
return String.fromCharCode(...chars);
|
|
3712
3847
|
}
|
|
3713
3848
|
function parseSprite(buffer) {
|
|
3714
|
-
if (buffer.byteLength <
|
|
3849
|
+
if (buffer.byteLength < HEADER_SIZE5) {
|
|
3715
3850
|
throw new SpriteParseError("Sprite buffer too small to contain header");
|
|
3716
3851
|
}
|
|
3717
3852
|
const view = new DataView(buffer);
|
|
@@ -3726,7 +3861,7 @@ function parseSprite(buffer) {
|
|
|
3726
3861
|
}
|
|
3727
3862
|
const frames = [];
|
|
3728
3863
|
const frameSize = 16 + MAX_SKINNAME;
|
|
3729
|
-
let offset =
|
|
3864
|
+
let offset = HEADER_SIZE5;
|
|
3730
3865
|
for (let i = 0; i < numFrames; i += 1) {
|
|
3731
3866
|
if (offset + frameSize > buffer.byteLength) {
|
|
3732
3867
|
throw new SpriteParseError("Sprite frame data exceeds buffer length");
|
|
@@ -4442,6 +4577,7 @@ var AssetManager = class {
|
|
|
4442
4577
|
this.textures = new TextureCache({ capacity: options.textureCacheCapacity ?? 128 });
|
|
4443
4578
|
this.audio = new AudioRegistry(vfs, { cacheSize: options.audioCacheSize ?? 64 });
|
|
4444
4579
|
this.dependencyTracker = options.dependencyTracker ?? new AssetDependencyTracker();
|
|
4580
|
+
this.resourceTracker = options.resourceTracker;
|
|
4445
4581
|
this.md2 = new Md2Loader(vfs);
|
|
4446
4582
|
this.md3 = new Md3Loader(vfs);
|
|
4447
4583
|
this.sprite = new SpriteLoader(vfs);
|
|
@@ -4478,6 +4614,9 @@ var AssetManager = class {
|
|
|
4478
4614
|
this.dependencyTracker.markLoaded(key);
|
|
4479
4615
|
}
|
|
4480
4616
|
async loadTexture(path) {
|
|
4617
|
+
if (this.resourceTracker) {
|
|
4618
|
+
this.resourceTracker.recordLoad("texture" /* Texture */, path);
|
|
4619
|
+
}
|
|
4481
4620
|
const cached = this.textures.get(path);
|
|
4482
4621
|
if (cached) return cached;
|
|
4483
4622
|
const buffer = await this.vfs.readFile(path);
|
|
@@ -4496,6 +4635,9 @@ var AssetManager = class {
|
|
|
4496
4635
|
return texture;
|
|
4497
4636
|
}
|
|
4498
4637
|
async loadSound(path) {
|
|
4638
|
+
if (this.resourceTracker) {
|
|
4639
|
+
this.resourceTracker.recordLoad("sound" /* Sound */, path);
|
|
4640
|
+
}
|
|
4499
4641
|
const audio = await this.audio.load(path);
|
|
4500
4642
|
const key = this.makeKey("sound", path);
|
|
4501
4643
|
this.dependencyTracker.register(key);
|
|
@@ -4503,6 +4645,9 @@ var AssetManager = class {
|
|
|
4503
4645
|
return audio;
|
|
4504
4646
|
}
|
|
4505
4647
|
async loadMd2Model(path, textureDependencies = []) {
|
|
4648
|
+
if (this.resourceTracker) {
|
|
4649
|
+
this.resourceTracker.recordLoad("model" /* Model */, path);
|
|
4650
|
+
}
|
|
4506
4651
|
const modelKey = this.makeKey("model", path);
|
|
4507
4652
|
const dependencyKeys = textureDependencies.map((dep) => this.makeKey("texture", dep));
|
|
4508
4653
|
this.dependencyTracker.register(modelKey, dependencyKeys);
|
|
@@ -4515,9 +4660,15 @@ var AssetManager = class {
|
|
|
4515
4660
|
return model;
|
|
4516
4661
|
}
|
|
4517
4662
|
getMd2Model(path) {
|
|
4663
|
+
if (this.resourceTracker) {
|
|
4664
|
+
this.resourceTracker.recordLoad("model" /* Model */, path);
|
|
4665
|
+
}
|
|
4518
4666
|
return this.md2.get(path);
|
|
4519
4667
|
}
|
|
4520
4668
|
async loadMd3Model(path, textureDependencies = []) {
|
|
4669
|
+
if (this.resourceTracker) {
|
|
4670
|
+
this.resourceTracker.recordLoad("model" /* Model */, path);
|
|
4671
|
+
}
|
|
4521
4672
|
const modelKey = this.makeKey("model", path);
|
|
4522
4673
|
const dependencyKeys = textureDependencies.map((dep) => this.makeKey("texture", dep));
|
|
4523
4674
|
this.dependencyTracker.register(modelKey, dependencyKeys);
|
|
@@ -4530,9 +4681,15 @@ var AssetManager = class {
|
|
|
4530
4681
|
return model;
|
|
4531
4682
|
}
|
|
4532
4683
|
getMd3Model(path) {
|
|
4684
|
+
if (this.resourceTracker) {
|
|
4685
|
+
this.resourceTracker.recordLoad("model" /* Model */, path);
|
|
4686
|
+
}
|
|
4533
4687
|
return this.md3.get(path);
|
|
4534
4688
|
}
|
|
4535
4689
|
async loadSprite(path) {
|
|
4690
|
+
if (this.resourceTracker) {
|
|
4691
|
+
this.resourceTracker.recordLoad("sprite" /* Sprite */, path);
|
|
4692
|
+
}
|
|
4536
4693
|
const spriteKey = this.makeKey("sprite", path);
|
|
4537
4694
|
this.dependencyTracker.register(spriteKey);
|
|
4538
4695
|
const sprite = await this.sprite.load(path);
|
|
@@ -4540,6 +4697,9 @@ var AssetManager = class {
|
|
|
4540
4697
|
return sprite;
|
|
4541
4698
|
}
|
|
4542
4699
|
async loadMap(path) {
|
|
4700
|
+
if (this.resourceTracker) {
|
|
4701
|
+
this.resourceTracker.recordLoad("map" /* Map */, path);
|
|
4702
|
+
}
|
|
4543
4703
|
const mapKey = this.makeKey("map", path);
|
|
4544
4704
|
if (this.maps.has(path)) {
|
|
4545
4705
|
return this.maps.get(path);
|
|
@@ -13607,6 +13767,8 @@ var DemoPlaybackController = class {
|
|
|
13607
13767
|
this.cachedStatistics = null;
|
|
13608
13768
|
this.cachedPlayerStats = null;
|
|
13609
13769
|
this.cachedWeaponStats = null;
|
|
13770
|
+
// Resource Tracking
|
|
13771
|
+
this.tracker = null;
|
|
13610
13772
|
// Camera State
|
|
13611
13773
|
this.cameraMode = 0 /* FirstPerson */;
|
|
13612
13774
|
this.thirdPersonDistance = 80;
|
|
@@ -13720,6 +13882,41 @@ var DemoPlaybackController = class {
|
|
|
13720
13882
|
seekToFrame(frameIndex) {
|
|
13721
13883
|
this.seek(frameIndex);
|
|
13722
13884
|
}
|
|
13885
|
+
frameToTime(frame) {
|
|
13886
|
+
return frame * this.frameDuration / 1e3;
|
|
13887
|
+
}
|
|
13888
|
+
timeToFrame(seconds) {
|
|
13889
|
+
return Math.floor(seconds * 1e3 / this.frameDuration);
|
|
13890
|
+
}
|
|
13891
|
+
playFrom(offset) {
|
|
13892
|
+
if (offset.type === "frame") {
|
|
13893
|
+
this.seek(offset.frame);
|
|
13894
|
+
} else {
|
|
13895
|
+
this.seekToTime(offset.seconds);
|
|
13896
|
+
}
|
|
13897
|
+
this.play();
|
|
13898
|
+
}
|
|
13899
|
+
playRange(start, end) {
|
|
13900
|
+
this.playFrom(start);
|
|
13901
|
+
const endFrame = end.type === "frame" ? end.frame : this.timeToFrame(end.seconds);
|
|
13902
|
+
const originalOnFrameUpdate = this.callbacks?.onFrameUpdate;
|
|
13903
|
+
const rangeCallback = {
|
|
13904
|
+
...this.callbacks,
|
|
13905
|
+
onFrameUpdate: (frame) => {
|
|
13906
|
+
if (originalOnFrameUpdate) originalOnFrameUpdate(frame);
|
|
13907
|
+
if (this.currentFrameIndex >= endFrame) {
|
|
13908
|
+
this.pause();
|
|
13909
|
+
if (this.callbacks === rangeCallback) {
|
|
13910
|
+
this.setCallbacks({ ...this.callbacks, onFrameUpdate: originalOnFrameUpdate });
|
|
13911
|
+
}
|
|
13912
|
+
if (this.callbacks?.onPlaybackComplete) {
|
|
13913
|
+
this.callbacks.onPlaybackComplete();
|
|
13914
|
+
}
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
};
|
|
13918
|
+
this.setCallbacks(rangeCallback);
|
|
13919
|
+
}
|
|
13723
13920
|
/**
|
|
13724
13921
|
* Seeks to a specific frame number.
|
|
13725
13922
|
*/
|
|
@@ -13797,6 +13994,10 @@ var DemoPlaybackController = class {
|
|
|
13797
13994
|
return false;
|
|
13798
13995
|
}
|
|
13799
13996
|
this.currentFrameIndex++;
|
|
13997
|
+
if (this.tracker) {
|
|
13998
|
+
this.tracker.setCurrentFrame(this.currentFrameIndex);
|
|
13999
|
+
this.tracker.setCurrentTime(this.getCurrentTime());
|
|
14000
|
+
}
|
|
13800
14001
|
try {
|
|
13801
14002
|
const proxyHandler = {
|
|
13802
14003
|
...this.handler,
|
|
@@ -14031,6 +14232,93 @@ var DemoPlaybackController = class {
|
|
|
14031
14232
|
setThirdPersonOffset(offset) {
|
|
14032
14233
|
this.thirdPersonOffset = offset;
|
|
14033
14234
|
}
|
|
14235
|
+
async playWithTracking(tracker, options = {}) {
|
|
14236
|
+
this.tracker = tracker;
|
|
14237
|
+
tracker.startTracking();
|
|
14238
|
+
if (options.fastForward) {
|
|
14239
|
+
try {
|
|
14240
|
+
if (this.state === 0 /* Stopped */ && this.reader) {
|
|
14241
|
+
}
|
|
14242
|
+
this.transitionState(1 /* Playing */);
|
|
14243
|
+
const CHUNK_SIZE = 100;
|
|
14244
|
+
const processChunk = async () => {
|
|
14245
|
+
if (this.state !== 1 /* Playing */) {
|
|
14246
|
+
throw new Error("Playback stopped unexpectedly during fast forward");
|
|
14247
|
+
}
|
|
14248
|
+
let count = 0;
|
|
14249
|
+
while (count < CHUNK_SIZE) {
|
|
14250
|
+
if (!this.processNextFrame()) {
|
|
14251
|
+
const log = tracker.stopTracking();
|
|
14252
|
+
this.tracker = null;
|
|
14253
|
+
if (this.callbacks?.onPlaybackComplete) this.callbacks.onPlaybackComplete();
|
|
14254
|
+
return log;
|
|
14255
|
+
}
|
|
14256
|
+
count++;
|
|
14257
|
+
}
|
|
14258
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
14259
|
+
return processChunk();
|
|
14260
|
+
};
|
|
14261
|
+
return await processChunk();
|
|
14262
|
+
} catch (e) {
|
|
14263
|
+
tracker.stopTracking();
|
|
14264
|
+
this.tracker = null;
|
|
14265
|
+
throw e;
|
|
14266
|
+
}
|
|
14267
|
+
} else {
|
|
14268
|
+
return new Promise((resolve, reject) => {
|
|
14269
|
+
const originalComplete = this.callbacks?.onPlaybackComplete;
|
|
14270
|
+
const originalError = this.callbacks?.onPlaybackError;
|
|
14271
|
+
const cleanup = () => {
|
|
14272
|
+
this.setCallbacks({ ...this.callbacks, onPlaybackComplete: originalComplete, onPlaybackError: originalError });
|
|
14273
|
+
this.tracker = null;
|
|
14274
|
+
};
|
|
14275
|
+
this.setCallbacks({
|
|
14276
|
+
...this.callbacks,
|
|
14277
|
+
onPlaybackComplete: () => {
|
|
14278
|
+
const log = tracker.stopTracking();
|
|
14279
|
+
if (originalComplete) originalComplete();
|
|
14280
|
+
cleanup();
|
|
14281
|
+
resolve(log);
|
|
14282
|
+
},
|
|
14283
|
+
onPlaybackError: (err2) => {
|
|
14284
|
+
tracker.stopTracking();
|
|
14285
|
+
if (originalError) originalError(err2);
|
|
14286
|
+
cleanup();
|
|
14287
|
+
reject(err2);
|
|
14288
|
+
}
|
|
14289
|
+
});
|
|
14290
|
+
this.play();
|
|
14291
|
+
});
|
|
14292
|
+
}
|
|
14293
|
+
}
|
|
14294
|
+
async playRangeWithTracking(start, end, tracker) {
|
|
14295
|
+
this.tracker = tracker;
|
|
14296
|
+
tracker.startTracking();
|
|
14297
|
+
return new Promise((resolve, reject) => {
|
|
14298
|
+
const originalComplete = this.callbacks?.onPlaybackComplete;
|
|
14299
|
+
const originalError = this.callbacks?.onPlaybackError;
|
|
14300
|
+
const cleanup = () => {
|
|
14301
|
+
this.setCallbacks({ ...this.callbacks, onPlaybackComplete: originalComplete, onPlaybackError: originalError });
|
|
14302
|
+
this.tracker = null;
|
|
14303
|
+
};
|
|
14304
|
+
this.setCallbacks({
|
|
14305
|
+
...this.callbacks,
|
|
14306
|
+
onPlaybackComplete: () => {
|
|
14307
|
+
const log = tracker.stopTracking();
|
|
14308
|
+
if (originalComplete) originalComplete();
|
|
14309
|
+
cleanup();
|
|
14310
|
+
resolve(log);
|
|
14311
|
+
},
|
|
14312
|
+
onPlaybackError: (err2) => {
|
|
14313
|
+
tracker.stopTracking();
|
|
14314
|
+
if (originalError) originalError(err2);
|
|
14315
|
+
cleanup();
|
|
14316
|
+
reject(err2);
|
|
14317
|
+
}
|
|
14318
|
+
});
|
|
14319
|
+
this.playRange(start, end);
|
|
14320
|
+
});
|
|
14321
|
+
}
|
|
14034
14322
|
};
|
|
14035
14323
|
|
|
14036
14324
|
// src/demo/recorder.ts
|
|
@@ -14106,6 +14394,165 @@ var DemoValidator = class {
|
|
|
14106
14394
|
}
|
|
14107
14395
|
};
|
|
14108
14396
|
|
|
14397
|
+
// src/demo/delta.ts
|
|
14398
|
+
function applyEntityDelta(to, from) {
|
|
14399
|
+
const bits = from.bits;
|
|
14400
|
+
const bitsHigh = from.bitsHigh;
|
|
14401
|
+
to.number = from.number;
|
|
14402
|
+
if (bits & U_MODEL5) to.modelindex = from.modelindex;
|
|
14403
|
+
if (bits & U_MODEL22) to.modelindex2 = from.modelindex2;
|
|
14404
|
+
if (bits & U_MODEL32) to.modelindex3 = from.modelindex3;
|
|
14405
|
+
if (bits & U_MODEL42) to.modelindex4 = from.modelindex4;
|
|
14406
|
+
if (bits & U_FRAME8) to.frame = from.frame;
|
|
14407
|
+
if (bits & U_FRAME16) to.frame = from.frame;
|
|
14408
|
+
if (bits & U_SKIN8 || bits & U_SKIN16) to.skinnum = from.skinnum;
|
|
14409
|
+
if (bits & U_EFFECTS8 || bits & U_EFFECTS16) to.effects = from.effects;
|
|
14410
|
+
if (bits & U_RENDERFX8 || bits & U_RENDERFX16) to.renderfx = from.renderfx;
|
|
14411
|
+
if (bits & U_ORIGIN12) to.origin.x = from.origin.x;
|
|
14412
|
+
if (bits & U_ORIGIN22) to.origin.y = from.origin.y;
|
|
14413
|
+
if (bits & U_ORIGIN32) to.origin.z = from.origin.z;
|
|
14414
|
+
if (bits & U_ANGLE12) to.angles.x = from.angles.x;
|
|
14415
|
+
if (bits & U_ANGLE22) to.angles.y = from.angles.y;
|
|
14416
|
+
if (bits & U_ANGLE32) to.angles.z = from.angles.z;
|
|
14417
|
+
if (bits & U_OLDORIGIN) {
|
|
14418
|
+
to.old_origin.x = from.old_origin.x;
|
|
14419
|
+
to.old_origin.y = from.old_origin.y;
|
|
14420
|
+
to.old_origin.z = from.old_origin.z;
|
|
14421
|
+
}
|
|
14422
|
+
if (bits & U_SOUND2) to.sound = from.sound;
|
|
14423
|
+
if (bits & U_EVENT2) to.event = from.event;
|
|
14424
|
+
if (bits & U_SOLID2) to.solid = from.solid;
|
|
14425
|
+
if (bits & U_ALPHA) to.alpha = from.alpha;
|
|
14426
|
+
if (bits & U_SCALE) to.scale = from.scale;
|
|
14427
|
+
if (bits & U_INSTANCE_BITS) to.instanceBits = from.instanceBits;
|
|
14428
|
+
if (bits & U_LOOP_VOLUME) to.loopVolume = from.loopVolume;
|
|
14429
|
+
if (bitsHigh & U_LOOP_ATTENUATION_HIGH) to.loopAttenuation = from.loopAttenuation;
|
|
14430
|
+
if (bitsHigh & U_OWNER_HIGH) to.owner = from.owner;
|
|
14431
|
+
if (bitsHigh & U_OLD_FRAME_HIGH) to.oldFrame = from.oldFrame;
|
|
14432
|
+
}
|
|
14433
|
+
|
|
14434
|
+
// src/demo/clipper.ts
|
|
14435
|
+
var DemoClipper = class {
|
|
14436
|
+
static extractClip(demoData, startFrame, endFrame) {
|
|
14437
|
+
const reader = new DemoReader(demoData);
|
|
14438
|
+
if (!reader.seekToMessage(startFrame)) {
|
|
14439
|
+
throw new Error(`Start frame ${startFrame} out of bounds`);
|
|
14440
|
+
}
|
|
14441
|
+
const startOffset = reader.getOffset();
|
|
14442
|
+
let endOffset = demoData.byteLength;
|
|
14443
|
+
if (endFrame < reader.getMessageCount()) {
|
|
14444
|
+
if (reader.seekToMessage(endFrame)) {
|
|
14445
|
+
endOffset = reader.getOffset();
|
|
14446
|
+
}
|
|
14447
|
+
}
|
|
14448
|
+
const length = endOffset - startOffset;
|
|
14449
|
+
const result = new Uint8Array(length + 4);
|
|
14450
|
+
const sourceView = new Uint8Array(demoData);
|
|
14451
|
+
result.set(sourceView.subarray(startOffset, endOffset), 0);
|
|
14452
|
+
const view = new DataView(result.buffer);
|
|
14453
|
+
view.setInt32(length, -1, true);
|
|
14454
|
+
return result;
|
|
14455
|
+
}
|
|
14456
|
+
static async captureWorldState(demoData, atFrame) {
|
|
14457
|
+
const reader = new DemoReader(demoData);
|
|
14458
|
+
const configStrings = /* @__PURE__ */ new Map();
|
|
14459
|
+
const entityBaselines = /* @__PURE__ */ new Map();
|
|
14460
|
+
let serverData = null;
|
|
14461
|
+
let playerState = createEmptyProtocolPlayerState();
|
|
14462
|
+
const entities = /* @__PURE__ */ new Map();
|
|
14463
|
+
let currentFrame = -1;
|
|
14464
|
+
const handler = {
|
|
14465
|
+
onServerData: (protocol, serverCount, attractLoop, gameDir, playerNum, levelName, tickRate, demoType) => {
|
|
14466
|
+
serverData = {
|
|
14467
|
+
protocolVersion: protocol,
|
|
14468
|
+
serverCount,
|
|
14469
|
+
attractLoop: attractLoop !== 0,
|
|
14470
|
+
gameDirectory: gameDir,
|
|
14471
|
+
levelName
|
|
14472
|
+
};
|
|
14473
|
+
},
|
|
14474
|
+
onConfigString: (index, str) => {
|
|
14475
|
+
configStrings.set(index, str);
|
|
14476
|
+
},
|
|
14477
|
+
onSpawnBaseline: (entity) => {
|
|
14478
|
+
entityBaselines.set(entity.number, entity);
|
|
14479
|
+
},
|
|
14480
|
+
onFrame: (frameData) => {
|
|
14481
|
+
playerState = frameData.playerState;
|
|
14482
|
+
const packetEntities = frameData.packetEntities;
|
|
14483
|
+
if (!packetEntities.delta) {
|
|
14484
|
+
entities.clear();
|
|
14485
|
+
}
|
|
14486
|
+
for (const partial of packetEntities.entities) {
|
|
14487
|
+
if (partial.bits & U_REMOVE2) {
|
|
14488
|
+
entities.delete(partial.number);
|
|
14489
|
+
continue;
|
|
14490
|
+
}
|
|
14491
|
+
const number = partial.number;
|
|
14492
|
+
let source;
|
|
14493
|
+
if (packetEntities.delta && entities.has(number)) {
|
|
14494
|
+
source = entities.get(number);
|
|
14495
|
+
} else if (entityBaselines.has(number)) {
|
|
14496
|
+
source = entityBaselines.get(number);
|
|
14497
|
+
} else {
|
|
14498
|
+
source = createEmptyEntityState();
|
|
14499
|
+
}
|
|
14500
|
+
const final = structuredClone(source);
|
|
14501
|
+
applyEntityDelta(final, partial);
|
|
14502
|
+
entities.set(number, final);
|
|
14503
|
+
}
|
|
14504
|
+
},
|
|
14505
|
+
onCenterPrint: () => {
|
|
14506
|
+
},
|
|
14507
|
+
onStuffText: () => {
|
|
14508
|
+
},
|
|
14509
|
+
onSound: () => {
|
|
14510
|
+
},
|
|
14511
|
+
onPrint: () => {
|
|
14512
|
+
},
|
|
14513
|
+
onMuzzleFlash: () => {
|
|
14514
|
+
},
|
|
14515
|
+
onMuzzleFlash2: () => {
|
|
14516
|
+
},
|
|
14517
|
+
onTempEntity: () => {
|
|
14518
|
+
},
|
|
14519
|
+
onLayout: () => {
|
|
14520
|
+
},
|
|
14521
|
+
onInventory: () => {
|
|
14522
|
+
},
|
|
14523
|
+
onDisconnect: () => {
|
|
14524
|
+
},
|
|
14525
|
+
onReconnect: () => {
|
|
14526
|
+
},
|
|
14527
|
+
onDownload: () => {
|
|
14528
|
+
}
|
|
14529
|
+
};
|
|
14530
|
+
while (reader.hasMore()) {
|
|
14531
|
+
if (currentFrame >= atFrame) {
|
|
14532
|
+
break;
|
|
14533
|
+
}
|
|
14534
|
+
const block = reader.readNextBlock();
|
|
14535
|
+
if (!block) break;
|
|
14536
|
+
currentFrame++;
|
|
14537
|
+
const parser = new NetworkMessageParser(block.data, handler);
|
|
14538
|
+
const currentProtocol = serverData ? serverData.protocolVersion : 34;
|
|
14539
|
+
parser.setProtocolVersion(currentProtocol);
|
|
14540
|
+
parser.parseMessage();
|
|
14541
|
+
}
|
|
14542
|
+
return {
|
|
14543
|
+
serverData,
|
|
14544
|
+
configStrings,
|
|
14545
|
+
entityBaselines,
|
|
14546
|
+
playerState,
|
|
14547
|
+
entities,
|
|
14548
|
+
gameTime: 0
|
|
14549
|
+
};
|
|
14550
|
+
}
|
|
14551
|
+
static extractStandaloneClip(demoData, startFrame, endFrame, worldState) {
|
|
14552
|
+
throw new Error("extractStandaloneClip not fully implemented: requires NetworkMessageWriter");
|
|
14553
|
+
}
|
|
14554
|
+
};
|
|
14555
|
+
|
|
14109
14556
|
// src/assets/fileType.ts
|
|
14110
14557
|
var FileType = /* @__PURE__ */ ((FileType2) => {
|
|
14111
14558
|
FileType2["Unknown"] = "unknown";
|
|
@@ -14484,6 +14931,7 @@ function createEngine(imports) {
|
|
|
14484
14931
|
CvarRegistry,
|
|
14485
14932
|
DemoAnalyzer,
|
|
14486
14933
|
DemoCameraMode,
|
|
14934
|
+
DemoClipper,
|
|
14487
14935
|
DemoEventType,
|
|
14488
14936
|
DemoPlaybackController,
|
|
14489
14937
|
DemoReader,
|
|
@@ -14523,10 +14971,13 @@ function createEngine(imports) {
|
|
|
14523
14971
|
PakParseError,
|
|
14524
14972
|
PakValidationError,
|
|
14525
14973
|
PakValidator,
|
|
14974
|
+
PakWriter,
|
|
14526
14975
|
ParticleRenderer,
|
|
14527
14976
|
ParticleSystem,
|
|
14528
14977
|
PlaybackState,
|
|
14529
14978
|
RERELEASE_KNOWN_PAKS,
|
|
14979
|
+
ResourceLoadTracker,
|
|
14980
|
+
ResourceType,
|
|
14530
14981
|
SKYBOX_FRAGMENT_SHADER,
|
|
14531
14982
|
SKYBOX_VERTEX_SHADER,
|
|
14532
14983
|
SOUND_FULLVOLUME,
|
|
@@ -14576,6 +15027,7 @@ function createEngine(imports) {
|
|
|
14576
15027
|
VertexBuffer,
|
|
14577
15028
|
VirtualFileSystem,
|
|
14578
15029
|
advanceAnimation,
|
|
15030
|
+
applyEntityDelta,
|
|
14579
15031
|
applySurfaceState,
|
|
14580
15032
|
attenuationToDistanceMultiplier,
|
|
14581
15033
|
boxIntersectsFrustum,
|