quake2ts 0.0.469 → 0.0.472

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 (39) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js +16 -16
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +130 -2
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +130 -2
  7. package/packages/client/dist/esm/index.js.map +1 -1
  8. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  9. package/packages/client/dist/types/chat.d.ts +20 -0
  10. package/packages/client/dist/types/chat.d.ts.map +1 -0
  11. package/packages/client/dist/types/chat.test.d.ts +2 -0
  12. package/packages/client/dist/types/chat.test.d.ts.map +1 -0
  13. package/packages/client/dist/types/demo/handler.d.ts +1 -0
  14. package/packages/client/dist/types/demo/handler.d.ts.map +1 -1
  15. package/packages/client/dist/types/index.d.ts +3 -0
  16. package/packages/client/dist/types/index.d.ts.map +1 -1
  17. package/packages/client/dist/types/scoreboard.d.ts +17 -0
  18. package/packages/client/dist/types/scoreboard.d.ts.map +1 -1
  19. package/packages/client/dist/types/scoreboard.test.d.ts +2 -0
  20. package/packages/client/dist/types/scoreboard.test.d.ts.map +1 -0
  21. package/packages/game/dist/browser/index.global.js +4 -4
  22. package/packages/game/dist/browser/index.global.js.map +1 -1
  23. package/packages/game/dist/cjs/index.cjs +130 -73
  24. package/packages/game/dist/cjs/index.cjs.map +1 -1
  25. package/packages/game/dist/esm/index.js +129 -73
  26. package/packages/game/dist/esm/index.js.map +1 -1
  27. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  28. package/packages/game/dist/types/ai/noise.d.ts +1 -0
  29. package/packages/game/dist/types/ai/noise.d.ts.map +1 -1
  30. package/packages/game/dist/types/ai/targeting.d.ts.map +1 -1
  31. package/packages/game/dist/types/inventory/playerInventory.d.ts +8 -0
  32. package/packages/game/dist/types/inventory/playerInventory.d.ts.map +1 -1
  33. package/packages/game/dist/types/physics/fluid.d.ts +5 -0
  34. package/packages/game/dist/types/physics/fluid.d.ts.map +1 -1
  35. package/packages/game/dist/types/physics/movement.d.ts.map +1 -1
  36. package/packages/server/dist/index.cjs +175 -34
  37. package/packages/server/dist/index.d.cts +37 -5
  38. package/packages/server/dist/index.d.ts +37 -5
  39. package/packages/server/dist/index.js +173 -33
@@ -111,7 +111,6 @@ var WebSocketNetDriver = class {
111
111
  };
112
112
 
113
113
  // src/dedicated.ts
114
- import { WebSocketServer } from "ws";
115
114
  import { createGame, MulticastType, Solid } from "@quake2ts/game";
116
115
 
117
116
  // src/client.ts
@@ -769,25 +768,71 @@ function writeTempEntity(writer, type, args) {
769
768
 
770
769
  // src/dedicated.ts
771
770
  import { lerpAngle } from "@quake2ts/shared";
772
- var MAX_CLIENTS = 16;
771
+
772
+ // src/transports/websocket.ts
773
+ import { WebSocketServer } from "ws";
774
+ var WebSocketTransport = class {
775
+ constructor() {
776
+ this.wss = null;
777
+ this.connectionCallback = null;
778
+ this.errorCallback = null;
779
+ }
780
+ async listen(port) {
781
+ return new Promise((resolve) => {
782
+ this.wss = new WebSocketServer({ port });
783
+ this.wss.on("listening", () => resolve());
784
+ this.wss.on("connection", (ws, req) => {
785
+ const driver = new WebSocketNetDriver();
786
+ driver.attach(ws);
787
+ if (this.connectionCallback) {
788
+ this.connectionCallback(driver, req);
789
+ }
790
+ });
791
+ this.wss.on("error", (err) => {
792
+ if (this.errorCallback) this.errorCallback(err);
793
+ });
794
+ });
795
+ }
796
+ close() {
797
+ if (this.wss) {
798
+ this.wss.close();
799
+ this.wss = null;
800
+ }
801
+ }
802
+ onConnection(callback) {
803
+ this.connectionCallback = callback;
804
+ }
805
+ onError(callback) {
806
+ this.errorCallback = callback;
807
+ }
808
+ };
809
+
810
+ // src/dedicated.ts
811
+ var DEFAULT_MAX_CLIENTS = 16;
773
812
  var FRAME_RATE = 10;
774
813
  var FRAME_TIME_MS = 1e3 / FRAME_RATE;
775
814
  var DedicatedServer = class {
776
- constructor(port = 27910) {
777
- this.port = port;
778
- this.wss = null;
815
+ constructor(optionsOrPort = {}) {
779
816
  this.game = null;
780
817
  this.frameTimeout = null;
781
818
  this.entityIndex = null;
782
819
  // History buffer: Map<EntityIndex, HistoryArray>
783
820
  this.history = /* @__PURE__ */ new Map();
784
821
  this.backup = /* @__PURE__ */ new Map();
822
+ const options = typeof optionsOrPort === "number" ? { port: optionsOrPort } : optionsOrPort;
823
+ this.options = {
824
+ port: 27910,
825
+ maxPlayers: DEFAULT_MAX_CLIENTS,
826
+ deathmatch: true,
827
+ ...options
828
+ };
829
+ this.transport = this.options.transport || new WebSocketTransport();
785
830
  this.svs = {
786
831
  initialized: false,
787
832
  realTime: 0,
788
833
  mapCmd: "",
789
834
  spawnCount: 0,
790
- clients: new Array(MAX_CLIENTS).fill(null),
835
+ clients: new Array(this.options.maxPlayers).fill(null),
791
836
  lastHeartbeat: 0,
792
837
  challenges: []
793
838
  };
@@ -807,20 +852,114 @@ var DedicatedServer = class {
807
852
  };
808
853
  this.entityIndex = new CollisionEntityIndex();
809
854
  }
855
+ setTransport(transport) {
856
+ if (this.svs.initialized) {
857
+ throw new Error("Cannot set transport after server started");
858
+ }
859
+ this.transport = transport;
860
+ }
861
+ async startServer(mapName) {
862
+ const map = mapName || this.options.mapName;
863
+ if (!map) {
864
+ throw new Error("No map specified");
865
+ }
866
+ await this.start(map);
867
+ }
868
+ stopServer() {
869
+ this.stop();
870
+ }
871
+ kickPlayer(clientId) {
872
+ if (clientId < 0 || clientId >= this.svs.clients.length) return;
873
+ const client = this.svs.clients[clientId];
874
+ if (client && client.state >= 2 /* Connected */) {
875
+ console.log(`Kicking client ${clientId}`);
876
+ if (client.netchan) {
877
+ const writer = new BinaryWriter2();
878
+ writer.writeByte(ServerCommand2.print);
879
+ writer.writeByte(2);
880
+ writer.writeString("Kicked by server.\n");
881
+ try {
882
+ const packet = client.netchan.transmit(writer.getData());
883
+ client.net.send(packet);
884
+ } catch (e) {
885
+ }
886
+ }
887
+ this.dropClient(client);
888
+ }
889
+ }
890
+ async changeMap(mapName) {
891
+ console.log(`Changing map to ${mapName}`);
892
+ this.multicast(
893
+ { x: 0, y: 0, z: 0 },
894
+ MulticastType.All,
895
+ ServerCommand2.print,
896
+ 2,
897
+ `Changing map to ${mapName}...
898
+ `
899
+ );
900
+ if (this.frameTimeout) clearTimeout(this.frameTimeout);
901
+ this.sv.state = 1 /* Loading */;
902
+ this.sv.collisionModel = null;
903
+ this.sv.time = 0;
904
+ this.sv.frame = 0;
905
+ this.sv.configStrings.fill("");
906
+ this.sv.baselines.fill(null);
907
+ this.history.clear();
908
+ this.entityIndex = new CollisionEntityIndex();
909
+ await this.loadMap(mapName);
910
+ this.initGame();
911
+ for (const client of this.svs.clients) {
912
+ if (client && client.state >= 2 /* Connected */) {
913
+ client.edict = null;
914
+ client.state = 2 /* Connected */;
915
+ this.sendServerData(client);
916
+ client.netchan.writeReliableByte(ServerCommand2.stufftext);
917
+ client.netchan.writeReliableString(`map ${mapName}
918
+ `);
919
+ this.handleBegin(client);
920
+ }
921
+ }
922
+ this.runFrame();
923
+ }
924
+ getConnectedClients() {
925
+ const list = [];
926
+ for (const client of this.svs.clients) {
927
+ if (client && client.state >= 2 /* Connected */) {
928
+ list.push({
929
+ id: client.index,
930
+ name: "Player",
931
+ // TODO: Parse userinfo for name
932
+ ping: client.ping,
933
+ address: "unknown"
934
+ });
935
+ }
936
+ }
937
+ return list;
938
+ }
810
939
  async start(mapName) {
811
- console.log(`Starting Dedicated Server on port ${this.port}...`);
940
+ console.log(`Starting Dedicated Server on port ${this.options.port}...`);
812
941
  this.sv.name = mapName;
813
942
  this.svs.initialized = true;
814
943
  this.svs.spawnCount++;
815
- this.wss = new WebSocketServer({ port: this.port });
816
- this.wss.on("connection", (ws) => {
817
- console.log("New connection");
818
- this.handleConnection(ws);
944
+ this.transport.onConnection((driver, info) => {
945
+ console.log("New connection", info ? `from ${info.socket?.remoteAddress}` : "");
946
+ this.handleConnection(driver, info);
947
+ });
948
+ this.transport.onError((err) => {
949
+ if (this.onServerError) this.onServerError(err);
819
950
  });
951
+ await this.transport.listen(this.options.port);
952
+ await this.loadMap(mapName);
953
+ this.initGame();
954
+ this.runFrame();
955
+ console.log("Server started.");
956
+ }
957
+ async loadMap(mapName) {
820
958
  try {
821
- console.log(`Loading map ${this.sv.name}...`);
959
+ console.log(`Loading map ${mapName}...`);
822
960
  this.sv.state = 1 /* Loading */;
823
- const mapData = await fs.readFile(this.sv.name);
961
+ this.sv.name = mapName;
962
+ const mapData = await fs.readFile(mapName);
824
963
  const arrayBuffer = mapData.buffer.slice(mapData.byteOffset, mapData.byteOffset + mapData.byteLength);
825
964
  const bspMap = parseBsp(arrayBuffer);
826
965
  const planes = bspMap.planes.map((p) => {
@@ -897,7 +1036,10 @@ var DedicatedServer = class {
897
1036
  console.log(`Map loaded successfully.`);
898
1037
  } catch (e) {
899
1038
  console.warn("Failed to load map:", e);
1039
+ if (this.onServerError) this.onServerError(e);
900
1040
  }
1041
+ }
1042
+ initGame() {
901
1043
  this.sv.startTime = Date.now();
902
1044
  const imports = {
903
1045
  trace: (start, mins, maxs, end, passent, contentmask) => {
@@ -954,7 +1096,6 @@ var DedicatedServer = class {
954
1096
  };
955
1097
  },
956
1098
  pointcontents: (p) => 0,
957
- // Empty
958
1099
  linkentity: (ent) => {
959
1100
  if (!this.entityIndex) return;
960
1101
  this.entityIndex.link({
@@ -963,7 +1104,6 @@ var DedicatedServer = class {
963
1104
  mins: ent.mins,
964
1105
  maxs: ent.maxs,
965
1106
  contents: ent.solid === 0 ? 0 : 1,
966
- // Simplified contents
967
1107
  surfaceFlags: 0
968
1108
  });
969
1109
  },
@@ -981,14 +1121,12 @@ var DedicatedServer = class {
981
1121
  };
982
1122
  this.game = createGame(imports, this, {
983
1123
  gravity: { x: 0, y: 0, z: -800 },
984
- deathmatch: true
1124
+ deathmatch: this.options.deathmatch !== false
985
1125
  });
986
1126
  this.game.init(0);
987
1127
  this.game.spawnWorld();
988
1128
  this.populateBaselines();
989
1129
  this.sv.state = 2 /* Game */;
990
- this.runFrame();
991
- console.log("Server started.");
992
1130
  }
993
1131
  populateBaselines() {
994
1132
  if (!this.game) return;
@@ -1011,21 +1149,18 @@ var DedicatedServer = class {
1011
1149
  renderfx: ent.renderfx,
1012
1150
  solid: ent.solid,
1013
1151
  sound: ent.sounds,
1014
- // Assuming ent.sounds maps to 'sound' field in EntityState
1015
1152
  event: 0
1016
1153
  };
1017
1154
  }
1018
1155
  stop() {
1019
1156
  if (this.frameTimeout) clearTimeout(this.frameTimeout);
1020
- if (this.wss) this.wss.close();
1157
+ this.transport.close();
1021
1158
  this.game?.shutdown();
1022
1159
  this.sv.state = 0 /* Dead */;
1023
1160
  }
1024
- handleConnection(ws) {
1025
- const driver = new WebSocketNetDriver();
1026
- driver.attach(ws);
1161
+ handleConnection(driver, info) {
1027
1162
  let clientIndex = -1;
1028
- for (let i = 0; i < MAX_CLIENTS; i++) {
1163
+ for (let i = 0; i < this.options.maxPlayers; i++) {
1029
1164
  if (this.svs.clients[i] === null || this.svs.clients[i].state === 0 /* Free */) {
1030
1165
  clientIndex = i;
1031
1166
  break;
@@ -1033,14 +1168,14 @@ var DedicatedServer = class {
1033
1168
  }
1034
1169
  if (clientIndex === -1) {
1035
1170
  console.log("Server full, rejecting connection");
1036
- ws.close();
1171
+ driver.disconnect();
1037
1172
  return;
1038
1173
  }
1039
1174
  const client = createClient(clientIndex, driver);
1040
1175
  client.lastMessage = this.sv.frame;
1041
1176
  client.lastCommandTime = Date.now();
1042
1177
  this.svs.clients[clientIndex] = client;
1043
- console.log(`Client ${clientIndex} attached to slot from ${ws._socket?.remoteAddress || "unknown"}`);
1178
+ console.log(`Client ${clientIndex} attached to slot from ${info?.socket?.remoteAddress || "unknown"}`);
1044
1179
  driver.onMessage((data) => this.onClientMessage(client, data));
1045
1180
  driver.onClose(() => this.onClientDisconnect(client));
1046
1181
  }
@@ -1053,6 +1188,9 @@ var DedicatedServer = class {
1053
1188
  if (client.edict && this.game) {
1054
1189
  this.game.clientDisconnect(client.edict);
1055
1190
  }
1191
+ if (this.onClientDisconnected) {
1192
+ this.onClientDisconnected(client.index);
1193
+ }
1056
1194
  client.state = 0 /* Free */;
1057
1195
  this.svs.clients[client.index] = null;
1058
1196
  if (this.entityIndex && client.edict) {
@@ -1101,7 +1239,7 @@ var DedicatedServer = class {
1101
1239
  }
1102
1240
  let status = `map: ${this.sv.name}
1103
1241
  `;
1104
- status += `players: ${activeClients} active (${MAX_CLIENTS} max)
1242
+ status += `players: ${activeClients} active (${this.options.maxPlayers} max)
1105
1243
 
1106
1244
  `;
1107
1245
  status += `num score ping name lastmsg address qport rate
@@ -1142,6 +1280,9 @@ var DedicatedServer = class {
1142
1280
  client.state = 2 /* Connected */;
1143
1281
  client.userInfo = userInfo;
1144
1282
  console.log(`Client ${client.index} connected: ${userInfo}`);
1283
+ if (this.onClientConnected) {
1284
+ this.onClientConnected(client.index, "Player");
1285
+ }
1145
1286
  try {
1146
1287
  this.sendServerData(client);
1147
1288
  client.netchan.writeReliableByte(ServerCommand2.stufftext);
@@ -1361,10 +1502,8 @@ var DedicatedServer = class {
1361
1502
  pm_time: snapshot.pm_time,
1362
1503
  pm_flags: snapshot.pmFlags,
1363
1504
  gravity: Math.abs(snapshot.gravity.z),
1364
- // Usually only Z is relevant for gravity value
1365
1505
  delta_angles: snapshot.deltaAngles,
1366
1506
  viewoffset: { x: 0, y: 0, z: 22 },
1367
- // Default view offset if not in snapshot
1368
1507
  viewangles: snapshot.viewangles,
1369
1508
  kick_angles: snapshot.kick_angles,
1370
1509
  gun_index: snapshot.gunindex,
@@ -1533,7 +1672,6 @@ var DedicatedServer = class {
1533
1672
  }
1534
1673
  if (i < 0) {
1535
1674
  i = 0;
1536
- } else if (i >= hist.length - 1) {
1537
1675
  }
1538
1676
  const s1 = hist[i];
1539
1677
  const s2 = i + 1 < hist.length ? hist[i + 1] : s1;
@@ -1559,7 +1697,6 @@ var DedicatedServer = class {
1559
1697
  maxs: { ...ent.maxs },
1560
1698
  angles: { ...ent.angles },
1561
1699
  link: true
1562
- // assume linked if we are tracking it
1563
1700
  });
1564
1701
  ent.origin = origin;
1565
1702
  ent.angles = angles;
@@ -1579,7 +1716,6 @@ var DedicatedServer = class {
1579
1716
  mins: ent.mins,
1580
1717
  maxs: ent.maxs,
1581
1718
  contents: ent.solid === 0 ? 0 : 1,
1582
- // Simplified
1583
1719
  surfaceFlags: 0
1584
1720
  });
1585
1721
  });
@@ -1605,10 +1741,14 @@ var DedicatedServer = class {
1605
1741
  }
1606
1742
  }
1607
1743
  };
1744
+ function createServer(options = {}) {
1745
+ return new DedicatedServer(options);
1746
+ }
1608
1747
  export {
1609
1748
  ClientMessageParser,
1610
1749
  ClientState,
1611
1750
  DedicatedServer,
1612
1751
  WebSocketNetDriver,
1613
- createClient
1752
+ createClient,
1753
+ createServer
1614
1754
  };