quake2ts 0.0.288 → 0.0.289

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 (40) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js +13 -13
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +554 -2
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +554 -2
  7. package/packages/client/dist/esm/index.js.map +1 -1
  8. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  9. package/packages/engine/dist/browser/index.global.js +16 -16
  10. package/packages/engine/dist/browser/index.global.js.map +1 -1
  11. package/packages/engine/dist/cjs/index.cjs +327 -0
  12. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  13. package/packages/engine/dist/esm/index.js +327 -0
  14. package/packages/engine/dist/esm/index.js.map +1 -1
  15. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  16. package/packages/game/dist/browser/index.global.js +3 -3
  17. package/packages/game/dist/browser/index.global.js.map +1 -1
  18. package/packages/game/dist/cjs/index.cjs +492 -1
  19. package/packages/game/dist/cjs/index.cjs.map +1 -1
  20. package/packages/game/dist/esm/index.js +492 -1
  21. package/packages/game/dist/esm/index.js.map +1 -1
  22. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  23. package/packages/server/dist/index.cjs +54 -11
  24. package/packages/server/dist/index.d.cts +6 -1
  25. package/packages/server/dist/index.d.ts +6 -1
  26. package/packages/server/dist/index.js +56 -13
  27. package/packages/shared/dist/browser/index.global.js +1 -1
  28. package/packages/shared/dist/browser/index.global.js.map +1 -1
  29. package/packages/shared/dist/cjs/index.cjs +509 -0
  30. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  31. package/packages/shared/dist/esm/index.js +507 -0
  32. package/packages/shared/dist/esm/index.js.map +1 -1
  33. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  34. package/packages/shared/dist/types/net/index.d.ts +1 -0
  35. package/packages/shared/dist/types/net/index.d.ts.map +1 -1
  36. package/packages/shared/dist/types/protocol/crc.d.ts +5 -0
  37. package/packages/shared/dist/types/protocol/crc.d.ts.map +1 -0
  38. package/packages/shared/dist/types/protocol/index.d.ts +1 -0
  39. package/packages/shared/dist/types/protocol/index.d.ts.map +1 -1
  40. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -175,13 +175,17 @@ function createClient(index, net) {
175
175
  numEntities: 0,
176
176
  firstEntity: 0,
177
177
  sentTime: 0,
178
- entities: []
178
+ entities: [],
179
+ packetCRC: 0
179
180
  });
180
181
  }
182
+ const netchan = new import_shared.NetChan();
183
+ netchan.setup(Math.floor(Math.random() * 65536));
181
184
  return {
182
185
  index,
183
186
  state: 2 /* Connected */,
184
187
  net,
188
+ netchan,
185
189
  userInfo: "",
186
190
  lastFrame: 0,
187
191
  lastCmd: createEmptyUserCommand(),
@@ -203,7 +207,10 @@ function createClient(index, net) {
203
207
  lastConnect: Date.now(),
204
208
  challenge: 0,
205
209
  messageQueue: [],
206
- lastPacketEntities: []
210
+ lastPacketEntities: [],
211
+ commandQueue: [],
212
+ lastCommandTime: 0,
213
+ commandCount: 0
207
214
  };
208
215
  }
209
216
  function createEmptyUserCommand() {
@@ -1007,9 +1014,17 @@ var DedicatedServer = class {
1007
1014
  client.net.disconnect();
1008
1015
  }
1009
1016
  }
1010
- handleMove(client, cmd) {
1017
+ handleMove(client, cmd, checksum, lastFrame) {
1018
+ if (lastFrame > 0 && lastFrame <= client.lastFrame && lastFrame > client.lastFrame - import_shared4.UPDATE_BACKUP) {
1019
+ const frameIdx = lastFrame % import_shared4.UPDATE_BACKUP;
1020
+ const frame = client.frames[frameIdx];
1021
+ if (frame.packetCRC !== checksum) {
1022
+ console.warn(`Client ${client.index} checksum mismatch for frame ${lastFrame}: expected ${frame.packetCRC}, got ${checksum}`);
1023
+ }
1024
+ }
1011
1025
  client.lastCmd = cmd;
1012
1026
  client.lastMessage = this.sv.frame;
1027
+ client.commandCount++;
1013
1028
  }
1014
1029
  handleUserInfo(client, info) {
1015
1030
  client.userInfo = info;
@@ -1091,12 +1106,20 @@ var DedicatedServer = class {
1091
1106
  SV_SetConfigString(index, value) {
1092
1107
  if (index < 0 || index >= import_shared4.MAX_CONFIGSTRINGS) return;
1093
1108
  this.sv.configStrings[index] = value;
1094
- const writer = new import_shared4.BinaryWriter();
1095
- this.SV_WriteConfigString(writer, index, value);
1096
- const data = writer.getData();
1097
1109
  for (const client of this.svs.clients) {
1098
1110
  if (client && client.state >= 2 /* Connected */) {
1099
- client.net.send(data);
1111
+ if (client.netchan) {
1112
+ try {
1113
+ client.netchan.writeReliableByte(import_shared4.ServerCommand.configstring);
1114
+ client.netchan.writeReliableShort(index);
1115
+ client.netchan.writeReliableString(value);
1116
+ const packet = client.netchan.transmit();
1117
+ client.net.send(packet);
1118
+ } catch (e) {
1119
+ console.warn(`Client ${client.index} reliable buffer overflow`);
1120
+ this.dropClient(client);
1121
+ }
1122
+ }
1100
1123
  }
1101
1124
  }
1102
1125
  }
@@ -1109,11 +1132,18 @@ var DedicatedServer = class {
1109
1132
  for (const client of this.svs.clients) {
1110
1133
  if (!client || client.state === 0 /* Free */) continue;
1111
1134
  while (client.messageQueue.length > 0) {
1112
- const data = client.messageQueue.shift();
1113
- if (!data) continue;
1135
+ const rawData = client.messageQueue.shift();
1136
+ if (!rawData) continue;
1137
+ const data = client.netchan.process(rawData);
1138
+ if (!data) {
1139
+ continue;
1140
+ }
1141
+ if (data.length === 0) {
1142
+ continue;
1143
+ }
1114
1144
  const reader = new import_shared4.BinaryStream(data.buffer);
1115
1145
  const parser = new ClientMessageParser(reader, {
1116
- onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd),
1146
+ onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd, checksum, lastFrame),
1117
1147
  onUserInfo: (info) => this.handleUserInfo(client, info),
1118
1148
  onStringCmd: (cmd) => this.handleStringCmd(client, cmd),
1119
1149
  onNop: () => {
@@ -1147,6 +1177,16 @@ var DedicatedServer = class {
1147
1177
  }
1148
1178
  }
1149
1179
  if (client && client.state === 4 /* Active */ && client.edict) {
1180
+ const now = Date.now();
1181
+ if (now - client.lastCommandTime >= 1e3) {
1182
+ client.lastCommandTime = now;
1183
+ client.commandCount = 0;
1184
+ }
1185
+ if (client.commandCount > 200) {
1186
+ console.warn(`Client ${client.index} kicked for command flooding`);
1187
+ this.dropClient(client);
1188
+ continue;
1189
+ }
1150
1190
  this.game.clientThink(client.edict, client.lastCmd);
1151
1191
  }
1152
1192
  }
@@ -1242,7 +1282,10 @@ var DedicatedServer = class {
1242
1282
  }
1243
1283
  }
1244
1284
  writer.writeShort(0);
1245
- client.net.send(writer.getData());
1285
+ const frameData = writer.getData();
1286
+ currentFrame.packetCRC = (0, import_shared4.crc8)(frameData);
1287
+ const packet = client.netchan.transmit(frameData);
1288
+ client.net.send(packet);
1246
1289
  client.lastFrame = this.sv.frame;
1247
1290
  client.lastPacketEntities = currentEntityIds;
1248
1291
  }
@@ -1,4 +1,4 @@
1
- import { NetDriver, ServerCommand, PlayerState, EntityState, UserCommand, BinaryStream } from '@quake2ts/shared';
1
+ import { NetDriver, ServerCommand, PlayerState, EntityState, NetChan, UserCommand, BinaryStream } from '@quake2ts/shared';
2
2
  import WebSocket from 'ws';
3
3
  import { GameEngine, MulticastType, Entity } from '@quake2ts/game';
4
4
 
@@ -68,11 +68,13 @@ interface ClientFrame {
68
68
  firstEntity: number;
69
69
  sentTime: number;
70
70
  entities: EntityState[];
71
+ packetCRC: number;
71
72
  }
72
73
  interface Client {
73
74
  index: number;
74
75
  state: ClientState;
75
76
  net: NetDriver;
77
+ netchan: NetChan;
76
78
  userInfo: string;
77
79
  lastFrame: number;
78
80
  lastCmd: UserCommand;
@@ -95,6 +97,9 @@ interface Client {
95
97
  challenge: number;
96
98
  messageQueue: Uint8Array[];
97
99
  lastPacketEntities: number[];
100
+ commandQueue: UserCommand[];
101
+ lastCommandTime: number;
102
+ commandCount: number;
98
103
  }
99
104
  declare function createClient(index: number, net: NetDriver): Client;
100
105
 
@@ -1,4 +1,4 @@
1
- import { NetDriver, ServerCommand, PlayerState, EntityState, UserCommand, BinaryStream } from '@quake2ts/shared';
1
+ import { NetDriver, ServerCommand, PlayerState, EntityState, NetChan, UserCommand, BinaryStream } from '@quake2ts/shared';
2
2
  import WebSocket from 'ws';
3
3
  import { GameEngine, MulticastType, Entity } from '@quake2ts/game';
4
4
 
@@ -68,11 +68,13 @@ interface ClientFrame {
68
68
  firstEntity: number;
69
69
  sentTime: number;
70
70
  entities: EntityState[];
71
+ packetCRC: number;
71
72
  }
72
73
  interface Client {
73
74
  index: number;
74
75
  state: ClientState;
75
76
  net: NetDriver;
77
+ netchan: NetChan;
76
78
  userInfo: string;
77
79
  lastFrame: number;
78
80
  lastCmd: UserCommand;
@@ -95,6 +97,9 @@ interface Client {
95
97
  challenge: number;
96
98
  messageQueue: Uint8Array[];
97
99
  lastPacketEntities: number[];
100
+ commandQueue: UserCommand[];
101
+ lastCommandTime: number;
102
+ commandCount: number;
98
103
  }
99
104
  declare function createClient(index: number, net: NetDriver): Client;
100
105
 
@@ -115,7 +115,7 @@ import { WebSocketServer } from "ws";
115
115
  import { createGame, MulticastType, Solid } from "@quake2ts/game";
116
116
 
117
117
  // src/client.ts
118
- import { UPDATE_BACKUP } from "@quake2ts/shared";
118
+ import { UPDATE_BACKUP, NetChan } from "@quake2ts/shared";
119
119
  var ClientState = /* @__PURE__ */ ((ClientState2) => {
120
120
  ClientState2[ClientState2["Free"] = 0] = "Free";
121
121
  ClientState2[ClientState2["Zombie"] = 1] = "Zombie";
@@ -135,13 +135,17 @@ function createClient(index, net) {
135
135
  numEntities: 0,
136
136
  firstEntity: 0,
137
137
  sentTime: 0,
138
- entities: []
138
+ entities: [],
139
+ packetCRC: 0
139
140
  });
140
141
  }
142
+ const netchan = new NetChan();
143
+ netchan.setup(Math.floor(Math.random() * 65536));
141
144
  return {
142
145
  index,
143
146
  state: 2 /* Connected */,
144
147
  net,
148
+ netchan,
145
149
  userInfo: "",
146
150
  lastFrame: 0,
147
151
  lastCmd: createEmptyUserCommand(),
@@ -163,7 +167,10 @@ function createClient(index, net) {
163
167
  lastConnect: Date.now(),
164
168
  challenge: 0,
165
169
  messageQueue: [],
166
- lastPacketEntities: []
170
+ lastPacketEntities: [],
171
+ commandQueue: [],
172
+ lastCommandTime: 0,
173
+ commandCount: 0
167
174
  };
168
175
  }
169
176
  function createEmptyUserCommand() {
@@ -271,7 +278,7 @@ var ClientMessageParser = class {
271
278
  };
272
279
 
273
280
  // src/dedicated.ts
274
- import { BinaryWriter as BinaryWriter2, ServerCommand as ServerCommand2, BinaryStream as BinaryStream2, traceBox, UPDATE_BACKUP as UPDATE_BACKUP2, MAX_CONFIGSTRINGS as MAX_CONFIGSTRINGS2, MAX_EDICTS, CollisionEntityIndex, inPVS, inPHS } from "@quake2ts/shared";
281
+ import { BinaryWriter as BinaryWriter2, ServerCommand as ServerCommand2, BinaryStream as BinaryStream2, traceBox, UPDATE_BACKUP as UPDATE_BACKUP2, MAX_CONFIGSTRINGS as MAX_CONFIGSTRINGS2, MAX_EDICTS, CollisionEntityIndex, inPVS, inPHS, crc8 } from "@quake2ts/shared";
275
282
  import { parseBsp } from "@quake2ts/engine";
276
283
  import fs from "fs/promises";
277
284
  import { createPlayerInventory, createPlayerWeaponStates } from "@quake2ts/game";
@@ -967,9 +974,17 @@ var DedicatedServer = class {
967
974
  client.net.disconnect();
968
975
  }
969
976
  }
970
- handleMove(client, cmd) {
977
+ handleMove(client, cmd, checksum, lastFrame) {
978
+ if (lastFrame > 0 && lastFrame <= client.lastFrame && lastFrame > client.lastFrame - UPDATE_BACKUP2) {
979
+ const frameIdx = lastFrame % UPDATE_BACKUP2;
980
+ const frame = client.frames[frameIdx];
981
+ if (frame.packetCRC !== checksum) {
982
+ console.warn(`Client ${client.index} checksum mismatch for frame ${lastFrame}: expected ${frame.packetCRC}, got ${checksum}`);
983
+ }
984
+ }
971
985
  client.lastCmd = cmd;
972
986
  client.lastMessage = this.sv.frame;
987
+ client.commandCount++;
973
988
  }
974
989
  handleUserInfo(client, info) {
975
990
  client.userInfo = info;
@@ -1051,12 +1066,20 @@ var DedicatedServer = class {
1051
1066
  SV_SetConfigString(index, value) {
1052
1067
  if (index < 0 || index >= MAX_CONFIGSTRINGS2) return;
1053
1068
  this.sv.configStrings[index] = value;
1054
- const writer = new BinaryWriter2();
1055
- this.SV_WriteConfigString(writer, index, value);
1056
- const data = writer.getData();
1057
1069
  for (const client of this.svs.clients) {
1058
1070
  if (client && client.state >= 2 /* Connected */) {
1059
- client.net.send(data);
1071
+ if (client.netchan) {
1072
+ try {
1073
+ client.netchan.writeReliableByte(ServerCommand2.configstring);
1074
+ client.netchan.writeReliableShort(index);
1075
+ client.netchan.writeReliableString(value);
1076
+ const packet = client.netchan.transmit();
1077
+ client.net.send(packet);
1078
+ } catch (e) {
1079
+ console.warn(`Client ${client.index} reliable buffer overflow`);
1080
+ this.dropClient(client);
1081
+ }
1082
+ }
1060
1083
  }
1061
1084
  }
1062
1085
  }
@@ -1069,11 +1092,18 @@ var DedicatedServer = class {
1069
1092
  for (const client of this.svs.clients) {
1070
1093
  if (!client || client.state === 0 /* Free */) continue;
1071
1094
  while (client.messageQueue.length > 0) {
1072
- const data = client.messageQueue.shift();
1073
- if (!data) continue;
1095
+ const rawData = client.messageQueue.shift();
1096
+ if (!rawData) continue;
1097
+ const data = client.netchan.process(rawData);
1098
+ if (!data) {
1099
+ continue;
1100
+ }
1101
+ if (data.length === 0) {
1102
+ continue;
1103
+ }
1074
1104
  const reader = new BinaryStream2(data.buffer);
1075
1105
  const parser = new ClientMessageParser(reader, {
1076
- onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd),
1106
+ onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd, checksum, lastFrame),
1077
1107
  onUserInfo: (info) => this.handleUserInfo(client, info),
1078
1108
  onStringCmd: (cmd) => this.handleStringCmd(client, cmd),
1079
1109
  onNop: () => {
@@ -1107,6 +1137,16 @@ var DedicatedServer = class {
1107
1137
  }
1108
1138
  }
1109
1139
  if (client && client.state === 4 /* Active */ && client.edict) {
1140
+ const now = Date.now();
1141
+ if (now - client.lastCommandTime >= 1e3) {
1142
+ client.lastCommandTime = now;
1143
+ client.commandCount = 0;
1144
+ }
1145
+ if (client.commandCount > 200) {
1146
+ console.warn(`Client ${client.index} kicked for command flooding`);
1147
+ this.dropClient(client);
1148
+ continue;
1149
+ }
1110
1150
  this.game.clientThink(client.edict, client.lastCmd);
1111
1151
  }
1112
1152
  }
@@ -1202,7 +1242,10 @@ var DedicatedServer = class {
1202
1242
  }
1203
1243
  }
1204
1244
  writer.writeShort(0);
1205
- client.net.send(writer.getData());
1245
+ const frameData = writer.getData();
1246
+ currentFrame.packetCRC = crc8(frameData);
1247
+ const packet = client.netchan.transmit(frameData);
1248
+ client.net.send(packet);
1206
1249
  client.lastFrame = this.sv.frame;
1207
1250
  client.lastPacketEntities = currentEntityIds;
1208
1251
  }