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.
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +16 -16
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +130 -2
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +130 -2
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/chat.d.ts +20 -0
- package/packages/client/dist/types/chat.d.ts.map +1 -0
- package/packages/client/dist/types/chat.test.d.ts +2 -0
- package/packages/client/dist/types/chat.test.d.ts.map +1 -0
- package/packages/client/dist/types/demo/handler.d.ts +1 -0
- package/packages/client/dist/types/demo/handler.d.ts.map +1 -1
- package/packages/client/dist/types/index.d.ts +3 -0
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/client/dist/types/scoreboard.d.ts +17 -0
- package/packages/client/dist/types/scoreboard.d.ts.map +1 -1
- package/packages/client/dist/types/scoreboard.test.d.ts +2 -0
- package/packages/client/dist/types/scoreboard.test.d.ts.map +1 -0
- package/packages/game/dist/browser/index.global.js +4 -4
- package/packages/game/dist/browser/index.global.js.map +1 -1
- package/packages/game/dist/cjs/index.cjs +130 -73
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +129 -73
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/ai/noise.d.ts +1 -0
- package/packages/game/dist/types/ai/noise.d.ts.map +1 -1
- package/packages/game/dist/types/ai/targeting.d.ts.map +1 -1
- package/packages/game/dist/types/inventory/playerInventory.d.ts +8 -0
- package/packages/game/dist/types/inventory/playerInventory.d.ts.map +1 -1
- package/packages/game/dist/types/physics/fluid.d.ts +5 -0
- package/packages/game/dist/types/physics/fluid.d.ts.map +1 -1
- package/packages/game/dist/types/physics/movement.d.ts.map +1 -1
- package/packages/server/dist/index.cjs +175 -34
- package/packages/server/dist/index.d.cts +37 -5
- package/packages/server/dist/index.d.ts +37 -5
- 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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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 ${
|
|
959
|
+
console.log(`Loading map ${mapName}...`);
|
|
822
960
|
this.sv.state = 1 /* Loading */;
|
|
823
|
-
|
|
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:
|
|
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
|
-
|
|
1157
|
+
this.transport.close();
|
|
1021
1158
|
this.game?.shutdown();
|
|
1022
1159
|
this.sv.state = 0 /* Dead */;
|
|
1023
1160
|
}
|
|
1024
|
-
handleConnection(
|
|
1025
|
-
const driver = new WebSocketNetDriver();
|
|
1026
|
-
driver.attach(ws);
|
|
1161
|
+
handleConnection(driver, info) {
|
|
1027
1162
|
let clientIndex = -1;
|
|
1028
|
-
for (let i = 0; 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
|
-
|
|
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 ${
|
|
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 (${
|
|
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
|
};
|