Files
2026-09-07 20:37:40 +02:00

377 lines
9.9 KiB
JavaScript

import { GameObject } from '../engine/GameObject.js';
import { Vector2D } from '../engine/Vector2D.js';
export class Zombie extends GameObject {
constructor(
x,
y,
target,
{
container,
viewport,
speed = 110,
size = 24,
color = [0.15, 0.65, 0.2, 1],
health = 1,
knockback = 260,
knockbackDamping = 10,
scoreValue = 1,
emergeTime = 0,
onDeath = null,
} = {},
) {
super(x, y);
this.target = target;
this.container = container;
this.viewport = viewport;
this.speed = speed;
this.size = size;
this.baseColor = [...color];
this.health = health;
this.maxHealth = health;
this.knockback = knockback;
this.knockbackDamping = knockbackDamping;
this.scoreValue = scoreValue;
this.onDeath = onDeath;
this._hitKnockback = new Vector2D(0, 0);
this._flash = 0;
this._time = 0;
this._broken = Math.random() * Math.PI * 2;
this._emergeTotal = emergeTime;
this._emerge = emergeTime;
this.width = size;
this.height = size;
}
die() {
if (this.onDeath) {
this.onDeath(this);
}
this.remove(this.container);
}
hit(impulse, damage = 1) {
this.health -= damage;
this._hitKnockback = impulse.mulS(this.knockback);
this._flash = 0.08;
return this.health <= 0;
}
update(dt) {
this._time += dt;
if (this._updateEmerge(dt)) return;
const delta = this.target.pos.subV(this.pos);
const distance = delta.length();
let chaseVelocity = new Vector2D(0, 0);
if (distance > 1) {
chaseVelocity = delta.divS(distance).mulS(this.speed);
}
this.velocity = chaseVelocity.addV(this._hitKnockback);
super.update(dt);
this._hitKnockback = this._hitKnockback.mulS(Math.max(0, 1 - dt * this.knockbackDamping));
this._flash = Math.max(0, this._flash - dt);
}
_updateEmerge(dt) {
if (this._emerge <= 0) return false;
this._emerge -= dt;
if (this._emerge <= 0) {
this._emerge = 0;
if (this._onEmergenceComplete) this._onEmergenceComplete();
}
return true;
}
_emergeSize() {
if (this._emerge > 0 && this._emergeTotal > 0) {
return 0.3 + 0.7 * (1 - this._emerge / this._emergeTotal);
}
return 1;
}
_drawEmerge(renderer) {
if (this._emerge <= 0) return;
const gear = 1 - this._emerge / this._emergeTotal;
renderer.setBlendMode('additive');
const g = this.size * (3.4 - gear * 1.4);
renderer.drawTexture(
renderer.glowTexture,
this.pos.x - g / 2,
this.pos.y - g / 2,
g,
g,
[this.baseColor[0], this.baseColor[1], this.baseColor[2], 0.5 * (1 - gear)],
);
renderer.setBlendMode('normal');
}
draw(renderer) {
this.width = this.size;
this.height = this.size;
let color = this.baseColor;
if (this._flash > 0) {
color = [1, 1, 1, 1];
} else if (this.maxHealth > 1) {
const hpRatio = this.health / this.maxHealth;
color = [
this.baseColor[0] + (1 - this.baseColor[0]) * (1 - hpRatio),
this.baseColor[1] * hpRatio,
this.baseColor[2] * hpRatio,
1,
];
}
if (this.maxHealth > 1) {
this._drawJuggernaut(renderer, color);
return;
}
this._drawEmerge(renderer);
const s = this.size * this._emergeSize();
const lean = s <= 16;
const facing = Math.atan2(
this.target.pos.y - this.pos.y,
this.target.pos.x - this.pos.x,
);
const cosF = Math.cos(facing);
const sinF = Math.sin(facing);
const walk = Math.sin(this._time * (lean ? 14 : 10) + this._broken);
const shade = [color[0] * 0.6, color[1] * 0.6, color[2] * 0.6, 1];
const shadeDark = [color[0] * 0.38, color[1] * 0.38, color[2] * 0.38, 1];
const light = [
Math.min(1, color[0] + 0.25),
Math.min(1, color[1] + 0.25),
Math.min(1, color[2] + 0.25),
1,
];
const drawLimb = (ax, ay, bx, by, thickness, limbColor) => {
const len = Math.hypot(bx - ax, by - ay);
if (len < 1) return;
renderer.drawTexture(
renderer.whiteTexture,
(ax + bx) / 2 - len / 2,
(ay + by) / 2 - thickness / 2,
len,
thickness,
limbColor,
Math.atan2(by - ay, bx - ax),
);
};
if (!lean) {
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - s * 0.62,
this.pos.y - s * 0.5 + s * 0.06,
s * 1.24,
s * 0.92,
[0, 0, 0, 0.35],
);
}
const legLen = s * (lean ? 0.32 : 0.4);
const legSpread = s * 0.22;
const hipX = this.pos.x - cosF * s * 0.18;
const hipY = this.pos.y - sinF * s * 0.18;
for (let i = 0; i < 2; i += 1) {
const sign = i === 0 ? 1 : -1;
const step = sign * walk * s * (lean ? 0.3 : 0.18);
const footX = hipX + cosF * legLen - sinF * legSpread * sign + cosF * step;
const footY = hipY + sinF * legLen + cosF * legSpread * sign + sinF * step;
drawLimb(hipX, hipY, footX, footY, s * 0.15, shadeDark);
}
const torsoW = s * (lean ? 0.5 : 0.62);
const torsoH = s * (lean ? 0.46 : 0.55);
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - torsoW / 2,
this.pos.y - torsoH / 2,
torsoW,
torsoH,
color,
);
const armLen = s * 0.5;
const armSpread = s * 0.27;
for (let i = 0; i < 2; i += 1) {
const sign = i === 0 ? 1 : -1;
const shX = this.pos.x - sinF * armSpread * sign;
const shY = this.pos.y + cosF * armSpread * sign - s * 0.02;
const reach = 1 + walk * 0.1;
const handX = shX + cosF * armLen * reach;
const handY = shY + sinF * armLen * reach;
drawLimb(shX, shY, handX, handY, s * 0.17, shade);
}
const headSize = s * (lean ? 0.4 : 0.44);
const headX = this.pos.x;
const headY = this.pos.y - s * (lean ? 0.34 : 0.4) + walk * s * 0.03;
renderer.drawTexture(
renderer.whiteTexture,
headX - headSize / 2,
headY - headSize / 2,
headSize,
headSize,
light,
);
const eyeSize = headSize * 0.2;
const eyeSpread = headSize * 0.24;
const look = lean ? 0.1 : 0.04;
const eyeY = headY + sinF * headSize * look;
renderer.drawTexture(
renderer.whiteTexture,
headX - eyeSize / 2 - sinF * eyeSpread,
eyeY - eyeSize / 2 + cosF * eyeSpread,
eyeSize,
eyeSize,
[0.05, 0.05, 0.05, 1],
);
renderer.drawTexture(
renderer.whiteTexture,
headX - eyeSize / 2 + sinF * eyeSpread,
eyeY - eyeSize / 2 - cosF * eyeSpread,
eyeSize,
eyeSize,
[0.05, 0.05, 0.05, 1],
);
}
_drawJuggernaut(renderer, color) {
const s = this.size;
const facing = Math.atan2(
this.target.pos.y - this.pos.y,
this.target.pos.x - this.pos.x,
);
const cosF = Math.cos(facing);
const sinF = Math.sin(facing);
const walk = Math.sin(this._time * 5 + this._broken);
const shade = [color[0] * 0.6, color[1] * 0.6, color[2] * 0.6, 1];
const shadeDark = [color[0] * 0.38, color[1] * 0.38, color[2] * 0.38, 1];
const light = [
Math.min(1, color[0] + 0.25),
Math.min(1, color[1] + 0.25),
Math.min(1, color[2] + 0.25),
1,
];
const mix = (a, b, t) => a + (b - a) * t;
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - s * 0.78,
this.pos.y - s * 0.55 + s * 0.08,
s * 1.56,
s * 1.14,
[0, 0, 0, 0.35],
);
const footSize = s * 0.16;
const footBaseY = this.pos.y + s * 0.46;
for (let i = 0; i < 2; i += 1) {
const sign = i === 0 ? 1 : -1;
const stepX = -sinF * s * 0.2 * sign + cosF * walk * s * 0.06 * sign;
const stepY = cosF * s * 0.2 * sign + sinF * walk * s * 0.06 * sign;
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x + stepX - footSize / 2,
footBaseY + stepY - footSize / 2,
footSize,
footSize,
shadeDark,
);
}
const shellW = s * 1.0;
const shellH = s * 0.95;
const bottomY = this.pos.y + s * 0.4;
const topY = this.pos.y - s * 0.5;
const layers = 6;
for (let i = 0; i < layers; i += 1) {
const u = i / (layers - 1);
const w = shellW * (1 - u * 0.66);
const h = (shellH / layers) * 1.4;
const cy = bottomY - u * (bottomY - topY) + walk * s * 0.02;
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - w / 2,
cy - h / 2,
w,
h,
[
mix(shadeDark[0], light[0], u),
mix(shadeDark[1], light[1], u),
mix(shadeDark[2], light[2], u),
1,
],
);
}
for (let i = 0; i < 3; i += 1) {
const rx = this.pos.x + (i - 1) * s * 0.16;
renderer.drawTexture(
renderer.whiteTexture,
rx - s * 0.045,
topY + s * 0.04,
s * 0.09,
shellH * 0.9,
shadeDark,
);
}
const rivet = s * 0.055;
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x - s * 0.44,
bottomY - rivet * 0.4,
rivet,
rivet,
shadeDark,
);
renderer.drawTexture(
renderer.whiteTexture,
this.pos.x + s * 0.44 - rivet,
bottomY - rivet * 0.4,
rivet,
rivet,
shadeDark,
);
const headSize = s * 0.24;
const headX = this.pos.x + cosF * s * 0.52;
const headY = this.pos.y + sinF * s * 0.52;
renderer.drawTexture(
renderer.whiteTexture,
headX - headSize / 2,
headY - headSize / 2,
headSize,
headSize,
light,
);
const eyeSize = headSize * 0.22;
const eyeSpread = headSize * 0.3;
renderer.drawTexture(
renderer.whiteTexture,
headX - eyeSize / 2 - sinF * eyeSpread,
headY - eyeSize / 2 + cosF * eyeSpread,
eyeSize,
eyeSize,
[0.05, 0.05, 0.05, 1],
);
renderer.drawTexture(
renderer.whiteTexture,
headX - eyeSize / 2 + sinF * eyeSpread,
headY - eyeSize / 2 - cosF * eyeSpread,
eyeSize,
eyeSize,
[0.05, 0.05, 0.05, 1],
);
}
}