quake2ts 0.0.561 → 0.0.562
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 +15 -15
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +273 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +273 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/net/connection.d.ts +2 -0
- package/packages/client/dist/types/net/connection.d.ts.map +1 -1
- package/packages/server/dist/client.d.ts +51 -0
- package/packages/server/dist/client.js +100 -0
- package/packages/server/dist/dedicated.d.ts +69 -0
- package/packages/server/dist/dedicated.js +1013 -0
- package/packages/server/dist/index.cjs +27 -2
- package/packages/server/dist/index.d.ts +7 -161
- package/packages/server/dist/index.js +26 -2
- package/packages/server/dist/net/nodeWsDriver.d.ts +16 -0
- package/packages/server/dist/net/nodeWsDriver.js +122 -0
- package/packages/server/dist/protocol/player.d.ts +23 -0
- package/packages/server/dist/protocol/player.js +137 -0
- package/packages/server/dist/protocol/write.d.ts +7 -0
- package/packages/server/dist/protocol/write.js +167 -0
- package/packages/server/dist/protocol.d.ts +17 -0
- package/packages/server/dist/protocol.js +71 -0
- package/packages/server/dist/server.d.ts +50 -0
- package/packages/server/dist/server.js +12 -0
- package/packages/server/dist/server.test.d.ts +1 -0
- package/packages/server/dist/server.test.js +69 -0
- package/packages/server/dist/transport.d.ts +7 -0
- package/packages/server/dist/transport.js +1 -0
- package/packages/server/dist/transports/websocket.d.ts +11 -0
- package/packages/server/dist/transports/websocket.js +38 -0
- package/packages/test-utils/dist/index.cjs +1422 -1192
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +268 -131
- package/packages/test-utils/dist/index.d.ts +268 -131
- package/packages/test-utils/dist/index.js +1415 -1194
- package/packages/test-utils/dist/index.js.map +1 -1
- package/packages/server/dist/index.d.cts +0 -161
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
InputInjector: () => InputInjector,
|
|
34
34
|
MockPointerLock: () => MockPointerLock,
|
|
35
|
+
MockTransport: () => MockTransport,
|
|
35
36
|
captureAudioEvents: () => captureAudioEvents,
|
|
36
37
|
captureCanvasDrawCalls: () => captureCanvasDrawCalls,
|
|
37
38
|
captureGameState: () => captureGameState,
|
|
@@ -46,13 +47,21 @@ __export(index_exports, {
|
|
|
46
47
|
createMockCanvasContext2D: () => createMockCanvasContext2D,
|
|
47
48
|
createMockEngine: () => createMockEngine,
|
|
48
49
|
createMockGame: () => createMockGame,
|
|
50
|
+
createMockGameState: () => createMockGameState,
|
|
49
51
|
createMockImage: () => createMockImage,
|
|
50
52
|
createMockImageData: () => createMockImageData,
|
|
51
53
|
createMockIndexedDB: () => createMockIndexedDB,
|
|
52
54
|
createMockLocalStorage: () => createMockLocalStorage,
|
|
55
|
+
createMockNetworkAddress: () => createMockNetworkAddress,
|
|
53
56
|
createMockPerformance: () => createMockPerformance,
|
|
54
57
|
createMockRAF: () => createMockRAF,
|
|
58
|
+
createMockServer: () => createMockServer,
|
|
59
|
+
createMockServerClient: () => createMockServerClient,
|
|
60
|
+
createMockServerState: () => createMockServerState,
|
|
61
|
+
createMockServerStatic: () => createMockServerStatic,
|
|
55
62
|
createMockSessionStorage: () => createMockSessionStorage,
|
|
63
|
+
createMockTransport: () => createMockTransport,
|
|
64
|
+
createMockUDPSocket: () => createMockUDPSocket,
|
|
56
65
|
createMockWebGL2Context: () => createMockWebGL2Context,
|
|
57
66
|
createNetChanMock: () => createNetChanMock,
|
|
58
67
|
createPlayerStateFactory: () => createPlayerStateFactory,
|
|
@@ -81,1284 +90,1497 @@ __export(index_exports, {
|
|
|
81
90
|
});
|
|
82
91
|
module.exports = __toCommonJS(index_exports);
|
|
83
92
|
|
|
84
|
-
// src/
|
|
85
|
-
var
|
|
86
|
-
var
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
cancelable: true,
|
|
165
|
-
view: this.win
|
|
166
|
-
});
|
|
167
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
168
|
-
target.dispatchEvent(event);
|
|
169
|
-
}
|
|
170
|
-
wheel(deltaY) {
|
|
171
|
-
const event = new this.win.WheelEvent("wheel", {
|
|
172
|
-
deltaY,
|
|
173
|
-
bubbles: true,
|
|
174
|
-
cancelable: true,
|
|
175
|
-
view: this.win
|
|
176
|
-
});
|
|
177
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
178
|
-
target.dispatchEvent(event);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
93
|
+
// src/shared/mocks.ts
|
|
94
|
+
var import_vitest = require("vitest");
|
|
95
|
+
var createBinaryWriterMock = () => ({
|
|
96
|
+
writeByte: import_vitest.vi.fn(),
|
|
97
|
+
writeShort: import_vitest.vi.fn(),
|
|
98
|
+
writeLong: import_vitest.vi.fn(),
|
|
99
|
+
writeString: import_vitest.vi.fn(),
|
|
100
|
+
writeBytes: import_vitest.vi.fn(),
|
|
101
|
+
getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
102
|
+
reset: import_vitest.vi.fn(),
|
|
103
|
+
// Legacy methods (if any)
|
|
104
|
+
writeInt8: import_vitest.vi.fn(),
|
|
105
|
+
writeUint8: import_vitest.vi.fn(),
|
|
106
|
+
writeInt16: import_vitest.vi.fn(),
|
|
107
|
+
writeUint16: import_vitest.vi.fn(),
|
|
108
|
+
writeInt32: import_vitest.vi.fn(),
|
|
109
|
+
writeUint32: import_vitest.vi.fn(),
|
|
110
|
+
writeFloat: import_vitest.vi.fn(),
|
|
111
|
+
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
112
|
+
});
|
|
113
|
+
var createNetChanMock = () => ({
|
|
114
|
+
qport: 1234,
|
|
115
|
+
// Sequencing
|
|
116
|
+
incomingSequence: 0,
|
|
117
|
+
outgoingSequence: 0,
|
|
118
|
+
incomingAcknowledged: 0,
|
|
119
|
+
// Reliable messaging
|
|
120
|
+
incomingReliableAcknowledged: false,
|
|
121
|
+
incomingReliableSequence: 0,
|
|
122
|
+
outgoingReliableSequence: 0,
|
|
123
|
+
reliableMessage: createBinaryWriterMock(),
|
|
124
|
+
reliableLength: 0,
|
|
125
|
+
// Fragmentation
|
|
126
|
+
fragmentSendOffset: 0,
|
|
127
|
+
fragmentBuffer: null,
|
|
128
|
+
fragmentLength: 0,
|
|
129
|
+
fragmentReceived: 0,
|
|
130
|
+
// Timing
|
|
131
|
+
lastReceived: 0,
|
|
132
|
+
lastSent: 0,
|
|
133
|
+
remoteAddress: { type: "IP", port: 1234 },
|
|
134
|
+
// Methods
|
|
135
|
+
setup: import_vitest.vi.fn(),
|
|
136
|
+
reset: import_vitest.vi.fn(),
|
|
137
|
+
transmit: import_vitest.vi.fn(),
|
|
138
|
+
process: import_vitest.vi.fn(),
|
|
139
|
+
canSendReliable: import_vitest.vi.fn(() => true),
|
|
140
|
+
writeReliableByte: import_vitest.vi.fn(),
|
|
141
|
+
writeReliableShort: import_vitest.vi.fn(),
|
|
142
|
+
writeReliableLong: import_vitest.vi.fn(),
|
|
143
|
+
writeReliableString: import_vitest.vi.fn(),
|
|
144
|
+
getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
145
|
+
needsKeepalive: import_vitest.vi.fn(() => false),
|
|
146
|
+
isTimedOut: import_vitest.vi.fn(() => false)
|
|
147
|
+
});
|
|
148
|
+
var createBinaryStreamMock = () => ({
|
|
149
|
+
getPosition: import_vitest.vi.fn(() => 0),
|
|
150
|
+
getReadPosition: import_vitest.vi.fn(() => 0),
|
|
151
|
+
getLength: import_vitest.vi.fn(() => 0),
|
|
152
|
+
getRemaining: import_vitest.vi.fn(() => 0),
|
|
153
|
+
seek: import_vitest.vi.fn(),
|
|
154
|
+
setReadPosition: import_vitest.vi.fn(),
|
|
155
|
+
hasMore: import_vitest.vi.fn(() => true),
|
|
156
|
+
hasBytes: import_vitest.vi.fn((amount) => true),
|
|
157
|
+
readChar: import_vitest.vi.fn(() => 0),
|
|
158
|
+
readByte: import_vitest.vi.fn(() => 0),
|
|
159
|
+
readShort: import_vitest.vi.fn(() => 0),
|
|
160
|
+
readUShort: import_vitest.vi.fn(() => 0),
|
|
161
|
+
readLong: import_vitest.vi.fn(() => 0),
|
|
162
|
+
readULong: import_vitest.vi.fn(() => 0),
|
|
163
|
+
readFloat: import_vitest.vi.fn(() => 0),
|
|
164
|
+
readString: import_vitest.vi.fn(() => ""),
|
|
165
|
+
readStringLine: import_vitest.vi.fn(() => ""),
|
|
166
|
+
readCoord: import_vitest.vi.fn(() => 0),
|
|
167
|
+
readAngle: import_vitest.vi.fn(() => 0),
|
|
168
|
+
readAngle16: import_vitest.vi.fn(() => 0),
|
|
169
|
+
readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
|
|
170
|
+
readPos: import_vitest.vi.fn(),
|
|
171
|
+
readDir: import_vitest.vi.fn()
|
|
172
|
+
});
|
|
181
173
|
|
|
182
|
-
// src/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
FRAGMENT_SHADER: 35632,
|
|
191
|
-
COMPILE_STATUS: 35713,
|
|
192
|
-
LINK_STATUS: 35714,
|
|
193
|
-
ARRAY_BUFFER: 34962,
|
|
194
|
-
ELEMENT_ARRAY_BUFFER: 34963,
|
|
195
|
-
STATIC_DRAW: 35044,
|
|
196
|
-
DYNAMIC_DRAW: 35048,
|
|
197
|
-
FLOAT: 5126,
|
|
198
|
-
DEPTH_TEST: 2929,
|
|
199
|
-
BLEND: 3042,
|
|
200
|
-
SRC_ALPHA: 770,
|
|
201
|
-
ONE_MINUS_SRC_ALPHA: 771,
|
|
202
|
-
TEXTURE_2D: 3553,
|
|
203
|
-
RGBA: 6408,
|
|
204
|
-
UNSIGNED_BYTE: 5121,
|
|
205
|
-
COLOR_BUFFER_BIT: 16384,
|
|
206
|
-
DEPTH_BUFFER_BIT: 256,
|
|
207
|
-
TRIANGLES: 4,
|
|
208
|
-
TRIANGLE_STRIP: 5,
|
|
209
|
-
TRIANGLE_FAN: 6,
|
|
210
|
-
// Methods
|
|
211
|
-
createShader: () => ({}),
|
|
212
|
-
shaderSource: () => {
|
|
213
|
-
},
|
|
214
|
-
compileShader: () => {
|
|
215
|
-
},
|
|
216
|
-
getShaderParameter: (_, param) => {
|
|
217
|
-
if (param === 35713) return true;
|
|
218
|
-
return true;
|
|
219
|
-
},
|
|
220
|
-
getShaderInfoLog: () => "",
|
|
221
|
-
createProgram: () => ({}),
|
|
222
|
-
attachShader: () => {
|
|
223
|
-
},
|
|
224
|
-
linkProgram: () => {
|
|
225
|
-
},
|
|
226
|
-
getProgramParameter: (_, param) => {
|
|
227
|
-
if (param === 35714) return true;
|
|
228
|
-
return true;
|
|
229
|
-
},
|
|
230
|
-
getProgramInfoLog: () => "",
|
|
231
|
-
useProgram: () => {
|
|
232
|
-
},
|
|
233
|
-
createBuffer: () => ({}),
|
|
234
|
-
bindBuffer: () => {
|
|
235
|
-
},
|
|
236
|
-
bufferData: () => {
|
|
237
|
-
},
|
|
238
|
-
enableVertexAttribArray: () => {
|
|
239
|
-
},
|
|
240
|
-
vertexAttribPointer: () => {
|
|
241
|
-
},
|
|
242
|
-
enable: () => {
|
|
243
|
-
},
|
|
244
|
-
disable: () => {
|
|
245
|
-
},
|
|
246
|
-
depthMask: () => {
|
|
247
|
-
},
|
|
248
|
-
blendFunc: () => {
|
|
249
|
-
},
|
|
250
|
-
viewport: () => {
|
|
251
|
-
},
|
|
252
|
-
clearColor: () => {
|
|
253
|
-
},
|
|
254
|
-
clear: () => {
|
|
255
|
-
},
|
|
256
|
-
createTexture: () => ({}),
|
|
257
|
-
bindTexture: () => {
|
|
258
|
-
},
|
|
259
|
-
texImage2D: () => {
|
|
260
|
-
},
|
|
261
|
-
texParameteri: () => {
|
|
262
|
-
},
|
|
263
|
-
activeTexture: () => {
|
|
264
|
-
},
|
|
265
|
-
uniform1i: () => {
|
|
266
|
-
},
|
|
267
|
-
uniform1f: () => {
|
|
268
|
-
},
|
|
269
|
-
uniform2f: () => {
|
|
270
|
-
},
|
|
271
|
-
uniform3f: () => {
|
|
272
|
-
},
|
|
273
|
-
uniform4f: () => {
|
|
274
|
-
},
|
|
275
|
-
uniformMatrix4fv: () => {
|
|
276
|
-
},
|
|
277
|
-
getUniformLocation: () => ({}),
|
|
278
|
-
getAttribLocation: () => 0,
|
|
279
|
-
drawArrays: () => {
|
|
280
|
-
},
|
|
281
|
-
drawElements: () => {
|
|
282
|
-
},
|
|
283
|
-
createVertexArray: () => ({}),
|
|
284
|
-
bindVertexArray: () => {
|
|
285
|
-
},
|
|
286
|
-
deleteShader: () => {
|
|
287
|
-
},
|
|
288
|
-
deleteProgram: () => {
|
|
289
|
-
},
|
|
290
|
-
deleteBuffer: () => {
|
|
291
|
-
},
|
|
292
|
-
deleteTexture: () => {
|
|
293
|
-
},
|
|
294
|
-
deleteVertexArray: () => {
|
|
295
|
-
},
|
|
296
|
-
// WebGL2 specific
|
|
297
|
-
texImage3D: () => {
|
|
298
|
-
},
|
|
299
|
-
uniformBlockBinding: () => {
|
|
300
|
-
},
|
|
301
|
-
getExtension: () => null
|
|
174
|
+
// src/shared/bsp.ts
|
|
175
|
+
var import_shared = require("@quake2ts/shared");
|
|
176
|
+
function makePlane(normal, dist) {
|
|
177
|
+
return {
|
|
178
|
+
normal,
|
|
179
|
+
dist,
|
|
180
|
+
type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
|
|
181
|
+
signbits: (0, import_shared.computePlaneSignBits)(normal)
|
|
302
182
|
};
|
|
303
|
-
return gl;
|
|
304
183
|
}
|
|
184
|
+
function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
|
|
185
|
+
const half = size / 2;
|
|
186
|
+
const planes = [
|
|
187
|
+
makePlane({ x: 1, y: 0, z: 0 }, half),
|
|
188
|
+
makePlane({ x: -1, y: 0, z: 0 }, half),
|
|
189
|
+
makePlane({ x: 0, y: 1, z: 0 }, half),
|
|
190
|
+
makePlane({ x: 0, y: -1, z: 0 }, half),
|
|
191
|
+
makePlane({ x: 0, y: 0, z: 1 }, half),
|
|
192
|
+
makePlane({ x: 0, y: 0, z: -1 }, half)
|
|
193
|
+
];
|
|
194
|
+
return {
|
|
195
|
+
contents,
|
|
196
|
+
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function makeNode(plane, children) {
|
|
200
|
+
return { plane, children };
|
|
201
|
+
}
|
|
202
|
+
function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
|
|
203
|
+
return {
|
|
204
|
+
planes,
|
|
205
|
+
nodes,
|
|
206
|
+
leaves,
|
|
207
|
+
brushes,
|
|
208
|
+
leafBrushes,
|
|
209
|
+
bmodels: []
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
|
|
213
|
+
return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
|
|
214
|
+
}
|
|
215
|
+
function makeLeafModel(brushes) {
|
|
216
|
+
const planes = brushes.flatMap((brush) => brush.sides.map((side) => side.plane));
|
|
217
|
+
return {
|
|
218
|
+
planes,
|
|
219
|
+
nodes: [],
|
|
220
|
+
leaves: [makeLeaf(0, 0, brushes.length)],
|
|
221
|
+
brushes,
|
|
222
|
+
leafBrushes: brushes.map((_, i) => i),
|
|
223
|
+
bmodels: []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOLID) {
|
|
227
|
+
const planes = [
|
|
228
|
+
makePlane({ x: 1, y: 0, z: 0 }, maxs.x),
|
|
229
|
+
makePlane({ x: -1, y: 0, z: 0 }, -mins.x),
|
|
230
|
+
makePlane({ x: 0, y: 1, z: 0 }, maxs.y),
|
|
231
|
+
makePlane({ x: 0, y: -1, z: 0 }, -mins.y),
|
|
232
|
+
makePlane({ x: 0, y: 0, z: 1 }, maxs.z),
|
|
233
|
+
makePlane({ x: 0, y: 0, z: -1 }, -mins.z)
|
|
234
|
+
];
|
|
235
|
+
return {
|
|
236
|
+
contents,
|
|
237
|
+
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/game/factories.ts
|
|
242
|
+
var createPlayerStateFactory = (overrides) => ({
|
|
243
|
+
pm_type: 0,
|
|
244
|
+
pm_time: 0,
|
|
245
|
+
pm_flags: 0,
|
|
246
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
247
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
248
|
+
viewAngles: { x: 0, y: 0, z: 0 },
|
|
249
|
+
onGround: false,
|
|
250
|
+
waterLevel: 0,
|
|
251
|
+
watertype: 0,
|
|
252
|
+
mins: { x: 0, y: 0, z: 0 },
|
|
253
|
+
maxs: { x: 0, y: 0, z: 0 },
|
|
254
|
+
damageAlpha: 0,
|
|
255
|
+
damageIndicators: [],
|
|
256
|
+
blend: [0, 0, 0, 0],
|
|
257
|
+
stats: [],
|
|
258
|
+
kick_angles: { x: 0, y: 0, z: 0 },
|
|
259
|
+
kick_origin: { x: 0, y: 0, z: 0 },
|
|
260
|
+
gunoffset: { x: 0, y: 0, z: 0 },
|
|
261
|
+
gunangles: { x: 0, y: 0, z: 0 },
|
|
262
|
+
gunindex: 0,
|
|
263
|
+
gun_frame: 0,
|
|
264
|
+
rdflags: 0,
|
|
265
|
+
fov: 90,
|
|
266
|
+
renderfx: 0,
|
|
267
|
+
...overrides
|
|
268
|
+
});
|
|
269
|
+
var createEntityStateFactory = (overrides) => ({
|
|
270
|
+
number: 0,
|
|
271
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
272
|
+
angles: { x: 0, y: 0, z: 0 },
|
|
273
|
+
oldOrigin: { x: 0, y: 0, z: 0 },
|
|
274
|
+
modelIndex: 0,
|
|
275
|
+
modelIndex2: 0,
|
|
276
|
+
modelIndex3: 0,
|
|
277
|
+
modelIndex4: 0,
|
|
278
|
+
frame: 0,
|
|
279
|
+
skinNum: 0,
|
|
280
|
+
effects: 0,
|
|
281
|
+
renderfx: 0,
|
|
282
|
+
solid: 0,
|
|
283
|
+
sound: 0,
|
|
284
|
+
event: 0,
|
|
285
|
+
...overrides
|
|
286
|
+
});
|
|
287
|
+
var createGameStateSnapshotFactory = (overrides) => ({
|
|
288
|
+
gravity: { x: 0, y: 0, z: -800 },
|
|
289
|
+
origin: { x: 0, y: 0, z: 0 },
|
|
290
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
291
|
+
viewangles: { x: 0, y: 0, z: 0 },
|
|
292
|
+
level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
|
|
293
|
+
entities: {
|
|
294
|
+
activeCount: 0,
|
|
295
|
+
worldClassname: "worldspawn"
|
|
296
|
+
},
|
|
297
|
+
packetEntities: [],
|
|
298
|
+
pmFlags: 0,
|
|
299
|
+
pmType: 0,
|
|
300
|
+
waterlevel: 0,
|
|
301
|
+
watertype: 0,
|
|
302
|
+
deltaAngles: { x: 0, y: 0, z: 0 },
|
|
303
|
+
health: 100,
|
|
304
|
+
armor: 0,
|
|
305
|
+
ammo: 0,
|
|
306
|
+
blend: [0, 0, 0, 0],
|
|
307
|
+
damageAlpha: 0,
|
|
308
|
+
damageIndicators: [],
|
|
309
|
+
stats: [],
|
|
310
|
+
kick_angles: { x: 0, y: 0, z: 0 },
|
|
311
|
+
kick_origin: { x: 0, y: 0, z: 0 },
|
|
312
|
+
gunoffset: { x: 0, y: 0, z: 0 },
|
|
313
|
+
gunangles: { x: 0, y: 0, z: 0 },
|
|
314
|
+
gunindex: 0,
|
|
315
|
+
pm_time: 0,
|
|
316
|
+
gun_frame: 0,
|
|
317
|
+
rdflags: 0,
|
|
318
|
+
fov: 90,
|
|
319
|
+
renderfx: 0,
|
|
320
|
+
pm_flags: 0,
|
|
321
|
+
pm_type: 0,
|
|
322
|
+
...overrides
|
|
323
|
+
});
|
|
305
324
|
|
|
306
|
-
// src/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
global.DragEvent = dom.window.DragEvent;
|
|
341
|
-
global.MouseEvent = dom.window.MouseEvent;
|
|
342
|
-
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
343
|
-
global.FocusEvent = dom.window.FocusEvent;
|
|
344
|
-
global.WheelEvent = dom.window.WheelEvent;
|
|
345
|
-
global.InputEvent = dom.window.InputEvent;
|
|
346
|
-
global.UIEvent = dom.window.UIEvent;
|
|
347
|
-
try {
|
|
348
|
-
global.localStorage = dom.window.localStorage;
|
|
349
|
-
} catch (e) {
|
|
350
|
-
}
|
|
351
|
-
if (!global.localStorage) {
|
|
352
|
-
const storage = /* @__PURE__ */ new Map();
|
|
353
|
-
global.localStorage = {
|
|
354
|
-
getItem: (key) => storage.get(key) || null,
|
|
355
|
-
setItem: (key, value) => storage.set(key, value),
|
|
356
|
-
removeItem: (key) => storage.delete(key),
|
|
357
|
-
clear: () => storage.clear(),
|
|
358
|
-
key: (index) => Array.from(storage.keys())[index] || null,
|
|
359
|
-
get length() {
|
|
360
|
-
return storage.size;
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
const originalCreateElement = document.createElement.bind(document);
|
|
365
|
-
document.createElement = function(tagName, options2) {
|
|
366
|
-
if (tagName.toLowerCase() === "canvas") {
|
|
367
|
-
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
368
|
-
const domCanvas = originalCreateElement("canvas", options2);
|
|
369
|
-
Object.defineProperty(domCanvas, "width", {
|
|
370
|
-
get: () => napiCanvas.width,
|
|
371
|
-
set: (value) => {
|
|
372
|
-
napiCanvas.width = value;
|
|
373
|
-
},
|
|
374
|
-
enumerable: true,
|
|
375
|
-
configurable: true
|
|
376
|
-
});
|
|
377
|
-
Object.defineProperty(domCanvas, "height", {
|
|
378
|
-
get: () => napiCanvas.height,
|
|
379
|
-
set: (value) => {
|
|
380
|
-
napiCanvas.height = value;
|
|
381
|
-
},
|
|
382
|
-
enumerable: true,
|
|
383
|
-
configurable: true
|
|
384
|
-
});
|
|
385
|
-
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
386
|
-
domCanvas.getContext = function(contextId, options3) {
|
|
387
|
-
if (contextId === "2d") {
|
|
388
|
-
return napiCanvas.getContext("2d", options3);
|
|
389
|
-
}
|
|
390
|
-
if (enableWebGL2 && contextId === "webgl2") {
|
|
391
|
-
return createMockWebGL2Context(domCanvas);
|
|
392
|
-
}
|
|
393
|
-
if (contextId === "webgl" || contextId === "webgl2") {
|
|
394
|
-
return originalGetContext(contextId, options3);
|
|
395
|
-
}
|
|
396
|
-
return napiCanvas.getContext(contextId, options3);
|
|
397
|
-
};
|
|
398
|
-
domCanvas.__napiCanvas = napiCanvas;
|
|
399
|
-
return domCanvas;
|
|
400
|
-
}
|
|
401
|
-
return originalCreateElement(tagName, options2);
|
|
325
|
+
// src/game/helpers.ts
|
|
326
|
+
var import_vitest2 = require("vitest");
|
|
327
|
+
var import_game = require("@quake2ts/game");
|
|
328
|
+
var import_shared2 = require("@quake2ts/shared");
|
|
329
|
+
var import_shared3 = require("@quake2ts/shared");
|
|
330
|
+
var createMockEngine = () => ({
|
|
331
|
+
sound: import_vitest2.vi.fn(),
|
|
332
|
+
soundIndex: import_vitest2.vi.fn((sound) => 0),
|
|
333
|
+
modelIndex: import_vitest2.vi.fn((model) => 0),
|
|
334
|
+
centerprintf: import_vitest2.vi.fn()
|
|
335
|
+
});
|
|
336
|
+
var createMockGame = (seed = 12345) => {
|
|
337
|
+
const spawnRegistry = new import_game.SpawnRegistry();
|
|
338
|
+
const hooks = new import_game.ScriptHookRegistry();
|
|
339
|
+
const game = {
|
|
340
|
+
random: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
341
|
+
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
342
|
+
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
343
|
+
}),
|
|
344
|
+
unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
|
|
345
|
+
spawnRegistry.unregister(classname);
|
|
346
|
+
}),
|
|
347
|
+
getCustomEntities: import_vitest2.vi.fn(() => Array.from(spawnRegistry.keys())),
|
|
348
|
+
hooks,
|
|
349
|
+
registerHooks: import_vitest2.vi.fn((newHooks) => hooks.register(newHooks)),
|
|
350
|
+
spawnWorld: import_vitest2.vi.fn(() => {
|
|
351
|
+
hooks.onMapLoad("q2dm1");
|
|
352
|
+
}),
|
|
353
|
+
clientBegin: import_vitest2.vi.fn((client) => {
|
|
354
|
+
hooks.onPlayerSpawn({});
|
|
355
|
+
}),
|
|
356
|
+
damage: import_vitest2.vi.fn((amount) => {
|
|
357
|
+
hooks.onDamage({}, null, null, amount, 0, 0);
|
|
358
|
+
})
|
|
402
359
|
};
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
360
|
+
return { game, spawnRegistry };
|
|
361
|
+
};
|
|
362
|
+
function createTestContext(options) {
|
|
363
|
+
const engine = createMockEngine();
|
|
364
|
+
const seed = options?.seed ?? 12345;
|
|
365
|
+
const { game, spawnRegistry } = createMockGame(seed);
|
|
366
|
+
const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
|
|
367
|
+
fraction: 1,
|
|
368
|
+
ent: null,
|
|
369
|
+
allsolid: false,
|
|
370
|
+
startsolid: false,
|
|
371
|
+
endpos: end,
|
|
372
|
+
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
373
|
+
surfaceFlags: 0,
|
|
374
|
+
contents: 0
|
|
375
|
+
}));
|
|
376
|
+
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
377
|
+
const hooks = game.hooks;
|
|
378
|
+
const entities = {
|
|
379
|
+
spawn: import_vitest2.vi.fn(() => {
|
|
380
|
+
const ent = new import_game.Entity(entityList.length + 1);
|
|
381
|
+
entityList.push(ent);
|
|
382
|
+
hooks.onEntitySpawn(ent);
|
|
383
|
+
return ent;
|
|
384
|
+
}),
|
|
385
|
+
free: import_vitest2.vi.fn((ent) => {
|
|
386
|
+
const idx = entityList.indexOf(ent);
|
|
387
|
+
if (idx !== -1) {
|
|
388
|
+
entityList.splice(idx, 1);
|
|
408
389
|
}
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
417
|
-
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
418
|
-
const ctx = canvas2.getContext("2d");
|
|
419
|
-
if (image.data) {
|
|
420
|
-
ctx.putImageData(image, 0, 0);
|
|
421
|
-
}
|
|
422
|
-
return canvas2;
|
|
390
|
+
hooks.onEntityRemove(ent);
|
|
391
|
+
}),
|
|
392
|
+
finalizeSpawn: import_vitest2.vi.fn(),
|
|
393
|
+
freeImmediate: import_vitest2.vi.fn((ent) => {
|
|
394
|
+
const idx = entityList.indexOf(ent);
|
|
395
|
+
if (idx !== -1) {
|
|
396
|
+
entityList.splice(idx, 1);
|
|
423
397
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
398
|
+
}),
|
|
399
|
+
setSpawnRegistry: import_vitest2.vi.fn(),
|
|
400
|
+
timeSeconds: 10,
|
|
401
|
+
deltaSeconds: 0.1,
|
|
402
|
+
modelIndex: import_vitest2.vi.fn(() => 0),
|
|
403
|
+
scheduleThink: import_vitest2.vi.fn((entity, time) => {
|
|
404
|
+
entity.nextthink = time;
|
|
405
|
+
}),
|
|
406
|
+
linkentity: import_vitest2.vi.fn(),
|
|
407
|
+
trace: traceFn,
|
|
408
|
+
pointcontents: import_vitest2.vi.fn(() => 0),
|
|
409
|
+
multicast: import_vitest2.vi.fn(),
|
|
410
|
+
unicast: import_vitest2.vi.fn(),
|
|
411
|
+
engine,
|
|
412
|
+
game,
|
|
413
|
+
sound: import_vitest2.vi.fn((ent, chan, sound, vol, attn, timeofs) => {
|
|
414
|
+
engine.sound(ent, chan, sound, vol, attn, timeofs);
|
|
415
|
+
}),
|
|
416
|
+
soundIndex: import_vitest2.vi.fn((sound) => engine.soundIndex(sound)),
|
|
417
|
+
useTargets: import_vitest2.vi.fn((entity, activator) => {
|
|
418
|
+
}),
|
|
419
|
+
findByTargetName: import_vitest2.vi.fn(() => []),
|
|
420
|
+
pickTarget: import_vitest2.vi.fn(() => null),
|
|
421
|
+
killBox: import_vitest2.vi.fn(),
|
|
422
|
+
rng: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
423
|
+
imports: {
|
|
424
|
+
configstring: import_vitest2.vi.fn(),
|
|
425
|
+
trace: traceFn,
|
|
426
|
+
pointcontents: import_vitest2.vi.fn(() => 0)
|
|
427
|
+
},
|
|
428
|
+
level: {
|
|
429
|
+
intermission_angle: { x: 0, y: 0, z: 0 },
|
|
430
|
+
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
431
|
+
next_auto_save: 0,
|
|
432
|
+
health_bar_entities: null
|
|
433
|
+
},
|
|
434
|
+
targetNameIndex: /* @__PURE__ */ new Map(),
|
|
435
|
+
forEachEntity: import_vitest2.vi.fn((callback) => {
|
|
436
|
+
entityList.forEach(callback);
|
|
437
|
+
}),
|
|
438
|
+
find: import_vitest2.vi.fn((predicate) => {
|
|
439
|
+
return entityList.find(predicate);
|
|
440
|
+
}),
|
|
441
|
+
findByClassname: import_vitest2.vi.fn((classname) => {
|
|
442
|
+
return entityList.find((e) => e.classname === classname);
|
|
443
|
+
}),
|
|
444
|
+
beginFrame: import_vitest2.vi.fn((timeSeconds) => {
|
|
445
|
+
entities.timeSeconds = timeSeconds;
|
|
446
|
+
}),
|
|
447
|
+
targetAwareness: {
|
|
448
|
+
timeSeconds: 10,
|
|
449
|
+
frameNumber: 1,
|
|
450
|
+
sightEntity: null,
|
|
451
|
+
soundEntity: null
|
|
452
|
+
},
|
|
453
|
+
// Adding missing properties to satisfy EntitySystem interface partially or fully
|
|
454
|
+
// We cast to unknown first anyway, but filling these in makes it safer for consumers
|
|
455
|
+
skill: 1,
|
|
456
|
+
deathmatch: false,
|
|
457
|
+
coop: false,
|
|
458
|
+
activeCount: entityList.length,
|
|
459
|
+
world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
|
|
460
|
+
// ... other EntitySystem properties would go here
|
|
461
|
+
};
|
|
462
|
+
return {
|
|
463
|
+
keyValues: {},
|
|
464
|
+
entities,
|
|
465
|
+
game,
|
|
466
|
+
engine,
|
|
467
|
+
health_multiplier: 1,
|
|
468
|
+
warn: import_vitest2.vi.fn(),
|
|
469
|
+
free: import_vitest2.vi.fn(),
|
|
470
|
+
// Mock precache functions if they are part of SpawnContext in future or TestContext extensions
|
|
471
|
+
precacheModel: import_vitest2.vi.fn(),
|
|
472
|
+
precacheSound: import_vitest2.vi.fn(),
|
|
473
|
+
precacheImage: import_vitest2.vi.fn()
|
|
474
|
+
};
|
|
456
475
|
}
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
delete global.location;
|
|
463
|
-
delete global.HTMLElement;
|
|
464
|
-
delete global.HTMLCanvasElement;
|
|
465
|
-
delete global.Image;
|
|
466
|
-
delete global.ImageData;
|
|
467
|
-
delete global.createImageBitmap;
|
|
468
|
-
delete global.Event;
|
|
469
|
-
delete global.CustomEvent;
|
|
470
|
-
delete global.DragEvent;
|
|
471
|
-
delete global.MouseEvent;
|
|
472
|
-
delete global.KeyboardEvent;
|
|
473
|
-
delete global.FocusEvent;
|
|
474
|
-
delete global.WheelEvent;
|
|
475
|
-
delete global.InputEvent;
|
|
476
|
-
delete global.UIEvent;
|
|
476
|
+
function createSpawnContext() {
|
|
477
|
+
return createTestContext();
|
|
478
|
+
}
|
|
479
|
+
function createEntity() {
|
|
480
|
+
return new import_game.Entity(1);
|
|
477
481
|
}
|
|
478
482
|
|
|
479
|
-
// src/
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
const canvas = new import_canvas2.Canvas(width, height);
|
|
489
|
-
const originalGetContext = canvas.getContext.bind(canvas);
|
|
490
|
-
canvas.getContext = function(contextId, options) {
|
|
491
|
-
if (contextId === "webgl2") {
|
|
492
|
-
return createMockWebGL2Context(canvas);
|
|
493
|
-
}
|
|
494
|
-
if (contextId === "2d") {
|
|
495
|
-
return originalGetContext("2d", options);
|
|
496
|
-
}
|
|
497
|
-
return originalGetContext(contextId, options);
|
|
483
|
+
// src/game/mocks.ts
|
|
484
|
+
function createMockGameState(overrides) {
|
|
485
|
+
return {
|
|
486
|
+
levelName: "test_level",
|
|
487
|
+
time: 0,
|
|
488
|
+
entities: [],
|
|
489
|
+
clients: [],
|
|
490
|
+
...overrides
|
|
498
491
|
};
|
|
499
|
-
return canvas;
|
|
500
492
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
493
|
+
|
|
494
|
+
// src/server/mocks/transport.ts
|
|
495
|
+
var import_vitest3 = require("vitest");
|
|
496
|
+
var MockTransport = class {
|
|
497
|
+
constructor() {
|
|
498
|
+
this.address = "127.0.0.1";
|
|
499
|
+
this.port = 27910;
|
|
500
|
+
this.sentMessages = [];
|
|
501
|
+
this.receivedMessages = [];
|
|
502
|
+
this.listening = false;
|
|
503
|
+
this.listenSpy = import_vitest3.vi.fn().mockImplementation(async (port) => {
|
|
504
|
+
this.port = port;
|
|
505
|
+
this.listening = true;
|
|
506
|
+
});
|
|
507
|
+
this.closeSpy = import_vitest3.vi.fn().mockImplementation(() => {
|
|
508
|
+
this.listening = false;
|
|
509
|
+
});
|
|
504
510
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
"fillRect",
|
|
511
|
-
"strokeRect",
|
|
512
|
-
"clearRect",
|
|
513
|
-
"fillText",
|
|
514
|
-
"strokeText",
|
|
515
|
-
"drawImage",
|
|
516
|
-
"beginPath",
|
|
517
|
-
"closePath",
|
|
518
|
-
"moveTo",
|
|
519
|
-
"lineTo",
|
|
520
|
-
"arc",
|
|
521
|
-
"arcTo",
|
|
522
|
-
"bezierCurveTo",
|
|
523
|
-
"quadraticCurveTo",
|
|
524
|
-
"stroke",
|
|
525
|
-
"fill",
|
|
526
|
-
"putImageData"
|
|
527
|
-
];
|
|
528
|
-
methodsToSpy.forEach((method) => {
|
|
529
|
-
const original = context[method];
|
|
530
|
-
if (typeof original === "function") {
|
|
531
|
-
context[method] = function(...args) {
|
|
532
|
-
drawCalls.push({ method, args });
|
|
533
|
-
return original.apply(this, args);
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
return drawCalls;
|
|
538
|
-
}
|
|
539
|
-
function createMockImageData(width, height, fillColor) {
|
|
540
|
-
const imageData = new import_canvas2.ImageData(width, height);
|
|
541
|
-
if (fillColor) {
|
|
542
|
-
const [r, g, b, a] = fillColor;
|
|
543
|
-
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
544
|
-
imageData.data[i] = r;
|
|
545
|
-
imageData.data[i + 1] = g;
|
|
546
|
-
imageData.data[i + 2] = b;
|
|
547
|
-
imageData.data[i + 3] = a;
|
|
548
|
-
}
|
|
511
|
+
/**
|
|
512
|
+
* Start listening on the specified port.
|
|
513
|
+
*/
|
|
514
|
+
async listen(port) {
|
|
515
|
+
return this.listenSpy(port);
|
|
549
516
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (height) img.height = height;
|
|
556
|
-
if (src) img.src = src;
|
|
557
|
-
return img;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// src/setup/node.ts
|
|
561
|
-
function setupNodeEnvironment() {
|
|
562
|
-
if (typeof global.fetch === "undefined") {
|
|
517
|
+
/**
|
|
518
|
+
* Close the transport.
|
|
519
|
+
*/
|
|
520
|
+
close() {
|
|
521
|
+
this.closeSpy();
|
|
563
522
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Register a callback for new connections.
|
|
525
|
+
*/
|
|
526
|
+
onConnection(callback) {
|
|
527
|
+
this.onConnectionCallback = callback;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Register a callback for errors.
|
|
531
|
+
*/
|
|
532
|
+
onError(callback) {
|
|
533
|
+
this.onErrorCallback = callback;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Check if the transport is currently listening.
|
|
537
|
+
*/
|
|
538
|
+
isListening() {
|
|
539
|
+
return this.listening;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Helper to simulate a new connection.
|
|
543
|
+
* @param driver The network driver for the connection.
|
|
544
|
+
* @param info Optional connection info.
|
|
545
|
+
*/
|
|
546
|
+
simulateConnection(driver, info) {
|
|
547
|
+
if (this.onConnectionCallback) {
|
|
548
|
+
this.onConnectionCallback(driver, info);
|
|
578
549
|
}
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function createMockSessionStorage(initialData = {}) {
|
|
582
|
-
return createMockLocalStorage(initialData);
|
|
583
|
-
}
|
|
584
|
-
function createMockIndexedDB() {
|
|
585
|
-
if (typeof indexedDB === "undefined") {
|
|
586
|
-
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
587
550
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
596
|
-
},
|
|
597
|
-
verify(key, value) {
|
|
598
|
-
return storage.getItem(key) === value;
|
|
551
|
+
/**
|
|
552
|
+
* Helper to simulate an error.
|
|
553
|
+
* @param error The error to simulate.
|
|
554
|
+
*/
|
|
555
|
+
simulateError(error) {
|
|
556
|
+
if (this.onErrorCallback) {
|
|
557
|
+
this.onErrorCallback(error);
|
|
599
558
|
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
function createMockUDPSocket(overrides) {
|
|
562
|
+
const socket = {
|
|
563
|
+
send: import_vitest3.vi.fn(),
|
|
564
|
+
on: import_vitest3.vi.fn(),
|
|
565
|
+
close: import_vitest3.vi.fn(),
|
|
566
|
+
bind: import_vitest3.vi.fn(),
|
|
567
|
+
address: import_vitest3.vi.fn().mockReturnValue({ address: "127.0.0.1", family: "IPv4", port: 0 }),
|
|
568
|
+
...overrides
|
|
600
569
|
};
|
|
570
|
+
return socket;
|
|
571
|
+
}
|
|
572
|
+
function createMockNetworkAddress(ip = "127.0.0.1", port = 27910) {
|
|
573
|
+
return { ip, port };
|
|
574
|
+
}
|
|
575
|
+
function createMockTransport(address = "127.0.0.1", port = 27910, overrides) {
|
|
576
|
+
const transport = new MockTransport();
|
|
577
|
+
transport.address = address;
|
|
578
|
+
transport.port = port;
|
|
579
|
+
Object.assign(transport, overrides);
|
|
580
|
+
return transport;
|
|
601
581
|
}
|
|
602
582
|
|
|
603
|
-
// src/
|
|
604
|
-
|
|
583
|
+
// src/server/mocks/state.ts
|
|
584
|
+
var import_server = require("@quake2ts/server");
|
|
585
|
+
var import_shared4 = require("@quake2ts/shared");
|
|
586
|
+
var import_vitest4 = require("vitest");
|
|
587
|
+
function createMockServerState(overrides) {
|
|
605
588
|
return {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
589
|
+
state: import_server.ServerState.Game,
|
|
590
|
+
attractLoop: false,
|
|
591
|
+
loadGame: false,
|
|
592
|
+
startTime: Date.now(),
|
|
593
|
+
time: 0,
|
|
594
|
+
frame: 0,
|
|
595
|
+
name: "test_map",
|
|
596
|
+
collisionModel: null,
|
|
597
|
+
configStrings: new Array(import_shared4.MAX_CONFIGSTRINGS).fill(""),
|
|
598
|
+
baselines: new Array(import_shared4.MAX_EDICTS).fill(null),
|
|
599
|
+
multicastBuf: new Uint8Array(0),
|
|
600
|
+
...overrides
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function createMockServerStatic(maxClients = 16, overrides) {
|
|
604
|
+
return {
|
|
605
|
+
initialized: true,
|
|
606
|
+
realTime: Date.now(),
|
|
607
|
+
mapCmd: "",
|
|
608
|
+
spawnCount: 1,
|
|
609
|
+
clients: new Array(maxClients).fill(null),
|
|
610
|
+
lastHeartbeat: 0,
|
|
611
|
+
challenges: [],
|
|
612
|
+
...overrides
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function createMockServerClient(clientNum, overrides) {
|
|
616
|
+
const mockNet = {
|
|
617
|
+
connect: import_vitest4.vi.fn(),
|
|
618
|
+
disconnect: import_vitest4.vi.fn(),
|
|
619
|
+
send: import_vitest4.vi.fn(),
|
|
620
|
+
onMessage: import_vitest4.vi.fn(),
|
|
621
|
+
onClose: import_vitest4.vi.fn(),
|
|
622
|
+
onError: import_vitest4.vi.fn(),
|
|
623
|
+
isConnected: import_vitest4.vi.fn().mockReturnValue(true)
|
|
624
|
+
};
|
|
625
|
+
return {
|
|
626
|
+
index: clientNum,
|
|
627
|
+
state: import_server.ClientState.Connected,
|
|
628
|
+
edict: { index: clientNum + 1 },
|
|
629
|
+
net: mockNet,
|
|
630
|
+
netchan: {
|
|
631
|
+
qport: 0,
|
|
632
|
+
remoteAddress: "127.0.0.1",
|
|
633
|
+
incomingSequence: 0,
|
|
634
|
+
outgoingSequence: 0,
|
|
635
|
+
lastReceived: 0,
|
|
636
|
+
process: import_vitest4.vi.fn(),
|
|
637
|
+
transmit: import_vitest4.vi.fn(),
|
|
638
|
+
writeReliableByte: import_vitest4.vi.fn(),
|
|
639
|
+
writeReliableShort: import_vitest4.vi.fn(),
|
|
640
|
+
writeReliableLong: import_vitest4.vi.fn(),
|
|
641
|
+
writeReliableString: import_vitest4.vi.fn(),
|
|
642
|
+
writeReliableData: import_vitest4.vi.fn()
|
|
638
643
|
},
|
|
639
|
-
|
|
644
|
+
// Cast as any because NetChan might be complex to fully mock here
|
|
645
|
+
userInfo: "",
|
|
646
|
+
lastMessage: 0,
|
|
647
|
+
lastCommandTime: 0,
|
|
648
|
+
commandCount: 0,
|
|
649
|
+
messageQueue: [],
|
|
650
|
+
frames: [],
|
|
651
|
+
lastFrame: 0,
|
|
652
|
+
lastPacketEntities: [],
|
|
653
|
+
challenge: 0,
|
|
654
|
+
lastConnect: 0,
|
|
655
|
+
ping: 0,
|
|
656
|
+
rate: 0,
|
|
657
|
+
name: `Client${clientNum}`,
|
|
658
|
+
messageLevel: 0,
|
|
659
|
+
datagram: new Uint8Array(0),
|
|
660
|
+
downloadSize: 0,
|
|
661
|
+
downloadCount: 0,
|
|
662
|
+
commandMsec: 0,
|
|
663
|
+
frameLatency: [],
|
|
664
|
+
messageSize: [],
|
|
665
|
+
suppressCount: 0,
|
|
666
|
+
commandQueue: [],
|
|
667
|
+
lastCmd: {
|
|
668
|
+
msec: 0,
|
|
669
|
+
buttons: 0,
|
|
670
|
+
angles: { x: 0, y: 0, z: 0 },
|
|
671
|
+
forwardmove: 0,
|
|
672
|
+
sidemove: 0,
|
|
673
|
+
upmove: 0,
|
|
674
|
+
sequence: 0,
|
|
675
|
+
lightlevel: 0,
|
|
676
|
+
impulse: 0,
|
|
677
|
+
serverFrame: 0
|
|
640
678
|
},
|
|
641
|
-
|
|
642
|
-
duration: 1,
|
|
643
|
-
length: 44100,
|
|
644
|
-
sampleRate: 44100,
|
|
645
|
-
numberOfChannels: 2,
|
|
646
|
-
getChannelData: () => new Float32Array(44100)
|
|
647
|
-
}),
|
|
648
|
-
createBuffer: (channels, length, sampleRate) => ({
|
|
649
|
-
duration: length / sampleRate,
|
|
650
|
-
length,
|
|
651
|
-
sampleRate,
|
|
652
|
-
numberOfChannels: channels,
|
|
653
|
-
getChannelData: () => new Float32Array(length)
|
|
654
|
-
})
|
|
679
|
+
...overrides
|
|
655
680
|
};
|
|
656
681
|
}
|
|
657
|
-
function
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
682
|
+
function createMockServer(overrides) {
|
|
683
|
+
return {
|
|
684
|
+
start: import_vitest4.vi.fn().mockResolvedValue(void 0),
|
|
685
|
+
stop: import_vitest4.vi.fn(),
|
|
686
|
+
multicast: import_vitest4.vi.fn(),
|
|
687
|
+
unicast: import_vitest4.vi.fn(),
|
|
688
|
+
configstring: import_vitest4.vi.fn(),
|
|
689
|
+
kickPlayer: import_vitest4.vi.fn(),
|
|
690
|
+
changeMap: import_vitest4.vi.fn().mockResolvedValue(void 0),
|
|
691
|
+
...overrides
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/setup/browser.ts
|
|
696
|
+
var import_jsdom = require("jsdom");
|
|
697
|
+
var import_canvas = require("@napi-rs/canvas");
|
|
698
|
+
var import_auto = require("fake-indexeddb/auto");
|
|
699
|
+
|
|
700
|
+
// src/e2e/input.ts
|
|
701
|
+
var MockPointerLock = class {
|
|
702
|
+
static setup(doc) {
|
|
703
|
+
let _pointerLockElement = null;
|
|
704
|
+
Object.defineProperty(doc, "pointerLockElement", {
|
|
705
|
+
get: () => _pointerLockElement,
|
|
706
|
+
configurable: true
|
|
707
|
+
});
|
|
708
|
+
doc.exitPointerLock = () => {
|
|
709
|
+
if (_pointerLockElement) {
|
|
710
|
+
_pointerLockElement = null;
|
|
711
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
662
712
|
}
|
|
663
713
|
};
|
|
664
|
-
global.
|
|
665
|
-
|
|
714
|
+
global.HTMLElement.prototype.requestPointerLock = function() {
|
|
715
|
+
_pointerLockElement = this;
|
|
716
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
717
|
+
};
|
|
666
718
|
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
delete global.window.webkitAudioContext;
|
|
719
|
+
};
|
|
720
|
+
var InputInjector = class {
|
|
721
|
+
constructor(doc, win) {
|
|
722
|
+
this.doc = doc;
|
|
723
|
+
this.win = win;
|
|
673
724
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
725
|
+
keyDown(key, code) {
|
|
726
|
+
const event = new this.win.KeyboardEvent("keydown", {
|
|
727
|
+
key,
|
|
728
|
+
code: code || key,
|
|
729
|
+
bubbles: true,
|
|
730
|
+
cancelable: true,
|
|
731
|
+
view: this.win
|
|
732
|
+
});
|
|
733
|
+
this.doc.dispatchEvent(event);
|
|
734
|
+
}
|
|
735
|
+
keyUp(key, code) {
|
|
736
|
+
const event = new this.win.KeyboardEvent("keyup", {
|
|
737
|
+
key,
|
|
738
|
+
code: code || key,
|
|
739
|
+
bubbles: true,
|
|
740
|
+
cancelable: true,
|
|
741
|
+
view: this.win
|
|
742
|
+
});
|
|
743
|
+
this.doc.dispatchEvent(event);
|
|
744
|
+
}
|
|
745
|
+
mouseMove(movementX, movementY, clientX = 0, clientY = 0) {
|
|
746
|
+
const event = new this.win.MouseEvent("mousemove", {
|
|
747
|
+
bubbles: true,
|
|
748
|
+
cancelable: true,
|
|
749
|
+
view: this.win,
|
|
750
|
+
clientX,
|
|
751
|
+
clientY,
|
|
752
|
+
movementX,
|
|
753
|
+
// Note: JSDOM might not support this standard property fully on event init
|
|
754
|
+
movementY
|
|
755
|
+
});
|
|
756
|
+
Object.defineProperty(event, "movementX", { value: movementX });
|
|
757
|
+
Object.defineProperty(event, "movementY", { value: movementY });
|
|
758
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
759
|
+
target.dispatchEvent(event);
|
|
760
|
+
}
|
|
761
|
+
mouseDown(button = 0) {
|
|
762
|
+
const event = new this.win.MouseEvent("mousedown", {
|
|
763
|
+
button,
|
|
764
|
+
bubbles: true,
|
|
765
|
+
cancelable: true,
|
|
766
|
+
view: this.win
|
|
767
|
+
});
|
|
768
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
769
|
+
target.dispatchEvent(event);
|
|
770
|
+
}
|
|
771
|
+
mouseUp(button = 0) {
|
|
772
|
+
const event = new this.win.MouseEvent("mouseup", {
|
|
773
|
+
button,
|
|
774
|
+
bubbles: true,
|
|
775
|
+
cancelable: true,
|
|
776
|
+
view: this.win
|
|
777
|
+
});
|
|
778
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
779
|
+
target.dispatchEvent(event);
|
|
780
|
+
}
|
|
781
|
+
wheel(deltaY) {
|
|
782
|
+
const event = new this.win.WheelEvent("wheel", {
|
|
783
|
+
deltaY,
|
|
784
|
+
bubbles: true,
|
|
785
|
+
cancelable: true,
|
|
786
|
+
view: this.win
|
|
787
|
+
});
|
|
788
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
789
|
+
target.dispatchEvent(event);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
678
792
|
|
|
679
|
-
// src/setup/
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
793
|
+
// src/setup/webgl.ts
|
|
794
|
+
function createMockWebGL2Context(canvas) {
|
|
795
|
+
const gl = {
|
|
796
|
+
canvas,
|
|
797
|
+
drawingBufferWidth: canvas.width,
|
|
798
|
+
drawingBufferHeight: canvas.height,
|
|
799
|
+
// Constants
|
|
800
|
+
VERTEX_SHADER: 35633,
|
|
801
|
+
FRAGMENT_SHADER: 35632,
|
|
802
|
+
COMPILE_STATUS: 35713,
|
|
803
|
+
LINK_STATUS: 35714,
|
|
804
|
+
ARRAY_BUFFER: 34962,
|
|
805
|
+
ELEMENT_ARRAY_BUFFER: 34963,
|
|
806
|
+
STATIC_DRAW: 35044,
|
|
807
|
+
DYNAMIC_DRAW: 35048,
|
|
808
|
+
FLOAT: 5126,
|
|
809
|
+
DEPTH_TEST: 2929,
|
|
810
|
+
BLEND: 3042,
|
|
811
|
+
SRC_ALPHA: 770,
|
|
812
|
+
ONE_MINUS_SRC_ALPHA: 771,
|
|
813
|
+
TEXTURE_2D: 3553,
|
|
814
|
+
RGBA: 6408,
|
|
815
|
+
UNSIGNED_BYTE: 5121,
|
|
816
|
+
COLOR_BUFFER_BIT: 16384,
|
|
817
|
+
DEPTH_BUFFER_BIT: 256,
|
|
818
|
+
TRIANGLES: 4,
|
|
819
|
+
TRIANGLE_STRIP: 5,
|
|
820
|
+
TRIANGLE_FAN: 6,
|
|
821
|
+
// Methods
|
|
822
|
+
createShader: () => ({}),
|
|
823
|
+
shaderSource: () => {
|
|
824
|
+
},
|
|
825
|
+
compileShader: () => {
|
|
826
|
+
},
|
|
827
|
+
getShaderParameter: (_, param) => {
|
|
828
|
+
if (param === 35713) return true;
|
|
829
|
+
return true;
|
|
830
|
+
},
|
|
831
|
+
getShaderInfoLog: () => "",
|
|
832
|
+
createProgram: () => ({}),
|
|
833
|
+
attachShader: () => {
|
|
834
|
+
},
|
|
835
|
+
linkProgram: () => {
|
|
836
|
+
},
|
|
837
|
+
getProgramParameter: (_, param) => {
|
|
838
|
+
if (param === 35714) return true;
|
|
839
|
+
return true;
|
|
840
|
+
},
|
|
841
|
+
getProgramInfoLog: () => "",
|
|
842
|
+
useProgram: () => {
|
|
843
|
+
},
|
|
844
|
+
createBuffer: () => ({}),
|
|
845
|
+
bindBuffer: () => {
|
|
846
|
+
},
|
|
847
|
+
bufferData: () => {
|
|
707
848
|
},
|
|
708
|
-
|
|
709
|
-
this.tick(currentTime + deltaMs);
|
|
849
|
+
enableVertexAttribArray: () => {
|
|
710
850
|
},
|
|
711
|
-
|
|
712
|
-
return callbacks.map((c) => c.callback);
|
|
851
|
+
vertexAttribPointer: () => {
|
|
713
852
|
},
|
|
714
|
-
|
|
715
|
-
callbacks = [];
|
|
716
|
-
nextId = 1;
|
|
717
|
-
currentTime = 0;
|
|
853
|
+
enable: () => {
|
|
718
854
|
},
|
|
719
|
-
|
|
720
|
-
activeMockRAF = this;
|
|
721
|
-
global.requestAnimationFrame = raf;
|
|
722
|
-
global.cancelAnimationFrame = cancel;
|
|
855
|
+
disable: () => {
|
|
723
856
|
},
|
|
724
|
-
|
|
725
|
-
if (activeMockRAF === this) {
|
|
726
|
-
activeMockRAF = void 0;
|
|
727
|
-
}
|
|
728
|
-
if (originalRAF) {
|
|
729
|
-
global.requestAnimationFrame = originalRAF;
|
|
730
|
-
} else {
|
|
731
|
-
delete global.requestAnimationFrame;
|
|
732
|
-
}
|
|
733
|
-
if (originalCancelRAF) {
|
|
734
|
-
global.cancelAnimationFrame = originalCancelRAF;
|
|
735
|
-
} else {
|
|
736
|
-
delete global.cancelAnimationFrame;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
};
|
|
740
|
-
return mock;
|
|
741
|
-
}
|
|
742
|
-
function createMockPerformance(startTime = 0) {
|
|
743
|
-
let currentTime = startTime;
|
|
744
|
-
const mockPerf = {
|
|
745
|
-
now: () => currentTime,
|
|
746
|
-
timeOrigin: startTime,
|
|
747
|
-
timing: {
|
|
748
|
-
navigationStart: startTime
|
|
857
|
+
depthMask: () => {
|
|
749
858
|
},
|
|
750
|
-
|
|
859
|
+
blendFunc: () => {
|
|
751
860
|
},
|
|
752
|
-
|
|
861
|
+
viewport: () => {
|
|
753
862
|
},
|
|
754
|
-
|
|
863
|
+
clearColor: () => {
|
|
755
864
|
},
|
|
756
|
-
|
|
757
|
-
getEntriesByName: () => [],
|
|
758
|
-
getEntriesByType: () => [],
|
|
759
|
-
mark: () => {
|
|
865
|
+
clear: () => {
|
|
760
866
|
},
|
|
761
|
-
|
|
867
|
+
createTexture: () => ({}),
|
|
868
|
+
bindTexture: () => {
|
|
762
869
|
},
|
|
763
|
-
|
|
870
|
+
texImage2D: () => {
|
|
764
871
|
},
|
|
765
|
-
|
|
766
|
-
addEventListener: () => {
|
|
872
|
+
texParameteri: () => {
|
|
767
873
|
},
|
|
768
|
-
|
|
874
|
+
activeTexture: () => {
|
|
769
875
|
},
|
|
770
|
-
|
|
771
|
-
};
|
|
772
|
-
mockPerf.advance = (deltaMs) => {
|
|
773
|
-
currentTime += deltaMs;
|
|
774
|
-
};
|
|
775
|
-
mockPerf.setTime = (time) => {
|
|
776
|
-
currentTime = time;
|
|
777
|
-
};
|
|
778
|
-
return mockPerf;
|
|
779
|
-
}
|
|
780
|
-
function createControlledTimer() {
|
|
781
|
-
let currentTime = 0;
|
|
782
|
-
let timers = [];
|
|
783
|
-
let nextId = 1;
|
|
784
|
-
const originalSetTimeout = global.setTimeout;
|
|
785
|
-
const originalClearTimeout = global.clearTimeout;
|
|
786
|
-
const originalSetInterval = global.setInterval;
|
|
787
|
-
const originalClearInterval = global.clearInterval;
|
|
788
|
-
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
789
|
-
const id = nextId++;
|
|
790
|
-
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
791
|
-
return id;
|
|
792
|
-
};
|
|
793
|
-
const mockClearTimeout = (id) => {
|
|
794
|
-
timers = timers.filter((t) => t.id !== id);
|
|
795
|
-
};
|
|
796
|
-
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
797
|
-
const id = nextId++;
|
|
798
|
-
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
799
|
-
return id;
|
|
800
|
-
};
|
|
801
|
-
const mockClearInterval = (id) => {
|
|
802
|
-
timers = timers.filter((t) => t.id !== id);
|
|
803
|
-
};
|
|
804
|
-
global.setTimeout = mockSetTimeout;
|
|
805
|
-
global.clearTimeout = mockClearTimeout;
|
|
806
|
-
global.setInterval = mockSetInterval;
|
|
807
|
-
global.clearInterval = mockClearInterval;
|
|
808
|
-
return {
|
|
809
|
-
tick() {
|
|
810
|
-
this.advanceBy(0);
|
|
876
|
+
uniform1i: () => {
|
|
811
877
|
},
|
|
812
|
-
|
|
813
|
-
const targetTime = currentTime + ms;
|
|
814
|
-
while (true) {
|
|
815
|
-
let earliest = null;
|
|
816
|
-
for (const t of timers) {
|
|
817
|
-
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
818
|
-
earliest = t;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
if (!earliest || earliest.dueTime > targetTime) {
|
|
822
|
-
break;
|
|
823
|
-
}
|
|
824
|
-
currentTime = earliest.dueTime;
|
|
825
|
-
const { callback, args, interval, id } = earliest;
|
|
826
|
-
if (interval !== void 0) {
|
|
827
|
-
earliest.dueTime += interval;
|
|
828
|
-
if (interval === 0) earliest.dueTime += 1;
|
|
829
|
-
} else {
|
|
830
|
-
timers = timers.filter((t) => t.id !== id);
|
|
831
|
-
}
|
|
832
|
-
callback(...args);
|
|
833
|
-
}
|
|
834
|
-
currentTime = targetTime;
|
|
878
|
+
uniform1f: () => {
|
|
835
879
|
},
|
|
836
|
-
|
|
837
|
-
timers = [];
|
|
880
|
+
uniform2f: () => {
|
|
838
881
|
},
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
mock.advance(frameTimeMs);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// src/e2e/playwright.ts
|
|
864
|
-
var import_playwright = require("playwright");
|
|
865
|
-
var import_http = require("http");
|
|
866
|
-
var import_serve_handler = __toESM(require("serve-handler"), 1);
|
|
867
|
-
async function createPlaywrightTestClient(options = {}) {
|
|
868
|
-
let staticServer;
|
|
869
|
-
let clientUrl = options.clientUrl;
|
|
870
|
-
const rootPath = options.rootPath || process.cwd();
|
|
871
|
-
if (!clientUrl) {
|
|
872
|
-
staticServer = (0, import_http.createServer)((request, response) => {
|
|
873
|
-
return (0, import_serve_handler.default)(request, response, {
|
|
874
|
-
public: rootPath,
|
|
875
|
-
cleanUrls: false,
|
|
876
|
-
headers: [
|
|
877
|
-
{
|
|
878
|
-
source: "**/*",
|
|
879
|
-
headers: [
|
|
880
|
-
{ key: "Cache-Control", value: "no-cache" },
|
|
881
|
-
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
882
|
-
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
883
|
-
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
884
|
-
]
|
|
885
|
-
}
|
|
886
|
-
]
|
|
887
|
-
});
|
|
888
|
-
});
|
|
889
|
-
await new Promise((resolve) => {
|
|
890
|
-
if (!staticServer) return;
|
|
891
|
-
staticServer.listen(0, () => {
|
|
892
|
-
const addr = staticServer?.address();
|
|
893
|
-
const port = typeof addr === "object" ? addr?.port : 0;
|
|
894
|
-
clientUrl = `http://localhost:${port}`;
|
|
895
|
-
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
896
|
-
resolve();
|
|
897
|
-
});
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
const browser = await import_playwright.chromium.launch({
|
|
901
|
-
headless: options.headless ?? true,
|
|
902
|
-
args: [
|
|
903
|
-
"--use-gl=egl",
|
|
904
|
-
"--ignore-gpu-blocklist",
|
|
905
|
-
...options.launchOptions?.args || []
|
|
906
|
-
],
|
|
907
|
-
...options.launchOptions
|
|
908
|
-
});
|
|
909
|
-
const width = options.width || 1280;
|
|
910
|
-
const height = options.height || 720;
|
|
911
|
-
const context = await browser.newContext({
|
|
912
|
-
viewport: { width, height },
|
|
913
|
-
deviceScaleFactor: 1,
|
|
914
|
-
...options.contextOptions
|
|
915
|
-
});
|
|
916
|
-
const page = await context.newPage();
|
|
917
|
-
const close = async () => {
|
|
918
|
-
await browser.close();
|
|
919
|
-
if (staticServer) {
|
|
920
|
-
staticServer.close();
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
const navigate = async (url) => {
|
|
924
|
-
const targetUrl = url || clientUrl;
|
|
925
|
-
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
926
|
-
let finalUrl = targetUrl;
|
|
927
|
-
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
928
|
-
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
929
|
-
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
930
|
-
}
|
|
931
|
-
console.log(`Navigating to: ${finalUrl}`);
|
|
932
|
-
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
933
|
-
};
|
|
934
|
-
return {
|
|
935
|
-
browser,
|
|
936
|
-
context,
|
|
937
|
-
page,
|
|
938
|
-
server: staticServer,
|
|
939
|
-
close,
|
|
940
|
-
navigate,
|
|
941
|
-
waitForGame: async (timeout = 1e4) => {
|
|
942
|
-
await waitForGameReady(page, timeout);
|
|
882
|
+
uniform3f: () => {
|
|
883
|
+
},
|
|
884
|
+
uniform4f: () => {
|
|
885
|
+
},
|
|
886
|
+
uniformMatrix4fv: () => {
|
|
887
|
+
},
|
|
888
|
+
getUniformLocation: () => ({}),
|
|
889
|
+
getAttribLocation: () => 0,
|
|
890
|
+
drawArrays: () => {
|
|
891
|
+
},
|
|
892
|
+
drawElements: () => {
|
|
893
|
+
},
|
|
894
|
+
createVertexArray: () => ({}),
|
|
895
|
+
bindVertexArray: () => {
|
|
896
|
+
},
|
|
897
|
+
deleteShader: () => {
|
|
898
|
+
},
|
|
899
|
+
deleteProgram: () => {
|
|
900
|
+
},
|
|
901
|
+
deleteBuffer: () => {
|
|
943
902
|
},
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
903
|
+
deleteTexture: () => {
|
|
904
|
+
},
|
|
905
|
+
deleteVertexArray: () => {
|
|
906
|
+
},
|
|
907
|
+
// WebGL2 specific
|
|
908
|
+
texImage3D: () => {
|
|
909
|
+
},
|
|
910
|
+
uniformBlockBinding: () => {
|
|
911
|
+
},
|
|
912
|
+
getExtension: () => null
|
|
949
913
|
};
|
|
914
|
+
return gl;
|
|
950
915
|
}
|
|
951
|
-
|
|
916
|
+
|
|
917
|
+
// src/setup/browser.ts
|
|
918
|
+
function setupBrowserEnvironment(options = {}) {
|
|
919
|
+
const {
|
|
920
|
+
url = "http://localhost",
|
|
921
|
+
pretendToBeVisual = true,
|
|
922
|
+
resources = void 0,
|
|
923
|
+
enableWebGL2 = false,
|
|
924
|
+
enablePointerLock = false
|
|
925
|
+
} = options;
|
|
926
|
+
const dom = new import_jsdom.JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
927
|
+
url,
|
|
928
|
+
pretendToBeVisual,
|
|
929
|
+
resources
|
|
930
|
+
});
|
|
931
|
+
global.window = dom.window;
|
|
932
|
+
global.document = dom.window.document;
|
|
952
933
|
try {
|
|
953
|
-
|
|
954
|
-
return window.gameInstance && window.gameInstance.isReady;
|
|
955
|
-
}, null, { timeout });
|
|
934
|
+
global.navigator = dom.window.navigator;
|
|
956
935
|
} catch (e) {
|
|
957
|
-
|
|
936
|
+
try {
|
|
937
|
+
Object.defineProperty(global, "navigator", {
|
|
938
|
+
value: dom.window.navigator,
|
|
939
|
+
writable: true,
|
|
940
|
+
configurable: true
|
|
941
|
+
});
|
|
942
|
+
} catch (e2) {
|
|
943
|
+
console.warn("Could not assign global.navigator, skipping.");
|
|
944
|
+
}
|
|
958
945
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
946
|
+
global.location = dom.window.location;
|
|
947
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
948
|
+
global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
|
|
949
|
+
global.Event = dom.window.Event;
|
|
950
|
+
global.CustomEvent = dom.window.CustomEvent;
|
|
951
|
+
global.DragEvent = dom.window.DragEvent;
|
|
952
|
+
global.MouseEvent = dom.window.MouseEvent;
|
|
953
|
+
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
954
|
+
global.FocusEvent = dom.window.FocusEvent;
|
|
955
|
+
global.WheelEvent = dom.window.WheelEvent;
|
|
956
|
+
global.InputEvent = dom.window.InputEvent;
|
|
957
|
+
global.UIEvent = dom.window.UIEvent;
|
|
958
|
+
try {
|
|
959
|
+
global.localStorage = dom.window.localStorage;
|
|
960
|
+
} catch (e) {
|
|
961
|
+
}
|
|
962
|
+
if (!global.localStorage) {
|
|
963
|
+
const storage = /* @__PURE__ */ new Map();
|
|
964
|
+
global.localStorage = {
|
|
965
|
+
getItem: (key) => storage.get(key) || null,
|
|
966
|
+
setItem: (key, value) => storage.set(key, value),
|
|
967
|
+
removeItem: (key) => storage.delete(key),
|
|
968
|
+
clear: () => storage.clear(),
|
|
969
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
970
|
+
get length() {
|
|
971
|
+
return storage.size;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const originalCreateElement = document.createElement.bind(document);
|
|
976
|
+
document.createElement = function(tagName, options2) {
|
|
977
|
+
if (tagName.toLowerCase() === "canvas") {
|
|
978
|
+
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
979
|
+
const domCanvas = originalCreateElement("canvas", options2);
|
|
980
|
+
Object.defineProperty(domCanvas, "width", {
|
|
981
|
+
get: () => napiCanvas.width,
|
|
982
|
+
set: (value) => {
|
|
983
|
+
napiCanvas.width = value;
|
|
984
|
+
},
|
|
985
|
+
enumerable: true,
|
|
986
|
+
configurable: true
|
|
987
|
+
});
|
|
988
|
+
Object.defineProperty(domCanvas, "height", {
|
|
989
|
+
get: () => napiCanvas.height,
|
|
990
|
+
set: (value) => {
|
|
991
|
+
napiCanvas.height = value;
|
|
992
|
+
},
|
|
993
|
+
enumerable: true,
|
|
994
|
+
configurable: true
|
|
995
|
+
});
|
|
996
|
+
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
997
|
+
domCanvas.getContext = function(contextId, options3) {
|
|
998
|
+
if (contextId === "2d") {
|
|
999
|
+
return napiCanvas.getContext("2d", options3);
|
|
1000
|
+
}
|
|
1001
|
+
if (enableWebGL2 && contextId === "webgl2") {
|
|
1002
|
+
return createMockWebGL2Context(domCanvas);
|
|
1003
|
+
}
|
|
1004
|
+
if (contextId === "webgl" || contextId === "webgl2") {
|
|
1005
|
+
return originalGetContext(contextId, options3);
|
|
1006
|
+
}
|
|
1007
|
+
return napiCanvas.getContext(contextId, options3);
|
|
1008
|
+
};
|
|
1009
|
+
domCanvas.__napiCanvas = napiCanvas;
|
|
1010
|
+
return domCanvas;
|
|
964
1011
|
}
|
|
965
|
-
return
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
// src/shared/bsp.ts
|
|
970
|
-
var import_shared = require("@quake2ts/shared");
|
|
971
|
-
function makePlane(normal, dist) {
|
|
972
|
-
return {
|
|
973
|
-
normal,
|
|
974
|
-
dist,
|
|
975
|
-
type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
|
|
976
|
-
signbits: (0, import_shared.computePlaneSignBits)(normal)
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
|
|
980
|
-
const half = size / 2;
|
|
981
|
-
const planes = [
|
|
982
|
-
makePlane({ x: 1, y: 0, z: 0 }, half),
|
|
983
|
-
makePlane({ x: -1, y: 0, z: 0 }, half),
|
|
984
|
-
makePlane({ x: 0, y: 1, z: 0 }, half),
|
|
985
|
-
makePlane({ x: 0, y: -1, z: 0 }, half),
|
|
986
|
-
makePlane({ x: 0, y: 0, z: 1 }, half),
|
|
987
|
-
makePlane({ x: 0, y: 0, z: -1 }, half)
|
|
988
|
-
];
|
|
989
|
-
return {
|
|
990
|
-
contents,
|
|
991
|
-
sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
function makeNode(plane, children) {
|
|
995
|
-
return { plane, children };
|
|
996
|
-
}
|
|
997
|
-
function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
|
|
998
|
-
return {
|
|
999
|
-
planes,
|
|
1000
|
-
nodes,
|
|
1001
|
-
leaves,
|
|
1002
|
-
brushes,
|
|
1003
|
-
leafBrushes,
|
|
1004
|
-
bmodels: []
|
|
1012
|
+
return originalCreateElement(tagName, options2);
|
|
1005
1013
|
};
|
|
1014
|
+
if (enableWebGL2) {
|
|
1015
|
+
const originalProtoGetContext = global.HTMLCanvasElement.prototype.getContext;
|
|
1016
|
+
global.HTMLCanvasElement.prototype.getContext = function(contextId, options2) {
|
|
1017
|
+
if (contextId === "webgl2") {
|
|
1018
|
+
return createMockWebGL2Context(this);
|
|
1019
|
+
}
|
|
1020
|
+
return originalProtoGetContext.call(this, contextId, options2);
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
global.Image = import_canvas.Image;
|
|
1024
|
+
global.ImageData = import_canvas.ImageData;
|
|
1025
|
+
if (typeof global.createImageBitmap === "undefined") {
|
|
1026
|
+
global.createImageBitmap = async function(image, _options) {
|
|
1027
|
+
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
1028
|
+
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
1029
|
+
const ctx = canvas2.getContext("2d");
|
|
1030
|
+
if (image.data) {
|
|
1031
|
+
ctx.putImageData(image, 0, 0);
|
|
1032
|
+
}
|
|
1033
|
+
return canvas2;
|
|
1034
|
+
}
|
|
1035
|
+
const canvas = new import_canvas.Canvas(100, 100);
|
|
1036
|
+
return canvas;
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
if (typeof global.btoa === "undefined") {
|
|
1040
|
+
global.btoa = function(str) {
|
|
1041
|
+
return Buffer.from(str, "binary").toString("base64");
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
if (typeof global.atob === "undefined") {
|
|
1045
|
+
global.atob = function(str) {
|
|
1046
|
+
return Buffer.from(str, "base64").toString("binary");
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (enablePointerLock) {
|
|
1050
|
+
MockPointerLock.setup(global.document);
|
|
1051
|
+
}
|
|
1052
|
+
if (typeof global.requestAnimationFrame === "undefined") {
|
|
1053
|
+
let lastTime = 0;
|
|
1054
|
+
global.requestAnimationFrame = (callback) => {
|
|
1055
|
+
const currTime = Date.now();
|
|
1056
|
+
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
1057
|
+
const id = setTimeout(() => {
|
|
1058
|
+
callback(currTime + timeToCall);
|
|
1059
|
+
}, timeToCall);
|
|
1060
|
+
lastTime = currTime + timeToCall;
|
|
1061
|
+
return id;
|
|
1062
|
+
};
|
|
1063
|
+
global.cancelAnimationFrame = (id) => {
|
|
1064
|
+
clearTimeout(id);
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1006
1067
|
}
|
|
1007
|
-
function
|
|
1008
|
-
|
|
1068
|
+
function teardownBrowserEnvironment() {
|
|
1069
|
+
delete global.window;
|
|
1070
|
+
delete global.document;
|
|
1071
|
+
delete global.navigator;
|
|
1072
|
+
delete global.localStorage;
|
|
1073
|
+
delete global.location;
|
|
1074
|
+
delete global.HTMLElement;
|
|
1075
|
+
delete global.HTMLCanvasElement;
|
|
1076
|
+
delete global.Image;
|
|
1077
|
+
delete global.ImageData;
|
|
1078
|
+
delete global.createImageBitmap;
|
|
1079
|
+
delete global.Event;
|
|
1080
|
+
delete global.CustomEvent;
|
|
1081
|
+
delete global.DragEvent;
|
|
1082
|
+
delete global.MouseEvent;
|
|
1083
|
+
delete global.KeyboardEvent;
|
|
1084
|
+
delete global.FocusEvent;
|
|
1085
|
+
delete global.WheelEvent;
|
|
1086
|
+
delete global.InputEvent;
|
|
1087
|
+
delete global.UIEvent;
|
|
1009
1088
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1089
|
+
|
|
1090
|
+
// src/setup/canvas.ts
|
|
1091
|
+
var import_canvas2 = require("@napi-rs/canvas");
|
|
1092
|
+
function createMockCanvas(width = 300, height = 150) {
|
|
1093
|
+
if (typeof document !== "undefined" && document.createElement) {
|
|
1094
|
+
const canvas2 = document.createElement("canvas");
|
|
1095
|
+
canvas2.width = width;
|
|
1096
|
+
canvas2.height = height;
|
|
1097
|
+
return canvas2;
|
|
1098
|
+
}
|
|
1099
|
+
const canvas = new import_canvas2.Canvas(width, height);
|
|
1100
|
+
const originalGetContext = canvas.getContext.bind(canvas);
|
|
1101
|
+
canvas.getContext = function(contextId, options) {
|
|
1102
|
+
if (contextId === "webgl2") {
|
|
1103
|
+
return createMockWebGL2Context(canvas);
|
|
1104
|
+
}
|
|
1105
|
+
if (contextId === "2d") {
|
|
1106
|
+
return originalGetContext("2d", options);
|
|
1107
|
+
}
|
|
1108
|
+
return originalGetContext(contextId, options);
|
|
1019
1109
|
};
|
|
1110
|
+
return canvas;
|
|
1020
1111
|
}
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1112
|
+
function createMockCanvasContext2D(canvas) {
|
|
1113
|
+
if (!canvas) {
|
|
1114
|
+
canvas = createMockCanvas();
|
|
1115
|
+
}
|
|
1116
|
+
return canvas.getContext("2d");
|
|
1117
|
+
}
|
|
1118
|
+
function captureCanvasDrawCalls(context) {
|
|
1119
|
+
const drawCalls = [];
|
|
1120
|
+
const methodsToSpy = [
|
|
1121
|
+
"fillRect",
|
|
1122
|
+
"strokeRect",
|
|
1123
|
+
"clearRect",
|
|
1124
|
+
"fillText",
|
|
1125
|
+
"strokeText",
|
|
1126
|
+
"drawImage",
|
|
1127
|
+
"beginPath",
|
|
1128
|
+
"closePath",
|
|
1129
|
+
"moveTo",
|
|
1130
|
+
"lineTo",
|
|
1131
|
+
"arc",
|
|
1132
|
+
"arcTo",
|
|
1133
|
+
"bezierCurveTo",
|
|
1134
|
+
"quadraticCurveTo",
|
|
1135
|
+
"stroke",
|
|
1136
|
+
"fill",
|
|
1137
|
+
"putImageData"
|
|
1029
1138
|
];
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1139
|
+
methodsToSpy.forEach((method) => {
|
|
1140
|
+
const original = context[method];
|
|
1141
|
+
if (typeof original === "function") {
|
|
1142
|
+
context[method] = function(...args) {
|
|
1143
|
+
drawCalls.push({ method, args });
|
|
1144
|
+
return original.apply(this, args);
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
return drawCalls;
|
|
1149
|
+
}
|
|
1150
|
+
function createMockImageData(width, height, fillColor) {
|
|
1151
|
+
const imageData = new import_canvas2.ImageData(width, height);
|
|
1152
|
+
if (fillColor) {
|
|
1153
|
+
const [r, g, b, a] = fillColor;
|
|
1154
|
+
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
1155
|
+
imageData.data[i] = r;
|
|
1156
|
+
imageData.data[i + 1] = g;
|
|
1157
|
+
imageData.data[i + 2] = b;
|
|
1158
|
+
imageData.data[i + 3] = a;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return imageData;
|
|
1162
|
+
}
|
|
1163
|
+
function createMockImage(width, height, src) {
|
|
1164
|
+
const img = new import_canvas2.Image();
|
|
1165
|
+
if (width) img.width = width;
|
|
1166
|
+
if (height) img.height = height;
|
|
1167
|
+
if (src) img.src = src;
|
|
1168
|
+
return img;
|
|
1034
1169
|
}
|
|
1035
1170
|
|
|
1036
|
-
// src/
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
writeLong: import_vitest.vi.fn(),
|
|
1042
|
-
writeString: import_vitest.vi.fn(),
|
|
1043
|
-
writeBytes: import_vitest.vi.fn(),
|
|
1044
|
-
getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
1045
|
-
reset: import_vitest.vi.fn(),
|
|
1046
|
-
// Legacy methods (if any)
|
|
1047
|
-
writeInt8: import_vitest.vi.fn(),
|
|
1048
|
-
writeUint8: import_vitest.vi.fn(),
|
|
1049
|
-
writeInt16: import_vitest.vi.fn(),
|
|
1050
|
-
writeUint16: import_vitest.vi.fn(),
|
|
1051
|
-
writeInt32: import_vitest.vi.fn(),
|
|
1052
|
-
writeUint32: import_vitest.vi.fn(),
|
|
1053
|
-
writeFloat: import_vitest.vi.fn(),
|
|
1054
|
-
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
1055
|
-
});
|
|
1056
|
-
var createNetChanMock = () => ({
|
|
1057
|
-
qport: 1234,
|
|
1058
|
-
// Sequencing
|
|
1059
|
-
incomingSequence: 0,
|
|
1060
|
-
outgoingSequence: 0,
|
|
1061
|
-
incomingAcknowledged: 0,
|
|
1062
|
-
// Reliable messaging
|
|
1063
|
-
incomingReliableAcknowledged: false,
|
|
1064
|
-
incomingReliableSequence: 0,
|
|
1065
|
-
outgoingReliableSequence: 0,
|
|
1066
|
-
reliableMessage: createBinaryWriterMock(),
|
|
1067
|
-
reliableLength: 0,
|
|
1068
|
-
// Fragmentation
|
|
1069
|
-
fragmentSendOffset: 0,
|
|
1070
|
-
fragmentBuffer: null,
|
|
1071
|
-
fragmentLength: 0,
|
|
1072
|
-
fragmentReceived: 0,
|
|
1073
|
-
// Timing
|
|
1074
|
-
lastReceived: 0,
|
|
1075
|
-
lastSent: 0,
|
|
1076
|
-
remoteAddress: { type: "IP", port: 1234 },
|
|
1077
|
-
// Methods
|
|
1078
|
-
setup: import_vitest.vi.fn(),
|
|
1079
|
-
reset: import_vitest.vi.fn(),
|
|
1080
|
-
transmit: import_vitest.vi.fn(),
|
|
1081
|
-
process: import_vitest.vi.fn(),
|
|
1082
|
-
canSendReliable: import_vitest.vi.fn(() => true),
|
|
1083
|
-
writeReliableByte: import_vitest.vi.fn(),
|
|
1084
|
-
writeReliableShort: import_vitest.vi.fn(),
|
|
1085
|
-
writeReliableLong: import_vitest.vi.fn(),
|
|
1086
|
-
writeReliableString: import_vitest.vi.fn(),
|
|
1087
|
-
getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
1088
|
-
needsKeepalive: import_vitest.vi.fn(() => false),
|
|
1089
|
-
isTimedOut: import_vitest.vi.fn(() => false)
|
|
1090
|
-
});
|
|
1091
|
-
var createBinaryStreamMock = () => ({
|
|
1092
|
-
getPosition: import_vitest.vi.fn(() => 0),
|
|
1093
|
-
getReadPosition: import_vitest.vi.fn(() => 0),
|
|
1094
|
-
getLength: import_vitest.vi.fn(() => 0),
|
|
1095
|
-
getRemaining: import_vitest.vi.fn(() => 0),
|
|
1096
|
-
seek: import_vitest.vi.fn(),
|
|
1097
|
-
setReadPosition: import_vitest.vi.fn(),
|
|
1098
|
-
hasMore: import_vitest.vi.fn(() => true),
|
|
1099
|
-
hasBytes: import_vitest.vi.fn((amount) => true),
|
|
1100
|
-
readChar: import_vitest.vi.fn(() => 0),
|
|
1101
|
-
readByte: import_vitest.vi.fn(() => 0),
|
|
1102
|
-
readShort: import_vitest.vi.fn(() => 0),
|
|
1103
|
-
readUShort: import_vitest.vi.fn(() => 0),
|
|
1104
|
-
readLong: import_vitest.vi.fn(() => 0),
|
|
1105
|
-
readULong: import_vitest.vi.fn(() => 0),
|
|
1106
|
-
readFloat: import_vitest.vi.fn(() => 0),
|
|
1107
|
-
readString: import_vitest.vi.fn(() => ""),
|
|
1108
|
-
readStringLine: import_vitest.vi.fn(() => ""),
|
|
1109
|
-
readCoord: import_vitest.vi.fn(() => 0),
|
|
1110
|
-
readAngle: import_vitest.vi.fn(() => 0),
|
|
1111
|
-
readAngle16: import_vitest.vi.fn(() => 0),
|
|
1112
|
-
readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
|
|
1113
|
-
readPos: import_vitest.vi.fn(),
|
|
1114
|
-
readDir: import_vitest.vi.fn()
|
|
1115
|
-
});
|
|
1171
|
+
// src/setup/node.ts
|
|
1172
|
+
function setupNodeEnvironment() {
|
|
1173
|
+
if (typeof global.fetch === "undefined") {
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1116
1176
|
|
|
1117
|
-
// src/
|
|
1118
|
-
var
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
modelIndex4: 0,
|
|
1154
|
-
frame: 0,
|
|
1155
|
-
skinNum: 0,
|
|
1156
|
-
effects: 0,
|
|
1157
|
-
renderfx: 0,
|
|
1158
|
-
solid: 0,
|
|
1159
|
-
sound: 0,
|
|
1160
|
-
event: 0,
|
|
1161
|
-
...overrides
|
|
1162
|
-
});
|
|
1163
|
-
var createGameStateSnapshotFactory = (overrides) => ({
|
|
1164
|
-
gravity: { x: 0, y: 0, z: -800 },
|
|
1165
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
1166
|
-
velocity: { x: 0, y: 0, z: 0 },
|
|
1167
|
-
viewangles: { x: 0, y: 0, z: 0 },
|
|
1168
|
-
level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
|
|
1169
|
-
entities: {
|
|
1170
|
-
activeCount: 0,
|
|
1171
|
-
worldClassname: "worldspawn"
|
|
1172
|
-
},
|
|
1173
|
-
packetEntities: [],
|
|
1174
|
-
pmFlags: 0,
|
|
1175
|
-
pmType: 0,
|
|
1176
|
-
waterlevel: 0,
|
|
1177
|
-
watertype: 0,
|
|
1178
|
-
deltaAngles: { x: 0, y: 0, z: 0 },
|
|
1179
|
-
health: 100,
|
|
1180
|
-
armor: 0,
|
|
1181
|
-
ammo: 0,
|
|
1182
|
-
blend: [0, 0, 0, 0],
|
|
1183
|
-
damageAlpha: 0,
|
|
1184
|
-
damageIndicators: [],
|
|
1185
|
-
stats: [],
|
|
1186
|
-
kick_angles: { x: 0, y: 0, z: 0 },
|
|
1187
|
-
kick_origin: { x: 0, y: 0, z: 0 },
|
|
1188
|
-
gunoffset: { x: 0, y: 0, z: 0 },
|
|
1189
|
-
gunangles: { x: 0, y: 0, z: 0 },
|
|
1190
|
-
gunindex: 0,
|
|
1191
|
-
pm_time: 0,
|
|
1192
|
-
gun_frame: 0,
|
|
1193
|
-
rdflags: 0,
|
|
1194
|
-
fov: 90,
|
|
1195
|
-
renderfx: 0,
|
|
1196
|
-
pm_flags: 0,
|
|
1197
|
-
pm_type: 0,
|
|
1198
|
-
...overrides
|
|
1199
|
-
});
|
|
1177
|
+
// src/setup/storage.ts
|
|
1178
|
+
var import_auto2 = require("fake-indexeddb/auto");
|
|
1179
|
+
function createMockLocalStorage(initialData = {}) {
|
|
1180
|
+
const storage = new Map(Object.entries(initialData));
|
|
1181
|
+
return {
|
|
1182
|
+
getItem: (key) => storage.get(key) || null,
|
|
1183
|
+
setItem: (key, value) => storage.set(key, value),
|
|
1184
|
+
removeItem: (key) => storage.delete(key),
|
|
1185
|
+
clear: () => storage.clear(),
|
|
1186
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
1187
|
+
get length() {
|
|
1188
|
+
return storage.size;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function createMockSessionStorage(initialData = {}) {
|
|
1193
|
+
return createMockLocalStorage(initialData);
|
|
1194
|
+
}
|
|
1195
|
+
function createMockIndexedDB() {
|
|
1196
|
+
if (typeof indexedDB === "undefined") {
|
|
1197
|
+
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
1198
|
+
}
|
|
1199
|
+
return indexedDB;
|
|
1200
|
+
}
|
|
1201
|
+
function createStorageTestScenario(storageType = "local") {
|
|
1202
|
+
const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
|
|
1203
|
+
return {
|
|
1204
|
+
storage,
|
|
1205
|
+
populate(data) {
|
|
1206
|
+
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
1207
|
+
},
|
|
1208
|
+
verify(key, value) {
|
|
1209
|
+
return storage.getItem(key) === value;
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1200
1213
|
|
|
1201
|
-
// src/
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
modelIndex: import_vitest2.vi.fn((model) => 0),
|
|
1210
|
-
centerprintf: import_vitest2.vi.fn()
|
|
1211
|
-
});
|
|
1212
|
-
var createMockGame = (seed = 12345) => {
|
|
1213
|
-
const spawnRegistry = new import_game.SpawnRegistry();
|
|
1214
|
-
const hooks = new import_game.ScriptHookRegistry();
|
|
1215
|
-
const game = {
|
|
1216
|
-
random: (0, import_shared2.createRandomGenerator)({ seed }),
|
|
1217
|
-
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
1218
|
-
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
1214
|
+
// src/setup/audio.ts
|
|
1215
|
+
function createMockAudioContext() {
|
|
1216
|
+
return {
|
|
1217
|
+
createGain: () => ({
|
|
1218
|
+
connect: () => {
|
|
1219
|
+
},
|
|
1220
|
+
gain: { value: 1, setValueAtTime: () => {
|
|
1221
|
+
} }
|
|
1219
1222
|
}),
|
|
1220
|
-
|
|
1221
|
-
|
|
1223
|
+
createOscillator: () => ({
|
|
1224
|
+
connect: () => {
|
|
1225
|
+
},
|
|
1226
|
+
start: () => {
|
|
1227
|
+
},
|
|
1228
|
+
stop: () => {
|
|
1229
|
+
},
|
|
1230
|
+
frequency: { value: 440 }
|
|
1222
1231
|
}),
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1232
|
+
createBufferSource: () => ({
|
|
1233
|
+
connect: () => {
|
|
1234
|
+
},
|
|
1235
|
+
start: () => {
|
|
1236
|
+
},
|
|
1237
|
+
stop: () => {
|
|
1238
|
+
},
|
|
1239
|
+
buffer: null,
|
|
1240
|
+
playbackRate: { value: 1 },
|
|
1241
|
+
loop: false
|
|
1228
1242
|
}),
|
|
1229
|
-
|
|
1230
|
-
|
|
1243
|
+
destination: {},
|
|
1244
|
+
currentTime: 0,
|
|
1245
|
+
state: "running",
|
|
1246
|
+
resume: async () => {
|
|
1247
|
+
},
|
|
1248
|
+
suspend: async () => {
|
|
1249
|
+
},
|
|
1250
|
+
close: async () => {
|
|
1251
|
+
},
|
|
1252
|
+
decodeAudioData: async (buffer) => ({
|
|
1253
|
+
duration: 1,
|
|
1254
|
+
length: 44100,
|
|
1255
|
+
sampleRate: 44100,
|
|
1256
|
+
numberOfChannels: 2,
|
|
1257
|
+
getChannelData: () => new Float32Array(44100)
|
|
1231
1258
|
}),
|
|
1232
|
-
|
|
1233
|
-
|
|
1259
|
+
createBuffer: (channels, length, sampleRate) => ({
|
|
1260
|
+
duration: length / sampleRate,
|
|
1261
|
+
length,
|
|
1262
|
+
sampleRate,
|
|
1263
|
+
numberOfChannels: channels,
|
|
1264
|
+
getChannelData: () => new Float32Array(length)
|
|
1234
1265
|
})
|
|
1235
1266
|
};
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
|
|
1243
|
-
fraction: 1,
|
|
1244
|
-
ent: null,
|
|
1245
|
-
allsolid: false,
|
|
1246
|
-
startsolid: false,
|
|
1247
|
-
endpos: end,
|
|
1248
|
-
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
1249
|
-
surfaceFlags: 0,
|
|
1250
|
-
contents: 0
|
|
1251
|
-
}));
|
|
1252
|
-
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
1253
|
-
const hooks = game.hooks;
|
|
1254
|
-
const entities = {
|
|
1255
|
-
spawn: import_vitest2.vi.fn(() => {
|
|
1256
|
-
const ent = new import_game.Entity(entityList.length + 1);
|
|
1257
|
-
entityList.push(ent);
|
|
1258
|
-
hooks.onEntitySpawn(ent);
|
|
1259
|
-
return ent;
|
|
1260
|
-
}),
|
|
1261
|
-
free: import_vitest2.vi.fn((ent) => {
|
|
1262
|
-
const idx = entityList.indexOf(ent);
|
|
1263
|
-
if (idx !== -1) {
|
|
1264
|
-
entityList.splice(idx, 1);
|
|
1267
|
+
}
|
|
1268
|
+
function setupMockAudioContext() {
|
|
1269
|
+
if (typeof global.AudioContext === "undefined" && typeof global.window !== "undefined") {
|
|
1270
|
+
global.AudioContext = class {
|
|
1271
|
+
constructor() {
|
|
1272
|
+
return createMockAudioContext();
|
|
1265
1273
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1274
|
+
};
|
|
1275
|
+
global.window.AudioContext = global.AudioContext;
|
|
1276
|
+
global.window.webkitAudioContext = global.AudioContext;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function teardownMockAudioContext() {
|
|
1280
|
+
if (global.AudioContext && global.AudioContext.toString().includes("class")) {
|
|
1281
|
+
delete global.AudioContext;
|
|
1282
|
+
delete global.window.AudioContext;
|
|
1283
|
+
delete global.window.webkitAudioContext;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
function captureAudioEvents(context) {
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// src/setup/timing.ts
|
|
1291
|
+
var activeMockRAF;
|
|
1292
|
+
function createMockRAF() {
|
|
1293
|
+
let callbacks = [];
|
|
1294
|
+
let nextId = 1;
|
|
1295
|
+
let currentTime = 0;
|
|
1296
|
+
const originalRAF = global.requestAnimationFrame;
|
|
1297
|
+
const originalCancelRAF = global.cancelAnimationFrame;
|
|
1298
|
+
const raf = (callback) => {
|
|
1299
|
+
const id = nextId++;
|
|
1300
|
+
callbacks.push({ id, callback });
|
|
1301
|
+
return id;
|
|
1302
|
+
};
|
|
1303
|
+
const cancel = (id) => {
|
|
1304
|
+
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
1305
|
+
};
|
|
1306
|
+
const mock = {
|
|
1307
|
+
tick(timestamp) {
|
|
1308
|
+
if (typeof timestamp !== "number") {
|
|
1309
|
+
currentTime += 16.6;
|
|
1310
|
+
} else {
|
|
1311
|
+
currentTime = timestamp;
|
|
1273
1312
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1313
|
+
const currentCallbacks = [...callbacks];
|
|
1314
|
+
callbacks = [];
|
|
1315
|
+
currentCallbacks.forEach(({ callback }) => {
|
|
1316
|
+
callback(currentTime);
|
|
1317
|
+
});
|
|
1318
|
+
},
|
|
1319
|
+
advance(deltaMs = 16.6) {
|
|
1320
|
+
this.tick(currentTime + deltaMs);
|
|
1321
|
+
},
|
|
1322
|
+
getCallbacks() {
|
|
1323
|
+
return callbacks.map((c) => c.callback);
|
|
1324
|
+
},
|
|
1325
|
+
reset() {
|
|
1326
|
+
callbacks = [];
|
|
1327
|
+
nextId = 1;
|
|
1328
|
+
currentTime = 0;
|
|
1329
|
+
},
|
|
1330
|
+
enable() {
|
|
1331
|
+
activeMockRAF = this;
|
|
1332
|
+
global.requestAnimationFrame = raf;
|
|
1333
|
+
global.cancelAnimationFrame = cancel;
|
|
1334
|
+
},
|
|
1335
|
+
disable() {
|
|
1336
|
+
if (activeMockRAF === this) {
|
|
1337
|
+
activeMockRAF = void 0;
|
|
1338
|
+
}
|
|
1339
|
+
if (originalRAF) {
|
|
1340
|
+
global.requestAnimationFrame = originalRAF;
|
|
1341
|
+
} else {
|
|
1342
|
+
delete global.requestAnimationFrame;
|
|
1343
|
+
}
|
|
1344
|
+
if (originalCancelRAF) {
|
|
1345
|
+
global.cancelAnimationFrame = originalCancelRAF;
|
|
1346
|
+
} else {
|
|
1347
|
+
delete global.cancelAnimationFrame;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
return mock;
|
|
1352
|
+
}
|
|
1353
|
+
function createMockPerformance(startTime = 0) {
|
|
1354
|
+
let currentTime = startTime;
|
|
1355
|
+
const mockPerf = {
|
|
1356
|
+
now: () => currentTime,
|
|
1357
|
+
timeOrigin: startTime,
|
|
1358
|
+
timing: {
|
|
1359
|
+
navigationStart: startTime
|
|
1303
1360
|
},
|
|
1304
|
-
|
|
1305
|
-
intermission_angle: { x: 0, y: 0, z: 0 },
|
|
1306
|
-
intermission_origin: { x: 0, y: 0, z: 0 },
|
|
1307
|
-
next_auto_save: 0,
|
|
1308
|
-
health_bar_entities: null
|
|
1361
|
+
clearMarks: () => {
|
|
1309
1362
|
},
|
|
1310
|
-
|
|
1311
|
-
forEachEntity: import_vitest2.vi.fn((callback) => {
|
|
1312
|
-
entityList.forEach(callback);
|
|
1313
|
-
}),
|
|
1314
|
-
find: import_vitest2.vi.fn((predicate) => {
|
|
1315
|
-
return entityList.find(predicate);
|
|
1316
|
-
}),
|
|
1317
|
-
findByClassname: import_vitest2.vi.fn((classname) => {
|
|
1318
|
-
return entityList.find((e) => e.classname === classname);
|
|
1319
|
-
}),
|
|
1320
|
-
beginFrame: import_vitest2.vi.fn((timeSeconds) => {
|
|
1321
|
-
entities.timeSeconds = timeSeconds;
|
|
1322
|
-
}),
|
|
1323
|
-
targetAwareness: {
|
|
1324
|
-
timeSeconds: 10,
|
|
1325
|
-
frameNumber: 1,
|
|
1326
|
-
sightEntity: null,
|
|
1327
|
-
soundEntity: null
|
|
1363
|
+
clearMeasures: () => {
|
|
1328
1364
|
},
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1365
|
+
clearResourceTimings: () => {
|
|
1366
|
+
},
|
|
1367
|
+
getEntries: () => [],
|
|
1368
|
+
getEntriesByName: () => [],
|
|
1369
|
+
getEntriesByType: () => [],
|
|
1370
|
+
mark: () => {
|
|
1371
|
+
},
|
|
1372
|
+
measure: () => {
|
|
1373
|
+
},
|
|
1374
|
+
setResourceTimingBufferSize: () => {
|
|
1375
|
+
},
|
|
1376
|
+
toJSON: () => ({}),
|
|
1377
|
+
addEventListener: () => {
|
|
1378
|
+
},
|
|
1379
|
+
removeEventListener: () => {
|
|
1380
|
+
},
|
|
1381
|
+
dispatchEvent: () => true
|
|
1382
|
+
};
|
|
1383
|
+
mockPerf.advance = (deltaMs) => {
|
|
1384
|
+
currentTime += deltaMs;
|
|
1385
|
+
};
|
|
1386
|
+
mockPerf.setTime = (time) => {
|
|
1387
|
+
currentTime = time;
|
|
1388
|
+
};
|
|
1389
|
+
return mockPerf;
|
|
1390
|
+
}
|
|
1391
|
+
function createControlledTimer() {
|
|
1392
|
+
let currentTime = 0;
|
|
1393
|
+
let timers = [];
|
|
1394
|
+
let nextId = 1;
|
|
1395
|
+
const originalSetTimeout = global.setTimeout;
|
|
1396
|
+
const originalClearTimeout = global.clearTimeout;
|
|
1397
|
+
const originalSetInterval = global.setInterval;
|
|
1398
|
+
const originalClearInterval = global.clearInterval;
|
|
1399
|
+
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
1400
|
+
const id = nextId++;
|
|
1401
|
+
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
1402
|
+
return id;
|
|
1403
|
+
};
|
|
1404
|
+
const mockClearTimeout = (id) => {
|
|
1405
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1406
|
+
};
|
|
1407
|
+
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
1408
|
+
const id = nextId++;
|
|
1409
|
+
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
1410
|
+
return id;
|
|
1411
|
+
};
|
|
1412
|
+
const mockClearInterval = (id) => {
|
|
1413
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1337
1414
|
};
|
|
1415
|
+
global.setTimeout = mockSetTimeout;
|
|
1416
|
+
global.clearTimeout = mockClearTimeout;
|
|
1417
|
+
global.setInterval = mockSetInterval;
|
|
1418
|
+
global.clearInterval = mockClearInterval;
|
|
1338
1419
|
return {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1420
|
+
tick() {
|
|
1421
|
+
this.advanceBy(0);
|
|
1422
|
+
},
|
|
1423
|
+
advanceBy(ms) {
|
|
1424
|
+
const targetTime = currentTime + ms;
|
|
1425
|
+
while (true) {
|
|
1426
|
+
let earliest = null;
|
|
1427
|
+
for (const t of timers) {
|
|
1428
|
+
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
1429
|
+
earliest = t;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (!earliest || earliest.dueTime > targetTime) {
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
currentTime = earliest.dueTime;
|
|
1436
|
+
const { callback, args, interval, id } = earliest;
|
|
1437
|
+
if (interval !== void 0) {
|
|
1438
|
+
earliest.dueTime += interval;
|
|
1439
|
+
if (interval === 0) earliest.dueTime += 1;
|
|
1440
|
+
} else {
|
|
1441
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1442
|
+
}
|
|
1443
|
+
callback(...args);
|
|
1444
|
+
}
|
|
1445
|
+
currentTime = targetTime;
|
|
1446
|
+
},
|
|
1447
|
+
clear() {
|
|
1448
|
+
timers = [];
|
|
1449
|
+
},
|
|
1450
|
+
restore() {
|
|
1451
|
+
global.setTimeout = originalSetTimeout;
|
|
1452
|
+
global.clearTimeout = originalClearTimeout;
|
|
1453
|
+
global.setInterval = originalSetInterval;
|
|
1454
|
+
global.clearInterval = originalClearInterval;
|
|
1455
|
+
}
|
|
1350
1456
|
};
|
|
1351
1457
|
}
|
|
1352
|
-
function
|
|
1353
|
-
|
|
1458
|
+
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
1459
|
+
if (!activeMockRAF) {
|
|
1460
|
+
throw new Error("simulateFrames requires an active MockRAF. Ensure createMockRAF().enable() is called.");
|
|
1461
|
+
}
|
|
1462
|
+
for (let i = 0; i < count; i++) {
|
|
1463
|
+
if (callback) callback(i);
|
|
1464
|
+
activeMockRAF.advance(frameTimeMs);
|
|
1465
|
+
}
|
|
1354
1466
|
}
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1467
|
+
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
1468
|
+
for (let i = 0; i < count; i++) {
|
|
1469
|
+
if (callback) callback(i);
|
|
1470
|
+
mock.advance(frameTimeMs);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/e2e/playwright.ts
|
|
1475
|
+
var import_playwright = require("playwright");
|
|
1476
|
+
var import_http = require("http");
|
|
1477
|
+
var import_serve_handler = __toESM(require("serve-handler"), 1);
|
|
1478
|
+
async function createPlaywrightTestClient(options = {}) {
|
|
1479
|
+
let staticServer;
|
|
1480
|
+
let clientUrl = options.clientUrl;
|
|
1481
|
+
const rootPath = options.rootPath || process.cwd();
|
|
1482
|
+
if (!clientUrl) {
|
|
1483
|
+
staticServer = (0, import_http.createServer)((request, response) => {
|
|
1484
|
+
return (0, import_serve_handler.default)(request, response, {
|
|
1485
|
+
public: rootPath,
|
|
1486
|
+
cleanUrls: false,
|
|
1487
|
+
headers: [
|
|
1488
|
+
{
|
|
1489
|
+
source: "**/*",
|
|
1490
|
+
headers: [
|
|
1491
|
+
{ key: "Cache-Control", value: "no-cache" },
|
|
1492
|
+
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
1493
|
+
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
1494
|
+
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
1495
|
+
]
|
|
1496
|
+
}
|
|
1497
|
+
]
|
|
1498
|
+
});
|
|
1499
|
+
});
|
|
1500
|
+
await new Promise((resolve) => {
|
|
1501
|
+
if (!staticServer) return;
|
|
1502
|
+
staticServer.listen(0, () => {
|
|
1503
|
+
const addr = staticServer?.address();
|
|
1504
|
+
const port = typeof addr === "object" ? addr?.port : 0;
|
|
1505
|
+
clientUrl = `http://localhost:${port}`;
|
|
1506
|
+
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
1507
|
+
resolve();
|
|
1508
|
+
});
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
const browser = await import_playwright.chromium.launch({
|
|
1512
|
+
headless: options.headless ?? true,
|
|
1513
|
+
args: [
|
|
1514
|
+
"--use-gl=egl",
|
|
1515
|
+
"--ignore-gpu-blocklist",
|
|
1516
|
+
...options.launchOptions?.args || []
|
|
1517
|
+
],
|
|
1518
|
+
...options.launchOptions
|
|
1519
|
+
});
|
|
1520
|
+
const width = options.width || 1280;
|
|
1521
|
+
const height = options.height || 720;
|
|
1522
|
+
const context = await browser.newContext({
|
|
1523
|
+
viewport: { width, height },
|
|
1524
|
+
deviceScaleFactor: 1,
|
|
1525
|
+
...options.contextOptions
|
|
1526
|
+
});
|
|
1527
|
+
const page = await context.newPage();
|
|
1528
|
+
const close = async () => {
|
|
1529
|
+
await browser.close();
|
|
1530
|
+
if (staticServer) {
|
|
1531
|
+
staticServer.close();
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
const navigate = async (url) => {
|
|
1535
|
+
const targetUrl = url || clientUrl;
|
|
1536
|
+
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
1537
|
+
let finalUrl = targetUrl;
|
|
1538
|
+
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
1539
|
+
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
1540
|
+
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
1541
|
+
}
|
|
1542
|
+
console.log(`Navigating to: ${finalUrl}`);
|
|
1543
|
+
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
1544
|
+
};
|
|
1545
|
+
return {
|
|
1546
|
+
browser,
|
|
1547
|
+
context,
|
|
1548
|
+
page,
|
|
1549
|
+
server: staticServer,
|
|
1550
|
+
close,
|
|
1551
|
+
navigate,
|
|
1552
|
+
waitForGame: async (timeout = 1e4) => {
|
|
1553
|
+
await waitForGameReady(page, timeout);
|
|
1554
|
+
},
|
|
1555
|
+
injectInput: async (type, data) => {
|
|
1556
|
+
await page.evaluate(({ type: type2, data: data2 }) => {
|
|
1557
|
+
if (window.injectGameInput) window.injectGameInput(type2, data2);
|
|
1558
|
+
}, { type, data });
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
async function waitForGameReady(page, timeout = 1e4) {
|
|
1563
|
+
try {
|
|
1564
|
+
await page.waitForFunction(() => {
|
|
1565
|
+
return window.gameInstance && window.gameInstance.isReady;
|
|
1566
|
+
}, null, { timeout });
|
|
1567
|
+
} catch (e) {
|
|
1568
|
+
await page.waitForSelector("canvas", { timeout });
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
async function captureGameState(page) {
|
|
1572
|
+
return await page.evaluate(() => {
|
|
1573
|
+
if (window.gameInstance && window.gameInstance.getState) {
|
|
1574
|
+
return window.gameInstance.getState();
|
|
1575
|
+
}
|
|
1576
|
+
return {};
|
|
1577
|
+
});
|
|
1357
1578
|
}
|
|
1358
1579
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1359
1580
|
0 && (module.exports = {
|
|
1360
1581
|
InputInjector,
|
|
1361
1582
|
MockPointerLock,
|
|
1583
|
+
MockTransport,
|
|
1362
1584
|
captureAudioEvents,
|
|
1363
1585
|
captureCanvasDrawCalls,
|
|
1364
1586
|
captureGameState,
|
|
@@ -1373,13 +1595,21 @@ function createEntity() {
|
|
|
1373
1595
|
createMockCanvasContext2D,
|
|
1374
1596
|
createMockEngine,
|
|
1375
1597
|
createMockGame,
|
|
1598
|
+
createMockGameState,
|
|
1376
1599
|
createMockImage,
|
|
1377
1600
|
createMockImageData,
|
|
1378
1601
|
createMockIndexedDB,
|
|
1379
1602
|
createMockLocalStorage,
|
|
1603
|
+
createMockNetworkAddress,
|
|
1380
1604
|
createMockPerformance,
|
|
1381
1605
|
createMockRAF,
|
|
1606
|
+
createMockServer,
|
|
1607
|
+
createMockServerClient,
|
|
1608
|
+
createMockServerState,
|
|
1609
|
+
createMockServerStatic,
|
|
1382
1610
|
createMockSessionStorage,
|
|
1611
|
+
createMockTransport,
|
|
1612
|
+
createMockUDPSocket,
|
|
1383
1613
|
createMockWebGL2Context,
|
|
1384
1614
|
createNetChanMock,
|
|
1385
1615
|
createPlayerStateFactory,
|