//---------------------------------------------------------------------------------------------------- // Leader3 core: leader roster (index 0 = primary), add/remove/promote, lifecycle handling. //---------------------------------------------------------------------------------------------------- bool IsValidClient(int client, bool nobots = true) { if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (nobots && IsFakeClient(client))) return false; return IsClientInGame(client); } bool IsLeader(int client) { return client >= 1 && client <= MaxClients && g_bIsLeader[client]; } int GetPrimaryLeader() { if (g_alLeaders.Length == 0) return -1; int client = GetClientOfUserId(g_alLeaders.Get(0)); return client > 0 ? client : -1; } bool IsPrimaryLeader(int client) { return client != -1 && client == GetPrimaryLeader(); } // ZR_IsClientZombie/ZR_IsClientHuman throw on dead clients, so the alive check // has to come first in both helpers. bool IsLivingZombie(int client) { return IsPlayerAlive(client) && ZR_IsClientZombie(client); } bool IsLivingHuman(int client) { return IsPlayerAlive(client) && ZR_IsClientHuman(client); } // The single gate every visual effect checks. bool IsEffectsActive(int client) { return IsLeader(client) && IsClientInGame(client) && IsLivingHuman(client); } int GetLeaderRosterIndex(int client) { if (!IsLeader(client)) return -1; return g_alLeaders.FindValue(GetClientUserId(client)); } // 'actor' is who performed the promotion (0 = console); -1 or self = no credit shown. bool AddLeader(int client, bool asPrimary = false, int actor = -1) { if (!IsValidClient(client)) return false; if (g_bIsLeader[client]) { if (asPrimary) Core_MakePrimary(client, actor); return true; } if (g_alLeaders.Length >= g_cvMaxLeaders.IntValue) { PrintToChatAll("%sCannot add \x04%N\x01 as leader: the maximum of %d leaders is reached.", PREFIX, client, g_cvMaxLeaders.IntValue); return false; } int oldPrimary = GetPrimaryLeader(); bool primary = asPrimary || g_alLeaders.Length == 0; if (primary && g_alLeaders.Length > 0) { g_alLeaders.ShiftUp(0); g_alLeaders.Set(0, GetClientUserId(client)); } else { g_alLeaders.Push(GetClientUserId(client)); } g_bIsLeader[client] = true; // Fresh defaults: xray off, beacon off, no sprite, hold-to-show tracer. g_iEffectFlags[client] = 0; g_iSpriteChoice[client] = SPRITE_NONE; g_iTracerMode[client] = TRACER_MODE_HOLD; g_iLastButtons[client] = 0; g_fNextTracerSend[client] = 0.0; bool credit = (actor >= 0 && actor != client); if (primary) { if (credit) PrintToChatAll("%s\x04%N\x01: \x04%N\x01 is now the primary leader! Follow their markers and orders.", PREFIX, actor, client); else PrintToChatAll("%s\x04%N\x01 is now the leader! Follow their markers and orders.", PREFIX, client); PrintToChat(client, "%sYou are now the \x04primary leader\x01! Type \x04!leader\x01 or press your \x04flashlight key\x01 for the menu.", PREFIX); } else { if (credit) PrintToChatAll("%s\x04%N\x01: \x04%N\x01 is now a sub-leader!", PREFIX, actor, client); else PrintToChatAll("%s\x04%N\x01 is now a sub-leader!", PREFIX, client); PrintToChat(client, "%sYou are now a \x04sub-leader\x01! Type \x04!leader\x01 or press your \x04flashlight key\x01 for the menu.", PREFIX); } Call_StartForward(g_fwdOnLeaderAdded); Call_PushCell(client); Call_PushCell(primary); Call_Finish(); if (primary && oldPrimary != client) FirePrimaryChanged(oldPrimary, client); if (IsEffectsActive(client)) Effects_Resume(client); return true; } // Make a client the primary leader (adding them as leader if needed). // 'actor' is who performed the promotion (0 = console); -1 or self = no credit shown. void Core_MakePrimary(int client, int actor = -1) { if (!IsValidClient(client)) return; if (!g_bIsLeader[client]) { AddLeader(client, true, actor); return; } int oldPrimary = GetPrimaryLeader(); if (oldPrimary == client) return; int idx = GetLeaderRosterIndex(client); if (idx <= 0) return; g_alLeaders.Erase(idx); g_alLeaders.ShiftUp(0); g_alLeaders.Set(0, GetClientUserId(client)); if (actor >= 0 && actor != client) PrintToChatAll("%s\x04%N\x01: \x04%N\x01 is now the primary leader!", PREFIX, actor, client); else PrintToChatAll("%s\x04%N\x01 is now the primary leader!", PREFIX, client); FirePrimaryChanged(oldPrimary, client); // Primary/sub xray colours differ: refresh both affected leaders. Effects_RefreshXray(client); if (oldPrimary != -1) Effects_RefreshXray(oldPrimary); } void RemoveLeaderClient(int client, const char[] reason, bool announce = true) { int idx = GetLeaderRosterIndex(client); if (idx == -1) return; bool wasPrimary = (idx == 0); Effects_Suspend(client); Marker_ClearByOwner(client, false); g_alLeaders.Erase(idx); g_bIsLeader[client] = false; g_iEffectFlags[client] = 0; g_iSpriteChoice[client] = SPRITE_NONE; if (announce) PrintToChatAll("%s\x04%N\x01 is no longer a leader (%s).", PREFIX, client, reason); Call_StartForward(g_fwdOnLeaderRemoved); Call_PushCell(client); Call_Finish(); if (wasPrimary) { int newPrimary = GetPrimaryLeader(); if (newPrimary != -1) { PrintToChatAll("%s\x04%N\x01 has been promoted to primary leader!", PREFIX, newPrimary); Effects_RefreshXray(newPrimary); } FirePrimaryChanged(client, newPrimary); } } void Core_RemoveAllLeaders(bool announce = false, int actor = -1) { for (int i = 1; i <= MaxClients; i++) { if (g_bIsLeader[i]) { Effects_Suspend(i); Marker_ClearByOwner(i, false); g_bIsLeader[i] = false; g_iEffectFlags[i] = 0; g_iSpriteChoice[i] = SPRITE_NONE; Call_StartForward(g_fwdOnLeaderRemoved); Call_PushCell(i); Call_Finish(); } } int oldPrimary = GetPrimaryLeader(); g_alLeaders.Clear(); if (oldPrimary != -1) FirePrimaryChanged(oldPrimary, -1); if (announce) { if (actor >= 0) PrintToChatAll("%sAll leaders have been removed by \x04%N\x01.", PREFIX, actor); else PrintToChatAll("%sAll leaders have been removed.", PREFIX); } } void FirePrimaryChanged(int oldLeader, int newLeader) { Call_StartForward(g_fwdOnPrimaryChanged); Call_PushCell(oldLeader); Call_PushCell(newLeader); Call_Finish(); } // A leader died or got infected: effects off, markers and leadership stay. void Core_OnLeaderIncapacitated(int client, const char[] verb, bool announce = true) { if (!IsLeader(client)) return; Effects_Suspend(client); if (!announce) return; char remaining[256]; int count = Core_GetActiveLeaderNames(client, remaining, sizeof(remaining)); if (count == 0) PrintToChatAll("%s\x04%N\x01 %s! \x07FF4040No leaders remain!", PREFIX, client, verb); else if (count == 1) PrintToChatAll("%s\x04%N\x01 %s! Remaining leader: \x04%s\x01.", PREFIX, client, verb, remaining); else PrintToChatAll("%s\x04%N\x01 %s! Remaining leaders: \x04%s\x01.", PREFIX, client, verb, remaining); } // List the leaders still standing (alive humans), excluding 'exclude'. Roster order, primary first. int Core_GetActiveLeaderNames(int exclude, char[] buf, int maxlen) { int count = 0; char name[MAX_NAME_LENGTH]; for (int i = 0; i < g_alLeaders.Length; i++) { int leader = GetClientOfUserId(g_alLeaders.Get(i)); if (leader <= 0 || leader == exclude) continue; if (!IsLivingHuman(leader)) continue; GetClientName(leader, name, sizeof(name)); if (buf[0]) StrCat(buf, maxlen, "\x01, \x04"); StrCat(buf, maxlen, name); count++; } return count; } void Core_OnClientDisconnect(int client) { if (g_bIsLeader[client]) RemoveLeaderClient(client, "disconnected"); g_iEffectFlags[client] = 0; g_iSpriteChoice[client] = SPRITE_NONE; g_iTracerMode[client] = TRACER_MODE_HOLD; g_bHasCustomColour[client] = false; g_iXrayRef[client] = INVALID_ENT_REFERENCE; g_iSpriteRef[client] = INVALID_ENT_REFERENCE; g_iBeaconSerial[client] = 0; g_iLastButtons[client] = 0; } //---------------------------------------------------------------------------------------------------- // Commands //---------------------------------------------------------------------------------------------------- public Action Cmd_Leader(int client, int args) { bool admin = CheckCommandAccess(client, "sm_admin", ADMFLAG_GENERIC, false); if (args >= 1) { if (!admin) { ReplyToCommand(client, "%s Only admins can assign leaders. Use !voteleader instead.", PREFIX_PLAIN); return Plugin_Handled; } char arg[65], targetName[MAX_TARGET_LENGTH]; int targets[MAXPLAYERS]; bool tnIsMl; GetCmdArg(1, arg, sizeof(arg)); int count = ProcessTargetString(arg, client, targets, sizeof(targets), COMMAND_FILTER_ALIVE | COMMAND_FILTER_NO_BOTS, targetName, sizeof(targetName), tnIsMl); if (count <= 0) { ReplyToTargetError(client, count); return Plugin_Handled; } for (int i = 0; i < count; i++) { int target = targets[i]; if (g_bIsLeader[target]) { ReplyToCommand(client, "%s %N is already a leader.", PREFIX_PLAIN, target); continue; } if (ZR_IsClientZombie(target)) { ReplyToCommand(client, "%s %N has to be a human!", PREFIX_PLAIN, target); continue; } if (AddLeader(target, false, client) && count == 1) ShowLeaderMenu(target); } return Plugin_Handled; } if (client == 0) { ReplyToCommand(client, "%s Usage: sm_leader ", PREFIX_PLAIN); return Plugin_Handled; } if (g_bIsLeader[client]) { ShowLeaderMenu(client); return Plugin_Handled; } // Admins may always self-assign as an override; whitelisted players may too. if (admin || Whitelist_IsWhitelisted(client)) { if (!IsPlayerAlive(client)) { ReplyToCommand(client, "%s You have to be alive!", PREFIX_PLAIN); return Plugin_Handled; } if (ZR_IsClientZombie(client)) { ReplyToCommand(client, "%s You have to be a human!", PREFIX_PLAIN); return Plugin_Handled; } if (AddLeader(client)) ShowLeaderMenu(client); return Plugin_Handled; } ReplyToCommand(client, "%s You are not a leader. Ask players to vote for you with !voteleader , or ask an admin to whitelist you.", PREFIX_PLAIN); return Plugin_Handled; } void Core_AnnounceLeaders() { char buf[512], name[MAX_NAME_LENGTH]; int count = 0; // Index 0 is the primary leader, so the roster order already puts them first. for (int i = 0; i < g_alLeaders.Length; i++) { int leader = GetClientOfUserId(g_alLeaders.Get(i)); if (leader <= 0) continue; GetClientName(leader, name, sizeof(name)); if (buf[0]) StrCat(buf, sizeof(buf), "\x01, \x04"); StrCat(buf, sizeof(buf), name); count++; } if (count == 1) PrintToChatAll("%sYour leader for this round is \x04%s\x01.", PREFIX, buf); else if (count > 1) PrintToChatAll("%sYour leaders for this round are \x04%s\x01.", PREFIX, buf); } public Action Cmd_Leaders(int client, int args) { if (g_alLeaders.Length == 0) { ReplyToCommand(client, "%s There are no leaders right now.", PREFIX_PLAIN); return Plugin_Handled; } char buf[512], name[MAX_NAME_LENGTH]; for (int i = 0; i < g_alLeaders.Length; i++) { int leader = GetClientOfUserId(g_alLeaders.Get(i)); if (leader <= 0) continue; GetClientName(leader, name, sizeof(name)); if (buf[0]) StrCat(buf, sizeof(buf), ", "); StrCat(buf, sizeof(buf), name); if (i == 0) StrCat(buf, sizeof(buf), " [PRIMARY]"); if (!IsLivingHuman(leader)) StrCat(buf, sizeof(buf), " (dead)"); } ReplyToCommand(client, "%s Current leaders: %s", PREFIX_PLAIN, buf); return Plugin_Handled; } public Action Cmd_LeaderResign(int client, int args) { if (client == 0) { ReplyToCommand(client, "%s This command can only be used in-game.", PREFIX_PLAIN); return Plugin_Handled; } if (!g_bIsLeader[client]) { ReplyToCommand(client, "%s You are not a leader.", PREFIX_PLAIN); return Plugin_Handled; } RemoveLeaderClient(client, "resigned"); return Plugin_Handled; } public Action Cmd_PrimaryLeader(int client, int args) { if (args < 1) { ReplyToCommand(client, "%s Usage: sm_primaryleader ", PREFIX_PLAIN); return Plugin_Handled; } char arg[65], targetName[MAX_TARGET_LENGTH]; int targets[MAXPLAYERS]; bool tnIsMl; GetCmdArg(1, arg, sizeof(arg)); int count = ProcessTargetString(arg, client, targets, sizeof(targets), COMMAND_FILTER_ALIVE | COMMAND_FILTER_NO_BOTS, targetName, sizeof(targetName), tnIsMl); if (count <= 0) { ReplyToTargetError(client, count); return Plugin_Handled; } if (count > 1) { ReplyToCommand(client, "%s There can only be one primary leader, pick a single player.", PREFIX_PLAIN); return Plugin_Handled; } int target = targets[0]; if (ZR_IsClientZombie(target)) { ReplyToCommand(client, "%s %N has to be a human!", PREFIX_PLAIN, target); return Plugin_Handled; } if (IsPrimaryLeader(target)) { ReplyToCommand(client, "%s %N is already the primary leader.", PREFIX_PLAIN, target); return Plugin_Handled; } Core_MakePrimary(target, client); LogAction(client, target, "\"%L\" made \"%L\" the primary leader", client, target); return Plugin_Handled; } public Action Cmd_RemoveLeader(int client, int args) { if (args == 0) { if (g_alLeaders.Length == 0) { ReplyToCommand(client, "%s There is no leader to remove.", PREFIX_PLAIN); return Plugin_Handled; } Core_RemoveAllLeaders(true, client); LogAction(client, -1, "\"%L\" removed all leaders", client); return Plugin_Handled; } char arg[65], targetName[MAX_TARGET_LENGTH]; int targets[MAXPLAYERS]; bool tnIsMl; GetCmdArg(1, arg, sizeof(arg)); int count = ProcessTargetString(arg, client, targets, sizeof(targets), COMMAND_FILTER_CONNECTED | COMMAND_FILTER_NO_BOTS, targetName, sizeof(targetName), tnIsMl); if (count <= 0) { ReplyToTargetError(client, count); return Plugin_Handled; } int removed = 0; for (int i = 0; i < count; i++) { int target = targets[i]; if (!g_bIsLeader[target]) { if (count == 1) ReplyToCommand(client, "%s %N is not a leader.", PREFIX_PLAIN, target); continue; } char reason[80]; Format(reason, sizeof(reason), "removed by \x04%N\x01", client); RemoveLeaderClient(target, reason); LogAction(client, target, "\"%L\" removed leader \"%L\"", client, target); removed++; } if (count > 1) ReplyToCommand(client, "%s Removed %d leader(s) in %s.", PREFIX_PLAIN, removed, targetName); return Plugin_Handled; }