// Моб AI import { state } from '../core/state.js'; import { GRAV, GRAV_WATER, TILE } from '../core/constants.js'; import { updateWaterFlag } from '../physics/water-detect.js'; import { calculateDamage } from '../entities/player.js'; import { playSound } from '../audio/sound-engine.js'; import { explodeAt } from '../world/tnt.js'; import { resolveY } from '../physics/collision.js'; import { resolveX } from '../physics/collision.js'; import { isSolid } from '../world/world-storage.js'; import { getCachedBiome } from '../world/generation.js'; export function mobAI(m, dt) { updateWaterFlag(m); if (m.kind === 'zombie') { const night = isNight(); if (!night) { m.hp -= 30 * dt; m.vx *= 0.5; return; } const dir = Math.sign((state.player.x) - m.x); m.vx = dir * m.speed; if (m.inWater && Math.random() < 0.06) m.vy = -260; if (Math.abs((m.x + m.w / 2) - (state.player.x + state.player.w / 2)) < 28 && Math.abs((m.y + m.h / 2) - (state.player.y + state.player.h / 2)) < 40 && state.player.invuln <= 0) { const damage = calculateDamage(15); state.player.hp -= damage; state.player.invuln = 0.8; state.player.vx += dir * 420; state.player.vy -= 260; playSound('hit1'); } } else if (m.kind === 'creeper') { const night = isNight(); if (!night) { m.hp -= 30 * dt; m.vx *= 0.5; return; } const dir = Math.sign((state.player.x) - m.x); const dist = Math.hypot((state.player.x + state.player.w / 2) - (m.x + m.w / 2), (state.player.y + state.player.h / 2) - (m.y + m.h / 2)); m.vx = dir * m.speed; if (m.inWater && Math.random() < 0.06) m.vy = -260; if (dist < 60) { m.fuse -= dt; if (m.fuse <= 0) { explodeAt(Math.floor((m.x + m.w / 2) / TILE), Math.floor((m.y + m.h / 2) / TILE)); m.hp = 0; } } else { if (dist < 40) { m.fuse = 0.5; } } } else if (m.kind === 'skeleton') { const night = isNight(); if (!night) { m.hp -= 30 * dt; m.vx *= 0.5; return; } const dir = Math.sign((state.player.x) - m.x); const dist = Math.hypot((state.player.x + state.player.w / 2) - (m.x + m.w / 2), (state.player.y + state.player.h / 2) - (m.y + m.h / 2)); m.vx = dir * m.speed; if (m.inWater && Math.random() < 0.06) m.vy = -260; m.shootCooldown -= dt; if (dist < 300 && m.shootCooldown <= 0) { m.shootCooldown = 2.0; const dx = (state.player.x + state.player.w / 2) - (m.x + m.w / 2); const dy = (state.player.y + state.player.h / 2) - (m.y + m.h / 2); const angle = Math.atan2(dy, dx); const speed = 450; state.projectiles.push({ x: m.x + m.w / 2, y: m.y + m.h / 3, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, dmg: 6, owner: 'mob', life: 3 }); } } else if (m.kind === 'scorpion') { // Скорпион — бежит к игроку, яд (замедление) const dir = Math.sign((state.player.x) - m.x); m.vx = dir * m.speed; if (m.inWater && Math.random() < 0.06) m.vy = -260; if (Math.abs((m.x + m.w / 2) - (state.player.x + state.player.w / 2)) < 28 && Math.abs((m.y + m.h / 2) - (state.player.y + state.player.h / 2)) < 40 && state.player.invuln <= 0) { const damage = calculateDamage(8); state.player.hp -= damage; state.player.invuln = 0.8; state.player.slowTimer = 3; state.player.vx += dir * 300; state.player.vy -= 200; playSound('hit1'); } } else if (m.kind === 'polar_bear') { // Белый медведь — нейтрален, бродит m.aiT -= dt; if (m.aiT <= 0) { m.aiT = 2.0 + Math.random() * 3; m.dir = Math.random() < 0.5 ? -1 : 1; if (Math.random() < 0.3) m.dir = 0; } m.vx = m.dir * m.speed; if (m.inWater) m.vy = -120; } else if (m.kind === 'slime') { // Слизь — прыгает к игроку const dir = Math.sign((state.player.x + state.player.w / 2) - (m.x + m.w / 2)); m.aiT -= dt; if (m.aiT <= 0) { m.aiT = 1.5 + Math.random() * 1.5; m.dir = dir; m.vy = -200; } m.vx = m.dir * m.speed; } else if (m.kind === 'eagle') { // Орёл — летает, атакует пикированием const dx = (state.player.x + state.player.w / 2) - (m.x + m.w / 2); const dy = (state.player.y + state.player.h / 2) - (m.y + m.h / 2); const dist = Math.hypot(dx, dy); if (dist < 400) { m.vx = Math.sign(dx) * m.speed; m.vy = dy > 0 ? 60 : -60; } else { m.aiT -= dt; if (m.aiT <= 0) { m.aiT = 2 + Math.random() * 3; m.dir = Math.random() < 0.5 ? -1 : 1; } m.vx = m.dir * m.speed * 0.5; m.vy = Math.sin(performance.now() / 1000) * 30; } // Атака при касании if (dist < 30 && state.player.invuln <= 0) { const damage = calculateDamage(10); state.player.hp -= damage; state.player.invuln = 0.8; state.player.vy -= 200; playSound('hit1'); } // Орёл не падает — летающий моб m.vy *= 0.5; m.grounded = false; } else { // животные (pig, chicken) m.aiT -= dt; if (m.aiT <= 0) { m.aiT = 1.8 + Math.random() * 2.5; m.dir = Math.random() < 0.5 ? -1 : 1; if (Math.random() < 0.25) m.dir = 0; } m.vx = m.dir * (m.kind === 'chicken' ? 55 : 40); if (m.inWater) m.vy = -120; } // Мобы прыгают при столкновении со стеной if (m.grounded && Math.abs(m.vx) > 5 && m.kind !== 'eagle') { const probeX = m.vx > 0 ? m.x + m.w + 2 : m.x - 2; const probeGY = Math.floor((m.y + m.h - 1) / TILE); if (isSolid(Math.floor(probeX / TILE), probeGY)) { m.vy = -280; } } // физика моба const g = (m.inWater && m.kind !== 'eagle') ? GRAV_WATER : GRAV; m.vy += g * dt; m.y += m.vy * dt; m.grounded = false; resolveY(m); m.x += m.vx * dt; resolveX(m); } export function isNight() { return state.worldTime > 0.5; } // Mob loot table export function getMobLoot(kind) { const table = { chicken: [{item:'chicken_meat',min:1,max:2,chance:1},{item:'feather',min:0,max:1,chance:0.5}], pig: [{item:'meat',min:1,max:2,chance:1}], zombie: [{item:'meat',min:0,max:1,chance:0.5},{item:'iron_ingot',min:0,max:1,chance:0.3}], skeleton: [{item:'arrow',min:2,max:4,chance:1},{item:'bone',min:0,max:2,chance:0.6},{item:'bow',min:1,max:1,chance:0.15}], creeper: [{item:'gunpowder',min:1,max:2,chance:1}], scorpion: [{item:'scorpion_stinger',min:0,max:1,chance:0.4}], polar_bear:[{item:'polar_fur',min:1,max:2,chance:0.8},{item:'meat',min:1,max:2,chance:1}], slime: [{item:'slime_ball',min:1,max:2,chance:1}], eagle: [{item:'eagle_feather',min:1,max:2,chance:0.7}] }; return table[kind] || []; }