From 02fc0aa4ee1e06b0661d4e915279ad23e3f4ea44 Mon Sep 17 00:00:00 2001 From: Linrador Date: Tue, 9 Sep 2025 23:32:22 +0200 Subject: [PATCH] updated --- server.js | 115 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/server.js b/server.js index 6f6b385..2781ad9 100644 --- a/server.js +++ b/server.js @@ -101,6 +101,81 @@ function parseVec3(str) { return { x, y, z }; } +function pickVec3Any(pos) { + if (!pos) return { x: 0, y: 0, z: 0 }; + if (Array.isArray(pos)) return { x: +pos[0]||0, y: +pos[1]||0, z: +pos[2]||0 }; + if (typeof pos === 'string') return parseVec3(pos); + return { x: +pos.x||0, y: +pos.y||0, z: +pos.z||0 }; +} + +const NADE_DEFAULTS = { + smoke: { radius: 150, lifetimeMs: 18_000 }, + molotov:{ radius: 120, lifetimeMs: 7_000 }, + he: { radius: 40, lifetimeMs: 300 }, + flash: { radius: 36, lifetimeMs: 300 }, + decoy: { radius: 80, lifetimeMs: 15_000 }, +}; + +function normalizeGrenadesFromGSI(raw, now) { + if (!raw || typeof raw !== 'object') return []; + + // Dr. Weissbrot Format: buckets (smokes, infernos, hegrenades, flashes, decoys, ...), + // evtl. auch *_projectile – beides unterstützen. + const mapKind = (k) => { + const s = k.toLowerCase(); + if (s.includes('smoke')) return 'smoke'; + if (s.includes('inferno') || s.includes('molotov') || s.includes('incendiary')) return 'molotov'; + if (s.includes('flash')) return 'flash'; + if (s.includes('decoy')) return 'decoy'; + if (s.includes('he')) return 'he'; + return 'unknown'; + }; + + const pushList = (acc, kind, list) => { + if (!list) return; + const arr = Array.isArray(list) ? list : Object.values(list); + let i = 0; + for (const g of arr) { + const pos = pickVec3Any(g?.position || g?.pos || g?.location || g); + const id = + String(g?.id ?? g?.entityid ?? g?.entindex ?? + `${kind}#${Math.round(pos.x)}:${Math.round(pos.y)}:${i++}`); + + const ownerTeam = (g?.owner?.team || g?.team || '').toString().toUpperCase(); + const team = ownerTeam === 'T' || ownerTeam === 'CT' ? ownerTeam : null; + + const def = NADE_DEFAULTS[kind] || { radius: 60, lifetimeMs: 2_000 }; + const bornAt = + Number.isFinite(+g?.lifetime) ? (now - Math.max(0, +g.lifetime * 1000)) + : Number.isFinite(+g?.spawn_time) ? +g.spawn_time + : now; + + // Für aktive Effekte (z.B. smoke/inferno) gibt es teils direkte "expire"-Infos. + const radius = Number.isFinite(+g?.radius) ? +g.radius : def.radius; + const expiresAt = Number.isFinite(+g?.expiresAt) + ? +g.expiresAt + : bornAt + def.lifetimeMs; + + acc.push({ + id, + kind, + x: pos.x, y: pos.y, z: pos.z, + radius, + expiresAt, + team, + }); + } + }; + + const out = []; + for (const [k, v] of Object.entries(raw)) { + // z.B. "smokes", "smokegrenade_projectile", "inferno", "hegrenade_projectile" ... + const kind = mapKind(k); + pushList(out, kind, v); + } + return out; +} + function forwardToYawPitch(fwd) { const yaw = Math.atan2(fwd.y || 0, fwd.x || 0) * 180 / Math.PI; const z = Math.max(-1, Math.min(1, fwd.z || 0)); @@ -141,20 +216,27 @@ function normalizeBombFromGSI(body) { : { x: Number(b?.x) || 0, y: Number(b?.y) || 0, z: Number(b?.z) || 0 }; let raw = String(b?.state || b?.status || "").toLowerCase(); - let status = "unknown"; // 'planted'|'dropped'|'carried'|'unknown' - if (raw.includes("plant")) status = "planted"; - else if (raw.includes("drop")) status = "dropped"; - else if (raw.includes("carry")) status = "carried"; + let status = "unknown"; + + if (raw.includes("planted")) status = "planted"; + else if (raw.includes("defusing")) status = "defusing"; + else if (raw.includes("defused")) status = "defused"; + else if (raw.includes("dropped")) status = "dropped"; + else if (raw.includes("carried")) status = "carried"; + // "planting" bewusst NICHT als planted behandeln // bool-Fallbacks - if (b?.planted === true) status = "planted"; - if (b?.dropped === true) status = "dropped"; - if (b?.carried === true) status = "carried"; + if (b?.planted === true) status = "planted"; + if (b?.defusing === true) status = "defusing"; + if (b?.defused === true) status = "defused"; + if (b?.dropped === true) status = "dropped"; + if (b?.carried === true) status = "carried"; if (!Number.isFinite(pos.x) || !Number.isFinite(pos.y)) return null; return { x: pos.x, y: pos.y, z: pos.z, status }; } + function sameBomb(a, b) { if (!a && !b) return true; if (!a || !b) return false; @@ -253,17 +335,29 @@ app.post(GSI_PATH, (req, res) => { } // 4) Grenades roh (du kannst bei Bedarf normalisieren) - const grenades = body?.grenades || {}; + const grenades = normalizeGrenadesFromGSI(body?.grenades || {}, Date.now()); // 5) Bombe normalisieren + Events bei Statuswechsel const bomb = normalizeBombFromGSI(body); if (!sameBomb(bomb, lastBomb)) { if (bomb) { - if (bomb.status === "planted" && (!lastBomb || lastBomb.status !== "planted")) { + const prev = lastBomb; + // echte Plant-Transition + if (bomb.status === "planted" && (!prev || prev.status !== "planted")) { broadcast({ type: "bomb_planted", bomb }); + // Defuse begonnen + } else if (bomb.status === "defusing" && (!prev || prev.status !== "defusing")) { + broadcast({ type: "bomb_begindefuse", bomb }); + // Defuse abgebrochen (zurück zu planted) + } else if (prev && prev.status === "defusing" && bomb.status === "planted") { + broadcast({ type: "bomb_abortdefuse", bomb }); + // Erfolgreich entschärft + } else if (bomb.status === "defused" && (!prev || prev.status !== "defused")) { + broadcast({ type: "bomb_defused", bomb }); + // Drop / Pickup } else if (bomb.status === "dropped") { broadcast({ type: "bomb_dropped", bomb }); - } else if (bomb.status === "carried" && lastBomb && lastBomb.status === "dropped") { + } else if (bomb.status === "carried" && prev && prev.status === "dropped") { broadcast({ type: "bomb_pickup", bomb }); } else { broadcast({ type: "bomb", bomb }); @@ -274,6 +368,7 @@ app.post(GSI_PATH, (req, res) => { lastBomb = bomb || null; } + // 6) Tick mit kompletter Momentaufnahme (inkl. bomb) broadcast({ type: "tick",