108 lines
3.7 KiB
JavaScript
108 lines
3.7 KiB
JavaScript
// Моб 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;
|
||
} |