// Weapons/STK table: mirrors Services/Weapons.cs WeaponMath function-for-function with the SAME operation order, // so the load-time parity check (SSR default state: distance 0, armored, vanilla — pow(x,0)=1 keeps libm out of // the picture) compares exact integers. The four global constants (stomach ×1.25, legs ×0.75 + no armor on legs, // armor factor ×0.5, falloff divisor 500) live in compiled engine code, not in the extracted data — they're // validated against anchor numbers (AK-47 helmet HS = 111, USP-S = 70, Glock = 56, AWP armored legs = 86). (function () { "use strict"; const dataEl = document.getElementById("weapons-data"); if (!dataEl) return; let data; try { data = JSON.parse(dataEl.textContent); } catch { return; } if (!data?.Weapons?.length) return; const HG = ["head", "chest", "stomach", "legs"]; // WeaponMath.Hitgroups order (SSR cells + Ssr parity blob) const $ = function (id) { return document.getElementById(id); }; const dist = $("wpn-dist"), distv = $("wpn-distv"), armor = $("wpn-armor"), mod = $("wpn-mod"); // WeaponMath.Pellet: raw per-pellet damage, unfloored. The mod layer composes HERE, pre-floor: the plugin // (Stats.cs) multiplies float info.Damage before the engine applies armor and truncates, and multiplication // commutes — head applies the hs_damage chain ON TOP of the engine's per-weapon hsMult. out/hsx = 1 is exact. function pellet(w, d, hg, armored, out, hsx) { if (d > w.Range) return 0; // beyond the weapon's own max bullet range nothing connects let v = w.Dmg * Math.pow(w.RangeMod, d / 500.0); v *= hg === "head" ? w.HsMult : hg === "stomach" ? 1.25 : hg === "legs" ? 0.75 : 1.0; if (armored && hg !== "legs") v *= w.ArmorRatio * 0.5; v *= out; if (hg === "head") v *= hsx; return v; } // WeaponMath.Shot: floored per pellet (each pellet is its own trace), all pellets in the same hitgroup. function shot(w, d, hg, armored, out, hsx) { return Math.floor(pellet(w, d, hg, armored, out, hsx)) * w.Pellets; } // WeaponMath.Stk: 0 = can't kill (out of range). function stk(s) { return s <= 0 ? 0 : Math.ceil(100 / s); } function render() { const d = Number.parseFloat(dist.value) || 0; const armored = armor.checked; const sim = mod.checked && window.OGSim ? window.OGSim : { out: 1, hs: 1 }; distv.textContent = d + " u"; data.Weapons.forEach(function (w) { const row = document.querySelector('#wpn-table tr[data-wid="' + w.Id + '"]'); if (!row) return; const s = HG.map(function (hg) { return shot(w, d, hg, armored, sim.out, sim.hs); }); row.querySelector('[data-c="dmg"]').textContent = s[1]; HG.forEach(function (hg, i) { const k = stk(s[i]); row.querySelector('[data-c="' + hg + '"]').textContent = k > 0 ? k : "—"; }); const kc = stk(s[1]); row.querySelector('[data-c="ttk"]').textContent = kc > 0 ? ((kc - 1) * w.Cycle).toFixed(2) + " s" : "—"; }); } // Drift oracle (same pattern as theory.js): recompute the SSR default state and compare against the server's // own numbers shipped in the blob. Any mismatch means this file drifted from WeaponMath and self-reports. function verifyParity() { for (const row of data.Ssr || []) { const w = data.Weapons.find(function (x) { return x.Id === row.Id; }); if (!w) continue; for (let i = 0; i < HG.length; i++) { const js = shot(w, 0, HG[i], true, 1, 1); if (js !== row.Shot[i]) { console.warn("weapons.js drift vs server:", row.Id, HG[i], "js=" + js, "server=" + row.Shot[i]); const banner = $("wpn-drift"); if (banner) banner.hidden = false; return false; } } } return true; } // Explicit defaults (browsers restore form state on back-nav; the SSR cells are only true for THIS state). dist.value = 0; armor.checked = true; armor.disabled = false; mod.checked = false; // Gate on the sim actually having initialized, not on the blob element existing: #sim-data can render as // literal null (fleet reachable, balance payload without usable curves) and theory.js bails on several other // early returns too. theory.js runs first (defer order) and publishes OGSim at load when — and only when — // the simulator is live, so its absence HERE is definitive. if (!window.OGSim) { mod.disabled = true; mod.parentElement.title = "needs a live server config (the simulator above)"; } dist.addEventListener("input", render); armor.addEventListener("change", render); mod.addEventListener("change", function () { // Bots always wear the assault suit — with the build applied, "unarmored" models nothing real. armor.disabled = mod.checked; if (mod.checked) armor.checked = true; render(); }); document.addEventListener("og:sim", function () { if (mod.checked) render(); }); verifyParity(); // No initial render: the SSR cells already ARE the default state (and remain server truth without JS). })();