OrbitMC commited on
Commit
702806d
·
verified ·
1 Parent(s): 8afe353

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +781 -232
app.py CHANGED
@@ -6,6 +6,7 @@ import threading
6
  import subprocess
7
  import signal
8
  import socket
 
9
  from datetime import datetime, timedelta
10
  from flask import Flask, render_template_string, jsonify, request
11
  import logging
@@ -27,239 +28,721 @@ bots = {}
27
  bot_processes = {}
28
  current_bot_index = 0
29
  rotation_start_time = None
30
- server_status = {"online": False, "players": "?", "last_check": None}
31
 
32
- # HTML Interface
33
  HTML = """<!DOCTYPE html>
34
  <html>
35
  <head>
36
- <title>Bot Manager</title>
37
  <meta charset="utf-8">
38
  <meta name="viewport" content="width=device-width,initial-scale=1">
39
  <style>
40
- *{margin:0;padding:0;box-sizing:border-box}
41
- body{font-family:Arial,sans-serif;background:#1a1a1a;color:#fff;padding:10px}
42
- .header{background:#2a2a2a;padding:15px;border-radius:8px;margin-bottom:15px}
43
- h1{font-size:24px;margin-bottom:10px}
44
- .server-status{background:#333;padding:15px;border-radius:8px;margin-bottom:15px;border-left:4px solid #666}
45
- .server-status.online{border-color:#4CAF50}
46
- .server-status.offline{border-color:#f44336}
47
- .server-info{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:10px}
48
- .server-item{background:#2a2a2a;padding:10px;border-radius:5px}
49
- .server-item label{display:block;color:#999;font-size:12px;margin-bottom:3px}
50
- .server-item span{font-size:16px;font-weight:bold;color:#4CAF50}
51
- .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-bottom:15px}
52
- .stat{background:#333;padding:12px;border-radius:5px;text-align:center}
53
- .stat label{display:block;color:#999;font-size:12px;margin-bottom:5px}
54
- .stat span{display:block;font-size:24px;font-weight:bold;color:#4CAF50;margin-top:5px}
55
- .rotation-info{background:#1976D2;padding:15px;border-radius:8px;margin-bottom:15px;text-align:center}
56
- .rotation-info h2{font-size:18px;margin-bottom:8px}
57
- .rotation-info .current-bot{font-size:32px;font-weight:bold;margin:10px 0}
58
- .rotation-info .timer{font-size:24px;color:#FFF59D}
59
- .controls{margin-bottom:15px;text-align:center}
60
- button{background:#4CAF50;border:none;color:white;padding:10px 20px;margin:5px;border-radius:5px;cursor:pointer;font-size:14px}
61
- button:hover{background:#45a049}
62
- button.secondary{background:#2196F3}
63
- button.secondary:hover{background:#1976D2}
64
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}
65
- .bot{background:#2a2a2a;padding:15px;border-radius:8px;border-left:4px solid #666}
66
- .bot.online{border-color:#4CAF50}
67
- .bot.offline{border-color:#666}
68
- .bot.connecting{border-color:#ff9800}
69
- .bot.active{background:#1565C0;border-color:#2196F3}
70
- .bot-name{font-weight:bold;font-size:16px;margin-bottom:8px}
71
- .bot-info{color:#999;font-size:13px;margin:4px 0}
72
- .bot-status{display:inline-block;padding:4px 10px;border-radius:4px;font-size:12px;margin-top:8px;font-weight:bold}
73
- .online .bot-status{background:#4CAF50;color:white}
74
- .offline .bot-status{background:#666;color:white}
75
- .connecting .bot-status{background:#ff9800;color:white}
76
- .active .bot-status{background:#2196F3;color:white}
77
- .msg{background:#2196F3;color:white;padding:10px;border-radius:5px;margin:10px 0}
78
- .error{background:#f44336}
79
- .badge{display:inline-block;background:#FFC107;color:#000;padding:3px 8px;border-radius:3px;font-size:11px;font-weight:bold;margin-left:8px}
80
- </style>
81
- </head>
82
- <body>
83
- <div class="header">
84
- <h1>🎮 Minecraft Bot Manager</h1>
85
-
86
- <div class="server-status" id="server-status">
87
- <h2>📡 Server Status</h2>
88
- <div class="server-info">
89
- <div class="server-item">
90
- <label>Address</label>
91
- <span id="server-address">-</span>
92
- </div>
93
- <div class="server-item">
94
- <label>Status</label>
95
- <span id="server-online">-</span>
96
- </div>
97
- <div class="server-item">
98
- <label>Players</label>
99
- <span id="server-players">-</span>
100
- </div>
101
- <div class="server-item">
102
- <label>Version</label>
103
- <span id="server-version">-</span>
104
- </div>
105
- </div>
106
- </div>
107
 
108
- <div class="rotation-info">
109
- <h2>🔄 Current Rotation</h2>
110
- <div class="current-bot" id="current-bot">-</div>
111
- <div class="timer" id="rotation-timer">⏱️ 00:00</div>
112
- <small>Next rotation in <span id="next-rotation">-</span></small>
113
- </div>
 
114
 
115
- <div class="stats">
116
- <div class="stat">
117
- <label>Total Bots</label>
118
- <span id="total">0</span>
119
- </div>
120
- <div class="stat">
121
- <label>Active</label>
122
- <span id="active">0</span>
123
- </div>
124
- <div class="stat">
125
- <label>Online</label>
126
- <span id="online">0</span>
127
- </div>
128
- <div class="stat">
129
- <label>Offline</label>
130
- <span id="offline">0</span>
131
- </div>
132
- </div>
133
 
134
- <div class="controls">
135
- <button onclick="refreshStatus()">🔄 Refresh</button>
136
- <button class="secondary" onclick="forceRotation()">⏭️ Next Bot</button>
137
- </div>
138
- <div id="msg"></div>
139
- </div>
140
 
141
- <div class="grid" id="grid"></div>
 
 
 
 
142
 
143
- <script>
144
- let data={};
145
- let updateTimer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
 
147
- async function fetchBots(){
148
- try{
149
- const r=await fetch('/api/status');
150
- data=await r.json();
151
- renderStatus();
152
- }catch(e){
153
- showMsg('Failed to fetch status','error');
154
  }
 
 
 
 
 
 
 
 
 
 
155
  }
156
 
157
- function renderStatus(){
158
- // Server Status
159
- const serverStatus = data.server_status;
160
- const statusEl = document.getElementById('server-status');
161
- statusEl.className = 'server-status ' + (serverStatus.online ? 'online' : 'offline');
 
 
 
 
162
 
163
- document.getElementById('server-address').textContent = data.server_info.host + ':' + data.server_info.port;
164
- document.getElementById('server-online').textContent = serverStatus.online ? '✅ Online' : '❌ Offline';
165
- document.getElementById('server-players').textContent = serverStatus.players;
166
- document.getElementById('server-version').textContent = data.server_info.version;
167
 
168
- // Rotation Info
169
- document.getElementById('current-bot').textContent = data.rotation.current_bot || '-';
170
- document.getElementById('rotation-timer').textContent = '⏱️ ' + formatTime(data.rotation.elapsed);
171
- document.getElementById('next-rotation').textContent = formatTime(data.rotation.remaining);
172
 
173
- // Stats
174
- const bots = data.bots;
175
- let total = 0, active = 0, online = 0, offline = 0;
176
 
177
- for(const bot of bots){
178
- total++;
179
- if(bot.is_active) active++;
180
- if(bot.status === 'online') online++;
181
- else if(bot.status === 'offline') offline++;
182
  }
183
 
184
- document.getElementById('total').textContent = total;
185
- document.getElementById('active').textContent = active;
186
- document.getElementById('online').textContent = online;
187
- document.getElementById('offline').textContent = offline;
188
 
189
- // Bot Grid
190
- const grid = document.getElementById('grid');
191
- grid.innerHTML = '';
 
 
 
192
 
193
- bots.forEach(bot => {
194
- const div = document.createElement('div');
195
- let className = 'bot ' + bot.status;
196
- if(bot.is_active) className += ' active';
197
- div.className = className;
 
 
 
 
 
 
198
 
199
- const badge = bot.is_active ? '<span class="badge">ACTIVE</span>' : '';
 
 
 
 
 
 
200
 
201
- div.innerHTML = `
202
- <div class="bot-name">${bot.name}${badge}</div>
203
- <div class="bot-info">Status: ${bot.status.toUpperCase()}</div>
204
- <div class="bot-info">Uptime: ${formatTime(bot.uptime)}</div>
205
- <div class="bot-info">Deaths: ${bot.deaths}</div>
206
- <div class="bot-info">Reconnects: ${bot.reconnects}</div>
207
- `;
208
- grid.appendChild(div);
209
- });
210
  }
211
 
212
- function formatTime(seconds){
213
- if(!seconds || seconds < 0) return '0:00';
214
- const h = Math.floor(seconds / 3600);
215
- const m = Math.floor((seconds % 3600) / 60);
216
- const s = seconds % 60;
217
- if(h > 0) return `${h}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
218
- return `${m}:${s.toString().padStart(2,'0')}`;
219
  }
220
 
221
- async function forceRotation(){
222
- if(!confirm('Force switch to next bot?')) return;
223
- try{
224
- const r = await fetch('/api/next_rotation', {method: 'POST'});
225
- const res = await r.json();
226
- if(res.success){
227
- showMsg('Switching to next bot...');
228
- setTimeout(fetchBots, 1000);
229
- }else{
230
- showMsg(res.error || 'Failed to rotate', 'error');
231
  }
232
- }catch(e){
233
- showMsg('Failed to rotate bot','error');
 
 
234
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  }
236
 
237
- function refreshStatus(){
238
- fetchBots();
239
- showMsg('Refreshing...');
 
 
 
240
  }
241
 
242
- function showMsg(text,type=''){
243
- const el=document.getElementById('msg');
244
- el.className='msg '+(type||'');
245
- el.textContent=text;
246
- if(text)setTimeout(()=>el.textContent='',3000);
 
 
247
  }
248
 
249
- function startAutoUpdate(){
250
- if(updateTimer)clearInterval(updateTimer);
251
- updateTimer=setInterval(fetchBots,2000);
 
252
  }
253
 
254
- fetchBots();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  startAutoUpdate();
256
  </script>
257
  </body>
258
  </html>"""
259
 
260
- # Bot Node.js script
261
  BOT_SCRIPT = """
262
  const mineflayer = require('mineflayer');
 
263
 
264
  const botName = process.argv[2];
265
  const host = process.argv[3];
@@ -270,7 +753,9 @@ console.log(JSON.stringify({event:'starting',name:botName}));
270
 
271
  let reconnectAttempts = 0;
272
  let isConnected = false;
273
- let afkInterval = null;
 
 
274
 
275
  function createBot() {
276
  const bot = mineflayer.createBot({
@@ -285,47 +770,98 @@ function createBot() {
285
  skipValidation: true
286
  });
287
 
288
- function startAfk() {
289
- if (afkInterval) clearInterval(afkInterval);
290
 
291
- afkInterval = setInterval(() => {
 
292
  if (!bot.entity || !isConnected) return;
293
 
294
- const actions = ['forward', 'back', 'left', 'right'];
295
- const action = actions[Math.floor(Math.random() * actions.length)];
 
296
 
297
- bot.setControlState(action, true);
298
- setTimeout(() => {
299
- bot.setControlState(action, false);
300
- }, 100);
301
 
302
- if (Math.random() > 0.5) {
303
- const yaw = bot.entity.yaw + (Math.random() - 0.5);
304
- const pitch = bot.entity.pitch + (Math.random() - 0.5) * 0.5;
305
- bot.look(yaw, pitch, true);
306
- }
307
- }, 10000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  }
309
 
310
  bot.once('spawn', () => {
311
  console.log(JSON.stringify({event:'connected',name:botName}));
312
  isConnected = true;
313
  reconnectAttempts = 0;
314
- startAfk();
315
  });
316
 
317
  bot.on('respawn', () => {
318
  console.log(JSON.stringify({event:'respawned',name:botName}));
319
- startAfk();
 
 
320
  });
321
 
322
  bot.on('death', () => {
323
  console.log(JSON.stringify({event:'death',name:botName}));
 
 
324
  });
325
 
326
  bot.on('kicked', (reason) => {
327
  console.log(JSON.stringify({event:'kicked',name:botName,reason:reason}));
328
  isConnected = false;
 
329
  });
330
 
331
  bot.on('error', (err) => {
@@ -335,7 +871,7 @@ function createBot() {
335
  bot.on('end', (reason) => {
336
  console.log(JSON.stringify({event:'disconnected',name:botName}));
337
  isConnected = false;
338
- if (afkInterval) clearInterval(afkInterval);
339
 
340
  // Auto reconnect
341
  reconnectAttempts++;
@@ -353,13 +889,13 @@ function createBot() {
353
  }, 30000);
354
 
355
  process.on('SIGTERM', () => {
356
- if (afkInterval) clearInterval(afkInterval);
357
  bot.quit();
358
  process.exit();
359
  });
360
 
361
  process.on('SIGINT', () => {
362
- if (afkInterval) clearInterval(afkInterval);
363
  bot.quit();
364
  process.exit();
365
  });
@@ -380,15 +916,18 @@ def write_bot_script():
380
  return False
381
 
382
  def check_server_status():
383
- """Check if Minecraft server is online"""
384
  global server_status
385
  try:
 
386
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
387
  sock.settimeout(3)
388
  result = sock.connect_ex((SERVER_HOST, SERVER_PORT))
 
389
  sock.close()
390
 
391
  server_status["online"] = result == 0
 
392
  server_status["last_check"] = datetime.now()
393
 
394
  if not server_status["online"]:
@@ -396,11 +935,11 @@ def check_server_status():
396
  except Exception as e:
397
  server_status["online"] = False
398
  server_status["players"] = "N/A"
 
399
  server_status["last_check"] = datetime.now()
400
 
401
  def start_bot(name):
402
  """Start a bot process"""
403
- # Kill existing process if any
404
  if name in bot_processes:
405
  try:
406
  proc = bot_processes[name]
@@ -426,19 +965,19 @@ def start_bot(name):
426
  'start_time': time.time(),
427
  'deaths': 0,
428
  'reconnects': 0,
429
- 'uptime': 0
 
430
  }
431
  else:
432
  bots[name]['status'] = 'connecting'
433
  bots[name]['start_time'] = time.time()
434
 
435
- # Start monitoring thread
436
  threading.Thread(target=monitor_bot, args=(name,), daemon=True).start()
437
 
438
- print(f"Started bot: {name}")
439
  return True
440
  except Exception as e:
441
- print(f"Error starting bot {name}: {e}")
442
  return False
443
 
444
  def stop_bot(name):
@@ -451,7 +990,7 @@ def stop_bot(name):
451
  del bot_processes[name]
452
  if name in bots:
453
  bots[name]['status'] = 'offline'
454
- print(f"Stopped bot: {name}")
455
  except Exception as e:
456
  print(f"Error stopping bot {name}: {e}")
457
 
@@ -482,6 +1021,9 @@ def monitor_bot(name):
482
  bots[name]['status'] = 'connecting'
483
  elif event in ['disconnected', 'error']:
484
  bots[name]['status'] = 'offline'
 
 
 
485
 
486
  if event in ['connected', 'disconnected', 'kicked', 'error', 'death']:
487
  print(f"[{name}] {event}")
@@ -502,7 +1044,6 @@ def rotation_manager():
502
  try:
503
  current_bot = BOT_NAMES[current_bot_index]
504
 
505
- # Start current bot
506
  if rotation_start_time is None or time.time() - rotation_start_time >= ROTATION_DURATION:
507
  # Stop all bots
508
  for name in BOT_NAMES:
@@ -513,7 +1054,7 @@ def rotation_manager():
513
  # Start new bot
514
  start_bot(current_bot)
515
  rotation_start_time = time.time()
516
- print(f"Rotation: Now active -> {current_bot}")
517
 
518
  # Move to next bot for next rotation
519
  current_bot_index = (current_bot_index + 1) % len(BOT_NAMES)
@@ -531,19 +1072,28 @@ def initialize_bots():
531
  'start_time': 0,
532
  'deaths': 0,
533
  'reconnects': 0,
534
- 'uptime': 0
 
535
  }
536
 
537
  @app.route('/')
538
  def index():
539
  return HTML
540
 
 
 
 
 
 
 
541
  @app.route('/api/status')
542
  def api_status():
543
  """Get complete status"""
544
  check_server_status()
545
 
546
- current_bot = BOT_NAMES[current_bot_index - 1 if rotation_start_time else current_bot_index]
 
 
547
  elapsed = int(time.time() - rotation_start_time) if rotation_start_time else 0
548
  remaining = max(0, ROTATION_DURATION - elapsed)
549
 
@@ -558,7 +1108,8 @@ def api_status():
558
  'is_active': name == current_bot,
559
  'uptime': uptime,
560
  'deaths': bot.get('deaths', 0),
561
- 'reconnects': bot.get('reconnects', 0)
 
562
  })
563
 
564
  return jsonify({
@@ -570,8 +1121,10 @@ def api_status():
570
  'server_status': server_status,
571
  'rotation': {
572
  'current_bot': current_bot,
 
573
  'elapsed': elapsed,
574
- 'remaining': remaining
 
575
  },
576
  'bots': bot_list
577
  })
@@ -580,12 +1133,12 @@ def api_status():
580
  def api_next_rotation():
581
  """Force next rotation"""
582
  global rotation_start_time
583
- rotation_start_time = 0 # Force rotation on next cycle
584
  return jsonify({'success': True})
585
 
586
  def cleanup(sig=None, frame=None):
587
  """Clean shutdown"""
588
- print("\nShutting down...")
589
  for name in BOT_NAMES:
590
  stop_bot(name)
591
  sys.exit(0)
@@ -594,22 +1147,18 @@ if __name__ == '__main__':
594
  signal.signal(signal.SIGINT, cleanup)
595
  signal.signal(signal.SIGTERM, cleanup)
596
 
597
- # Setup
598
  if not write_bot_script():
599
- print("Failed to write bot script!")
600
  sys.exit(1)
601
 
602
- print("Bot Manager starting...")
603
- print(f"Server: {SERVER_HOST}:{SERVER_PORT}")
604
- print(f"Bots: {', '.join(BOT_NAMES)}")
605
- print(f"Rotation: {ROTATION_DURATION//60} minutes per bot")
606
 
607
- # Initialize
608
  initialize_bots()
609
 
610
- # Start rotation manager
611
  threading.Thread(target=rotation_manager, daemon=True).start()
612
 
613
- # Start Flask server
614
- print("Dashboard starting on port 7860...")
615
  app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False)
 
6
  import subprocess
7
  import signal
8
  import socket
9
+ import struct
10
  from datetime import datetime, timedelta
11
  from flask import Flask, render_template_string, jsonify, request
12
  import logging
 
28
  bot_processes = {}
29
  current_bot_index = 0
30
  rotation_start_time = None
31
+ server_status = {"online": False, "players": "0/0", "latency": 0, "last_check": None}
32
 
33
+ # Modern HTML Interface
34
  HTML = """<!DOCTYPE html>
35
  <html>
36
  <head>
37
+ <title>Bot Manager - OrbitMC</title>
38
  <meta charset="utf-8">
39
  <meta name="viewport" content="width=device-width,initial-scale=1">
40
  <style>
41
+ * {
42
+ margin: 0;
43
+ padding: 0;
44
+ box-sizing: border-box;
45
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ body {
48
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
49
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
50
+ min-height: 100vh;
51
+ padding: 20px;
52
+ color: #fff;
53
+ }
54
 
55
+ .container {
56
+ max-width: 1400px;
57
+ margin: 0 auto;
58
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ .header {
61
+ text-align: center;
62
+ margin-bottom: 30px;
63
+ }
 
 
64
 
65
+ .header h1 {
66
+ font-size: 42px;
67
+ margin-bottom: 10px;
68
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
69
+ }
70
 
71
+ .header p {
72
+ font-size: 18px;
73
+ opacity: 0.9;
74
+ }
75
+
76
+ /* Server Status Card */
77
+ .server-card {
78
+ background: rgba(255, 255, 255, 0.1);
79
+ backdrop-filter: blur(10px);
80
+ border-radius: 20px;
81
+ padding: 30px;
82
+ margin-bottom: 25px;
83
+ border: 1px solid rgba(255, 255, 255, 0.2);
84
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
85
+ }
86
+
87
+ .server-header {
88
+ display: flex;
89
+ justify-content: space-between;
90
+ align-items: center;
91
+ margin-bottom: 20px;
92
+ }
93
+
94
+ .server-title {
95
+ font-size: 24px;
96
+ font-weight: bold;
97
+ }
98
+
99
+ .status-badge {
100
+ padding: 8px 20px;
101
+ border-radius: 20px;
102
+ font-weight: bold;
103
+ font-size: 14px;
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 8px;
107
+ }
108
+
109
+ .status-badge.online {
110
+ background: #10b981;
111
+ animation: pulse 2s infinite;
112
+ }
113
+
114
+ .status-badge.offline {
115
+ background: #ef4444;
116
+ }
117
+
118
+ @keyframes pulse {
119
+ 0%, 100% { opacity: 1; }
120
+ 50% { opacity: 0.7; }
121
+ }
122
+
123
+ .status-dot {
124
+ width: 10px;
125
+ height: 10px;
126
+ border-radius: 50%;
127
+ background: white;
128
+ }
129
+
130
+ .server-info {
131
+ display: grid;
132
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
133
+ gap: 15px;
134
+ }
135
+
136
+ .info-item {
137
+ background: rgba(255, 255, 255, 0.1);
138
+ padding: 15px;
139
+ border-radius: 10px;
140
+ text-align: center;
141
+ }
142
+
143
+ .info-label {
144
+ font-size: 12px;
145
+ opacity: 0.8;
146
+ margin-bottom: 5px;
147
+ }
148
+
149
+ .info-value {
150
+ font-size: 20px;
151
+ font-weight: bold;
152
+ }
153
+
154
+ /* Rotation Card */
155
+ .rotation-card {
156
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
157
+ border-radius: 20px;
158
+ padding: 30px;
159
+ margin-bottom: 25px;
160
+ box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
161
+ text-align: center;
162
+ }
163
+
164
+ .rotation-title {
165
+ font-size: 18px;
166
+ opacity: 0.9;
167
+ margin-bottom: 15px;
168
+ }
169
+
170
+ .current-bot {
171
+ font-size: 48px;
172
+ font-weight: bold;
173
+ margin: 15px 0;
174
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
175
+ }
176
+
177
+ .rotation-timer {
178
+ font-size: 36px;
179
+ font-weight: bold;
180
+ margin: 20px 0;
181
+ font-family: 'Courier New', monospace;
182
+ }
183
+
184
+ .progress-bar {
185
+ background: rgba(255, 255, 255, 0.3);
186
+ border-radius: 10px;
187
+ height: 20px;
188
+ overflow: hidden;
189
+ margin: 20px 0;
190
+ }
191
+
192
+ .progress-fill {
193
+ background: rgba(255, 255, 255, 0.9);
194
+ height: 100%;
195
+ border-radius: 10px;
196
+ transition: width 1s linear;
197
+ }
198
+
199
+ .next-bot-info {
200
+ background: rgba(255, 255, 255, 0.2);
201
+ padding: 15px;
202
+ border-radius: 10px;
203
+ margin-top: 15px;
204
+ font-size: 16px;
205
+ }
206
+
207
+ .controls {
208
+ display: flex;
209
+ justify-content: center;
210
+ gap: 10px;
211
+ margin-top: 20px;
212
+ }
213
+
214
+ button {
215
+ background: rgba(255, 255, 255, 0.3);
216
+ border: 2px solid rgba(255, 255, 255, 0.5);
217
+ color: white;
218
+ padding: 12px 30px;
219
+ border-radius: 25px;
220
+ cursor: pointer;
221
+ font-size: 16px;
222
+ font-weight: bold;
223
+ transition: all 0.3s;
224
+ }
225
+
226
+ button:hover {
227
+ background: rgba(255, 255, 255, 0.5);
228
+ transform: translateY(-2px);
229
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
230
+ }
231
+
232
+ button:active {
233
+ transform: translateY(0);
234
+ }
235
+
236
+ button.primary {
237
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
238
+ border: none;
239
+ }
240
+
241
+ button.primary:hover {
242
+ background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
243
+ }
244
 
245
+ /* Bot Grid */
246
+ .bot-grid {
247
+ display: grid;
248
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
249
+ gap: 20px;
250
+ margin-bottom: 30px;
 
251
  }
252
+
253
+ .bot-card {
254
+ background: rgba(255, 255, 255, 0.1);
255
+ backdrop-filter: blur(10px);
256
+ border-radius: 15px;
257
+ padding: 20px;
258
+ border: 2px solid rgba(255, 255, 255, 0.2);
259
+ transition: all 0.3s;
260
+ position: relative;
261
+ overflow: hidden;
262
  }
263
 
264
+ .bot-card::before {
265
+ content: '';
266
+ position: absolute;
267
+ top: 0;
268
+ left: 0;
269
+ right: 0;
270
+ height: 4px;
271
+ background: #6b7280;
272
+ }
273
 
274
+ .bot-card.active::before {
275
+ background: linear-gradient(90deg, #f093fb 0%, #f5576c 100%);
276
+ animation: shimmer 2s infinite;
277
+ }
278
 
279
+ .bot-card.online::before {
280
+ background: #10b981;
281
+ }
 
282
 
283
+ .bot-card.connecting::before {
284
+ background: #f59e0b;
285
+ }
286
 
287
+ .bot-card.offline::before {
288
+ background: #6b7280;
 
 
 
289
  }
290
 
291
+ @keyframes shimmer {
292
+ 0% { transform: translateX(-100%); }
293
+ 100% { transform: translateX(100%); }
294
+ }
295
 
296
+ .bot-card.active {
297
+ border-color: rgba(240, 147, 251, 0.5);
298
+ background: rgba(240, 147, 251, 0.15);
299
+ transform: scale(1.05);
300
+ box-shadow: 0 10px 30px rgba(240, 147, 251, 0.3);
301
+ }
302
 
303
+ .bot-header {
304
+ display: flex;
305
+ justify-content: space-between;
306
+ align-items: center;
307
+ margin-bottom: 15px;
308
+ }
309
+
310
+ .bot-name {
311
+ font-size: 20px;
312
+ font-weight: bold;
313
+ }
314
 
315
+ .active-badge {
316
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
317
+ padding: 4px 12px;
318
+ border-radius: 12px;
319
+ font-size: 11px;
320
+ font-weight: bold;
321
+ }
322
 
323
+ .bot-stats {
324
+ display: grid;
325
+ grid-template-columns: 1fr 1fr;
326
+ gap: 10px;
327
+ margin: 15px 0;
 
 
 
 
328
  }
329
 
330
+ .stat-item {
331
+ background: rgba(255, 255, 255, 0.1);
332
+ padding: 10px;
333
+ border-radius: 8px;
334
+ text-align: center;
 
 
335
  }
336
 
337
+ .stat-label {
338
+ font-size: 11px;
339
+ opacity: 0.7;
340
+ margin-bottom: 3px;
 
 
 
 
 
 
341
  }
342
+
343
+ .stat-value {
344
+ font-size: 18px;
345
+ font-weight: bold;
346
  }
347
+
348
+ .bot-status-text {
349
+ text-align: center;
350
+ padding: 8px;
351
+ border-radius: 8px;
352
+ font-weight: bold;
353
+ margin-top: 10px;
354
+ }
355
+
356
+ .bot-status-text.online {
357
+ background: rgba(16, 185, 129, 0.3);
358
+ color: #10b981;
359
+ }
360
+
361
+ .bot-status-text.offline {
362
+ background: rgba(107, 114, 128, 0.3);
363
+ color: #9ca3af;
364
+ }
365
+
366
+ .bot-status-text.connecting {
367
+ background: rgba(245, 158, 11, 0.3);
368
+ color: #f59e0b;
369
+ }
370
+
371
+ .bot-status-text.active {
372
+ background: rgba(240, 147, 251, 0.3);
373
+ color: #f093fb;
374
+ }
375
+
376
+ /* Queue Section */
377
+ .queue-section {
378
+ background: rgba(255, 255, 255, 0.1);
379
+ backdrop-filter: blur(10px);
380
+ border-radius: 20px;
381
+ padding: 25px;
382
+ margin-bottom: 25px;
383
+ border: 1px solid rgba(255, 255, 255, 0.2);
384
+ }
385
+
386
+ .queue-title {
387
+ font-size: 20px;
388
+ font-weight: bold;
389
+ margin-bottom: 20px;
390
+ text-align: center;
391
  }
392
 
393
+ .queue-list {
394
+ display: flex;
395
+ justify-content: center;
396
+ align-items: center;
397
+ gap: 15px;
398
+ flex-wrap: wrap;
399
  }
400
 
401
+ .queue-item {
402
+ background: rgba(255, 255, 255, 0.1);
403
+ padding: 15px 25px;
404
+ border-radius: 15px;
405
+ font-weight: bold;
406
+ transition: all 0.3s;
407
+ border: 2px solid transparent;
408
  }
409
 
410
+ .queue-item.active {
411
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
412
+ transform: scale(1.15);
413
+ box-shadow: 0 5px 20px rgba(240, 147, 251, 0.4);
414
  }
415
 
416
+ .queue-item.next {
417
+ border-color: rgba(245, 158, 11, 0.6);
418
+ background: rgba(245, 158, 11, 0.2);
419
+ }
420
+
421
+ .queue-arrow {
422
+ font-size: 24px;
423
+ opacity: 0.5;
424
+ }
425
+
426
+ .msg {
427
+ position: fixed;
428
+ top: 20px;
429
+ right: 20px;
430
+ background: #10b981;
431
+ color: white;
432
+ padding: 15px 25px;
433
+ border-radius: 10px;
434
+ box-shadow: 0 5px 20px rgba(0,0,0,0.3);
435
+ z-index: 1000;
436
+ animation: slideIn 0.3s;
437
+ }
438
+
439
+ .msg.error {
440
+ background: #ef4444;
441
+ }
442
+
443
+ @keyframes slideIn {
444
+ from {
445
+ transform: translateX(400px);
446
+ opacity: 0;
447
+ }
448
+ to {
449
+ transform: translateX(0);
450
+ opacity: 1;
451
+ }
452
+ }
453
+
454
+ @media (max-width: 768px) {
455
+ .header h1 { font-size: 28px; }
456
+ .current-bot { font-size: 32px; }
457
+ .rotation-timer { font-size: 24px; }
458
+ .bot-grid { grid-template-columns: 1fr; }
459
+ }
460
+ </style>
461
+ </head>
462
+ <body>
463
+ <div class="container">
464
+ <div class="header">
465
+ <h1>🎮 Minecraft Bot Manager</h1>
466
+ <p>OrbitMC Server Monitor & Bot Rotation System</p>
467
+ </div>
468
+
469
+ <!-- Server Status -->
470
+ <div class="server-card">
471
+ <div class="server-header">
472
+ <div class="server-title">📡 Server Status</div>
473
+ <div class="status-badge" id="server-badge">
474
+ <div class="status-dot"></div>
475
+ <span id="server-status-text">Checking...</span>
476
+ </div>
477
+ </div>
478
+ <div class="server-info">
479
+ <div class="info-item">
480
+ <div class="info-label">Address</div>
481
+ <div class="info-value" id="server-address">-</div>
482
+ </div>
483
+ <div class="info-item">
484
+ <div class="info-label">Players Online</div>
485
+ <div class="info-value" id="server-players">-</div>
486
+ </div>
487
+ <div class="info-item">
488
+ <div class="info-label">Latency</div>
489
+ <div class="info-value" id="server-latency">-</div>
490
+ </div>
491
+ <div class="info-item">
492
+ <div class="info-label">Version</div>
493
+ <div class="info-value" id="server-version">-</div>
494
+ </div>
495
+ </div>
496
+ </div>
497
+
498
+ <!-- Current Rotation -->
499
+ <div class="rotation-card">
500
+ <div class="rotation-title">🔄 CURRENT ACTIVE BOT</div>
501
+ <div class="current-bot" id="current-bot">-</div>
502
+ <div class="rotation-timer" id="rotation-timer">00:00:00</div>
503
+
504
+ <div class="progress-bar">
505
+ <div class="progress-fill" id="progress-fill"></div>
506
+ </div>
507
+
508
+ <div class="next-bot-info">
509
+ <strong>Next Bot:</strong> <span id="next-bot">-</span> in <span id="time-until-next">-</span>
510
+ </div>
511
+
512
+ <div class="controls">
513
+ <button class="primary" onclick="forceRotation()">⏭️ Switch to Next Bot</button>
514
+ <button onclick="refreshStatus()">🔄 Refresh</button>
515
+ </div>
516
+ </div>
517
+
518
+ <!-- Queue -->
519
+ <div class="queue-section">
520
+ <div class="queue-title">📋 Rotation Queue</div>
521
+ <div class="queue-list" id="queue-list"></div>
522
+ </div>
523
+
524
+ <!-- Bot Grid -->
525
+ <div class="bot-grid" id="bot-grid"></div>
526
+
527
+ <div id="msg-container"></div>
528
+ </div>
529
+
530
+ <script>
531
+ let data = {};
532
+ let updateTimer;
533
+ let serverCheckTimer;
534
+
535
+ async function fetchStatus() {
536
+ try {
537
+ const r = await fetch('/api/status');
538
+ data = await r.json();
539
+ renderStatus();
540
+ } catch(e) {
541
+ console.error('Failed to fetch status', e);
542
+ }
543
+ }
544
+
545
+ function renderStatus() {
546
+ // Server Status
547
+ const server = data.server_status;
548
+ const badge = document.getElementById('server-badge');
549
+
550
+ if (server.online) {
551
+ badge.className = 'status-badge online';
552
+ document.getElementById('server-status-text').textContent = 'Online';
553
+ } else {
554
+ badge.className = 'status-badge offline';
555
+ document.getElementById('server-status-text').textContent = 'Offline';
556
+ }
557
+
558
+ document.getElementById('server-address').textContent = `${data.server_info.host}:${data.server_info.port}`;
559
+ document.getElementById('server-players').textContent = server.players;
560
+ document.getElementById('server-latency').textContent = server.latency > 0 ? `${server.latency}ms` : 'N/A';
561
+ document.getElementById('server-version').textContent = data.server_info.version;
562
+
563
+ // Rotation Info
564
+ const rotation = data.rotation;
565
+ document.getElementById('current-bot').textContent = rotation.current_bot || '-';
566
+ document.getElementById('rotation-timer').textContent = formatTime(rotation.elapsed);
567
+ document.getElementById('next-bot').textContent = rotation.next_bot || '-';
568
+ document.getElementById('time-until-next').textContent = formatTime(rotation.remaining);
569
+
570
+ // Progress Bar
571
+ const progress = (rotation.elapsed / 3600) * 100;
572
+ document.getElementById('progress-fill').style.width = progress + '%';
573
+
574
+ // Queue
575
+ renderQueue(rotation.queue, rotation.current_bot, rotation.next_bot);
576
+
577
+ // Bot Grid
578
+ renderBots(data.bots);
579
+ }
580
+
581
+ function renderQueue(queue, current, next) {
582
+ const queueList = document.getElementById('queue-list');
583
+ queueList.innerHTML = '';
584
+
585
+ queue.forEach((bot, index) => {
586
+ const item = document.createElement('div');
587
+ item.className = 'queue-item';
588
+
589
+ if (bot === current) {
590
+ item.className += ' active';
591
+ } else if (bot === next) {
592
+ item.className += ' next';
593
+ }
594
+
595
+ item.textContent = bot;
596
+ queueList.appendChild(item);
597
+
598
+ if (index < queue.length - 1) {
599
+ const arrow = document.createElement('div');
600
+ arrow.className = 'queue-arrow';
601
+ arrow.textContent = '→';
602
+ queueList.appendChild(arrow);
603
+ }
604
+ });
605
+ }
606
+
607
+ function renderBots(bots) {
608
+ const grid = document.getElementById('bot-grid');
609
+ grid.innerHTML = '';
610
+
611
+ bots.forEach(bot => {
612
+ const card = document.createElement('div');
613
+ card.className = `bot-card ${bot.status}`;
614
+
615
+ if (bot.is_active) {
616
+ card.className += ' active';
617
+ }
618
+
619
+ const activeBadge = bot.is_active ? '<div class="active-badge">⚡ ACTIVE NOW</div>' : '';
620
+
621
+ const statusClass = bot.is_active ? 'active' : bot.status;
622
+ const statusText = bot.is_active ? '⚡ ACTIVE & ONLINE' : bot.status.toUpperCase();
623
+
624
+ card.innerHTML = `
625
+ <div class="bot-header">
626
+ <div class="bot-name">${bot.name}</div>
627
+ ${activeBadge}
628
+ </div>
629
+ <div class="bot-stats">
630
+ <div class="stat-item">
631
+ <div class="stat-label">Uptime</div>
632
+ <div class="stat-value">${formatTimeShort(bot.uptime)}</div>
633
+ </div>
634
+ <div class="stat-item">
635
+ <div class="stat-label">Deaths</div>
636
+ <div class="stat-value">${bot.deaths}</div>
637
+ </div>
638
+ <div class="stat-item">
639
+ <div class="stat-label">Reconnects</div>
640
+ <div class="stat-value">${bot.reconnects}</div>
641
+ </div>
642
+ <div class="stat-item">
643
+ <div class="stat-label">Position</div>
644
+ <div class="stat-value">${bot.position}</div>
645
+ </div>
646
+ </div>
647
+ <div class="bot-status-text ${statusClass}">${statusText}</div>
648
+ `;
649
+
650
+ grid.appendChild(card);
651
+ });
652
+ }
653
+
654
+ function formatTime(seconds) {
655
+ if (!seconds || seconds < 0) return '00:00:00';
656
+ const h = Math.floor(seconds / 3600);
657
+ const m = Math.floor((seconds % 3600) / 60);
658
+ const s = seconds % 60;
659
+ return `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
660
+ }
661
+
662
+ function formatTimeShort(seconds) {
663
+ if (!seconds || seconds < 0) return '0m';
664
+ const h = Math.floor(seconds / 3600);
665
+ const m = Math.floor((seconds % 3600) / 60);
666
+ if (h > 0) return `${h}h ${m}m`;
667
+ return `${m}m`;
668
+ }
669
+
670
+ async function forceRotation() {
671
+ if (!confirm('Switch to the next bot now?')) return;
672
+
673
+ try {
674
+ const r = await fetch('/api/next_rotation', {method: 'POST'});
675
+ const res = await r.json();
676
+
677
+ if (res.success) {
678
+ showMsg('✅ Switching to next bot...', 'success');
679
+ setTimeout(fetchStatus, 1000);
680
+ } else {
681
+ showMsg('❌ ' + (res.error || 'Failed to rotate'), 'error');
682
+ }
683
+ } catch(e) {
684
+ showMsg('❌ Failed to switch bot', 'error');
685
+ }
686
+ }
687
+
688
+ function refreshStatus() {
689
+ fetchStatus();
690
+ showMsg('🔄 Refreshing...', 'success');
691
+ }
692
+
693
+ function showMsg(text, type = 'success') {
694
+ const container = document.getElementById('msg-container');
695
+ const msg = document.createElement('div');
696
+ msg.className = 'msg' + (type === 'error' ? ' error' : '');
697
+ msg.textContent = text;
698
+ container.appendChild(msg);
699
+
700
+ setTimeout(() => {
701
+ msg.remove();
702
+ }, 3000);
703
+ }
704
+
705
+ function startAutoUpdate() {
706
+ if (updateTimer) clearInterval(updateTimer);
707
+ if (serverCheckTimer) clearInterval(serverCheckTimer);
708
+
709
+ // Update full status every 2 seconds
710
+ updateTimer = setInterval(fetchStatus, 2000);
711
+
712
+ // Update server status every 1 second
713
+ serverCheckTimer = setInterval(async () => {
714
+ try {
715
+ const r = await fetch('/api/server_status');
716
+ const server = await r.json();
717
+
718
+ const badge = document.getElementById('server-badge');
719
+ if (server.online) {
720
+ badge.className = 'status-badge online';
721
+ document.getElementById('server-status-text').textContent = 'Online';
722
+ } else {
723
+ badge.className = 'status-badge offline';
724
+ document.getElementById('server-status-text').textContent = 'Offline';
725
+ }
726
+
727
+ document.getElementById('server-players').textContent = server.players;
728
+ document.getElementById('server-latency').textContent = server.latency > 0 ? `${server.latency}ms` : 'N/A';
729
+ } catch(e) {
730
+ console.error('Server check failed', e);
731
+ }
732
+ }, 1000);
733
+ }
734
+
735
+ // Initialize
736
+ fetchStatus();
737
  startAutoUpdate();
738
  </script>
739
  </body>
740
  </html>"""
741
 
742
+ # Bot Node.js script with circular movement
743
  BOT_SCRIPT = """
744
  const mineflayer = require('mineflayer');
745
+ const { Vec3 } = require('vec3');
746
 
747
  const botName = process.argv[2];
748
  const host = process.argv[3];
 
753
 
754
  let reconnectAttempts = 0;
755
  let isConnected = false;
756
+ let circleInterval = null;
757
+ let angle = 0;
758
+ let centerPos = null;
759
 
760
  function createBot() {
761
  const bot = mineflayer.createBot({
 
770
  skipValidation: true
771
  });
772
 
773
+ function startCircularMovement() {
774
+ if (circleInterval) clearInterval(circleInterval);
775
 
776
+ // Wait a bit for spawn
777
+ setTimeout(() => {
778
  if (!bot.entity || !isConnected) return;
779
 
780
+ // Set center position
781
+ centerPos = bot.entity.position.clone();
782
+ angle = 0;
783
 
784
+ console.log(JSON.stringify({event:'movement_started',name:botName,center:centerPos}));
 
 
 
785
 
786
+ // Move in circle every 100ms
787
+ circleInterval = setInterval(() => {
788
+ if (!bot.entity || !isConnected || !centerPos) return;
789
+
790
+ try {
791
+ const radius = 3; // 3 block radius circle
792
+ angle += 0.05; // Rotation speed
793
+
794
+ // Calculate target position
795
+ const targetX = centerPos.x + Math.cos(angle) * radius;
796
+ const targetZ = centerPos.z + Math.sin(angle) * radius;
797
+ const targetY = centerPos.y;
798
+
799
+ const target = new Vec3(targetX, targetY, targetZ);
800
+
801
+ // Look at target
802
+ const dx = target.x - bot.entity.position.x;
803
+ const dz = target.z - bot.entity.position.z;
804
+ const yaw = Math.atan2(-dx, -dz);
805
+ bot.look(yaw, 0, true);
806
+
807
+ // Move forward
808
+ bot.setControlState('forward', true);
809
+
810
+ // Send position update
811
+ if (Math.floor(angle * 10) % 10 === 0) {
812
+ const pos = bot.entity.position;
813
+ console.log(JSON.stringify({
814
+ event:'position',
815
+ name:botName,
816
+ x: Math.floor(pos.x),
817
+ y: Math.floor(pos.y),
818
+ z: Math.floor(pos.z)
819
+ }));
820
+ }
821
+ } catch(err) {
822
+ // Ignore movement errors
823
+ }
824
+ }, 100);
825
+ }, 2000);
826
+ }
827
+
828
+ function stopMovement() {
829
+ if (circleInterval) {
830
+ clearInterval(circleInterval);
831
+ circleInterval = null;
832
+ }
833
+ try {
834
+ bot.setControlState('forward', false);
835
+ bot.setControlState('back', false);
836
+ bot.setControlState('left', false);
837
+ bot.setControlState('right', false);
838
+ } catch(err) {}
839
  }
840
 
841
  bot.once('spawn', () => {
842
  console.log(JSON.stringify({event:'connected',name:botName}));
843
  isConnected = true;
844
  reconnectAttempts = 0;
845
+ startCircularMovement();
846
  });
847
 
848
  bot.on('respawn', () => {
849
  console.log(JSON.stringify({event:'respawned',name:botName}));
850
+ stopMovement();
851
+ centerPos = null;
852
+ startCircularMovement();
853
  });
854
 
855
  bot.on('death', () => {
856
  console.log(JSON.stringify({event:'death',name:botName}));
857
+ stopMovement();
858
+ centerPos = null;
859
  });
860
 
861
  bot.on('kicked', (reason) => {
862
  console.log(JSON.stringify({event:'kicked',name:botName,reason:reason}));
863
  isConnected = false;
864
+ stopMovement();
865
  });
866
 
867
  bot.on('error', (err) => {
 
871
  bot.on('end', (reason) => {
872
  console.log(JSON.stringify({event:'disconnected',name:botName}));
873
  isConnected = false;
874
+ stopMovement();
875
 
876
  // Auto reconnect
877
  reconnectAttempts++;
 
889
  }, 30000);
890
 
891
  process.on('SIGTERM', () => {
892
+ stopMovement();
893
  bot.quit();
894
  process.exit();
895
  });
896
 
897
  process.on('SIGINT', () => {
898
+ stopMovement();
899
  bot.quit();
900
  process.exit();
901
  });
 
916
  return False
917
 
918
  def check_server_status():
919
+ """Check Minecraft server status with latency"""
920
  global server_status
921
  try:
922
+ start_time = time.time()
923
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
924
  sock.settimeout(3)
925
  result = sock.connect_ex((SERVER_HOST, SERVER_PORT))
926
+ latency = int((time.time() - start_time) * 1000)
927
  sock.close()
928
 
929
  server_status["online"] = result == 0
930
+ server_status["latency"] = latency if result == 0 else 0
931
  server_status["last_check"] = datetime.now()
932
 
933
  if not server_status["online"]:
 
935
  except Exception as e:
936
  server_status["online"] = False
937
  server_status["players"] = "N/A"
938
+ server_status["latency"] = 0
939
  server_status["last_check"] = datetime.now()
940
 
941
  def start_bot(name):
942
  """Start a bot process"""
 
943
  if name in bot_processes:
944
  try:
945
  proc = bot_processes[name]
 
965
  'start_time': time.time(),
966
  'deaths': 0,
967
  'reconnects': 0,
968
+ 'uptime': 0,
969
+ 'position': '0, 0, 0'
970
  }
971
  else:
972
  bots[name]['status'] = 'connecting'
973
  bots[name]['start_time'] = time.time()
974
 
 
975
  threading.Thread(target=monitor_bot, args=(name,), daemon=True).start()
976
 
977
+ print(f"Started bot: {name}")
978
  return True
979
  except Exception as e:
980
+ print(f"Error starting bot {name}: {e}")
981
  return False
982
 
983
  def stop_bot(name):
 
990
  del bot_processes[name]
991
  if name in bots:
992
  bots[name]['status'] = 'offline'
993
+ print(f"⏹️ Stopped bot: {name}")
994
  except Exception as e:
995
  print(f"Error stopping bot {name}: {e}")
996
 
 
1021
  bots[name]['status'] = 'connecting'
1022
  elif event in ['disconnected', 'error']:
1023
  bots[name]['status'] = 'offline'
1024
+ elif event == 'position':
1025
+ x, y, z = data.get('x', 0), data.get('y', 0), data.get('z', 0)
1026
+ bots[name]['position'] = f"{x}, {y}, {z}"
1027
 
1028
  if event in ['connected', 'disconnected', 'kicked', 'error', 'death']:
1029
  print(f"[{name}] {event}")
 
1044
  try:
1045
  current_bot = BOT_NAMES[current_bot_index]
1046
 
 
1047
  if rotation_start_time is None or time.time() - rotation_start_time >= ROTATION_DURATION:
1048
  # Stop all bots
1049
  for name in BOT_NAMES:
 
1054
  # Start new bot
1055
  start_bot(current_bot)
1056
  rotation_start_time = time.time()
1057
+ print(f"🔄 Rotation: Now active -> {current_bot}")
1058
 
1059
  # Move to next bot for next rotation
1060
  current_bot_index = (current_bot_index + 1) % len(BOT_NAMES)
 
1072
  'start_time': 0,
1073
  'deaths': 0,
1074
  'reconnects': 0,
1075
+ 'uptime': 0,
1076
+ 'position': '0, 0, 0'
1077
  }
1078
 
1079
  @app.route('/')
1080
  def index():
1081
  return HTML
1082
 
1083
+ @app.route('/api/server_status')
1084
+ def api_server_status():
1085
+ """Get only server status (called every 1s)"""
1086
+ check_server_status()
1087
+ return jsonify(server_status)
1088
+
1089
  @app.route('/api/status')
1090
  def api_status():
1091
  """Get complete status"""
1092
  check_server_status()
1093
 
1094
+ current_bot = BOT_NAMES[(current_bot_index - 1) % len(BOT_NAMES)] if rotation_start_time else BOT_NAMES[current_bot_index]
1095
+ next_bot = BOT_NAMES[current_bot_index]
1096
+
1097
  elapsed = int(time.time() - rotation_start_time) if rotation_start_time else 0
1098
  remaining = max(0, ROTATION_DURATION - elapsed)
1099
 
 
1108
  'is_active': name == current_bot,
1109
  'uptime': uptime,
1110
  'deaths': bot.get('deaths', 0),
1111
+ 'reconnects': bot.get('reconnects', 0),
1112
+ 'position': bot.get('position', '0, 0, 0')
1113
  })
1114
 
1115
  return jsonify({
 
1121
  'server_status': server_status,
1122
  'rotation': {
1123
  'current_bot': current_bot,
1124
+ 'next_bot': next_bot,
1125
  'elapsed': elapsed,
1126
+ 'remaining': remaining,
1127
+ 'queue': BOT_NAMES
1128
  },
1129
  'bots': bot_list
1130
  })
 
1133
  def api_next_rotation():
1134
  """Force next rotation"""
1135
  global rotation_start_time
1136
+ rotation_start_time = 0
1137
  return jsonify({'success': True})
1138
 
1139
  def cleanup(sig=None, frame=None):
1140
  """Clean shutdown"""
1141
+ print("\n🛑 Shutting down...")
1142
  for name in BOT_NAMES:
1143
  stop_bot(name)
1144
  sys.exit(0)
 
1147
  signal.signal(signal.SIGINT, cleanup)
1148
  signal.signal(signal.SIGTERM, cleanup)
1149
 
 
1150
  if not write_bot_script():
1151
+ print("Failed to write bot script!")
1152
  sys.exit(1)
1153
 
1154
+ print("🚀 Bot Manager Starting...")
1155
+ print(f"📡 Server: {SERVER_HOST}:{SERVER_PORT}")
1156
+ print(f"🤖 Bots: {', '.join(BOT_NAMES)}")
1157
+ print(f"⏱️ Rotation: {ROTATION_DURATION//60} minutes per bot")
1158
 
 
1159
  initialize_bots()
1160
 
 
1161
  threading.Thread(target=rotation_manager, daemon=True).start()
1162
 
1163
+ print("🌐 Dashboard: http://localhost:7860")
 
1164
  app.run(host='0.0.0.0', port=7860, debug=False, use_reloader=False)