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

186 lines
6.8 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';
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] || [];
}