'use strict'; const fs = require('fs'); const path = require('path'); const ts = () => new Date().toISOString().replace('T', ' ').slice(0, 19); const log = (tag, msg) => console.log(`[${ts()}] [AI] ${tag} ${msg}`); const FEATURES = [ // أساسيات (5) 'health','food','y_level','xp_level','time_of_day', // البيئة (7) 'biome','dimension','light_level','is_raining','can_see_sky', 'is_in_water','is_on_lava', // البلوكات حوله (5) 'block_below','block_above','is_on_ladder','is_in_shelter', 'is_in_danger', // الموبات والأغراض (3) 'mobs_nearby','danger_mobs','items_nearby', // الاحتياطي (6) 'inventory_pct','has_weapon','has_pickaxe','has_axe', 'has_shield','has_bow', // الدروع والطعام (5) 'has_chestplate','has_boots','has_food','has_bed','has_torches', // الموارد (5) 'has_logs','has_stone','has_iron_raw','has_diamond_raw','has_building_blocks', // حالة + اجتماعي (4) 'player_nearby','bot_count','is_sleeping','is_sprinting', // اتجاهات البلوكات (8) 'block_north','block_south','block_east','block_west', 'block_north2','block_south2','block_east2','block_west2', // عدادات الموارد (8) 'count_logs','count_stone','count_iron','count_gold','count_diamond','count_coal','count_food_items','count_torches', // مستويات الأدوات (4) 'pickaxe_level','axe_level','sword_level','shovel_level', // دروع (2) 'total_armor','has_extra_shield', // تضاريس (2) 'terrain_flatness','elevation_change', // قرب ماء/حمم (2) 'near_water','near_lava', // وقت منذ الضرر (2) 'time_since_hurt','is_thundering', // بورتال/ستراكتشر (3) 'portal_nearby','structure_nearby','y_relative_surface', // أدوات الإند (4) 'has_obsidian','has_flint_steel','has_ender_pearl','has_blaze_rod', // حيوانات وقرويون (2) 'animals_nearby','villagers_nearby', // المساحة (3) 'explored_radius','trees_nearby','flowers_nearby', ]; const ACTIONS = [ // أساسيات (25) 'WANDER','EXPLORE','COLLECT','MINE','CRAFT','BUILD_SHELTER','LIGHT_AREA','FIGHT','FLEE', 'EAT','SLEEP','SWIM','CHAT','EMOTE','FOLLOW','HELP_ALLY','TRADE','SMELT','EQUIP_ARMOR', 'GO_HOME','FARM','IDLE','JUMP','LOOK_AROUND','SPRINT', // بناء (5) 'BUILD_PATH','BUILD_BRIDGE','BUILD_WALL','PLACE_DOOR','PLACE_LADDER', // تعدين متقدم (4) 'MINE_IRON','MINE_GOLD','MINE_DIAMOND','MINE_OBSIDIAN', // كرافت متقدم (5) 'CRAFT_ENCHANT_TABLE','CRAFT_ANVIL','CRAFT_BREWING_STAND','CRAFT_BED','CRAFT_BOAT', // نذر (3) 'ENTER_NETHER','EXPLORE_NETHER','FIGHT_BLAZE', // إند (2) 'ENTER_END','FIGHT_DRAGON', // زراعة (5) 'PLANT_CROP','HARVEST_CROP','FISH','SHEAR','MILK', // قرية (1) 'SLEEP_AT_VILLAGE', // محادثة متقدمة (5) 'CHAT_GREETING','CHAT_STATUS','CHAT_THANK','CHAT_QUESTION','CHAT_ALERT', // تفكير (1) 'THINK', ]; const SEQ_LEN = 50; const INPUT_DIM = FEATURES.length; const FEATURE_MINMAX = { 'health':[0,20],'food':[0,20],'y_level':[-64,320],'xp_level':[0,100],'time_of_day':[0,24000], 'biome':[0,14],'dimension':[0,2],'light_level':[0,15],'is_raining':[0,1],'can_see_sky':[0,1], 'is_in_water':[0,1],'is_on_lava':[0,1], 'block_below':[0,80],'block_above':[0,100],'is_on_ladder':[0,1],'is_in_shelter':[0,1],'is_in_danger':[0,1], 'mobs_nearby':[0,8],'danger_mobs':[0,5],'items_nearby':[0,10], 'inventory_pct':[0,100],'has_weapon':[0,1],'has_pickaxe':[0,1],'has_axe':[0,1],'has_shield':[0,1],'has_bow':[0,1], 'has_chestplate':[0,1],'has_boots':[0,1],'has_food':[0,1],'has_bed':[0,1],'has_torches':[0,1], 'has_logs':[0,1],'has_stone':[0,1],'has_iron_raw':[0,1],'has_diamond_raw':[0,1],'has_building_blocks':[0,1], 'player_nearby':[0,1],'bot_count':[0,4],'is_sleeping':[0,1],'is_sprinting':[0,1], // اتجاهات البلوكات (8) 'block_north':[0,99],'block_south':[0,99],'block_east':[0,99],'block_west':[0,99], 'block_north2':[0,99],'block_south2':[0,99],'block_east2':[0,99],'block_west2':[0,99], // عدادات الموارد (8) 'count_logs':[0,64],'count_stone':[0,64],'count_iron':[0,64],'count_gold':[0,64], 'count_diamond':[0,64],'count_coal':[0,64],'count_food_items':[0,64],'count_torches':[0,64], // مستويات الأدوات (4) 'pickaxe_level':[0,5],'axe_level':[0,5],'sword_level':[0,5],'shovel_level':[0,5], // دروع (2) 'total_armor':[0,20],'has_extra_shield':[0,1], // تضاريس (2) 'terrain_flatness':[0,10],'elevation_change':[-10,10], // قرب ماء/حمم (2) 'near_water':[0,1],'near_lava':[0,1], // وقت منذ الضرر (2) 'time_since_hurt':[0,1000],'is_thundering':[0,1], // بورتال/ستراكتشر (3) 'portal_nearby':[0,1],'structure_nearby':[0,5],'y_relative_surface':[-64,100], // أدوات الإند (4) 'has_obsidian':[0,1],'has_flint_steel':[0,1],'has_ender_pearl':[0,1],'has_blaze_rod':[0,1], // حيوانات وقرويون (2) 'animals_nearby':[0,5],'villagers_nearby':[0,3], // المساحة (3) 'explored_radius':[0,500],'trees_nearby':[0,10],'flowers_nearby':[0,5], }; // تصنيفات للميزات التي تحتاج معايرة const BLOCK_TYPES = { 'stone':1,'cobblestone':2,'dirt':3,'grass_block':4,'grass':4, 'sand':5,'gravel':6,'water':7,'lava':8,'bedrock':9, 'oak_log':10,'spruce_log':10,'birch_log':10,'jungle_log':10, 'acacia_log':10,'dark_oak_log':10,'mangrove_log':10,'cherry_log':10, 'oak_planks':11,'spruce_planks':11,'birch_planks':11, 'glass':12,'glass_pane':12,'coal_ore':13,'iron_ore':14, 'gold_ore':15,'diamond_ore':16,'redstone_ore':17,'lapis_ore':18, 'air':0,'cave_air':0,'void_air':0, // بناء إضافي 'deepslate':19,'andesite':20,'diorite':21,'granite':22,'tuff':23, 'cobbled_deepslate':24,'calcite':25,'dripstone_block':26,'moss_block':27, 'oak_leaves':28,'spruce_leaves':28,'birch_leaves':28, 'stone_bricks':29,'mossy_stone_bricks':29,'cracked_stone_bricks':29, 'bricks':30,'nether_bricks':31,'red_nether_bricks':31, // خامات متقدمة 'netherite_block':32,'ancient_debris':33, 'copper_ore':34,'emerald_ore':35, 'obsidian':36,'crying_obsidian':36, // بورتال وستراكتشر 'nether_portal':37,'end_portal':37,'end_gateway':37, 'spawner':38,'chest':39,'ender_chest':39, 'furnace':40,'blast_furnace':40,'smoker':40, 'crafting_table':41,'enchanting_table':42,'anvil':43, 'brewing_stand':44,'beacon':45, // نباتات 'oak_sapling':46,'spruce_sapling':46,'wheat':47,'wheat_seeds':47, 'carrots':48,'potatoes':49,'beetroots':50, 'dandelion':51,'poppy':51,'cornflower':51,'azure_bluet':51, 'tall_grass':52,'fern':52,'dead_bush':52, 'vine':53,'lily_pad':54,'sugar_cane':55,'cactus':56, 'pumpkin':57,'melon':58, // أضواء 'torch':59,'lantern':59,'soul_torch':59,'soul_lantern':59, 'glowstone':60,'shroomlight':60,'sea_lantern':60, // بنايات 'ladder':61,'scaffolding':62, 'oak_door':63,'spruce_door':63,'iron_door':63, 'oak_fence':64,'spruce_fence':64,'nether_brick_fence':64, 'oak_slab':65,'spruce_slab':65,'stone_slab':65, 'oak_stairs':66,'spruce_stairs':66,'cobblestone_stairs':66, // أرضيات 'snow_block':67,'ice':68,'blue_ice':68,'packed_ice':68, 'clay':69,'mud':70,'podzol':70,'mycelium':70, // نذر 'netherrack':71,'soul_sand':72,'soul_soil':72, 'crimson_nylium':73,'warped_nylium':73, 'basalt':74,'blackstone':75, 'crimson_stem':76,'warped_stem':76, 'shroomlight':77, // إند 'end_stone':78,'end_stone_bricks':78, 'purpur_block':79,'purpur_pillar':79, 'chorus_plant':80,'chorus_flower':80, }; const BIOME_MAP = { 'plains':0,'forest':1,'dark_forest':1,'wooded_hills':1, 'desert':2,'taiga':3,'swamp':4,'ocean':5,'river':6, 'jungle':7,'savanna':8,'badlands':9,'mountains':10, 'mountain':10,'stony_peaks':10,'cave':11,'dripstone_caves':11, 'lush_caves':11,'nether_wastes':12,'soul_sand_valley':12, 'crimson_forest':12,'warped_forest':12,'the_end':13,'end':13, }; const DIM_MAP = { 'minecraft:overworld':0, 'minecraft:the_nether':1, 'minecraft:the_end':2 }; class ContextManager { constructor(maxLen) { this.maxLen = maxLen || SEQ_LEN; this.buffer = []; } addState(state) { this.buffer.push({ ...state }); if (this.buffer.length > this.maxLen) this.buffer.shift(); } getContextTensor() { const total = this.maxLen * INPUT_DIM; const data = new Float32Array(total); for (let i = 0; i < this.maxLen; i++) { const bufIdx = this.buffer.length - this.maxLen + i; const state = bufIdx >= 0 ? this.buffer[bufIdx] : null; for (let j = 0; j < INPUT_DIM; j++) { const raw = state ? (state[FEATURES[j]] ?? 0) : 0; const mm = FEATURE_MINMAX[FEATURES[j]]; data[i * INPUT_DIM + j] = mm ? (raw - mm[0]) / (mm[1] - mm[0]) : 0; } } return data; } reset() { this.buffer = []; } get length() { return this.buffer.length; } } class BotModel { constructor() { this.session = null; this.inputName = 'float_input'; this.loaded = false; this.fallbackAction = 1; // EXPLORE this.stats = { predictions: 0, fallbacks: 0, avgTime: 0 }; this._modelPath = null; this.context = new ContextManager(SEQ_LEN); } async loadFromHF(username, repoName, fileName = 'best_model.onnx') { const repo = repoName && repoName.includes('/') ? repoName : `${username}/${repoName}`; const url = `https://huggingface.co/${repo}/resolve/main/${fileName}`; const localPath = path.join(__dirname, 'cache', fileName); // Try local cache first if (fs.existsSync(localPath)) { const stat = fs.statSync(localPath); if (stat.size > 50 * 1024 * 1024) { log('💾', `وجد cache محلي (${(stat.size / 1024 / 1024).toFixed(1)} MB)`); const ok = await this.loadLocal(localPath); if (ok) return true; log('🔄', 'cache تالف، إعادة تحميل...'); } else { log('⚠️', 'cache صغير جداً (>50MB متوقع)، إعادة تحميل'); } } const maxAttempts = 3; for (let attempt = 0; attempt < maxAttempts; attempt++) { log('⬇️', `تحميل النموذج... (محاولة ${attempt+1}/${maxAttempts})`); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 600000); const resp = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const contentLength = parseInt(resp.headers.get('content-length') || '0', 10); let downloaded = 0; const reader = resp.body.getReader(); const chunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); downloaded += value.length; if (contentLength > 0) { const pct = (downloaded / contentLength * 100).toFixed(0); if (downloaded % (10 * 1024 * 1024) < value.length) { log('⬇️', `${pct}% (${(downloaded / 1024 / 1024).toFixed(1)} MB)`); } } } const buffer = Buffer.concat(chunks); if (buffer.length < 50 * 1024 * 1024) { log('⚠️', `ملف صغير جداً (${(buffer.length / 1024 / 1024).toFixed(1)} MB) — يتلف؟`); } fs.mkdirSync(path.join(__dirname, 'cache'), { recursive: true }); fs.writeFileSync(localPath, buffer); log('✅', `تم التحميل (${(buffer.length / 1024 / 1024).toFixed(1)} MB)`); const ok = await this.loadLocal(localPath); if (ok) return true; try { fs.unlinkSync(localPath); } catch(_) {} log('🔄', 'إعادة المحاولة...'); } catch (err) { log('⚠️', `فشل تحميل النموذج: ${err.message}`); if (attempt < maxAttempts - 1) { const wait = (attempt + 1) * 10000; log('⏳', `انتظار ${wait/1000}ث قبل إعادة المحاولة...`); await new Promise(r => setTimeout(r, wait)); } } } log('❌', `فشل تحميل النموذج بعد ${maxAttempts} محاولات`); return false; } _softmax(logits) { if (!logits || logits.length === 0) return []; const max = Math.max(...logits); const exps = logits.map(v => Math.exp(v - max)); const sum = exps.reduce((a, b) => a + b, 0); return sum > 0 ? exps.map(v => v / sum) : exps; } async loadLocal(filePath) { try { const ort = require('onnxruntime-node'); this._modelPath = filePath; let sessionOpts = { executionProviders: ['dml', 'cpu'], interOpNumThreads: 4, intraOpNumThreads: 4, graphOptimizationLevel: 'all' }; try { this.session = await ort.InferenceSession.create(filePath, sessionOpts); } catch (_) { this.session = await ort.InferenceSession.create(filePath, { executionProviders: ['cpu'], interOpNumThreads: 4, intraOpNumThreads: 4, graphOptimizationLevel: 'all' }); } if (this.session.inputNames) { this.inputName = this.session.inputNames[0]; } else { this.inputName = this.session.inputs[0].name; } this.loaded = true; log('🧠', `نموذج AI جاهز (Input: ${this.inputName})`); this.context.reset(); return true; } catch (err) { log('❌', `فشل تحميل النموذج المحلي: ${err.message}`); return false; } } extractState(bot) { if (!bot || !bot.entity) return null; const pos = bot.entity.position; const inv = bot.inventory.items(); const entities = Object.values(bot.entities || {}); const time = bot.time ? bot.time.timeOfDay : 0; // ─── البيئة ─── let biome = 0; try { const b = bot.world.biomeAt ? bot.world.biomeAt(pos) : null; if (b) biome = biomeAt(b.id || b.name || b); } catch (_) {} let dimension = 0; try { dimension = DIM_MAP[bot.game.dimension] ?? 0; } catch (_) {} let lightLevel = 0; try { const block = bot.blockAt(pos); if (block) lightLevel = block.skyLight ?? block.light ?? 0; } catch (_) {} const isRaining = bot.isRaining ? 1 : 0; let canSeeSky = 1; try { for (let y = pos.y + 1; y < 256; y++) { const b = bot.blockAt(pos.offset(0, y - pos.y, 0)); if (b && b.name !== 'air' && b.name !== 'cave_air' && b.name !== 'void_air') { canSeeSky = 0; break; } } } catch (_) { canSeeSky = 0; } const isInWater = bot.entity.isInWater ? 1 : 0; let isOnLava = 0; try { const below = bot.blockAt(pos.offset(0, -1, 0)); if (below && (below.name.includes('lava') || below.name.includes('fire'))) isOnLava = 1; } catch (_) {} const isSprinting = bot.entity.isSprinting ? 1 : 0; // ─── البلوكات حوله ─── let blockBelow = 0, blockAbove = 0, isOnLadder = 0, isInShelter = 0; try { const bBelow = bot.blockAt(pos.offset(0, -1, 0)); if (bBelow) { blockBelow = BLOCK_TYPES[bBelow.name] ?? 0; if (bBelow.name.includes('ladder')) isOnLadder = 1; } const bAbove = bot.blockAt(pos.offset(0, 1, 0)); if (bAbove) blockAbove = BLOCK_TYPES[bAbove.name] ?? (['air','cave_air','void_air'].includes(bAbove.name) ? 0 : 99); // فحص المأوى: هل هناك بلوك فوق الرأس؟ for (let y = 1; y <= 3; y++) { const b = bot.blockAt(pos.offset(0, y, 0)); if (b && !['air','cave_air','void_air'].includes(b.name)) { isInShelter = 1; break; } } } catch (_) {} // ─── الموبات والأغراض ─── let mobsNearby = 0, dangerMobs = 0, itemsNearby = 0; for (const e of entities) { if (!e || e === bot.entity) continue; const d = pos.distanceTo(e.position); if (d > 10) continue; if (e.type === 'mob') { mobsNearby++; const mobName = (e.mobType || e.name || '').toLowerCase(); if (['zombie','skeleton','creeper','spider','enderman', 'witch','phantom','drowned','husk','stray','blaze', 'piglin','zombified piglin'].includes(mobName)) { dangerMobs++; } } if (e.type === 'object') itemsNearby++; } // ─── خطر ─── let isInDanger = 0; if (dangerMobs > 0) { for (const e of entities) { if (!e || e === bot.entity || e.type !== 'mob') continue; if (pos.distanceTo(e.position) < 6) { isInDanger = 1; break; } } } if (isOnLava || (pos.y < -10)) isInDanger = 1; // ─── المخزون ─── const invPct = Math.min(100, Math.round(inv.length / 36 * 100)); const hasWeapon = inv.some(i => i.name.includes('sword') || i.name.includes('axe')) ? 1 : 0; const hasPickaxe = inv.some(i => i.name.includes('pickaxe')) ? 1 : 0; const hasAxe = inv.some(i => i.name.includes('axe') && !i.name.includes('pickaxe')) ? 1 : 0; const hasShield = inv.some(i => i.name.includes('shield')) ? 1 : 0; const hasBow = inv.some(i => i.name.includes('bow')) ? 1 : 0; const hasChestplate = inv.some(i => i.name.includes('chestplate')) ? 1 : 0; const hasBoots = inv.some(i => i.name.includes('boots')) ? 1 : 0; const hasFood = inv.some(i => ['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'].includes(i.name)) ? 1 : 0; const hasBed = inv.some(i => i.name.includes('bed')) ? 1 : 0; const hasTorches = inv.some(i => i.name.includes('torch')) ? 1 : 0; const hasLogs = inv.some(i => i.name.includes('_log') || i.name === 'log') ? 1 : 0; const hasStone = inv.some(i => ['cobblestone','stone','andesite','diorite','granite','deepslate','tuff'].includes(i.name)) ? 1 : 0; const hasIronRaw = inv.some(i => i.name.includes('raw_iron') || i.name === 'iron_ingot') ? 1 : 0; const hasDiamondRaw = inv.some(i => i.name.includes('diamond')) ? 1 : 0; const hasBuildingBlocks = inv.some(i => ['cobblestone','stone','oak_planks','spruce_planks','birch_planks', 'dirt','sand','gravel','deepslate'].includes(i.name)) ? 1 : 0; // ─── لاعبون ─── const playerNearby = Object.keys(bot.players).filter(n => n !== bot.username).length > 0 ? 1 : 0; const botCount = Math.max(0, Object.keys(bot.players).length - 1); const isSleeping = bot.isSleeping ? 1 : 0; // ─── اتجاهات البلوكات (NSEW + NSEW2) ─── let blockNorth = 0, blockSouth = 0, blockEast = 0, blockWest = 0; let blockNorth2 = 0, blockSouth2 = 0, blockEast2 = 0, blockWest2 = 0; try { const bN = bot.blockAt(pos.offset(0, 0, -1)); if (bN) blockNorth = BLOCK_TYPES[bN.name] ?? 0; const bS = bot.blockAt(pos.offset(0, 0, 1)); if (bS) blockSouth = BLOCK_TYPES[bS.name] ?? 0; const bE = bot.blockAt(pos.offset(1, 0, 0)); if (bE) blockEast = BLOCK_TYPES[bE.name] ?? 0; const bW = bot.blockAt(pos.offset(-1, 0, 0)); if (bW) blockWest = BLOCK_TYPES[bW.name] ?? 0; const bN2 = bot.blockAt(pos.offset(0, 0, -2)); if (bN2) blockNorth2 = BLOCK_TYPES[bN2.name] ?? 0; const bS2 = bot.blockAt(pos.offset(0, 0, 2)); if (bS2) blockSouth2 = BLOCK_TYPES[bS2.name] ?? 0; const bE2 = bot.blockAt(pos.offset(2, 0, 0)); if (bE2) blockEast2 = BLOCK_TYPES[bE2.name] ?? 0; const bW2 = bot.blockAt(pos.offset(-2, 0, 0)); if (bW2) blockWest2 = BLOCK_TYPES[bW2.name] ?? 0; } catch (_) {} // ─── عدادات المواد في المخزون ─── let countLogs = 0, countStone = 0, countIron = 0, countGold = 0; let countDiamond = 0, countCoal = 0, countFoodItems = 0, countTorches = 0; for (const i of inv) { if (i.name.includes('_log') || i.name === 'log') countLogs += i.count; if (['cobblestone','stone','andesite','diorite','granite','deepslate','tuff'].includes(i.name)) countStone += i.count; if (i.name.includes('raw_iron') || i.name === 'iron_ingot' || i.name.includes('iron_')) countIron += i.count; if (i.name.includes('raw_gold') || i.name === 'gold_ingot' || i.name.includes('gold_')) countGold += i.count; if (i.name.includes('diamond')) countDiamond += i.count; if (i.name === 'coal' || i.name.includes('coal_')) countCoal += i.count; if (['apple','bread','cooked_beef','cooked_porkchop','cooked_chicken','cooked_cod','cooked_salmon', 'baked_potato','carrot','golden_carrot','beetroot','melon_slice','cooked_mutton','steak', 'porkchop','mushroom_stew','suspicious_stew','beef','chicken','cod','salmon', 'potato','sweet_berries','cookie','pumpkin_pie','rabbit_stew','dried_kelp', 'chorus_fruit','enchanted_golden_apple','golden_apple','rotten_flesh'].includes(i.name)) countFoodItems += i.count; if (i.name.includes('torch')) countTorches += i.count; } // ─── مستويات الأدوات ─── const toolTier = name => { if (name.includes('netherite')) return 5; if (name.includes('diamond')) return 4; if (name.includes('iron')) return 3; if (name.includes('stone')) return 2; if (name.includes('wooden')) return 1; return 0; }; let pickaxeLevel = 0, axeLevel = 0, swordLevel = 0, shovelLevel = 0; for (const i of inv) { if (i.name.includes('pickaxe')) pickaxeLevel = Math.max(pickaxeLevel, toolTier(i.name)); if (i.name.includes('axe') && !i.name.includes('pickaxe')) axeLevel = Math.max(axeLevel, toolTier(i.name)); if (i.name.includes('sword')) swordLevel = Math.max(swordLevel, toolTier(i.name)); if (i.name.includes('shovel')) shovelLevel = Math.max(shovelLevel, toolTier(i.name)); } // ─── الدروع ─── let totalArmor = 0; let hasExtraShield = 0; for (const i of inv) { if (i.name.includes('helmet')) totalArmor += 2; if (i.name.includes('chestplate')) totalArmor += 6; if (i.name.includes('leggings')) totalArmor += 5; if (i.name.includes('boots')) totalArmor += 2; if (i.name.includes('shield')) hasExtraShield++; } if (hasExtraShield > 1) hasExtraShield = 1; // ─── التضاريس ─── let terrainFlatness = 0, elevationChange = 0; try { let minY = 999, maxY = -999; for (let dx = -2; dx <= 2; dx++) { for (let dz = -2; dz <= 2; dz++) { const b = bot.blockAt(pos.offset(dx, -1, dz)); if (b && b.name !== 'air') { const by = Math.floor(pos.y + (dx === 0 && dz === 0 ? 0 : 0)); minY = Math.min(minY, by); maxY = Math.max(maxY, by); } } } if (minY < 999) terrainFlatness = maxY - minY; elevationChange = Math.round(pos.y - 64); } catch (_) {} // ─── ماء/حمم قريبة ─── let nearWater = 0, nearLava = 0; for (let dx = -4; dx <= 4; dx++) { for (let dz = -4; dz <= 4; dz++) { for (let dy = -2; dy <= 2; dy++) { const b = bot.blockAt(pos.offset(dx, dy, dz)); if (!b) continue; if (b.name.includes('water') || b.name === 'water') nearWater = 1; if (b.name.includes('lava') || b.name === 'lava') nearLava = 1; } } } // ─── البورتالات والهياكل ─── let portalNearby = 0, structureNearby = 0; let yRelativeSurface = Math.round(pos.y - 64); for (let dx = -8; dx <= 8; dx++) { for (let dz = -8; dz <= 8; dz++) { for (let dy = -3; dy <= 3; dy++) { const b = bot.blockAt(pos.offset(dx, dy, dz)); if (!b) continue; if (b.name.includes('portal') || b.name.includes('gateway')) portalNearby = 1; if (['chest','ender_chest','crafting_table','furnace','enchanting_table', 'anvil','brewing_stand','beacon'].includes(b.name)) structureNearby++; } } } if (structureNearby > 5) structureNearby = 5; // ─── أدوات النذر والإند ─── const hasObsidian = inv.some(i => i.name.includes('obsidian')) ? 1 : 0; const hasFlintSteel = inv.some(i => i.name.includes('flint') && i.name.includes('steel') || i.name === 'flint_and_steel') ? 1 : 0; const hasEnderPearl = inv.some(i => i.name.includes('ender_pearl')) ? 1 : 0; const hasBlazeRod = inv.some(i => i.name.includes('blaze_rod')) ? 1 : 0; // ─── الحيوانات والقرويون ─── let animalsNearby = 0, villagersNearby = 0; for (const e of entities) { if (!e || e === bot.entity || e.type !== 'mob') continue; const d = pos.distanceTo(e.position); if (d > 10) continue; const mobName = (e.name || e.displayName || e.mobType || '').toLowerCase(); if (['cow','sheep','pig','chicken','horse','donkey','mule','rabbit','goat', 'llama','wolf','cat','ocelot','fox','bee','strider'].includes(mobName)) animalsNearby++; if (mobName === 'villager' || mobName === 'wandering_trader') villagersNearby++; } // ─── الأشجار والزهور ─── let treesNearby = 0, flowersNearby = 0; for (let dx = -5; dx <= 5; dx++) { for (let dz = -5; dz <= 5; dz++) { for (let dy = -2; dy <= 2; dy++) { const b = bot.blockAt(pos.offset(dx, dy, dz)); if (!b) continue; if (b.name.includes('_log') || b.name === 'log' || b.name.includes('_leaves') || b.name === 'leaves') treesNearby++; if (['dandelion','poppy','cornflower','azure_bluet','allium','oxeye_daisy', 'lily_of_the_valley','wither_rose','sunflower','lilac','peony','rose_bush', 'blue_orchid','tulip'].includes(b.name)) flowersNearby++; } } } if (treesNearby > 10) treesNearby = 10; if (flowersNearby > 5) flowersNearby = 5; return { health: Math.round(bot.health || 20), food: Math.round(bot.food || 20), y_level: Math.round(pos.y), xp_level: Math.round((bot.experience?.level || 0)), time_of_day: Math.round(time), biome, dimension, light_level: lightLevel, is_raining: isRaining, can_see_sky: canSeeSky, is_in_water: isInWater, is_on_lava: isOnLava, block_below: blockBelow, block_above: blockAbove, is_on_ladder: isOnLadder, is_in_shelter: isInShelter, is_in_danger: isInDanger, mobs_nearby: mobsNearby, danger_mobs: dangerMobs, items_nearby: itemsNearby, inventory_pct: invPct, has_weapon: hasWeapon, has_pickaxe: hasPickaxe, has_axe: hasAxe, has_shield: hasShield, has_bow: hasBow, has_chestplate: hasChestplate, has_boots: hasBoots, has_food: hasFood, has_bed: hasBed, has_torches: hasTorches, has_logs: hasLogs, has_stone: hasStone, has_iron_raw: hasIronRaw, has_diamond_raw: hasDiamondRaw, has_building_blocks: hasBuildingBlocks, player_nearby: playerNearby, bot_count: botCount, is_sleeping: isSleeping, is_sprinting: isSprinting, // اتجاهات block_north: blockNorth, block_south: blockSouth, block_east: blockEast, block_west: blockWest, block_north2: blockNorth2, block_south2: blockSouth2, block_east2: blockEast2, block_west2: blockWest2, // عدادات count_logs: countLogs, count_stone: countStone, count_iron: countIron, count_gold: countGold, count_diamond: countDiamond, count_coal: countCoal, count_food_items: countFoodItems, count_torches: countTorches, // مستويات الأدوات pickaxe_level: pickaxeLevel, axe_level: axeLevel, sword_level: swordLevel, shovel_level: shovelLevel, // دروع total_armor: totalArmor, has_extra_shield: hasExtraShield, // تضاريس terrain_flatness: terrainFlatness, elevation_change: elevationChange, // ماء/حمم near_water: nearWater, near_lava: nearLava, // وقت منذ الضرر ورعد time_since_hurt: Math.min(1000, Math.floor((Date.now() - (bot._lastHurtTime || Date.now())) / 1000)), is_thundering: (bot.thunderState || bot.isThundering ? 1 : 0), // بورتال وستراكتشر portal_nearby: portalNearby, structure_nearby: structureNearby, y_relative_surface: Math.round(yRelativeSurface), // أدوات الإند has_obsidian: hasObsidian, has_flint_steel: hasFlintSteel, has_ender_pearl: hasEnderPearl, has_blaze_rod: hasBlazeRod, // حيوانات وقرويون animals_nearby: animalsNearby, villagers_nearby: villagersNearby, // مساحة explored_radius: Math.round(Math.min(500, (bot.exploredChunks ? Object.keys(bot.exploredChunks).length : 0) * 5)), trees_nearby: treesNearby, flowers_nearby: flowersNearby, }; } async predict(state) { const start = Date.now(); this.context.addState(state); if (!this.loaded || !this.session) { this.stats.fallbacks++; return this.fallbackAction; } try { const contextData = this.context.getContextTensor(); const ort = require('onnxruntime-node'); const tensor = new ort.Tensor('float32', contextData, [1, SEQ_LEN, INPUT_DIM]); const results = await this.session.run({ [this.inputName]: tensor }); const values = Object.values(results); const actionData = values[0].data; const dirData = values.length > 1 ? values[1].data : null; let bestAction = 0; for (let i = 1; i < actionData.length; i++) { if (actionData[i] > actionData[bestAction]) bestAction = i; } let bestDir = 0; if (dirData) { for (let i = 1; i < dirData.length; i++) { if (dirData[i] > dirData[bestDir]) bestDir = i; } } this.stats.predictions++; this.stats.avgTime = (this.stats.avgTime * (this.stats.predictions - 1) + (Date.now() - start)) / this.stats.predictions; return { actionIdx: bestAction, dirIdx: bestDir }; } catch (err) { log('⚠️', `Inference error: ${err.message}`); this.stats.fallbacks++; return this.fallbackAction; } } async predictActionName(state) { const result = await this.predict(state); const idx = typeof result === 'object' ? result.actionIdx : result; return ACTIONS[idx] || 'EXPLORE'; } async predictDirection(state) { const result = await this.predict(state); return typeof result === 'object' ? result.dirIdx : 0; } getStats() { return { ...this.stats, loaded: this.loaded, modelPath: this._modelPath, contextLen: this.context.length, }; } unload() { this.session = null; this.loaded = false; this.context.reset(); log('🔴', 'نموذج AI: unloaded'); } } // Helper لتحويل biome name/id إلى رقم function biomeAt(val) { if (typeof val === 'number') return Math.min(val, 13); const name = String(val || '').toLowerCase().replace('minecraft:', ''); return BIOME_MAP[name] ?? 0; } module.exports = { BotModel, ContextManager, FEATURES, ACTIONS, SEQ_LEN, INPUT_DIM, BLOCK_TYPES, FEATURE_MINMAX };