Repository navigation
Expand file tree
/
Copy pathGameInterfaces.ts
More file actions
660 lines (558 loc) · 24.7 KB
/
Copy pathGameInterfaces.ts
File metadata and controls
660 lines (558 loc) · 24.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
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
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
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
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
import type { BaseClientEdictHandler } from './ClientEdict.ts';
import type { ClientEngineAPI as ClientEngineApiValue } from '../engine/client/ClientEngineAPI.ts';
import type { CommonEngineAPI as CommonEngineApiValue } from '../engine/common/CommonEngineAPI.ts';
import type { ServerEngineAPI as ServerEngineApiValue } from '../engine/server/ServerEngineAPI.ts';
import type { ClientEdict as ClientEdictValue } from '../engine/client/ClientEntities.ts';
import type { ServerEdict as ServerEdictValue } from '../engine/server/Edict.ts';
import type { BitmapFont as BitmapFontValue } from '../engine/client/BitmapFont.ts';
import type { GLTexture as GLTextureValue } from '../engine/client/GL.ts';
import type { SpritePlayback as SpritePlaybackValue } from '../engine/client/renderer/sprite/SpritePlayback.ts';
import type { Action as ActionValue, ColorPicker as ColorPickerValue, KeyBindItem as KeyBindItemValue, MenuItem as MenuItemValue, SaveSlotItem as SaveSlotItemValue, Textbox as TextboxValue } from '../engine/client/menu/MenuItem.ts';
import type { DialogPage as DialogPageValue, ListPage as ListPageValue, MenuPage as MenuPageValue } from '../engine/client/menu/MenuPage.ts';
import type { BackButtonAnchor as BackButtonAnchorValue } from '../engine/client/menu/MenuPage.ts';
import type { MenuViewport as MenuViewportValue, MenuViewportCorner as MenuViewportCornerValue } from '../engine/client/menu/MenuViewport.ts';
import type { MenuPic as MenuPicValue } from '../engine/client/Menu.ts';
import type { SFX as SFXValue } from '../engine/client/Sound.ts';
import type CvarValue from '../engine/common/Cvar.ts';
import type Vector from './Vector.ts';
import type { gameCapabilities } from './Defs.ts';
import type { PmoveConfiguration as PmoveConfigurationValue, PmoveQuake2Configuration as PmoveQuake2ConfigurationValue } from '../shared/Pmove.ts';
import type { BaseModel } from '../engine/common/model/BaseModel.ts';
import type { StartGameInterface } from '../engine/client/ClientLifecycle.ts';
import type { DiscoveredSession as DiscoveredSessionValue, SessionDiscoveryStatus as SessionDiscoveryStatusValue } from '../engine/client/menu/SessionDiscovery.ts';
export type { StartGameInterface } from '../engine/client/ClientLifecycle.ts';
export type { ClientGameEventMap, ServerGameEventMap, TypedEventBus } from '../engine/common/EventBus.ts';
export type DiscoveredSession = DiscoveredSessionValue;
export type SessionDiscoveryStatus = SessionDiscoveryStatusValue;
export type ClientEngineAPI = Readonly<ClientEngineApiValue>;
export type ServerEngineAPI = Readonly<ServerEngineApiValue>;
export type CommonEngineAPI = Readonly<CommonEngineApiValue>;
export type ClientEdict = Readonly<ClientEdictValue>;
export type ServerEdict = Readonly<ServerEdictValue>;
export type GLTexture = GLTextureValue;
export type SpritePlayback = SpritePlaybackValue;
export type { DrawSpriteOptions } from '../engine/client/Draw.ts';
export type { LoadSpriteOptions } from '../engine/client/renderer/sprite/SpriteAssets.ts';
export type BitmapFont = BitmapFontValue;
export type Cvar = Readonly<CvarValue>;
// Menu widgets are mutable-by-design (game code configures labels/items directly), so these
// are plain aliases rather than Readonly wrappers.
export type MenuPage = MenuPageValue;
export type DialogPage = DialogPageValue;
export type ListPage = ListPageValue;
export type MenuItem = MenuItemValue;
export type Action = ActionValue;
export type Textbox = TextboxValue;
export type ColorPicker = ColorPickerValue;
export type SaveSlotItem = SaveSlotItemValue;
export type KeyBindItem = KeyBindItemValue;
export type MenuPic = MenuPicValue;
export type BackButtonAnchor = BackButtonAnchorValue;
export type MenuViewport = MenuViewportValue;
export type MenuViewportCorner = MenuViewportCornerValue;
export type PmoveConfiguration = Readonly<PmoveConfigurationValue>;
export type PmoveQuake2Configuration = Readonly<PmoveQuake2ConfigurationValue>;
export interface SerializableObject {
[key: string]: SerializableType;
}
export type SerializableType = string | number | boolean | Vector | ServerEdict | SerializableObject | SerializableType[] | null;
export type ClientSerializableType = string | number | boolean | Vector | ClientEdict | ClientSerializableType[] | null;
export type ClientEventValue = ClientSerializableType | object;
export type ClientdataMap = Record<string, ClientSerializableType>;
export interface ServerEntityReference {
readonly edict?: ServerEdict | null;
readonly edictId?: number | null;
}
export type EdictValueType = string | number | boolean | Vector | ServerEdict | ServerEntityReference | null;
export type EdictData = Record<string, EdictValueType>;
export type SFX = Readonly<SFXValue>;
/**
* What the engine draws as the first-person weapon model. The engine only reads it.
*/
export interface ViewmodelConfig {
readonly visible: boolean;
/** The model to draw. `null` while the game has none, in which case nothing is drawn. */
readonly model: BaseModel | null;
readonly frame: number;
}
export type ViewportDimensions = {
width: number;
height: number;
};
export type RefDef = {
vrect: ViewportDimensions;
vieworg: Vector;
viewangles: Vector;
};
export interface ParsedQC {
cd: string;
origin: Vector;
base: string | null;
skin: string | null;
frames: string[];
animations: Record<string, number[]>;
scale: number;
}
export type ViewportResizeEvent = ViewportDimensions;
export type ClientDamageEvent = {
damageReceived: number;
armorLost: number;
attackOrigin: Vector;
};
export type HostAlertSeverity = 'info' | 'error';
/**
* Payload for the `host.alert` event, published by `Host.EndGame`/`Host.Error` instead of
* calling into the menu system directly -- game code decides independently whether/how to
* present it (see docs/events.md#host).
*/
export interface HostAlertEvent {
readonly title: string;
readonly message: string;
readonly severity: HostAlertSeverity;
}
/**
* What a first-person view is calculated from, for a client game that calculates its own (`calcView`). The vectors
* are the engine's and valid for the call only; read them, and copy what is kept.
*/
export interface ViewInput {
/** Where the player is. */
readonly origin: Vector;
/** The angles of the player entity, which are not the view's: they face where the model faces. */
readonly angles: Vector;
/** How fast the player moves. */
readonly velocity: Vector;
/** Where the player looks, from the input and the prediction. Prediction reads it, so a game does not write it back. */
readonly viewangles: Vector;
/** The kick the server gave the view, which is added to the angles last. */
readonly punchangle: Vector;
/** Height of the eye above the origin. */
readonly viewheight: number;
/** Whether the player stands on something. */
readonly onground: boolean;
/** Client time in seconds. */
readonly time: number;
/** Seconds since the last frame. */
readonly frametime: number;
/** The `viewsize` variable. */
readonly viewsize: number;
}
/**
* Where a first-person view puts the eye and the weapon. A `calcView` writes every member, in place.
*/
export interface ViewResult {
/** The eye. */
readonly vieworg: Vector;
/** Where the eye looks, with the punch angle already in it. */
readonly viewangles: Vector;
/** Where the weapon model is. */
readonly weaponOrigin: Vector;
/** The angles of the weapon model. */
readonly weaponAngles: Vector;
}
export type PostProcessColorGradeDescriptor = {
readonly saturation?: number;
readonly contrast?: number;
readonly exposure?: number;
readonly tintColor?: Vector;
readonly tintStrength?: number;
readonly pulseStrength?: number;
readonly pulsePeriod?: number;
};
export type PostProcessBlurDescriptor = {
readonly radius?: number;
};
/**
* A tint over the whole view. Entries of this effect that follow each other are mixed into one tint, in the
* order they are applied, as Quake mixes its color shifts: each is laid over the result of the ones before.
*/
export type PostProcessColorShiftDescriptor = {
/** Red, green and blue, each 0 to 1. Black when left out. */
readonly color?: ArrayLike<number>;
/** How much the color covers the view, 0 to 1. Nothing when left out. */
readonly alpha?: number;
};
/**
* Where in the frame an entry is applied. `scene` is before the renderer's own effects (bloom, warp), so they see
* the result; `final` is after them, over everything and under the HUD. Each effect has a default.
*/
export type PostProcessStage = 'scene' | 'final';
/**
* The shape of a pulse over time, in seconds: it fades in from nothing, holds, fades out and is gone. Parts that
* are left out are zero.
*/
export interface PostProcessTiming {
readonly fadeIn?: number;
readonly hold?: number;
readonly fadeOut?: number;
}
/**
* Whether a pulse sits below or above the entries of the stack it shares a channel with.
*/
export type PostProcessPlacement = 'below' | 'above';
/**
* What every entry of the stack and every pulse carries besides its effect and settings.
*/
export interface PostProcessEntryOptions {
/**
* Names the entry. A new stack keeps the entries it shares a key with (an effect id without a key matches the
* n-th entry of that id) and fades them from their old settings to the new ones. For a pulse, a pulse that is
* already running under the same key restarts instead of a second one stacking up.
*/
readonly key?: string;
/**
* Where in the frame the entry is applied, `scene` unless the effect says otherwise.
*/
readonly stage?: PostProcessStage;
}
export type PostProcessEffectDescriptor =
| ({ readonly id: 'color-grade'; readonly settings: PostProcessColorGradeDescriptor } & PostProcessEntryOptions)
| ({ readonly id: 'blur'; readonly settings: PostProcessBlurDescriptor } & PostProcessEntryOptions)
| ({ readonly id: 'color-shift'; readonly settings: PostProcessColorShiftDescriptor } & PostProcessEntryOptions);
export type PostProcessStack = readonly PostProcessEffectDescriptor[];
/**
* An entry that lives for a while and then removes itself, see `PostProcess.pulse`.
*/
export type PostProcessPulseDescriptor = PostProcessEffectDescriptor & {
readonly timing: PostProcessTiming;
readonly placement?: PostProcessPlacement;
};
/**
* What `PostProcess.setStack` can be asked besides the stack.
*/
export interface PostProcessStackOptions {
/**
* The channel the stack belongs to. Channels are composed in the order they were first used and replaced and
* cleared on their own, so two parts of a game do not have to know about each other. `'default'` when left out.
*/
readonly channel?: string;
/**
* Seconds to cross-fade from the channel's previous stack to this one: new entries fade in from nothing, entries
* that are gone fade out, entries that stay move to their new settings. Instant when left out.
*/
readonly fade?: number;
}
/**
* What `PostProcess.pulse` can be asked besides the entry.
*/
export interface PostProcessPulseOptions {
/**
* The channel whose entries the pulse is composed with. `'default'` when left out.
*/
readonly channel?: string;
}
/**
* Everything the engine calls, reads, or writes on a running client-side game instance.
*
* The engine constructs a new instance through `ClientGameConstructor` every time the server
* announces a map (when connecting and on every changelevel) and calls `shutdown` when the client
* disconnects. A changelevel replaces the instance without calling `shutdown` on the old one. A
* game's client API class declares `implements ClientGameInterface` and its `main.ts` asserts
* `satisfies GameModuleInterface`, so a change on either side of this boundary is a compile error
* instead of a runtime surprise. Members that are not listed here are internal to the game.
*/
export interface ClientGameInterface {
/**
* The player's replicated state. The engine writes the values the server sends straight into
* this object and raises an error if it is `null` when such an update arrives, so a game sets
* it up in its constructor.
*/
readonly clientdata: ClientdataMap | null;
/**
* The first-person weapon model to draw. The engine only reads it; `null` draws nothing.
*/
readonly viewmodel: ViewmodelConfig | null;
/**
* Called once per map, after the map's models and sounds finished loading and the renderer was
* prepared for it (when connecting and after every changelevel). When a savegame is being
* restored, `loadGame` follows.
*/
init(): void;
/**
* Called when the client disconnects from the server, right before the engine drops its
* reference to this instance. Not called on a changelevel: the engine replaces the instance with
* a new one.
*
* The order on a disconnect is: `shutdown()`, then `client.disconnected` is published, then the
* map's `eventBus` listeners are released. A game that unsubscribes in `shutdown()` therefore
* does not hear `client.disconnected`; one that leaves its listeners to the engine does.
*/
shutdown(): void;
/**
* Called once per client frame after the connection is fully established, before the client
* entities think.
*/
startFrame(): void;
/**
* Called every frame the HUD is drawn. Skipped while the `nohud` cvar is set.
*/
draw(): void;
/**
* Called every frame the HUD is drawn, right after `draw()`, and also while the `nohud` cvar is set. For what the
* player has to see whatever the HUD shows, such as the pause sign. Leave it out when there is none.
*/
drawOverlay?(): void;
/**
* Called every frame while the client is connecting or changing level, right after the engine
* cleared the screen to black.
*/
drawLoading(): void;
/**
* Called when a savegame is written. The returned string is stored next to the server state.
*/
saveGame(): string;
/**
* Restores what `saveGame` produced. Called right after `init` when a savegame is being restored.
*/
loadGame(data: string): void;
/**
* Called for every client event the server sends to this player.
* @param code The event code, as defined by the game.
*/
handleClientEvent(code: number, ...args: ClientEventValue[]): void;
/**
* Called each frame after the engine calculated the view, so the game can adjust it before it is
* rendered.
*/
updateRefDef(refdef: RefDef): void;
/**
* How many pixels at the bottom of the screen the game's HUD keeps for itself at a view size, so that the
* 3D view is drawn above them (a status bar). Asked whenever the view rectangle is calculated, and not during
* an intermission, which uses the whole screen. Leave it out to get the whole height of the screen.
* @param viewsize The value of the `viewsize` variable, 30 to 120.
* @returns Pixels, 0 or more.
*/
hudReservedHeight?(viewsize: number): number;
/**
* Calculates the first-person view of a frame, in place of the engine's (bob, roll into a strafe, damage kick,
* idle sway, smoothing of steps). The engine does the pitch drift before, and after it: the model and frame of
* the weapon, `updateRefDef`, and the chase camera. `ClientEngineAPI.View.calculateDefault` is the engine's own
* calculation, for a game that wants it and adjusts the result. Leave it out to get the engine's.
* @param input What the view is calculated from.
* @param result Where the eye and the weapon go. Write every member.
*/
calcView?(input: ViewInput, result: ViewResult): void;
}
/**
* The static side of a game's client API class: how the engine creates and initializes it.
*/
export interface ClientGameConstructor {
/**
* Creates the game instance for one map of a server connection. The engine calls it while
* handling the server's serverdata message (when connecting and on every changelevel), after
* `IsServerCompatible` accepted the server.
*/
new (engineAPI: ClientEngineAPI): ClientGameInterface;
/**
* One-time module initialization, called once when the game module is loaded on the client
* (e.g. to register menu pages).
*/
Init(engineAPI: ClientEngineAPI): void;
/**
* Counterpart of `Init`. Reserved: the engine has no module-unload path yet and does not call it.
*/
Shutdown(engineAPI: ClientEngineAPI): void;
/**
* Lets the game replace the engine's default way of starting a game. Called once, right after
* `Init`.
* @returns The game's start behavior, or `null` to keep the engine default.
*/
GetStartGameInterface(engineAPI: ClientEngineAPI): StartGameInterface | null;
/**
* Called when a client entity is assigned a classname, to let the game attach client-side
* behavior to it.
* @returns The handler class for that classname, or `null` when the game has none.
*/
GetClientEdictHandler(classname: string): typeof BaseClientEdictHandler | null;
/**
* Decides whether this client can play on a server running the given game version. Called before
* the game instance is created.
* @param version The server's game version as [major, minor, patch].
*/
IsServerCompatible(version: number[]): boolean;
}
export interface PlayerEntitySpawnParamsDynamic {
saveSpawnParameters(): string;
restoreSpawnParameters(data: string): void;
}
/**
* Describes one server setting. Game-side helper shape: games return it from their own static
* helpers, the engine does not call them.
*/
export interface ServerInfoField {
name: string;
label: string;
type: 'string' | 'number' | 'boolean' | 'maplist' | 'enum';
enumValues?: Record<string, string | number>;
}
export type SerializedPrimitive = string | number | boolean | null;
export type SerializedSkipped = ['X'];
export type SerializedInfinity = ['I', number];
export type SerializedPrimitiveValue = ['P', SerializedPrimitive];
export type SerializedFunction = ['F', string];
export type SerializedArray = ['A', SerializedValue[]];
export type SerializedEdictReference = ['E', number | null];
export type SerializedObject = ['S', SerializedData];
export type SerializedVector = ['V', ...number[]];
export type SerializedValue = SerializedSkipped | SerializedInfinity | SerializedPrimitiveValue | SerializedFunction | SerializedArray | SerializedEdictReference | SerializedObject | SerializedVector;
export type SerializedData = Record<string, SerializedValue>;
interface SerializableEntityCandidate {
readonly classname?: unknown;
readonly serialize?: unknown;
readonly deserialize?: unknown;
}
export abstract class SerializableEntity {
static [Symbol.hasInstance](value: unknown): boolean {
if (value === null || typeof value !== 'object') {
return false;
}
const candidate = value as SerializableEntityCandidate;
return typeof candidate.classname === 'string'
&& typeof candidate.serialize === 'function'
&& typeof candidate.deserialize === 'function';
}
abstract classname: string;
abstract serialize(): SerializedData;
abstract deserialize(data: SerializedData): void;
}
/**
* Everything the engine calls, reads, or writes on a running server-side game instance.
*
* The engine constructs one instance per map load through `ServerGameConstructor`. A game's server
* API class declares `implements ServerGameInterface` and its `main.ts` asserts
* `satisfies GameModuleInterface`, so a change on either side of this boundary is a compile error
* instead of a runtime surprise. Members that are not listed here are internal to the game.
*/
export interface ServerGameInterface {
/**
* Server time in seconds. The engine writes it right before every call into the game: the
* current server time for touches and player callbacks, and the scheduled time when an entity's
* think runs (between the current server time and the end of the current frame). Game code
* should treat it as read-only.
*/
time: number;
/**
* Duration of the current server frame in seconds. The engine writes it once per frame.
*/
frametime: number;
/**
* While non-zero, the engine re-links every entity each frame so stationary triggers re-check
* their contacts (e.g. telefrag triggers). The game sets it (typically to 2), the engine
* decrements it once per frame.
*/
force_retouch: number;
/**
* Game-defined bit field of progress that survives level changes (the classic Quake use is the
* collected episode runes). The engine passes it to `init` on every map load and reads it back
* at changelevel to carry it into the next map. The game owns and mutates it.
*/
readonly serverflags: number;
/**
* Called once per map load, after the engine prepared the player entities and client fields and
* before the worldspawn entity is spawned.
* @param mapname The map being loaded.
* @param serverflags The value carried over from the previous map.
*/
init(mapname: string, serverflags: number): void;
/**
* Called when the server shuts down (quitting, the `map` command, leaving a listen server, a
* failed map load, ...), right before the engine drops its reference to this instance. A
* changelevel does not call it: the engine replaces the instance with a new one for the next map.
* @param isCrashShutdown True for an emergency shutdown, in which case the engine also skips
* `ClientDisconnect` for the connected clients. No engine path raises it today.
*/
shutdown(isCrashShutdown: boolean): void;
/**
* Called at the start of every simulated server frame, after `time` was written and before any
* entity is processed.
*/
startFrame(): void;
/**
* Called before the engine runs the movement physics of a connected client.
*/
PlayerPreThink(clientEdict: ServerEdict): void;
/**
* Called after the engine ran the movement physics and touch handling of a connected client.
*/
PlayerPostThink(clientEdict: ServerEdict): void;
/**
* Called when a client spawns into a freshly started map, right before `PutClientInServer`.
* Not called while a savegame is being restored.
*/
ClientConnect(clientEdict: ServerEdict): void;
/**
* Called when a spawned client is removed while the engine can still talk to it: it
* disconnected, was kicked, or the server shut down. Not called for a client the engine drops
* because its connection failed, nor for a client that never finished spawning.
*/
ClientDisconnect(clientEdict: ServerEdict): void;
/**
* Called when a client issues the `kill` command.
*/
ClientKill(clientEdict: ServerEdict): void;
/**
* Called right after `ClientConnect` to place the player into the world. Not called while a
* savegame is being restored.
*/
PutClientInServer(clientEdict: ServerEdict): void;
/**
* Optional. Called when a client finished loading and enters the game (the `begin` command).
*/
ClientBegin?(clientEdict: ServerEdict): void;
/**
* Maps an entity classname to the field names the engine replicates to clients for that class.
*/
getClientEntityFields(): Record<string, string[]>;
/**
* First half of entity creation: attaches a game entity of the given class to the edict and
* applies the initial data.
* @returns False when the class is unknown or must not exist in the current game mode.
*/
prepareEntity(edict: ServerEdict, classname: string, initialData?: EdictData): boolean;
/**
* Second half of entity creation: runs the spawn logic of an entity set up by `prepareEntity`.
*/
spawnPreparedEntity(edict: ServerEdict): boolean;
/**
* Game-wide state stored in a savegame next to the entities.
*/
serialize(): SerializedData;
/**
* Restores what `serialize` produced.
*/
deserialize(data: SerializedData): void;
}
/**
* The static side of a game's server API class: how the engine creates and initializes it.
*/
export interface ServerGameConstructor {
/**
* Creates the game instance for one map load (spawning a server or changing level).
*/
new (engineAPI: ServerEngineAPI): ServerGameInterface;
/**
* One-time module initialization, called once when the game module is loaded (e.g. to register cvars).
*/
Init(serverEngineAPI: ServerEngineAPI): void;
/**
* Counterpart of `Init`. Reserved: the engine has no module-unload path yet and does not call it.
*/
Shutdown(): void;
}
/**
* Identification metadata a game module exports for logging, version checks and capability negotiation.
*/
export interface GameModuleIdentification {
readonly name: string;
readonly author: string;
readonly version: readonly [number, number, number];
readonly capabilities: readonly gameCapabilities[];
}
/**
* The shape of a game module's `main.ts`, as the engine loads it. A game asserts
* `satisfies GameModuleInterface` on its exports to have the compiler check it against the engine.
*/
export interface GameModuleInterface {
readonly identification: GameModuleIdentification;
readonly ServerGameAPI: ServerGameConstructor;
readonly ClientGameAPI: ClientGameConstructor;
}