This commit is contained in:
Linrador 2025-09-09 13:51:31 +02:00
parent 144335f503
commit 97e52dba2d
2 changed files with 103 additions and 2 deletions

View File

@ -146,6 +146,14 @@ type PlayerState = {
defuse?: boolean | null defuse?: boolean | null
} }
type BombState = {
x: number
y: number
z: number
status: 'carried'|'dropped'|'planted'|'unknown'
changedAt: number
}
type Grenade = { type Grenade = {
id: string id: string
kind: 'smoke' | 'molotov' | 'he' | 'flash' | 'decoy' | 'unknown' kind: 'smoke' | 'molotov' | 'he' | 'flash' | 'decoy' | 'unknown'
@ -186,6 +194,10 @@ export default function LiveRadar() {
const deathMarkersRef = useRef<DeathMarker[]>([]) const deathMarkersRef = useRef<DeathMarker[]>([])
const [deathMarkers, setDeathMarkers] = useState<DeathMarker[]>([]) const [deathMarkers, setDeathMarkers] = useState<DeathMarker[]>([])
// Bomb
const bombRef = useRef<BombState | null>(null)
const [bomb, setBomb] = useState<BombState | null>(null)
// Flush // Flush
const flushTimer = useRef<number | null>(null) const flushTimer = useRef<number | null>(null)
const scheduleFlush = () => { const scheduleFlush = () => {
@ -196,8 +208,11 @@ export default function LiveRadar() {
setGrenades(Array.from(grenadesRef.current.values())) setGrenades(Array.from(grenadesRef.current.values()))
setTrails(Array.from(trailsRef.current.values())) setTrails(Array.from(trailsRef.current.values()))
setDeathMarkers([...deathMarkersRef.current]) setDeathMarkers([...deathMarkersRef.current])
updateBombFromPlayers()
setBomb(bombRef.current)
}, 66) }, 66)
} }
useEffect(() => { useEffect(() => {
return () => { return () => {
if (flushTimer.current != null) { if (flushTimer.current != null) {
@ -212,6 +227,7 @@ export default function LiveRadar() {
deathMarkersRef.current = [] deathMarkersRef.current = []
trailsRef.current.clear() trailsRef.current.clear()
grenadesRef.current.clear() grenadesRef.current.clear()
bombRef.current = null
scheduleFlush() scheduleFlush()
} }
useEffect(() => { useEffect(() => {
@ -265,6 +281,59 @@ export default function LiveRadar() {
} }
} }
/* ───────── Bomben-Helper ───────── */
function pickVec3(src:any) {
const p = src?.pos ?? src?.position ?? src?.location ?? src?.coordinates
if (Array.isArray(p)) return { x: asNum(p[0]), y: asNum(p[1]), z: asNum(p[2]) }
if (typeof p === 'string') return parseVec3String(p)
return { x: asNum(src?.x), y: asNum(src?.y), z: asNum(src?.z) }
}
function normalizeBomb(raw:any): BombState | null {
if (!raw) return null
// „event“-Hüllen abfangen
const payload = raw.bomb ?? raw.c4 ?? raw
const pos = pickVec3(payload)
let status: BombState['status'] = 'unknown'
const s = String(payload?.status ?? payload?.state ?? '').toLowerCase()
if (s.includes('plant')) status = 'planted'
else if (s.includes('drop')) status = 'dropped'
else if (s.includes('carry')) status = 'carried'
// bool-Varianten
if (payload?.planted === true) status = 'planted'
if (payload?.dropped === true) status = 'dropped'
if (payload?.carried === true) status = 'carried'
// Event-Typen
const t = String(raw?.type ?? '').toLowerCase()
if (t === 'bomb_planted') status = 'planted'
if (t === 'bomb_dropped') status = 'dropped'
if (t === 'bomb_pickup') status = 'carried'
// Ohne brauchbare Position: ignorieren
if (!Number.isFinite(pos.x) || !Number.isFinite(pos.y)) return null
return { x: pos.x, y: pos.y, z: pos.z, status, changedAt: Date.now() }
}
// Fallback: aus Spielerzustand ableiten (Bombenträger)
const updateBombFromPlayers = () => {
const carrier = Array.from(playersRef.current.values()).find(p => p.hasBomb)
if (carrier) {
bombRef.current = {
x: carrier.x, y: carrier.y, z: carrier.z,
status: 'carried', changedAt: Date.now()
}
} else if (bombRef.current?.status === 'carried') {
// Träger hat die Bombe nicht mehr → als „gedroppt“ an letzter Position belassen
bombRef.current = { ...bombRef.current, status: 'dropped', changedAt: Date.now() }
}
}
/* ───────── Positions-Callbacks ───────── */ /* ───────── Positions-Callbacks ───────── */
const addDeathMarker = (x:number, y:number, idHint?: string) => { const addDeathMarker = (x:number, y:number, idHint?: string) => {
deathMarkersRef.current.push({ id: idHint ?? `d#${Date.now()}`, x, y, t: Date.now() }) deathMarkersRef.current.push({ id: idHint ?? `d#${Date.now()}`, x, y, t: Date.now() })
@ -664,6 +733,8 @@ export default function LiveRadar() {
onPlayerUpdate={(p)=> { upsertPlayer(p); scheduleFlush() }} onPlayerUpdate={(p)=> { upsertPlayer(p); scheduleFlush() }}
onPlayersAll={(m)=> { handlePlayersAll(m); scheduleFlush() }} onPlayersAll={(m)=> { handlePlayersAll(m); scheduleFlush() }}
onGrenades={(g)=> { handleGrenades(g); scheduleFlush() }} onGrenades={(g)=> { handleGrenades(g); scheduleFlush() }}
onRoundStart={() => { bombRef.current = null; scheduleFlush() }} // ⬅️ sinnvoll
onBomb={(b)=> { const nb = normalizeBomb(b); if (nb) { bombRef.current = nb; scheduleFlush() } }} // ⬅️ NEU
/> />
{/* Inhalt: 3-Spalten-Layout (T | Radar | CT) */} {/* Inhalt: 3-Spalten-Layout (T | Radar | CT) */}
@ -772,6 +843,30 @@ export default function LiveRadar() {
return <circle key={g.id} cx={P.x} cy={P.y} r={Math.max(4, rPx*0.4)} fill={g.kind === 'he' ? UI.nade.heFill : '#999'} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} /> return <circle key={g.id} cx={P.x} cy={P.y} r={Math.max(4, rPx*0.4)} fill={g.kind === 'he' ? UI.nade.heFill : '#999'} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} />
})} })}
{/* Bombe */}
{bomb && (() => {
const P = worldToPx(bomb.x, bomb.y)
if (!Number.isFinite(P.x) || !Number.isFinite(P.y)) return null
const r = Math.max(6, unitsToPx(28)) // Marker-Größe
const color = bomb.status === 'planted' ? '#ef4444' : '#dddddd'
return (
<g key={`bomb-${bomb.changedAt}`}>
{/* Ping nur wenn geplantet */}
{bomb.status === 'planted' && (
<circle cx={P.x} cy={P.y} r={r} fill="none" stroke="#ef4444" strokeWidth={2}>
<animate attributeName="r" from={r} to={r*3} dur="1.2s" repeatCount="indefinite" />
<animate attributeName="opacity" from="0.6" to="0" dur="1.2s" repeatCount="indefinite" />
</circle>
)}
{/* Marker */}
<circle cx={P.x} cy={P.y} r={r} fill={color} stroke="#111" strokeWidth={2} />
<text x={P.x} y={P.y + r*0.35} textAnchor="middle" fontSize={r} fill="#fff" fontWeight="700">B</text>
</g>
)
})()}
{/* Spieler */} {/* Spieler */}
{players {players
.filter(p => (p.team === 'CT' || p.team === 'T') && p.alive !== false) .filter(p => (p.team === 'CT' || p.team === 'T') && p.alive !== false)

View File

@ -11,8 +11,9 @@ type PositionsSocketProps = {
onPlayerUpdate?: (p: any) => void onPlayerUpdate?: (p: any) => void
onPlayersAll?: (allplayers: any) => void onPlayersAll?: (allplayers: any) => void
onGrenades?: (g: any) => void onGrenades?: (g: any) => void
onRoundStart?: () => void // ⬅️ NEU onRoundStart?: () => void
onRoundEnd?: () => void // ⬅️ optional onRoundEnd?: () => void
onBomb?: (b:any) => void
} }
export default function PositionsSocket({ export default function PositionsSocket({
@ -24,6 +25,7 @@ export default function PositionsSocket({
onGrenades, onGrenades,
onRoundStart, onRoundStart,
onRoundEnd, onRoundEnd,
onBomb
}: PositionsSocketProps) { }: PositionsSocketProps) {
const wsRef = useRef<WebSocket | null>(null) const wsRef = useRef<WebSocket | null>(null)
const aliveRef = useRef(true) const aliveRef = useRef(true)
@ -41,6 +43,7 @@ export default function PositionsSocket({
if (typeof msg.map === 'string') onMap?.(msg.map.toLowerCase()) if (typeof msg.map === 'string') onMap?.(msg.map.toLowerCase())
if (Array.isArray(msg.players)) msg.players.forEach(onPlayerUpdate ?? (() => {})) if (Array.isArray(msg.players)) msg.players.forEach(onPlayerUpdate ?? (() => {}))
if (msg.grenades) onGrenades?.(msg.grenades) if (msg.grenades) onGrenades?.(msg.grenades)
if (msg.bomb) onBomb?.(msg.bomb)
return return
} }
@ -48,6 +51,9 @@ export default function PositionsSocket({
if (msg.allplayers) onPlayersAll?.(msg) if (msg.allplayers) onPlayersAll?.(msg)
if (msg.player || msg.steamId || msg.position || msg.pos) onPlayerUpdate?.(msg) if (msg.player || msg.steamId || msg.position || msg.pos) onPlayerUpdate?.(msg)
if (msg.grenades && msg.type !== 'tick') onGrenades?.(msg.grenades) if (msg.grenades && msg.type !== 'tick') onGrenades?.(msg.grenades)
if (msg.bomb || msg.c4 || ['bomb_planted','bomb_dropped','bomb_pickup'].includes(String(msg.type).toLowerCase())) {
onBomb?.(msg)
}
} }
useEffect(() => { useEffect(() => {