quake2ts 0.0.181 → 0.0.183

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js.map +1 -1
  3. package/packages/client/dist/cjs/index.cjs.map +1 -1
  4. package/packages/client/dist/esm/index.js.map +1 -1
  5. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  6. package/packages/engine/dist/browser/index.global.js +1 -1
  7. package/packages/engine/dist/browser/index.global.js.map +1 -1
  8. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  9. package/packages/engine/dist/esm/index.js.map +1 -1
  10. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  11. package/packages/game/dist/browser/index.global.js +2 -2
  12. package/packages/game/dist/browser/index.global.js.map +1 -1
  13. package/packages/game/dist/cjs/index.cjs.map +1 -1
  14. package/packages/game/dist/esm/index.js.map +1 -1
  15. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  16. package/packages/server/dist/index.cjs +281 -57
  17. package/packages/server/dist/index.d.cts +33 -10
  18. package/packages/server/dist/index.d.ts +33 -10
  19. package/packages/server/dist/index.js +270 -46
  20. package/packages/shared/dist/browser/index.global.js +1 -1
  21. package/packages/shared/dist/browser/index.global.js.map +1 -1
  22. package/packages/shared/dist/cjs/index.cjs +17 -0
  23. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  24. package/packages/shared/dist/esm/index.js +12 -0
  25. package/packages/shared/dist/esm/index.js.map +1 -1
  26. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  27. package/packages/shared/dist/types/protocol/constants.d.ts +6 -0
  28. package/packages/shared/dist/types/protocol/constants.d.ts.map +1 -0
  29. package/packages/shared/dist/types/protocol/index.d.ts +1 -0
  30. package/packages/shared/dist/types/protocol/index.d.ts.map +1 -1
  31. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -115,32 +115,77 @@ import { WebSocketServer } from "ws";
115
115
  import { createGame } from "@quake2ts/game";
116
116
 
117
117
  // src/client.ts
118
+ import { UPDATE_BACKUP } from "@quake2ts/shared";
118
119
  var ClientState = /* @__PURE__ */ ((ClientState2) => {
119
120
  ClientState2[ClientState2["Free"] = 0] = "Free";
120
- ClientState2[ClientState2["Connected"] = 1] = "Connected";
121
- ClientState2[ClientState2["Primed"] = 2] = "Primed";
122
- ClientState2[ClientState2["Active"] = 3] = "Active";
123
- ClientState2[ClientState2["Spawned"] = 4] = "Spawned";
121
+ ClientState2[ClientState2["Zombie"] = 1] = "Zombie";
122
+ ClientState2[ClientState2["Connected"] = 2] = "Connected";
123
+ ClientState2[ClientState2["Spawned"] = 3] = "Spawned";
124
+ ClientState2[ClientState2["Active"] = 4] = "Active";
124
125
  return ClientState2;
125
126
  })(ClientState || {});
126
127
  function createClient(index, net) {
128
+ const frames = [];
129
+ for (let i = 0; i < UPDATE_BACKUP; i++) {
130
+ frames.push({
131
+ areaBytes: 0,
132
+ areaBits: new Uint8Array(0),
133
+ // Size depends on map areas
134
+ playerState: createEmptyPlayerState(),
135
+ numEntities: 0,
136
+ firstEntity: 0,
137
+ sentTime: 0
138
+ });
139
+ }
127
140
  return {
128
141
  index,
129
- state: 1 /* Connected */,
142
+ state: 2 /* Connected */,
130
143
  net,
131
- name: `Player ${index}`,
132
144
  userInfo: "",
145
+ lastFrame: 0,
146
+ lastCmd: createEmptyUserCommand(),
147
+ commandMsec: 0,
148
+ frameLatency: [],
149
+ ping: 0,
150
+ messageSize: [],
151
+ rate: 25e3,
152
+ // Default rate
153
+ suppressCount: 0,
133
154
  edict: null,
134
- lastCmd: {
135
- msec: 0,
136
- buttons: 0,
137
- angles: { x: 0, y: 0, z: 0 },
138
- forwardmove: 0,
139
- sidemove: 0,
140
- upmove: 0
141
- },
142
- lastPacketTime: Date.now(),
143
- lastFrame: 0
155
+ name: `Player ${index}`,
156
+ messageLevel: 0,
157
+ datagram: new Uint8Array(0),
158
+ frames,
159
+ downloadSize: 0,
160
+ downloadCount: 0,
161
+ lastMessage: 0,
162
+ lastConnect: Date.now(),
163
+ challenge: 0,
164
+ messageQueue: []
165
+ };
166
+ }
167
+ function createEmptyUserCommand() {
168
+ return {
169
+ msec: 0,
170
+ buttons: 0,
171
+ angles: { x: 0, y: 0, z: 0 },
172
+ forwardmove: 0,
173
+ sidemove: 0,
174
+ upmove: 0
175
+ };
176
+ }
177
+ function createEmptyPlayerState() {
178
+ return {
179
+ origin: { x: 0, y: 0, z: 0 },
180
+ velocity: { x: 0, y: 0, z: 0 },
181
+ viewAngles: { x: 0, y: 0, z: 0 },
182
+ onGround: false,
183
+ waterLevel: 0,
184
+ mins: { x: -16, y: -16, z: -24 },
185
+ maxs: { x: 16, y: 16, z: 32 },
186
+ damageAlpha: 0,
187
+ damageIndicators: [],
188
+ blend: [0, 0, 0, 0]
144
189
  };
145
190
  }
146
191
 
@@ -211,10 +256,150 @@ var ClientMessageParser = class {
211
256
  };
212
257
 
213
258
  // src/dedicated.ts
214
- import { BinaryWriter, ServerCommand, BinaryStream as BinaryStream2, traceBox } from "@quake2ts/shared";
259
+ import { BinaryWriter, ServerCommand, BinaryStream as BinaryStream2, traceBox, MAX_CONFIGSTRINGS as MAX_CONFIGSTRINGS2, MAX_EDICTS } from "@quake2ts/shared";
215
260
  import { parseBsp } from "@quake2ts/engine";
216
261
  import fs from "fs/promises";
217
262
  import { createPlayerInventory, createPlayerWeaponStates } from "@quake2ts/game";
263
+
264
+ // src/protocol/entity.ts
265
+ var U_ORIGIN1 = 1 << 0;
266
+ var U_ORIGIN2 = 1 << 1;
267
+ var U_ANGLE2 = 1 << 2;
268
+ var U_ANGLE3 = 1 << 3;
269
+ var U_FRAME8 = 1 << 4;
270
+ var U_EVENT = 1 << 5;
271
+ var U_REMOVE = 1 << 6;
272
+ var U_MOREBITS1 = 1 << 7;
273
+ var U_NUMBER16 = 1 << 8;
274
+ var U_ORIGIN3 = 1 << 9;
275
+ var U_ANGLE1 = 1 << 10;
276
+ var U_MODEL = 1 << 11;
277
+ var U_RENDERFX8 = 1 << 12;
278
+ var U_EFFECTS8 = 1 << 14;
279
+ var U_MOREBITS2 = 1 << 15;
280
+ var U_SKIN8 = 1 << 16;
281
+ var U_FRAME16 = 1 << 17;
282
+ var U_RENDERFX16 = 1 << 18;
283
+ var U_EFFECTS16 = 1 << 19;
284
+ var U_MOREBITS3 = 1 << 23;
285
+ var U_OLDORIGIN = 1 << 24;
286
+ var U_SKIN16 = 1 << 25;
287
+ var U_SOUND = 1 << 26;
288
+ var U_SOLID = 1 << 27;
289
+ var NULL_STATE = {
290
+ number: 0,
291
+ origin: { x: 0, y: 0, z: 0 },
292
+ angles: { x: 0, y: 0, z: 0 },
293
+ modelIndex: 0,
294
+ frame: 0,
295
+ skinNum: 0,
296
+ effects: 0,
297
+ renderfx: 0,
298
+ solid: 0,
299
+ sound: 0,
300
+ event: 0
301
+ };
302
+ function writeDeltaEntity(from, to, writer, force, newEntity) {
303
+ let bits = 0;
304
+ if (newEntity) {
305
+ from = NULL_STATE;
306
+ }
307
+ if (to.modelIndex !== from.modelIndex || force) {
308
+ bits |= U_MODEL;
309
+ }
310
+ if (to.origin.x !== from.origin.x || force) {
311
+ bits |= U_ORIGIN1;
312
+ }
313
+ if (to.origin.y !== from.origin.y || force) {
314
+ bits |= U_ORIGIN2;
315
+ }
316
+ if (to.origin.z !== from.origin.z || force) {
317
+ bits |= U_ORIGIN3;
318
+ }
319
+ if (to.angles.x !== from.angles.x || force) {
320
+ bits |= U_ANGLE1;
321
+ }
322
+ if (to.angles.y !== from.angles.y || force) {
323
+ bits |= U_ANGLE2;
324
+ }
325
+ if (to.angles.z !== from.angles.z || force) {
326
+ bits |= U_ANGLE3;
327
+ }
328
+ if (to.frame !== from.frame || force) {
329
+ if (to.frame >= 256) bits |= U_FRAME16;
330
+ else bits |= U_FRAME8;
331
+ }
332
+ if (to.skinNum !== from.skinNum || force) {
333
+ if (to.skinNum >= 256) bits |= U_SKIN16;
334
+ else bits |= U_SKIN8;
335
+ }
336
+ if (to.effects !== from.effects || force) {
337
+ if (to.effects >= 256) bits |= U_EFFECTS16;
338
+ else bits |= U_EFFECTS8;
339
+ }
340
+ if (to.renderfx !== from.renderfx || force) {
341
+ if (to.renderfx >= 256) bits |= U_RENDERFX16;
342
+ else bits |= U_RENDERFX8;
343
+ }
344
+ if (to.solid !== from.solid || force) {
345
+ bits |= U_SOLID;
346
+ }
347
+ if (to.sound !== from.sound || force) {
348
+ bits |= U_SOUND;
349
+ }
350
+ if (to.event !== from.event || force) {
351
+ bits |= U_EVENT;
352
+ }
353
+ if (to.number >= 256) {
354
+ bits |= U_NUMBER16;
355
+ }
356
+ if (bits & 4294967040) {
357
+ bits |= U_MOREBITS1;
358
+ }
359
+ if (bits & 4294901760) {
360
+ bits |= U_MOREBITS2;
361
+ }
362
+ if (bits & 4278190080) {
363
+ bits |= U_MOREBITS3;
364
+ }
365
+ writer.writeByte(bits & 255);
366
+ if (bits & U_MOREBITS1) {
367
+ writer.writeByte(bits >> 8 & 255);
368
+ }
369
+ if (bits & U_MOREBITS2) {
370
+ writer.writeByte(bits >> 16 & 255);
371
+ }
372
+ if (bits & U_MOREBITS3) {
373
+ writer.writeByte(bits >> 24 & 255);
374
+ }
375
+ if (bits & U_NUMBER16) {
376
+ writer.writeShort(to.number);
377
+ } else {
378
+ writer.writeByte(to.number);
379
+ }
380
+ if (bits & U_MODEL) writer.writeByte(to.modelIndex);
381
+ if (bits & U_FRAME8) writer.writeByte(to.frame);
382
+ if (bits & U_FRAME16) writer.writeShort(to.frame);
383
+ if (bits & U_SKIN8) writer.writeByte(to.skinNum);
384
+ if (bits & U_SKIN16) writer.writeShort(to.skinNum);
385
+ if (bits & U_EFFECTS8) writer.writeByte(to.effects);
386
+ if (bits & U_EFFECTS16) writer.writeShort(to.effects);
387
+ if (bits & U_RENDERFX8) writer.writeByte(to.renderfx);
388
+ if (bits & U_RENDERFX16) writer.writeShort(to.renderfx);
389
+ if (bits & U_ORIGIN1) writer.writeCoord(to.origin.x);
390
+ if (bits & U_ORIGIN2) writer.writeCoord(to.origin.y);
391
+ if (bits & U_ORIGIN3) writer.writeCoord(to.origin.z);
392
+ if (bits & U_ANGLE1) writer.writeAngle(to.angles.x);
393
+ if (bits & U_ANGLE2) writer.writeAngle(to.angles.y);
394
+ if (bits & U_ANGLE3) writer.writeAngle(to.angles.z);
395
+ if (bits & U_OLDORIGIN) {
396
+ }
397
+ if (bits & U_SOUND) writer.writeByte(to.sound ?? 0);
398
+ if (bits & U_EVENT) writer.writeByte(to.event ?? 0);
399
+ if (bits & U_SOLID) writer.writeShort(to.solid);
400
+ }
401
+
402
+ // src/dedicated.ts
218
403
  var MAX_CLIENTS = 16;
219
404
  var FRAME_RATE = 10;
220
405
  var FRAME_TIME_MS = 1e3 / FRAME_RATE;
@@ -225,27 +410,43 @@ var DedicatedServer = class {
225
410
  this.game = null;
226
411
  this.frameInterval = null;
227
412
  this.svs = {
228
- clients: new Array(MAX_CLIENTS).fill(null)
413
+ initialized: false,
414
+ realTime: 0,
415
+ mapCmd: "",
416
+ spawnCount: 0,
417
+ clients: new Array(MAX_CLIENTS).fill(null),
418
+ lastHeartbeat: 0,
419
+ challenges: []
229
420
  };
230
421
  this.sv = {
422
+ state: 0 /* Dead */,
423
+ attractLoop: false,
424
+ loadGame: false,
231
425
  startTime: 0,
426
+ // Initialize startTime
232
427
  time: 0,
233
428
  frame: 0,
234
- mapName: "",
235
- collisionModel: null
429
+ name: "",
430
+ collisionModel: null,
431
+ configStrings: new Array(MAX_CONFIGSTRINGS2).fill(""),
432
+ baselines: new Array(MAX_EDICTS).fill(null),
433
+ multicastBuf: new Uint8Array(0)
236
434
  };
237
435
  }
238
436
  async start(mapName) {
239
437
  console.log(`Starting Dedicated Server on port ${this.port}...`);
240
- this.sv.mapName = mapName;
438
+ this.sv.name = mapName;
439
+ this.svs.initialized = true;
440
+ this.svs.spawnCount++;
241
441
  this.wss = new WebSocketServer({ port: this.port });
242
442
  this.wss.on("connection", (ws) => {
243
443
  console.log("New connection");
244
444
  this.handleConnection(ws);
245
445
  });
246
446
  try {
247
- console.log(`Loading map ${this.sv.mapName}...`);
248
- const mapData = await fs.readFile(this.sv.mapName);
447
+ console.log(`Loading map ${this.sv.name}...`);
448
+ this.sv.state = 1 /* Loading */;
449
+ const mapData = await fs.readFile(this.sv.name);
249
450
  const arrayBuffer = mapData.buffer.slice(mapData.byteOffset, mapData.byteOffset + mapData.byteLength);
250
451
  const bspMap = parseBsp(arrayBuffer);
251
452
  this.sv.collisionModel = bspMap;
@@ -302,6 +503,7 @@ var DedicatedServer = class {
302
503
  });
303
504
  this.game.init(0);
304
505
  this.game.spawnWorld();
506
+ this.sv.state = 2 /* Game */;
305
507
  this.frameInterval = setInterval(() => this.runFrame(), FRAME_TIME_MS);
306
508
  console.log("Server started.");
307
509
  }
@@ -309,6 +511,7 @@ var DedicatedServer = class {
309
511
  if (this.frameInterval) clearInterval(this.frameInterval);
310
512
  if (this.wss) this.wss.close();
311
513
  this.game?.shutdown();
514
+ this.sv.state = 0 /* Dead */;
312
515
  }
313
516
  handleConnection(ws) {
314
517
  let clientIndex = -1;
@@ -331,19 +534,7 @@ var DedicatedServer = class {
331
534
  }
332
535
  onClientMessage(client, data) {
333
536
  const buffer = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer : data.slice().buffer;
334
- const reader = new BinaryStream2(buffer);
335
- const parser = new ClientMessageParser(reader, {
336
- onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd),
337
- onUserInfo: (info) => this.handleUserInfo(client, info),
338
- onStringCmd: (cmd) => this.handleStringCmd(client, cmd),
339
- onNop: () => {
340
- },
341
- onBad: () => {
342
- console.warn(`Bad command from client ${client.index}`);
343
- client.net.disconnect();
344
- }
345
- });
346
- parser.parseMessage();
537
+ client.messageQueue.push(new Uint8Array(buffer));
347
538
  }
348
539
  onClientDisconnect(client) {
349
540
  console.log(`Client ${client.index} disconnected`);
@@ -351,8 +542,8 @@ var DedicatedServer = class {
351
542
  }
352
543
  handleMove(client, cmd) {
353
544
  client.lastCmd = cmd;
354
- client.lastPacketTime = Date.now();
355
- if (client.state === 1 /* Connected */) {
545
+ client.lastMessage = this.sv.frame;
546
+ if (client.state === 2 /* Connected */) {
356
547
  this.spawnClient(client);
357
548
  }
358
549
  }
@@ -369,7 +560,7 @@ var DedicatedServer = class {
369
560
  buttons: 0
370
561
  });
371
562
  client.edict = ent;
372
- client.state = 3 /* Active */;
563
+ client.state = 4 /* Active */;
373
564
  this.sendServerData(client);
374
565
  }
375
566
  sendServerData(client) {
@@ -384,31 +575,57 @@ var DedicatedServer = class {
384
575
  client.net.send(writer.getData());
385
576
  }
386
577
  SV_ReadPackets() {
578
+ for (const client of this.svs.clients) {
579
+ if (!client || client.state === 0 /* Free */) continue;
580
+ while (client.messageQueue.length > 0) {
581
+ const data = client.messageQueue.shift();
582
+ if (!data) continue;
583
+ const reader = new BinaryStream2(data.buffer);
584
+ const parser = new ClientMessageParser(reader, {
585
+ onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd),
586
+ onUserInfo: (info) => this.handleUserInfo(client, info),
587
+ onStringCmd: (cmd) => this.handleStringCmd(client, cmd),
588
+ onNop: () => {
589
+ },
590
+ onBad: () => {
591
+ console.warn(`Bad command from client ${client.index}`);
592
+ }
593
+ });
594
+ try {
595
+ parser.parseMessage();
596
+ } catch (e) {
597
+ console.error(`Error parsing message from client ${client.index}:`, e);
598
+ }
599
+ }
600
+ }
387
601
  }
388
602
  runFrame() {
389
603
  if (!this.game) return;
390
604
  this.sv.frame++;
605
+ this.sv.time += 100;
391
606
  this.SV_ReadPackets();
392
607
  for (const client of this.svs.clients) {
393
- if (client && client.state === 3 /* Active */ && client.edict) {
608
+ if (client && client.state === 4 /* Active */ && client.edict) {
394
609
  this.game.clientThink(client.edict, client.lastCmd);
395
610
  }
396
611
  }
397
- this.game.frame({
612
+ const snapshot = this.game.frame({
398
613
  frame: this.sv.frame,
399
614
  deltaMs: FRAME_TIME_MS,
400
615
  nowMs: Date.now()
401
616
  });
402
- this.SV_SendClientMessages();
617
+ if (snapshot && snapshot.state) {
618
+ this.SV_SendClientMessages(snapshot.state);
619
+ }
403
620
  }
404
- SV_SendClientMessages() {
621
+ SV_SendClientMessages(snapshot) {
405
622
  for (const client of this.svs.clients) {
406
- if (client && client.state === 3 /* Active */) {
407
- this.SV_SendClientFrame(client);
623
+ if (client && client.state === 4 /* Active */) {
624
+ this.SV_SendClientFrame(client, snapshot);
408
625
  }
409
626
  }
410
627
  }
411
- SV_SendClientFrame(client) {
628
+ SV_SendClientFrame(client, snapshot) {
412
629
  const writer = new BinaryWriter();
413
630
  writer.writeByte(ServerCommand.frame);
414
631
  writer.writeLong(this.sv.frame);
@@ -418,7 +635,14 @@ var DedicatedServer = class {
418
635
  writer.writeByte(ServerCommand.playerinfo);
419
636
  writer.writeShort(0);
420
637
  writer.writeLong(0);
638
+ writer.writeByte(ServerCommand.packetentities);
639
+ const entities = snapshot.packetEntities || [];
640
+ for (const entity of entities) {
641
+ writeDeltaEntity({}, entity, writer, false, true);
642
+ }
643
+ writer.writeShort(0);
421
644
  client.net.send(writer.getData());
645
+ client.lastFrame = this.sv.frame;
422
646
  }
423
647
  // GameEngine Implementation
424
648
  trace(start, end) {