lots of changes

This commit is contained in:
2026-10-22 16:17:56 +01:00
parent e4a75367aa
commit ac3ffd5be2
53 changed files with 7716 additions and 193 deletions
+544
View File
@@ -0,0 +1,544 @@
//----------------------------------------------------------------------------------------------------
// 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 <name> 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 <target>", 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 <your name>, 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 <target>", 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;
}