ddbot.js-0374 3.2.7 → 4.0.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/docs/documentation.md +0 -141
- package/index.js +4 -14
- package/lib/core/core.d.ts +105 -0
- package/lib/core/core.js +270 -0
- package/lib/core/ddutils.d.ts +33 -0
- package/lib/core/ddutils.js +149 -0
- package/lib/core/module.d.ts +40 -0
- package/lib/core/module.js +65 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +16 -0
- package/lib/manager.d.ts +17 -0
- package/lib/manager.js +136 -0
- package/lib/modules/chat.d.ts +46 -0
- package/lib/modules/chat.js +120 -0
- package/lib/modules/playerlist.d.ts +33 -0
- package/lib/modules/playerlist.js +78 -0
- package/lib/modules/reconnect.d.ts +14 -0
- package/lib/modules/reconnect.js +81 -0
- package/lib/modules/snap.d.ts +17 -0
- package/lib/modules/snap.js +61 -0
- package/lib/types.d.ts +255 -0
- package/lib/types.js +4 -0
- package/package.json +6 -9
- package/README.md +0 -167
- package/ddbot.js-0374.d.ts +0 -200
- package/docs/examples/chat.js +0 -87
- package/docs/examples/echo-bot.js +0 -66
- package/docs/examples/main.js +0 -140
- package/src/bot/bot.js +0 -512
- package/src/bot/index.js +0 -5
package/docs/documentation.md
CHANGED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# bot/BotManager
|
|
2
|
-
|
|
3
|
-
установка
|
|
4
|
-
"npm i ddbot.js-0374"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Ето модуль для роботы с ДДНет (DDNet/teeworlds) ботами. внутри изпользует чистую библиотеку teeworlds (https://www.npmjs.com/package/teeworlds, https://www.npmjs.com/~swarfey) и просто делает рутинные вещи за вас. Например, может работать с кучей ботов на разных серверах, делает чат не повторяющимся без спамов. Убирает системные сообщения из чата. Можно получить весь список игроков одним методом.
|
|
8
|
-
|
|
9
|
-
Вообще я делал его для себя, и по приколу. Но решил выложить чтобы проще работать с зависимостями и не качать с гита.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Окей...
|
|
13
|
-
Сейчас я вам разкажу как работает bot/BotManager.
|
|
14
|
-
|
|
15
|
-
teeworlds
|
|
16
|
-
## методы
|
|
17
|
-
У нас есть :
|
|
18
|
-
1. bot.createBot()
|
|
19
|
-
1. fulladdress - Полный адрес сервера (IP:порт)
|
|
20
|
-
2. botName - Имя бота
|
|
21
|
-
3. parameter - Параметры бота
|
|
22
|
-
1. identity со скином и другими косметическими деталями.
|
|
23
|
-
2. reconnect переподключаеться бот или нет
|
|
24
|
-
3. reconnectAttempts количество возможных реконектов (-1 для безконечного)
|
|
25
|
-
4. randreconnect будет ли бот переключаться с чуть разной задержкой или нет (в районе 0 - 1 сек)
|
|
26
|
-
- На выходе у нас имя бота по которому можно обращаться. (Уникальное имя бота или null в случае ошибки)
|
|
27
|
-
2. bot.connectBot()
|
|
28
|
-
1. Принимает уникальное имя бота и подключает нужного.
|
|
29
|
-
- На выхоте булевое значение (нет точных данных подключился ли бот или нет)
|
|
30
|
-
3. bot.disconnectBot()
|
|
31
|
-
1. Принимает уникальное имя бота и отключает нужного.
|
|
32
|
-
- Работает также как и connectBot().
|
|
33
|
-
4. bot.disconnectAllBots()
|
|
34
|
-
Отключает всех ботов, не принимает аргументов.
|
|
35
|
-
Под капотом disconnectBot().
|
|
36
|
-
5. bot.getBotInfo()
|
|
37
|
-
1. Принимает уникальное имя бота и возвращает информацию про него.
|
|
38
|
-
- Информация о боте или null, если бот не найден
|
|
39
|
-
6. bot.isBotConnected()
|
|
40
|
-
1. Принимает уникальное имя бота.
|
|
41
|
-
- Возвращает булевое значение.
|
|
42
|
-
7. bot.isFreezeBot()
|
|
43
|
-
1. Принимает уникальное имя бота.
|
|
44
|
-
- Возвращает булевое значение.
|
|
45
|
-
8. bot.setFreezeBot()
|
|
46
|
-
Просто меняет значение того заморожен ли бот. (не влияет на игру)
|
|
47
|
-
9. bot.getAllActiveBots()
|
|
48
|
-
Получение всех активных ботов
|
|
49
|
-
- Массив имен всех активных ботов
|
|
50
|
-
10. bot.getBotClient()
|
|
51
|
-
1. Принимает уникальное имя бота.
|
|
52
|
-
- Возвращает клиент teeworlds
|
|
53
|
-
11. bot.removeBot()
|
|
54
|
-
Удаляет бота полностью. (и отключает)
|
|
55
|
-
1. Принимает уникальное имя бота.
|
|
56
|
-
- Возвращает булевое значение.
|
|
57
|
-
12. bot.getBot()
|
|
58
|
-
1. Принимает уникальное имя бота.
|
|
59
|
-
- Возвращает прокси-объект бота.
|
|
60
|
-
13. bot._setupBotEvents()
|
|
61
|
-
Заставляет ивенты работать. Изпользуеться в bot.createBot() чтобы вы могли делать все проще и вам не нужно было получать оригинальный клиент teeworlds для ивентов.
|
|
62
|
-
1. Принимает уникальное имя бота.
|
|
63
|
-
2. Принимает клиент teeworlds
|
|
64
|
-
Ничего не возвращает.
|
|
65
|
-
14. bot.getPlayerList()
|
|
66
|
-
1. Принимает уникальное имя бота.
|
|
67
|
-
- Array список игроков.
|
|
68
|
-
15. bot.getPlayerName()
|
|
69
|
-
1. Принимает уникальное имя бота/Array список игроков.
|
|
70
|
-
2. clientid нужного игрока.
|
|
71
|
-
- Имя нужного игрока.
|
|
72
|
-
|
|
73
|
-
## пример ехо бота
|
|
74
|
-
```js
|
|
75
|
-
// Пример писался под комит c453b96f9fd717375f5ff70525603b7e1491c290.
|
|
76
|
-
|
|
77
|
-
const DebugLogger = require('loger0374'); // мой логер (не обязательно изпользовать)
|
|
78
|
-
const { bot, botClassAndLoger } = require('../../index');
|
|
79
|
-
const botdebug = botClassAndLoger.logDebuger;
|
|
80
|
-
botdebug.setDebugMode(true, true, true);
|
|
81
|
-
|
|
82
|
-
const logDebuger = new DebugLogger('example', true, true, null, true);
|
|
83
|
-
|
|
84
|
-
async function main() {
|
|
85
|
-
logDebuger.logDebug('Main started');
|
|
86
|
-
|
|
87
|
-
const identitybot = {
|
|
88
|
-
name: "Towa",
|
|
89
|
-
clan: "Towa Team",
|
|
90
|
-
skin: "Astolfofinho",
|
|
91
|
-
use_custom_color: 1,
|
|
92
|
-
color_body: 16711680,
|
|
93
|
-
color_feet: 16711680,
|
|
94
|
-
country: 804
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const botName = await bot.createBot('45.141.57.22:8311', 'Towa', {
|
|
98
|
-
identity: identitybot,
|
|
99
|
-
reconnect: true,
|
|
100
|
-
reconnectAttempts: -1,
|
|
101
|
-
randreconnect: true
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
bot.connectBot(botName); // подкюлчаем
|
|
105
|
-
|
|
106
|
-
const botClient = bot.getBotClient(botName); // получаем оригинальный клиент teeworlds
|
|
107
|
-
|
|
108
|
-
// Подписка на событие подключения
|
|
109
|
-
bot.on(`${botName}:connect`, () => {
|
|
110
|
-
let timemsg = 0; // время
|
|
111
|
-
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
botClient.game.Say('Ку всем');
|
|
114
|
-
}, 1251);
|
|
115
|
-
|
|
116
|
-
// подписка на чат
|
|
117
|
-
bot.on(`${botName}:ChatNoSystem`, (msgraw, autormsg, text, team, client_id) => {
|
|
118
|
-
logDebuger.logDebug(`${client_id} ${team} '${autormsg}' : ${text}`); // вывод чата в консоль
|
|
119
|
-
if (text == 'exit') exit(); // выход
|
|
120
|
-
|
|
121
|
-
// Эхо-логика
|
|
122
|
-
if (Date.now() - timemsg > 6000) {
|
|
123
|
-
timemsg = Date.now(); // устанавливаем текущее время
|
|
124
|
-
if (text && autormsg) {
|
|
125
|
-
botClient.game.Say(`${autormsg}: ${text}`); // отправка сообения (teeworlds)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Выход через Ctrl+C
|
|
132
|
-
async function exit() {
|
|
133
|
-
logDebuger.logDebug('Shutting down...');
|
|
134
|
-
await bot.disconnectAllBots(); // отключаем всех ботов
|
|
135
|
-
process.exit(0); // завершаем процес
|
|
136
|
-
}
|
|
137
|
-
process.on('SIGINT', exit); // Ctrl+C
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (require.main === module) main();
|
|
141
|
-
```
|
package/index.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Зделал так чтобы было меньше експорта.
|
|
6
|
-
* Тут у нас не активированый BotManager.
|
|
7
|
-
* И logDebuger который изпользуеться в BotManager, а точнее в bot.
|
|
8
|
-
*/
|
|
9
|
-
const botClassAndLoger = {
|
|
10
|
-
BotManager,
|
|
11
|
-
logDebuger
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
module.exports = { bot, botClassAndLoger }; // Экспортируем bot, mapLoader, Automaploader, botClassAndLoger и DebugLogger
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var ddbot = require("./lib/index");
|
|
4
|
+
exports.default = ddbot;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Client } from 'teeworlds';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import * as Types from '../types.js';
|
|
4
|
+
interface BotEvents {
|
|
5
|
+
connect: (info: Types.ConnectionInfo) => void;
|
|
6
|
+
disconnect: (reason: string | null, info: Types.ConnectionInfo) => void;
|
|
7
|
+
broadcast: (message: string) => void;
|
|
8
|
+
capabilities: (message: {
|
|
9
|
+
ChatTimeoutCode: boolean;
|
|
10
|
+
AnyPlayerFlag: boolean;
|
|
11
|
+
PingEx: boolean;
|
|
12
|
+
AllowDummy: boolean;
|
|
13
|
+
SyncWeaponInput: boolean;
|
|
14
|
+
}) => void;
|
|
15
|
+
emote: (message: Types.SnapshotItemTypes.iEmoticon) => void;
|
|
16
|
+
kill: (kill: Types.SnapshotItemTypes.iKillMsg) => void;
|
|
17
|
+
snapshot: (items: Types.DeltaItem[]) => void;
|
|
18
|
+
map_change: (message: Types.SnapshotItemTypes.iMapChange) => void;
|
|
19
|
+
motd: (message: string) => void;
|
|
20
|
+
message: (message: Types.SnapshotItemTypes.iMessage) => void;
|
|
21
|
+
teams: (teams: Array<number>) => void;
|
|
22
|
+
teamkill: (teamkill: Types.SnapshotItemTypes.iKillMsgTeam) => void;
|
|
23
|
+
spawn: (msg: Types.SnapshotItemTypes.Spawn) => void;
|
|
24
|
+
death: (msg: Types.SnapshotItemTypes.Death) => void;
|
|
25
|
+
hammerhit: (msg: Types.SnapshotItemTypes.HammerHit) => void;
|
|
26
|
+
sound_world: (msg: Types.SnapshotItemTypes.SoundWorld) => void;
|
|
27
|
+
explosion: (msg: Types.SnapshotItemTypes.Explosion) => void;
|
|
28
|
+
common: (msg: Types.SnapshotItemTypes.Common) => void;
|
|
29
|
+
damage_indicator: (msg: Types.SnapshotItemTypes.DamageInd) => void;
|
|
30
|
+
sound_global: (msg: Types.SnapshotItemTypes.SoundGlobal) => void;
|
|
31
|
+
}
|
|
32
|
+
interface BotStatus {
|
|
33
|
+
connect: {
|
|
34
|
+
connected: boolean;
|
|
35
|
+
connecting: boolean;
|
|
36
|
+
};
|
|
37
|
+
addr: string | null;
|
|
38
|
+
port: number | null;
|
|
39
|
+
}
|
|
40
|
+
export declare class Bot extends EventEmitter {
|
|
41
|
+
private teeworlds;
|
|
42
|
+
private client;
|
|
43
|
+
private _clientProxy;
|
|
44
|
+
options: Types.SnapshotItemTypes.iOptions;
|
|
45
|
+
identity: Types.SnapshotItemTypes.Identity;
|
|
46
|
+
status: BotStatus;
|
|
47
|
+
constructor(identity?: Types.SnapshotItemTypes.Identity, options?: Types.SnapshotItemTypes.iOptions, CustomTeeworlds?: typeof import('teeworlds'));
|
|
48
|
+
/**
|
|
49
|
+
* Get bot identity
|
|
50
|
+
*/
|
|
51
|
+
get bot_identity(): Types.SnapshotItemTypes.Identity;
|
|
52
|
+
/**
|
|
53
|
+
* Create new Teeworlds client instance
|
|
54
|
+
*/
|
|
55
|
+
private create_client;
|
|
56
|
+
/**
|
|
57
|
+
* Clean up client event listeners
|
|
58
|
+
*/
|
|
59
|
+
private clean;
|
|
60
|
+
/**
|
|
61
|
+
* Connect to a DDNet server
|
|
62
|
+
* @param addr - Server address
|
|
63
|
+
* @param port - Server port (default: 8303)
|
|
64
|
+
* @param timeout - Connection timeout in ms (default: 5000)
|
|
65
|
+
* @returns Promise with connection info
|
|
66
|
+
*/
|
|
67
|
+
connect(addr: string, port?: number, timeout?: number): Promise<Types.ConnectionInfo>;
|
|
68
|
+
/**
|
|
69
|
+
* Disconnect from server
|
|
70
|
+
* @returns Promise with disconnection info or null
|
|
71
|
+
*/
|
|
72
|
+
disconnect(): Promise<Types.ConnectionInfo | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Change bot identity (name, skin, colors, etc.)
|
|
75
|
+
* @param identity - New identity or partial identity update
|
|
76
|
+
*/
|
|
77
|
+
change_identity(identity: Types.SnapshotItemTypes.Identity | Partial<Types.SnapshotItemTypes.Identity>): void;
|
|
78
|
+
/**
|
|
79
|
+
* Send input to the game (movement, aim, actions)
|
|
80
|
+
* @param input - Partial input object
|
|
81
|
+
*/
|
|
82
|
+
send_input(input: Partial<Types.SnapshotItemTypes.PlayerInput>): void;
|
|
83
|
+
/**
|
|
84
|
+
* Setup all client event listeners and forward them
|
|
85
|
+
*/
|
|
86
|
+
private client_events;
|
|
87
|
+
private setup_snapshot_events;
|
|
88
|
+
/**
|
|
89
|
+
* Get bot's own client ID
|
|
90
|
+
*/
|
|
91
|
+
get OwnID(): number | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Get proxied client instance for safe access
|
|
94
|
+
*/
|
|
95
|
+
get bot_client(): Client | null;
|
|
96
|
+
/**
|
|
97
|
+
* Destroy bot instance and clean up all resources
|
|
98
|
+
*/
|
|
99
|
+
destroy(): Promise<void>;
|
|
100
|
+
on<K extends keyof BotEvents>(event: K, listener: BotEvents[K]): this;
|
|
101
|
+
once<K extends keyof BotEvents>(event: K, listener: BotEvents[K]): this;
|
|
102
|
+
emit<K extends keyof BotEvents>(event: K, ...args: Parameters<BotEvents[K]>): boolean;
|
|
103
|
+
off<K extends keyof BotEvents>(event: K, listener: BotEvents[K]): this;
|
|
104
|
+
}
|
|
105
|
+
export default Bot;
|
package/lib/core/core.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import { Client } from 'teeworlds';
|
|
3
|
+
import * as Teeworlds from 'teeworlds';
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import * as DDUtils from './ddutils.js';
|
|
6
|
+
import * as Types from '../types.js';
|
|
7
|
+
export class Bot extends EventEmitter {
|
|
8
|
+
teeworlds;
|
|
9
|
+
client = null;
|
|
10
|
+
_clientProxy = null;
|
|
11
|
+
options;
|
|
12
|
+
identity;
|
|
13
|
+
status = {
|
|
14
|
+
connect: {
|
|
15
|
+
connected: false,
|
|
16
|
+
connecting: false,
|
|
17
|
+
},
|
|
18
|
+
addr: null,
|
|
19
|
+
port: null,
|
|
20
|
+
};
|
|
21
|
+
constructor(identity, options = {}, CustomTeeworlds = Teeworlds) {
|
|
22
|
+
super();
|
|
23
|
+
this.teeworlds = CustomTeeworlds;
|
|
24
|
+
this.options = options;
|
|
25
|
+
this.identity = DDUtils.IsValidIdentity(identity)
|
|
26
|
+
? identity
|
|
27
|
+
: DDUtils.DefaultIdentity('nameless tee');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get bot identity
|
|
31
|
+
*/
|
|
32
|
+
get bot_identity() {
|
|
33
|
+
return this.identity;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create new Teeworlds client instance
|
|
37
|
+
*/
|
|
38
|
+
create_client(addr, port) {
|
|
39
|
+
this.disconnect();
|
|
40
|
+
this.clean(true);
|
|
41
|
+
this.client = new this.teeworlds.Client(addr, port, this.identity.name, {
|
|
42
|
+
...this.options,
|
|
43
|
+
identity: this.identity,
|
|
44
|
+
});
|
|
45
|
+
this.client.movement.FlagScoreboard(true);
|
|
46
|
+
this.client_events();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Clean up client event listeners
|
|
50
|
+
*/
|
|
51
|
+
clean(clear = false) {
|
|
52
|
+
try {
|
|
53
|
+
if (!this.client)
|
|
54
|
+
return;
|
|
55
|
+
this.client.removeAllListeners();
|
|
56
|
+
this.client.SnapshotUnpacker?.removeAllListeners();
|
|
57
|
+
if (clear) {
|
|
58
|
+
this.client = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
console.error('Error during clean:', e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Connect to a DDNet server
|
|
67
|
+
* @param addr - Server address
|
|
68
|
+
* @param port - Server port (default: 8303)
|
|
69
|
+
* @param timeout - Connection timeout in ms (default: 5000)
|
|
70
|
+
* @returns Promise with connection info
|
|
71
|
+
*/
|
|
72
|
+
connect(addr, port = 8303, timeout = 5000) {
|
|
73
|
+
if (typeof addr !== 'string' || typeof port !== 'number') {
|
|
74
|
+
return Promise.reject(new Error('Invalid address or port'));
|
|
75
|
+
}
|
|
76
|
+
if (timeout <= 0) {
|
|
77
|
+
return Promise.reject(new Error('Timeout must be positive'));
|
|
78
|
+
}
|
|
79
|
+
if (this.status.connect.connected || this.status.connect.connecting) {
|
|
80
|
+
return Promise.reject(new Error('Already connected or connecting'));
|
|
81
|
+
}
|
|
82
|
+
this.status.connect.connecting = true;
|
|
83
|
+
this.status.addr = addr;
|
|
84
|
+
this.status.port = port;
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
this.create_client(addr, port);
|
|
87
|
+
let settled = false;
|
|
88
|
+
const cleanup = () => {
|
|
89
|
+
this.off('connect', onConnect);
|
|
90
|
+
this.off('disconnect', onDisconnect);
|
|
91
|
+
};
|
|
92
|
+
const onConnect = () => {
|
|
93
|
+
if (settled)
|
|
94
|
+
return;
|
|
95
|
+
settled = true;
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
cleanup();
|
|
98
|
+
this.status.connect.connecting = false;
|
|
99
|
+
resolve({ addr: this.status.addr, port: this.status.port });
|
|
100
|
+
};
|
|
101
|
+
const onDisconnect = (reason) => {
|
|
102
|
+
if (settled)
|
|
103
|
+
return;
|
|
104
|
+
settled = true;
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
cleanup();
|
|
107
|
+
this.status.connect.connecting = false;
|
|
108
|
+
reject(new Error(`Disconnected during connect: ${reason ?? 'unknown reason'}`));
|
|
109
|
+
};
|
|
110
|
+
const timer = setTimeout(() => {
|
|
111
|
+
if (settled)
|
|
112
|
+
return;
|
|
113
|
+
settled = true;
|
|
114
|
+
cleanup();
|
|
115
|
+
this.status.connect.connecting = false;
|
|
116
|
+
this.clean(true);
|
|
117
|
+
reject(new Error('Connection timeout'));
|
|
118
|
+
}, timeout);
|
|
119
|
+
this.once('connect', onConnect);
|
|
120
|
+
this.once('disconnect', onDisconnect);
|
|
121
|
+
this.client.connect();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Disconnect from server
|
|
126
|
+
* @returns Promise with disconnection info or null
|
|
127
|
+
*/
|
|
128
|
+
async disconnect() {
|
|
129
|
+
this.status.connect.connecting = false;
|
|
130
|
+
let info = null;
|
|
131
|
+
if (this.client && this.status.connect.connected) {
|
|
132
|
+
try {
|
|
133
|
+
await this.client.Disconnect();
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
console.error('Error during disconnect:', e);
|
|
137
|
+
}
|
|
138
|
+
this.status.connect.connected = false;
|
|
139
|
+
info = { addr: this.status.addr, port: this.status.port };
|
|
140
|
+
this.emit('disconnect', null, info);
|
|
141
|
+
}
|
|
142
|
+
this.clean(true);
|
|
143
|
+
return info;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Change bot identity (name, skin, colors, etc.)
|
|
147
|
+
* @param identity - New identity or partial identity update
|
|
148
|
+
*/
|
|
149
|
+
change_identity(identity) {
|
|
150
|
+
this.identity =
|
|
151
|
+
typeof identity === 'object' && identity !== null
|
|
152
|
+
? { ...this.identity, ...identity }
|
|
153
|
+
: DDUtils.DefaultIdentity(this.identity.name);
|
|
154
|
+
if (this.client && this.status.connect.connected) {
|
|
155
|
+
this.client.game.ChangePlayerInfo(this.identity);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Send input to the game (movement, aim, actions)
|
|
160
|
+
* @param input - Partial input object
|
|
161
|
+
*/
|
|
162
|
+
send_input(input) {
|
|
163
|
+
if (!this.client)
|
|
164
|
+
return;
|
|
165
|
+
this.client.movement.input = { ...this.client.movement.input, ...input };
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Setup all client event listeners and forward them
|
|
169
|
+
*/
|
|
170
|
+
client_events() {
|
|
171
|
+
this.clean(false);
|
|
172
|
+
if (!this.client)
|
|
173
|
+
return;
|
|
174
|
+
// Connection events
|
|
175
|
+
this.client.on('connected', () => {
|
|
176
|
+
this.status.connect.connected = true;
|
|
177
|
+
this.emit('connect', { addr: this.status.addr, port: this.status.port });
|
|
178
|
+
this.setup_snapshot_events();
|
|
179
|
+
});
|
|
180
|
+
this.client.on('disconnect', (reason = null) => {
|
|
181
|
+
this.status.connect.connected = false;
|
|
182
|
+
this.clean(true);
|
|
183
|
+
if (reason) {
|
|
184
|
+
this.emit('disconnect', reason, { addr: this.status.addr, port: this.status.port });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// Game events
|
|
188
|
+
this.client.on('broadcast', (msg) => this.emit('broadcast', msg));
|
|
189
|
+
this.client.on('capabilities', (msg) => this.emit('capabilities', msg));
|
|
190
|
+
this.client.on('emote', (msg) => this.emit('emote', msg));
|
|
191
|
+
this.client.on('kill', (msg) => this.emit('kill', msg));
|
|
192
|
+
this.client.on('snapshot', (msg) => this.emit('snapshot', msg));
|
|
193
|
+
this.client.on('map_change', (msg) => this.emit('map_change', msg));
|
|
194
|
+
this.client.on('motd', (msg) => this.emit('motd', msg));
|
|
195
|
+
this.client.on('message', (msg) => this.emit('message', msg));
|
|
196
|
+
this.client.on('teams', (msg) => this.emit('teams', msg));
|
|
197
|
+
this.client.on('teamkill', (msg) => this.emit('teamkill', msg));
|
|
198
|
+
}
|
|
199
|
+
/*
|
|
200
|
+
* Setup snapshot unpacker events
|
|
201
|
+
*/
|
|
202
|
+
setup_snapshot_events() {
|
|
203
|
+
if (!this.client?.SnapshotUnpacker) {
|
|
204
|
+
console.warn('SnapshotUnpacker not available yet');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.client.SnapshotUnpacker.on('spawn', (msg) => this.emit('spawn', msg));
|
|
208
|
+
this.client.SnapshotUnpacker.on('death', (msg) => this.emit('death', msg));
|
|
209
|
+
this.client.SnapshotUnpacker.on('hammerhit', (msg) => this.emit('hammerhit', msg));
|
|
210
|
+
this.client.SnapshotUnpacker.on('sound_world', (msg) => this.emit('sound_world', msg));
|
|
211
|
+
this.client.SnapshotUnpacker.on('explosion', (msg) => this.emit('explosion', msg));
|
|
212
|
+
this.client.SnapshotUnpacker.on('common', (msg) => this.emit('common', msg));
|
|
213
|
+
this.client.SnapshotUnpacker.on('damage_indicator', (msg) => this.emit('damage_indicator', msg));
|
|
214
|
+
this.client.SnapshotUnpacker.on('sound_global', (msg) => this.emit('sound_global', msg));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get bot's own client ID
|
|
218
|
+
*/
|
|
219
|
+
get OwnID() {
|
|
220
|
+
if (!this.client || !this.status.connect.connected)
|
|
221
|
+
return undefined;
|
|
222
|
+
return this.client.SnapshotUnpacker?.OwnID;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get proxied client instance for safe access
|
|
226
|
+
*/
|
|
227
|
+
get bot_client() {
|
|
228
|
+
if (!this.client)
|
|
229
|
+
return null;
|
|
230
|
+
if (!this._clientProxy) {
|
|
231
|
+
const self = this;
|
|
232
|
+
this._clientProxy = new Proxy({}, {
|
|
233
|
+
get(target, prop) {
|
|
234
|
+
if (!self.client)
|
|
235
|
+
return undefined;
|
|
236
|
+
const value = self.client[prop];
|
|
237
|
+
return typeof value === 'function' ? value.bind(self.client) : value;
|
|
238
|
+
},
|
|
239
|
+
set(target, prop, value) {
|
|
240
|
+
if (!self.client)
|
|
241
|
+
return false;
|
|
242
|
+
self.client[prop] = value;
|
|
243
|
+
return true;
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return this._clientProxy;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Destroy bot instance and clean up all resources
|
|
251
|
+
*/
|
|
252
|
+
async destroy() {
|
|
253
|
+
await this.disconnect();
|
|
254
|
+
this.removeAllListeners();
|
|
255
|
+
this._clientProxy = null;
|
|
256
|
+
}
|
|
257
|
+
on(event, listener) {
|
|
258
|
+
return super.on(event, listener);
|
|
259
|
+
}
|
|
260
|
+
once(event, listener) {
|
|
261
|
+
return super.once(event, listener);
|
|
262
|
+
}
|
|
263
|
+
emit(event, ...args) {
|
|
264
|
+
return super.emit(event, ...args);
|
|
265
|
+
}
|
|
266
|
+
off(event, listener) {
|
|
267
|
+
return super.off(event, listener);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export default Bot;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ConnectionInfo {
|
|
2
|
+
addr: string;
|
|
3
|
+
port: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Identity {
|
|
6
|
+
name: string;
|
|
7
|
+
clan: string;
|
|
8
|
+
skin: string;
|
|
9
|
+
use_custom_color: 0 | 1;
|
|
10
|
+
color_body: number;
|
|
11
|
+
color_feet: number;
|
|
12
|
+
country: number;
|
|
13
|
+
}
|
|
14
|
+
export interface Input {
|
|
15
|
+
m_Direction: -1 | 0 | 1;
|
|
16
|
+
m_TargetX: number;
|
|
17
|
+
m_TargetY: number;
|
|
18
|
+
m_Jump: 0 | 1;
|
|
19
|
+
m_Fire: 0 | 1;
|
|
20
|
+
m_Hook: 0 | 1;
|
|
21
|
+
m_PlayerFlags: number;
|
|
22
|
+
m_WantedWeapon: 1 | 2 | 3 | 4 | 5 | 6;
|
|
23
|
+
m_NextWeapon: 0 | 1;
|
|
24
|
+
m_PrevWeapon: 0 | 1;
|
|
25
|
+
}
|
|
26
|
+
export declare function DefaultIdentity(name?: string): Identity;
|
|
27
|
+
export declare function IsValidIdentity(identity: unknown): identity is Identity;
|
|
28
|
+
export declare function IsValidClient(client: unknown): boolean;
|
|
29
|
+
export declare function IsValidInput(input: unknown): input is Input;
|
|
30
|
+
export declare function random(min: number, max: number): number;
|
|
31
|
+
export declare function connectionInfo(): ConnectionInfo;
|
|
32
|
+
import type { SnapshotItemTypes } from '../types.js';
|
|
33
|
+
export declare function reconstructPlayerInput(char: SnapshotItemTypes.Character, ddnetChar?: SnapshotItemTypes.DDNetCharacter | null, tick?: number | null): SnapshotItemTypes.PlayerInput;
|