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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export function DefaultIdentity(name = 'nameless tee') {
|
|
2
|
+
return {
|
|
3
|
+
name: name,
|
|
4
|
+
clan: "",
|
|
5
|
+
skin: "default",
|
|
6
|
+
use_custom_color: 0,
|
|
7
|
+
color_body: 0,
|
|
8
|
+
color_feet: 0,
|
|
9
|
+
country: 0
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function IsValidIdentity(identity) {
|
|
13
|
+
if (!identity || typeof identity !== 'object') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const obj = identity;
|
|
17
|
+
// Только name обязательно!
|
|
18
|
+
if (!('name' in obj))
|
|
19
|
+
return false;
|
|
20
|
+
if (typeof obj.name !== 'string')
|
|
21
|
+
return false;
|
|
22
|
+
if (obj.name.length > 15 || obj.name.length === 0)
|
|
23
|
+
return false;
|
|
24
|
+
// Остальные поля опциональны, но если есть - валидируй
|
|
25
|
+
if ('clan' in obj) {
|
|
26
|
+
if (typeof obj.clan !== 'string')
|
|
27
|
+
return false;
|
|
28
|
+
if (obj.clan.length > 11)
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if ('skin' in obj) {
|
|
32
|
+
if (typeof obj.skin !== 'string')
|
|
33
|
+
return false;
|
|
34
|
+
if (obj.skin.length > 23 || obj.skin.length === 0)
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if ('use_custom_color' in obj) {
|
|
38
|
+
if (typeof obj.use_custom_color !== 'number')
|
|
39
|
+
return false;
|
|
40
|
+
if (obj.use_custom_color !== 0 && obj.use_custom_color !== 1)
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if ('color_body' in obj) {
|
|
44
|
+
if (typeof obj.color_body !== 'number')
|
|
45
|
+
return false;
|
|
46
|
+
if (!Number.isInteger(obj.color_body) || obj.color_body < 0)
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if ('color_feet' in obj) {
|
|
50
|
+
if (typeof obj.color_feet !== 'number')
|
|
51
|
+
return false;
|
|
52
|
+
if (!Number.isInteger(obj.color_feet) || obj.color_feet < 0)
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if ('country' in obj) {
|
|
56
|
+
if (typeof obj.country !== 'number')
|
|
57
|
+
return false;
|
|
58
|
+
if (!Number.isInteger(obj.country) || obj.country < -1 || obj.country > 999)
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
export function IsValidClient(client) {
|
|
64
|
+
if (!client || typeof client !== 'object') {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const obj = client;
|
|
68
|
+
const requiredMethods = ['Flush', 'SendMsgRaw', 'sendInput', 'Disconnect', 'connect'];
|
|
69
|
+
for (const method of requiredMethods) {
|
|
70
|
+
if (typeof obj[method] !== 'function') {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
export function IsValidInput(input) {
|
|
77
|
+
if (!input || typeof input !== 'object') {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const obj = input;
|
|
81
|
+
const requiredFields = ['m_Direction', 'm_TargetX', 'm_TargetY', 'm_Jump', 'm_Fire', 'm_Hook', 'm_PlayerFlags', 'm_WantedWeapon', 'm_NextWeapon', 'm_PrevWeapon'];
|
|
82
|
+
for (const field of requiredFields) {
|
|
83
|
+
if (!(field in obj) || typeof obj[field] !== 'number' || !Number.isInteger(obj[field])) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (obj.m_Direction < -1 || obj.m_Direction > 1)
|
|
88
|
+
return false;
|
|
89
|
+
if (obj.m_Jump !== 0 && obj.m_Jump !== 1)
|
|
90
|
+
return false;
|
|
91
|
+
if (obj.m_Fire !== 0 && obj.m_Fire !== 1)
|
|
92
|
+
return false;
|
|
93
|
+
if (obj.m_Hook !== 0 && obj.m_Hook !== 1)
|
|
94
|
+
return false;
|
|
95
|
+
if (obj.m_WantedWeapon < 1 || obj.m_WantedWeapon > 6)
|
|
96
|
+
return false;
|
|
97
|
+
if (obj.m_NextWeapon !== 0 && obj.m_NextWeapon !== 1)
|
|
98
|
+
return false;
|
|
99
|
+
if (obj.m_PrevWeapon !== 0 && obj.m_PrevWeapon !== 1)
|
|
100
|
+
return false;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
export function random(min, max) {
|
|
104
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
105
|
+
}
|
|
106
|
+
export function connectionInfo() {
|
|
107
|
+
return {
|
|
108
|
+
addr: 'string',
|
|
109
|
+
port: 8303
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function reconstructPlayerInput(char, ddnetChar = null, tick = null) {
|
|
113
|
+
const input = {
|
|
114
|
+
direction: char.character_core.direction,
|
|
115
|
+
target_x: 0,
|
|
116
|
+
target_y: -1,
|
|
117
|
+
jump: 0,
|
|
118
|
+
fire: 0,
|
|
119
|
+
hook: 0,
|
|
120
|
+
player_flags: char.player_flags || 0,
|
|
121
|
+
wanted_weapon: char.weapon,
|
|
122
|
+
next_weapon: 0,
|
|
123
|
+
prev_weapon: 0
|
|
124
|
+
};
|
|
125
|
+
if (ddnetChar && (ddnetChar.m_TargetX !== 0 || ddnetChar.m_TargetY !== 0)) {
|
|
126
|
+
input.target_x = ddnetChar.m_TargetX;
|
|
127
|
+
input.target_y = ddnetChar.m_TargetY;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const angleRad = (char.character_core.angle / 256.0) * Math.PI / 128.0;
|
|
131
|
+
input.target_x = Math.cos(angleRad) * 256;
|
|
132
|
+
input.target_y = Math.sin(angleRad) * 256;
|
|
133
|
+
}
|
|
134
|
+
if (input.target_x === 0 && input.target_y === 0) {
|
|
135
|
+
input.target_y = -1;
|
|
136
|
+
}
|
|
137
|
+
const hookActive = char.character_core.hook_state !== 0 ||
|
|
138
|
+
char.character_core.hooked_player !== -1;
|
|
139
|
+
input.hook = hookActive ? 1 : 0;
|
|
140
|
+
const jumped = char.character_core.jumped;
|
|
141
|
+
const grounded = Math.abs(char.character_core.vel_y) < 1 && jumped === 0;
|
|
142
|
+
input.jump = jumped > 0 && !grounded ? 1 : 0;
|
|
143
|
+
const isNinja = ddnetChar != null && (ddnetChar.m_Flags & 0x20) !== 0;
|
|
144
|
+
input.wanted_weapon = isNinja ? 5 : char.weapon;
|
|
145
|
+
const isAutofireWeapon = [2, 3, 4].includes(input.wanted_weapon);
|
|
146
|
+
const isJetpackGun = input.wanted_weapon === 1 && ddnetChar?.m_Flags != null;
|
|
147
|
+
input.fire = isAutofireWeapon || isJetpackGun ? 0 : 0; // без tick всегда 0
|
|
148
|
+
return input;
|
|
149
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Bot } from './core.js';
|
|
3
|
+
interface BaseModuleOptions {
|
|
4
|
+
moduleName?: string;
|
|
5
|
+
offonDisconnect?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare class BaseModule extends EventEmitter {
|
|
8
|
+
protected readonly bot: Bot;
|
|
9
|
+
readonly moduleName: string;
|
|
10
|
+
isRunning: boolean;
|
|
11
|
+
private readonly _onDisconnect;
|
|
12
|
+
constructor(bot: Bot, options?: BaseModuleOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Запускает модуль, если он ещё не запущен
|
|
15
|
+
* @param args — аргументы, которые будут переданы в _start
|
|
16
|
+
*/
|
|
17
|
+
start(...args: unknown[]): void;
|
|
18
|
+
/**
|
|
19
|
+
* Останавливает модуль, если он запущен
|
|
20
|
+
*/
|
|
21
|
+
stop(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Метод, который нужно переопределить в наследниках
|
|
24
|
+
* Здесь происходит основная логика запуска
|
|
25
|
+
*/
|
|
26
|
+
protected _start(...args: unknown[]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Метод, который нужно переопределить в наследниках
|
|
29
|
+
* Здесь происходит очистка при остановке
|
|
30
|
+
*/
|
|
31
|
+
protected _stop(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Полная очистка модуля:
|
|
34
|
+
* - останавливает работу
|
|
35
|
+
* - снимает обработчик disconnect с bot
|
|
36
|
+
* - удаляет все слушатели событий самого модуля
|
|
37
|
+
*/
|
|
38
|
+
destroy(): void;
|
|
39
|
+
}
|
|
40
|
+
export default BaseModule;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
class BaseModule extends EventEmitter {
|
|
3
|
+
bot;
|
|
4
|
+
moduleName;
|
|
5
|
+
isRunning = false;
|
|
6
|
+
_onDisconnect;
|
|
7
|
+
constructor(bot, options = {}) {
|
|
8
|
+
super();
|
|
9
|
+
const { moduleName = 'Module' } = options;
|
|
10
|
+
if (!bot) {
|
|
11
|
+
throw new Error(`${moduleName} requires bot core`);
|
|
12
|
+
}
|
|
13
|
+
this.bot = bot;
|
|
14
|
+
this.moduleName = moduleName;
|
|
15
|
+
this._onDisconnect = () => this.destroy();
|
|
16
|
+
if (options.offonDisconnect !== false) {
|
|
17
|
+
this.bot.on('disconnect', this._onDisconnect);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Запускает модуль, если он ещё не запущен
|
|
22
|
+
* @param args — аргументы, которые будут переданы в _start
|
|
23
|
+
*/
|
|
24
|
+
start(...args) {
|
|
25
|
+
if (this.isRunning)
|
|
26
|
+
return;
|
|
27
|
+
this.isRunning = true;
|
|
28
|
+
this._start(...args);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Останавливает модуль, если он запущен
|
|
32
|
+
*/
|
|
33
|
+
stop() {
|
|
34
|
+
if (!this.isRunning)
|
|
35
|
+
return;
|
|
36
|
+
this.isRunning = false;
|
|
37
|
+
this._stop();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Метод, который нужно переопределить в наследниках
|
|
41
|
+
* Здесь происходит основная логика запуска
|
|
42
|
+
*/
|
|
43
|
+
_start(...args) {
|
|
44
|
+
// по умолчанию ничего не делаем
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Метод, который нужно переопределить в наследниках
|
|
48
|
+
* Здесь происходит очистка при остановке
|
|
49
|
+
*/
|
|
50
|
+
_stop() {
|
|
51
|
+
// по умолчанию ничего не делаем
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Полная очистка модуля:
|
|
55
|
+
* - останавливает работу
|
|
56
|
+
* - снимает обработчик disconnect с bot
|
|
57
|
+
* - удаляет все слушатели событий самого модуля
|
|
58
|
+
*/
|
|
59
|
+
destroy() {
|
|
60
|
+
this.stop();
|
|
61
|
+
this.bot.off('disconnect', this._onDisconnect);
|
|
62
|
+
this.removeAllListeners();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export default BaseModule;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Bot } from './core/core.js';
|
|
2
|
+
import * as DDUtils from './core/ddutils.js';
|
|
3
|
+
import BaseModule from './core/module.js';
|
|
4
|
+
import BotManager from './manager.js';
|
|
5
|
+
import Chat from './modules/chat.js';
|
|
6
|
+
import PlayerList from './modules/playerlist.js';
|
|
7
|
+
import Reconnect from './modules/reconnect.js';
|
|
8
|
+
import Snap from './modules/snap.js';
|
|
9
|
+
import * as Types from './types.js';
|
|
10
|
+
declare const StandardModules: {
|
|
11
|
+
Chat: typeof Chat;
|
|
12
|
+
PlayerList: typeof PlayerList;
|
|
13
|
+
Reconnect: typeof Reconnect;
|
|
14
|
+
Snap: typeof Snap;
|
|
15
|
+
};
|
|
16
|
+
export { Bot, Types, DDUtils, BaseModule, BotManager, StandardModules, };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Bot } from './core/core.js';
|
|
2
|
+
import * as DDUtils from './core/ddutils.js';
|
|
3
|
+
import BaseModule from './core/module.js';
|
|
4
|
+
import BotManager from './manager.js';
|
|
5
|
+
import Chat from './modules/chat.js';
|
|
6
|
+
import PlayerList from './modules/playerlist.js';
|
|
7
|
+
import Reconnect from './modules/reconnect.js';
|
|
8
|
+
import Snap from './modules/snap.js';
|
|
9
|
+
import * as Types from './types.js';
|
|
10
|
+
const StandardModules = {
|
|
11
|
+
Chat,
|
|
12
|
+
PlayerList,
|
|
13
|
+
Reconnect,
|
|
14
|
+
Snap,
|
|
15
|
+
};
|
|
16
|
+
export { Bot, Types, DDUtils, BaseModule, BotManager, StandardModules, };
|
package/lib/manager.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Bot } from './core/core.js';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
declare class BotManager extends EventEmitter {
|
|
4
|
+
private bots;
|
|
5
|
+
private names;
|
|
6
|
+
constructor();
|
|
7
|
+
private botevents;
|
|
8
|
+
private createUniqueName;
|
|
9
|
+
createBot(...config: ConstructorParameters<typeof Bot>): string;
|
|
10
|
+
removeBotById(id: string): Promise<void>;
|
|
11
|
+
addBot(bot: Bot): string;
|
|
12
|
+
getBotById(id: string): Bot | undefined;
|
|
13
|
+
disconnectAllBots(): Promise<void>;
|
|
14
|
+
removeAllBots(): Promise<void>;
|
|
15
|
+
sendinputToAllBots(input: any): void;
|
|
16
|
+
}
|
|
17
|
+
export default BotManager;
|
package/lib/manager.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Bot } from './core/core.js';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import * as Types from './types.js';
|
|
4
|
+
class BotManager extends EventEmitter {
|
|
5
|
+
bots = new Map();
|
|
6
|
+
names = new Set();
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
botevents(id, bot) {
|
|
11
|
+
bot.on('connect', (data) => {
|
|
12
|
+
this.emit(id + ':connect', data);
|
|
13
|
+
});
|
|
14
|
+
bot.on('disconnect', (reason, data) => {
|
|
15
|
+
this.emit(id + ':disconnect', reason, data);
|
|
16
|
+
});
|
|
17
|
+
bot.on('broadcast', (message) => {
|
|
18
|
+
this.emit(id + ':broadcast', message);
|
|
19
|
+
});
|
|
20
|
+
bot.on('capabilities', (message) => {
|
|
21
|
+
this.emit(id + ':capabilities', message);
|
|
22
|
+
});
|
|
23
|
+
bot.on('emote', (message) => {
|
|
24
|
+
this.emit(id + ':emote', message);
|
|
25
|
+
});
|
|
26
|
+
bot.on('kill', (message) => {
|
|
27
|
+
this.emit(id + ':kill', message);
|
|
28
|
+
});
|
|
29
|
+
bot.on('snapshot', (message) => {
|
|
30
|
+
this.emit(id + ':snapshot', message);
|
|
31
|
+
});
|
|
32
|
+
bot.on('map_change', (message) => {
|
|
33
|
+
this.emit(id + ':map_change', message);
|
|
34
|
+
});
|
|
35
|
+
bot.on('motd', (message) => {
|
|
36
|
+
this.emit(id + ':motd', message);
|
|
37
|
+
});
|
|
38
|
+
bot.on('message', (message) => {
|
|
39
|
+
this.emit(id + ':message', message);
|
|
40
|
+
});
|
|
41
|
+
bot.on('teams', (message) => {
|
|
42
|
+
this.emit(id + ':teams', message);
|
|
43
|
+
});
|
|
44
|
+
bot.on('teamkill', (message) => {
|
|
45
|
+
this.emit(id + ':teamkill', message);
|
|
46
|
+
});
|
|
47
|
+
bot.on('spawn', (message) => {
|
|
48
|
+
this.emit(id + ':spawn', message);
|
|
49
|
+
});
|
|
50
|
+
bot.on('death', (message) => {
|
|
51
|
+
this.emit(id + ':death', message);
|
|
52
|
+
});
|
|
53
|
+
bot.on('hammerhit', (message) => {
|
|
54
|
+
this.emit(id + ':hammerhit', message);
|
|
55
|
+
});
|
|
56
|
+
bot.on('sound_world', (message) => {
|
|
57
|
+
this.emit(id + ':sound_world', message);
|
|
58
|
+
});
|
|
59
|
+
bot.on('explosion', (message) => {
|
|
60
|
+
this.emit(id + ':explosion', message);
|
|
61
|
+
});
|
|
62
|
+
bot.on('common', (message) => {
|
|
63
|
+
this.emit(id + ':common', message);
|
|
64
|
+
});
|
|
65
|
+
bot.on('damage_indicator', (message) => {
|
|
66
|
+
this.emit(id + ':damage_indicator', message);
|
|
67
|
+
});
|
|
68
|
+
bot.on('sound_global', (message) => {
|
|
69
|
+
this.emit(id + ':sound_global', message);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
createUniqueName() {
|
|
73
|
+
const baseName = 'bot_';
|
|
74
|
+
let counter = this.names.size + 1;
|
|
75
|
+
while (this.names.has(baseName + counter)) {
|
|
76
|
+
counter++;
|
|
77
|
+
}
|
|
78
|
+
const uniqueName = baseName + counter;
|
|
79
|
+
this.names.add(uniqueName);
|
|
80
|
+
return uniqueName;
|
|
81
|
+
}
|
|
82
|
+
createBot(...config) {
|
|
83
|
+
const bot = new Bot(...config);
|
|
84
|
+
const id = this.createUniqueName();
|
|
85
|
+
this.botevents(id, bot);
|
|
86
|
+
this.bots.set(id, bot);
|
|
87
|
+
return id;
|
|
88
|
+
}
|
|
89
|
+
async removeBotById(id) {
|
|
90
|
+
const bot = this.bots.get(id);
|
|
91
|
+
if (bot) {
|
|
92
|
+
await bot.destroy();
|
|
93
|
+
this.bots.delete(id);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
addBot(bot) {
|
|
97
|
+
const id = this.createUniqueName();
|
|
98
|
+
this.bots.set(id, bot);
|
|
99
|
+
return id;
|
|
100
|
+
}
|
|
101
|
+
getBotById(id) {
|
|
102
|
+
return this.bots.get(id);
|
|
103
|
+
}
|
|
104
|
+
async disconnectAllBots() {
|
|
105
|
+
for (const bot of this.bots.values()) {
|
|
106
|
+
try {
|
|
107
|
+
await bot?.disconnect();
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
console.error(e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async removeAllBots() {
|
|
115
|
+
for (const bot of this.bots.values()) {
|
|
116
|
+
try {
|
|
117
|
+
await bot?.destroy();
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
console.error(e);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.bots.clear();
|
|
124
|
+
}
|
|
125
|
+
sendinputToAllBots(input) {
|
|
126
|
+
for (const bot of this.bots.values()) {
|
|
127
|
+
try {
|
|
128
|
+
bot?.send_input(input);
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
console.error(e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export default BotManager;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import BaseModule from '../core/module.js';
|
|
2
|
+
import type { Bot } from '../core/core.js';
|
|
3
|
+
import * as Types from '../types.js';
|
|
4
|
+
interface ChatEvents {
|
|
5
|
+
anychat: (msg: Types.SnapshotItemTypes.iMessage, author: string | null, text: string, team: number, client_id: number) => void;
|
|
6
|
+
chat: (msg: Types.SnapshotItemTypes.iMessage, author: string, text: string, team: number, client_id: number) => void;
|
|
7
|
+
systemchat: (msg: Types.SnapshotItemTypes.iMessage, text: string) => void;
|
|
8
|
+
queued: (info: {
|
|
9
|
+
text: string;
|
|
10
|
+
team: boolean;
|
|
11
|
+
queueSize: number;
|
|
12
|
+
}) => void;
|
|
13
|
+
sent: (info: {
|
|
14
|
+
text: string;
|
|
15
|
+
team: boolean;
|
|
16
|
+
queueSize: number;
|
|
17
|
+
}) => void;
|
|
18
|
+
}
|
|
19
|
+
declare class Chat extends BaseModule {
|
|
20
|
+
private chatinterval;
|
|
21
|
+
private sendinterval;
|
|
22
|
+
private readonly chatset;
|
|
23
|
+
private readonly queue;
|
|
24
|
+
private lastSentTime;
|
|
25
|
+
private cooldown;
|
|
26
|
+
private readonly chatlistener;
|
|
27
|
+
constructor(bot: Bot);
|
|
28
|
+
/**
|
|
29
|
+
* Добавить сообщение в очередь на отправку
|
|
30
|
+
*
|
|
31
|
+
* @param text текст сообщения
|
|
32
|
+
* @param team отправить в командный чат? (true = team, false = all)
|
|
33
|
+
* @param priority добавить в начало очереди (приоритетное сообщение)
|
|
34
|
+
* @returns true если сообщение добавлено в очередь
|
|
35
|
+
*/
|
|
36
|
+
send(text: string, team?: boolean, priority?: boolean): boolean;
|
|
37
|
+
private _processQueue;
|
|
38
|
+
protected _start(interval?: number, cooldown?: number): void;
|
|
39
|
+
protected _stop(): void;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
on<K extends keyof ChatEvents>(event: K, listener: ChatEvents[K]): this;
|
|
42
|
+
once<K extends keyof ChatEvents>(event: K, listener: ChatEvents[K]): this;
|
|
43
|
+
emit<K extends keyof ChatEvents>(event: K, ...args: Parameters<ChatEvents[K]>): boolean;
|
|
44
|
+
off<K extends keyof ChatEvents>(event: K, listener: ChatEvents[K]): this;
|
|
45
|
+
}
|
|
46
|
+
export default Chat;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import BaseModule from '../core/module.js';
|
|
2
|
+
import * as Types from '../types.js';
|
|
3
|
+
class Chat extends BaseModule {
|
|
4
|
+
chatinterval = null;
|
|
5
|
+
sendinterval = null;
|
|
6
|
+
chatset = new Set();
|
|
7
|
+
queue = [];
|
|
8
|
+
lastSentTime = 0;
|
|
9
|
+
cooldown = 1000;
|
|
10
|
+
chatlistener = (msg) => {
|
|
11
|
+
try {
|
|
12
|
+
const msgraw = msg;
|
|
13
|
+
const text = String(msgraw?.message ?? '');
|
|
14
|
+
const client_id = msgraw.client_id ?? -1;
|
|
15
|
+
const team = msgraw.team ?? 0;
|
|
16
|
+
const autormsg = msgraw?.author?.ClientInfo?.name ?? null;
|
|
17
|
+
const key = `${client_id}:${text}:${team}`;
|
|
18
|
+
if (this.chatset.has(key))
|
|
19
|
+
return;
|
|
20
|
+
this.chatset.add(key);
|
|
21
|
+
this.emit('anychat', msgraw, autormsg, text, team, client_id);
|
|
22
|
+
if (autormsg) {
|
|
23
|
+
this.emit('chat', msgraw, autormsg, text, team, client_id);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.emit('systemchat', msgraw, text);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
console.error(`[${this.moduleName}] chat listener error:`, e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
constructor(bot) {
|
|
34
|
+
super(bot, { moduleName: 'Chat', offonDisconnect: false });
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Добавить сообщение в очередь на отправку
|
|
38
|
+
*
|
|
39
|
+
* @param text текст сообщения
|
|
40
|
+
* @param team отправить в командный чат? (true = team, false = all)
|
|
41
|
+
* @param priority добавить в начало очереди (приоритетное сообщение)
|
|
42
|
+
* @returns true если сообщение добавлено в очередь
|
|
43
|
+
*/
|
|
44
|
+
send(text, team = false, priority = false) {
|
|
45
|
+
if (!text || text.trim().length === 0) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const item = { text, team };
|
|
49
|
+
if (priority) {
|
|
50
|
+
this.queue.unshift(item);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.queue.push(item);
|
|
54
|
+
}
|
|
55
|
+
this.emit('queued', {
|
|
56
|
+
text,
|
|
57
|
+
team,
|
|
58
|
+
queueSize: this.queue.length,
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
_processQueue() {
|
|
63
|
+
if (this.queue.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
if (now - this.lastSentTime < this.cooldown)
|
|
67
|
+
return;
|
|
68
|
+
const item = this.queue.shift();
|
|
69
|
+
if (!item)
|
|
70
|
+
return;
|
|
71
|
+
const { text, team } = item;
|
|
72
|
+
if (this.bot.bot_client?.game) {
|
|
73
|
+
this.bot.bot_client.game.Say(text, team);
|
|
74
|
+
this.lastSentTime = now;
|
|
75
|
+
this.emit('sent', {
|
|
76
|
+
text,
|
|
77
|
+
team,
|
|
78
|
+
queueSize: this.queue.length,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_start(interval = 1000, cooldown = 1000) {
|
|
83
|
+
this.cooldown = cooldown;
|
|
84
|
+
this.chatinterval = setInterval(() => {
|
|
85
|
+
this.chatset.clear();
|
|
86
|
+
}, interval);
|
|
87
|
+
this.sendinterval = setInterval(() => {
|
|
88
|
+
this._processQueue();
|
|
89
|
+
}, 100);
|
|
90
|
+
this.bot.on('message', this.chatlistener);
|
|
91
|
+
}
|
|
92
|
+
_stop() {
|
|
93
|
+
this.bot.off('message', this.chatlistener);
|
|
94
|
+
this.chatset.clear();
|
|
95
|
+
if (this.chatinterval) {
|
|
96
|
+
clearInterval(this.chatinterval);
|
|
97
|
+
this.chatinterval = null;
|
|
98
|
+
}
|
|
99
|
+
if (this.sendinterval) {
|
|
100
|
+
clearInterval(this.sendinterval);
|
|
101
|
+
this.sendinterval = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
destroy() {
|
|
105
|
+
super.destroy();
|
|
106
|
+
}
|
|
107
|
+
on(event, listener) {
|
|
108
|
+
return super.on(event, listener);
|
|
109
|
+
}
|
|
110
|
+
once(event, listener) {
|
|
111
|
+
return super.once(event, listener);
|
|
112
|
+
}
|
|
113
|
+
emit(event, ...args) {
|
|
114
|
+
return super.emit(event, ...args);
|
|
115
|
+
}
|
|
116
|
+
off(event, listener) {
|
|
117
|
+
return super.off(event, listener);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export default Chat;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import BaseModule from '../core/module.js';
|
|
2
|
+
import type { Bot } from '../core/core.js';
|
|
3
|
+
import type { Types } from '../index.js';
|
|
4
|
+
interface PlayerData {
|
|
5
|
+
client_id: number;
|
|
6
|
+
clientInfo: Types.SnapshotItemTypes.ClientInfo;
|
|
7
|
+
playerInfo: Types.SnapshotItemTypes.PlayerInfo;
|
|
8
|
+
character: Types.SnapshotItemTypes.Character | null;
|
|
9
|
+
DDNetCharacter: Types.SnapshotItemTypes.DDNetCharacter | null;
|
|
10
|
+
}
|
|
11
|
+
declare class PlayerList extends BaseModule {
|
|
12
|
+
constructor(bot: Bot);
|
|
13
|
+
private client;
|
|
14
|
+
private maxclients;
|
|
15
|
+
private playermap;
|
|
16
|
+
private previousMap;
|
|
17
|
+
private readonly snapshotlistener;
|
|
18
|
+
/**
|
|
19
|
+
* Get list of all players
|
|
20
|
+
*/
|
|
21
|
+
get list(): [number, PlayerData][];
|
|
22
|
+
/**
|
|
23
|
+
* Get player by ID
|
|
24
|
+
*/
|
|
25
|
+
getPlayer(client_id: number): PlayerData | null;
|
|
26
|
+
/**
|
|
27
|
+
* Get number of online players
|
|
28
|
+
*/
|
|
29
|
+
getPlayerCount(): number;
|
|
30
|
+
protected _start(maxclients?: number): void;
|
|
31
|
+
protected _stop(): void;
|
|
32
|
+
}
|
|
33
|
+
export default PlayerList;
|