@takaro/gameserver 0.0.1
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/README.md +36 -0
- package/dist/TakaroEmitter.d.ts +26 -0
- package/dist/TakaroEmitter.js +97 -0
- package/dist/TakaroEmitter.js.map +1 -0
- package/dist/gameservers/7d2d/apiResponses.d.ts +193 -0
- package/dist/gameservers/7d2d/apiResponses.js +2 -0
- package/dist/gameservers/7d2d/apiResponses.js.map +1 -0
- package/dist/gameservers/7d2d/connectionInfo.d.ts +32 -0
- package/dist/gameservers/7d2d/connectionInfo.js +58 -0
- package/dist/gameservers/7d2d/connectionInfo.js.map +1 -0
- package/dist/gameservers/7d2d/emitter.d.ts +30 -0
- package/dist/gameservers/7d2d/emitter.js +261 -0
- package/dist/gameservers/7d2d/emitter.js.map +1 -0
- package/dist/gameservers/7d2d/index.d.ts +28 -0
- package/dist/gameservers/7d2d/index.js +267 -0
- package/dist/gameservers/7d2d/index.js.map +1 -0
- package/dist/gameservers/7d2d/itemWorker.d.ts +1 -0
- package/dist/gameservers/7d2d/itemWorker.js +31 -0
- package/dist/gameservers/7d2d/itemWorker.js.map +1 -0
- package/dist/gameservers/7d2d/items-7d2d.json +17705 -0
- package/dist/gameservers/7d2d/sdtdAPIClient.d.ts +14 -0
- package/dist/gameservers/7d2d/sdtdAPIClient.js +60 -0
- package/dist/gameservers/7d2d/sdtdAPIClient.js.map +1 -0
- package/dist/gameservers/mock/connectionInfo.d.ts +20 -0
- package/dist/gameservers/mock/connectionInfo.js +37 -0
- package/dist/gameservers/mock/connectionInfo.js.map +1 -0
- package/dist/gameservers/mock/emitter.d.ts +12 -0
- package/dist/gameservers/mock/emitter.js +33 -0
- package/dist/gameservers/mock/emitter.js.map +1 -0
- package/dist/gameservers/mock/index.d.ts +31 -0
- package/dist/gameservers/mock/index.js +135 -0
- package/dist/gameservers/mock/index.js.map +1 -0
- package/dist/gameservers/rust/connectionInfo.d.ts +28 -0
- package/dist/gameservers/rust/connectionInfo.js +51 -0
- package/dist/gameservers/rust/connectionInfo.js.map +1 -0
- package/dist/gameservers/rust/emitter.d.ts +30 -0
- package/dist/gameservers/rust/emitter.js +160 -0
- package/dist/gameservers/rust/emitter.js.map +1 -0
- package/dist/gameservers/rust/index.d.ts +29 -0
- package/dist/gameservers/rust/index.js +189 -0
- package/dist/gameservers/rust/index.js.map +1 -0
- package/dist/gameservers/rust/items-rust.json +20771 -0
- package/dist/getGame.d.ts +8 -0
- package/dist/getGame.js +26 -0
- package/dist/getGame.js.map +1 -0
- package/dist/interfaces/GameServer.d.ts +57 -0
- package/dist/interfaces/GameServer.js +95 -0
- package/dist/interfaces/GameServer.js.map +1 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.js +10 -0
- package/dist/main.js.map +1 -0
- package/package.json +26 -0
- package/src/TakaroEmitter.ts +138 -0
- package/src/TakaroEmitter.unit.test.ts +125 -0
- package/src/__tests__/gameEventEmitter.test.ts +36 -0
- package/src/gameservers/7d2d/__tests__/7d2dActions.unit.test.ts +91 -0
- package/src/gameservers/7d2d/__tests__/7d2dEventDetection.unit.test.ts +324 -0
- package/src/gameservers/7d2d/apiResponses.ts +214 -0
- package/src/gameservers/7d2d/connectionInfo.ts +40 -0
- package/src/gameservers/7d2d/emitter.ts +325 -0
- package/src/gameservers/7d2d/index.ts +318 -0
- package/src/gameservers/7d2d/itemWorker.ts +34 -0
- package/src/gameservers/7d2d/items-7d2d.json +17705 -0
- package/src/gameservers/7d2d/sdtdAPIClient.ts +82 -0
- package/src/gameservers/mock/connectionInfo.ts +25 -0
- package/src/gameservers/mock/emitter.ts +37 -0
- package/src/gameservers/mock/index.ts +156 -0
- package/src/gameservers/rust/__tests__/rustActions.unit.test.ts +140 -0
- package/src/gameservers/rust/connectionInfo.ts +35 -0
- package/src/gameservers/rust/emitter.ts +198 -0
- package/src/gameservers/rust/index.ts +230 -0
- package/src/gameservers/rust/items-rust.json +20771 -0
- package/src/getGame.ts +32 -0
- package/src/interfaces/GameServer.ts +95 -0
- package/src/main.ts +14 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +9 -0
- package/typedoc.json +3 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { GameServerOutputDTOTypeEnum, Settings } from '@takaro/apiclient';
|
|
2
|
+
import { IGameServer } from './interfaces/GameServer.js';
|
|
3
|
+
export declare enum GAME_SERVER_TYPE {
|
|
4
|
+
'MOCK' = "MOCK",
|
|
5
|
+
'SEVENDAYSTODIE' = "SEVENDAYSTODIE",
|
|
6
|
+
'RUST' = "RUST"
|
|
7
|
+
}
|
|
8
|
+
export declare function getGame(type: GAME_SERVER_TYPE | GameServerOutputDTOTypeEnum, connectionInfo: Record<string, unknown>, settings: Partial<Settings>): Promise<IGameServer>;
|
package/dist/getGame.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { errors } from '@takaro/util';
|
|
2
|
+
import { SdtdConnectionInfo } from './gameservers/7d2d/connectionInfo.js';
|
|
3
|
+
import { SevenDaysToDie } from './gameservers/7d2d/index.js';
|
|
4
|
+
import { MockConnectionInfo } from './gameservers/mock/connectionInfo.js';
|
|
5
|
+
import { Mock } from './gameservers/mock/index.js';
|
|
6
|
+
import { RustConnectionInfo } from './gameservers/rust/connectionInfo.js';
|
|
7
|
+
import { Rust } from './gameservers/rust/index.js';
|
|
8
|
+
export var GAME_SERVER_TYPE;
|
|
9
|
+
(function (GAME_SERVER_TYPE) {
|
|
10
|
+
GAME_SERVER_TYPE["MOCK"] = "MOCK";
|
|
11
|
+
GAME_SERVER_TYPE["SEVENDAYSTODIE"] = "SEVENDAYSTODIE";
|
|
12
|
+
GAME_SERVER_TYPE["RUST"] = "RUST";
|
|
13
|
+
})(GAME_SERVER_TYPE || (GAME_SERVER_TYPE = {}));
|
|
14
|
+
export async function getGame(type, connectionInfo, settings) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case GAME_SERVER_TYPE.SEVENDAYSTODIE:
|
|
17
|
+
return new SevenDaysToDie(new SdtdConnectionInfo(connectionInfo), settings);
|
|
18
|
+
case GAME_SERVER_TYPE.RUST:
|
|
19
|
+
return new Rust(new RustConnectionInfo(connectionInfo), settings);
|
|
20
|
+
case GAME_SERVER_TYPE.MOCK:
|
|
21
|
+
return new Mock(new MockConnectionInfo(connectionInfo), settings);
|
|
22
|
+
default:
|
|
23
|
+
throw new errors.NotImplementedError();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=getGame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getGame.js","sourceRoot":"","sources":["../src/getGame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAGnD,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,iCAAe,CAAA;IACf,qDAAmC,CAAA;IACnC,iCAAe,CAAA;AACjB,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAoD,EACpD,cAAuC,EACvC,QAA2B;IAE3B,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,gBAAgB,CAAC,cAAc;YAClC,OAAO,IAAI,cAAc,CAAC,IAAI,kBAAkB,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9E,KAAK,gBAAgB,CAAC,IAAI;YACxB,OAAO,IAAI,IAAI,CAAC,IAAI,kBAAkB,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC;QACpE,KAAK,gBAAgB,CAAC,IAAI;YACxB,OAAO,IAAI,IAAI,CAAC,IAAI,kBAAkB,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,CAAC;QACpE;YACE,MAAM,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TakaroEmitter } from '../TakaroEmitter.js';
|
|
2
|
+
import { IGamePlayer, IPosition } from '@takaro/modules';
|
|
3
|
+
import { TakaroDTO } from '@takaro/util';
|
|
4
|
+
export declare class CommandOutput extends TakaroDTO<CommandOutput> {
|
|
5
|
+
rawResult: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
errorMessage?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class TestReachabilityOutputDTO extends TakaroDTO<TestReachabilityOutputDTO> {
|
|
10
|
+
connectable: boolean;
|
|
11
|
+
reason?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* This is used whenever we want to target a specific player
|
|
15
|
+
* We only allow a subset of IGamePlayer here because to work across gameservers we need to be generic
|
|
16
|
+
* Eg, if we allow users to reference players by Steam ID, that wont work for all gameservers. Not all gameservers have Steam integration
|
|
17
|
+
*/
|
|
18
|
+
export declare class IPlayerReferenceDTO extends TakaroDTO<IPlayerReferenceDTO> {
|
|
19
|
+
gameId: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class IItemDTO extends TakaroDTO<IItemDTO> {
|
|
22
|
+
name: string;
|
|
23
|
+
code: string;
|
|
24
|
+
description: string;
|
|
25
|
+
amount?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class IMessageOptsDTO extends TakaroDTO<IMessageOptsDTO> {
|
|
28
|
+
/** When specified, will send a DM to this player instead of a global message */
|
|
29
|
+
recipient?: IPlayerReferenceDTO;
|
|
30
|
+
}
|
|
31
|
+
export declare class BanDTO extends TakaroDTO<BanDTO> {
|
|
32
|
+
player: IPlayerReferenceDTO;
|
|
33
|
+
reason: string;
|
|
34
|
+
expiresAt: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface IGameServer {
|
|
37
|
+
connectionInfo: unknown;
|
|
38
|
+
getEventEmitter(): TakaroEmitter;
|
|
39
|
+
getPlayer(player: IPlayerReferenceDTO): Promise<IGamePlayer | null>;
|
|
40
|
+
getPlayers(): Promise<IGamePlayer[]>;
|
|
41
|
+
getPlayerLocation(player: IPlayerReferenceDTO): Promise<IPosition | null>;
|
|
42
|
+
getPlayerInventory(player: IPlayerReferenceDTO): Promise<IItemDTO[]>;
|
|
43
|
+
giveItem(player: IPlayerReferenceDTO, item: string, amount: number): Promise<void>;
|
|
44
|
+
listItems(): Promise<IItemDTO[]>;
|
|
45
|
+
executeConsoleCommand(rawCommand: string): Promise<CommandOutput>;
|
|
46
|
+
sendMessage(message: string, opts: IMessageOptsDTO): Promise<void>;
|
|
47
|
+
teleportPlayer(player: IPlayerReferenceDTO, x: number, y: number, z: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Try and connect to the gameserver
|
|
50
|
+
* If anything goes wrong, this function will report a detailed reason
|
|
51
|
+
*/
|
|
52
|
+
testReachability(): Promise<TestReachabilityOutputDTO>;
|
|
53
|
+
kickPlayer(player: IPlayerReferenceDTO, reason: string): Promise<void>;
|
|
54
|
+
banPlayer(options: BanDTO): Promise<void>;
|
|
55
|
+
unbanPlayer(player: IPlayerReferenceDTO): Promise<void>;
|
|
56
|
+
listBans(): Promise<BanDTO[]>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { TakaroDTO } from '@takaro/util';
|
|
11
|
+
import { IsBoolean, IsISO8601, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
|
|
12
|
+
import { Type } from 'class-transformer';
|
|
13
|
+
export class CommandOutput extends TakaroDTO {
|
|
14
|
+
}
|
|
15
|
+
__decorate([
|
|
16
|
+
IsString(),
|
|
17
|
+
__metadata("design:type", String)
|
|
18
|
+
], CommandOutput.prototype, "rawResult", void 0);
|
|
19
|
+
__decorate([
|
|
20
|
+
IsBoolean(),
|
|
21
|
+
__metadata("design:type", Boolean)
|
|
22
|
+
], CommandOutput.prototype, "success", void 0);
|
|
23
|
+
__decorate([
|
|
24
|
+
IsOptional(),
|
|
25
|
+
IsString(),
|
|
26
|
+
__metadata("design:type", String)
|
|
27
|
+
], CommandOutput.prototype, "errorMessage", void 0);
|
|
28
|
+
export class TestReachabilityOutputDTO extends TakaroDTO {
|
|
29
|
+
}
|
|
30
|
+
__decorate([
|
|
31
|
+
IsBoolean(),
|
|
32
|
+
__metadata("design:type", Boolean)
|
|
33
|
+
], TestReachabilityOutputDTO.prototype, "connectable", void 0);
|
|
34
|
+
__decorate([
|
|
35
|
+
IsString(),
|
|
36
|
+
IsOptional(),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], TestReachabilityOutputDTO.prototype, "reason", void 0);
|
|
39
|
+
/**
|
|
40
|
+
* This is used whenever we want to target a specific player
|
|
41
|
+
* We only allow a subset of IGamePlayer here because to work across gameservers we need to be generic
|
|
42
|
+
* Eg, if we allow users to reference players by Steam ID, that wont work for all gameservers. Not all gameservers have Steam integration
|
|
43
|
+
*/
|
|
44
|
+
export class IPlayerReferenceDTO extends TakaroDTO {
|
|
45
|
+
}
|
|
46
|
+
__decorate([
|
|
47
|
+
IsString(),
|
|
48
|
+
__metadata("design:type", String)
|
|
49
|
+
], IPlayerReferenceDTO.prototype, "gameId", void 0);
|
|
50
|
+
export class IItemDTO extends TakaroDTO {
|
|
51
|
+
}
|
|
52
|
+
__decorate([
|
|
53
|
+
IsString(),
|
|
54
|
+
__metadata("design:type", String)
|
|
55
|
+
], IItemDTO.prototype, "name", void 0);
|
|
56
|
+
__decorate([
|
|
57
|
+
IsString(),
|
|
58
|
+
__metadata("design:type", String)
|
|
59
|
+
], IItemDTO.prototype, "code", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
IsString(),
|
|
62
|
+
IsOptional(),
|
|
63
|
+
__metadata("design:type", String)
|
|
64
|
+
], IItemDTO.prototype, "description", void 0);
|
|
65
|
+
__decorate([
|
|
66
|
+
IsNumber(),
|
|
67
|
+
IsOptional(),
|
|
68
|
+
__metadata("design:type", Number)
|
|
69
|
+
], IItemDTO.prototype, "amount", void 0);
|
|
70
|
+
export class IMessageOptsDTO extends TakaroDTO {
|
|
71
|
+
}
|
|
72
|
+
__decorate([
|
|
73
|
+
Type(() => IPlayerReferenceDTO),
|
|
74
|
+
ValidateNested()
|
|
75
|
+
/** When specified, will send a DM to this player instead of a global message */
|
|
76
|
+
,
|
|
77
|
+
__metadata("design:type", IPlayerReferenceDTO)
|
|
78
|
+
], IMessageOptsDTO.prototype, "recipient", void 0);
|
|
79
|
+
export class BanDTO extends TakaroDTO {
|
|
80
|
+
}
|
|
81
|
+
__decorate([
|
|
82
|
+
Type(() => IPlayerReferenceDTO),
|
|
83
|
+
ValidateNested(),
|
|
84
|
+
__metadata("design:type", IPlayerReferenceDTO)
|
|
85
|
+
], BanDTO.prototype, "player", void 0);
|
|
86
|
+
__decorate([
|
|
87
|
+
IsString(),
|
|
88
|
+
__metadata("design:type", String)
|
|
89
|
+
], BanDTO.prototype, "reason", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
IsISO8601(),
|
|
92
|
+
IsOptional(),
|
|
93
|
+
__metadata("design:type", Object)
|
|
94
|
+
], BanDTO.prototype, "expiresAt", void 0);
|
|
95
|
+
//# sourceMappingURL=GameServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GameServer.js","sourceRoot":"","sources":["../../src/interfaces/GameServer.ts"],"names":[],"mappings":";;;;;;;;;AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,MAAM,OAAO,aAAc,SAAQ,SAAwB;CAQ1D;AANC;IADC,QAAQ,EAAE;;gDACO;AAElB;IADC,SAAS,EAAE;;8CACK;AAGjB;IAFC,UAAU,EAAE;IACZ,QAAQ,EAAE;;mDACW;AAExB,MAAM,OAAO,yBAA0B,SAAQ,SAAoC;CAOlF;AALC;IADC,SAAS,EAAE;;8DACS;AAIrB;IAFC,QAAQ,EAAE;IACV,UAAU,EAAE;;yDACG;AAGlB;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,SAA8B;CAGtE;AADC;IADC,QAAQ,EAAE;;mDACI;AAGjB,MAAM,OAAO,QAAS,SAAQ,SAAmB;CAWhD;AATC;IADC,QAAQ,EAAE;;sCACE;AAEb;IADC,QAAQ,EAAE;;sCACE;AAGb;IAFC,QAAQ,EAAE;IACV,UAAU,EAAE;;6CACO;AAGpB;IAFC,QAAQ,EAAE;IACV,UAAU,EAAE;;wCACG;AAGlB,MAAM,OAAO,eAAgB,SAAQ,SAA0B;CAK9D;AADC;IAHC,IAAI,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC;IAC/B,cAAc,EAAE;IACjB,gFAAgF;;8BACpE,mBAAmB;kDAAC;AAGlC,MAAM,OAAO,MAAO,SAAQ,SAAiB;CAW5C;AARC;IAFC,IAAI,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC;IAC/B,cAAc,EAAE;8BACT,mBAAmB;sCAAC;AAG5B;IADC,QAAQ,EAAE;;sCACI;AAIf;IAFC,SAAS,EAAE;IACX,UAAU,EAAE;;yCACY"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './interfaces/GameServer.js';
|
|
2
|
+
export { TakaroEmitter } from './TakaroEmitter.js';
|
|
3
|
+
export { Mock } from './gameservers/mock/index.js';
|
|
4
|
+
export { MockConnectionInfo, mockJsonSchema } from './gameservers/mock/connectionInfo.js';
|
|
5
|
+
export { SevenDaysToDie } from './gameservers/7d2d/index.js';
|
|
6
|
+
export { SdtdConnectionInfo, sdtdJsonSchema } from './gameservers/7d2d/connectionInfo.js';
|
|
7
|
+
export { Rust } from './gameservers/rust/index.js';
|
|
8
|
+
export { RustConnectionInfo, rustJsonSchema } from './gameservers/rust/connectionInfo.js';
|
|
9
|
+
export { getGame, GAME_SERVER_TYPE } from './getGame.js';
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './interfaces/GameServer.js';
|
|
2
|
+
export { TakaroEmitter } from './TakaroEmitter.js';
|
|
3
|
+
export { Mock } from './gameservers/mock/index.js';
|
|
4
|
+
export { MockConnectionInfo, mockJsonSchema } from './gameservers/mock/connectionInfo.js';
|
|
5
|
+
export { SevenDaysToDie } from './gameservers/7d2d/index.js';
|
|
6
|
+
export { SdtdConnectionInfo, sdtdJsonSchema } from './gameservers/7d2d/connectionInfo.js';
|
|
7
|
+
export { Rust } from './gameservers/rust/index.js';
|
|
8
|
+
export { RustConnectionInfo, rustJsonSchema } from './gameservers/rust/connectionInfo.js';
|
|
9
|
+
export { getGame, GAME_SERVER_TYPE } from './getGame.js';
|
|
10
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAE1F,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@takaro/gameserver",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Handles abstraction between Takaro and game servers",
|
|
5
|
+
"main": "dist/main.js",
|
|
6
|
+
"types": "dist/main.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start:dev": "tsc --watch --preserveWatchOutput -p ./tsconfig.build.json",
|
|
10
|
+
"build": "tsc -p ./tsconfig.build.json",
|
|
11
|
+
"test": "npm run test:unit --if-present && npm run test:integration --if-present",
|
|
12
|
+
"test:unit": "mocha --config ../../.mocharc.js src/**/*.unit.test.ts",
|
|
13
|
+
"test:integration": "echo 'No tests (yet :))'"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@takaro/modules": "0.0.1",
|
|
20
|
+
"eventsource": "^2.0.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@takaro/test": "0.0.1",
|
|
24
|
+
"@types/eventsource": "^1.1.9"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseEvent,
|
|
3
|
+
EventChatMessage,
|
|
4
|
+
EventEntityKilled,
|
|
5
|
+
EventLogLine,
|
|
6
|
+
EventPlayerConnected,
|
|
7
|
+
EventPlayerDeath,
|
|
8
|
+
EventPlayerDisconnected,
|
|
9
|
+
GameEvents,
|
|
10
|
+
} from '@takaro/modules';
|
|
11
|
+
import { errors, isTakaroDTO, logger } from '@takaro/util';
|
|
12
|
+
import { isPromise } from 'util/types';
|
|
13
|
+
|
|
14
|
+
const log = logger('TakaroEmitter');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Maps event types to their listener function signatures
|
|
18
|
+
* This allows our EventEmitter to be strongly typed
|
|
19
|
+
*/
|
|
20
|
+
export interface IEventMap {
|
|
21
|
+
[GameEvents.LOG_LINE]: (log: EventLogLine) => Promise<void>;
|
|
22
|
+
[GameEvents.PLAYER_CONNECTED]: (player: EventPlayerConnected) => Promise<void>;
|
|
23
|
+
[GameEvents.PLAYER_DISCONNECTED]: (player: EventPlayerDisconnected) => Promise<void>;
|
|
24
|
+
[GameEvents.CHAT_MESSAGE]: (chatMessage: EventChatMessage) => Promise<void>;
|
|
25
|
+
[GameEvents.PLAYER_DEATH]: (playerDeath: EventPlayerDeath) => Promise<void>;
|
|
26
|
+
[GameEvents.ENTITY_KILLED]: (entityKilled: EventEntityKilled) => Promise<void>;
|
|
27
|
+
error: (error: errors.TakaroError | Error) => Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export abstract class TakaroEmitter {
|
|
31
|
+
private listenerMap: Map<keyof IEventMap, IEventMap[keyof IEventMap][]> = new Map();
|
|
32
|
+
|
|
33
|
+
abstract stop(): Promise<void>;
|
|
34
|
+
abstract start(): Promise<void>;
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
return getErrorProxyHandler(this);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async emit<E extends keyof IEventMap>(event: E, data: BaseEvent<unknown> | Error) {
|
|
41
|
+
try {
|
|
42
|
+
// No listeners are attached, return early
|
|
43
|
+
if (!this.listenerMap.has(event)) return;
|
|
44
|
+
|
|
45
|
+
// Validate the data, it is user-input after all :)
|
|
46
|
+
if (isTakaroDTO(data)) {
|
|
47
|
+
if (!data.timestamp) data.timestamp = new Date().toISOString();
|
|
48
|
+
// await data.validate();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const listeners = this.listenerMap.get(event);
|
|
52
|
+
|
|
53
|
+
if (listeners) {
|
|
54
|
+
for (const listener of listeners) {
|
|
55
|
+
// We know this is okay because our listener map always corresponds to the right event
|
|
56
|
+
// This is implicit in our implementation and checked in the tests
|
|
57
|
+
// @ts-expect-error Can't get the types quite right :(
|
|
58
|
+
await listener(data);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.emit('error', error as Error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
on<E extends keyof IEventMap>(event: E, listener: IEventMap[E]): this {
|
|
67
|
+
this.listenerMap.set(event, [listener]);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
off<E extends keyof IEventMap>(event: E, listener: IEventMap[E]): this {
|
|
72
|
+
const listeners = this.listenerMap.get(event);
|
|
73
|
+
|
|
74
|
+
if (listeners) {
|
|
75
|
+
this.listenerMap.set(
|
|
76
|
+
event,
|
|
77
|
+
listeners.filter((l) => l !== listener)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
hasErrorListener() {
|
|
85
|
+
return this.listenerMap.has('error');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getErrorProxyHandler<T extends TakaroEmitter>(emitter: T) {
|
|
90
|
+
const errorProxyHandler: ProxyHandler<T> = {
|
|
91
|
+
construct(target: any, argArray) {
|
|
92
|
+
return Reflect.construct(target, argArray, TakaroEmitter);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
set: function (obj, prop: keyof TakaroEmitter, value) {
|
|
96
|
+
obj[prop] = value;
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
get(target, prop: keyof TakaroEmitter) {
|
|
101
|
+
return async (...[one, two]: any[]) => {
|
|
102
|
+
try {
|
|
103
|
+
// Check if callable function
|
|
104
|
+
if (typeof target[prop] === 'function') {
|
|
105
|
+
return await target[prop](one, two);
|
|
106
|
+
// Or if its a Promise, await it
|
|
107
|
+
} else if (isPromise(target[prop])) {
|
|
108
|
+
return await target[prop];
|
|
109
|
+
} else {
|
|
110
|
+
// Otherwise, return the value
|
|
111
|
+
return target[prop];
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!target.hasErrorListener()) {
|
|
115
|
+
log.error('Unhandled error', error);
|
|
116
|
+
const err = new Error(
|
|
117
|
+
'Unhandled error in TakaroEmitter, attach a listener to the "error" event to handle this'
|
|
118
|
+
);
|
|
119
|
+
Error.captureStackTrace(err);
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (error instanceof errors.TakaroError) {
|
|
124
|
+
await target.emit('error', error);
|
|
125
|
+
} else if (error instanceof Error) {
|
|
126
|
+
await target.emit('error', error);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!TakaroEmitter.prototype.hasOwnProperty(prop)) {
|
|
130
|
+
return Promise.reject(error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return new Proxy(emitter, errorProxyHandler);
|
|
138
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { TakaroEmitter } from './TakaroEmitter.js';
|
|
2
|
+
import { expect, sandbox } from '@takaro/test';
|
|
3
|
+
import { EventLogLine, GameEvents } from '@takaro/modules';
|
|
4
|
+
|
|
5
|
+
class ExtendedTakaroEmitter extends TakaroEmitter {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
7
|
+
async start() {}
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
9
|
+
async stop() {}
|
|
10
|
+
|
|
11
|
+
async foo() {
|
|
12
|
+
throw new Error('testing error');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('TakaroEmitter', () => {
|
|
17
|
+
it('Can listen for events', async () => {
|
|
18
|
+
const emitter = new ExtendedTakaroEmitter();
|
|
19
|
+
const spy = sandbox.spy();
|
|
20
|
+
|
|
21
|
+
emitter.on(GameEvents.LOG_LINE, spy);
|
|
22
|
+
|
|
23
|
+
await emitter.emit(
|
|
24
|
+
GameEvents.LOG_LINE,
|
|
25
|
+
new EventLogLine({
|
|
26
|
+
msg: 'test',
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(spy).to.have.been.calledOnce;
|
|
31
|
+
|
|
32
|
+
await emitter.emit(
|
|
33
|
+
GameEvents.LOG_LINE,
|
|
34
|
+
new EventLogLine({
|
|
35
|
+
msg: 'test',
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(spy).to.have.been.calledTwice;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('Can remove a listener, which causes further events to not be received', async () => {
|
|
43
|
+
const emitter = new ExtendedTakaroEmitter();
|
|
44
|
+
const spy = sandbox.spy();
|
|
45
|
+
|
|
46
|
+
emitter.on(GameEvents.LOG_LINE, spy);
|
|
47
|
+
|
|
48
|
+
await emitter.emit(
|
|
49
|
+
GameEvents.LOG_LINE,
|
|
50
|
+
new EventLogLine({
|
|
51
|
+
msg: 'test',
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(spy).to.have.been.calledOnce;
|
|
56
|
+
|
|
57
|
+
emitter.off(GameEvents.LOG_LINE, spy);
|
|
58
|
+
|
|
59
|
+
await emitter.emit(
|
|
60
|
+
GameEvents.LOG_LINE,
|
|
61
|
+
new EventLogLine({
|
|
62
|
+
msg: 'test',
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(spy).to.have.been.calledOnce;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('Errors happening inside extended class do not interrupt flow of events', async () => {
|
|
70
|
+
const emitter = new ExtendedTakaroEmitter();
|
|
71
|
+
const spy = sandbox.spy();
|
|
72
|
+
const errorSpy = sandbox.spy();
|
|
73
|
+
|
|
74
|
+
emitter.on(GameEvents.LOG_LINE, spy);
|
|
75
|
+
emitter.on('error', errorSpy);
|
|
76
|
+
|
|
77
|
+
await expect(emitter.foo()).to.eventually.be.rejectedWith('testing error');
|
|
78
|
+
|
|
79
|
+
expect(errorSpy).to.have.been.calledOnce;
|
|
80
|
+
|
|
81
|
+
await emitter.emit(
|
|
82
|
+
GameEvents.LOG_LINE,
|
|
83
|
+
new EventLogLine({
|
|
84
|
+
msg: 'test',
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(spy).to.have.been.calledOnce;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
xit('Validates data on emitting', async () => {
|
|
92
|
+
const emitter = new ExtendedTakaroEmitter();
|
|
93
|
+
const spy = sandbox.spy();
|
|
94
|
+
const errorSpy = sandbox.spy();
|
|
95
|
+
|
|
96
|
+
emitter.on(GameEvents.LOG_LINE, spy);
|
|
97
|
+
emitter.on('error', errorSpy);
|
|
98
|
+
|
|
99
|
+
await emitter.emit(
|
|
100
|
+
GameEvents.LOG_LINE,
|
|
101
|
+
new EventLogLine({
|
|
102
|
+
msg: 'test',
|
|
103
|
+
// @ts-expect-error testing validation, our types accurately detect this is invalid
|
|
104
|
+
unknownProperty: 'this should trip validation',
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(errorSpy).to.have.been.calledOnce;
|
|
109
|
+
expect(errorSpy.getCall(0).args[0].message).to.match(
|
|
110
|
+
/property unknownProperty has failed the following constraints: whitelistValidation/
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('Throws when an error occurs and no listeners are attached to the "error" event', async () => {
|
|
115
|
+
const emitter = new ExtendedTakaroEmitter();
|
|
116
|
+
const errorSpy = sandbox.spy();
|
|
117
|
+
|
|
118
|
+
await expect(emitter.foo()).to.eventually.be.rejectedWith('Unhandled error in TakaroEmitter');
|
|
119
|
+
|
|
120
|
+
emitter.on('error', errorSpy);
|
|
121
|
+
|
|
122
|
+
await expect(emitter.foo()).to.eventually.be.rejectedWith('testing error');
|
|
123
|
+
expect(errorSpy).to.have.been.calledOnce;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { expect, sandbox } from '@takaro/test';
|
|
2
|
+
import { MockConnectionInfo } from '../gameservers/mock/connectionInfo.js';
|
|
3
|
+
import { Mock } from '../gameservers/mock/index.js';
|
|
4
|
+
import { GameEvents } from '@takaro/modules';
|
|
5
|
+
|
|
6
|
+
describe('GameEventEmitter', () => {
|
|
7
|
+
/**
|
|
8
|
+
* This test doesn't really do much interesting runtime validation
|
|
9
|
+
* It exists to ensure that the event emitter is safely typed
|
|
10
|
+
* We use @ts-expect-error so that if the compiler fails to mark these as errors, we'll know instantly
|
|
11
|
+
*/
|
|
12
|
+
it('Has a typed event emitter', async () => {
|
|
13
|
+
const gameServer = new Mock(new MockConnectionInfo({}));
|
|
14
|
+
const emitter = await gameServer.getEventEmitter();
|
|
15
|
+
|
|
16
|
+
const listenerSpy = sandbox.spy();
|
|
17
|
+
|
|
18
|
+
emitter.on(GameEvents.PLAYER_CONNECTED, async (e) => {
|
|
19
|
+
expect(() => e.player.name).to.throw(
|
|
20
|
+
// Eslint and prettier disagree on how to format this
|
|
21
|
+
// And I cba fixing it for this specific instance :)
|
|
22
|
+
// eslint-disable-next-line quotes
|
|
23
|
+
"Cannot read properties of undefined (reading 'name')"
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
emitter.on(GameEvents.PLAYER_CONNECTED, listenerSpy);
|
|
27
|
+
|
|
28
|
+
// @ts-expect-error Should use the enum here
|
|
29
|
+
emitter.on('non-existent-event', listenerSpy);
|
|
30
|
+
|
|
31
|
+
// But the raw string will also work in runtime when ignoring the compilation error
|
|
32
|
+
emitter.on('player-connected', listenerSpy);
|
|
33
|
+
|
|
34
|
+
expect(listenerSpy).to.have.been.calledTwice;
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { expect, sandbox } from '@takaro/test';
|
|
2
|
+
import { SevenDaysToDie } from '../index.js';
|
|
3
|
+
import { CommandOutput } from '../../../interfaces/GameServer.js';
|
|
4
|
+
import { SdtdConnectionInfo } from '../connectionInfo.js';
|
|
5
|
+
|
|
6
|
+
const testData = {
|
|
7
|
+
oneBan:
|
|
8
|
+
'Ban list entries:\n Banned until - UserID (name) - Reason\n 2023-06-29 19:39:56 - EOS_00028a9b73bb45b2b74e8f22cda7d225 (-unknown-) - Totally valid testing reason :)\n',
|
|
9
|
+
twoBans:
|
|
10
|
+
'Ban list entries:\n Banned until - UserID (name) - Reason\n 2028-06-29 17:49:45 - EOS_0002e0daea3b493fa146ce6d06e79a57 (-unknown-) - test\n 2028-06-29 19:19:40 - EOS_00028a9b73bb45b2b74e8f22cda7d225 (-unknown-) - test\n',
|
|
11
|
+
noBans: 'Ban list entries:\n Banned until - UserID (name) - Reason\n',
|
|
12
|
+
oneBanWithDisplayName:
|
|
13
|
+
'Ban list entries:\n Banned until - UserID (name) - Reason\n 2023-06-29 19:40:59 - EOS_00028a9b73bb45b2b74e8f22cda7d225 (testing display name) - Totally valid testing reason :)\n',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockSdtdConnectionInfo = new SdtdConnectionInfo({
|
|
17
|
+
adminToken: 'aaa',
|
|
18
|
+
adminUser: 'aaa',
|
|
19
|
+
useTls: false,
|
|
20
|
+
host: 'localhost',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('7d2d Actions', () => {
|
|
24
|
+
describe('listBans', () => {
|
|
25
|
+
it('Can parse ban list with a single ban', async () => {
|
|
26
|
+
sandbox.stub(SevenDaysToDie.prototype, 'executeConsoleCommand').resolves(
|
|
27
|
+
new CommandOutput({
|
|
28
|
+
rawResult: testData.oneBan,
|
|
29
|
+
success: true,
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const result = await new SevenDaysToDie(await mockSdtdConnectionInfo).listBans();
|
|
34
|
+
|
|
35
|
+
expect(result).to.be.an('array');
|
|
36
|
+
expect(result).to.have.lengthOf(1);
|
|
37
|
+
expect(result[0].player.gameId).to.equal('EOS_00028a9b73bb45b2b74e8f22cda7d225');
|
|
38
|
+
expect(result[0].expiresAt).to.equal('2023-06-29T19:39:56.000Z');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('Can parse ban list with two bans', async () => {
|
|
42
|
+
sandbox.stub(SevenDaysToDie.prototype, 'executeConsoleCommand').resolves(
|
|
43
|
+
new CommandOutput({
|
|
44
|
+
rawResult: testData.twoBans,
|
|
45
|
+
success: true,
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const result = await new SevenDaysToDie(await mockSdtdConnectionInfo).listBans();
|
|
50
|
+
|
|
51
|
+
expect(result).to.be.an('array');
|
|
52
|
+
expect(result).to.have.lengthOf(2);
|
|
53
|
+
|
|
54
|
+
expect(result[0].player.gameId).to.equal('EOS_0002e0daea3b493fa146ce6d06e79a57');
|
|
55
|
+
expect(result[0].expiresAt).to.equal('2028-06-29T17:49:45.000Z');
|
|
56
|
+
|
|
57
|
+
expect(result[1].player.gameId).to.equal('EOS_00028a9b73bb45b2b74e8f22cda7d225');
|
|
58
|
+
expect(result[1].expiresAt).to.equal('2028-06-29T19:19:40.000Z');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Can parse ban list with no bans', async () => {
|
|
62
|
+
sandbox.stub(SevenDaysToDie.prototype, 'executeConsoleCommand').resolves(
|
|
63
|
+
new CommandOutput({
|
|
64
|
+
rawResult: testData.noBans,
|
|
65
|
+
success: true,
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const result = await new SevenDaysToDie(await mockSdtdConnectionInfo).listBans();
|
|
70
|
+
|
|
71
|
+
expect(result).to.be.an('array');
|
|
72
|
+
expect(result).to.have.lengthOf(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('Can parse ban list with a single ban with a display name', async () => {
|
|
76
|
+
sandbox.stub(SevenDaysToDie.prototype, 'executeConsoleCommand').resolves(
|
|
77
|
+
new CommandOutput({
|
|
78
|
+
rawResult: testData.oneBanWithDisplayName,
|
|
79
|
+
success: true,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const result = await new SevenDaysToDie(await mockSdtdConnectionInfo).listBans();
|
|
84
|
+
|
|
85
|
+
expect(result).to.be.an('array');
|
|
86
|
+
expect(result).to.have.lengthOf(1);
|
|
87
|
+
expect(result[0].player.gameId).to.equal('EOS_00028a9b73bb45b2b74e8f22cda7d225');
|
|
88
|
+
expect(result[0].expiresAt).to.equal('2023-06-29T19:40:59.000Z');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|