grechka-game/src/entities/mob-ai.js

108 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Моб 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;
}