// l4d2-common-kill-counter.sp — Left 4 Dead 2 SourceMod plugin // // Tracks how many common infected each player kills, plus headshot counts // and percentage, and announces the breakdown at map end. Good for adding // some friendly competition to a casual server. // // Build: // spcomp l4d2-common-kill-counter.sp -o l4d2-common-kill-counter.smx // Drop the .smx into addons/sourcemod/plugins/ on the server. // // Commands: // !kills show current map's kill counts to the caller // !killcount alias for !kills // // Stat announcement triggers: // - finale_win (after 3s so the victory animation plays first) // - map_transition (between chapters) // - mission_lost (wiped) // - round_end reason 5 or 8 (chapter end / mission complete) // // Output example: // [Stats] ===== Map Kill Counts ===== // [Stats] alice: 142 zombies (27 headshots, 19.0%) // [Stats] bob: 98 zombies (12 headshots, 12.2%) // [Stats] MVP: alice with 142 kills! // [Stats] Total team kills: 240 // // Author: magikh0e // License: GPL-3.0-or-later #include #include #pragma semicolon 1 #pragma newdecls required #define PLUGIN_VERSION "1.0" int g_iKills[MAXPLAYERS + 1]; int g_iHeadshots[MAXPLAYERS + 1]; int g_iMapStartKills[MAXPLAYERS + 1]; public Plugin myinfo = { name = "Common Kill Counter", author = "magikh0e", description = "Tracks common infected kills per player, announces at map end", version = PLUGIN_VERSION, url = "" }; public void OnPluginStart() { HookEvent("infected_death", Event_InfectedDeath); HookEvent("round_start", Event_RoundStart); HookEvent("map_transition", Event_MapEnd); HookEvent("mission_lost", Event_MapEnd); HookEvent("finale_win", Event_FinaleWin); HookEvent("round_end", Event_RoundEnd); RegConsoleCmd("sm_kills", Cmd_Kills, "Show current common kill counts"); RegConsoleCmd("sm_killcount", Cmd_Kills, "Show current common kill counts"); } public void OnClientPutInServer(int client) { g_iKills[client] = 0; g_iHeadshots[client] = 0; g_iMapStartKills[client] = 0; } public void OnClientDisconnect(int client) { g_iKills[client] = 0; g_iHeadshots[client] = 0; g_iMapStartKills[client] = 0; } public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { for (int i = 1; i <= MaxClients; i++) { g_iMapStartKills[i] = g_iKills[i]; } return Plugin_Continue; } public Action Event_InfectedDeath(Event event, const char[] name, bool dontBroadcast) { int attacker = GetClientOfUserId(event.GetInt("attacker")); if (attacker < 1 || attacker > MaxClients) return Plugin_Continue; if (!IsClientInGame(attacker)) return Plugin_Continue; if (IsFakeClient(attacker)) return Plugin_Continue; if (GetClientTeam(attacker) != 2) return Plugin_Continue; g_iKills[attacker]++; if (event.GetBool("headshot")) { g_iHeadshots[attacker]++; } return Plugin_Continue; } public Action Event_FinaleWin(Event event, const char[] name, bool dontBroadcast) { CreateTimer(3.0, Timer_AnnounceStats); return Plugin_Continue; } public Action Event_MapEnd(Event event, const char[] name, bool dontBroadcast) { AnnounceStats(); return Plugin_Continue; } public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { int reason = event.GetInt("reason"); if (reason == 5 || reason == 8) { CreateTimer(2.0, Timer_AnnounceStats); } return Plugin_Continue; } public Action Timer_AnnounceStats(Handle timer) { AnnounceStats(); return Plugin_Stop; } void AnnounceStats() { int topClient = 0; int topKills = 0; int totalKills = 0; for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i)) continue; if (IsFakeClient(i)) continue; if (GetClientTeam(i) != 2 && g_iKills[i] == 0) continue; int mapKills = g_iKills[i] - g_iMapStartKills[i]; if (mapKills < 0) mapKills = g_iKills[i]; totalKills += mapKills; if (mapKills > topKills) { topKills = mapKills; topClient = i; } } if (totalKills == 0) return; PrintToChatAll("\x04[Stats]\x01 ===== \x05Map Kill Counts\x01 ====="); for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i)) continue; if (IsFakeClient(i)) continue; int mapKills = g_iKills[i] - g_iMapStartKills[i]; if (mapKills < 0) mapKills = g_iKills[i]; if (mapKills == 0) continue; int hs = g_iHeadshots[i]; float hsPercent = (mapKills > 0) ? (float(hs) / float(mapKills)) * 100.0 : 0.0; PrintToChatAll("\x04[Stats]\x01 \x05%N\x01: \x04%d\x01 zombies (\x03%d headshots, %.1f%%\x01)", i, mapKills, hs, hsPercent); } if (topClient > 0 && topKills > 0) { PrintToChatAll("\x04[Stats]\x01 \x05MVP:\x01 \x04%N\x01 with \x04%d\x01 kills!", topClient, topKills); } PrintToChatAll("\x04[Stats]\x01 Total team kills: \x04%d\x01", totalKills); } public Action Cmd_Kills(int client, int args) { if (client == 0) { ReplyToCommand(client, "[Stats] Use this command in-game."); return Plugin_Handled; } PrintToChat(client, "\x04[Stats]\x01 === Current Map Kills ==="); for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i)) continue; if (IsFakeClient(i)) continue; int mapKills = g_iKills[i] - g_iMapStartKills[i]; if (mapKills < 0) mapKills = g_iKills[i]; if (mapKills == 0) continue; PrintToChat(client, "\x04[Stats]\x01 \x05%N\x01: \x04%d\x01 zombies (\x03%d HS\x01)", i, mapKills, g_iHeadshots[i]); } return Plugin_Handled; }