quake2ts 0.0.561 → 0.0.563

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 +15 -15
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +273 -1
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +273 -1
  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/net/connection.d.ts +2 -0
  10. package/packages/client/dist/types/net/connection.d.ts.map +1 -1
  11. package/packages/server/dist/client.d.ts +51 -0
  12. package/packages/server/dist/client.js +100 -0
  13. package/packages/server/dist/dedicated.d.ts +69 -0
  14. package/packages/server/dist/dedicated.js +1013 -0
  15. package/packages/server/dist/index.cjs +27 -2
  16. package/packages/server/dist/index.d.ts +7 -161
  17. package/packages/server/dist/index.js +26 -2
  18. package/packages/server/dist/net/nodeWsDriver.d.ts +16 -0
  19. package/packages/server/dist/net/nodeWsDriver.js +122 -0
  20. package/packages/server/dist/protocol/player.d.ts +23 -0
  21. package/packages/server/dist/protocol/player.js +137 -0
  22. package/packages/server/dist/protocol/write.d.ts +7 -0
  23. package/packages/server/dist/protocol/write.js +167 -0
  24. package/packages/server/dist/protocol.d.ts +17 -0
  25. package/packages/server/dist/protocol.js +71 -0
  26. package/packages/server/dist/server.d.ts +50 -0
  27. package/packages/server/dist/server.js +12 -0
  28. package/packages/server/dist/server.test.d.ts +1 -0
  29. package/packages/server/dist/server.test.js +69 -0
  30. package/packages/server/dist/transport.d.ts +7 -0
  31. package/packages/server/dist/transport.js +1 -0
  32. package/packages/server/dist/transports/websocket.d.ts +11 -0
  33. package/packages/server/dist/transports/websocket.js +38 -0
  34. package/packages/test-utils/dist/index.cjs +1610 -1188
  35. package/packages/test-utils/dist/index.cjs.map +1 -1
  36. package/packages/test-utils/dist/index.d.cts +326 -132
  37. package/packages/test-utils/dist/index.d.ts +326 -132
  38. package/packages/test-utils/dist/index.js +1596 -1189
  39. package/packages/test-utils/dist/index.js.map +1 -1
  40. package/packages/server/dist/index.d.cts +0 -161
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  ClientMessageParser: () => ClientMessageParser,
34
34
  ClientState: () => ClientState,
35
35
  DedicatedServer: () => DedicatedServer,
36
+ ServerState: () => ServerState,
36
37
  WebSocketNetDriver: () => WebSocketNetDriver,
37
38
  createClient: () => createClient,
38
39
  createServer: () => createServer
@@ -331,6 +332,19 @@ var import_shared4 = require("@quake2ts/shared");
331
332
  var import_engine = require("@quake2ts/engine");
332
333
  var import_promises = __toESM(require("fs/promises"), 1);
333
334
  var import_game2 = require("@quake2ts/game");
335
+
336
+ // src/server.ts
337
+ var ServerState = /* @__PURE__ */ ((ServerState2) => {
338
+ ServerState2[ServerState2["Dead"] = 0] = "Dead";
339
+ ServerState2[ServerState2["Loading"] = 1] = "Loading";
340
+ ServerState2[ServerState2["Game"] = 2] = "Game";
341
+ ServerState2[ServerState2["Cinematic"] = 3] = "Cinematic";
342
+ ServerState2[ServerState2["Demo"] = 4] = "Demo";
343
+ ServerState2[ServerState2["Pic"] = 5] = "Pic";
344
+ return ServerState2;
345
+ })(ServerState || {});
346
+
347
+ // src/dedicated.ts
334
348
  var import_shared5 = require("@quake2ts/shared");
335
349
 
336
350
  // src/protocol/player.ts
@@ -1003,7 +1017,11 @@ var DedicatedServer = class {
1003
1017
  }
1004
1018
  onClientMessage(client, data) {
1005
1019
  const buffer = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer : data.slice().buffer;
1006
- client.messageQueue.push(new Uint8Array(buffer));
1020
+ if (buffer instanceof ArrayBuffer) {
1021
+ client.messageQueue.push(new Uint8Array(buffer));
1022
+ } else {
1023
+ client.messageQueue.push(new Uint8Array(buffer));
1024
+ }
1007
1025
  }
1008
1026
  onClientDisconnect(client) {
1009
1027
  console.log(`Client ${client.index} disconnected`);
@@ -1229,7 +1247,13 @@ var DedicatedServer = class {
1229
1247
  if (data.length === 0) {
1230
1248
  continue;
1231
1249
  }
1232
- const reader = new import_shared4.BinaryStream(data.buffer);
1250
+ let buffer;
1251
+ if (data.buffer instanceof ArrayBuffer) {
1252
+ buffer = data.buffer;
1253
+ } else {
1254
+ buffer = new Uint8Array(data).buffer;
1255
+ }
1256
+ const reader = new import_shared4.BinaryStream(buffer);
1233
1257
  const parser = new ClientMessageParser(reader, {
1234
1258
  onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd, checksum, lastFrame),
1235
1259
  onUserInfo: (info) => this.handleUserInfo(client, info),
@@ -1573,6 +1597,7 @@ function createServer(options = {}) {
1573
1597
  ClientMessageParser,
1574
1598
  ClientState,
1575
1599
  DedicatedServer,
1600
+ ServerState,
1576
1601
  WebSocketNetDriver,
1577
1602
  createClient,
1578
1603
  createServer
@@ -1,161 +1,7 @@
1
- import { NetDriver, ServerCommand, PlayerState, EntityState, NetChan, UserCommand, BinaryStream } from '@quake2ts/shared';
2
- import WebSocket from 'ws';
3
- import { GameEngine, MulticastType, Entity } from '@quake2ts/game';
4
-
5
- declare class WebSocketNetDriver implements NetDriver {
6
- private socket;
7
- private messageCallback;
8
- private closeCallback;
9
- private errorCallback;
10
- connect(url: string): Promise<void>;
11
- attach(socket: WebSocket): void;
12
- disconnect(): void;
13
- send(data: Uint8Array): void;
14
- onMessage(callback: (data: Uint8Array) => void): void;
15
- onClose(callback: () => void): void;
16
- onError(callback: (error: Error) => void): void;
17
- isConnected(): boolean;
18
- }
19
-
20
- interface NetworkTransport {
21
- listen(port: number): Promise<void>;
22
- close(): void;
23
- onConnection(callback: (driver: NetDriver, info?: any) => void): void;
24
- onError(callback: (error: Error) => void): void;
25
- }
26
-
27
- interface ClientInfo {
28
- id: number;
29
- name: string;
30
- ping: number;
31
- address: string;
32
- }
33
- interface ServerOptions {
34
- mapName?: string;
35
- maxPlayers?: number;
36
- deathmatch?: boolean;
37
- port?: number;
38
- transport?: NetworkTransport;
39
- }
40
- declare class DedicatedServer implements GameEngine {
41
- private transport;
42
- private svs;
43
- private sv;
44
- private game;
45
- private frameTimeout;
46
- private entityIndex;
47
- private history;
48
- private backup;
49
- onClientConnected?: (clientId: number, name: string) => void;
50
- onClientDisconnected?: (clientId: number) => void;
51
- onServerError?: (error: Error) => void;
52
- private options;
53
- constructor(optionsOrPort?: ServerOptions | number);
54
- setTransport(transport: NetworkTransport): void;
55
- startServer(mapName?: string): Promise<void>;
56
- stopServer(): void;
57
- kickPlayer(clientId: number): void;
58
- changeMap(mapName: string): Promise<void>;
59
- getConnectedClients(): ClientInfo[];
60
- private start;
61
- private loadMap;
62
- private initGame;
63
- private populateBaselines;
64
- private entityToState;
65
- stop(): void;
66
- private handleConnection;
67
- private onClientMessage;
68
- private onClientDisconnect;
69
- private dropClient;
70
- private handleMove;
71
- private handleUserInfo;
72
- private handleStringCmd;
73
- private handleStatus;
74
- private handleGetChallenge;
75
- private handleConnect;
76
- private handleBegin;
77
- private spawnClient;
78
- private sendServerData;
79
- private SV_SetConfigString;
80
- private SV_WriteConfigString;
81
- private SV_ReadPackets;
82
- private runFrame;
83
- private SV_SendClientMessages;
84
- private SV_SendClientFrame;
85
- trace(start: any, end: any): any;
86
- multicast(origin: any, type: MulticastType, event: ServerCommand, ...args: any[]): void;
87
- unicast(ent: Entity, reliable: boolean, event: ServerCommand, ...args: any[]): void;
88
- configstring(index: number, value: string): void;
89
- private recordHistory;
90
- setLagCompensation(active: boolean, client?: Entity, lagMs?: number): void;
91
- }
92
- declare function createServer(options?: ServerOptions): DedicatedServer;
93
-
94
- declare enum ClientState {
95
- Free = 0,
96
- Zombie = 1,// client has been disconnected, but don't reuse connection for a couple seconds
97
- Connected = 2,// has been assigned to a client_t, but not in game yet
98
- Spawned = 3,// client is fully in game
99
- Active = 4
100
- }
101
- interface ClientFrame {
102
- areaBytes: number;
103
- areaBits: Uint8Array;
104
- playerState: PlayerState;
105
- numEntities: number;
106
- firstEntity: number;
107
- sentTime: number;
108
- entities: EntityState[];
109
- packetCRC: number;
110
- }
111
- interface Client {
112
- index: number;
113
- state: ClientState;
114
- net: NetDriver;
115
- netchan: NetChan;
116
- userInfo: string;
117
- lastFrame: number;
118
- lastCmd: UserCommand;
119
- commandMsec: number;
120
- frameLatency: number[];
121
- ping: number;
122
- messageSize: number[];
123
- rate: number;
124
- suppressCount: number;
125
- edict: Entity | null;
126
- name: string;
127
- messageLevel: number;
128
- datagram: Uint8Array;
129
- frames: ClientFrame[];
130
- download?: Uint8Array;
131
- downloadSize: number;
132
- downloadCount: number;
133
- lastMessage: number;
134
- lastConnect: number;
135
- challenge: number;
136
- messageQueue: Uint8Array[];
137
- lastPacketEntities: number[];
138
- commandQueue: UserCommand[];
139
- lastCommandTime: number;
140
- commandCount: number;
141
- }
142
- declare function createClient(index: number, net: NetDriver): Client;
143
-
144
- interface ClientMessageHandler {
145
- onMove(checksum: number, lastFrame: number, userCmd: UserCommand): void;
146
- onUserInfo(info: string): void;
147
- onStringCmd(cmd: string): void;
148
- onNop(): void;
149
- onBad(): void;
150
- }
151
- declare class ClientMessageParser {
152
- private stream;
153
- private handler;
154
- constructor(stream: BinaryStream, handler: ClientMessageHandler);
155
- parseMessage(): void;
156
- private parseMove;
157
- private parseUserInfo;
158
- private parseStringCmd;
159
- }
160
-
161
- export { type Client, type ClientFrame, type ClientInfo, type ClientMessageHandler, ClientMessageParser, ClientState, DedicatedServer, type NetworkTransport, type ServerOptions, WebSocketNetDriver, createClient, createServer };
1
+ export * from './net/nodeWsDriver.js';
2
+ export * from './dedicated.js';
3
+ export * from './client.js';
4
+ export * from './protocol.js';
5
+ export { ServerOptions, createServer } from './dedicated.js';
6
+ export { NetworkTransport } from './transport.js';
7
+ export * from './server.js';
@@ -290,6 +290,19 @@ import { BinaryWriter as BinaryWriter2, ServerCommand as ServerCommand2, BinaryS
290
290
  import { parseBsp } from "@quake2ts/engine";
291
291
  import fs from "fs/promises";
292
292
  import { createPlayerInventory, createPlayerWeaponStates } from "@quake2ts/game";
293
+
294
+ // src/server.ts
295
+ var ServerState = /* @__PURE__ */ ((ServerState2) => {
296
+ ServerState2[ServerState2["Dead"] = 0] = "Dead";
297
+ ServerState2[ServerState2["Loading"] = 1] = "Loading";
298
+ ServerState2[ServerState2["Game"] = 2] = "Game";
299
+ ServerState2[ServerState2["Cinematic"] = 3] = "Cinematic";
300
+ ServerState2[ServerState2["Demo"] = 4] = "Demo";
301
+ ServerState2[ServerState2["Pic"] = 5] = "Pic";
302
+ return ServerState2;
303
+ })(ServerState || {});
304
+
305
+ // src/dedicated.ts
293
306
  import { writeDeltaEntity, writeRemoveEntity } from "@quake2ts/shared";
294
307
 
295
308
  // src/protocol/player.ts
@@ -962,7 +975,11 @@ var DedicatedServer = class {
962
975
  }
963
976
  onClientMessage(client, data) {
964
977
  const buffer = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer : data.slice().buffer;
965
- client.messageQueue.push(new Uint8Array(buffer));
978
+ if (buffer instanceof ArrayBuffer) {
979
+ client.messageQueue.push(new Uint8Array(buffer));
980
+ } else {
981
+ client.messageQueue.push(new Uint8Array(buffer));
982
+ }
966
983
  }
967
984
  onClientDisconnect(client) {
968
985
  console.log(`Client ${client.index} disconnected`);
@@ -1188,7 +1205,13 @@ var DedicatedServer = class {
1188
1205
  if (data.length === 0) {
1189
1206
  continue;
1190
1207
  }
1191
- const reader = new BinaryStream2(data.buffer);
1208
+ let buffer;
1209
+ if (data.buffer instanceof ArrayBuffer) {
1210
+ buffer = data.buffer;
1211
+ } else {
1212
+ buffer = new Uint8Array(data).buffer;
1213
+ }
1214
+ const reader = new BinaryStream2(buffer);
1192
1215
  const parser = new ClientMessageParser(reader, {
1193
1216
  onMove: (checksum, lastFrame, cmd) => this.handleMove(client, cmd, checksum, lastFrame),
1194
1217
  onUserInfo: (info) => this.handleUserInfo(client, info),
@@ -1531,6 +1554,7 @@ export {
1531
1554
  ClientMessageParser,
1532
1555
  ClientState,
1533
1556
  DedicatedServer,
1557
+ ServerState,
1534
1558
  WebSocketNetDriver,
1535
1559
  createClient,
1536
1560
  createServer
@@ -0,0 +1,16 @@
1
+ import { NetDriver } from '@quake2ts/shared';
2
+ import WebSocket from 'ws';
3
+ export declare class WebSocketNetDriver implements NetDriver {
4
+ private socket;
5
+ private messageCallback;
6
+ private closeCallback;
7
+ private errorCallback;
8
+ connect(url: string): Promise<void>;
9
+ attach(socket: WebSocket): void;
10
+ disconnect(): void;
11
+ send(data: Uint8Array): void;
12
+ onMessage(callback: (data: Uint8Array) => void): void;
13
+ onClose(callback: () => void): void;
14
+ onError(callback: (error: Error) => void): void;
15
+ isConnected(): boolean;
16
+ }
@@ -0,0 +1,122 @@
1
+ import WebSocket from 'ws';
2
+ export class WebSocketNetDriver {
3
+ constructor() {
4
+ this.socket = null;
5
+ this.messageCallback = null;
6
+ this.closeCallback = null;
7
+ this.errorCallback = null;
8
+ }
9
+ async connect(url) {
10
+ return new Promise((resolve, reject) => {
11
+ try {
12
+ this.socket = new WebSocket(url);
13
+ this.socket.binaryType = 'arraybuffer';
14
+ this.socket.onopen = () => {
15
+ resolve();
16
+ };
17
+ this.socket.onerror = (event) => {
18
+ const error = new Error('WebSocket connection error ' + event.message);
19
+ if (this.errorCallback) {
20
+ this.errorCallback(error);
21
+ }
22
+ reject(error);
23
+ };
24
+ this.socket.onclose = () => {
25
+ if (this.closeCallback) {
26
+ this.closeCallback();
27
+ }
28
+ this.socket = null;
29
+ };
30
+ this.socket.onmessage = (event) => {
31
+ if (this.messageCallback) {
32
+ if (event.data instanceof ArrayBuffer) {
33
+ this.messageCallback(new Uint8Array(event.data));
34
+ }
35
+ else if (Buffer.isBuffer(event.data)) {
36
+ // ws in Node might return Buffer
37
+ this.messageCallback(new Uint8Array(event.data));
38
+ }
39
+ else if (Array.isArray(event.data)) {
40
+ // Buffer[]
41
+ const totalLength = event.data.reduce((acc, buf) => acc + buf.length, 0);
42
+ const result = new Uint8Array(totalLength);
43
+ let offset = 0;
44
+ for (const buf of event.data) {
45
+ result.set(buf, offset);
46
+ offset += buf.length;
47
+ }
48
+ this.messageCallback(result);
49
+ }
50
+ else {
51
+ console.warn('Received non-binary message from server', typeof event.data);
52
+ }
53
+ }
54
+ };
55
+ }
56
+ catch (e) {
57
+ reject(e);
58
+ }
59
+ });
60
+ }
61
+ // Method to attach an existing socket (server-side incoming connection)
62
+ attach(socket) {
63
+ this.socket = socket;
64
+ this.socket.binaryType = 'arraybuffer';
65
+ this.socket.onclose = () => {
66
+ if (this.closeCallback)
67
+ this.closeCallback();
68
+ this.socket = null;
69
+ };
70
+ this.socket.onerror = (event) => {
71
+ if (this.errorCallback)
72
+ this.errorCallback(new Error(event.message));
73
+ };
74
+ this.socket.onmessage = (event) => {
75
+ if (this.messageCallback) {
76
+ if (event.data instanceof ArrayBuffer) {
77
+ this.messageCallback(new Uint8Array(event.data));
78
+ }
79
+ else if (Buffer.isBuffer(event.data)) {
80
+ this.messageCallback(new Uint8Array(event.data));
81
+ }
82
+ else if (Array.isArray(event.data)) { // ws specific
83
+ // handle fragmentation if necessary, usually it's Buffer[]
84
+ const totalLength = event.data.reduce((acc, buf) => acc + buf.length, 0);
85
+ const result = new Uint8Array(totalLength);
86
+ let offset = 0;
87
+ for (const buf of event.data) {
88
+ result.set(buf, offset);
89
+ offset += buf.length;
90
+ }
91
+ this.messageCallback(result);
92
+ }
93
+ }
94
+ };
95
+ }
96
+ disconnect() {
97
+ if (this.socket) {
98
+ this.socket.close();
99
+ this.socket = null;
100
+ }
101
+ }
102
+ send(data) {
103
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
104
+ this.socket.send(data);
105
+ }
106
+ else {
107
+ console.warn('Attempted to send data on closed or connecting socket');
108
+ }
109
+ }
110
+ onMessage(callback) {
111
+ this.messageCallback = callback;
112
+ }
113
+ onClose(callback) {
114
+ this.closeCallback = callback;
115
+ }
116
+ onError(callback) {
117
+ this.errorCallback = callback;
118
+ }
119
+ isConnected() {
120
+ return this.socket !== null && this.socket.readyState === WebSocket.OPEN;
121
+ }
122
+ }
@@ -0,0 +1,23 @@
1
+ import { BinaryWriter, Vec3 } from '@quake2ts/shared';
2
+ export interface ProtocolPlayerState {
3
+ pm_type: number;
4
+ origin: Vec3;
5
+ velocity: Vec3;
6
+ pm_time: number;
7
+ pm_flags: number;
8
+ gravity: number;
9
+ delta_angles: Vec3;
10
+ viewoffset: Vec3;
11
+ viewangles: Vec3;
12
+ kick_angles: Vec3;
13
+ gun_index: number;
14
+ gun_frame: number;
15
+ gun_offset: Vec3;
16
+ gun_angles: Vec3;
17
+ blend: number[];
18
+ fov: number;
19
+ rdflags: number;
20
+ stats: number[];
21
+ watertype: number;
22
+ }
23
+ export declare function writePlayerState(writer: BinaryWriter, ps: ProtocolPlayerState): void;
@@ -0,0 +1,137 @@
1
+ // Bitflags matching demo/parser.ts
2
+ const PS_M_TYPE = (1 << 0);
3
+ const PS_M_ORIGIN = (1 << 1);
4
+ const PS_M_VELOCITY = (1 << 2);
5
+ const PS_M_TIME = (1 << 3);
6
+ const PS_M_FLAGS = (1 << 4);
7
+ const PS_M_GRAVITY = (1 << 5);
8
+ const PS_M_DELTA_ANGLES = (1 << 6);
9
+ const PS_VIEWOFFSET = (1 << 7);
10
+ const PS_VIEWANGLES = (1 << 8);
11
+ const PS_KICKANGLES = (1 << 9);
12
+ const PS_BLEND = (1 << 10);
13
+ const PS_FOV = (1 << 11);
14
+ const PS_WEAPONINDEX = (1 << 12);
15
+ const PS_WEAPONFRAME = (1 << 13);
16
+ const PS_RDFLAGS = (1 << 14);
17
+ const PS_WATERTYPE = (1 << 15);
18
+ export function writePlayerState(writer, ps) {
19
+ // Determine mask
20
+ let mask = 0;
21
+ if (ps.pm_type !== 0)
22
+ mask |= PS_M_TYPE;
23
+ if (ps.origin.x !== 0 || ps.origin.y !== 0 || ps.origin.z !== 0)
24
+ mask |= PS_M_ORIGIN;
25
+ if (ps.velocity.x !== 0 || ps.velocity.y !== 0 || ps.velocity.z !== 0)
26
+ mask |= PS_M_VELOCITY;
27
+ if (ps.pm_time !== 0)
28
+ mask |= PS_M_TIME;
29
+ if (ps.pm_flags !== 0)
30
+ mask |= PS_M_FLAGS;
31
+ if (ps.gravity !== 0)
32
+ mask |= PS_M_GRAVITY;
33
+ if (ps.delta_angles.x !== 0 || ps.delta_angles.y !== 0 || ps.delta_angles.z !== 0)
34
+ mask |= PS_M_DELTA_ANGLES;
35
+ if (ps.viewoffset.x !== 0 || ps.viewoffset.y !== 0 || ps.viewoffset.z !== 0)
36
+ mask |= PS_VIEWOFFSET;
37
+ if (ps.viewangles.x !== 0 || ps.viewangles.y !== 0 || ps.viewangles.z !== 0)
38
+ mask |= PS_VIEWANGLES;
39
+ if (ps.kick_angles.x !== 0 || ps.kick_angles.y !== 0 || ps.kick_angles.z !== 0)
40
+ mask |= PS_KICKANGLES;
41
+ if (ps.gun_index !== 0)
42
+ mask |= PS_WEAPONINDEX;
43
+ // Weapon frame includes offset/angles
44
+ if (ps.gun_frame !== 0 ||
45
+ ps.gun_offset.x !== 0 || ps.gun_offset.y !== 0 || ps.gun_offset.z !== 0 ||
46
+ ps.gun_angles.x !== 0 || ps.gun_angles.y !== 0 || ps.gun_angles.z !== 0) {
47
+ mask |= PS_WEAPONFRAME;
48
+ }
49
+ if (ps.blend && (ps.blend[0] !== 0 || ps.blend[1] !== 0 || ps.blend[2] !== 0 || ps.blend[3] !== 0)) {
50
+ mask |= PS_BLEND;
51
+ }
52
+ if (ps.fov !== 0)
53
+ mask |= PS_FOV;
54
+ if (ps.rdflags !== 0)
55
+ mask |= PS_RDFLAGS;
56
+ if (ps.watertype !== 0)
57
+ mask |= PS_WATERTYPE;
58
+ // Stats mask calculation
59
+ let statsMask = 0;
60
+ // Only support first 32 stats for now
61
+ for (let i = 0; i < 32; i++) {
62
+ if (ps.stats[i] && ps.stats[i] !== 0) {
63
+ statsMask |= (1 << i);
64
+ }
65
+ }
66
+ // Write header
67
+ writer.writeShort(mask);
68
+ // Write fields
69
+ if (mask & PS_M_TYPE)
70
+ writer.writeByte(ps.pm_type);
71
+ if (mask & PS_M_ORIGIN) {
72
+ writer.writeShort(Math.round(ps.origin.x * 8));
73
+ writer.writeShort(Math.round(ps.origin.y * 8));
74
+ writer.writeShort(Math.round(ps.origin.z * 8));
75
+ }
76
+ if (mask & PS_M_VELOCITY) {
77
+ writer.writeShort(Math.round(ps.velocity.x * 8));
78
+ writer.writeShort(Math.round(ps.velocity.y * 8));
79
+ writer.writeShort(Math.round(ps.velocity.z * 8));
80
+ }
81
+ if (mask & PS_M_TIME)
82
+ writer.writeByte(ps.pm_time);
83
+ if (mask & PS_M_FLAGS)
84
+ writer.writeByte(ps.pm_flags);
85
+ if (mask & PS_M_GRAVITY)
86
+ writer.writeShort(ps.gravity);
87
+ if (mask & PS_M_DELTA_ANGLES) {
88
+ writer.writeShort(Math.round(ps.delta_angles.x * (32768 / 180)));
89
+ writer.writeShort(Math.round(ps.delta_angles.y * (32768 / 180)));
90
+ writer.writeShort(Math.round(ps.delta_angles.z * (32768 / 180)));
91
+ }
92
+ if (mask & PS_VIEWOFFSET) {
93
+ writer.writeChar(Math.round(ps.viewoffset.x * 4));
94
+ writer.writeChar(Math.round(ps.viewoffset.y * 4));
95
+ writer.writeChar(Math.round(ps.viewoffset.z * 4));
96
+ }
97
+ if (mask & PS_VIEWANGLES) {
98
+ writer.writeAngle16(ps.viewangles.x);
99
+ writer.writeAngle16(ps.viewangles.y);
100
+ writer.writeAngle16(ps.viewangles.z);
101
+ }
102
+ if (mask & PS_KICKANGLES) {
103
+ writer.writeChar(Math.round(ps.kick_angles.x * 4));
104
+ writer.writeChar(Math.round(ps.kick_angles.y * 4));
105
+ writer.writeChar(Math.round(ps.kick_angles.z * 4));
106
+ }
107
+ if (mask & PS_WEAPONINDEX)
108
+ writer.writeByte(ps.gun_index);
109
+ if (mask & PS_WEAPONFRAME) {
110
+ writer.writeByte(ps.gun_frame);
111
+ writer.writeChar(Math.round(ps.gun_offset.x * 4));
112
+ writer.writeChar(Math.round(ps.gun_offset.y * 4));
113
+ writer.writeChar(Math.round(ps.gun_offset.z * 4));
114
+ writer.writeChar(Math.round(ps.gun_angles.x * 4));
115
+ writer.writeChar(Math.round(ps.gun_angles.y * 4));
116
+ writer.writeChar(Math.round(ps.gun_angles.z * 4));
117
+ }
118
+ if (mask & PS_BLEND) {
119
+ writer.writeByte(Math.round(ps.blend[0]));
120
+ writer.writeByte(Math.round(ps.blend[1]));
121
+ writer.writeByte(Math.round(ps.blend[2]));
122
+ writer.writeByte(Math.round(ps.blend[3]));
123
+ }
124
+ if (mask & PS_FOV)
125
+ writer.writeByte(ps.fov);
126
+ if (mask & PS_RDFLAGS)
127
+ writer.writeByte(ps.rdflags);
128
+ if (mask & PS_WATERTYPE)
129
+ writer.writeByte(ps.watertype);
130
+ // Write Stats
131
+ writer.writeLong(statsMask);
132
+ for (let i = 0; i < 32; i++) {
133
+ if (statsMask & (1 << i)) {
134
+ writer.writeShort(ps.stats[i]);
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,7 @@
1
+ import { BinaryWriter, ServerCommand } from '@quake2ts/shared';
2
+ /**
3
+ * Writes a server command and its arguments to a BinaryWriter.
4
+ * This handles the serialization of generic arguments passed to multicast/unicast
5
+ * into the specific binary format expected by the protocol.
6
+ */
7
+ export declare function writeServerCommand(writer: BinaryWriter, event: ServerCommand, ...args: any[]): void;