blockmine 1.22.0 → 1.23.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/.claude/settings.json +5 -1
- package/.claude/settings.local.json +10 -1
- package/CHANGELOG.md +27 -3
- package/CLAUDE.md +284 -0
- package/README.md +302 -152
- package/backend/package-lock.json +681 -9
- package/backend/package.json +8 -0
- package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
- package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
- package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +70 -1
- package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
- package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
- package/backend/src/api/middleware/auth.js +27 -0
- package/backend/src/api/middleware/botAccess.js +7 -3
- package/backend/src/api/middleware/panelApiAuth.js +135 -0
- package/backend/src/api/routes/aiAssistant.js +995 -0
- package/backend/src/api/routes/auth.js +669 -633
- package/backend/src/api/routes/botCommands.js +107 -0
- package/backend/src/api/routes/botGroups.js +165 -0
- package/backend/src/api/routes/botHistory.js +108 -0
- package/backend/src/api/routes/botPermissions.js +99 -0
- package/backend/src/api/routes/botStatus.js +36 -0
- package/backend/src/api/routes/botUsers.js +162 -0
- package/backend/src/api/routes/bots.js +2451 -2402
- package/backend/src/api/routes/eventGraphs.js +4 -1
- package/backend/src/api/routes/logs.js +13 -3
- package/backend/src/api/routes/panel.js +66 -66
- package/backend/src/api/routes/panelApiKeys.js +179 -0
- package/backend/src/api/routes/pluginIde.js +1715 -135
- package/backend/src/api/routes/plugins.js +376 -219
- package/backend/src/api/routes/proxies.js +130 -0
- package/backend/src/api/routes/search.js +4 -0
- package/backend/src/api/routes/servers.js +20 -3
- package/backend/src/api/routes/settings.js +5 -0
- package/backend/src/api/routes/system.js +174 -174
- package/backend/src/api/routes/traces.js +131 -0
- package/backend/src/config/debug.config.js +36 -0
- package/backend/src/core/BotHistoryStore.js +180 -0
- package/backend/src/core/BotManager.js +14 -4
- package/backend/src/core/BotProcess.js +1517 -1092
- package/backend/src/core/EventGraphManager.js +37 -123
- package/backend/src/core/GraphExecutionEngine.js +977 -321
- package/backend/src/core/MessageQueue.js +12 -6
- package/backend/src/core/PluginLoader.js +99 -5
- package/backend/src/core/PluginManager.js +74 -13
- package/backend/src/core/TaskScheduler.js +1 -1
- package/backend/src/core/commands/whois.js +1 -1
- package/backend/src/core/node-registries/actions.js +70 -0
- package/backend/src/core/node-registries/arrays.js +18 -0
- package/backend/src/core/node-registries/data.js +1 -1
- package/backend/src/core/node-registries/events.js +14 -0
- package/backend/src/core/node-registries/logic.js +17 -0
- package/backend/src/core/node-registries/strings.js +34 -0
- package/backend/src/core/node-registries/type.js +25 -0
- package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
- package/backend/src/core/nodes/actions/create_command.js +189 -0
- package/backend/src/core/nodes/actions/delete_command.js +92 -0
- package/backend/src/core/nodes/actions/update_command.js +133 -0
- package/backend/src/core/nodes/arrays/join.js +28 -0
- package/backend/src/core/nodes/data/cast.js +2 -1
- package/backend/src/core/nodes/logic/not.js +22 -0
- package/backend/src/core/nodes/strings/starts_with.js +1 -1
- package/backend/src/core/nodes/strings/to_lower.js +22 -0
- package/backend/src/core/nodes/strings/to_upper.js +22 -0
- package/backend/src/core/nodes/type/to_string.js +32 -0
- package/backend/src/core/services/BotLifecycleService.js +255 -16
- package/backend/src/core/services/CommandExecutionService.js +430 -351
- package/backend/src/core/services/DebugSessionManager.js +347 -0
- package/backend/src/core/services/GraphCollaborationManager.js +501 -0
- package/backend/src/core/services/MinecraftBotManager.js +259 -0
- package/backend/src/core/services/MinecraftViewerService.js +216 -0
- package/backend/src/core/services/TraceCollectorService.js +545 -0
- package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
- package/backend/src/core/system/Transport.js +0 -4
- package/backend/src/core/validation/nodeSchemas.js +6 -6
- package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
- package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
- package/backend/src/real-time/botApi/utils.js +11 -0
- package/backend/src/real-time/panelNamespace.js +387 -0
- package/backend/src/real-time/presence.js +7 -2
- package/backend/src/real-time/socketHandler.js +395 -4
- package/backend/src/server.js +18 -0
- package/frontend/dist/assets/index-B1serztM.js +11210 -0
- package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package-lock.json +9437 -0
- package/frontend/package.json +8 -0
- package/package.json +2 -2
- package/screen/console.png +0 -0
- package/screen/dashboard.png +0 -0
- package/screen/graph_collabe.png +0 -0
- package/screen/graph_live_debug.png +0 -0
- package/screen/management_command.png +0 -0
- package/screen/node_debug_trace.png +0 -0
- package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
- package/screen/websocket.png +0 -0
- package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
- package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
- package/frontend/dist/assets/index-CfTo92bP.css +0 -1
- package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
const mineflayer = require('mineflayer');
|
|
2
|
+
const { pathfinder, Movements, goals } = require('mineflayer-pathfinder');
|
|
3
|
+
const Vec3 = require('vec3').Vec3;
|
|
4
|
+
|
|
5
|
+
class MinecraftBotManager {
|
|
6
|
+
constructor({ logger }) {
|
|
7
|
+
this.logger = logger;
|
|
8
|
+
this.bots = new Map();
|
|
9
|
+
this.sessionIdCounter = 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async createBot(sessionId, config) {
|
|
13
|
+
const { host, port, version, username, password } = config;
|
|
14
|
+
|
|
15
|
+
if (this.bots.has(sessionId)) {
|
|
16
|
+
await this.disconnectBot(sessionId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.logger.info(`[Minecraft] Creating bot ${username} for session ${sessionId}`);
|
|
20
|
+
|
|
21
|
+
const bot = mineflayer.createBot({
|
|
22
|
+
host,
|
|
23
|
+
port: port || 25565,
|
|
24
|
+
username,
|
|
25
|
+
password: password || undefined,
|
|
26
|
+
version: version || '1.19.2',
|
|
27
|
+
hideErrors: false
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
bot.loadPlugin(pathfinder);
|
|
31
|
+
|
|
32
|
+
const botState = {
|
|
33
|
+
bot,
|
|
34
|
+
config,
|
|
35
|
+
status: 'connecting',
|
|
36
|
+
health: 20,
|
|
37
|
+
food: 20,
|
|
38
|
+
position: null,
|
|
39
|
+
yaw: 0,
|
|
40
|
+
pitch: 0
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.bots.set(sessionId, botState);
|
|
44
|
+
this._attachEventHandlers(sessionId, bot);
|
|
45
|
+
|
|
46
|
+
return sessionId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_attachEventHandlers(sessionId, bot) {
|
|
50
|
+
bot.on('spawn', () => {
|
|
51
|
+
this.logger.info(`[Minecraft] Bot spawned for session ${sessionId}`);
|
|
52
|
+
const state = this.bots.get(sessionId);
|
|
53
|
+
if (state) {
|
|
54
|
+
state.status = 'online';
|
|
55
|
+
state.position = bot.entity.position;
|
|
56
|
+
state.yaw = bot.entity.yaw;
|
|
57
|
+
state.pitch = bot.entity.pitch;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
bot.on('health', () => {
|
|
62
|
+
const state = this.bots.get(sessionId);
|
|
63
|
+
if (state) {
|
|
64
|
+
state.health = bot.health;
|
|
65
|
+
state.food = bot.food;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
bot.on('move', () => {
|
|
70
|
+
const state = this.bots.get(sessionId);
|
|
71
|
+
if (state && bot.entity) {
|
|
72
|
+
state.position = bot.entity.position;
|
|
73
|
+
state.yaw = bot.entity.yaw;
|
|
74
|
+
state.pitch = bot.entity.pitch;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
bot.on('end', (reason) => {
|
|
79
|
+
this.logger.info(`[Minecraft] Bot disconnected for session ${sessionId}: ${reason}`);
|
|
80
|
+
const state = this.bots.get(sessionId);
|
|
81
|
+
if (state) {
|
|
82
|
+
state.status = 'offline';
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
bot.on('error', (err) => {
|
|
87
|
+
this.logger.error(`[Minecraft] Bot error for session ${sessionId}:`, err);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
bot.on('kicked', (reason) => {
|
|
91
|
+
this.logger.warn(`[Minecraft] Bot kicked for session ${sessionId}: ${reason}`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async disconnectBot(sessionId) {
|
|
96
|
+
const state = this.bots.get(sessionId);
|
|
97
|
+
if (!state) return;
|
|
98
|
+
|
|
99
|
+
this.logger.info(`[Minecraft] Disconnecting bot for session ${sessionId}`);
|
|
100
|
+
|
|
101
|
+
if (state.bot) {
|
|
102
|
+
state.bot.quit();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.bots.delete(sessionId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getBotState(sessionId) {
|
|
109
|
+
const state = this.bots.get(sessionId);
|
|
110
|
+
if (!state || !state.bot) return null;
|
|
111
|
+
|
|
112
|
+
const bot = state.bot;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
status: state.status,
|
|
116
|
+
health: state.health || 20,
|
|
117
|
+
food: state.food || 20,
|
|
118
|
+
position: state.position ? {
|
|
119
|
+
x: state.position.x,
|
|
120
|
+
y: state.position.y,
|
|
121
|
+
z: state.position.z
|
|
122
|
+
} : null,
|
|
123
|
+
yaw: state.yaw || 0,
|
|
124
|
+
pitch: state.pitch || 0,
|
|
125
|
+
gameMode: bot.game?.gameMode,
|
|
126
|
+
dimension: bot.game?.dimension,
|
|
127
|
+
inventory: this._getInventory(bot),
|
|
128
|
+
nearbyPlayers: this._getNearbyPlayers(bot),
|
|
129
|
+
nearbyMobs: this._getNearbyMobs(bot)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_getInventory(bot) {
|
|
134
|
+
if (!bot.inventory) return [];
|
|
135
|
+
|
|
136
|
+
return bot.inventory.items().map(item => ({
|
|
137
|
+
name: item.name,
|
|
138
|
+
displayName: item.displayName,
|
|
139
|
+
count: item.count,
|
|
140
|
+
slot: item.slot
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_getNearbyPlayers(bot) {
|
|
145
|
+
if (!bot.entities) return [];
|
|
146
|
+
|
|
147
|
+
return Object.values(bot.entities)
|
|
148
|
+
.filter(e => e.type === 'player' && e.username !== bot.username)
|
|
149
|
+
.map(e => ({
|
|
150
|
+
username: e.username,
|
|
151
|
+
position: {
|
|
152
|
+
x: e.position.x,
|
|
153
|
+
y: e.position.y,
|
|
154
|
+
z: e.position.z
|
|
155
|
+
},
|
|
156
|
+
distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_getNearbyMobs(bot) {
|
|
161
|
+
if (!bot.entities) return [];
|
|
162
|
+
|
|
163
|
+
return Object.values(bot.entities)
|
|
164
|
+
.filter(e => e.type === 'mob')
|
|
165
|
+
.map(e => ({
|
|
166
|
+
name: e.name || e.displayName,
|
|
167
|
+
mobType: e.mobType,
|
|
168
|
+
position: {
|
|
169
|
+
x: e.position.x,
|
|
170
|
+
y: e.position.y,
|
|
171
|
+
z: e.position.z
|
|
172
|
+
},
|
|
173
|
+
distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async handleControlCommand(sessionId, command) {
|
|
178
|
+
const state = this.bots.get(sessionId);
|
|
179
|
+
if (!state || !state.bot) return;
|
|
180
|
+
|
|
181
|
+
const bot = state.bot;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
switch (command.type) {
|
|
185
|
+
case 'move':
|
|
186
|
+
bot.setControlState(command.direction, command.active);
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
case 'look':
|
|
190
|
+
const yaw = command.yaw !== undefined ? command.yaw : bot.entity.yaw;
|
|
191
|
+
const pitch = command.pitch !== undefined ? command.pitch : bot.entity.pitch;
|
|
192
|
+
await bot.look(yaw, pitch, true);
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'chat':
|
|
196
|
+
bot.chat(command.message);
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'dig':
|
|
200
|
+
await this._digBlock(bot, command.position);
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
case 'place':
|
|
204
|
+
await this._placeBlock(bot, command.position, command.blockType);
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
default:
|
|
208
|
+
this.logger.warn(`[Minecraft] Unknown command type: ${command.type}`);
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.logger.error(`[Minecraft] Error handling command:`, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async _digBlock(bot, position) {
|
|
216
|
+
if (!position) return;
|
|
217
|
+
|
|
218
|
+
const block = bot.blockAt(new Vec3(position.x, position.y, position.z));
|
|
219
|
+
if (!block) return;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
await bot.dig(block);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
this.logger.error('[Minecraft] Error digging block:', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async _placeBlock(bot, position, blockType) {
|
|
229
|
+
if (!position || !blockType) return;
|
|
230
|
+
|
|
231
|
+
const referenceBlock = bot.blockAt(new Vec3(position.x, position.y, position.z));
|
|
232
|
+
if (!referenceBlock) return;
|
|
233
|
+
|
|
234
|
+
const itemToPlace = bot.inventory.items().find(item => item.name === blockType);
|
|
235
|
+
if (!itemToPlace) return;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await bot.equip(itemToPlace, 'hand');
|
|
239
|
+
await bot.placeBlock(referenceBlock, new Vec3(0, 1, 0));
|
|
240
|
+
} catch (error) {
|
|
241
|
+
this.logger.error('[Minecraft] Error placing block:', error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getBot(sessionId) {
|
|
246
|
+
const state = this.bots.get(sessionId);
|
|
247
|
+
return state?.bot || null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
getAllSessions() {
|
|
251
|
+
return Array.from(this.bots.keys());
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
generateSessionId() {
|
|
255
|
+
return `mc_${++this.sessionIdCounter}_${Date.now()}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
module.exports = MinecraftBotManager;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
const { v4: uuidv4 } = require('uuid');
|
|
2
|
+
|
|
3
|
+
class MinecraftViewerService {
|
|
4
|
+
constructor({ io, botProcessManager, logger }) {
|
|
5
|
+
this.processManager = botProcessManager;
|
|
6
|
+
this.logger = logger;
|
|
7
|
+
this.viewerNamespace = io.of('/minecraft-viewer');
|
|
8
|
+
this.activeViewers = new Map(); // botId -> Set<socketId>
|
|
9
|
+
this.pendingRequests = new Map();
|
|
10
|
+
|
|
11
|
+
this._setupNamespace();
|
|
12
|
+
this._setupIPCHandlers();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_setupNamespace() {
|
|
16
|
+
this.viewerNamespace.on('connection', (socket) => {
|
|
17
|
+
this.logger.info(`[MinecraftViewer] Client connected: ${socket.id}`);
|
|
18
|
+
|
|
19
|
+
socket.on('viewer:connect', ({ botId }) => {
|
|
20
|
+
const child = this.processManager.getProcess(botId);
|
|
21
|
+
if (!child) {
|
|
22
|
+
socket.emit('viewer:error', { message: 'Bot not running' });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
socket.botId = botId;
|
|
27
|
+
socket.join(`bot:${botId}`);
|
|
28
|
+
|
|
29
|
+
if (!this.activeViewers.has(botId)) {
|
|
30
|
+
this.activeViewers.set(botId, new Set());
|
|
31
|
+
}
|
|
32
|
+
this.activeViewers.get(botId).add(socket.id);
|
|
33
|
+
|
|
34
|
+
this.logger.info(`[MinecraftViewer] Socket ${socket.id} connected to bot ${botId}`);
|
|
35
|
+
|
|
36
|
+
const requestId = uuidv4();
|
|
37
|
+
this.processManager.sendMessage(botId, {
|
|
38
|
+
type: 'viewer:get_state',
|
|
39
|
+
requestId,
|
|
40
|
+
includeBlocks: true
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.pendingRequests.set(requestId, {
|
|
44
|
+
resolve: (state) => {
|
|
45
|
+
socket.emit('viewer:connected', { botId });
|
|
46
|
+
socket.emit('viewer:state', state);
|
|
47
|
+
},
|
|
48
|
+
reject: (error) => {
|
|
49
|
+
socket.emit('viewer:error', { message: error.message });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this._startStateStream(botId);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
socket.on('viewer:control', ({ command }) => {
|
|
57
|
+
const botId = socket.botId;
|
|
58
|
+
if (!botId) return;
|
|
59
|
+
|
|
60
|
+
this.processManager.sendMessage(botId, {
|
|
61
|
+
type: 'viewer:control',
|
|
62
|
+
command
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
socket.on('viewer:disconnect', () => {
|
|
67
|
+
const botId = socket.botId;
|
|
68
|
+
if (botId) {
|
|
69
|
+
this._removeViewer(botId, socket.id);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
socket.on('disconnect', () => {
|
|
74
|
+
this.logger.info(`[MinecraftViewer] Client disconnected: ${socket.id}`);
|
|
75
|
+
const botId = socket.botId;
|
|
76
|
+
if (botId) {
|
|
77
|
+
this._removeViewer(botId, socket.id);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_setupIPCHandlers() {
|
|
84
|
+
this.processManager.getAllProcesses().forEach((child, botId) => {
|
|
85
|
+
this._attachChildHandlers(child, botId);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const originalSpawn = this.processManager.spawn.bind(this.processManager);
|
|
89
|
+
this.processManager.spawn = async (...args) => {
|
|
90
|
+
const child = await originalSpawn(...args);
|
|
91
|
+
const botId = child.botConfig.id;
|
|
92
|
+
this._attachChildHandlers(child, botId);
|
|
93
|
+
return child;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_attachChildHandlers(child, botId) {
|
|
98
|
+
child.on('message', (message) => {
|
|
99
|
+
if (message.type === 'viewer:state_response') {
|
|
100
|
+
const pending = this.pendingRequests.get(message.requestId);
|
|
101
|
+
if (pending) {
|
|
102
|
+
pending.resolve(message.payload);
|
|
103
|
+
this.pendingRequests.delete(message.requestId);
|
|
104
|
+
}
|
|
105
|
+
} else if (message.type === 'viewer:spawn') {
|
|
106
|
+
this.viewerNamespace.to(`bot:${botId}`).emit('viewer:spawn', message.payload);
|
|
107
|
+
} else if (message.type === 'viewer:move') {
|
|
108
|
+
this.viewerNamespace.to(`bot:${botId}`).emit('viewer:move', message.payload);
|
|
109
|
+
} else if (message.type === 'viewer:health') {
|
|
110
|
+
this.viewerNamespace.to(`bot:${botId}`).emit('viewer:health', message.payload);
|
|
111
|
+
} else if (message.type === 'viewer:chat') {
|
|
112
|
+
this.viewerNamespace.to(`bot:${botId}`).emit('viewer:chat', message.payload);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_startStateStream(botId) {
|
|
118
|
+
if (this.activeViewers.get(botId)?.size === 1) {
|
|
119
|
+
let lastPosition = null;
|
|
120
|
+
let lastSentPosition = null;
|
|
121
|
+
let lastSentPlayers = new Map(); // username -> {x, y, z, yaw, pitch}
|
|
122
|
+
let tickCounter = 0;
|
|
123
|
+
|
|
124
|
+
const interval = setInterval(() => {
|
|
125
|
+
if (this.activeViewers.get(botId)?.size > 0) {
|
|
126
|
+
tickCounter++;
|
|
127
|
+
const shouldSendBlocks = tickCounter % 60 === 0;
|
|
128
|
+
|
|
129
|
+
const requestId = uuidv4();
|
|
130
|
+
this.processManager.sendMessage(botId, {
|
|
131
|
+
type: 'viewer:get_state',
|
|
132
|
+
requestId,
|
|
133
|
+
includeBlocks: shouldSendBlocks
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this.pendingRequests.set(requestId, {
|
|
137
|
+
resolve: (state) => {
|
|
138
|
+
const currentPos = state.position;
|
|
139
|
+
|
|
140
|
+
// Проверяем изменения в nearbyPlayers
|
|
141
|
+
let playersChanged = false;
|
|
142
|
+
if (state.nearbyPlayers) {
|
|
143
|
+
// Проверяем количество
|
|
144
|
+
if (state.nearbyPlayers.length !== lastSentPlayers.size) {
|
|
145
|
+
playersChanged = true;
|
|
146
|
+
} else {
|
|
147
|
+
// Проверяем каждого игрока
|
|
148
|
+
for (const player of state.nearbyPlayers) {
|
|
149
|
+
const last = lastSentPlayers.get(player.username);
|
|
150
|
+
if (!last ||
|
|
151
|
+
Math.abs(last.x - player.position.x) > 0.1 ||
|
|
152
|
+
Math.abs(last.y - player.position.y) > 0.1 ||
|
|
153
|
+
Math.abs(last.z - player.position.z) > 0.1 ||
|
|
154
|
+
Math.abs(last.yaw - player.yaw) > 0.05 ||
|
|
155
|
+
Math.abs(last.pitch - player.pitch) > 0.05) {
|
|
156
|
+
playersChanged = true;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Отправляем если позиция бота изменилась, игроки изменились, или пора отправлять блоки
|
|
164
|
+
const shouldSend = shouldSendBlocks || playersChanged || !lastSentPosition ||
|
|
165
|
+
Math.abs(currentPos?.x - lastSentPosition.x) > 0.1 ||
|
|
166
|
+
Math.abs(currentPos?.y - lastSentPosition.y) > 0.1 ||
|
|
167
|
+
Math.abs(currentPos?.z - lastSentPosition.z) > 0.1;
|
|
168
|
+
|
|
169
|
+
if (shouldSend) {
|
|
170
|
+
this.viewerNamespace.to(`bot:${botId}`).emit('viewer:update', state);
|
|
171
|
+
lastSentPosition = currentPos ? { ...currentPos } : null;
|
|
172
|
+
|
|
173
|
+
// Обновляем кэш игроков
|
|
174
|
+
lastSentPlayers.clear();
|
|
175
|
+
if (state.nearbyPlayers) {
|
|
176
|
+
state.nearbyPlayers.forEach(player => {
|
|
177
|
+
lastSentPlayers.set(player.username, {
|
|
178
|
+
x: player.position.x,
|
|
179
|
+
y: player.position.y,
|
|
180
|
+
z: player.position.z,
|
|
181
|
+
yaw: player.yaw,
|
|
182
|
+
pitch: player.pitch
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Обновляем lastPosition для блоков
|
|
189
|
+
if (shouldSendBlocks || !lastPosition ||
|
|
190
|
+
Math.abs(currentPos?.x - lastPosition.x) > 8 ||
|
|
191
|
+
Math.abs(currentPos?.y - lastPosition.y) > 8 ||
|
|
192
|
+
Math.abs(currentPos?.z - lastPosition.z) > 8) {
|
|
193
|
+
lastPosition = currentPos ? { ...currentPos } : null;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
reject: () => {}
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
clearInterval(interval);
|
|
200
|
+
}
|
|
201
|
+
}, 100);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_removeViewer(botId, socketId) {
|
|
206
|
+
const viewers = this.activeViewers.get(botId);
|
|
207
|
+
if (viewers) {
|
|
208
|
+
viewers.delete(socketId);
|
|
209
|
+
if (viewers.size === 0) {
|
|
210
|
+
this.activeViewers.delete(botId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = MinecraftViewerService;
|