import type { Payload } from "payload"; import { emitGameEvent } from "@/utils/event-log/emit"; import { EventTypes } from "@/utils/event-log/eventTypes"; import { broadcastToUser } from "@/lib/realtime/bus"; type ScoreboardField = "infantryKills" | "vehicleKills" | "armorKills" | "airKills" | "deaths"; const VEHICLE_CLASS_AIR = /(^|[_])(Heli|Plane|VTOL|UAV)(_|$)/i; const VEHICLE_CLASS_ARMOR = /(^|[_])(MBT|APC|IFV|Tank|LT[_]?)(_|$)/i; const VEHICLE_CLASS_INFANTRY = /(^|[_])(Soldier|Crew|Officer|Sniper|Spotter|Medic|Engineer|Rifleman|Grenadier|MachineGunner)(_|$)/i; export function classifyKillCategory(victimVehicle: string | null | undefined): ScoreboardField { if (!victimVehicle) return "infantryKills"; if (VEHICLE_CLASS_AIR.test(victimVehicle)) return "airKills"; if (VEHICLE_CLASS_ARMOR.test(victimVehicle)) return "armorKills"; if (VEHICLE_CLASS_INFANTRY.test(victimVehicle)) return "infantryKills"; return "vehicleKills"; } interface KillPayload { victimName: string; killerName: string; killerSteamId: string; victimVehicle: string | null; killerVehicle: string | null; } export function parseKillPayload(payload: unknown): KillPayload | null { if (!Array.isArray(payload) || payload.length < 6) return null; const victimName = String(payload[0] ?? ""); const killerName = String(payload[2] ?? ""); const killerSteamId = String(payload[3] ?? ""); const victimVehicle = payload[6] ? String(payload[6]) : null; const killerVehicle = payload[7] ? String(payload[7]) : null; if (!killerSteamId) return null; return { victimName, killerName, killerSteamId, victimVehicle, killerVehicle }; } export async function processPlayerKill( payload: Payload, eventDoc: { id: number; payload: unknown; occurredAt: string }, ): Promise { const kill = parseKillPayload(eventDoc.payload); if (!kill) return; const users = await payload.find({ collection: "users", where: { steamId: { equals: kill.killerSteamId } }, limit: 1, depth: 0, overrideAccess: true, }); const user = users.docs[0]; if (!user) { payload.logger.warn( `[KillTracker] No user found for Steam ID ${kill.killerSteamId}; kill not recorded.`, ); return; } const profiles = await payload.find({ collection: "profiles", where: { user: { equals: user.id } }, limit: 1, depth: 0, overrideAccess: true, }); const profile = profiles.docs[0]; if (!profile) { payload.logger.warn( `[KillTracker] No profile found for user ${user.id} (${user.username}); kill not recorded.`, ); return; } const field = classifyKillCategory(kill.victimVehicle); const current = (profile.progression?.scoreboard as Record)?.[field] ?? 0; await payload.update({ collection: "profiles", id: profile.id, data: { progression: { scoreboard: { ...((profile.progression?.scoreboard as Record) ?? {}), [field]: current + 1, }, }, }, depth: 0, overrideAccess: true, }); await emitGameEvent(payload, { type: EventTypes.PlayerKill, actor: user.id, message: `${kill.killerName} eliminated ${kill.victimName} (${field.replace("Kills", "")})`, targetCollection: "users", targetId: user.id, data: { victim: kill.victimName, category: field, victimVehicle: kill.victimVehicle, killerVehicle: kill.killerVehicle, }, }); broadcastToUser("profile-update", user.id, { profileId: profile.id }); }