lots of changes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user