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.
@@ -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
- const { bot, BotManager, logDebuger } = require('./src/bot'); // Импортируем bot и BotManager из модуля bot/index.js
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;
@@ -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;