196 lines
5.3 KiB
JavaScript
196 lines
5.3 KiB
JavaScript
import { Zombie } from '../game/Zombie.js';
|
|
import { MegaTank } from '../game/MegaTank.js';
|
|
import { Leaper } from '../game/Leaper.js';
|
|
import { Witch } from '../game/Witch.js';
|
|
import { MegaWitch } from '../game/MegaWitch.js';
|
|
import { Boss } from '../game/Boss.js';
|
|
import { Bullet } from '../game/Bullet.js';
|
|
import { WitchProjectile } from '../game/WitchProjectile.js';
|
|
import { Pickup } from '../game/Pickup.js';
|
|
import { Player } from '../game/Player.js';
|
|
import { Vector2D } from '../engine/Vector2D.js';
|
|
|
|
const CLASSES = {
|
|
zombie: Zombie,
|
|
megatank: MegaTank,
|
|
leaper: Leaper,
|
|
witch: Witch,
|
|
megawitch: MegaWitch,
|
|
boss: Boss,
|
|
bullet: Bullet,
|
|
projectile: WitchProjectile,
|
|
};
|
|
|
|
const PLAYER_SIZE = 24;
|
|
|
|
export class NetEntities {
|
|
constructor() {
|
|
this.target = { pos: new Vector2D(960, 540) };
|
|
this.views = new Map();
|
|
this.players = new Map();
|
|
}
|
|
|
|
setTarget(x, y) {
|
|
this.target.pos.x = x;
|
|
this.target.pos.y = y;
|
|
}
|
|
|
|
_timeFor(eid, now) {
|
|
return now + (eid % 17) * 0.21 + (eid % 5) * 0.07;
|
|
}
|
|
|
|
_key(entity) {
|
|
return `${entity.d}#${entity.eid}`;
|
|
}
|
|
|
|
_createView(entity) {
|
|
const Ctor = CLASSES[entity.d];
|
|
if (entity.d === 'pickup') {
|
|
return new Pickup(entity.x, entity.y, entity.t, { container: null, viewport: {}, life: 10 });
|
|
}
|
|
if (entity.d === 'bullet' || entity.d === 'projectile') {
|
|
return new Ctor(entity.x, entity.y, new Vector2D(1, 0), {});
|
|
}
|
|
return new Ctor(entity.x, entity.y, this.target, {});
|
|
}
|
|
|
|
draw(entities, players, renderer, textRenderer, now) {
|
|
const seen = new Set();
|
|
let boss = null;
|
|
|
|
for (const entity of entities) {
|
|
const key = this._key(entity);
|
|
seen.add(key);
|
|
let view = this.views.get(key);
|
|
if (!view) {
|
|
view = this._createView(entity);
|
|
this.views.set(key, view);
|
|
}
|
|
this._stamp(view, entity, now);
|
|
view.draw(renderer, textRenderer);
|
|
if (entity.d === 'boss') {
|
|
boss = view;
|
|
}
|
|
}
|
|
|
|
for (const [key, view] of this.views) {
|
|
if (!seen.has(key)) {
|
|
this.views.delete(key);
|
|
}
|
|
}
|
|
|
|
for (const snapshot of players) {
|
|
let view = this.players.get(snapshot.id);
|
|
if (!view) {
|
|
view = new Player({
|
|
input: null,
|
|
container: null,
|
|
viewport: { width: 1920, height: 1080 },
|
|
size: PLAYER_SIZE,
|
|
});
|
|
this.players.set(snapshot.id, view);
|
|
}
|
|
this._stampPlayer(view, snapshot, now);
|
|
if (!snapshot.dead) {
|
|
view.draw(renderer);
|
|
if (snapshot.name) {
|
|
this._drawName(snapshot, renderer, textRenderer);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const [id, view] of this.players) {
|
|
if (!players.some((snapshot) => snapshot.id === id)) {
|
|
this.players.delete(id);
|
|
}
|
|
}
|
|
|
|
return boss;
|
|
}
|
|
|
|
clear() {
|
|
this.views.clear();
|
|
this.players.clear();
|
|
}
|
|
|
|
_drawName(snapshot, renderer, textRenderer) {
|
|
const hp = snapshot.hp ?? 0;
|
|
const mhp = snapshot.mhp ?? hp;
|
|
const label = `${snapshot.name} HP: ${hp}/${mhp}`;
|
|
const opts = { size: 12, fillStyle: '#fff', bold: true };
|
|
const entry = textRenderer.measure(label, opts);
|
|
|
|
const barW = 40;
|
|
const barH = 4;
|
|
const barX = snapshot.x - barW / 2;
|
|
const barY = snapshot.y - PLAYER_SIZE / 2 - barH - 2;
|
|
renderer.drawTexture(renderer.whiteTexture, barX, barY, barW, barH, [0, 0, 0, 0.6]);
|
|
const ratio = mhp > 0 ? Math.max(0, Math.min(1, hp / mhp)) : 0;
|
|
if (ratio > 0) {
|
|
const color = ratio > 2 / 3 ? [0.3, 0.9, 0.35, 0.9] : ratio > 1 / 3 ? [0.95, 0.8, 0.2, 0.9] : [0.9, 0.25, 0.25, 0.9];
|
|
renderer.drawTexture(renderer.whiteTexture, barX, barY, barW * ratio, barH, color);
|
|
}
|
|
|
|
textRenderer.draw(renderer, label, snapshot.x - entry.width / 2, barY - entry.height - 2, opts);
|
|
}
|
|
|
|
_stamp(view, entity, now) {
|
|
view.pos.x = entity.x;
|
|
view.pos.y = entity.y;
|
|
if (entity.s !== undefined) {
|
|
view.size = entity.s;
|
|
}
|
|
if (entity.c) {
|
|
view.baseColor = [entity.c[0], entity.c[1], entity.c[2], 1];
|
|
}
|
|
if (entity.hp !== undefined && entity.mhp !== undefined) {
|
|
view.health = entity.hp;
|
|
view.maxHealth = entity.mhp;
|
|
}
|
|
view._time = this._timeFor(entity.eid, now);
|
|
view._flash = 0;
|
|
|
|
if (entity.et !== undefined) {
|
|
view._emergeTotal = entity.et;
|
|
view._emerge = entity.er ?? 0;
|
|
}
|
|
|
|
switch (entity.d) {
|
|
case 'leaper':
|
|
view._dashTimer = entity.dash ? 999 : 0;
|
|
view._windupTimer = entity.wind ? 999 : 0;
|
|
if (!view._dashDirection) {
|
|
view._dashDirection = { x: 1, y: 0 };
|
|
}
|
|
break;
|
|
case 'megawitch':
|
|
view._novaTimer = (1 - (entity.charge ?? 0)) * view.novaInterval;
|
|
break;
|
|
case 'witch':
|
|
view._cast = 0;
|
|
view._fireTimer = view.fireInterval * (1 - ((entity.eid * 0.37 + now * 0.11) % 1));
|
|
break;
|
|
case 'boss':
|
|
view._hurt = 0;
|
|
break;
|
|
case 'pickup':
|
|
view._time = Math.max(0, 10 - entity.life);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
_stampPlayer(view, snapshot, now) {
|
|
view.pos.x = snapshot.x;
|
|
view.pos.y = snapshot.y;
|
|
const aim = new Vector2D(snapshot.aimX, snapshot.aimY);
|
|
if (aim.lengthSqrt() > 0.01) {
|
|
view._aimDirection = aim.normalize();
|
|
}
|
|
view._time = now;
|
|
view._damageCooldown = 0;
|
|
view.health = snapshot.hp;
|
|
view.maxHealth = snapshot.mhp;
|
|
}
|
|
} |