// Моб 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'; 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 { // животные 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; } // физика моба const g = m.inWater ? 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() { // Автоматический цикл: ночь когда worldTime > 0.5 return state.worldTime > 0.5; }