/* ============================================================ * Diablo2D — util.js : math, RNG, misc helpers * ============================================================ */ 'use strict'; window.D2 = window.D2 || {}; (function (D2) { const TAU = Math.PI * 2; const clamp = (v, a, b) => v < a ? a : (v > b ? b : v); const lerp = (a, b, t) => a + (b - a) * t; const smoothstep = (a, b, x) => { const t = clamp((x - a) / (b - a), 0, 1); return t * t * (3 - 2 * t); }; const dist2 = (ax, ay, bx, by) => { const dx = ax - bx, dy = ay - by; return dx * dx + dy * dy; }; const dist = (ax, ay, bx, by) => Math.sqrt(dist2(ax, ay, bx, by)); const angleTo = (ax, ay, bx, by) => Math.atan2(by - ay, bx - ax); const wrapAngle = a => { while (a > Math.PI) a -= TAU; while (a < -Math.PI) a += TAU; return a; }; const approachAngle = (cur, target, maxStep) => { let d = wrapAngle(target - cur); if (Math.abs(d) <= maxStep) return target; return cur + Math.sign(d) * maxStep; }; const easeOutCubic = t => 1 - Math.pow(1 - t, 3); const easeInQuad = t => t * t; /* deterministic RNG */ function mulberry32(seed) { let a = seed >>> 0; return function () { a |= 0; a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } class RNG { constructor(seed) { this.seedFn = mulberry32(seed); this.seed = seed >>> 0; } next() { return this.seedFn(); } range(a, b) { return a + this.seedFn() * (b - a); } // float [a,b) int(a, b) { return Math.floor(this.range(a, b + 1)); } // int [a,b] chance(p) { return this.seedFn() < p; } pick(arr) { return arr[Math.floor(this.seedFn() * arr.length)]; } weighted(entries) { // [{v,w}] or [[val,weight]] let total = 0; for (const e of entries) total += e.w !== undefined ? e.w : e[1]; let roll = this.seedFn() * total; for (const e of entries) { roll -= e.w !== undefined ? e.w : e[1]; if (roll <= 0) return e.v !== undefined ? e.v : e[0]; } const last = entries[entries.length - 1]; return last.v !== undefined ? last.v : last[0]; } shuffle(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(this.seedFn() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } } let _uid = 1; const uid = () => _uid++; function fmtNum(n) { n = Math.round(n); if (Math.abs(n) >= 1000000) return (n / 1000000).toFixed(1) + 'M'; if (Math.abs(n) >= 10000) return (n / 1000).toFixed(1) + 'k'; return String(n); } function roman(n) { if (n <= 0) return '0'; const map = [[1000,'M'],[900,'CM'],[500,'D'],[400,'CD'],[100,'C'],[90,'XC'],[50,'L'],[40,'XL'],[10,'X'],[9,'IX'],[5,'V'],[4,'IV'],[1,'I']]; let out = ''; for (const [v, s] of map) while (n >= v) { out += s; n -= v; } return out; } function deepClone(o) { return JSON.parse(JSON.stringify(o)); } /* color helpers: '#rrggbb' <-> [r,g,b] */ function hexToRgb(hex) { const h = hex.replace('#', ''); return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]; } function rgbToHex(r, g, b) { const c = v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0'); return '#' + c(r) + c(g) + c(b); } function lerpColor(c1, c2, t) { const a = typeof c1 === 'string' ? hexToRgb(c1) : c1; const b = typeof c2 === 'string' ? hexToRgb(c2) : c2; return rgbToHex(lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t)); } function shade(hex, f) { // f<1 darken, f>1 lighten const [r, g, b] = hexToRgb(hex); return rgbToHex(r * f, g * f, b * f); } function rgba(hex, alpha) { const [r, g, b] = hexToRgb(hex); return `rgba(${r},${g},${b},${alpha})`; } /* simple event bus */ class Bus { constructor() { this._l = new Map(); } on(evt, fn) { if (!this._l.has(evt)) this._l.set(evt, []); this._l.get(evt).push(fn); } off(evt, fn) { const l = this._l.get(evt); if (l) { const i = l.indexOf(fn); if (i >= 0) l.splice(i, 1); } } emit(evt, ...args) { const l = this._l.get(evt); if (l) for (const fn of [...l]) fn(...args); } } D2.util = { TAU, clamp, lerp, smoothstep, dist, dist2, angleTo, wrapAngle, approachAngle, easeOutCubic, easeInQuad, mulberry32, RNG, uid, fmtNum, roman, deepClone, hexToRgb, rgbToHex, lerpColor, shade, rgba, Bus, }; })(window.D2);