// l4d2-finale-next-campaign.sp — Left 4 Dead 2 SourceMod plugin // // On every successful finale, waits 10 seconds and then auto-rotates the // server to the next campaign in a fixed order. Loops back to the start // after Dead Center. Set-and-forget map rotation for 24/7 servers — no // votes, no RTV waits, the game just keeps going. // // Build: // spcomp l4d2-finale-next-campaign.sp -o l4d2-finale-next-campaign.smx // Drop the .smx into addons/sourcemod/plugins/ on the server. // // Rotation order (13 campaigns total, L4D2 then L4D1): // Dark Carnival → Swamp Fever → Hard Rain → The Parish → The Passing → // The Sacrifice → No Mercy → Crash Course → Death Toll → Dead Air → // Blood Harvest → Cold Stream → Dead Center → (wrap) // // State: // g_CampaignIndex is in-memory — resets to 0 on plugin reload / server // restart. For persistent rotation across restarts, swap it for a // KeyValues file or a simple Cookie/SaveTextFile pair. // // Mission-lost handling: // Event_MissionLost is hooked but no-ops on purpose. L4D2's default // behavior on wipe (restart the same chapter) is preserved, so a wiped // team doesn't get cheated of a successful run by being yanked to a new // campaign. Only finale_win advances the rotation. // // Author: magikh0e // License: GPL-3.0-or-later #include #include #pragma semicolon 1 #pragma newdecls required char g_NextCampaign[][] = { "c2m1_highway", "c3m1_plankcountry", "c4m1_milltown_a", "c5m1_waterfront", "c6m1_riverbank", "c7m1_docks", "c8m1_apartment", "c9m1_alleys", "c10m1_caves", "c11m1_greenhouse", "c12m1_hilltop", "c13m1_alpinecreek", "c1m1_hotel" }; int g_CampaignIndex = 0; public Plugin myinfo = { name = "Finale to Next Campaign", author = "magikh0e", description = "Auto-changes to next campaign after finale", version = "1.0", }; public void OnPluginStart() { HookEvent("finale_win", Event_FinaleWin); HookEvent("mission_lost", Event_MissionLost); } public Action Event_FinaleWin(Event event, const char[] name, bool dontBroadcast) { CreateTimer(10.0, Timer_NextCampaign); PrintToChatAll("\x04[Server]\x01 Next campaign in 10 seconds..."); return Plugin_Continue; } public Action Event_MissionLost(Event event, const char[] name, bool dontBroadcast) { return Plugin_Continue; } public Action Timer_NextCampaign(Handle timer) { char nextMap[64]; strcopy(nextMap, sizeof(nextMap), g_NextCampaign[g_CampaignIndex]); g_CampaignIndex = (g_CampaignIndex + 1) % sizeof(g_NextCampaign); char cmd[128]; Format(cmd, sizeof(cmd), "changelevel %s", nextMap); ServerCommand(cmd); return Plugin_Stop; }