@takaro/gameserver 0.0.29 → 0.1.0
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/dist/TakaroEmitter.d.ts +2 -1
- package/dist/TakaroEmitter.d.ts.map +1 -1
- package/dist/TakaroEmitter.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -0
- package/dist/gameservers/7d2d/index.d.ts +5 -3
- package/dist/gameservers/7d2d/index.d.ts.map +1 -1
- package/dist/gameservers/7d2d/index.js +11 -5
- package/dist/gameservers/7d2d/index.js.map +1 -1
- package/dist/gameservers/7d2d/sdtdAPIClient.d.ts +1 -1
- package/dist/gameservers/7d2d/sdtdAPIClient.d.ts.map +1 -1
- package/dist/gameservers/7d2d/sdtdAPIClient.js +2 -1
- package/dist/gameservers/7d2d/sdtdAPIClient.js.map +1 -1
- package/dist/gameservers/generic/connectionInfo.d.ts +16 -0
- package/dist/gameservers/generic/connectionInfo.d.ts.map +1 -0
- package/dist/gameservers/{mock → generic}/connectionInfo.js +6 -14
- package/dist/gameservers/generic/connectionInfo.js.map +1 -0
- package/dist/gameservers/generic/connectorClient.d.ts +7 -0
- package/dist/gameservers/generic/connectorClient.d.ts.map +1 -0
- package/dist/gameservers/generic/connectorClient.js +84 -0
- package/dist/gameservers/generic/connectorClient.js.map +1 -0
- package/dist/gameservers/generic/emitter.d.ts +13 -0
- package/dist/gameservers/generic/emitter.d.ts.map +1 -0
- package/dist/gameservers/generic/emitter.js +31 -0
- package/dist/gameservers/generic/emitter.js.map +1 -0
- package/dist/gameservers/generic/index.d.ts +36 -0
- package/dist/gameservers/generic/index.d.ts.map +1 -0
- package/dist/gameservers/generic/index.js +170 -0
- package/dist/gameservers/generic/index.js.map +1 -0
- package/dist/gameservers/rust/index.d.ts +5 -3
- package/dist/gameservers/rust/index.d.ts.map +1 -1
- package/dist/gameservers/rust/index.js +12 -5
- package/dist/gameservers/rust/index.js.map +1 -1
- package/dist/getGame.d.ts +4 -3
- package/dist/getGame.d.ts.map +1 -1
- package/dist/getGame.js +7 -6
- package/dist/getGame.js.map +1 -1
- package/dist/interfaces/GameServer.d.ts +28 -2
- package/dist/interfaces/GameServer.d.ts.map +1 -1
- package/dist/interfaces/GameServer.js +97 -2
- package/dist/interfaces/GameServer.js.map +1 -1
- package/dist/main.d.ts +4 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -2
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/src/TakaroEmitter.ts +2 -1
- package/src/__tests__/gameEventEmitter.test.ts +4 -9
- package/src/config.ts +20 -0
- package/src/gameservers/7d2d/__tests__/7d2dEventDetection.unit.test.ts +0 -1
- package/src/gameservers/7d2d/index.ts +14 -4
- package/src/gameservers/7d2d/sdtdAPIClient.ts +3 -2
- package/src/gameservers/generic/connectionInfo.ts +19 -0
- package/src/gameservers/generic/connectorClient.ts +107 -0
- package/src/gameservers/generic/emitter.ts +36 -0
- package/src/gameservers/generic/index.ts +193 -0
- package/src/gameservers/rust/index.ts +16 -5
- package/src/getGame.ts +7 -5
- package/src/interfaces/GameServer.ts +91 -3
- package/src/main.ts +5 -3
- package/dist/gameservers/mock/connectionInfo.d.ts +0 -21
- package/dist/gameservers/mock/connectionInfo.d.ts.map +0 -1
- package/dist/gameservers/mock/connectionInfo.js.map +0 -1
- package/dist/gameservers/mock/emitter.d.ts +0 -13
- package/dist/gameservers/mock/emitter.d.ts.map +0 -1
- package/dist/gameservers/mock/emitter.js +0 -34
- package/dist/gameservers/mock/emitter.js.map +0 -1
- package/dist/gameservers/mock/index.d.ts +0 -35
- package/dist/gameservers/mock/index.d.ts.map +0 -1
- package/dist/gameservers/mock/index.js +0 -154
- package/dist/gameservers/mock/index.js.map +0 -1
- package/src/gameservers/mock/connectionInfo.ts +0 -25
- package/src/gameservers/mock/emitter.ts +0 -41
- package/src/gameservers/mock/index.ts +0 -182
package/src/TakaroEmitter.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
GameEvents,
|
|
12
12
|
} from '@takaro/modules';
|
|
13
13
|
import { errors, isTakaroDTO, logger } from '@takaro/util';
|
|
14
|
+
import EventEmitter from 'events';
|
|
14
15
|
import { isPromise } from 'util/types';
|
|
15
16
|
|
|
16
17
|
const log = logger('TakaroEmitter');
|
|
@@ -32,8 +33,8 @@ export interface IEventMap {
|
|
|
32
33
|
export abstract class TakaroEmitter {
|
|
33
34
|
private listenerMap: Map<keyof IEventMap, IEventMap[keyof IEventMap][]> = new Map();
|
|
34
35
|
|
|
36
|
+
abstract start(nodeEventEmitter?: EventEmitter): Promise<void>;
|
|
35
37
|
abstract stop(): Promise<void>;
|
|
36
|
-
abstract start(): Promise<void>;
|
|
37
38
|
|
|
38
39
|
constructor() {
|
|
39
40
|
return getErrorProxyHandler(this);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, sandbox } from '@takaro/test';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { GenericConnectionInfo } from '../gameservers/generic/connectionInfo.js';
|
|
3
|
+
import { Generic } from '../gameservers/generic/index.js';
|
|
4
4
|
import { GameEvents } from '@takaro/modules';
|
|
5
5
|
import { describe, it } from 'node:test';
|
|
6
6
|
|
|
@@ -11,17 +11,12 @@ describe('GameEventEmitter', () => {
|
|
|
11
11
|
* We use @ts-expect-error so that if the compiler fails to mark these as errors, we'll know instantly
|
|
12
12
|
*/
|
|
13
13
|
it('Has a typed event emitter', async () => {
|
|
14
|
-
const gameServer = new
|
|
14
|
+
const gameServer = new Generic(new GenericConnectionInfo({}), {}, 'dummy-id');
|
|
15
15
|
const emitter = gameServer.getEventEmitter();
|
|
16
16
|
const listenerSpy = sandbox.spy();
|
|
17
17
|
|
|
18
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
|
-
);
|
|
19
|
+
expect(() => e.player.name).to.throw("Cannot read properties of undefined (reading 'name')");
|
|
25
20
|
});
|
|
26
21
|
emitter.on(GameEvents.PLAYER_CONNECTED, listenerSpy);
|
|
27
22
|
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config, IBaseConfig } from '@takaro/config';
|
|
2
|
+
|
|
3
|
+
interface IGameServerLibConfig extends IBaseConfig {
|
|
4
|
+
connector: {
|
|
5
|
+
host: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const configSchema = {
|
|
10
|
+
connector: {
|
|
11
|
+
host: {
|
|
12
|
+
doc: 'The host of the connector',
|
|
13
|
+
format: String,
|
|
14
|
+
default: 'http://localhost:3003',
|
|
15
|
+
env: 'CONNECTOR_HOST',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const config = new Config<IGameServerLibConfig>([configSchema]);
|
|
@@ -3,11 +3,13 @@ import { IGamePlayer, IPosition } from '@takaro/modules';
|
|
|
3
3
|
import {
|
|
4
4
|
BanDTO,
|
|
5
5
|
CommandOutput,
|
|
6
|
+
IEntityDTO,
|
|
6
7
|
IGameServer,
|
|
7
8
|
IItemDTO,
|
|
8
9
|
IMessageOptsDTO,
|
|
9
10
|
IPlayerReferenceDTO,
|
|
10
11
|
TestReachabilityOutputDTO,
|
|
12
|
+
ILocationDTO,
|
|
11
13
|
} from '../../interfaces/GameServer.js';
|
|
12
14
|
import { SevenDaysToDieEmitter } from './emitter.js';
|
|
13
15
|
import { SdtdApiClient } from './sdtdAPIClient.js';
|
|
@@ -92,11 +94,11 @@ export class SevenDaysToDie implements IGameServer {
|
|
|
92
94
|
return null;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
return {
|
|
97
|
+
return new IPosition({
|
|
96
98
|
x: playerLocation.position.x,
|
|
97
99
|
y: playerLocation.position.y,
|
|
98
100
|
z: playerLocation.position.z,
|
|
99
|
-
};
|
|
101
|
+
});
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
async giveItem(player: IPlayerReferenceDTO, item: string, amount: number = 1, quality?: string): Promise<void> {
|
|
@@ -163,7 +165,6 @@ export class SevenDaysToDie implements IGameServer {
|
|
|
163
165
|
}
|
|
164
166
|
|
|
165
167
|
async sendMessage(message: string, opts?: IMessageOptsDTO) {
|
|
166
|
-
// eslint-disable-next-line quotes
|
|
167
168
|
const escapedMessage = message.replaceAll(/"/g, "'");
|
|
168
169
|
|
|
169
170
|
let command = `say "${escapedMessage}"`;
|
|
@@ -184,7 +185,8 @@ export class SevenDaysToDie implements IGameServer {
|
|
|
184
185
|
await this.executeConsoleCommand(command);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number) {
|
|
188
|
+
async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number, _dimension?: string) {
|
|
189
|
+
// 7D2D doesn't support dimensions, so we ignore the dimension parameter
|
|
188
190
|
const command = `teleportplayer EOS_${player.gameId} ${x} ${y} ${z}`;
|
|
189
191
|
await this.executeConsoleCommand(command);
|
|
190
192
|
}
|
|
@@ -349,4 +351,12 @@ export class SevenDaysToDie implements IGameServer {
|
|
|
349
351
|
async getMapTile(x: number, y: number, z: number) {
|
|
350
352
|
return this.apiClient.getMapTile(x, y, z);
|
|
351
353
|
}
|
|
354
|
+
|
|
355
|
+
async listEntities(): Promise<IEntityDTO[]> {
|
|
356
|
+
throw new errors.NotImplementedError();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async listLocations(): Promise<ILocationDTO[]> {
|
|
360
|
+
throw new errors.NotImplementedError();
|
|
361
|
+
}
|
|
352
362
|
}
|
|
@@ -92,10 +92,11 @@ export class SdtdApiClient {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
async getMapTile(x: number, y: number, z: number): Promise<
|
|
95
|
+
async getMapTile(x: number, y: number, z: number): Promise<string> {
|
|
96
96
|
const res = await this.client.get(`/map/${z}/${x}/${y}.png?t=${Date.now() / 1000}`, {
|
|
97
97
|
responseType: 'arraybuffer',
|
|
98
98
|
});
|
|
99
|
-
|
|
99
|
+
// Convert to b64
|
|
100
|
+
return Buffer.from(res.data, 'binary').toString('base64');
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IsString } from 'class-validator';
|
|
2
|
+
import { TakaroDTO } from '@takaro/util';
|
|
3
|
+
|
|
4
|
+
export class GenericConnectionInfo extends TakaroDTO<GenericConnectionInfo> {
|
|
5
|
+
@IsString()
|
|
6
|
+
public identityToken: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const genericJsonSchema = {
|
|
10
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
11
|
+
title: 'GenericConnectionInfo',
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
identityToken: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ['identityToken'],
|
|
19
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import axios, { AxiosError, AxiosInstance, isAxiosError } from 'axios';
|
|
2
|
+
import { config } from '../../config.js';
|
|
3
|
+
import { errors, logger } from '@takaro/util';
|
|
4
|
+
|
|
5
|
+
function getConnectorClient() {
|
|
6
|
+
const log = logger('client:connector');
|
|
7
|
+
|
|
8
|
+
const connectorClient = axios.create({
|
|
9
|
+
baseURL: config.get('connector.host'),
|
|
10
|
+
headers: {
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
'User-Agent': 'Takaro',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
connectorClient.interceptors.request.use((request) => {
|
|
17
|
+
log.debug(`➡️ ${request.method?.toUpperCase()} ${request.url}`, {
|
|
18
|
+
method: request.method,
|
|
19
|
+
url: request.url,
|
|
20
|
+
operation: request.data?.operation,
|
|
21
|
+
});
|
|
22
|
+
return request;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
connectorClient.interceptors.response.use(
|
|
26
|
+
(response) => {
|
|
27
|
+
log.debug(
|
|
28
|
+
`⬅️ ${response.request.method?.toUpperCase()} ${response.request.path} ${response.status} ${
|
|
29
|
+
response.statusText
|
|
30
|
+
}`,
|
|
31
|
+
{
|
|
32
|
+
status: response.status,
|
|
33
|
+
method: response.request.method,
|
|
34
|
+
url: response.request.url,
|
|
35
|
+
operation: response.config.data?.operation,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return response;
|
|
40
|
+
},
|
|
41
|
+
(error: AxiosError) => {
|
|
42
|
+
let details = {};
|
|
43
|
+
|
|
44
|
+
if (error.response?.data) {
|
|
45
|
+
const data = error.response.data as Record<string, unknown>;
|
|
46
|
+
details = JSON.stringify(data.error_description);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
log.warn(`☠️ Request errored: [${error.response?.status}] ${details}`, {
|
|
50
|
+
status: error.response?.status,
|
|
51
|
+
statusText: error.response?.statusText,
|
|
52
|
+
method: error.config?.method,
|
|
53
|
+
url: error.config?.url,
|
|
54
|
+
response: error.response?.data,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return Promise.reject(error);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return connectorClient;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class TakaroConnector {
|
|
65
|
+
private client: AxiosInstance;
|
|
66
|
+
constructor() {
|
|
67
|
+
this.client = getConnectorClient();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async requestFromServer(id: string, operation: string, data: string): Promise<any> {
|
|
71
|
+
try {
|
|
72
|
+
const res = await this.client.post(`/gameserver/${id}/request`, { operation, data });
|
|
73
|
+
return res.data.data;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (isAxiosError(error)) {
|
|
76
|
+
if (error.response?.data.meta.error.code === 'ValidationError') {
|
|
77
|
+
throw new errors.BadRequestError(
|
|
78
|
+
'The gameserver responded with bad data, please verify that the mod is up to date.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (error.response?.status) {
|
|
83
|
+
if (error.response?.status >= 400 && error.response?.status < 500) {
|
|
84
|
+
throw new errors.BadRequestError(error.response?.data.meta.error.message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async resetConnection(id: string): Promise<any> {
|
|
93
|
+
try {
|
|
94
|
+
await this.client.post(`/gameserver/${id}/reset`);
|
|
95
|
+
return;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (isAxiosError(error)) {
|
|
98
|
+
if (error.response?.data.meta.error.code === 'ValidationError') {
|
|
99
|
+
throw new errors.BadRequestError(
|
|
100
|
+
'The gameserver responded with bad data, please verify that the mod is up to date.',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { logger } from '@takaro/util';
|
|
2
|
+
import { GenericConnectionInfo } from './connectionInfo.js';
|
|
3
|
+
import { TakaroEmitter } from '../../TakaroEmitter.js';
|
|
4
|
+
import { EventMapping, GameEventTypes } from '@takaro/modules';
|
|
5
|
+
|
|
6
|
+
const log = logger('Generic');
|
|
7
|
+
export class GenericEmitter extends TakaroEmitter {
|
|
8
|
+
private scopedListener = this.privateListener.bind(this);
|
|
9
|
+
|
|
10
|
+
constructor(private config: GenericConnectionInfo) {
|
|
11
|
+
super();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async start(): Promise<void> {
|
|
15
|
+
// No-op, generic connectors initiate the connection
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async stop(): Promise<void> {
|
|
19
|
+
// No-op, generic connectors initiate the connection
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get listener() {
|
|
23
|
+
return this.scopedListener;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async privateListener(event: GameEventTypes, args: any) {
|
|
27
|
+
log.debug(`Transmitting event ${event}`);
|
|
28
|
+
const dto = EventMapping[event];
|
|
29
|
+
|
|
30
|
+
if (dto) {
|
|
31
|
+
this.emit(event, new dto(args));
|
|
32
|
+
} else {
|
|
33
|
+
this.emit(event, args);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { errors, logger, traceableClass } from '@takaro/util';
|
|
2
|
+
import { IGamePlayer, IPosition } from '@takaro/modules';
|
|
3
|
+
import {
|
|
4
|
+
BanDTO,
|
|
5
|
+
CommandOutput,
|
|
6
|
+
IEntityDTO,
|
|
7
|
+
IGameServer,
|
|
8
|
+
IItemDTO,
|
|
9
|
+
ILocationDTO,
|
|
10
|
+
IMessageOptsDTO,
|
|
11
|
+
IPlayerReferenceDTO,
|
|
12
|
+
MapInfoDTO,
|
|
13
|
+
TestReachabilityOutputDTO,
|
|
14
|
+
} from '../../interfaces/GameServer.js';
|
|
15
|
+
import { Settings } from '@takaro/apiclient';
|
|
16
|
+
import { TakaroConnector } from './connectorClient.js';
|
|
17
|
+
import { GenericConnectionInfo } from './connectionInfo.js';
|
|
18
|
+
import { JsonObject } from 'type-fest';
|
|
19
|
+
import { GenericEmitter } from './emitter.js';
|
|
20
|
+
|
|
21
|
+
@traceableClass('game:generic')
|
|
22
|
+
export class Generic implements IGameServer {
|
|
23
|
+
private logger = logger('Generic');
|
|
24
|
+
connectionInfo: GenericConnectionInfo;
|
|
25
|
+
emitter: GenericEmitter;
|
|
26
|
+
private takaroConnector = new TakaroConnector();
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
config: GenericConnectionInfo,
|
|
30
|
+
private settings: Partial<Settings> = {},
|
|
31
|
+
private gameServerId: string,
|
|
32
|
+
) {
|
|
33
|
+
this.connectionInfo = config;
|
|
34
|
+
this.emitter = new GenericEmitter(config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getEventEmitter() {
|
|
38
|
+
return this.emitter;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async requestFromServer(operation: string, data?: JsonObject) {
|
|
42
|
+
if (!data) data = {};
|
|
43
|
+
const resp = await this.takaroConnector.requestFromServer(this.gameServerId, operation, JSON.stringify(data));
|
|
44
|
+
|
|
45
|
+
if (resp && resp.error) {
|
|
46
|
+
throw new errors.BadRequestError(`Error from server: ${resp.error}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return resp;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getPlayer(rawPlayer: IPlayerReferenceDTO): Promise<IGamePlayer | null> {
|
|
53
|
+
const player = new IPlayerReferenceDTO({ gameId: rawPlayer.gameId });
|
|
54
|
+
const res = await this.requestFromServer('getPlayer', player.toJSON());
|
|
55
|
+
if (!res) return null;
|
|
56
|
+
const dto = new IGamePlayer(res);
|
|
57
|
+
await dto.validate();
|
|
58
|
+
return dto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getPlayers(): Promise<IGamePlayer[]> {
|
|
62
|
+
const res = await this.requestFromServer('getPlayers');
|
|
63
|
+
if (!res)
|
|
64
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
65
|
+
const dto: IGamePlayer[] = res.map((p: any) => new IGamePlayer(p));
|
|
66
|
+
await Promise.all(dto.map((p) => p.validate()));
|
|
67
|
+
return dto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getPlayerLocation(rawPlayer: IPlayerReferenceDTO): Promise<IPosition | null> {
|
|
71
|
+
const player = new IPlayerReferenceDTO({ gameId: rawPlayer.gameId });
|
|
72
|
+
const res = await this.requestFromServer('getPlayerLocation', player.toJSON());
|
|
73
|
+
if (!res) return null;
|
|
74
|
+
const dto = new IPosition(res);
|
|
75
|
+
await dto.validate();
|
|
76
|
+
return dto;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async testReachability(): Promise<TestReachabilityOutputDTO> {
|
|
80
|
+
try {
|
|
81
|
+
const response = await this.takaroConnector.requestFromServer(this.gameServerId, 'testReachability', '{}');
|
|
82
|
+
const dto = new TestReachabilityOutputDTO(response);
|
|
83
|
+
await dto.validate();
|
|
84
|
+
return dto;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return new TestReachabilityOutputDTO({
|
|
87
|
+
connectable: false,
|
|
88
|
+
reason: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeConsoleCommand(rawCommand: string): Promise<CommandOutput> {
|
|
94
|
+
const res = await this.requestFromServer('executeConsoleCommand', { command: rawCommand });
|
|
95
|
+
if (!res)
|
|
96
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
97
|
+
const dto = new CommandOutput(res);
|
|
98
|
+
await dto.validate();
|
|
99
|
+
return dto;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async sendMessage(message: string, opts?: IMessageOptsDTO) {
|
|
103
|
+
if (!opts) opts = new IMessageOptsDTO();
|
|
104
|
+
await this.requestFromServer('sendMessage', {
|
|
105
|
+
message,
|
|
106
|
+
opts: opts.toJSON(),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number, dimension?: string) {
|
|
111
|
+
await this.requestFromServer('teleportPlayer', { player: player.toJSON(), x, y, z, dimension });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async kickPlayer(player: IGamePlayer, reason: string) {
|
|
115
|
+
await this.requestFromServer('kickPlayer', { player: player.toJSON(), reason });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async banPlayer(options: BanDTO) {
|
|
119
|
+
await this.requestFromServer('banPlayer', options.toJSON());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async unbanPlayer(player: IGamePlayer) {
|
|
123
|
+
await this.requestFromServer('unbanPlayer', player.toJSON());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async listBans(): Promise<BanDTO[]> {
|
|
127
|
+
const res = await this.requestFromServer('listBans');
|
|
128
|
+
if (!res)
|
|
129
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
130
|
+
const dto: BanDTO[] = res.map((p: any) => new BanDTO(p));
|
|
131
|
+
await Promise.all(dto.map((p) => p.validate()));
|
|
132
|
+
return dto;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async giveItem(rawPlayer: IPlayerReferenceDTO, item: string, amount: number, quality: string): Promise<void> {
|
|
136
|
+
await this.requestFromServer('giveItem', {
|
|
137
|
+
player: { gameId: rawPlayer.gameId },
|
|
138
|
+
item,
|
|
139
|
+
amount,
|
|
140
|
+
quality,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async listItems(): Promise<IItemDTO[]> {
|
|
145
|
+
const res = await this.requestFromServer('listItems');
|
|
146
|
+
if (!res)
|
|
147
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
148
|
+
const dto: IItemDTO[] = res.map((p: any) => new IItemDTO(p));
|
|
149
|
+
await Promise.all(dto.map((p) => p.validate()));
|
|
150
|
+
return dto;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getPlayerInventory(rawPlayer: IPlayerReferenceDTO): Promise<IItemDTO[]> {
|
|
154
|
+
const player = new IPlayerReferenceDTO({ gameId: rawPlayer.gameId });
|
|
155
|
+
const res = await this.requestFromServer('getPlayerInventory', player.toJSON());
|
|
156
|
+
if (!res)
|
|
157
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
158
|
+
const dto: IItemDTO[] = res.map((p: any) => new IItemDTO(p));
|
|
159
|
+
await Promise.all(dto.map((p) => p.validate()));
|
|
160
|
+
return dto;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async shutdown(): Promise<void> {
|
|
164
|
+
await this.requestFromServer('shutdown');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getMapInfo(): Promise<MapInfoDTO> {
|
|
168
|
+
const res = await this.requestFromServer('getMapInfo');
|
|
169
|
+
if (!res)
|
|
170
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
171
|
+
const dto = new MapInfoDTO(res);
|
|
172
|
+
await dto.validate();
|
|
173
|
+
return dto;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async getMapTile(_x: number, _y: number, _z: number): Promise<string> {
|
|
177
|
+
const res = await this.requestFromServer('getMapTile', { x: _x, y: _y, z: _z });
|
|
178
|
+
return res;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async listEntities(): Promise<IEntityDTO[]> {
|
|
182
|
+
const res = await this.requestFromServer('listEntities');
|
|
183
|
+
if (!res)
|
|
184
|
+
throw new errors.ValidationError('Nothing returned from server, is the server responding the right data?');
|
|
185
|
+
const dto: IEntityDTO[] = res.map((p: any) => new IEntityDTO(p));
|
|
186
|
+
await Promise.all(dto.map((p) => p.validate()));
|
|
187
|
+
return dto;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async listLocations(): Promise<ILocationDTO[]> {
|
|
191
|
+
throw new errors.NotImplementedError();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { logger, traceableClass } from '@takaro/util';
|
|
1
|
+
import { errors, logger, traceableClass } from '@takaro/util';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
3
|
import { IGamePlayer, IPosition } from '@takaro/modules';
|
|
4
4
|
import {
|
|
5
5
|
BanDTO,
|
|
6
6
|
CommandOutput,
|
|
7
|
+
IEntityDTO,
|
|
7
8
|
IGameServer,
|
|
8
9
|
IItemDTO,
|
|
10
|
+
ILocationDTO,
|
|
9
11
|
IPlayerReferenceDTO,
|
|
10
12
|
MapInfoDTO,
|
|
11
13
|
TestReachabilityOutputDTO,
|
|
@@ -86,11 +88,11 @@ export class Rust implements IGameServer {
|
|
|
86
88
|
const z = matches[4].replace(')', '');
|
|
87
89
|
|
|
88
90
|
if (steamId === player.gameId) {
|
|
89
|
-
return {
|
|
91
|
+
return new IPosition({
|
|
90
92
|
x: parseInt(x, 10),
|
|
91
93
|
y: parseInt(y, 10),
|
|
92
94
|
z: parseInt(z, 10),
|
|
93
|
-
};
|
|
95
|
+
});
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
}
|
|
@@ -145,7 +147,8 @@ export class Rust implements IGameServer {
|
|
|
145
147
|
await this.executeConsoleCommand(`say "${message}"`);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number) {
|
|
150
|
+
async teleportPlayer(player: IGamePlayer, x: number, y: number, z: number, _dimension?: string) {
|
|
151
|
+
// Rust doesn't support dimensions, so we ignore the dimension parameter
|
|
149
152
|
await this.executeConsoleCommand(`teleportplayer.pos ${player.gameId} ${x} ${y} ${z}`);
|
|
150
153
|
}
|
|
151
154
|
|
|
@@ -250,7 +253,15 @@ export class Rust implements IGameServer {
|
|
|
250
253
|
});
|
|
251
254
|
}
|
|
252
255
|
|
|
253
|
-
async getMapTile(_x: number, _y: number, _z: number): Promise<
|
|
256
|
+
async getMapTile(_x: number, _y: number, _z: number): Promise<string> {
|
|
254
257
|
throw new Error('Not implemented');
|
|
255
258
|
}
|
|
259
|
+
|
|
260
|
+
async listEntities(): Promise<IEntityDTO[]> {
|
|
261
|
+
throw new errors.NotImplementedError();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async listLocations(): Promise<ILocationDTO[]> {
|
|
265
|
+
throw new errors.NotImplementedError();
|
|
266
|
+
}
|
|
256
267
|
}
|
package/src/getGame.ts
CHANGED
|
@@ -2,30 +2,32 @@ import { errors } from '@takaro/util';
|
|
|
2
2
|
import { GameServerOutputDTOTypeEnum, Settings } from '@takaro/apiclient';
|
|
3
3
|
import { SdtdConnectionInfo } from './gameservers/7d2d/connectionInfo.js';
|
|
4
4
|
import { SevenDaysToDie } from './gameservers/7d2d/index.js';
|
|
5
|
-
import { MockConnectionInfo } from './gameservers/mock/connectionInfo.js';
|
|
6
|
-
import { Mock } from './gameservers/mock/index.js';
|
|
7
5
|
import { RustConnectionInfo } from './gameservers/rust/connectionInfo.js';
|
|
8
6
|
import { Rust } from './gameservers/rust/index.js';
|
|
9
7
|
import { IGameServer } from './interfaces/GameServer.js';
|
|
8
|
+
import { GenericConnectionInfo } from './gameservers/generic/connectionInfo.js';
|
|
9
|
+
import { Generic } from './gameservers/generic/index.js';
|
|
10
10
|
|
|
11
11
|
export enum GAME_SERVER_TYPE {
|
|
12
|
-
'MOCK' = 'MOCK',
|
|
13
12
|
'SEVENDAYSTODIE' = 'SEVENDAYSTODIE',
|
|
14
13
|
'RUST' = 'RUST',
|
|
14
|
+
'GENERIC' = 'GENERIC',
|
|
15
|
+
'MOCK' = 'MOCK',
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export async function getGame(
|
|
18
19
|
type: GAME_SERVER_TYPE | GameServerOutputDTOTypeEnum,
|
|
19
20
|
connectionInfo: Record<string, unknown>,
|
|
20
21
|
settings: Partial<Settings>,
|
|
22
|
+
gameServerId: string,
|
|
21
23
|
): Promise<IGameServer> {
|
|
22
24
|
switch (type) {
|
|
23
25
|
case GAME_SERVER_TYPE.SEVENDAYSTODIE:
|
|
24
26
|
return new SevenDaysToDie(new SdtdConnectionInfo(connectionInfo), settings);
|
|
25
27
|
case GAME_SERVER_TYPE.RUST:
|
|
26
28
|
return new Rust(new RustConnectionInfo(connectionInfo), settings);
|
|
27
|
-
case GAME_SERVER_TYPE.
|
|
28
|
-
return new
|
|
29
|
+
case GAME_SERVER_TYPE.GENERIC:
|
|
30
|
+
return new Generic(new GenericConnectionInfo(connectionInfo), settings, gameServerId);
|
|
29
31
|
default:
|
|
30
32
|
throw new errors.NotImplementedError();
|
|
31
33
|
}
|