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); } } 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(); } _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; } }