ddbot.js-0374 4.4.3 → 4.5.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/ideas.md +104 -0
- package/lib/core/core.d.ts +1 -0
- package/lib/core/core.js +13 -13
- package/lib/ddutils.js +22 -22
- package/lib/modules/playerlist.d.ts +2 -0
- package/lib/modules/playerlist.js +23 -13
- package/lib/modules/reconnect.js +6 -4
- package/lib/modules/snap.d.ts +6 -0
- package/lib/modules/snap.js +47 -9
- package/lib/types.d.ts +12 -12
- package/package.json +2 -2
package/docs/ideas.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# ddbot.js v5 — идеи и архитектура
|
|
2
|
+
|
|
3
|
+
## Философия
|
|
4
|
+
|
|
5
|
+
v4 — стабильный клиент поверх teeworlds. Ядро решает проблему нестабильности.
|
|
6
|
+
v5 — фреймворк для ботов. Модули становятся полноценными, общаются между собой и не конфликтуют.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Новая архитектура модулей
|
|
11
|
+
|
|
12
|
+
### InputModule
|
|
13
|
+
|
|
14
|
+
Базовый класс поверх `BaseModule` для модулей которые отправляют инпут.
|
|
15
|
+
|
|
16
|
+
Модуль декларирует какие каналы инпута он использует:
|
|
17
|
+
```ts
|
|
18
|
+
channels: ['aim', 'move', 'jump', 'hook', 'fire']
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Добавляет метод `pauseInput()` — останавливает только отправку инпута, не останавливая весь модуль. Модуль продолжает слушать события.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### InputMixer
|
|
26
|
+
|
|
27
|
+
Менеджер приоритетов инпута. Знает все активные `InputModule` и их каналы.
|
|
28
|
+
|
|
29
|
+
- Если два модуля хотят один канал — побеждает тот у кого выше приоритет
|
|
30
|
+
- Если модули не пересекаются по каналам — работают параллельно
|
|
31
|
+
- При смене задачи сам паузит и возобновляет нужные модули
|
|
32
|
+
|
|
33
|
+
Пример:
|
|
34
|
+
```
|
|
35
|
+
LookAt → [aim]
|
|
36
|
+
Follow → [move, hook]
|
|
37
|
+
Pathfinding → [aim, move, jump, hook]
|
|
38
|
+
```
|
|
39
|
+
`LookAt` + `Follow` работают одновременно. `Pathfinding` вытесняет обоих по приоритету.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### ModuleContainer
|
|
44
|
+
|
|
45
|
+
Отдельный класс — реестр модулей бота. Живёт в боте но не является ядром.
|
|
46
|
+
|
|
47
|
+
- Хранит все зарегистрированные модули
|
|
48
|
+
- Резолвит зависимости между модулями
|
|
49
|
+
- Отвечает за порядок инициализации
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
bot.modules.register(playerList)
|
|
53
|
+
bot.modules.get(PlayerList) // доступно любому модулю
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Ядро (Bot) остаётся чистым. Вся сложность графа зависимостей живёт здесь.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### EventBus
|
|
61
|
+
|
|
62
|
+
Шина состояния для общения между модулями. Модули не знают друг о друге напрямую — публикуют и читают через шину.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
this.publish('target', { client_id: 3, x: 100, y: 200 })
|
|
66
|
+
this.subscribe('target', (data) => { ... })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Решает проблему повторения одной и той же логики в разных модулях. Например `Greeter` подписывается на события `PlayerList` не зная о нём напрямую.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### Таймеры в BaseModule
|
|
74
|
+
|
|
75
|
+
`BaseModule` предоставляет обёртки над таймерами:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
this.setTimeout(() => { ... }, 3000)
|
|
79
|
+
this.setInterval(() => { ... }, 1000)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
При `stop()` или `destroy()` все таймеры чистятся автоматически. Решает утечки состояния при реконнекте — старые таймеры не срабатывают в новой сессии.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Итого — слои v5
|
|
87
|
+
|
|
88
|
+
| Слой | Класс | Отвечает за |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| Стабильность | `Bot` | жизненный цикл, реконнект, идентичность |
|
|
91
|
+
| Зависимости | `ModuleContainer` | граф модулей, резолв зависимостей |
|
|
92
|
+
| Инпут | `InputMixer` | каналы, приоритеты, параллельность |
|
|
93
|
+
| Общение | `EventBus` | состояние между модулями |
|
|
94
|
+
| Время | `BaseModule` | чистые таймеры без утечек |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## В Ideas (не срочно)
|
|
99
|
+
|
|
100
|
+
**TickSystem** — таймеры привязанные к тикам снапшота (~25/сек) для высокоточных вещей. Пока не нужно.
|
|
101
|
+
|
|
102
|
+
**Recorder/Replayer** — запись и воспроизведение инпута. Полезно для отладки.
|
|
103
|
+
|
|
104
|
+
**Условный запуск модулей** — модуль активируется по условию а не вручную.
|
package/lib/core/core.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export declare class Bot extends EventEmitter {
|
|
|
98
98
|
* @param input - Partial input object
|
|
99
99
|
*/
|
|
100
100
|
send_input(input: Partial<Types.SnapshotItemTypes.PlayerInput>): void;
|
|
101
|
+
private snapshotUnpackerRef;
|
|
101
102
|
/**
|
|
102
103
|
* Setup all client event listeners and forward them
|
|
103
104
|
*/
|
package/lib/core/core.js
CHANGED
|
@@ -210,6 +210,7 @@ class Bot extends events_1.EventEmitter {
|
|
|
210
210
|
return;
|
|
211
211
|
this.client.movement.input = { ...this.client.movement.input, ...input };
|
|
212
212
|
}
|
|
213
|
+
snapshotUnpackerRef = null;
|
|
213
214
|
/**
|
|
214
215
|
* Setup all client event listeners and forward them
|
|
215
216
|
*/
|
|
@@ -217,36 +218,34 @@ class Bot extends events_1.EventEmitter {
|
|
|
217
218
|
this.clean(false);
|
|
218
219
|
if (!this.client)
|
|
219
220
|
return;
|
|
220
|
-
// Connection events
|
|
221
221
|
this.client.on('connected', () => {
|
|
222
|
+
this.emit('rawconnect', { addr: this.status.addr, port: this.status.port });
|
|
223
|
+
this.setup_snapshot_events();
|
|
222
224
|
if (this.status.connect.connected) {
|
|
223
225
|
this.error('connection received when already connected');
|
|
224
226
|
}
|
|
225
227
|
else {
|
|
226
228
|
this.status.connect.connected = true;
|
|
227
229
|
this.emit('connect', { addr: this.status.addr, port: this.status.port });
|
|
228
|
-
this.setup_snapshot_events();
|
|
229
230
|
}
|
|
230
231
|
});
|
|
231
232
|
this.client.on('disconnect', (reason) => {
|
|
233
|
+
this.snapshotUnpackerRef = null;
|
|
234
|
+
this.emit('rawdisconnect', reason, { addr: this.status.addr, port: this.status.port });
|
|
232
235
|
this.status.connect.connected = false;
|
|
233
236
|
this.clean(true);
|
|
234
237
|
if (!!reason) {
|
|
235
238
|
this.emit('disconnect', reason, { addr: this.status.addr, port: this.status.port });
|
|
236
239
|
}
|
|
237
240
|
});
|
|
238
|
-
this.client.on('connected', () => {
|
|
239
|
-
this.emit('rawconnect', { addr: this.status.addr, port: this.status.port });
|
|
240
|
-
});
|
|
241
|
-
this.client.on('disconnect', (reason) => {
|
|
242
|
-
this.emit('rawdisconnect', reason, { addr: this.status.addr, port: this.status.port });
|
|
243
|
-
});
|
|
244
|
-
// Game events
|
|
245
241
|
this.client.on('broadcast', (msg) => this.emit('broadcast', msg));
|
|
246
242
|
this.client.on('capabilities', (msg) => this.emit('capabilities', msg));
|
|
247
243
|
this.client.on('emote', (msg) => this.emit('emote', msg));
|
|
248
244
|
this.client.on('kill', (msg) => this.emit('kill', msg));
|
|
249
|
-
this.client.on('snapshot', (msg) =>
|
|
245
|
+
this.client.on('snapshot', (msg) => {
|
|
246
|
+
this.setup_snapshot_events();
|
|
247
|
+
this.emit('snapshot', msg);
|
|
248
|
+
});
|
|
250
249
|
this.client.on('map_change', (msg) => this.emit('map_change', msg));
|
|
251
250
|
this.client.on('map_details', (msg) => this.emit('map_details', msg));
|
|
252
251
|
this.client.on('motd', (msg) => this.emit('motd', msg));
|
|
@@ -258,10 +257,11 @@ class Bot extends events_1.EventEmitter {
|
|
|
258
257
|
* Setup snapshot unpacker events
|
|
259
258
|
*/
|
|
260
259
|
setup_snapshot_events() {
|
|
261
|
-
if (!this.client?.SnapshotUnpacker)
|
|
262
|
-
this.error('SnapshotUnpacker not available yet');
|
|
260
|
+
if (!this.client?.SnapshotUnpacker)
|
|
263
261
|
return;
|
|
264
|
-
|
|
262
|
+
if (this.snapshotUnpackerRef === this.client.SnapshotUnpacker)
|
|
263
|
+
return;
|
|
264
|
+
this.snapshotUnpackerRef = this.client.SnapshotUnpacker;
|
|
265
265
|
this.client.SnapshotUnpacker.removeAllListeners();
|
|
266
266
|
this.client.SnapshotUnpacker.on('spawn', (msg) => this.emit('spawn', msg));
|
|
267
267
|
this.client.SnapshotUnpacker.on('death', (msg) => this.emit('death', msg));
|
package/lib/ddutils.js
CHANGED
|
@@ -18,39 +18,39 @@ function DefaultIdentity(name = 'nameless tee') {
|
|
|
18
18
|
*/
|
|
19
19
|
function reconstructPlayerInput(char, ddnetChar = null, tick = null) {
|
|
20
20
|
const input = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
m_Direction: char.character_core.direction,
|
|
22
|
+
m_TargetX: 0,
|
|
23
|
+
m_TargetY: -1,
|
|
24
|
+
m_Jump: 0,
|
|
25
|
+
m_Fire: 0,
|
|
26
|
+
m_Hook: 0,
|
|
27
|
+
m_PlayerFlags: char.player_flags || 0,
|
|
28
|
+
m_WantedWeapon: char.weapon,
|
|
29
|
+
m_NextWeapon: 0,
|
|
30
|
+
m_PrevWeapon: 0
|
|
31
31
|
};
|
|
32
32
|
if (ddnetChar && (ddnetChar.m_TargetX !== 0 || ddnetChar.m_TargetY !== 0)) {
|
|
33
|
-
input.
|
|
34
|
-
input.
|
|
33
|
+
input.m_TargetX = ddnetChar.m_TargetX;
|
|
34
|
+
input.m_TargetY = ddnetChar.m_TargetY;
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
const angleRad = (char.character_core.angle / 256.0) * Math.PI / 128.0;
|
|
38
|
-
input.
|
|
39
|
-
input.
|
|
38
|
+
input.m_TargetX = Math.cos(angleRad) * 256;
|
|
39
|
+
input.m_TargetY = Math.sin(angleRad) * 256;
|
|
40
40
|
}
|
|
41
|
-
if (input.
|
|
42
|
-
input.
|
|
41
|
+
if (input.m_TargetX === 0 && input.m_TargetY === 0) {
|
|
42
|
+
input.m_TargetY = -1;
|
|
43
43
|
}
|
|
44
44
|
const hookActive = char.character_core.hook_state !== 0 ||
|
|
45
45
|
char.character_core.hooked_player !== -1;
|
|
46
|
-
input.
|
|
46
|
+
input.m_Hook = hookActive ? 1 : 0;
|
|
47
47
|
const jumped = char.character_core.jumped;
|
|
48
48
|
const grounded = Math.abs(char.character_core.vel_y) < 1 && jumped === 0;
|
|
49
|
-
input.
|
|
49
|
+
input.m_Jump = jumped > 0 && !grounded ? 1 : 0;
|
|
50
50
|
const isNinja = ddnetChar != null && (ddnetChar.m_Flags & 0x20) !== 0;
|
|
51
|
-
input.
|
|
52
|
-
const isAutofireWeapon = [2, 3, 4].includes(input.
|
|
53
|
-
const isJetpackGun = input.
|
|
54
|
-
input.
|
|
51
|
+
input.m_WantedWeapon = isNinja ? 5 : char.weapon;
|
|
52
|
+
const isAutofireWeapon = [2, 3, 4].includes(input.m_WantedWeapon);
|
|
53
|
+
const isJetpackGun = input.m_WantedWeapon === 1 && ddnetChar?.m_Flags != null;
|
|
54
|
+
input.m_Fire = isAutofireWeapon || isJetpackGun ? 0 : 0;
|
|
55
55
|
return input;
|
|
56
56
|
}
|
|
@@ -30,7 +30,9 @@ declare class PlayerList extends BaseModule<[maxclients?: number]> {
|
|
|
30
30
|
private maxclients;
|
|
31
31
|
private playermap;
|
|
32
32
|
private previousMap;
|
|
33
|
+
private isFirstSnapshot;
|
|
33
34
|
private readonly snapshotlistener;
|
|
35
|
+
private readonly resetState;
|
|
34
36
|
/**
|
|
35
37
|
* Get list of all players
|
|
36
38
|
*/
|
|
@@ -13,6 +13,7 @@ class PlayerList extends module_js_1.default {
|
|
|
13
13
|
maxclients = 64;
|
|
14
14
|
playermap = new Map();
|
|
15
15
|
previousMap = new Map();
|
|
16
|
+
isFirstSnapshot = true;
|
|
16
17
|
snapshotlistener = () => {
|
|
17
18
|
this.previousMap = new Map(this.playermap);
|
|
18
19
|
this.playermap.clear();
|
|
@@ -32,25 +33,29 @@ class PlayerList extends module_js_1.default {
|
|
|
32
33
|
DDNetCharacter: DDNetCharacter || null,
|
|
33
34
|
};
|
|
34
35
|
this.playermap.set(client_id, playerData);
|
|
35
|
-
if (!this.previousMap.has(client_id)) {
|
|
36
|
-
this.emit('player_joined', {
|
|
37
|
-
client_id,
|
|
38
|
-
name: clientInfo.name,
|
|
39
|
-
playerData,
|
|
40
|
-
});
|
|
36
|
+
if (!this.isFirstSnapshot && !this.previousMap.has(client_id)) {
|
|
37
|
+
this.emit('player_joined', { client_id, name: clientInfo.name, playerData });
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
if (!this.isFirstSnapshot) {
|
|
42
|
+
for (const [client_id, oldData] of this.previousMap) {
|
|
43
|
+
if (!this.playermap.has(client_id)) {
|
|
44
|
+
this.emit('player_left', {
|
|
45
|
+
client_id,
|
|
46
|
+
name: oldData.clientInfo.name,
|
|
47
|
+
playerData: oldData,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
51
50
|
}
|
|
52
51
|
}
|
|
52
|
+
this.isFirstSnapshot = false;
|
|
53
|
+
this.previousMap.clear();
|
|
54
|
+
};
|
|
55
|
+
resetState = () => {
|
|
56
|
+
this.playermap.clear();
|
|
53
57
|
this.previousMap.clear();
|
|
58
|
+
this.isFirstSnapshot = true;
|
|
54
59
|
};
|
|
55
60
|
/**
|
|
56
61
|
* Get list of all players
|
|
@@ -72,10 +77,15 @@ class PlayerList extends module_js_1.default {
|
|
|
72
77
|
}
|
|
73
78
|
_start(maxclients = 64) {
|
|
74
79
|
this.maxclients = maxclients;
|
|
80
|
+
this.isFirstSnapshot = true;
|
|
75
81
|
this.bot.on('snapshot', this.snapshotlistener);
|
|
82
|
+
this.bot.on('connect', this.resetState);
|
|
83
|
+
this.bot.on('disconnect', this.resetState);
|
|
76
84
|
}
|
|
77
85
|
_stop() {
|
|
78
86
|
this.bot.off('snapshot', this.snapshotlistener);
|
|
87
|
+
this.bot.off('connect', this.resetState);
|
|
88
|
+
this.bot.off('disconnect', this.resetState);
|
|
79
89
|
this.playermap.clear();
|
|
80
90
|
this.previousMap.clear();
|
|
81
91
|
}
|
package/lib/modules/reconnect.js
CHANGED
|
@@ -23,7 +23,9 @@ class Reconnect extends module_js_1.default {
|
|
|
23
23
|
return;
|
|
24
24
|
if (this.reconnecting)
|
|
25
25
|
return;
|
|
26
|
-
|
|
26
|
+
const addr = connectionInfo.addr;
|
|
27
|
+
const port = connectionInfo.port;
|
|
28
|
+
if (!addr || !port) {
|
|
27
29
|
this.emit('reconnect_failed', 'No connection info');
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
@@ -42,11 +44,11 @@ class Reconnect extends module_js_1.default {
|
|
|
42
44
|
});
|
|
43
45
|
this.reconnectTimer = setTimeout(async () => {
|
|
44
46
|
try {
|
|
45
|
-
await this.bot.connect(
|
|
47
|
+
await this.bot.connect(addr, port, 30000);
|
|
46
48
|
this.currentAttempts = 0;
|
|
47
49
|
this.emit('reconnected', {
|
|
48
|
-
addr:
|
|
49
|
-
port:
|
|
50
|
+
addr: addr,
|
|
51
|
+
port: port
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
catch (err) {
|
package/lib/modules/snap.d.ts
CHANGED
|
@@ -22,9 +22,14 @@ interface SnapEvents {
|
|
|
22
22
|
frozen: () => void;
|
|
23
23
|
/** Персонаж разморожен */
|
|
24
24
|
unfrozen: () => void;
|
|
25
|
+
/** Другой игрок заморожен */
|
|
26
|
+
player_frozen: (client_id: number) => void;
|
|
27
|
+
/** Другой игрок разморожен */
|
|
28
|
+
player_unfrozen: (client_id: number) => void;
|
|
25
29
|
}
|
|
26
30
|
declare class Snap extends BaseModule {
|
|
27
31
|
private _isFrozen;
|
|
32
|
+
private _playerFreezeState;
|
|
28
33
|
private readonly hammerHitlistener;
|
|
29
34
|
private readonly firelistener;
|
|
30
35
|
private readonly snapslistener;
|
|
@@ -36,6 +41,7 @@ declare class Snap extends BaseModule {
|
|
|
36
41
|
y: number;
|
|
37
42
|
} | null;
|
|
38
43
|
get isFrozen(): boolean;
|
|
44
|
+
isPlayerFrozen(client_id: number): boolean;
|
|
39
45
|
lookatplayer(client_id: number): void;
|
|
40
46
|
protected _start(): void;
|
|
41
47
|
protected _stop(): void;
|
package/lib/modules/snap.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const module_js_1 = __importDefault(require("../core/module.js"));
|
|
7
7
|
class Snap extends module_js_1.default {
|
|
8
8
|
_isFrozen = false;
|
|
9
|
+
_playerFreezeState = new Map();
|
|
9
10
|
hammerHitlistener = (hit) => {
|
|
10
11
|
if (this.bot.OwnID === undefined)
|
|
11
12
|
return;
|
|
@@ -35,16 +36,44 @@ class Snap extends module_js_1.default {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
};
|
|
38
|
-
|
|
39
|
+
const fps = () => {
|
|
40
|
+
if (!this.bot.bot_client?.SnapshotUnpacker)
|
|
41
|
+
return;
|
|
42
|
+
const allChars = this.bot.bot_client.SnapshotUnpacker.AllObjCharacter || [];
|
|
43
|
+
const currentIds = new Set();
|
|
44
|
+
for (const char of allChars) {
|
|
45
|
+
if (char.client_id === this.bot.OwnID)
|
|
46
|
+
continue;
|
|
47
|
+
currentIds.add(char.client_id);
|
|
48
|
+
const ddnetChar = this.bot.bot_client.SnapshotUnpacker.getObjExDDNetCharacter(char.client_id);
|
|
49
|
+
if (!ddnetChar)
|
|
50
|
+
continue;
|
|
51
|
+
const isFrozen = ddnetChar.m_FreezeEnd !== 0;
|
|
52
|
+
const wasFrozen = this._playerFreezeState.get(char.client_id);
|
|
53
|
+
if (wasFrozen === undefined) {
|
|
54
|
+
this._playerFreezeState.set(char.client_id, isFrozen);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (wasFrozen !== isFrozen) {
|
|
58
|
+
this._playerFreezeState.set(char.client_id, isFrozen);
|
|
59
|
+
this.emit(isFrozen ? 'player_frozen' : 'player_unfrozen', char.client_id);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const id of this._playerFreezeState.keys()) {
|
|
63
|
+
if (!currentIds.has(id)) {
|
|
64
|
+
this._playerFreezeState.delete(id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
ffs();
|
|
69
|
+
fps();
|
|
39
70
|
};
|
|
40
71
|
constructor(bot) {
|
|
41
72
|
super(bot, { moduleName: 'Snap', offonDisconnect: false });
|
|
42
73
|
}
|
|
43
74
|
static areWithinTile(x1, y1, x2, y2) {
|
|
44
75
|
const TILE = 32 * 1.1;
|
|
45
|
-
|
|
46
|
-
const distanceY = Math.abs(y1 - y2);
|
|
47
|
-
return distanceX <= TILE && distanceY <= TILE;
|
|
76
|
+
return Math.abs(x1 - x2) <= TILE && Math.abs(y1 - y2) <= TILE;
|
|
48
77
|
}
|
|
49
78
|
static whoareWithinTile(x, y, list = [], ignoreClients = []) {
|
|
50
79
|
for (const character of list) {
|
|
@@ -72,13 +101,21 @@ class Snap extends module_js_1.default {
|
|
|
72
101
|
get isFrozen() {
|
|
73
102
|
return this._isFrozen;
|
|
74
103
|
}
|
|
104
|
+
isPlayerFrozen(client_id) {
|
|
105
|
+
return this._playerFreezeState.get(client_id) ?? false;
|
|
106
|
+
}
|
|
75
107
|
lookatplayer(client_id) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
try {
|
|
109
|
+
const pl_character = this.bot.bot_client?.SnapshotUnpacker.getObjCharacter(client_id);
|
|
110
|
+
const own_character = this.bot.bot_client?.SnapshotUnpacker.getObjCharacter(this.bot.OwnID);
|
|
111
|
+
if (!pl_character || !own_character)
|
|
112
|
+
return;
|
|
113
|
+
const angle = Math.atan2(pl_character.character_core.y - own_character.character_core.y, pl_character.character_core.x - own_character.character_core.x);
|
|
114
|
+
this.bot.send_input({ m_TargetX: Math.cos(angle) * 256, m_TargetY: Math.sin(angle) * 256 });
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
79
117
|
return;
|
|
80
|
-
|
|
81
|
-
this.bot.send_input({ target_x: Math.cos(angle) * 256, target_y: Math.sin(angle) * 256 });
|
|
118
|
+
}
|
|
82
119
|
}
|
|
83
120
|
_start() {
|
|
84
121
|
this.bot.on('snapshot', this.snapslistener);
|
|
@@ -89,6 +126,7 @@ class Snap extends module_js_1.default {
|
|
|
89
126
|
this.bot.off('snapshot', this.snapslistener);
|
|
90
127
|
this.bot.off('hammerhit', this.hammerHitlistener);
|
|
91
128
|
this.bot.off('sound_world', this.firelistener);
|
|
129
|
+
this._playerFreezeState.clear();
|
|
92
130
|
}
|
|
93
131
|
on(event, listener) {
|
|
94
132
|
return super.on(event, listener);
|
package/lib/types.d.ts
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare namespace SnapshotItemTypes {
|
|
5
5
|
interface PlayerInput {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
m_Direction: number;
|
|
7
|
+
m_TargetX: number;
|
|
8
|
+
m_TargetY: number;
|
|
9
|
+
m_Jump: number;
|
|
10
|
+
m_Fire: number;
|
|
11
|
+
m_Hook: number;
|
|
12
|
+
m_PlayerFlags: number;
|
|
13
|
+
m_WantedWeapon: number;
|
|
14
|
+
m_NextWeapon: number;
|
|
15
|
+
m_PrevWeapon: number;
|
|
16
16
|
}
|
|
17
17
|
interface iOptions {
|
|
18
18
|
identity?: Identity;
|
|
@@ -250,6 +250,6 @@ export type DeltaItem = {
|
|
|
250
250
|
key: number;
|
|
251
251
|
};
|
|
252
252
|
export interface ConnectionInfo {
|
|
253
|
-
addr: string;
|
|
254
|
-
port: number;
|
|
253
|
+
addr: string | null;
|
|
254
|
+
port: number | null;
|
|
255
255
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"teeworlds": "^2.5.11"
|
|
4
4
|
},
|
|
5
5
|
"name": "ddbot.js-0374",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.5.0",
|
|
7
7
|
"description": "ddbot.js — это Node.js проект для автоматизации и управления ботами.",
|
|
8
8
|
"main": "./lib/index.js",
|
|
9
9
|
"scripts": {
|
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/0374flop/ddbot.js#readme",
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^25.2
|
|
33
|
+
"@types/node": "^25.5.2"
|
|
34
34
|
}
|
|
35
35
|
}
|