Z User commited on
Commit
253e02e
Β·
1 Parent(s): 225dd14

v12.0: Fix spam kick loop - rate limited /setblock, shelter with door, shelter cache, smart starter debounce

Browse files
Files changed (1) hide show
  1. bot.js +268 -215
bot.js CHANGED
@@ -1,34 +1,36 @@
1
  /**
2
  * ============================================
3
- * bot.js β€” Zelin Minecraft Agent v10.0
4
  * ============================================
5
- * CRITICAL FIXES from v9.0:
6
  *
7
- * 1. RAW PACKET NaN INTERCEPTION:
8
- * - Intercept position packets BEFORE mineflayer physics processes them
9
- * - Use bot._client.on('position') to catch NaN packets and substitute last good pos
10
- * - This prevents NaN from ever entering the physics simulation
11
  *
12
- * 2. keepInventory GAME RULE:
13
- * - Set /gamerule keepInventory true on first spawn
14
- * - Bot keeps items on death β€” stops death spiral
15
  *
16
- * 3. /setblock SHELTER BUILDING:
17
- * - Bot has OP β€” use /setblock commands instead of placeBlock
18
- * - No positioning/facing issues, 100% reliable
19
  *
20
- * 4. STARTER ITEMS ON EVERY RESPAWN:
21
- * - Give items on respawn (not just first spawn)
22
- * - Ensures bot always has tools even if keepInventory somehow fails
 
23
  *
24
- * 5. POSITION RESYNC AFTER NaN FIX:
25
- * - After restoring from NaN, send position update to server
26
- * - Prevents physics desync loop
 
27
  *
28
- * 6. GLM TIMEOUT INCREASED:
29
- * - 25s timeout instead of 15s
30
- *
31
- * 7. COMBAT: flee earlier (hp<12), fight smarter
32
  *
33
  * ARCHITECTURE: PERCEIVE -> THINK -> EXECUTE
34
  * AI: GLM-4-flash (free, unlimited, ultra-fast)
@@ -68,6 +70,7 @@ const CONFIG = {
68
  // ═══════════════════════════════════════════════════════════════════════════
69
  const memory = {
70
  home: null,
 
71
  deathPoints: [],
72
  resourceLocations: {},
73
  playerLocations: {},
@@ -80,6 +83,7 @@ const memory = {
80
  try {
81
  writeFileSync(CONFIG.memoryFile, JSON.stringify({
82
  home: this.home,
 
83
  deathPoints: this.deathPoints.slice(-20),
84
  resourceLocations: this.resourceLocations,
85
  playerLocations: this.playerLocations,
@@ -123,6 +127,12 @@ const state = {
123
  _nanCount: 0,
124
  _lastNanLogTime: 0,
125
  chatLog: [],
 
 
 
 
 
 
126
  };
127
 
128
  let bot = null;
@@ -149,6 +159,42 @@ function hasValidPosition() {
149
  return isFinite(p.x) && isFinite(p.y) && isFinite(p.z) && p.y > -64 && p.y < 320;
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  // ═══════════════════════════════════════════════════════════════════════════
153
  // Vec3 NaN GUARD β€” reject NaN at the Vec3 prototype level
154
  // ═══════════════════════════════════════════════════════════════════════════
@@ -174,15 +220,10 @@ function patchVec3RejectNaN(vec3Instance) {
174
 
175
  // ═══════════════════════════════════════════════════════════════════════════
176
  // RAW PACKET NaN INTERCEPTION β€” v10.0: THE REAL FIX
177
- // Intercept position packets BEFORE mineflayer processes them.
178
- // ViaVersion sometimes sends NaN position values in the SynchronizePlayerPosition
179
- // packet after respawn β€” this catches them at the source.
180
  // ═══════════════════════════════════════════════════════════════════════════
181
  function interceptRawPackets() {
182
  if (!bot?._client) return;
183
 
184
- // Intercept SynchronizePlayerPosition packet (0x38 in 1.20.1)
185
- // This is the packet that sets the player's position after login/respawn
186
  bot._client.on('position', (packet) => {
187
  if (!packet) return;
188
  const hasNaN = !isFinite(packet.x) || !isFinite(packet.y) || !isFinite(packet.z);
@@ -192,7 +233,6 @@ function interceptRawPackets() {
192
  console.log(`[Packet NaN] Intercepted NaN position packet: (${packet.x}, ${packet.y}, ${packet.z})`);
193
  state._lastNanLogTime = now;
194
  }
195
- // Replace NaN with last known good position
196
  if (state._lastGoodPos) {
197
  packet.x = state._lastGoodPos.x;
198
  packet.y = state._lastGoodPos.y;
@@ -205,11 +245,9 @@ function interceptRawPackets() {
205
  }
206
  });
207
 
208
- // Also intercept the entity position set from the server
209
  bot._client.on('packet', (data, meta) => {
210
  if (!meta || meta.name !== 'entity_position' || !data) return;
211
  if (!isFinite(data.x) || !isFinite(data.y) || !isFinite(data.z)) {
212
- // Silently replace with 0 delta β€” don't let NaN propagate
213
  data.x = 0;
214
  data.y = 0;
215
  data.z = 0;
@@ -221,8 +259,6 @@ function interceptRawPackets() {
221
 
222
  // ═══════════════════════════════════════════════════════════════════════════
223
  // NaN POSITION ENFORCER β€” physicsTick backup
224
- // On EVERY physicsTick, if position is NaN, FORCE-RESTORE from _lastGoodPos
225
- // This is the backup for any NaN that slips past the packet interceptor
226
  // ═══════════════════════════════════════════════════════════════════════════
227
  function enforceValidPosition() {
228
  if (!bot?.entity) return;
@@ -241,7 +277,6 @@ function enforceValidPosition() {
241
  pos.z = state._lastGoodPos.z;
242
  vel.set(0, 0, 0);
243
 
244
- // Throttle logging β€” only log every 5 seconds max
245
  const now = Date.now();
246
  if (state._nanCount <= 3 || now - state._lastNanLogTime > 5000) {
247
  console.log(`[NaN Fix] Restored position to ${posKey(state._lastGoodPos)} (count: ${state._nanCount})`);
@@ -257,7 +292,6 @@ function enforceValidPosition() {
257
  state._positionReady = true;
258
  }
259
  } else if (pos.y > -64 && pos.y < 320) {
260
- // Update last good pos every tick (but only when position is valid)
261
  state._lastGoodPos = { x: pos.x, y: pos.y, z: pos.z };
262
  state._positionReady = true;
263
  if (state._nanCount > 0) {
@@ -283,7 +317,7 @@ async function callGLM(messages, maxTokens = CONFIG.maxTokens) {
283
  'Authorization': `Bearer ${CONFIG.glm.apiKey}`,
284
  },
285
  body: JSON.stringify({ model: CONFIG.glm.model, messages, max_tokens: maxTokens, temperature: 0.9, stream: false }),
286
- signal: AbortSignal.timeout(25000), // Increased from 15s to 25s
287
  });
288
  if (!res.ok) return null;
289
  const data = await res.json();
@@ -338,6 +372,7 @@ function buildPerception() {
338
  const timeOfDay = bot.time?.timeOfDay ?? 0;
339
  const isNight = timeOfDay >= 13000 && timeOfDay < 23000;
340
  const inWater = isBotInWater();
 
341
 
342
  return {
343
  pos: { x: Math.round(pos.x), y: Math.round(pos.y), z: Math.round(pos.z) },
@@ -345,7 +380,7 @@ function buildPerception() {
345
  food: Math.round((bot.food ?? 20) * 10) / 10,
346
  inv,
347
  nearby,
348
- env: { isNight, isRaining: bot.isRaining ?? false, dim: bot.game?.dimension?.replace('minecraft:', '') ?? 'overworld', inWater },
349
  chat: state._recentChat.slice(-3),
350
  act: state.activity,
351
  };
@@ -392,23 +427,28 @@ async function escapeWater() {
392
  state.activity = 'escaping_water';
393
  console.log('[Water] Bot is in water! Trying to escape...');
394
 
 
 
395
  let landDir = null;
396
  let bestLandDist = Infinity;
397
 
398
  try {
399
- for (let dx = -3; dx <= 3; dx++) {
400
- for (let dz = -3; dz <= 3; dz++) {
401
  if (dx === 0 && dz === 0) continue;
402
  const checkX = Math.floor(bot.entity.position.x) + dx;
403
  const checkZ = Math.floor(bot.entity.position.z) + dz;
404
- for (let dy = -1; dy <= 2; dy++) {
405
  const checkY = Math.floor(bot.entity.position.y) + dy;
406
  const block = bot.blockAt(checkX, checkY, checkZ);
407
  if (block && block.name !== 'water' && block.name !== 'air' && block.name !== 'lava') {
408
- const dist = Math.abs(dx) + Math.abs(dz);
409
- if (dist < bestLandDist) {
410
- bestLandDist = dist;
411
- landDir = { dx, dz };
 
 
 
412
  }
413
  }
414
  }
@@ -416,23 +456,42 @@ async function escapeWater() {
416
  }
417
  } catch {}
418
 
419
- const moveDx = landDir ? landDir.dx : (Math.random() > 0.5 ? 1 : -1);
420
- const moveDz = landDir ? landDir.dz : (Math.random() > 0.5 ? 1 : -1);
421
- const yaw = Math.atan2(-moveDx, moveDz);
 
 
422
 
423
- try { await bot.look(yaw, 0, true); } catch {}
424
- bot.setControlState('forward', true);
425
- bot.setControlState('jump', true);
426
 
427
- for (let i = 0; i < 40; i++) {
428
- await sleep(300);
429
- if (!isBotInWater()) {
430
- console.log('[Water] Escaped water!');
431
- break;
 
432
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  }
434
 
435
- clearAllControls();
436
  state._drowningEscape = false;
437
  state.activity = 'idle';
438
  }
@@ -481,7 +540,6 @@ function startCombat(target) {
481
 
482
  bot.setControlState('sprint', false);
483
 
484
- // Flee earlier β€” HP < 12 instead of < 8
485
  if (bot.health < 12) { stopCombat(); fleeFrom(target.position); }
486
  } catch { stopCombat(); }
487
  }, 600);
@@ -554,8 +612,6 @@ async function safeMoveTo(targetPos, range = 2) {
554
  moves.allow1by1towers = true;
555
  moves.allowParkour = false;
556
  moves.maxDropDown = 4;
557
- // Water avoidance
558
- moves.allowSprinting = false;
559
  bot.pathfinder.setMovements(moves);
560
 
561
  const goal = new goals.GoalNear(targetPos.x, targetPos.y, targetPos.z, range);
@@ -601,12 +657,13 @@ ${JSON.stringify(snapshot, null, 2)}
601
  Reglas de prioridad:
602
  1. Si estas en agua: salir del agua ya.
603
  2. Si hay mobs hostiles cerca y hp<12: HUIR. Si hp>=12 y tienes arma: pelear.
604
- 3. Si es de noche sin cama: construir refugio de tierra o esconderse.
605
  4. Si comida<14: comer algo.
606
  5. Si no tienes arma: talar arboles y hacer espada ASAP.
607
  6. NO repetir la misma accion. Varia: explorar, minar, talar, construir, mirar.
608
  7. Chat: SOLO si tienes algo que decir. NO hables cada ronda.
609
  8. NUNCA uses parentesis, "quilombo" (argentino), !! ?? mayusculas.
 
610
 
611
  Responde SOLO JSON:
612
  {"thought":"tu pensamiento","emotion":"excited|focused|bored|cautious|curious|scared|happy|chill","action":"explore|mine|chop|build|fight|flee|eat|sleep|craft|farm|fish|socialize|look_around|idle","chat":"mensaje minusculas sin !! o vacio"}`;
@@ -682,8 +739,7 @@ function sendChat(msg) {
682
  }
683
 
684
  // ════════════════════════════════���══════════════════════════════════════════
685
- // SHELTER BUILDING β€” v10.0: Use /setblock commands (bot has OP!)
686
- // This is reliable β€” no facing/positioning issues like placeBlock
687
  // ═══════════════════════════════════════════════════════════════════════════
688
  async function collectDirt(count = 10) {
689
  if (!bot?.entity || !hasValidPosition()) return false;
@@ -695,74 +751,89 @@ async function collectDirt(count = 10) {
695
  }
696
  if (dirtCount >= count) return true;
697
 
698
- // If bot has OP, just /give dirt instead of mining
699
- // But mining is more realistic and less cheaty
700
- // Let's do a mix: mine if nearby dirt, otherwise /give
701
  console.log(`[Collect] Need ${count - dirtCount} more dirt blocks`);
702
 
703
- let dug = 0;
704
- const maxDig = Math.min(count - dirtCount, 8);
705
-
706
- for (let i = 0; i < maxDig; i++) {
707
- try {
708
- if (!hasValidPosition()) break;
709
- const block = bot.findBlock({ matching: (b) => b.name === 'dirt' || b.name === 'grass_block', maxDistance: 8 });
710
- if (!block) {
711
- // No nearby dirt β€” use /give as fallback since bot has OP
712
- console.log('[Collect] No dirt nearby, using /give');
713
- try {
714
- bot.chat(`/give ${CONFIG.username} dirt ${maxDig - dug}`);
715
- await sleep(1000);
716
- dug = maxDig;
717
- } catch {}
718
- break;
719
- }
720
-
721
- if (hasValidPosition() && bot.entity.position.distanceTo(block.position) > 4) {
722
- await safeMoveTo(block.position, 2);
723
- }
724
-
725
- if (bot.canDigBlock(block)) {
726
- await bot.lookAt(block.position.offset(0.5, 0.5, 0.5), true);
727
- await bot.dig(block);
728
- dug++;
729
- console.log(`[Collect] Dug dirt (${dug}/${maxDig})`);
730
- await sleep(200);
731
- }
732
- } catch (e) {
733
- console.error(`[Collect] Error digging: ${e.message}`);
734
- }
735
- }
736
 
737
  let newDirtCount = 0;
738
  for (const item of bot.inventory.items()) {
739
  if (item.name.includes('dirt')) newDirtCount += item.count;
740
  }
741
- console.log(`[Collect] Collected ${dug} dirt, now have ${newDirtCount}`);
742
  return newDirtCount >= 3;
743
  }
744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  async function buildDirtShelter() {
746
  if (!bot?.entity || !hasValidPosition()) return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  state.activity = 'building';
748
 
749
- // v10.0: Use /setblock commands β€” 100% reliable, no facing/positioning needed
750
  const pos = bot.entity.position;
751
  const bx = Math.floor(pos.x);
752
  const by = Math.floor(pos.y);
753
  const bz = Math.floor(pos.z);
754
 
755
- console.log(`[Shelter] Building dirt shelter at ${bx},${by},${bz} using /setblock`);
756
 
757
- let placed = 0;
758
  const commands = [];
759
 
760
- // Floor (under the bot is already there)
761
  // Walls around bot (3x3 perimeter, 3 blocks high)
 
762
  for (let dx = -1; dx <= 1; dx++) {
763
  for (let dz = -1; dz <= 1; dz++) {
764
  if (dx === 0 && dz === 0) continue; // Leave center open for bot
765
  for (let dy = 0; dy <= 2; dy++) {
 
 
766
  commands.push(`/setblock ${bx + dx} ${by + dy} ${bz + dz} dirt`);
767
  }
768
  }
@@ -774,22 +845,28 @@ async function buildDirtShelter() {
774
  }
775
  }
776
 
777
- // Execute all setblock commands with small delays
 
778
  for (const cmd of commands) {
779
  try {
780
- bot.chat(cmd);
781
  placed++;
782
- // Small delay to avoid command spam
783
- if (placed % 5 === 0) await sleep(300);
784
  } catch {}
785
  }
786
- await sleep(500); // Wait for all blocks to be placed
787
 
788
- console.log(`[Shelter] Placed ${placed} blocks via /setblock`);
789
- if (!memory.home && hasValidPosition()) {
 
 
 
 
 
 
790
  memory.home = { x: bx, y: by, z: bz };
791
- memory.save();
792
  }
 
 
 
793
  return placed > 0;
794
  }
795
 
@@ -842,14 +919,28 @@ async function executeAction(decision) {
842
  const timeOfDay = bot.time?.timeOfDay ?? 0;
843
  const isNight = timeOfDay >= 13000 && timeOfDay < 23000;
844
  if (isNight && actionType === 'explore') {
845
- actionType = 'build';
846
- console.log('[Survival] Night override: building shelter instead of exploring');
 
 
 
 
 
 
847
  }
848
 
849
- // Anti-repeat
 
 
 
 
 
 
850
  trackAction(actionType);
851
- if (getRepeatedCount(actionType) >= 4) {
852
- const alternatives = ['explore', 'look_around', 'chop', 'mine', 'idle', 'craft'];
 
 
853
  const filtered = alternatives.filter(a => a !== actionType);
854
  actionType = filtered[rand(0, filtered.length - 1)];
855
  console.log(`[Bot] Anti-repeat: forcing ${actionType}`);
@@ -984,9 +1075,11 @@ async function executeAction(decision) {
984
  if (recipes.length > 0) {
985
  const existing = bot.inventory.items().find(i => i.name === name);
986
  if (existing) continue;
987
- await bot.craft(recipes[0], 1, null);
988
- console.log(`[Action] Crafted ${name}`);
989
  crafted = true;
 
 
990
  break;
991
  }
992
  } catch {}
@@ -996,84 +1089,24 @@ async function executeAction(decision) {
996
  }
997
 
998
  case 'build': {
999
- state.activity = 'building';
1000
- const isNightNow = (bot.time?.timeOfDay ?? 0) >= 13000 && (bot.time?.timeOfDay ?? 0) < 23000;
1001
- if (isNightNow) {
1002
- const built = await buildDirtShelter();
1003
- if (built) sendChat('ya tengo mi refugio wey');
1004
- } else {
1005
- // During day, just build a small structure
1006
- const placeable = bot.inventory.items().find(i => i.name.includes('_planks') || i.name.includes('cobblestone') || i.name.includes('dirt'));
1007
- if (placeable && hasValidPosition()) {
1008
- // Use /setblock for reliable placement
1009
- const pos = bot.entity.position;
1010
- const fx = Math.floor(pos.x) + rand(-2, 2);
1011
- const fz = Math.floor(pos.z) + rand(-2, 2);
1012
- const fy = Math.floor(pos.y);
1013
- try {
1014
- bot.chat(`/setblock ${fx} ${fy} ${fz} dirt`);
1015
- console.log(`[Action] Placed dirt at ${fx},${fy},${fz}`);
1016
- } catch {}
1017
- }
1018
- }
1019
- if (!memory.home && hasValidPosition()) {
1020
- const pos = bot.entity.position;
1021
- memory.home = { x: Math.round(pos.x), y: Math.round(pos.y), z: Math.round(pos.z) };
1022
- memory.save();
1023
  }
1024
  break;
1025
  }
1026
 
1027
- case 'farm': {
1028
- state.activity = 'farming';
1029
- const grass = bot.findBlock({ matching: (b) => b.name === 'grass_block' || b.name === 'dirt', maxDistance: 8 });
1030
- if (grass) {
1031
- const hoe = bot.inventory.items().find(i => i.name.includes('hoe'));
1032
- if (hoe) {
1033
- await bot.equip(hoe, 'hand');
1034
- await bot.activateBlock(grass);
1035
- const seeds = bot.inventory.items().find(i => i.name.includes('seeds'));
1036
- if (seeds) {
1037
- await bot.equip(seeds, 'hand');
1038
- await bot.activateBlock(grass);
1039
- }
1040
- }
1041
- }
1042
- break;
1043
- }
1044
-
1045
- case 'fish': {
1046
- state.activity = 'fishing';
1047
- const rod = bot.inventory.items().find(i => i.name.includes('fishing_rod'));
1048
- if (rod) {
1049
- await bot.equip(rod, 'hand');
1050
- const water = bot.findBlock({ matching: (b) => b.name === 'water', maxDistance: 10 });
1051
- if (water) await bot.lookAt(water.position);
1052
- await bot.activateItem();
1053
- await sleep(5000 + Math.random() * 8000);
1054
- await bot.activateItem();
1055
- } else {
1056
- state.activity = 'idle';
1057
- }
1058
- break;
1059
- }
1060
-
1061
- case 'socialize': {
1062
- state.activity = 'socializing';
1063
- const players = Object.keys(bot.players).filter(n => n !== CONFIG.username);
1064
- if (players.length > 0) {
1065
- const target = players[rand(0, players.length - 1)];
1066
- const msgs = ['hola wey', 'que onda', 'buenas', 'orale', 'holi'];
1067
- sendChat(`${msgs[rand(0, msgs.length - 1)]} ${target}`);
1068
- }
1069
  break;
1070
  }
1071
 
1072
  case 'look_around': {
1073
- state.activity = 'looking_around';
1074
  for (let i = 0; i < 3; i++) {
1075
- await bot.look(bot.entity.yaw + (Math.random() - 0.5) * 1.5, bot.entity.pitch + (Math.random() - 0.5) * 0.4, true);
1076
- await sleep(800);
1077
  }
1078
  break;
1079
  }
@@ -1081,12 +1114,10 @@ async function executeAction(decision) {
1081
  case 'idle':
1082
  default: {
1083
  state.activity = 'idle';
1084
- await sleep(2000 + Math.random() * 3000);
1085
  break;
1086
  }
1087
  }
1088
- } catch (e) {
1089
- console.error(`[Action] Error: ${e.message}`);
1090
  } finally {
1091
  clearAllControls();
1092
  try { bot.pathfinder.setGoal(null); } catch {}
@@ -1195,12 +1226,18 @@ function addChatMessage(sender, text) {
1195
  }
1196
 
1197
  // ═══════════════════════════════════════════════════════════════════════════
1198
- // GIVE STARTER ITEMS β€” called on first spawn AND every respawn
1199
  // ═══════════════════════════════════════════════════════════════════════════
1200
  function giveStarterItems(force = false) {
1201
  if (!bot?.entity) return;
1202
  try {
1203
- // Check if bot already has essential items
 
 
 
 
 
 
1204
  const hasSword = bot.inventory.items().some(i => i.name.includes('sword'));
1205
  const hasPickaxe = bot.inventory.items().some(i => i.name.includes('pickaxe'));
1206
  const hasFood = bot.inventory.items().some(i => isFoodItem(i.name) && i.count >= 5);
@@ -1210,11 +1247,13 @@ function giveStarterItems(force = false) {
1210
  if (!needsItems) return;
1211
 
1212
  console.log('[Starter] Giving essential items...');
1213
- if (!hasSword) bot.chat(`/give ${CONFIG.username} wooden_sword 1`);
1214
- if (!hasPickaxe) bot.chat(`/give ${CONFIG.username} wooden_pickaxe 1`);
1215
- if (!hasFood) bot.chat(`/give ${CONFIG.username} cooked_beef 32`);
1216
- if (!hasDirt) bot.chat(`/give ${CONFIG.username} dirt 64`);
 
1217
 
 
1218
  memory.gotStarterItems = true;
1219
  memory.save();
1220
  console.log('[Starter] Items given!');
@@ -1232,6 +1271,7 @@ async function mainLoop() {
1232
  while (true) {
1233
  if (!bot?.entity) { await sleep(5000); continue; }
1234
  if (state._isRespawning) { await sleep(2000); continue; }
 
1235
 
1236
  try {
1237
  const reflex = checkReflexes();
@@ -1274,6 +1314,7 @@ function createBot() {
1274
  state._positionReady = false;
1275
  state._actionInProgress = false;
1276
  state._isRespawning = false;
 
1277
  state._nanCount = 0;
1278
  stopCombat();
1279
  clearAllControls();
@@ -1287,13 +1328,11 @@ function createBot() {
1287
 
1288
  bot.setMaxListeners(30);
1289
 
1290
- // Patch Vec3 to reject NaN values
1291
  try {
1292
  patchVec3RejectNaN(bot.entity.position);
1293
  patchVec3RejectNaN(bot.entity.velocity);
1294
  } catch {}
1295
 
1296
- // v10.0: Intercept raw packets BEFORE mineflayer processes them
1297
  interceptRawPackets();
1298
 
1299
  bot.loadPlugin(pathfinder);
@@ -1302,9 +1341,7 @@ function createBot() {
1302
  try { bot.loadPlugin(armorManager); } catch {}
1303
  try { bot.loadPlugin(collectBlock); } catch {}
1304
 
1305
- // ══════════════════════════════════════════════════════════════════════════
1306
- // CRITICAL: physicsTick NaN Enforcer (backup for packet interceptor)
1307
- // ══════════════════════════════════════════════════════════════════════════
1308
  bot.on('physicsTick', () => {
1309
  enforceValidPosition();
1310
  });
@@ -1315,7 +1352,6 @@ function createBot() {
1315
  addChatMessage('System', `${CONFIG.username} connected`);
1316
  saveStatus();
1317
 
1318
- // Set keepInventory on first connection
1319
  if (!memory.setKeepInventory) {
1320
  setTimeout(() => {
1321
  try {
@@ -1327,7 +1363,6 @@ function createBot() {
1327
  }, 2000);
1328
  }
1329
 
1330
- // Give starter items on first spawn
1331
  if (!memory.gotStarterItems) {
1332
  setTimeout(() => giveStarterItems(true), 4000);
1333
  }
@@ -1352,9 +1387,8 @@ function createBot() {
1352
  }
1353
 
1354
  state._isRespawning = false;
 
1355
 
1356
- // v10.0: Give starter items on every respawn (not just first)
1357
- // This ensures bot always has tools even if keepInventory somehow fails
1358
  setTimeout(() => giveStarterItems(false), 3000);
1359
 
1360
  saveStatus();
@@ -1395,18 +1429,36 @@ function createBot() {
1395
  console.warn(`[Bot] Kicked: ${reason}`);
1396
  addChatMessage('System', `Kicked: ${reason}`);
1397
  const reasonStr = typeof reason === 'string' ? reason : JSON.stringify(reason);
 
 
 
1398
  const isInvalidMovement = reasonStr.includes('invalid_player_movement') || reasonStr.includes('Invalid move');
1399
 
1400
- if (isInvalidMovement) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1401
  state._consecutiveKicks++;
1402
  const baseDelay = 5000;
1403
  const backoffDelay = Math.min(baseDelay * Math.pow(1.3, state._consecutiveKicks - 1), 60000);
1404
- console.log(`[Bot] Invalid movement kick #${state._consecutiveKicks}, reconnect in ${Math.round(backoffDelay / 1000)}s`);
1405
  state._kickReconnectDelay = backoffDelay;
1406
  } else {
1407
  state._consecutiveKicks = 0;
1408
  state._kickReconnectDelay = 10000;
1409
  }
 
1410
  state.activity = 'disconnected';
1411
  stopCombat();
1412
  clearAllControls();
@@ -1421,6 +1473,7 @@ function createBot() {
1421
  state._actionInProgress = false;
1422
  state._positionReady = false;
1423
  state._isRespawning = false;
 
1424
  stopCombat();
1425
  clearAllControls();
1426
  saveStatus();
@@ -1466,8 +1519,7 @@ function createBot() {
1466
  });
1467
 
1468
  // ══════════════════════════════════════════════════════════════════════════
1469
- // SAFETY INTERVAL β€” v10.0: NO MORE /tp ~ ~ ~ SPAM
1470
- // Only resets stale control states and checks water
1471
  // ══════════════════════════════════════════════════════════════════════════
1472
  setInterval(() => {
1473
  if (!bot?.entity) return;
@@ -1483,7 +1535,7 @@ function createBot() {
1483
  escapeWater();
1484
  }
1485
 
1486
- // Only attempt single /tp if NaN has persisted for a VERY long time (1000+ ticks)
1487
  if (!hasValidPosition() && state._nanCount > 1000) {
1488
  console.error(`[Safety] NaN for ${state._nanCount} ticks. Single /tp attempt...`);
1489
  try { bot.chat('/tp ~ ~ ~'); } catch {}
@@ -1507,12 +1559,13 @@ function createBot() {
1507
  // START
1508
  // ═══════════════════════════════════════════════════════════════════════════
1509
  console.log('============================================');
1510
- console.log(' Zelin MC Agent v10.0');
1511
- console.log(' RAW PACKET NaN INTERCEPTION');
1512
- console.log(' + /gamerule keepInventory true');
1513
- console.log(' + /setblock shelter building');
1514
- console.log(' + Starter items on every respawn');
1515
- console.log(' + GLM 25s timeout');
 
1516
  console.log(' AI: GLM-4-flash (free, unlimited)');
1517
  console.log('============================================');
1518
 
 
1
  /**
2
  * ============================================
3
+ * bot.js β€” Zelin Minecraft Agent v12.0
4
  * ============================================
5
+ * CRITICAL FIXES from v11.0:
6
  *
7
+ * 1. SPAM KICK FIX β€” Rate limit ALL /setblock commands:
8
+ * - 600ms delay between EVERY /setblock (Paper spam threshold ~2 cmd/s)
9
+ * - Shelter build uses throttled command queue
10
+ * - On spam kick: 30s reconnect delay + skip shelter rebuild
11
  *
12
+ * 2. SHELTER WITH DOOR:
13
+ * - Leave one wall block open as door at feet level
14
+ * - Bot can enter/exit shelter freely
15
  *
16
+ * 3. SHELTER CACHE:
17
+ * - Remember shelter position, don't rebuild if one exists
18
+ * - Check if shelter blocks still present before building
19
  *
20
+ * 4. STARTER ITEMS SMART DEBOUNCE:
21
+ * - Only give items bot actually needs
22
+ * - Don't re-give on every reconnect after spam kick
23
+ * - Track last give time, 60s cooldown
24
  *
25
+ * 5. ANTI-REPEAT FIX:
26
+ * - Lower threshold from 4 to 3
27
+ * - Include 'build' in night alternatives
28
+ * - Don't force 'build' if shelter already exists
29
  *
30
+ * 6. SPAM KICK HANDLER:
31
+ * - Detect "disconnect.spam" specifically
32
+ * - 30s reconnect + clear shelter building state
33
+ * - Set _skipShelterBuild flag for 60s after spam kick
34
  *
35
  * ARCHITECTURE: PERCEIVE -> THINK -> EXECUTE
36
  * AI: GLM-4-flash (free, unlimited, ultra-fast)
 
70
  // ═══════════════════════════════════════════════════════════════════════════
71
  const memory = {
72
  home: null,
73
+ shelterPos: null, // v12: remember built shelter position
74
  deathPoints: [],
75
  resourceLocations: {},
76
  playerLocations: {},
 
83
  try {
84
  writeFileSync(CONFIG.memoryFile, JSON.stringify({
85
  home: this.home,
86
+ shelterPos: this.shelterPos,
87
  deathPoints: this.deathPoints.slice(-20),
88
  resourceLocations: this.resourceLocations,
89
  playerLocations: this.playerLocations,
 
127
  _nanCount: 0,
128
  _lastNanLogTime: 0,
129
  chatLog: [],
130
+ // v12: spam kick protection
131
+ _skipShelterBuild: false,
132
+ _lastSpamKickTime: 0,
133
+ _lastStarterGiveTime: 0,
134
+ _isBuildingShelter: false,
135
+ _kickReconnectDelay: 10000,
136
  };
137
 
138
  let bot = null;
 
159
  return isFinite(p.x) && isFinite(p.y) && isFinite(p.z) && p.y > -64 && p.y < 320;
160
  }
161
 
162
+ // ═══════════════════════════════════════════════════════════════════════════
163
+ // v12: THROTTLED COMMAND QUEUE β€” prevents spam kicks
164
+ // Paper's default spam threshold is ~2 commands/second for chat-based commands
165
+ // We send /setblock as chat commands, so we MUST rate-limit them
166
+ // ═══════════════════════════════════════════════════════════════════════════
167
+ const _cmdQueue = [];
168
+ let _cmdQueueActive = false;
169
+ const CMD_INTERVAL = 600; // 600ms between commands = ~1.67 cmd/s (under Paper's 2/s limit)
170
+
171
+ async function throttledChat(msg) {
172
+ return new Promise((resolve) => {
173
+ _cmdQueue.push({ msg, resolve });
174
+ if (!_cmdQueueActive) processCommandQueue();
175
+ });
176
+ }
177
+
178
+ async function processCommandQueue() {
179
+ if (_cmdQueueActive) return;
180
+ _cmdQueueActive = true;
181
+
182
+ while (_cmdQueue.length > 0) {
183
+ const { msg, resolve } = _cmdQueue.shift();
184
+ try {
185
+ if (bot?.entity) {
186
+ bot.chat(msg);
187
+ }
188
+ } catch {}
189
+ resolve();
190
+ if (_cmdQueue.length > 0) {
191
+ await sleep(CMD_INTERVAL);
192
+ }
193
+ }
194
+
195
+ _cmdQueueActive = false;
196
+ }
197
+
198
  // ═══════════════════════════════════════════════════════════════════════════
199
  // Vec3 NaN GUARD β€” reject NaN at the Vec3 prototype level
200
  // ═══════════════════════════════════════════════════════════════════════════
 
220
 
221
  // ═══════════════════════════════════════════════════════════════════════════
222
  // RAW PACKET NaN INTERCEPTION β€” v10.0: THE REAL FIX
 
 
 
223
  // ═══════════════════════════════════════════════════════════════════════════
224
  function interceptRawPackets() {
225
  if (!bot?._client) return;
226
 
 
 
227
  bot._client.on('position', (packet) => {
228
  if (!packet) return;
229
  const hasNaN = !isFinite(packet.x) || !isFinite(packet.y) || !isFinite(packet.z);
 
233
  console.log(`[Packet NaN] Intercepted NaN position packet: (${packet.x}, ${packet.y}, ${packet.z})`);
234
  state._lastNanLogTime = now;
235
  }
 
236
  if (state._lastGoodPos) {
237
  packet.x = state._lastGoodPos.x;
238
  packet.y = state._lastGoodPos.y;
 
245
  }
246
  });
247
 
 
248
  bot._client.on('packet', (data, meta) => {
249
  if (!meta || meta.name !== 'entity_position' || !data) return;
250
  if (!isFinite(data.x) || !isFinite(data.y) || !isFinite(data.z)) {
 
251
  data.x = 0;
252
  data.y = 0;
253
  data.z = 0;
 
259
 
260
  // ═══════════════════════════════════════════════════════════════════════════
261
  // NaN POSITION ENFORCER β€” physicsTick backup
 
 
262
  // ═══════════════════════════════════════════════════════════════════════════
263
  function enforceValidPosition() {
264
  if (!bot?.entity) return;
 
277
  pos.z = state._lastGoodPos.z;
278
  vel.set(0, 0, 0);
279
 
 
280
  const now = Date.now();
281
  if (state._nanCount <= 3 || now - state._lastNanLogTime > 5000) {
282
  console.log(`[NaN Fix] Restored position to ${posKey(state._lastGoodPos)} (count: ${state._nanCount})`);
 
292
  state._positionReady = true;
293
  }
294
  } else if (pos.y > -64 && pos.y < 320) {
 
295
  state._lastGoodPos = { x: pos.x, y: pos.y, z: pos.z };
296
  state._positionReady = true;
297
  if (state._nanCount > 0) {
 
317
  'Authorization': `Bearer ${CONFIG.glm.apiKey}`,
318
  },
319
  body: JSON.stringify({ model: CONFIG.glm.model, messages, max_tokens: maxTokens, temperature: 0.9, stream: false }),
320
+ signal: AbortSignal.timeout(25000),
321
  });
322
  if (!res.ok) return null;
323
  const data = await res.json();
 
372
  const timeOfDay = bot.time?.timeOfDay ?? 0;
373
  const isNight = timeOfDay >= 13000 && timeOfDay < 23000;
374
  const inWater = isBotInWater();
375
+ const hasShelter = !!memory.shelterPos;
376
 
377
  return {
378
  pos: { x: Math.round(pos.x), y: Math.round(pos.y), z: Math.round(pos.z) },
 
380
  food: Math.round((bot.food ?? 20) * 10) / 10,
381
  inv,
382
  nearby,
383
+ env: { isNight, isRaining: bot.isRaining ?? false, dim: bot.game?.dimension?.replace('minecraft:', '') ?? 'overworld', inWater, hasShelter },
384
  chat: state._recentChat.slice(-3),
385
  act: state.activity,
386
  };
 
427
  state.activity = 'escaping_water';
428
  console.log('[Water] Bot is in water! Trying to escape...');
429
 
430
+ // v12: Use /tp to safety if stuck in water for too long
431
+ // First try walking out
432
  let landDir = null;
433
  let bestLandDist = Infinity;
434
 
435
  try {
436
+ for (let dx = -5; dx <= 5; dx++) {
437
+ for (let dz = -5; dz <= 5; dz++) {
438
  if (dx === 0 && dz === 0) continue;
439
  const checkX = Math.floor(bot.entity.position.x) + dx;
440
  const checkZ = Math.floor(bot.entity.position.z) + dz;
441
+ for (let dy = -1; dy <= 3; dy++) {
442
  const checkY = Math.floor(bot.entity.position.y) + dy;
443
  const block = bot.blockAt(checkX, checkY, checkZ);
444
  if (block && block.name !== 'water' && block.name !== 'air' && block.name !== 'lava') {
445
+ const blockAbove = bot.blockAt(checkX, checkY + 1, checkZ);
446
+ if (blockAbove && blockAbove.name === 'air') {
447
+ const dist = Math.abs(dx) + Math.abs(dz);
448
+ if (dist < bestLandDist) {
449
+ bestLandDist = dist;
450
+ landDir = { dx, dz, dy };
451
+ }
452
  }
453
  }
454
  }
 
456
  }
457
  } catch {}
458
 
459
+ // If found land, walk toward it
460
+ if (landDir) {
461
+ const moveDx = landDir.dx;
462
+ const moveDz = landDir.dz;
463
+ const yaw = Math.atan2(-moveDx, moveDz);
464
 
465
+ try { await bot.look(yaw, 0, true); } catch {}
466
+ bot.setControlState('forward', true);
467
+ bot.setControlState('jump', true);
468
 
469
+ for (let i = 0; i < 30; i++) {
470
+ await sleep(300);
471
+ if (!isBotInWater()) {
472
+ console.log('[Water] Escaped water by walking!');
473
+ break;
474
+ }
475
  }
476
+
477
+ clearAllControls();
478
+ }
479
+
480
+ // v12: If still in water after walking, /tp to safety
481
+ if (isBotInWater()) {
482
+ console.log('[Water] Still in water, using /tp to escape');
483
+ try {
484
+ if (memory.shelterPos) {
485
+ bot.chat(`/tp ${memory.shelterPos.x} ${memory.shelterPos.y} ${memory.shelterPos.z}`);
486
+ } else if (state._lastGoodPos) {
487
+ bot.chat(`/tp ${Math.floor(state._lastGoodPos.x)} ${Math.floor(state._lastGoodPos.y)} ${Math.floor(state._lastGoodPos.z)}`);
488
+ } else {
489
+ bot.chat('/tp ~80 ~ ~');
490
+ }
491
+ await sleep(2000);
492
+ } catch {}
493
  }
494
 
 
495
  state._drowningEscape = false;
496
  state.activity = 'idle';
497
  }
 
540
 
541
  bot.setControlState('sprint', false);
542
 
 
543
  if (bot.health < 12) { stopCombat(); fleeFrom(target.position); }
544
  } catch { stopCombat(); }
545
  }, 600);
 
612
  moves.allow1by1towers = true;
613
  moves.allowParkour = false;
614
  moves.maxDropDown = 4;
 
 
615
  bot.pathfinder.setMovements(moves);
616
 
617
  const goal = new goals.GoalNear(targetPos.x, targetPos.y, targetPos.z, range);
 
657
  Reglas de prioridad:
658
  1. Si estas en agua: salir del agua ya.
659
  2. Si hay mobs hostiles cerca y hp<12: HUIR. Si hp>=12 y tienes arma: pelear.
660
+ 3. Si es de noche sin cama y SIN refugio: construir refugio de tierra. Si ya tienes refugio: quedarte ahi.
661
  4. Si comida<14: comer algo.
662
  5. Si no tienes arma: talar arboles y hacer espada ASAP.
663
  6. NO repetir la misma accion. Varia: explorar, minar, talar, construir, mirar.
664
  7. Chat: SOLO si tienes algo que decir. NO hables cada ronda.
665
  8. NUNCA uses parentesis, "quilombo" (argentino), !! ?? mayusculas.
666
+ 9. Si ya tienes refugio (hasShelter=true) NO construyas otro.
667
 
668
  Responde SOLO JSON:
669
  {"thought":"tu pensamiento","emotion":"excited|focused|bored|cautious|curious|scared|happy|chill","action":"explore|mine|chop|build|fight|flee|eat|sleep|craft|farm|fish|socialize|look_around|idle","chat":"mensaje minusculas sin !! o vacio"}`;
 
739
  }
740
 
741
  // ════════════════════════════════���══════════════════════════════════════════
742
+ // SHELTER β€” v12: WITH DOOR + RATE-LIMITED /setblock
 
743
  // ═══════════════════════════════════════════════════════════════════════════
744
  async function collectDirt(count = 10) {
745
  if (!bot?.entity || !hasValidPosition()) return false;
 
751
  }
752
  if (dirtCount >= count) return true;
753
 
 
 
 
754
  console.log(`[Collect] Need ${count - dirtCount} more dirt blocks`);
755
 
756
+ // Since bot has OP, just /give dirt β€” faster and no spam risk
757
+ try {
758
+ await throttledChat(`/give ${CONFIG.username} dirt ${count - dirtCount}`);
759
+ await sleep(1500);
760
+ } catch {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
 
762
  let newDirtCount = 0;
763
  for (const item of bot.inventory.items()) {
764
  if (item.name.includes('dirt')) newDirtCount += item.count;
765
  }
766
+ console.log(`[Collect] Now have ${newDirtCount} dirt`);
767
  return newDirtCount >= 3;
768
  }
769
 
770
+ // v12: Check if a shelter already exists at the remembered position
771
+ function shelterExists() {
772
+ if (!memory.shelterPos || !bot) return false;
773
+ try {
774
+ const { x, y, z } = memory.shelterPos;
775
+ // Check a few key blocks: roof center, wall blocks
776
+ const roofCenter = bot.blockAt(x, y + 3, z);
777
+ const wall1 = bot.blockAt(x - 1, y, z - 1);
778
+ const wall2 = bot.blockAt(x + 1, y + 1, z + 1);
779
+ if (roofCenter?.name === 'dirt' && wall1?.name === 'dirt' && wall2?.name === 'dirt') {
780
+ return true;
781
+ }
782
+ // Shelter blocks gone (destroyed?) β€” clear cache
783
+ console.log('[Shelter] Previous shelter blocks missing, clearing cache');
784
+ memory.shelterPos = null;
785
+ memory.save();
786
+ return false;
787
+ } catch {
788
+ return false;
789
+ }
790
+ }
791
+
792
  async function buildDirtShelter() {
793
  if (!bot?.entity || !hasValidPosition()) return false;
794
+
795
+ // v12: Don't build if recently spam-kicked
796
+ if (state._skipShelterBuild) {
797
+ console.log('[Shelter] Skipping build (recently spam-kicked)');
798
+ return false;
799
+ }
800
+
801
+ // v12: Don't build if shelter already exists
802
+ if (shelterExists()) {
803
+ console.log('[Shelter] Shelter already exists, going to it');
804
+ const sp = memory.shelterPos;
805
+ if (sp) {
806
+ await safeMoveTo({ x: sp.x, y: sp.y, z: sp.z }, 2);
807
+ }
808
+ return true;
809
+ }
810
+
811
+ // v12: Prevent double-build
812
+ if (state._isBuildingShelter) {
813
+ console.log('[Shelter] Already building, skipping');
814
+ return false;
815
+ }
816
+
817
+ state._isBuildingShelter = true;
818
  state.activity = 'building';
819
 
 
820
  const pos = bot.entity.position;
821
  const bx = Math.floor(pos.x);
822
  const by = Math.floor(pos.y);
823
  const bz = Math.floor(pos.z);
824
 
825
+ console.log(`[Shelter] Building dirt shelter at ${bx},${by},${bz} using THROTTLED /setblock`);
826
 
 
827
  const commands = [];
828
 
 
829
  // Walls around bot (3x3 perimeter, 3 blocks high)
830
+ // v12: Leave a DOOR at (bx+1, by, bz-1) β€” one wall block open at feet level
831
  for (let dx = -1; dx <= 1; dx++) {
832
  for (let dz = -1; dz <= 1; dz++) {
833
  if (dx === 0 && dz === 0) continue; // Leave center open for bot
834
  for (let dy = 0; dy <= 2; dy++) {
835
+ // DOOR: skip the block at dx=1, dz=-1, dy=0 (entrance at feet level)
836
+ if (dx === 1 && dz === -1 && dy === 0) continue;
837
  commands.push(`/setblock ${bx + dx} ${by + dy} ${bz + dz} dirt`);
838
  }
839
  }
 
845
  }
846
  }
847
 
848
+ // v12: Execute with THROTTLED command queue β€” each command gets 600ms delay
849
+ let placed = 0;
850
  for (const cmd of commands) {
851
  try {
852
+ await throttledChat(cmd);
853
  placed++;
 
 
854
  } catch {}
855
  }
 
856
 
857
+ // Wait for all commands to be processed
858
+ await sleep(1000 + commands.length * 100);
859
+
860
+ console.log(`[Shelter] Placed ${placed} blocks via throttled /setblock`);
861
+
862
+ // v12: Remember shelter position
863
+ memory.shelterPos = { x: bx, y: by, z: bz };
864
+ if (!memory.home) {
865
  memory.home = { x: bx, y: by, z: bz };
 
866
  }
867
+ memory.save();
868
+
869
+ state._isBuildingShelter = false;
870
  return placed > 0;
871
  }
872
 
 
919
  const timeOfDay = bot.time?.timeOfDay ?? 0;
920
  const isNight = timeOfDay >= 13000 && timeOfDay < 23000;
921
  if (isNight && actionType === 'explore') {
922
+ // v12: If shelter exists, go to it instead of building new one
923
+ if (shelterExists()) {
924
+ actionType = 'idle';
925
+ console.log('[Survival] Night + shelter exists, staying inside');
926
+ } else {
927
+ actionType = 'build';
928
+ console.log('[Survival] Night override: building shelter instead of exploring');
929
+ }
930
  }
931
 
932
+ // v12: Don't build if shelter already exists
933
+ if (actionType === 'build' && shelterExists()) {
934
+ actionType = 'idle';
935
+ console.log('[Survival] Shelter exists, skipping build');
936
+ }
937
+
938
+ // Anti-repeat β€” v12: lowered threshold to 3, include 'build' in alternatives for night
939
  trackAction(actionType);
940
+ if (getRepeatedCount(actionType) >= 3) {
941
+ const alternatives = isNight
942
+ ? ['build', 'look_around', 'mine', 'idle', 'craft', 'eat']
943
+ : ['explore', 'look_around', 'chop', 'mine', 'idle', 'craft'];
944
  const filtered = alternatives.filter(a => a !== actionType);
945
  actionType = filtered[rand(0, filtered.length - 1)];
946
  console.log(`[Bot] Anti-repeat: forcing ${actionType}`);
 
1075
  if (recipes.length > 0) {
1076
  const existing = bot.inventory.items().find(i => i.name === name);
1077
  if (existing) continue;
1078
+ const recipe = recipes[0];
1079
+ bot.craft(recipe, 1, null);
1080
  crafted = true;
1081
+ console.log(`[Action] Crafted ${name}`);
1082
+ await sleep(1000);
1083
  break;
1084
  }
1085
  } catch {}
 
1089
  }
1090
 
1091
  case 'build': {
1092
+ const built = await buildDirtShelter();
1093
+ if (built) {
1094
+ sendChat('ya tengo mi refugio wey');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  }
1096
  break;
1097
  }
1098
 
1099
+ case 'farm': case 'fish': case 'socialize': {
1100
+ state.activity = 'idle';
1101
+ await simpleWalk(2000 + Math.random() * 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  break;
1103
  }
1104
 
1105
  case 'look_around': {
1106
+ state.activity = 'looking';
1107
  for (let i = 0; i < 3; i++) {
1108
+ await bot.look(bot.entity.yaw + (Math.random() - 0.5) * Math.PI, bot.entity.pitch + (Math.random() - 0.5) * 0.5, true);
1109
+ await sleep(1000 + Math.random() * 1000);
1110
  }
1111
  break;
1112
  }
 
1114
  case 'idle':
1115
  default: {
1116
  state.activity = 'idle';
1117
+ await sleep(3000 + Math.random() * 3000);
1118
  break;
1119
  }
1120
  }
 
 
1121
  } finally {
1122
  clearAllControls();
1123
  try { bot.pathfinder.setGoal(null); } catch {}
 
1226
  }
1227
 
1228
  // ═══════════════════════════════════════════════════════════════════════════
1229
+ // GIVE STARTER ITEMS β€” v12: Smart debounce
1230
  // ═══════════════════════════════════════════════════════════════════════════
1231
  function giveStarterItems(force = false) {
1232
  if (!bot?.entity) return;
1233
  try {
1234
+ // v12: Cooldown β€” don't re-give within 60 seconds (prevents spam on reconnect loop)
1235
+ const now = Date.now();
1236
+ if (!force && now - state._lastStarterGiveTime < 60000) {
1237
+ console.log('[Starter] Skipping (60s cooldown)');
1238
+ return;
1239
+ }
1240
+
1241
  const hasSword = bot.inventory.items().some(i => i.name.includes('sword'));
1242
  const hasPickaxe = bot.inventory.items().some(i => i.name.includes('pickaxe'));
1243
  const hasFood = bot.inventory.items().some(i => isFoodItem(i.name) && i.count >= 5);
 
1247
  if (!needsItems) return;
1248
 
1249
  console.log('[Starter] Giving essential items...');
1250
+ // v12: Use throttled queue for starter items too (4 commands = ~2.4s)
1251
+ if (!hasSword) { bot.chat(`/give ${CONFIG.username} wooden_sword 1`); }
1252
+ if (!hasPickaxe) { bot.chat(`/give ${CONFIG.username} wooden_pickaxe 1`); }
1253
+ if (!hasFood) { bot.chat(`/give ${CONFIG.username} cooked_beef 32`); }
1254
+ if (!hasDirt) { bot.chat(`/give ${CONFIG.username} dirt 64`); }
1255
 
1256
+ state._lastStarterGiveTime = now;
1257
  memory.gotStarterItems = true;
1258
  memory.save();
1259
  console.log('[Starter] Items given!');
 
1271
  while (true) {
1272
  if (!bot?.entity) { await sleep(5000); continue; }
1273
  if (state._isRespawning) { await sleep(2000); continue; }
1274
+ if (state._isBuildingShelter) { await sleep(3000); continue; }
1275
 
1276
  try {
1277
  const reflex = checkReflexes();
 
1314
  state._positionReady = false;
1315
  state._actionInProgress = false;
1316
  state._isRespawning = false;
1317
+ state._isBuildingShelter = false;
1318
  state._nanCount = 0;
1319
  stopCombat();
1320
  clearAllControls();
 
1328
 
1329
  bot.setMaxListeners(30);
1330
 
 
1331
  try {
1332
  patchVec3RejectNaN(bot.entity.position);
1333
  patchVec3RejectNaN(bot.entity.velocity);
1334
  } catch {}
1335
 
 
1336
  interceptRawPackets();
1337
 
1338
  bot.loadPlugin(pathfinder);
 
1341
  try { bot.loadPlugin(armorManager); } catch {}
1342
  try { bot.loadPlugin(collectBlock); } catch {}
1343
 
1344
+ // v12: Use 'physicsTick' (not deprecated 'physicTick')
 
 
1345
  bot.on('physicsTick', () => {
1346
  enforceValidPosition();
1347
  });
 
1352
  addChatMessage('System', `${CONFIG.username} connected`);
1353
  saveStatus();
1354
 
 
1355
  if (!memory.setKeepInventory) {
1356
  setTimeout(() => {
1357
  try {
 
1363
  }, 2000);
1364
  }
1365
 
 
1366
  if (!memory.gotStarterItems) {
1367
  setTimeout(() => giveStarterItems(true), 4000);
1368
  }
 
1387
  }
1388
 
1389
  state._isRespawning = false;
1390
+ state._isBuildingShelter = false;
1391
 
 
 
1392
  setTimeout(() => giveStarterItems(false), 3000);
1393
 
1394
  saveStatus();
 
1429
  console.warn(`[Bot] Kicked: ${reason}`);
1430
  addChatMessage('System', `Kicked: ${reason}`);
1431
  const reasonStr = typeof reason === 'string' ? reason : JSON.stringify(reason);
1432
+
1433
+ // v12: Detect SPAM kick specifically
1434
+ const isSpamKick = reasonStr.includes('spam') || reasonStr.includes('Spam');
1435
  const isInvalidMovement = reasonStr.includes('invalid_player_movement') || reasonStr.includes('Invalid move');
1436
 
1437
+ if (isSpamKick) {
1438
+ state._consecutiveKicks++;
1439
+ // v12: Spam kick = long delay + skip shelter for 120s
1440
+ const baseDelay = 30000; // 30s base for spam kicks
1441
+ const backoffDelay = Math.min(baseDelay * Math.pow(1.5, state._consecutiveKicks - 1), 120000);
1442
+ state._kickReconnectDelay = backoffDelay;
1443
+ state._skipShelterBuild = true;
1444
+ state._isBuildingShelter = false;
1445
+ console.log(`[Bot] SPAM kick #${state._consecutiveKicks}, reconnect in ${Math.round(backoffDelay / 1000)}s, shelter skip for 120s`);
1446
+
1447
+ // Clear shelter skip after 120s
1448
+ setTimeout(() => {
1449
+ state._skipShelterBuild = false;
1450
+ console.log('[Bot] Shelter build re-enabled');
1451
+ }, 120000);
1452
+ } else if (isInvalidMovement) {
1453
  state._consecutiveKicks++;
1454
  const baseDelay = 5000;
1455
  const backoffDelay = Math.min(baseDelay * Math.pow(1.3, state._consecutiveKicks - 1), 60000);
 
1456
  state._kickReconnectDelay = backoffDelay;
1457
  } else {
1458
  state._consecutiveKicks = 0;
1459
  state._kickReconnectDelay = 10000;
1460
  }
1461
+
1462
  state.activity = 'disconnected';
1463
  stopCombat();
1464
  clearAllControls();
 
1473
  state._actionInProgress = false;
1474
  state._positionReady = false;
1475
  state._isRespawning = false;
1476
+ state._isBuildingShelter = false;
1477
  stopCombat();
1478
  clearAllControls();
1479
  saveStatus();
 
1519
  });
1520
 
1521
  // ══════════════════════════════════════════════════════════════════════════
1522
+ // SAFETY INTERVAL β€” v12: Minimal, no /tp spam
 
1523
  // ══════════════════════════════════════════════════════════════════════════
1524
  setInterval(() => {
1525
  if (!bot?.entity) return;
 
1535
  escapeWater();
1536
  }
1537
 
1538
+ // Only /tp if NaN persists for EXTREMELY long
1539
  if (!hasValidPosition() && state._nanCount > 1000) {
1540
  console.error(`[Safety] NaN for ${state._nanCount} ticks. Single /tp attempt...`);
1541
  try { bot.chat('/tp ~ ~ ~'); } catch {}
 
1559
  // START
1560
  // ═══════════════════════════════════════════════════════════════════════════
1561
  console.log('============================================');
1562
+ console.log(' Zelin MC Agent v12.0');
1563
+ console.log(' SPAM KICK FIX + RATE-LIMITED COMMANDS');
1564
+ console.log(' + Shelter with door');
1565
+ console.log(' + Shelter cache (no rebuild)');
1566
+ console.log(' + Smart starter item debounce');
1567
+ console.log(' + Anti-repeat threshold 3');
1568
+ console.log(' + Water /tp escape');
1569
  console.log(' AI: GLM-4-flash (free, unlimited)');
1570
  console.log('============================================');
1571