// /src/app/radar/TeamSidebar.tsx 'use client' import React, { useEffect, useState } from 'react' import { useAvatarDirectoryStore } from '../../lib/useAvatarDirectoryStore' export type Team = 'T' | 'CT' export type SidebarPlayer = { id: string name?: string | null hp?: number | null armor?: number | null helmet?: boolean | null defuse?: boolean | null hasBomb?: boolean | null alive?: boolean | null activeWeapon?: string | { name?: string | null } | null grenades?: Partial> | null weapons?: { name: string; state?: string | null }[] | null } const EQUIP_BASE = '/assets/img/icons/equipment' const equipIcon = (file: string) => `${EQUIP_BASE}/${file}` /* ── Inline SVG Icons (weiß via currentColor) ── */ const HeartIcon = ({ className = 'w-3.5 h-3.5' }: { className?: string }) => ( ) const ShieldIcon = ({ className = 'w-3.5 h-3.5' }: { className?: string }) => ( ) /* ── Rotes Bomben-Icon via CSS-Maske, damit es sicher rot ist ── */ const BombMaskIcon = ({ src, title, className = 'h-3.5 w-3.5' }: { src: string; title?: string; className?: string }) => ( ) /* ── Gear Blöcke (links/rechts trennen) ── */ function leftGear(opts: { armor?: number|null; helmet?: boolean|null }) { const out: { src: string; title: string; key: string }[] = [] if ((opts.armor ?? 0) > 0) out.push({ src: equipIcon('armor.svg'), title: 'Kevlar', key: 'armor' }) if (opts.helmet) out.push({ src: equipIcon('helmet.svg'), title: 'Helmet', key: 'helmet' }) return out } function rightGear(opts: { hasBomb?: boolean|null; team: Team; defuse?: boolean|null }) { const out: { src: string; title: string; key: string }[] = [] if (opts.hasBomb) out.push({ src: equipIcon('c4.svg'), title: 'C4', key: 'c4' }) if (opts.team === 'CT' && opts.defuse) out.push({ src: equipIcon('defuser.svg'), title: 'Defuse Kit', key: 'defuser' }) return out } /* ── Normalisierung ── */ function normWeaponName(raw?: string | null) { if (!raw) return '' let k = String(raw).toLowerCase().replace(/^weapon_/, '').replace(/\s+/g, '') if (k === 'usp-s' || k === 'usp-silencer') k = 'usp_silencer' if (k === 'm4a1-s' || k === 'm4a1s') k = 'm4a1_silencer' if (k === 'm4a1s_off' || k === 'm4a1-s_off') k = 'm4a1_silencer_off' return k } function isActiveWeapon(itemName?: string|null, active?: string | { name?: string|null } | null, state?: string|null) { if ((state ?? '').toLowerCase() === 'active') return true const ni = normWeaponName(itemName) const na = typeof active === 'string' ? normWeaponName(active) : normWeaponName(active?.name ?? null) return !!ni && !!na && ni === na } /* ── Sets ── */ const GRENADE_SET = new Set(['hegrenade','smokegrenade','flashbang','decoy','molotov','incgrenade']) const PRIMARY_SET = new Set([ 'ak47','aug','sg556','galilar','famas','m4a1','m4a1_silencer','m4a1_silencer_off', 'awp','ssg08','scar20','g3sg1','xm1014','mag7','sawedoff','nova','m249','negev', 'p90','ump45','mp9','mp7','mp5sd','mac10','bizon' ]) const SECONDARY_SET = new Set([ 'hkp2000','p2000','p250','glock','deagle','elite','usp_silencer','usp_silencer_off', 'fiveseven','cz75a','tec9','revolver','taser' ]) /* ── Icons ── */ const WEAPON_ALIAS: Record = { // Pistols 'hkp2000':'hkp2000','p2000':'p2000','p250':'p250','glock':'glock', 'deagle':'deagle','elite':'elite','usp_silencer':'usp_silencer','usp':'usp_silencer', 'usp_silencer_off':'usp_silencer_off','fiveseven':'fiveseven','cz75a':'cz75a','tec9':'tec9','revolver':'revolver', // SMGs 'mac10':'mac10','mp7':'mp7','mp5sd':'mp5sd','mp9':'mp9','bizon':'bizon','ump45':'ump45','p90':'p90', // Rifles 'ak47':'ak47','aug':'aug','sg556':'sg556','galilar':'galilar','famas':'famas', 'm4a1':'m4a1','m4a1_silencer':'m4a1_silencer','m4a1_silencer_off':'m4a1_silencer_off', // Snipers / Heavy / Shotguns 'awp':'awp','ssg08':'ssg08','scar20':'scar20','g3sg1':'g3sg1', 'xm1014':'xm1014','mag7':'mag7','sawedoff':'sawedoff','nova':'nova', 'm249':'m249','negev':'negev', // Grenades / misc 'hegrenade':'hegrenade','incgrenade':'incgrenade','molotov':'molotov', 'smokegrenade':'smokegrenade','flashbang':'flashbang','decoy':'decoy', 'taser':'taser','defuser':'defuser','c4':'c4','planted_c4':'planted_c4', // Knives 'knife':'knife','knife_t':'knife_t','melee':'melee' } function weaponIconFromName(raw?: string | null): string | null { if (!raw) return null const k = normWeaponName(raw) const file = WEAPON_ALIAS[k] return file ? equipIcon(`${file}.svg`) : null } const GRENADE_DISPLAY_ORDER = ['flashbang','smokegrenade','hegrenade','molotov','incgrenade','decoy'] as const function grenadeIconFromKey(k: string): string { switch (k) { case 'hegrenade': return equipIcon('hegrenade.svg') case 'smokegrenade':return equipIcon('smokegrenade.svg') case 'flashbang': return equipIcon('flashbang.svg') case 'decoy': return equipIcon('decoy.svg') case 'molotov': return equipIcon('molotov.svg') case 'incgrenade': return equipIcon('incgrenade.svg') default: return equipIcon('hegrenade.svg') } } function activeWeaponNameOf(w?: string | { name?: string | null } | null): string | null { if (!w) return null if (typeof w === 'string') return w if (typeof w === 'object' && w?.name) return w.name return null } export default function TeamSidebar({ team, teamId, players, align = 'left', onHoverPlayer, score, oppScore }: { team: Team teamId?: string players: SidebarPlayer[] align?: 'left' | 'right' onHoverPlayer?: (id: string | null) => void score?: number oppScore?: number }) { const [teamLogo, setTeamLogo] = useState(null) const [teamApiName, setTeamApiName] = useState(null) const BOT_ICON = '/assets/img/icons/ui/bot.svg' const isBotId = (id: string) => id?.toUpperCase().startsWith('BOT:') useEffect(() => { let abort = false ;(async () => { if (!teamId) { setTeamLogo(null); setTeamApiName(null); return } try { const res = await fetch(`/api/team/${teamId}`, { cache: 'no-store' }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const data = await res.json() if (!abort) { setTeamLogo(data?.logo || null); setTeamApiName(data?.name || null) } } catch { if (!abort) { setTeamLogo(null); setTeamApiName(null) } } })() return () => { abort = true } }, [teamId]) const ensureTeamsLoaded = useAvatarDirectoryStore(s => s.ensureTeamsLoaded) const avatarById = useAvatarDirectoryStore(s => s.byId) const avatarVer = useAvatarDirectoryStore(s => s.version) useEffect(() => { if (teamId) ensureTeamsLoaded([teamId]) }, [teamId, ensureTeamsLoaded]) const defaultTeamName = team === 'CT' ? 'Counter-Terrorists' : 'Terrorists' const teamName = teamApiName || defaultTeamName const teamColor = team === 'CT' ? 'text-blue-400' : 'text-amber-400' const barArmor = team === 'CT' ? 'bg-blue-500' : 'bg-amber-500' const ringColor = team === 'CT' ? 'ring-blue-500' : 'ring-amber-500' const isRight = align === 'right' const fallbackLogo = '/assets/img/logos/cs2.webp' const logoSrc = teamLogo || fallbackLogo const aliveCount = players.filter(p => p.alive !== false && (p.hp ?? 1) > 0).length const sorted = [...players].sort((a,b)=>{ const al = (b.alive ? 1 : 0) - (a.alive ? 1 : 0); if (al) return al const hp = (b.hp ?? -1) - (a.hp ?? -1); if (hp) return hp return (a.name ?? '').localeCompare(b.name ?? '') }) return ( ) } function clamp(n: number, a: number, b: number) { return Math.max(a, Math.min(b, n)) }