| 'use strict'; |
| const dangerMobs = ['zombie','skeleton','creeper','spider','enderman','witch','phantom','drowned','husk','stray','blaze','piglin','zombified piglin','spider jockey']; |
| const { ACTIONS } = require('./bot-model'); |
| const Vec3 = require('vec3'); |
| const DIR_VEC = [[0,0], [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1]]; |
|
|
| const rand = (a, b) => Math.floor(Math.random() * (b - a + 1)) + a; |
| const sleep = ms => new Promise(r => setTimeout(r, ms)); |
|
|
| class BotBrain { |
| constructor(bot, manager, memory, miner, movement, world, shaper, model) { |
| this.bot = bot; this.manager = manager; |
| this.memory = memory; this.miner = miner; |
| this.movement = movement; this.world = world; |
| this.shaper = shaper || null; |
| this.model = model || null; |
| this._timer = null; this._fleeing = false; |
| this._cmdCD = new Map(); |
| this._state = 'IDLE'; |
| this._idleUntil = 0; |
| this._taskQueue = []; |
| this._surroundings = { ores: [], crops: [], mobs: [], items: [] }; |
| this._lastScanPos = null; |
| this._scanTick = 0; |
| this._craftTick = 0; |
| this._lastDamage = Date.now(); |
| this._lastChatTime = Date.now(); |
| this._homePos = null; |
| this._actionCooldowns = {}; |
| this._actionResults = {}; |
|
|
| |
| this._serverMode = 'normal'; |
| this._tickInterval = 5000; |
|
|
| |
| bot.on('health', () => { |
| if (bot.health < (this._lastHealth || 20)) this._lastDamage = Date.now(); |
| this._lastHealth = bot.health; |
| }); |
| } |
|
|
| start() { |
| const loop = () => { |
| if (!this._timer) return; |
| this._tick(); |
| this._timer = setTimeout(loop, Math.max(2000, this._tickInterval + rand(-1000, 1000))); |
| }; |
| this._timer = setTimeout(loop, Math.max(2000, this._tickInterval + rand(-1000, 1000))); |
| } |
|
|
| stop() { |
| if (this._timer) { clearTimeout(this._timer); this._timer = null; } |
| this.movement.stop(); |
| } |
|
|
| _tick() { |
| if (!this.bot.entity || this.bot.health <= 0) return; |
|
|
| |
| if (Date.now() < this._idleUntil) return; |
| if (this._state !== 'IDLE' && Math.random() < 0.15) { |
| this._setState('IDLE'); |
| this._idleUntil = Date.now() + rand(8000, 25000); |
| return; |
| } |
|
|
| |
| if (this._inDanger()) { |
| if (this._hasWeapon() && this.bot.health > 10 && Math.random() > 0.2) { |
| this._setState('FIGHT'); this._fightNearest(); |
| } else { |
| this._setState('FLEE'); this._flee(); |
| } |
| return; |
| } |
|
|
| |
| if (this.bot.entity.position.y < 0 || this._isOnLava()) { this._jumpOut(); return; } |
|
|
| |
| this._scanTick++; |
| if (this._scanTick % 4 === 0 && Math.random() < 0.5) this._scanSurroundings(); |
|
|
| |
| for (const name of Object.keys(this.bot.players)) { |
| const pl = this.bot.players[name]; |
| if (!pl || !pl.entity || name === this.bot.username) continue; |
| if (this.bot.entity.position.distanceTo(pl.entity.position) > 10) continue; |
| this.memory.see(name); |
| if (this.memory.canGreet(name) && Math.random() < 0.6) { |
| this.memory.markGreeted(name); |
| this.memory.rememberInteraction(name, 'greeted'); |
| const msgs = ['مرحبا ' + name, 'هلا ' + name, 'شلونك ' + name, 'ها ' + name]; |
| this.bot.chat(msgs[rand(0, msgs.length - 1)]); |
| } |
| } |
|
|
| |
| this._craftTick++; |
| if (this._craftTick % 10 === 0 && Math.random() < 0.3) this._autoCraft(); |
|
|
| |
| this._execState(); |
| } |
|
|
| _setState(s) { if (this._state === s) return; this._state = s; } |
|
|
| _execState() { |
| switch (this._state) { |
| case 'FOLLOW': case 'FLEE': case 'FIGHT': case 'MINE': |
| case 'FARM': case 'COLLECT': case 'CRAFT': break; |
| case 'JUMP': case 'SNEAK': case 'SWIM': break; |
| default: this._autoDecide(); break; |
| } |
| } |
|
|
| async _autoDecide() { |
| |
| if (this.model && this.model.loaded && this.model.context.length >= 5) { |
| const state = this.model.extractState(this.bot); |
| if (state) { |
| try { |
| const result = await this.model.predict(state); |
| const actionIdx = typeof result === 'object' ? result.actionIdx : result; |
| const dirIdx = typeof result === 'object' ? result.dirIdx : 0; |
| const actionName = ACTIONS[actionIdx]; |
| if (this._execModelAction(actionName, dirIdx)) { |
| this._actionResults[actionName] = (this._actionResults[actionName] || 0) + 1; |
| return; |
| } |
| this._actionResults.__rejected = (this._actionResults.__rejected || 0) + 1; |
| } catch (_) {} |
| } |
| } |
| |
| this._wander(rand(0, 3)); |
| } |
|
|
| setServerMode(mode) { |
| this._serverMode = mode || 'normal'; |
| switch (this._serverMode) { |
| case 'critical': |
| this._tickInterval = 8000; |
| break; |
| case 'moderate': |
| this._tickInterval = 5000; |
| break; |
| default: |
| this._tickInterval = 3000; |
| break; |
| } |
| } |
|
|
| _execModelAction(action, dirIdx) { |
| |
| const now = Date.now(); |
| const cd = this._actionCooldowns[action] || 0; |
| if (now < cd) return false; |
| this._actionCooldowns[action] = now + 2000; |
|
|
| const s = this._surroundings; |
| switch (action) { |
| |
| case 'COLLECT': |
| if (s.items.length > 0) { this._setState('COLLECT'); this._collectItems(); return true; } |
| break; |
| case 'FIGHT': |
| if (s.mobs.length > 0 && this._hasWeapon()) { this._setState('FIGHT'); this._fightNearest(); return true; } |
| break; |
| case 'MINE': |
| if (s.ores.length > 0) { this._setState('MINE'); this._goMine(); return true; } |
| break; |
| case 'FARM': |
| if (s.crops.length > 0) { this._setState('FARM'); this._goFarm(); return true; } |
| break; |
| case 'EXPLORE': |
| this._explore(dirIdx); return true; |
| case 'FLEE': |
| this._setState('FLEE'); this._flee(dirIdx); return true; |
| case 'CRAFT': |
| this._autoCraft(); return true; |
| case 'CHAT': |
| this._chatRandom(); return true; |
| case 'FOLLOW': |
| this._follow(); return true; |
| case 'IDLE': |
| this._setState('IDLE'); |
| this._idleUntil = Date.now() + rand(8000, 25000); |
| return true; |
|
|
| |
| case 'PLACE_BLOCK': |
| this._placeBlock(); return true; |
| case 'BUILD_SHELTER': |
| this._buildShelter(); return true; |
| case 'LIGHT_AREA': |
| this._lightArea(); return true; |
| case 'EAT': |
| this._eat(); return true; |
| case 'SLEEP': |
| this._sleep(); return true; |
| case 'GO_HOME': |
| this._goHome(); return true; |
| case 'EQUIP_ARMOR': |
| this._equipArmor(); return true; |
| case 'SMELT': |
| this._setState('IDLE'); return true; |
| case 'JUMP': |
| this._doJump(dirIdx); return true; |
| case 'SPRINT': |
| this._sprint(dirIdx); return true; |
| case 'SNEAK': |
| this._doSneak(); return true; |
| case 'SWIM': |
| this._doSwim(); return true; |
| case 'EMOTE': |
| this._doEmote(); return true; |
| case 'WANDER': |
| this._wander(dirIdx); return true; |
| case 'HELP_ALLY': |
| this._helpAlly(); return true; |
| case 'TRADE': |
| this._trade(); return true; |
| case 'LOOK_AROUND': |
| this._lookAround(); return true; |
| |
| case 'BUILD_PATH': |
| this._buildPath(dirIdx); return true; |
| case 'BUILD_BRIDGE': |
| this._buildBridge(dirIdx); return true; |
| case 'BUILD_WALL': |
| this._buildWall(); return true; |
| case 'PLACE_DOOR': |
| this._placeDoor(); return true; |
| case 'PLACE_LADDER': |
| this._placeLadder(dirIdx); return true; |
| case 'MINE_IRON': |
| this._mineIron(); return true; |
| case 'MINE_GOLD': |
| this._mineGold(); return true; |
| case 'MINE_DIAMOND': |
| this._mineDiamond(); return true; |
| case 'MINE_OBSIDIAN': |
| this._mineObsidian(); return true; |
| case 'CRAFT_ENCHANT_TABLE': |
| this._craftEnchantTable(); return true; |
| case 'CRAFT_ANVIL': |
| this._craftAnvil(); return true; |
| case 'CRAFT_BREWING_STAND': |
| this._craftBrewingStand(); return true; |
| case 'CRAFT_BED': |
| this._craftBed(); return true; |
| case 'CRAFT_BOAT': |
| this._craftBoat(); return true; |
| case 'ENTER_NETHER': |
| this._enterNether(); return true; |
| case 'EXPLORE_NETHER': |
| this._exploreNether(dirIdx); return true; |
| case 'FIGHT_BLAZE': |
| this._fightBlaze(); return true; |
| case 'ENTER_END': |
| this._enterEnd(); return true; |
| case 'FIGHT_DRAGON': |
| this._fightDragon(); return true; |
| case 'PLANT_CROP': |
| this._plantCrop(); return true; |
| case 'HARVEST_CROP': |
| this._harvestCrop(); return true; |
| case 'FISH': |
| this._fish(); return true; |
| case 'SHEAR': |
| this._shear(); return true; |
| case 'MILK': |
| this._milk(); return true; |
| case 'SLEEP_AT_VILLAGE': |
| this._sleepAtVillage(); return true; |
| case 'CHAT_GREETING': |
| this._chatGreeting(); return true; |
| case 'CHAT_STATUS': |
| this._chatStatus(); return true; |
| case 'CHAT_THANK': |
| this._chatThank(); return true; |
| case 'CHAT_QUESTION': |
| this._chatQuestion(); return true; |
| case 'CHAT_ALERT': |
| this._chatAlert(); return true; |
| case 'THINK': |
| this._thinkDeep(); return true; |
| } |
| this._explore(); |
| return true; |
| } |
|
|
| |
| |
| |
|
|
| _scanSurroundings() { |
| const pos = this.bot.entity.position; |
| this._surroundings = { ores: [], crops: [], mobs: [], items: [] }; |
| const scanRange = 4; |
| for (let dx = -scanRange; dx <= scanRange; dx++) { |
| for (let dz = -scanRange; dz <= scanRange; dz++) { |
| for (let dy = -2; dy <= 2; dy++) { |
| const bp = pos.offset(dx, dy, dz); |
| const b = this.bot.blockAt(bp); |
| if (!b || b.name === 'air') continue; |
| if (b.name.includes('_ore')) { |
| this._surroundings.ores.push({ block: b, pos: bp, dist: Math.abs(dx) + Math.abs(dy) + Math.abs(dz) }); |
| this.world.addOreSpot(Math.floor(bp.x), Math.floor(bp.y), Math.floor(bp.z), b.name); |
| } |
| if (['wheat','carrots','potatoes','beetroots'].includes(b.name) && b.metadata >= 7) { |
| this._surroundings.crops.push({ block: b, pos: bp, dist: Math.abs(dx) + Math.abs(dz) }); |
| } |
| } |
| } |
| } |
| this._surroundings.ores.sort((a, b) => a.dist - b.dist); |
| this._surroundings.crops.sort((a, b) => a.dist - b.dist); |
| for (const e of Object.values(this.bot.entities)) { |
| if (!e || e === this.bot.entity) continue; |
| const d = pos.distanceTo(e.position); |
| if (d > 10) continue; |
| if (e.type === 'mob' && dangerMobs.includes((e.name || e.displayName || '').toLowerCase())) { |
| this._surroundings.mobs.push({ entity: e, dist: d }); |
| } |
| if (e.type === 'object') this._surroundings.items.push({ entity: e, dist: d }); |
| } |
| this._surroundings.mobs.sort((a, b) => a.dist - b.dist); |
| this._surroundings.items.sort((a, b) => a.dist - b.dist); |
| } |
|
|
| _autoCraft() { |
| if (this._state === 'CRAFT' || this.movement.isMoving()) return; |
| const tools = this.bot.inventory.items().filter(i => i.name.includes('pickaxe') || i.name.includes('axe') || i.name.includes('sword')); |
| const planks = this.bot.inventory.count('oak_planks') + this.bot.inventory.count('spruce_planks') + this.bot.inventory.count('birch_planks'); |
| const logs = this.bot.inventory.count('oak_log') + this.bot.inventory.count('spruce_log') + this.bot.inventory.count('birch_log'); |
|
|
| if (tools.length === 0 && (planks >= 3 || logs >= 1)) { |
| this._setState('CRAFT'); |
| this.miner.craftItem('wooden_pickaxe').then(r => this._setState('IDLE')); |
| } else if (tools.length <= 1 && planks < 3 && logs >= 1) { |
| this._setState('CRAFT'); |
| this.miner.craftItem('planks').then(r => this._setState('IDLE')); |
| } |
| } |
|
|
| |
| |
| |
|
|
| _goMine() { |
| const ore = this._surroundings.ores[0]; |
| if (!ore) { this._setState('IDLE'); return; } |
| this.movement.goto(ore.pos.x, ore.pos.z).then(ok => { |
| if (!ok) { this._setState('IDLE'); return; } |
| this.miner.mineOre(ore.block).then(r => { |
| if (r) this.world.markOreMined(Math.floor(ore.pos.x), Math.floor(ore.pos.y), Math.floor(ore.pos.z)); |
| this._setState('IDLE'); |
| }); |
| }); |
| } |
|
|
| _goFarm() { |
| const crop = this._surroundings.crops[0]; |
| if (!crop) { this._setState('IDLE'); return; } |
| this.movement.goto(crop.pos.x, crop.pos.z).then(ok => { |
| if (!ok) { this._setState('IDLE'); return; } |
| this.miner.harvestCrop(crop.block).then(r => this._setState('IDLE')); |
| }); |
| } |
|
|
| _collectItems() { |
| const item = this._surroundings.items[0]; |
| if (!item) { this._setState('IDLE'); return; } |
| const p = item.entity.position; |
| this.movement.goto(p.x, p.z).then(ok => this._setState('IDLE')); |
| } |
|
|
| _fightNearest() { |
| const mob = this._surroundings.mobs[0]; |
| if (!mob) { this._setState('IDLE'); return; } |
| this._fightEntity(mob.entity); |
| } |
|
|
| _fightEntity(entity) { |
| if (!entity || entity.isDead || !this.bot.entity) { this._setState('IDLE'); return; } |
| const d = this.bot.entity.position.distanceTo(entity.position); |
| if (d > 10) { this._setState('IDLE'); return; } |
| if (this.shaper && this.shaper.lookAt) { |
| this.shaper.lookAt(entity.position.offset( |
| (Math.random() - 0.5) * 0.3, 1 + (Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.3 |
| )); |
| } else { |
| this.bot.lookAt(entity.position.offset( |
| (Math.random() - 0.5) * 0.3, 1 + (Math.random() - 0.5) * 0.3, (Math.random() - 0.5) * 0.3 |
| ), false); |
| } |
| setTimeout(() => { |
| if (!entity || entity.isDead || !this.bot.entity) { this._setState('IDLE'); return; } |
| this.bot.attack(entity); |
| setTimeout(() => this._fightEntity(entity), rand(800, 1400)); |
| }, rand(300, 700)); |
| } |
|
|
| _explore(dirIdx) { |
| this._setState('EXPLORE'); |
| if (!this.movement.isMoving()) { |
| const r = this.bot.entity.position; |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| if (dx === 0 && dz === 0) { |
| this.movement.goto(r.x + rand(-15, 15), r.z + rand(-15, 15)); |
| } else { |
| this.movement.goto(r.x + dx * 15, r.z + dz * 15); |
| } |
| } |
| } |
|
|
| _wander(dirIdx) { |
| this._setState('WANDER'); |
| if (!this.movement.isMoving()) { |
| const r = this.bot.entity.position; |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| if (dx === 0 && dz === 0) { |
| this.movement.goto(r.x + rand(-30, 30), r.z + rand(-30, 30)); |
| } else { |
| this.movement.goto(r.x + dx * 30, r.z + dz * 30); |
| } |
| } |
| } |
|
|
| _chatRandom() { |
| const msgs = [ |
| 'السلام عليكم', 'كيف اللعب؟', 'وش تسوي؟', 'تعال هنا', |
| 'شكلك محترف', 'الماين كرفت مرة حلو', 'تعاون؟', |
| 'هههه', 'تم', 'ايوا', 'لا', 'ماشي', |
| ]; |
| this.bot.chat(msgs[rand(0, msgs.length - 1)]); |
| this._setState('IDLE'); |
| } |
|
|
| _follow() { |
| const target = Object.keys(this.bot.players).find(n => n !== this.bot.username); |
| if (!target) { this._setState('IDLE'); return; } |
| this._setState('FOLLOW'); |
| const loop = () => { |
| if (this._state !== 'FOLLOW') return; |
| const p = this.bot.players[target]; |
| if (!p || !p.entity) { this._setState('IDLE'); return; } |
| const d = this.bot.entity.position.distanceTo(p.entity.position); |
| if (d > 3) { |
| this.movement.goto(p.entity.position.x, p.entity.position.z); |
| } |
| setTimeout(loop, 3000); |
| }; |
| loop(); |
| } |
|
|
| |
| |
| |
|
|
| _placeBlock() { |
| const blocks = ['oak_planks','cobblestone','dirt','stone']; |
| const item = this._findItem(blocks); |
| if (!item) { this._setState('IDLE'); return; } |
| this._setState('PLACE_BLOCK'); |
| this.bot.equip(item, 'hand').then(() => { |
| const pos = this.bot.entity.position; |
| const target = this.bot.blockAt(pos.offset(0, -1, 0)); |
| if (!target) { this._setState('IDLE'); return; } |
| this.bot.placeBlock(target, new Vec3(0, 1, 0)).catch(() => {}); |
| setTimeout(() => this._setState('IDLE'), 1000); |
| }); |
| } |
|
|
| _buildShelter() { |
| const blocks = ['oak_planks','cobblestone','dirt']; |
| const item = this._findItem(blocks); |
| if (!item) { this._setState('IDLE'); return; } |
| this._setState('BUILD_SHELTER'); |
| const pos = this.bot.entity.position; |
| const startX = Math.floor(pos.x); |
| const startZ = Math.floor(pos.z); |
|
|
| this.bot.equip(item, 'hand').then(async () => { |
| |
| for (let x = 0; x < 5; x++) { |
| for (let z = 0; z < 5; z++) { |
| const b = this.bot.blockAt(pos.offset(x - 2, -1, z - 2)); |
| if (b && (b.name === 'air' || b.name.includes('grass') || b.name.includes('dirt'))) { |
| try { await this.bot.placeBlock(b, new Vec3(0, 1, 0)); } catch (_) {} |
| await sleep(rand(200, 500)); |
| } |
| } |
| } |
| |
| for (let h = 1; h <= 3; h++) { |
| for (let x = 0; x < 5; x++) { |
| for (let z = 0; z < 5; z++) { |
| if ((x > 0 && x < 4 && z > 0 && z < 4)) continue; |
| if (x === 2 && z === 4 && h === 1) continue; |
| const target = this.bot.blockAt(pos.offset(x - 2, h - 1, z - 2)); |
| if (target && target.name !== 'air') continue; |
| const refBlock = this.bot.blockAt(pos.offset(x - 2, h - 2, z - 2)); |
| if (!refBlock) continue; |
| try { await this.bot.placeBlock(refBlock, new Vec3(0, 1, 0)); } catch (_) {} |
| await sleep(rand(200, 400)); |
| } |
| } |
| } |
| this._setState('IDLE'); |
| }); |
| } |
|
|
| _lightArea() { |
| const torch = this._findItem(['torch']); |
| if (!torch) { this._setState('IDLE'); return; } |
| this._setState('LIGHT_AREA'); |
| this.bot.equip(torch, 'hand').then(async () => { |
| const pos = this.bot.entity.position; |
| for (let dx = -2; dx <= 2; dx += 2) { |
| for (let dz = -2; dz <= 2; dz += 2) { |
| const target = this.bot.blockAt(pos.offset(dx, 0, dz)); |
| if (!target || target.name !== 'air') continue; |
| const ref = this.bot.blockAt(pos.offset(dx, -1, dz)); |
| if (!ref || ref.name === 'air') continue; |
| try { |
| await this.bot.placeBlock(ref, new Vec3(0, 1, 0)); |
| await sleep(rand(200, 400)); |
| } catch (_) {} |
| } |
| } |
| this._setState('IDLE'); |
| }); |
| } |
|
|
| |
| |
| |
|
|
| _eat() { |
| const foodNames = ['apple','bread','cooked_beef','cooked_porkchop','cooked_chicken', |
| 'baked_potato','carrot','golden_carrot','beetroot','melon_slice', |
| 'cooked_mutton','cooked_cod','cooked_salmon','steak','porkchop', |
| 'mushroom_stew','suspicious_stew']; |
| const food = this._findItem(foodNames); |
| if (!food) { this._setState('IDLE'); return; } |
| this._setState('EAT'); |
| this.bot.equip(food, 'hand').then(() => { |
| this.bot.consume().catch(() => {}); |
| setTimeout(() => this._setState('IDLE'), 2000); |
| }); |
| } |
|
|
| _sleep() { |
| |
| const pos = this.bot.entity.position; |
| for (let dx = -4; dx <= 4; dx++) { |
| for (let dz = -4; dz <= 4; dz++) { |
| for (let dy = -1; dy <= 1; dy++) { |
| const b = this.bot.blockAt(pos.offset(dx, dy, dz)); |
| if (b && (b.name.includes('bed'))) { |
| this._setState('SLEEP'); |
| this.movement.goto(b.position.x, b.position.z).then(() => { |
| this.bot.sleep(b).catch(() => {}); |
| setTimeout(() => { this.bot.wake().catch(() => {}); this._setState('IDLE'); }, rand(5000, 15000)); |
| }); |
| return; |
| } |
| } |
| } |
| } |
| this._setState('IDLE'); |
| } |
|
|
| _goHome() { |
| const home = this._homePos || { x: 0, y: 64, z: 0 }; |
| this._setState('GO_HOME'); |
| this.movement.goto(home.x, home.z).then(() => this._setState('IDLE')); |
| } |
|
|
| _equipArmor() { |
| const inv = this.bot.inventory.items(); |
| const armor = [ |
| { slot: 'torso', names: ['chestplate','elytra'] }, |
| { slot: 'feet', names: ['boots'] }, |
| { slot: 'head', names: ['helmet','skull','head'] }, |
| { slot: 'legs', names: ['leggings'] }, |
| ]; |
| for (const a of armor) { |
| const item = inv.find(i => a.names.some(n => i.name.includes(n))); |
| if (item) this.bot.equip(item, a.slot).catch(() => {}); |
| } |
| this._setState('IDLE'); |
| } |
|
|
| |
| |
| |
|
|
| _doSneak() { |
| this.bot.setControlState('sneak', true); |
| this._setState('SNEAK'); |
| setTimeout(() => { |
| this.bot.setControlState('sneak', false); |
| this._setState('IDLE'); |
| }, rand(2000, 5000)); |
| } |
|
|
| _doSwim() { |
| if (!this.bot.entity.isInWater) { this._setState('IDLE'); return; } |
| this.bot.setControlState('forward', true); |
| this.bot.setControlState('jump', true); |
| this._setState('SWIM'); |
| setTimeout(() => { |
| this.bot.setControlState('forward', false); |
| this.bot.setControlState('jump', false); |
| this._setState('IDLE'); |
| }, rand(2000, 4000)); |
| } |
|
|
| _doEmote() { |
| const emotes = ['*يتثاوب*','*يلوح*','*يرقص*','*ينظر حوله*','*يجلس*','*يفكر*']; |
| this.bot.chat(emotes[rand(0, emotes.length - 1)]); |
| setTimeout(() => { |
| this.bot.animation(0, 1); |
| this._setState('IDLE'); |
| }, 500); |
| } |
|
|
| |
| _findNearestBlock(names) { |
| const pos = this.bot.entity.position; |
| let nearest = null, minDist = Infinity; |
| for (let x = -20; x <= 20; x++) { |
| for (let z = -20; z <= 20; z++) { |
| for (let y = -5; y <= 5; y++) { |
| const b = this.bot.blockAt(pos.offset(x, y, z)); |
| if (b && names.some(n => b.name.includes(n))) { |
| const d = pos.distanceTo(b.position); |
| if (d < minDist) { minDist = d; nearest = b; } |
| } |
| } |
| } |
| } |
| return nearest; |
| } |
| _findNearestEntity(name) { |
| for (const e of Object.values(this.bot.entities)) { |
| if (!e || e === this.bot.entity) continue; |
| if ((e.name || '').includes(name)) return e; |
| } |
| return null; |
| } |
| _attackEntity(entity) { |
| this.bot.lookAt(entity.position.offset(0, 1, 0), true).then(() => { |
| this.bot.attack(entity); |
| }); |
| } |
| async _placePathBlocks(target) { |
| const blocks = ['cobblestone', 'stone', 'oak_planks']; |
| const item = this._findItem(blocks); |
| if (!item) return; |
| await this.bot.equip(item, 'hand'); |
| const start = this.bot.entity.position.floored(); |
| const dx = Math.sign(target.x - start.x); |
| const dz = Math.sign(target.z - start.z); |
| for (let i = 0; i < 10; i++) { |
| const pos = start.offset(dx * i, -1, dz * i); |
| const below = this.bot.blockAt(pos); |
| if (below && below.name === 'air') { |
| const ref = this.bot.blockAt(pos.offset(0, -1, 0)); |
| if (ref) { |
| try { await this.bot.placeBlock(ref, new Vec3(0, 1, 0)); } catch (_) {} |
| await sleep(200); |
| } |
| } |
| } |
| } |
| async _bridgeForward(start, dx, dz) { |
| const blocks = ['cobblestone', 'dirt', 'oak_planks']; |
| const item = this._findItem(blocks); |
| if (!item) return; |
| await this.bot.equip(item, 'hand'); |
| for (let i = 1; i <= 5; i++) { |
| const pos = start.offset(dx * i, -1, dz * i); |
| const below = this.bot.blockAt(pos); |
| if (below && below.name === 'air') { |
| const ref = this.bot.blockAt(pos.offset(0, -1, 0)); |
| if (ref) { |
| try { await this.bot.placeBlock(ref, new Vec3(0, 1, 0)); } catch (_) {} |
| await sleep(300); |
| } |
| } |
| } |
| } |
| async _placeWallSegment() { |
| const blocks = ['cobblestone', 'stone', 'oak_planks']; |
| const item = this._findItem(blocks); |
| if (!item) return; |
| await this.bot.equip(item, 'hand'); |
| const pos = this.bot.entity.position.floored(); |
| for (let x = -2; x <= 2; x++) { |
| for (let y = 0; y < 3; y++) { |
| const target = this.bot.blockAt(pos.offset(x, y, 0)); |
| if (target && target.name !== 'air') continue; |
| const ref = this.bot.blockAt(pos.offset(x, y - 1, 0)); |
| if (!ref) continue; |
| try { await this.bot.placeBlock(ref, new Vec3(0, 1, 0)); } catch (_) {} |
| await sleep(200); |
| } |
| } |
| } |
|
|
| |
| _buildPath(dirIdx) { |
| this._setState('BUILD_PATH'); |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| const target = this.bot.entity.position.offset(dx * 15, 0, dz * 15); |
| this._placePathBlocks(target); |
| setTimeout(() => this._setState('IDLE'), 2000); |
| } |
| _buildBridge(dirIdx) { |
| this._setState('BUILD_BRIDGE'); |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| const start = this.bot.entity.position.floored(); |
| this._bridgeForward(start, dx, dz); |
| setTimeout(() => this._setState('IDLE'), 3000); |
| } |
| _buildWall() { |
| this._setState('BUILD_WALL'); |
| this._placeWallSegment(); |
| setTimeout(() => this._setState('IDLE'), 3000); |
| } |
| _placeDoor() { |
| this._setState('PLACE_DOOR'); |
| const door = this._findItem(['door', 'trapdoor']); |
| if (!door) { this._setState('IDLE'); return; } |
| this.bot.equip(door, 'hand').then(() => { |
| const pos = this.bot.entity.position.floored(); |
| const target = this.bot.blockAt(pos.offset(0, -1, 0)); |
| if (target) { |
| this.bot.placeBlock(target, new Vec3(0, 1, 0)).catch(() => {}); |
| } |
| setTimeout(() => this._setState('IDLE'), 1000); |
| }); |
| } |
| _placeLadder(dirIdx) { |
| this._setState('PLACE_LADDER'); |
| const ladder = this._findItem(['ladder']); |
| if (!ladder) { this._setState('IDLE'); return; } |
| this.bot.equip(ladder, 'hand').then(() => { |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| const pos = this.bot.entity.position.floored(); |
| for (let y = 0; y < 4; y++) { |
| const target = this.bot.blockAt(pos.offset(dx, y, dz)); |
| if (!target || target.name !== 'air') continue; |
| const ref = this.bot.blockAt(pos.offset(dx, y - 1, dz)); |
| if (!ref) continue; |
| this.bot.placeBlock(ref, new Vec3(-dx, 0, -dz)).catch(() => {}); |
| } |
| setTimeout(() => this._setState('IDLE'), 1000); |
| }); |
| } |
| _mineIron() { |
| this._setState('MINE_IRON'); |
| this.miner.mineOre({ name: 'iron_ore' }).then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _mineGold() { |
| this._setState('MINE_GOLD'); |
| this.miner.mineOre({ name: 'gold_ore' }).then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _mineDiamond() { |
| this._setState('MINE_DIAMOND'); |
| this.miner.mineOre({ name: 'diamond_ore' }).then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _mineObsidian() { |
| this._setState('MINE_OBSIDIAN'); |
| const block = this._findNearestBlock(['obsidian']); |
| if (block) { |
| this.movement.goto(block.position.x, block.position.z).then(() => { |
| this.bot.dig(block).catch(() => {}); |
| }); |
| } |
| setTimeout(() => this._setState('IDLE'), 3000); |
| } |
| _craftEnchantTable() { |
| this._setState('CRAFT'); |
| this.miner.craftItem('enchanting_table').then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _craftAnvil() { |
| this._setState('CRAFT'); |
| this.miner.craftItem('anvil').then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _craftBrewingStand() { |
| this._setState('CRAFT'); |
| this.miner.craftItem('brewing_stand').then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _craftBed() { |
| this._setState('CRAFT'); |
| this.miner.craftItem('bed').then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _craftBoat() { |
| this._setState('CRAFT'); |
| this.miner.craftItem('boat').then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _enterNether() { |
| this._setState('ENTER_NETHER'); |
| const portal = this._findNearestBlock(['nether_portal', 'portal']); |
| if (portal) { |
| this.movement.goto(portal.position.x, portal.position.z).then(() => { |
| this.bot.activateBlock(portal).catch(() => {}); |
| }); |
| } |
| setTimeout(() => this._setState('IDLE'), 3000); |
| } |
| _exploreNether(dirIdx) { |
| this._explore(dirIdx); |
| } |
| _fightBlaze() { |
| this._setState('FIGHT'); |
| const blaze = this._findNearestEntity('blaze'); |
| if (blaze) this._attackEntity(blaze); |
| setTimeout(() => this._setState('IDLE'), 2000); |
| } |
| _enterEnd() { |
| this._setState('ENTER_END'); |
| const portal = this._findNearestBlock(['end_portal', 'end_gateway']); |
| if (portal) { |
| this.movement.goto(portal.position.x, portal.position.z).then(() => { |
| this.bot.activateBlock(portal).catch(() => {}); |
| }); |
| } |
| setTimeout(() => this._setState('IDLE'), 3000); |
| } |
| _fightDragon() { |
| this._setState('FIGHT'); |
| const dragon = this._findNearestEntity('ender_dragon'); |
| if (dragon) this._attackEntity(dragon); |
| setTimeout(() => this._setState('IDLE'), 2000); |
| } |
| _plantCrop() { |
| this._setState('FARM'); |
| const seeds = this._findItem(['wheat_seeds', 'carrot', 'potato', 'beetroot_seeds']); |
| if (!seeds) { this._setState('IDLE'); return; } |
| this.bot.equip(seeds, 'hand').then(() => { |
| const pos = this.bot.entity.position.floored(); |
| for (let x = -2; x <= 2; x++) { |
| for (let z = -2; z <= 2; z++) { |
| const farmland = this.bot.blockAt(pos.offset(x, -1, z)); |
| if (farmland && farmland.name === 'farmland') { |
| const above = this.bot.blockAt(pos.offset(x, 0, z)); |
| if (above && above.name === 'air') { |
| this.bot.placeBlock(above, new Vec3(0, 1, 0)).catch(() => {}); |
| } |
| } |
| } |
| } |
| setTimeout(() => this._setState('IDLE'), 2000); |
| }); |
| } |
| _harvestCrop() { |
| this._setState('FARM'); |
| this.miner.harvestCrop().then(() => this._setState('IDLE')).catch(() => this._setState('IDLE')); |
| } |
| _fish() { |
| this._setState('FISH'); |
| const rod = this._findItem(['fishing_rod']); |
| if (!rod) { this._setState('IDLE'); return; } |
| this.bot.equip(rod, 'hand').then(() => { |
| this.bot.fish(); |
| setTimeout(() => { |
| this.bot.activateItem().catch(() => {}); |
| this._setState('IDLE'); |
| }, 5000); |
| }); |
| } |
| _shear() { |
| this._setState('SHEAR'); |
| const shears = this._findItem(['shears']); |
| if (!shears) { this._setState('IDLE'); return; } |
| const sheep = this._findNearestEntity('sheep'); |
| if (!sheep) { this._setState('IDLE'); return; } |
| this.bot.equip(shears, 'hand').then(() => { |
| this.movement.goto(sheep.position.x, sheep.position.z).then(() => { |
| this.bot.shear(sheep).catch(() => {}); |
| this._setState('IDLE'); |
| }); |
| }); |
| } |
| _milk() { |
| this._setState('MILK'); |
| const bucket = this._findItem(['bucket']); |
| if (!bucket) { this._setState('IDLE'); return; } |
| const cow = this._findNearestEntity('cow'); |
| if (!cow) { this._setState('IDLE'); return; } |
| this.bot.equip(bucket, 'hand').then(() => { |
| this.movement.goto(cow.position.x, cow.position.z).then(() => { |
| this.bot.milk(cow).catch(() => {}); |
| this._setState('IDLE'); |
| }); |
| }); |
| } |
| _sleepAtVillage() { this._sleep(); } |
| _chatGreeting() { |
| const msgs = ['السلام عليكم', 'مرحباً', 'هلا']; |
| this.bot.chat(msgs[rand(0, msgs.length - 1)]); |
| this._setState('IDLE'); |
| } |
| _chatStatus() { |
| this.bot.chat('صحتي ' + Math.round(this.bot.health) + ' | طعامي ' + Math.round(this.bot.food)); |
| this._setState('IDLE'); |
| } |
| _chatThank() { |
| this.bot.chat('شكراً'); |
| this._setState('IDLE'); |
| } |
| _chatQuestion() { |
| const qs = ['كيف اللعب؟', 'وش تسوي؟', 'تعاون؟']; |
| this.bot.chat(qs[rand(0, qs.length - 1)]); |
| this._setState('IDLE'); |
| } |
| _chatAlert() { |
| this.bot.chat('انتبه! خطر'); |
| this._setState('IDLE'); |
| } |
| _thinkDeep() { |
| this._setState('THINK'); |
| setTimeout(() => this._setState('IDLE'), rand(3000, 8000)); |
| } |
|
|
| _helpAlly() { |
| |
| const other = Object.keys(this.bot.players).find(n => |
| n !== this.bot.username && (n.includes('bot') || n.includes('Alex') || n.includes('Steve') || n.includes('Night') || n.includes('Redstone')) |
| ); |
| if (!other) { this._setState('IDLE'); return; } |
| const p = this.bot.players[other]; |
| if (!p || !p.entity) { this._setState('IDLE'); return; } |
| this._setState('HELP_ALLY'); |
| this.movement.goto(p.entity.position.x, p.entity.position.z).then(() => { |
| this.bot.chat('جيت اساعدك ' + other); |
| this._setState('IDLE'); |
| }); |
| } |
|
|
| _trade() { |
| |
| const pos = this.bot.entity.position; |
| for (const e of Object.values(this.bot.entities)) { |
| if (!e || e === this.bot.entity) continue; |
| const mobName = (e.name || e.displayName || '').toLowerCase(); |
| if (mobName === 'villager') { |
| if (pos.distanceTo(e.position) > 5) { |
| this._setState('TRADE'); |
| this.movement.goto(e.position.x, e.position.z); |
| return; |
| } |
| |
| this.bot.trade(e, 0, 1).catch(() => {}); |
| this._setState('IDLE'); |
| return; |
| } |
| } |
| this._setState('IDLE'); |
| } |
|
|
| _lookAround() { |
| this._setState('LOOK_AROUND'); |
| if (this.shaper && this.shaper.lookAt) { |
| const pos = this.bot.entity.position; |
| this.shaper.lookAt(pos.offset(rand(-5, 5), rand(0, 2), rand(-5, 5))); |
| } |
| setTimeout(() => { |
| this._scanSurroundings(); |
| this._setState('IDLE'); |
| }, rand(1500, 3000)); |
| } |
|
|
|
|
|
|
| |
| |
| |
|
|
| _findItem(names) { |
| const items = this.bot.inventory.items(); |
| for (const n of names) { |
| const found = items.find(i => i.name.includes(n)); |
| if (found) return found; |
| } |
| return null; |
| } |
|
|
| _inDanger() { |
| for (const e of Object.values(this.bot.entities)) { |
| if (!e || e === this.bot.entity || e.type !== 'mob') continue; |
| if (dangerMobs.includes((e.name || e.displayName || '').toLowerCase())) { |
| if (this.bot.entity.position.distanceTo(e.position) < 6) return true; |
| } |
| } |
| return false; |
| } |
|
|
| _isOnLava() { |
| const b = this.bot.blockAt(this.bot.entity.position.offset(0, -1, 0)); |
| return b && (b.name.includes('lava') || b.name.includes('fire')); |
| } |
|
|
| _hasWeapon() { |
| return this.bot.inventory.items().some(i => i.name.includes('sword') || i.name.includes('axe')); |
| } |
|
|
| _flee(dirIdx) { |
| if (this._fleeing) return; |
| this._fleeing = true; |
| this.bot.setControlState('back', true); |
| this.bot.setControlState('jump', true); |
| setTimeout(() => { |
| this.bot.setControlState('back', false); |
| this.bot.setControlState('jump', false); |
| this._fleeing = false; |
| }, 1800); |
| } |
|
|
| _doJump(dirIdx) { |
| this._setState('JUMP'); |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| if (dx !== 0 || dz !== 0) { |
| this.bot.lookAt(this.bot.entity.position.offset(dx * 5, 0, dz * 5), true); |
| } |
| this.bot.setControlState('jump', true); |
| this.bot.setControlState('forward', true); |
| setTimeout(() => { |
| this.bot.setControlState('jump', false); |
| this.bot.setControlState('forward', false); |
| this._setState('IDLE'); |
| }, 1200); |
| } |
|
|
| _sprint(dirIdx) { |
| this._setState('SPRINT'); |
| const [dx, dz] = DIR_VEC[dirIdx || 0] || [0, 0]; |
| if (dx !== 0 || dz !== 0) { |
| this.bot.lookAt(this.bot.entity.position.offset(dx * 5, 0, dz * 5), true); |
| } |
| this.bot.setControlState('sprint', true); |
| this.bot.setControlState('forward', true); |
| setTimeout(() => { |
| this.bot.setControlState('sprint', false); |
| this.bot.setControlState('forward', false); |
| this._setState('IDLE'); |
| }, rand(2000, 4000)); |
| } |
|
|
| _jumpOut() { |
| this.bot.setControlState('jump', true); |
| this.bot.setControlState('forward', true); |
| setTimeout(() => { |
| this.bot.setControlState('jump', false); |
| this.bot.setControlState('forward', false); |
| }, 2500); |
| } |
| } |
| module.exports = { BotBrain }; |
|
|