cs2-web/wwwroot/js/weapons.js
2026-07-07 20:23:39 +03:00

99 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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).
})();