- 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
46 lines
1.3 KiB
JavaScript
46 lines
1.3 KiB
JavaScript
/* ============================================================
|
|
* Diablo2D — fov.js : raycast visibility & fog of war
|
|
* ============================================================ */
|
|
'use strict';
|
|
window.D2 = window.D2 || {};
|
|
(function (D2) {
|
|
|
|
const RAY_COUNT = 160;
|
|
|
|
/**
|
|
* Update world.visible / world.explored from (cx, cy) within radius.
|
|
* world must expose: w, h, transparent(x,y), visible(Uint8), explored(Uint8)
|
|
*/
|
|
function compute(world, cx, cy, radius) {
|
|
const { w, h } = world;
|
|
world.visible.fill(0);
|
|
const r2 = radius * radius;
|
|
|
|
const mark = (x, y) => {
|
|
if (x < 0 || y < 0 || x >= w || y >= h) return;
|
|
const i = y * w + x;
|
|
world.visible[i] = 1;
|
|
world.explored[i] = 1;
|
|
};
|
|
|
|
mark(cx, cy);
|
|
|
|
for (let i = 0; i < RAY_COUNT; i++) {
|
|
const ang = (i / RAY_COUNT) * Math.PI * 2;
|
|
const dx = Math.cos(ang), dy = Math.sin(ang);
|
|
let x = cx + 0.5, y = cy + 0.5;
|
|
for (let d = 0; d < radius; d += 0.5) {
|
|
x += dx * 0.5; y += dy * 0.5;
|
|
const tx = x | 0, ty = y | 0;
|
|
if (tx < 0 || ty < 0 || tx >= w || ty >= h) break;
|
|
const ddx = x - cx - 0.5, ddy = y - cy - 0.5;
|
|
if (ddx * ddx + ddy * ddy > r2) break;
|
|
mark(tx, ty);
|
|
if (!world.transparent(tx, ty)) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
D2.fov = { compute };
|
|
})(window.D2);
|