/* ============================================================ * Diablo2D — entities.js : entity classes & spatial index * ============================================================ */ 'use strict'; window.D2 = window.D2 || {}; (function (D2) { let nextUid = 1; /* ---------------- Player ---------------- */ class Player { constructor(classId) { this.uid = nextUid++; this.kind = 'player'; this.classId = classId; this.x = 0; this.y = 0; this.radius = 0.34; this.dead = false; /* progression */ this.level = 1; this.xp = 0; this.gold = 60; this.statPoints = 0; this.skillPoints = 0; this.attributes = { str: 0, dex: 0, vit: 0, ene: 0 }; // allocated points this.skills = {}; // skillId -> rank this.hotbar = ['basic', null, null, null, null]; // slot0=basic(LMB), 1=RMB, 2..4 keys this.potions = { hp: 4, mp: 3 }; this.inventory = []; // items this.equip = { weapon: null, offhand: null, head: null, chest: null, hands: null, feet: null, amulet: null, ring1: null, ring2: null }; /* derived (recomputed) */ this.stats = {}; this.maxHp = 50; this.hp = 50; this.maxMana = 20; this.mana = 20; /* movement / anim */ this.moving = false; this.walkPhase = 0; this.attackAnim = 0; this.hurtFlash = 0; this.attackCd = 0; this.channel = null; // whirlwind/dash state this.guaranteedCritT = 0; this.smokeveilT = 0; this.buffs = []; // {id,name,icon,t,dur,mods} this.dots = []; /* run meta */ this.kills = 0; this.eliteKills = 0; this.bossKills = 0; this.deaths = 0; } } /* ---------------- Monster ---------------- */ class Monster { constructor(statBlock, x, y) { Object.assign(this, statBlock); this.uid = nextUid++; this.kind = 'monster'; this.x = x; this.y = y; this.homeX = x; this.homeY = y; this.dead = false; this.dying = false; /* ai state */ this.state = 'idle'; // idle | chase | windup | recover | charging | fleeing this.aggro = false; this.stateT = 0; this.attackT = 0; // windup progress this.attackAnim = 0; this.hurtFlash = 0; this.cdT = 0; // attack cooldown this.specialCd = 0; this.chargeVX = 0; this.chargeVY = 0; this.path = null; this.pathIdx = 0; this.pathT = 0; this.moving = false; this.speedFactor = 1; /* statuses */ this.frozen = 0; this.chillT = 0; this.chillSlow = 0; this.dots = []; this.regenAcc = 0; } } /* ---------------- Projectile ---------------- */ const projectiles = []; class Projectile { constructor(o) { Object.assign(this, { x: 0, y: 0, vx: 0, vy: 0, speed: 10, dmg: 5, elem: null, fromPlayer: true, pierce: false, maxHits: 1, hits: new Set(), life: 2.2, visual: 'bolt', color: '#fff', size: 1, slow: 0, slowDur: 0, bleed: false, aoe: 0, pool: false, freeze: 0, knockback: 0, guaranteedCrit: false, ownerSkillId: null, z: 0, }, o); this.uid = nextUid++; this.dead = false; } } /* ---------------- Pickup ---------------- */ class Pickup { constructor(kind, x, y, extra = {}) { this.uid = nextUid++; this.kind = kind; // gold | potion | item this.x = x; this.y = y; this.amount = extra.amount || 0; this.item = extra.item || null; this.potionType = extra.potionType || null; this.t = 0; // age (for pop animation) this.magnet = false; } } /* ---------------- Spatial hash for monsters ---------------- */ class Grid { constructor(cell = 2.5) { this.cell = cell; this.map = new Map(); } key(x, y) { return ((x / this.cell) | 0) + ',' + ((y / this.cell) | 0); } rebuild(entities) { this.map.clear(); for (const e of entities) { if (e.dead) continue; const k = this.key(e.x, e.y); let arr = this.map.get(k); if (!arr) { arr = []; this.map.set(k, arr); } arr.push(e); } } query(x, y, r, out = []) { out.length = 0; const c = this.cell; const x0 = ((x - r) / c) | 0, x1 = ((x + r) / c) | 0; const y0 = ((y - r) / c) | 0, y1 = ((y + r) / c) | 0; for (let gy = y0; gy <= y1; gy++) { for (let gx = x0; gx <= x1; gx++) { const arr = this.map.get(gx + ',' + gy); if (arr) for (const e of arr) { const dx = e.x - x, dy = e.y - y; if (dx * dx + dy * dy <= r * r) out.push(e); } } } return out; } nearest(x, y, maxR, filter) { let best = null, bd = maxR * maxR; const cand = this.query(x, y, maxR, []); for (const e of cand) { if (filter && !filter(e)) continue; const d = (e.x - x) ** 2 + (e.y - y) ** 2; if (d < bd) { bd = d; best = e; } } return best; } } D2.entities = { Player, Monster, Projectile, Pickup, Grid }; })(window.D2);