Diablo2D — Shadows of Tristram: complete browser ARPG
- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting) - 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X - Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash - Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage - NPC quest chain (accept -> hunt -> turn in) with rewards & gating - Procedural WebAudio SFX + generative music, EN/VI localization - Saves, settings, waypoints, hardcore mode, PWA manifest - 93-assertion headless suite + browser E2E via CDP
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/* ============================================================
|
||||
* 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);
|
||||
Reference in New Issue
Block a user