#pragma semicolon 1 #define LEADER3_CORE #include #include #include #include #include #include #include #include #include #include #undef REQUIRE_PLUGIN #include #define REQUIRE_PLUGIN #pragma newdecls required #define PLUGIN_VERSION "3.0" #define PREFIX "\x07FF00E1[Leader] \x01" #define PREFIX_PLAIN "[Leader]" #define MARKER_MODEL "models/unloze/unloze_ping.mdl" #define MARKER_MODEL_HEIGHT 80.0 // hull_max.z of unloze_ping.mdl #define MARKER_MODEL_SCALE 1.5 #define MARKER_TEXT_CLEARANCE 24.0 // headroom kept between the title text and a ceiling above it #define SPRITE_DEFEND "materials/nide/defend.vmt" #define SPRITE_FOLLOW "materials/nide/follow.vmt" // Stock ct_urban patched to pull its materials from materials/models/leader3/glow_ct/ // (additive + $ignorez -> visible through walls). Bonemerges onto any stock CT skeleton. // v2: IK chains/rules stripped (header-patched) -- the decompiled ragdoll sequence's IK // yanked hand/foot bones to world origin on frames where the parent's bones were unavailable. #define XRAY_MODEL "models/leader3/glow_ct2.mdl" // m_fEffects bits (not in SM includes) used by the xray shell #define EF_BONEMERGE (1 << 0) #define EF_NOSHADOW (1 << 4) #define EF_NORECEIVESHADOW (1 << 6) #define EF_BONEMERGE_FASTCULL (1 << 7) #define EF_PARENT_ANIMATES (1 << 9) // unloze_ping.mdl skins #define SKIN_STAR 0 #define SKIN_SKULL 1 #define SKIN_MOON 2 #define SKIN_CROSS 3 // g_iEffectFlags bits #define EFFECT_XRAY (1 << 0) #define EFFECT_BEACON (1 << 1) #define EFFECT_TRACER_TOGGLED (1 << 2) // g_iSpriteChoice #define SPRITE_NONE -1 #define SPRITE_CHOICE_DEFEND 0 #define SPRITE_CHOICE_FOLLOW 1 // g_iTracerMode #define TRACER_MODE_TAP 0 // hold +use OR tap to toggle #define TRACER_MODE_HOLD 1 // hold +use only #define TRACER_MODE_OFF 2 // Leader roster: userids, index 0 = primary ArrayList g_alLeaders; bool g_bIsLeader[MAXPLAYERS + 1]; // Per-leader effect state int g_iEffectFlags[MAXPLAYERS + 1]; int g_iSpriteChoice[MAXPLAYERS + 1] = { SPRITE_NONE, ... }; int g_iTracerMode[MAXPLAYERS + 1]; // Per-leader colour override for xray/beacon/tracer; persisted as a cookie ("RRGGBB" hex). bool g_bHasCustomColour[MAXPLAYERS + 1]; int g_iCustomColour[MAXPLAYERS + 1][3]; Cookie g_hColourCookie; int g_iXrayRef[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; // ShouldTransmit vtable hook armed on each xray shell; null if gamedata failed. Handle g_hXrayShouldTransmit; int g_iSpriteRef[MAXPLAYERS + 1] = { INVALID_ENT_REFERENCE, ... }; int g_iBeaconSerial[MAXPLAYERS + 1]; int g_iSerialGen; int g_iLastButtons[MAXPLAYERS + 1]; float g_fUsePressedAt[MAXPLAYERS + 1]; float g_fNextTracerSend[MAXPLAYERS + 1]; // Beam sprites for beacon/tracer int g_iBeamSprite = -1; int g_iHaloSprite = -1; // Leader whitelist (persisted in the SourceMod database) Database g_hWhitelistDB; bool g_bLeaderWhitelisted[MAXPLAYERS + 1]; // Force-mute bool g_bForceLeaderMute; bool g_bForceMuted[MAXPLAYERS + 1]; bool g_bAFKManager; bool g_bWorldTextBroken; GlobalForward g_fwdOnLeaderAdded; GlobalForward g_fwdOnLeaderRemoved; GlobalForward g_fwdOnPrimaryChanged; ConVar g_cvMaxLeaders; ConVar g_cvAllowVotes; ConVar g_cvVoteRatio; ConVar g_cvVoteAfkTime; ConVar g_cvMarkerLimit; ConVar g_cvBeaconRadius; ConVar g_cvBeaconInterval; ConVar g_cvTracerLife; ConVar g_cvTracerWidth; ConVar g_cvTracerInterval; ConVar g_cvTracerTapTime; ConVar g_cvXrayColourPrimary; ConVar g_cvXrayColourSub; ConVar g_cvXrayAlpha; ConVar g_cvDeathSound; int g_iXrayColourPrimary[4] = {0, 255, 0, 255}; int g_iXrayColourSub[4] = {0, 150, 255, 255}; #include "leader3/core.sp" #include "leader3/whitelist.sp" #include "leader3/effects.sp" #include "leader3/markers.sp" #include "leader3/chat.sp" #include "leader3/vote.sp" #include "leader3/menu.sp" public Plugin myinfo = { name = "Leader3", author = "marisakirisame.net", description = "ZE leader plugin: multiple leaders, effects, persistent markers with titles.", version = PLUGIN_VERSION, url = "" }; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { CreateNative("Leader_CurrentLeader", Native_CurrentLeader); CreateNative("Leader_SetLeader", Native_SetLeader); CreateNative("Leader_Is", Native_Is); CreateNative("Leader_GetLeaderCount", Native_GetLeaderCount); CreateNative("Leader_GetLeader", Native_GetLeader); RegPluginLibrary("leader"); return APLRes_Success; } public void OnPluginStart() { LoadTranslations("common.phrases"); LoadTranslations("core.phrases"); g_alLeaders = new ArrayList(); Markers_OnPluginStart(); Whitelist_OnPluginStart(); Xray_SetupForceTransmit(); g_hColourCookie = new Cookie("leader3_colour", "Leader3 custom effect colour (RRGGBB hex)", CookieAccess_Protected); for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && !IsFakeClient(i) && AreClientCookiesCached(i)) LeaderColour_LoadCookie(i); } g_fwdOnLeaderAdded = new GlobalForward("Leader_OnLeaderAdded", ET_Ignore, Param_Cell, Param_Cell); g_fwdOnLeaderRemoved = new GlobalForward("Leader_OnLeaderRemoved", ET_Ignore, Param_Cell); g_fwdOnPrimaryChanged = new GlobalForward("Leader_OnPrimaryChanged", ET_Ignore, Param_Cell, Param_Cell); CreateConVar("sm_leader_version", PLUGIN_VERSION, "Leader3 version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY); g_cvMaxLeaders = CreateConVar("sm_leader_max", "4", "Maximum concurrent leaders", FCVAR_NOTIFY, true, 1.0, true, 16.0); g_cvAllowVotes = CreateConVar("sm_leader_allow_votes", "1", "Allow !voteleader", FCVAR_NOTIFY); g_cvVoteRatio = CreateConVar("sm_leader_vote_ratio", "0.60", "Ratio of eligible voters needed for !voteleader", FCVAR_NOTIFY, true, 0.05, true, 1.0); g_cvVoteAfkTime = CreateConVar("sm_leader_vote_afk_time", "180", "Seconds of idle time before a player no longer counts as an eligible voter", FCVAR_NOTIFY); g_cvMarkerLimit = CreateConVar("sm_leader_marker_limit", "4", "Max markers per leader (one per type, max 4)", FCVAR_NOTIFY, true, 1.0, true, 4.0); g_cvBeaconRadius = CreateConVar("sm_leader_beacon_radius", "375.0", "Beacon ring max radius", FCVAR_NOTIFY); g_cvBeaconInterval = CreateConVar("sm_leader_beacon_interval", "1.0", "Beacon ring interval", FCVAR_NOTIFY, true, 0.1); g_cvTracerLife = CreateConVar("sm_leader_tracer_life", "0.1", "Tracer beam life", FCVAR_NOTIFY); g_cvTracerWidth = CreateConVar("sm_leader_tracer_width", "2.0", "Tracer beam width", FCVAR_NOTIFY); g_cvTracerInterval = CreateConVar("sm_leader_tracer_interval", "0.05", "Seconds between tracer beam sends", FCVAR_NOTIFY, true, 0.05); g_cvTracerTapTime = CreateConVar("sm_leader_tracer_tap_time", "0.3", "Max +use press duration counted as a tap (toggle)", FCVAR_NOTIFY); g_cvXrayColourPrimary = CreateConVar("sm_leader_xray_colour_primary", "0 255 0", "Xray/tracer RGB for the primary leader", FCVAR_NOTIFY); g_cvXrayColourSub = CreateConVar("sm_leader_xray_colour_sub", "0 150 255", "Xray/tracer RGB for sub-leaders", FCVAR_NOTIFY); g_cvXrayAlpha = CreateConVar("sm_leader_xray_alpha", "200", "Xray shell brightness (renderamt of the additive player-model shell)", FCVAR_NOTIFY, true, 1.0, true, 255.0); g_cvDeathSound = CreateConVar("sm_leader_death_sound", "ambient/alarms/klaxon1.wav", "Sound played to everyone when a leader dies (relative to sound/, empty = off)", FCVAR_NOTIFY); g_cvXrayColourPrimary.AddChangeHook(OnXrayColourChanged); g_cvXrayColourSub.AddChangeHook(OnXrayColourChanged); g_cvDeathSound.AddChangeHook(OnDeathSoundChanged); AutoExecConfig(true, "Leader3"); HookEvent("round_start", Event_RoundStart); HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_death", Event_PlayerDeath); HookEvent("player_team", Event_PlayerTeam); RegConsoleCmd("sm_leader", Cmd_Leader, "Open the leader menu / (admin) set a leader"); RegConsoleCmd("sm_pingmenu", Cmd_Leader, "Open the leader menu / (admin) set a leader"); RegConsoleCmd("sm_currentleader", Cmd_Leaders, "List the current leaders"); RegConsoleCmd("sm_currentleaders", Cmd_Leaders, "List the current leaders"); RegConsoleCmd("sm_leaders", Cmd_Leaders, "List the current leaders"); RegConsoleCmd("sm_voteleader", Cmd_VoteLeader, "Vote for a player to become a leader"); RegConsoleCmd("sm_vl", Cmd_VoteLeader, "Vote for a player to become a leader"); RegConsoleCmd("sm_leadercolour", Cmd_LeaderColour, "Set your leader colour: sm_leadercolour "); RegConsoleCmd("sm_leadercolor", Cmd_LeaderColour, "Set your leader colour: sm_leadercolor "); RegConsoleCmd("sm_marker", Cmd_Marker, "Place a marker: sm_marker "); RegConsoleCmd("sm_markertitle", Cmd_MarkerTitle, "Set the title of your last marker"); RegConsoleCmd("sm_clearmarkers", Cmd_ClearMarkers, "Clear your markers"); RegConsoleCmd("sm_resign", Cmd_LeaderResign, "Resign from being a leader"); RegAdminCmd("sm_primaryleader", Cmd_PrimaryLeader, ADMFLAG_GENERIC, "Set the primary leader: sm_primaryleader "); RegAdminCmd("sm_removeleader", Cmd_RemoveLeader, ADMFLAG_GENERIC, "Remove a leader: sm_removeleader "); RegAdminCmd("sm_addleaderwhitelist", Cmd_AddLeaderWhitelist, ADMFLAG_GENERIC, "Add a player to the leader whitelist: sm_addleaderwhitelist [name]"); RegAdminCmd("sm_removeleaderwhitelist", Cmd_RemoveLeaderWhitelist, ADMFLAG_GENERIC, "Remove a player from the leader whitelist: sm_removeleaderwhitelist "); RegAdminCmd("sm_leaderwhitelist", Cmd_LeaderWhitelist, ADMFLAG_GENERIC, "Show the leader whitelist"); RegAdminCmd("sm_forceleader", Cmd_ForceLeader, ADMFLAG_GENERIC, "Toggle force mute while a leader is talking"); CreateTimer(0.5, Chat_CheckLeaderVoice, _, TIMER_REPEAT); AddMultiTargetFilter("@leaders", Filter_Leaders, "all Leaders", false); AddMultiTargetFilter("@!leaders", Filter_NotLeaders, "everyone but the Leaders", false); AddMultiTargetFilter("@leader", Filter_Leader, "the Primary Leader", false); AddMultiTargetFilter("@!leader", Filter_NotLeader, "everyone but the Primary Leader", false); } public void OnPluginEnd() { RemoveMultiTargetFilter("@leaders", Filter_Leaders); RemoveMultiTargetFilter("@!leaders", Filter_NotLeaders); RemoveMultiTargetFilter("@leader", Filter_Leader); RemoveMultiTargetFilter("@!leader", Filter_NotLeader); } public void OnAllPluginsLoaded() { g_bAFKManager = LibraryExists("AFKManager"); } public void OnLibraryAdded(const char[] name) { if (StrEqual(name, "AFKManager")) g_bAFKManager = true; } public void OnLibraryRemoved(const char[] name) { if (StrEqual(name, "AFKManager")) g_bAFKManager = false; } public void OnMapStart() { g_iBeamSprite = PrecacheModel("sprites/laser.vmt"); g_iHaloSprite = PrecacheModel("sprites/halo01.vmt"); g_bWorldTextBroken = false; } public void OnMapEnd() { Core_RemoveAllLeaders(); Markers_ClearAll(false); } public void OnConfigsExecuted() { PrecacheModel(MARKER_MODEL); AddFileToDownloadsTable("models/unloze/unloze_ping.mdl"); AddFileToDownloadsTable("models/unloze/unloze_ping.vvd"); AddFileToDownloadsTable("models/unloze/unloze_ping.dx80.vtx"); AddFileToDownloadsTable("models/unloze/unloze_ping.dx90.vtx"); AddFileToDownloadsTable("models/unloze/unloze_ping.sw.vtx"); AddFileToDownloadsTable("materials/unloze/ping/ping.vmt"); AddFileToDownloadsTable("materials/unloze/ping/ping.vtf"); AddFileToDownloadsTable("materials/unloze/ping/ping_cross.vmt"); AddFileToDownloadsTable("materials/unloze/ping/ping_cross.vtf"); AddFileToDownloadsTable("materials/unloze/ping/ping_moon.vmt"); AddFileToDownloadsTable("materials/unloze/ping/ping_moon.vtf"); AddFileToDownloadsTable("materials/unloze/ping/ping_skull.vmt"); AddFileToDownloadsTable("materials/unloze/ping/ping_skull.vtf"); AddFileToDownloadsTable(SPRITE_DEFEND); AddFileToDownloadsTable("materials/nide/defend.vtf"); AddFileToDownloadsTable(SPRITE_FOLLOW); AddFileToDownloadsTable("materials/nide/follow.vtf"); PrecacheModel(XRAY_MODEL); AddFileToDownloadsTable(XRAY_MODEL); AddFileToDownloadsTable("models/leader3/glow_ct2.vvd"); AddFileToDownloadsTable("models/leader3/glow_ct2.dx80.vtx"); AddFileToDownloadsTable("models/leader3/glow_ct2.dx90.vtx"); AddFileToDownloadsTable("models/leader3/glow_ct2.sw.vtx"); AddFileToDownloadsTable("materials/models/leader3/glow_ct/ct_urban.vmt"); AddFileToDownloadsTable("materials/models/leader3/glow_ct/ct_urban_glass.vmt"); AddFileToDownloadsTable("materials/models/leader3/glow_ct/white.vtf"); PrecacheGeneric(SPRITE_DEFEND, true); PrecacheGeneric("materials/nide/defend.vtf", true); PrecacheGeneric(SPRITE_FOLLOW, true); PrecacheGeneric("materials/nide/follow.vtf", true); char sound[PLATFORM_MAX_PATH]; g_cvDeathSound.GetString(sound, sizeof(sound)); if (sound[0]) PrecacheSound(sound, true); ParseXrayColours(); } public void OnDeathSoundChanged(ConVar cvar, const char[] oldVal, const char[] newVal) { if (newVal[0]) PrecacheSound(newVal, true); } public void OnXrayColourChanged(ConVar cvar, const char[] oldVal, const char[] newVal) { ParseXrayColours(); } void ParseXrayColours() { char buf[32]; g_cvXrayColourPrimary.GetString(buf, sizeof(buf)); ParseColourString(buf, g_iXrayColourPrimary); g_cvXrayColourSub.GetString(buf, sizeof(buf)); ParseColourString(buf, g_iXrayColourSub); } void ParseColourString(const char[] str, int colour[4]) { char parts[3][8]; int found = ExplodeString(str, " ", parts, sizeof(parts), sizeof(parts[])); for (int i = 0; i < 3; i++) colour[i] = (i < found) ? StringToInt(parts[i]) : 255; colour[3] = 255; } public void OnClientAuthorized(int client, const char[] auth) { Whitelist_CheckClient(client); } public void OnClientDisconnect(int client) { Vote_OnClientDisconnect(client); Core_OnClientDisconnect(client); g_bLeaderWhitelisted[client] = false; if (g_bForceMuted[client]) { BaseComm_SetClientMute(client, false); g_bForceMuted[client] = false; } } public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { Markers_OnRoundStart(); Core_AnnounceLeaders(); } public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { int userid = event.GetInt("userid"); // Attach effects slightly late: at spawn time ZR state and position may not be final. CreateTimer(0.3, Timer_ResumeEffects, userid, TIMER_FLAG_NO_MAPCHANGE); } public Action Timer_ResumeEffects(Handle timer, any userid) { int client = GetClientOfUserId(userid); if (client > 0 && IsEffectsActive(client)) Effects_Resume(client); return Plugin_Stop; } public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if (client <= 0) return; // Toll and announce only for an active (human) leader falling; an infected leader dying later as a zombie stays quiet. // ZR_IsClientHuman throws on dead clients, so go by team: zombified leaders sit on T. bool diedAsHuman = GetClientTeam(client) == CS_TEAM_CT; if (IsLeader(client) && diedAsHuman) { char sound[PLATFORM_MAX_PATH]; g_cvDeathSound.GetString(sound, sizeof(sound)); if (sound[0]) EmitSoundToAll(sound); } // ZR infection fires a fake player_death (kill feed) while the client is still alive; // ZR_OnClientInfected announces that one, so only a real death tolls "died" here. if (IsPlayerAlive(client)) return; int attacker = GetClientOfUserId(event.GetInt("attacker")); char verb[80]; if (attacker > 0 && attacker != client) Format(verb, sizeof(verb), "was killed by \x04%N\x01", attacker); else strcopy(verb, sizeof(verb), "died"); Core_OnLeaderIncapacitated(client, verb, diedAsHuman); } public void Event_PlayerTeam(Event event, const char[] name, bool dontBroadcast) { Vote_RecheckAll(); } public void ZR_OnClientInfected(int client, int attacker, bool motherInfect, bool respawnOverride, bool respawn) { char verb[80]; if (attacker > 0 && attacker <= MaxClients && attacker != client) Format(verb, sizeof(verb), "has been infected by \x04%N\x01", attacker); else strcopy(verb, sizeof(verb), "has been infected"); Core_OnLeaderIncapacitated(client, verb); } public void ZR_OnClientHumanPost(int client, bool respawn, bool protect) { if (IsEffectsActive(client)) Effects_Resume(client); } public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) { if (!g_bIsLeader[client]) return Plugin_Continue; if (impulse == 100) { ShowLeaderMenu(client); } else if (impulse == 201 || impulse == -201) // player spray; inverse value comes from SprayManager { Marker_PlaceBySpray(client); } Effects_OnPlayerRunCmd(client, buttons); return Plugin_Continue; } //---------------------------------------------------------------------------------------------------- // Natives //---------------------------------------------------------------------------------------------------- public int Native_CurrentLeader(Handle plugin, int numParams) { return GetPrimaryLeader(); } public int Native_SetLeader(Handle plugin, int numParams) { int client = GetNativeCell(1); if (client >= 1 && client <= MaxClients && IsClientInGame(client)) Core_MakePrimary(client); return 0; } public int Native_Is(Handle plugin, int numParams) { int client = GetNativeCell(1); if (client < 1 || client > MaxClients) return 0; return g_bIsLeader[client] ? 1 : 0; } public int Native_GetLeaderCount(Handle plugin, int numParams) { return g_alLeaders.Length; } public int Native_GetLeader(Handle plugin, int numParams) { int index = GetNativeCell(1); if (index < 0 || index >= g_alLeaders.Length) return -1; int client = GetClientOfUserId(g_alLeaders.Get(index)); return client > 0 ? client : -1; } //---------------------------------------------------------------------------------------------------- // Target filters //---------------------------------------------------------------------------------------------------- public bool Filter_Leaders(const char[] pattern, Handle clients) { for (int i = 1; i <= MaxClients; i++) { if (g_bIsLeader[i] && IsClientInGame(i)) PushArrayCell(clients, i); } return true; } public bool Filter_NotLeaders(const char[] pattern, Handle clients) { for (int i = 1; i <= MaxClients; i++) { if (!g_bIsLeader[i] && IsClientInGame(i) && !IsFakeClient(i)) PushArrayCell(clients, i); } return true; } public bool Filter_Leader(const char[] pattern, Handle clients) { int primary = GetPrimaryLeader(); if (primary != -1) PushArrayCell(clients, primary); return true; } public bool Filter_NotLeader(const char[] pattern, Handle clients) { int primary = GetPrimaryLeader(); for (int i = 1; i <= MaxClients; i++) { if (i != primary && IsClientInGame(i) && !IsFakeClient(i)) PushArrayCell(clients, i); } return true; }