lots of changes
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
#pragma semicolon 1
|
||||
|
||||
#define LEADER3_CORE
|
||||
|
||||
#include <sourcemod>
|
||||
#include <sdktools>
|
||||
#include <sdkhooks>
|
||||
#include <cstrike>
|
||||
#include <clientprefs>
|
||||
#include <zombiereloaded>
|
||||
#include <basecomm>
|
||||
#include <voice>
|
||||
#include <leader>
|
||||
#include <dhooks>
|
||||
|
||||
#undef REQUIRE_PLUGIN
|
||||
#include <AFKManager>
|
||||
#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 <RRGGBB|R G B|default>");
|
||||
RegConsoleCmd("sm_leadercolor", Cmd_LeaderColour, "Set your leader colour: sm_leadercolor <RRGGBB|R G B|default>");
|
||||
RegConsoleCmd("sm_marker", Cmd_Marker, "Place a marker: sm_marker <star|skull|moon|cross>");
|
||||
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 <target>");
|
||||
RegAdminCmd("sm_removeleader", Cmd_RemoveLeader, ADMFLAG_GENERIC, "Remove a leader: sm_removeleader <target|blank = all current>");
|
||||
RegAdminCmd("sm_addleaderwhitelist", Cmd_AddLeaderWhitelist, ADMFLAG_GENERIC, "Add a player to the leader whitelist: sm_addleaderwhitelist <target|steamid> [name]");
|
||||
RegAdminCmd("sm_removeleaderwhitelist", Cmd_RemoveLeaderWhitelist, ADMFLAG_GENERIC, "Remove a player from the leader whitelist: sm_removeleaderwhitelist <target|steamid>");
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#if defined _leader_included_
|
||||
#endinput
|
||||
#endif
|
||||
#define _leader_included_
|
||||
|
||||
/**
|
||||
* Returns the current PRIMARY leader.
|
||||
*
|
||||
* @return Client index of the primary leader (-1 = no leader)
|
||||
*/
|
||||
native int Leader_CurrentLeader();
|
||||
|
||||
/**
|
||||
* Makes a client the PRIMARY leader. If another primary exists, they are
|
||||
* demoted to sub-leader. If the client is already a sub-leader, they are
|
||||
* promoted.
|
||||
*
|
||||
* @param client Client index to be set as primary leader
|
||||
*/
|
||||
native void Leader_SetLeader(int client);
|
||||
|
||||
/**
|
||||
* Checks if a client is currently a leader (primary or sub-leader).
|
||||
*
|
||||
* @param client Client index to check
|
||||
* @return True if the client is a leader
|
||||
*/
|
||||
native bool Leader_Is(int client);
|
||||
|
||||
/**
|
||||
* Returns the number of current leaders (primary + sub-leaders).
|
||||
*
|
||||
* @return Leader count
|
||||
*/
|
||||
native int Leader_GetLeaderCount();
|
||||
|
||||
/**
|
||||
* Returns the leader at the given roster index.
|
||||
*
|
||||
* @param index Roster index (0 = primary)
|
||||
* @return Client index, or -1 if no leader at that index
|
||||
*/
|
||||
native int Leader_GetLeader(int index);
|
||||
|
||||
/**
|
||||
* Called after a client becomes a leader.
|
||||
*
|
||||
* @param client The new leader
|
||||
* @param primary True if they became the primary leader
|
||||
*/
|
||||
forward void Leader_OnLeaderAdded(int client, bool primary);
|
||||
|
||||
/**
|
||||
* Called after a client stops being a leader.
|
||||
*
|
||||
* @param client The removed leader
|
||||
*/
|
||||
forward void Leader_OnLeaderRemoved(int client);
|
||||
|
||||
/**
|
||||
* Called when the primary leader changes.
|
||||
*
|
||||
* @param oldLeader Previous primary (-1 = none)
|
||||
* @param newLeader New primary (-1 = none)
|
||||
*/
|
||||
forward void Leader_OnPrimaryChanged(int oldLeader, int newLeader);
|
||||
|
||||
#if !defined LEADER3_CORE
|
||||
public SharedPlugin __pl_leader =
|
||||
{
|
||||
name = "leader",
|
||||
file = "Leader3.smx",
|
||||
#if defined REQUIRE_PLUGIN
|
||||
required = 1,
|
||||
#else
|
||||
required = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !defined REQUIRE_PLUGIN
|
||||
public void __pl_leader_SetNTVOptional()
|
||||
{
|
||||
MarkNativeAsOptional("Leader_CurrentLeader");
|
||||
MarkNativeAsOptional("Leader_SetLeader");
|
||||
MarkNativeAsOptional("Leader_Is");
|
||||
MarkNativeAsOptional("Leader_GetLeaderCount");
|
||||
MarkNativeAsOptional("Leader_GetLeader");
|
||||
}
|
||||
#endif
|
||||
#endif // LEADER3_CORE
|
||||
@@ -0,0 +1,105 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 chat: `title listener, force-mute while a leader talks.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// `<text> in chat sets the title of the leader's last marker. '`' alone clears it.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs)
|
||||
{
|
||||
if (client == 0 || sArgs[0] != '`' || !IsLeader(client))
|
||||
return Plugin_Continue;
|
||||
|
||||
char text[128];
|
||||
strcopy(text, sizeof(text), sArgs[1]);
|
||||
TrimString(text);
|
||||
|
||||
Marker_SetTitle(client, text);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Force-mute: while any leader is talking, mute everyone without admin access.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
public Action Cmd_ForceLeader(int client, int args)
|
||||
{
|
||||
if (g_alLeaders.Length == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s Cannot use this when there is no leader at the moment.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
// Enabling is reserved for whitelisted leaders (server console excepted); disabling is not.
|
||||
if (!g_bForceLeaderMute && client != 0 && !Whitelist_IsWhitelisted(client))
|
||||
{
|
||||
ReplyToCommand(client, "%s Only players on the leader whitelist can enable force mute.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
g_bForceLeaderMute = !g_bForceLeaderMute;
|
||||
|
||||
if (g_bForceLeaderMute)
|
||||
PrintToChatAll("%s\x04%N\x01 activated force mute while a leader is speaking!", PREFIX, client);
|
||||
else
|
||||
PrintToChatAll("%s\x04%N\x01 deactivated force mute while a leader is speaking!", PREFIX, client);
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
bool IsAnyLeaderTalking()
|
||||
{
|
||||
for (int i = 0; i < g_alLeaders.Length; i++)
|
||||
{
|
||||
int leader = GetClientOfUserId(g_alLeaders.Get(i));
|
||||
if (leader > 0 && IsClientTalking(leader))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Action Chat_CheckLeaderVoice(Handle timer)
|
||||
{
|
||||
if (!g_bForceLeaderMute || g_alLeaders.Length == 0)
|
||||
{
|
||||
if (!g_bForceLeaderMute)
|
||||
Chat_UnmuteForced();
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
if (IsAnyLeaderTalking())
|
||||
{
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (IsClientInGame(i) && !g_bIsLeader[i] && !BaseComm_IsClientMuted(i)
|
||||
&& !CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC, false))
|
||||
{
|
||||
BaseComm_SetClientMute(i, true);
|
||||
g_bForceMuted[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_bForceMuted[i] && IsClientInGame(i) && IsClientTalking(i))
|
||||
PrintHintText(i, "You are muted right now because a leader is talking!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Chat_UnmuteForced();
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void Chat_UnmuteForced()
|
||||
{
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_bForceMuted[i] && IsClientInGame(i))
|
||||
{
|
||||
BaseComm_SetClientMute(i, false);
|
||||
g_bForceMuted[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,599 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 effects: xray shell, overhead sprite, beacon rings, +use view tracer.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
void Effects_Resume(int client)
|
||||
{
|
||||
if (!IsEffectsActive(client))
|
||||
return;
|
||||
|
||||
if (g_iEffectFlags[client] & EFFECT_XRAY)
|
||||
Xray_Create(client);
|
||||
|
||||
if (g_iSpriteChoice[client] != SPRITE_NONE)
|
||||
Sprite_Create(client);
|
||||
|
||||
if ((g_iEffectFlags[client] & EFFECT_BEACON) && g_iBeaconSerial[client] == 0)
|
||||
Beacon_Start(client);
|
||||
}
|
||||
|
||||
void Effects_Suspend(int client)
|
||||
{
|
||||
Xray_Kill(client);
|
||||
Sprite_Kill(client);
|
||||
Beacon_Stop(client);
|
||||
g_iEffectFlags[client] &= ~EFFECT_TRACER_TOGGLED;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Xray shell: prop_dynamic_override with the shipped xray model ($additive + $ignorez material,
|
||||
// so it shows through walls), bonemerged onto the player and tinted with the tier colour.
|
||||
// SetTransmit hides it from zombies, from the leader themselves, and from first-person spectators
|
||||
// of the leader.
|
||||
//
|
||||
// $ignorez only helps once the client has the entity; CheckTransmit culls edicts against the
|
||||
// viewer's PVS first, so the shell (and the leader) vanished whenever the viewer was in another
|
||||
// vis area - thick walls, other side of the map. Fix: strip the shell's edict state down to
|
||||
// FL_EDICT_FULLCHECK so the engine calls ShouldTransmit() every frame, and DHook that to return
|
||||
// FL_EDICT_ALWAYS. The engine then calls SetTransmit(pInfo, true), which marks the shell AND its
|
||||
// move parent (the leader) for sending regardless of PVS, and still runs Hook_XrayShellTransmit,
|
||||
// so zombies keep seeing nothing.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Xray_SetupForceTransmit()
|
||||
{
|
||||
// SDKHooks' gamedata keeps the SetTransmit vtable index current; ShouldTransmit sits two
|
||||
// slots before it (ShouldTransmit, UpdateTransmitState, SetTransmit in baseentity.h).
|
||||
Handle gc = LoadGameConfigFile("sdkhooks.games/engine.ep2v");
|
||||
if (gc == null)
|
||||
{
|
||||
LogError("Could not load sdkhooks.games/engine.ep2v; leader xray stays PVS-limited.");
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = GameConfGetOffset(gc, "SetTransmit");
|
||||
delete gc;
|
||||
if (offset == -1)
|
||||
{
|
||||
LogError("No SetTransmit offset in sdkhooks.games/engine.ep2v; leader xray stays PVS-limited.");
|
||||
return;
|
||||
}
|
||||
|
||||
g_hXrayShouldTransmit = DHookCreate(offset - 2, HookType_Entity, ReturnType_Int, ThisPointer_Ignore, Hook_XrayShouldTransmit);
|
||||
DHookAddParam(g_hXrayShouldTransmit, HookParamType_ObjectPtr); // const CCheckTransmitInfo *
|
||||
}
|
||||
|
||||
public MRESReturn Hook_XrayShouldTransmit(Handle hReturn, Handle hParams)
|
||||
{
|
||||
DHookSetReturn(hReturn, FL_EDICT_ALWAYS);
|
||||
return MRES_Supercede;
|
||||
}
|
||||
|
||||
void Xray_Create(int client)
|
||||
{
|
||||
Xray_Kill(client);
|
||||
|
||||
if (!IsEffectsActive(client))
|
||||
return;
|
||||
|
||||
int ent = CreateEntityByName("prop_dynamic_override");
|
||||
if (ent == -1)
|
||||
return;
|
||||
|
||||
DispatchKeyValue(ent, "model", XRAY_MODEL);
|
||||
DispatchKeyValue(ent, "solid", "0");
|
||||
DispatchKeyValue(ent, "spawnflags", "256");
|
||||
DispatchKeyValue(ent, "disableshadows", "1");
|
||||
DispatchKeyValue(ent, "disablereceiveshadows", "1");
|
||||
if (!DispatchSpawn(ent))
|
||||
{
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
return;
|
||||
}
|
||||
SetEntProp(ent, Prop_Send, "m_CollisionGroup", 11); // COLLISION_GROUP_WEAPON: never collides with players
|
||||
|
||||
int colour[4];
|
||||
GetLeaderColour(client, colour);
|
||||
SetEntityRenderMode(ent, RENDER_TRANSADD);
|
||||
SetEntityRenderColor(ent, colour[0], colour[1], colour[2], g_cvXrayAlpha.IntValue);
|
||||
|
||||
// CBaseEntity::FollowEntity() recipe. The client only bone-merges when the entity has
|
||||
// EF_BONEMERGE, a move parent AND MOVETYPE_NONE (C_BaseEntity::IsFollowingEntity) --
|
||||
// prop_dynamic is MOVETYPE_PUSH and SetParent doesn't change that, hence t-posing shells.
|
||||
float origin[3], angles[3];
|
||||
GetClientAbsOrigin(client, origin);
|
||||
GetClientAbsAngles(client, angles);
|
||||
TeleportEntity(ent, origin, angles, NULL_VECTOR);
|
||||
|
||||
SetVariantString("!activator");
|
||||
AcceptEntityInput(ent, "SetParent", client, ent);
|
||||
SetEntityMoveType(ent, MOVETYPE_NONE);
|
||||
SetEntProp(ent, Prop_Send, "m_fEffects",
|
||||
EF_BONEMERGE|EF_BONEMERGE_FASTCULL|EF_NOSHADOW|EF_NORECEIVESHADOW|EF_PARENT_ANIMATES);
|
||||
|
||||
SetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity", client);
|
||||
SDKHook(ent, SDKHook_SetTransmit, Hook_XrayShellTransmit);
|
||||
|
||||
if (g_hXrayShouldTransmit != null)
|
||||
{
|
||||
DHookEntity(g_hXrayShouldTransmit, false, ent);
|
||||
// FULLCHECK is the absence of the other transmit-state flags.
|
||||
SetEdictFlags(ent, GetEdictFlags(ent) & ~(FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK|FL_EDICT_DONTSEND));
|
||||
}
|
||||
|
||||
g_iXrayRef[client] = EntIndexToEntRef(ent);
|
||||
}
|
||||
|
||||
public Action Hook_XrayShellTransmit(int ent, int client)
|
||||
{
|
||||
// Anything that recomputes transmit state (DispatchUpdateTransmitState) puts PVSCHECK
|
||||
// back on the edict; strip it so Hook_XrayShouldTransmit resumes overriding PVS culling.
|
||||
if (g_hXrayShouldTransmit != null)
|
||||
{
|
||||
int flags = GetEdictFlags(ent);
|
||||
if (flags & (FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK))
|
||||
SetEdictFlags(ent, flags & ~(FL_EDICT_ALWAYS|FL_EDICT_PVSCHECK));
|
||||
}
|
||||
|
||||
int owner = GetEntPropEnt(ent, Prop_Send, "m_hOwnerEntity");
|
||||
if (owner == client)
|
||||
return Plugin_Handled;
|
||||
|
||||
// Humans only: never reveal the leader's through-wall xray to zombies.
|
||||
if (IsLivingZombie(client))
|
||||
return Plugin_Handled;
|
||||
|
||||
// First-person spectators of the owner would get the shell in their face too.
|
||||
if (!IsPlayerAlive(client) && owner != -1
|
||||
&& GetEntProp(client, Prop_Send, "m_iObserverMode") == 4
|
||||
&& GetEntPropEnt(client, Prop_Send, "m_hObserverTarget") == owner)
|
||||
return Plugin_Handled;
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void Xray_Kill(int client)
|
||||
{
|
||||
int ent = EntRefToEntIndex(g_iXrayRef[client]);
|
||||
g_iXrayRef[client] = INVALID_ENT_REFERENCE;
|
||||
|
||||
if (ent == INVALID_ENT_REFERENCE || !IsValidEdict(ent))
|
||||
return;
|
||||
|
||||
char classname[64];
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
if (StrContains(classname, "prop_dynamic") == 0)
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
}
|
||||
|
||||
// Re-create the xray with the right tier colour (used when primary changes).
|
||||
void Effects_RefreshXray(int client)
|
||||
{
|
||||
if ((g_iEffectFlags[client] & EFFECT_XRAY) && IsEffectsActive(client))
|
||||
Xray_Create(client);
|
||||
}
|
||||
|
||||
void Xray_Toggle(int client)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_XRAY;
|
||||
if (g_iEffectFlags[client] & EFFECT_XRAY)
|
||||
{
|
||||
Xray_Create(client);
|
||||
PrintToChat(client, "%sXray enabled.", PREFIX);
|
||||
}
|
||||
else
|
||||
{
|
||||
Xray_Kill(client);
|
||||
PrintToChat(client, "%sXray disabled.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Overhead sprite: 'Defend here' / 'Follow me'
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Sprite_Set(int client, int choice)
|
||||
{
|
||||
g_iSpriteChoice[client] = choice;
|
||||
Sprite_Kill(client);
|
||||
|
||||
if (choice == SPRITE_NONE)
|
||||
{
|
||||
PrintToChat(client, "%sSprite removed.", PREFIX);
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite_Create(client);
|
||||
PrintToChat(client, "%sSprite changed to '%s'.", PREFIX, choice == SPRITE_CHOICE_DEFEND ? "Defend Here" : "Follow Me");
|
||||
}
|
||||
|
||||
void Sprite_Create(int client)
|
||||
{
|
||||
Sprite_Kill(client);
|
||||
|
||||
if (!IsEffectsActive(client) || g_iSpriteChoice[client] == SPRITE_NONE)
|
||||
return;
|
||||
|
||||
char mat[PLATFORM_MAX_PATH];
|
||||
strcopy(mat, sizeof(mat), g_iSpriteChoice[client] == SPRITE_CHOICE_DEFEND ? SPRITE_DEFEND : SPRITE_FOLLOW);
|
||||
|
||||
g_iSpriteRef[client] = AttachSpriteEnt(client, mat, "0.1", "1", "255 255 255", 45.0);
|
||||
}
|
||||
|
||||
void Sprite_Kill(int client)
|
||||
{
|
||||
KillSpriteRef(g_iSpriteRef[client]);
|
||||
g_iSpriteRef[client] = INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// env_sprite attach for the overhead sprite (Leader2's targetname-rename parenting trick).
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
int AttachSpriteEnt(int client, const char[] material, const char[] scale, const char[] rendermode, const char[] colour, float zOffset)
|
||||
{
|
||||
if (!IsPlayerAlive(client))
|
||||
return INVALID_ENT_REFERENCE;
|
||||
|
||||
char targetname[16], oldTargetname[64];
|
||||
GetEntPropString(client, Prop_Data, "m_iName", oldTargetname, sizeof(oldTargetname));
|
||||
Format(targetname, sizeof(targetname), "Client%d", client);
|
||||
DispatchKeyValue(client, "targetname", targetname);
|
||||
|
||||
float origin[3];
|
||||
GetClientEyePosition(client, origin);
|
||||
origin[2] += zOffset;
|
||||
|
||||
int ent = CreateEntityByName("env_sprite");
|
||||
if (ent == -1)
|
||||
{
|
||||
DispatchKeyValue(client, "targetname", oldTargetname);
|
||||
return INVALID_ENT_REFERENCE;
|
||||
}
|
||||
|
||||
DispatchKeyValue(ent, "model", material);
|
||||
DispatchKeyValue(ent, "classname", "env_sprite");
|
||||
DispatchKeyValue(ent, "spawnflags", "1");
|
||||
DispatchKeyValue(ent, "scale", scale);
|
||||
DispatchKeyValue(ent, "rendermode", rendermode);
|
||||
DispatchKeyValue(ent, "rendercolor", colour);
|
||||
DispatchSpawn(ent);
|
||||
TeleportEntity(ent, origin, NULL_VECTOR, NULL_VECTOR);
|
||||
SetVariantString(targetname);
|
||||
AcceptEntityInput(ent, "SetParent", ent, ent, 0);
|
||||
|
||||
DispatchKeyValue(client, "targetname", oldTargetname);
|
||||
|
||||
return EntIndexToEntRef(ent);
|
||||
}
|
||||
|
||||
void KillSpriteRef(int ref)
|
||||
{
|
||||
if (ref == INVALID_ENT_REFERENCE)
|
||||
return;
|
||||
|
||||
int ent = EntRefToEntIndex(ref);
|
||||
if (ent == INVALID_ENT_REFERENCE || !IsValidEdict(ent))
|
||||
return;
|
||||
|
||||
char classname[64];
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
if (strcmp(classname, "env_sprite") == 0)
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Beacon (serial-guarded repeating timer, Leader2/Couch ring pattern)
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Beacon_Toggle(int client)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_BEACON;
|
||||
if (g_iEffectFlags[client] & EFFECT_BEACON)
|
||||
{
|
||||
if (IsEffectsActive(client))
|
||||
Beacon_Start(client);
|
||||
PrintToChat(client, "%sBeacon enabled.", PREFIX);
|
||||
LogAction(client, client, "\"%L\" set a leader beacon on himself", client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Beacon_Stop(client);
|
||||
PrintToChat(client, "%sBeacon disabled.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
void Beacon_Start(int client)
|
||||
{
|
||||
g_iBeaconSerial[client] = ++g_iSerialGen;
|
||||
CreateTimer(g_cvBeaconInterval.FloatValue, Timer_Beacon, client | (g_iSerialGen << 7), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
void Beacon_Stop(int client)
|
||||
{
|
||||
g_iBeaconSerial[client] = 0;
|
||||
}
|
||||
|
||||
public Action Timer_Beacon(Handle timer, any value)
|
||||
{
|
||||
int client = value & 0x7f;
|
||||
int serial = value >> 7;
|
||||
|
||||
if (g_iBeaconSerial[client] != serial || !IsClientInGame(client) || !IsPlayerAlive(client))
|
||||
{
|
||||
if (g_iBeaconSerial[client] == serial)
|
||||
g_iBeaconSerial[client] = 0;
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
float vec[3];
|
||||
GetClientAbsOrigin(client, vec);
|
||||
vec[2] += 10.0;
|
||||
|
||||
float radius = g_cvBeaconRadius.FloatValue;
|
||||
|
||||
int tierColour[4];
|
||||
GetLeaderColour(client, tierColour);
|
||||
TE_SetupBeamRingPoint(vec, 10.0, radius, g_iBeamSprite, g_iHaloSprite, 0, 15, 0.5, 5.0, 0.0, tierColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
int rainbowColour[4];
|
||||
float gameTime = GetGameTime();
|
||||
rainbowColour[0] = RoundFloat(Sine(2.5 * gameTime + 0.0) * 127.0 + 128.0);
|
||||
rainbowColour[1] = RoundFloat(Sine(2.5 * gameTime + 2.0943951) * 127.0 + 128.0);
|
||||
rainbowColour[2] = RoundFloat(Sine(2.5 * gameTime + 4.1887902) * 127.0 + 128.0);
|
||||
rainbowColour[3] = 255;
|
||||
|
||||
TE_SetupBeamRingPoint(vec, 10.0, radius, g_iBeamSprite, g_iHaloSprite, 0, 10, 0.6, 10.0, 0.5, rainbowColour, 10, 0);
|
||||
TE_SendToAll();
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void GetLeaderColour(int client, int colour[4])
|
||||
{
|
||||
if (g_bHasCustomColour[client])
|
||||
{
|
||||
colour[0] = g_iCustomColour[client][0];
|
||||
colour[1] = g_iCustomColour[client][1];
|
||||
colour[2] = g_iCustomColour[client][2];
|
||||
}
|
||||
else if (IsPrimaryLeader(client))
|
||||
{
|
||||
colour[0] = g_iXrayColourPrimary[0];
|
||||
colour[1] = g_iXrayColourPrimary[1];
|
||||
colour[2] = g_iXrayColourPrimary[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
colour[0] = g_iXrayColourSub[0];
|
||||
colour[1] = g_iXrayColourSub[1];
|
||||
colour[2] = g_iXrayColourSub[2];
|
||||
}
|
||||
colour[3] = 255;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Custom leader colour: !leadercolour <name|RRGGBB|default>. Applies to xray, beacon and tracer.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
static const char g_sColourNames[][] = {
|
||||
"red", "orange", "yellow", "lime", "green", "cyan",
|
||||
"blue", "purple", "magenta", "pink", "white"
|
||||
};
|
||||
static const int g_iColourValues[][3] = {
|
||||
{255, 0, 0}, {255, 128, 0}, {255, 255, 0}, {128, 255, 0}, {0, 255, 0}, {0, 255, 255},
|
||||
{0, 100, 255}, {160, 0, 255}, {255, 0, 255}, {255, 105, 180}, {255, 255, 255}
|
||||
};
|
||||
|
||||
public Action Cmd_LeaderColour(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s This command can only be used in-game.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!IsLeader(client))
|
||||
{
|
||||
PrintToChat(client, "%sOnly leaders can set a leader colour.", PREFIX);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (args == 0)
|
||||
{
|
||||
LeaderColour_PrintUsage(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char arg[32];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
TrimString(arg);
|
||||
|
||||
if (StrEqual(arg, "default", false) || StrEqual(arg, "reset", false))
|
||||
{
|
||||
g_bHasCustomColour[client] = false;
|
||||
LeaderColour_SaveCookie(client);
|
||||
Effects_RefreshXray(client);
|
||||
PrintToChat(client, "%sColour reset to the default %s colour.", PREFIX,
|
||||
IsPrimaryLeader(client) ? "primary leader" : "sub-leader");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
int rgb[3];
|
||||
if (!ParseCustomColour(arg, rgb))
|
||||
{
|
||||
LeaderColour_PrintUsage(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
// Refuse colours too dark to see as a xray tint or beam.
|
||||
if (rgb[0] < 40 && rgb[1] < 40 && rgb[2] < 40)
|
||||
{
|
||||
PrintToChat(client, "%sThat colour is too dark to be visible. Pick a brighter one.", PREFIX);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
g_iCustomColour[client][0] = rgb[0];
|
||||
g_iCustomColour[client][1] = rgb[1];
|
||||
g_iCustomColour[client][2] = rgb[2];
|
||||
g_bHasCustomColour[client] = true;
|
||||
LeaderColour_SaveCookie(client);
|
||||
Effects_RefreshXray(client);
|
||||
|
||||
PrintToChat(client, "%sColour set to \x07%02X%02X%02X#%02X%02X%02X\x01.", PREFIX,
|
||||
rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2]);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public void OnClientCookiesCached(int client)
|
||||
{
|
||||
if (!IsFakeClient(client))
|
||||
LeaderColour_LoadCookie(client);
|
||||
}
|
||||
|
||||
void LeaderColour_LoadCookie(int client)
|
||||
{
|
||||
char buf[16];
|
||||
g_hColourCookie.Get(client, buf, sizeof(buf));
|
||||
|
||||
int rgb[3];
|
||||
if (buf[0] && ParseCustomColour(buf, rgb))
|
||||
{
|
||||
g_iCustomColour[client][0] = rgb[0];
|
||||
g_iCustomColour[client][1] = rgb[1];
|
||||
g_iCustomColour[client][2] = rgb[2];
|
||||
g_bHasCustomColour[client] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void LeaderColour_SaveCookie(int client)
|
||||
{
|
||||
if (g_bHasCustomColour[client])
|
||||
{
|
||||
char buf[8];
|
||||
Format(buf, sizeof(buf), "%02X%02X%02X",
|
||||
g_iCustomColour[client][0], g_iCustomColour[client][1], g_iCustomColour[client][2]);
|
||||
g_hColourCookie.Set(client, buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_hColourCookie.Set(client, "");
|
||||
}
|
||||
}
|
||||
|
||||
void LeaderColour_PrintUsage(int client)
|
||||
{
|
||||
char names[256];
|
||||
for (int i = 0; i < sizeof(g_sColourNames); i++)
|
||||
{
|
||||
if (names[0])
|
||||
StrCat(names, sizeof(names), ", ");
|
||||
StrCat(names, sizeof(names), g_sColourNames[i]);
|
||||
}
|
||||
PrintToChat(client, "%sUsage: \x04!leadercolour <colour|hex|default>\x01", PREFIX);
|
||||
PrintToChat(client, "%sColours: \x04%s\x01, or a hex code like \x04FF66CC\x01.", PREFIX, names);
|
||||
}
|
||||
|
||||
bool ParseCustomColour(const char[] str, int rgb[3])
|
||||
{
|
||||
// Colour name
|
||||
for (int i = 0; i < sizeof(g_sColourNames); i++)
|
||||
{
|
||||
if (StrEqual(str, g_sColourNames[i], false))
|
||||
{
|
||||
rgb[0] = g_iColourValues[i][0];
|
||||
rgb[1] = g_iColourValues[i][1];
|
||||
rgb[2] = g_iColourValues[i][2];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Hex code: RRGGBB, optionally prefixed with # or 0x
|
||||
int offset = 0;
|
||||
if (str[0] == '#')
|
||||
offset = 1;
|
||||
else if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
|
||||
offset = 2;
|
||||
|
||||
if (strlen(str[offset]) != 6)
|
||||
return false;
|
||||
|
||||
int value;
|
||||
if (StringToIntEx(str[offset], value, 16) != 6)
|
||||
return false;
|
||||
|
||||
rgb[0] = (value >> 16) & 0xFF;
|
||||
rgb[1] = (value >> 8) & 0xFF;
|
||||
rgb[2] = value & 0xFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// View tracer: hold +use to show, tap +use to toggle. Visual only; buttons are never modified.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Effects_OnPlayerRunCmd(int client, int buttons)
|
||||
{
|
||||
bool pressed = (buttons & IN_USE) && !(g_iLastButtons[client] & IN_USE);
|
||||
bool released = !(buttons & IN_USE) && (g_iLastButtons[client] & IN_USE);
|
||||
g_iLastButtons[client] = buttons;
|
||||
|
||||
if (!IsEffectsActive(client) || g_iTracerMode[client] == TRACER_MODE_OFF)
|
||||
return;
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
g_fUsePressedAt[client] = GetGameTime();
|
||||
}
|
||||
else if (released && g_iTracerMode[client] == TRACER_MODE_TAP
|
||||
&& GetGameTime() - g_fUsePressedAt[client] < g_cvTracerTapTime.FloatValue)
|
||||
{
|
||||
g_iEffectFlags[client] ^= EFFECT_TRACER_TOGGLED;
|
||||
}
|
||||
|
||||
bool active = (buttons & IN_USE) != 0 || (g_iEffectFlags[client] & EFFECT_TRACER_TOGGLED) != 0;
|
||||
if (!active)
|
||||
return;
|
||||
|
||||
float now = GetGameTime();
|
||||
if (now < g_fNextTracerSend[client])
|
||||
return;
|
||||
g_fNextTracerSend[client] = now + g_cvTracerInterval.FloatValue;
|
||||
|
||||
Tracer_Draw(client);
|
||||
}
|
||||
|
||||
void Tracer_Draw(int client)
|
||||
{
|
||||
float endPos[3];
|
||||
if (!TracePlayerAngles(client, endPos))
|
||||
return;
|
||||
|
||||
float startPos[3];
|
||||
GetClientEyePosition(client, startPos);
|
||||
startPos[2] -= 5.0; // keep the beam out of the leader's own crosshair
|
||||
|
||||
int colour[4];
|
||||
GetLeaderColour(client, colour);
|
||||
|
||||
TE_SetupBeamPoints(startPos, endPos, g_iBeamSprite, g_iHaloSprite, 0, 0,
|
||||
g_cvTracerLife.FloatValue, g_cvTracerWidth.FloatValue, g_cvTracerWidth.FloatValue,
|
||||
0, 0.0, colour, 0);
|
||||
TE_SendToAll();
|
||||
}
|
||||
|
||||
void Tracer_CycleMode(int client)
|
||||
{
|
||||
// Rotate Hold + Tap-toggle -> Hold only -> Off -> Hold + Tap-toggle.
|
||||
switch (g_iTracerMode[client])
|
||||
{
|
||||
case TRACER_MODE_TAP: g_iTracerMode[client] = TRACER_MODE_HOLD;
|
||||
case TRACER_MODE_HOLD: g_iTracerMode[client] = TRACER_MODE_OFF;
|
||||
default: g_iTracerMode[client] = TRACER_MODE_TAP;
|
||||
}
|
||||
g_iEffectFlags[client] &= ~EFFECT_TRACER_TOGGLED;
|
||||
|
||||
char mode[40];
|
||||
switch (g_iTracerMode[client])
|
||||
{
|
||||
case TRACER_MODE_TAP: mode = "hold +USE OR tap to toggle";
|
||||
case TRACER_MODE_HOLD: mode = "hold +USE only";
|
||||
default: mode = "off";
|
||||
}
|
||||
PrintToChat(client, "%sTracer mode: \x04%s\x01.", PREFIX, mode);
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 markers: unloze_ping prop_dynamic markers with point_worldtext titles.
|
||||
// The ArrayList is the authoritative store; entities are respawned from it every round.
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
enum struct Marker
|
||||
{
|
||||
int ownerUserId;
|
||||
char ownerName[MAX_NAME_LENGTH];
|
||||
int skin;
|
||||
float pos[3];
|
||||
char title[128];
|
||||
float placedAt;
|
||||
int propRef;
|
||||
int textRef;
|
||||
}
|
||||
|
||||
ArrayList g_alMarkers;
|
||||
|
||||
void Markers_OnPluginStart()
|
||||
{
|
||||
g_alMarkers = new ArrayList(sizeof(Marker));
|
||||
}
|
||||
|
||||
void SkinName(int skin, char[] buf, int maxlen)
|
||||
{
|
||||
switch (skin)
|
||||
{
|
||||
case SKIN_SKULL: strcopy(buf, maxlen, "Skull");
|
||||
case SKIN_MOON: strcopy(buf, maxlen, "Moon");
|
||||
case SKIN_CROSS: strcopy(buf, maxlen, "Cross");
|
||||
default: strcopy(buf, maxlen, "Star");
|
||||
}
|
||||
}
|
||||
|
||||
int SkinFromName(const char[] name)
|
||||
{
|
||||
if (StrEqual(name, "star", false)) return SKIN_STAR;
|
||||
if (StrEqual(name, "skull", false)) return SKIN_SKULL;
|
||||
if (StrEqual(name, "moon", false)) return SKIN_MOON;
|
||||
if (StrEqual(name, "cross", false)) return SKIN_CROSS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Placement
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void Marker_Place(int client, int skin)
|
||||
{
|
||||
if (!IsLeader(client))
|
||||
return;
|
||||
|
||||
// Dead and spectating leaders may keep placing markers (leading from spec);
|
||||
// only living zombies are blocked.
|
||||
if (IsLivingZombie(client))
|
||||
{
|
||||
PrintToChat(client, "%sYou cannot place markers as a zombie.", PREFIX);
|
||||
return;
|
||||
}
|
||||
|
||||
float pos[3];
|
||||
if (!TracePlayerAngles(client, pos))
|
||||
{
|
||||
PrintToChat(client, "%sCould not find a spot to place the marker on.", PREFIX);
|
||||
return;
|
||||
}
|
||||
|
||||
char skinName[16];
|
||||
SkinName(skin, skinName, sizeof(skinName));
|
||||
|
||||
int userid = GetClientUserId(client);
|
||||
int existing = FindMarkerByOwnerSkin(userid, skin);
|
||||
|
||||
Marker marker;
|
||||
if (existing != -1)
|
||||
{
|
||||
// Moving keeps the title and stays out of chat. The prop and text entities are
|
||||
// teleported rather than respawned so repeated moves don't churn edicts.
|
||||
g_alMarkers.GetArray(existing, marker);
|
||||
marker.pos = pos;
|
||||
marker.placedAt = GetGameTime();
|
||||
GetClientName(client, marker.ownerName, sizeof(marker.ownerName));
|
||||
g_alMarkers.SetArray(existing, marker);
|
||||
Marker_MoveProp(existing);
|
||||
Marker_UpdateText(existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CountMarkersByOwner(userid) >= g_cvMarkerLimit.IntValue)
|
||||
{
|
||||
PrintToChat(client, "%sMarker limit reached (%d). Replace one of your markers or use \x04!clearmarkers\x01.", PREFIX, g_cvMarkerLimit.IntValue);
|
||||
return;
|
||||
}
|
||||
|
||||
marker.ownerUserId = userid;
|
||||
GetClientName(client, marker.ownerName, sizeof(marker.ownerName));
|
||||
marker.skin = skin;
|
||||
marker.pos = pos;
|
||||
marker.title[0] = '\0';
|
||||
marker.placedAt = GetGameTime();
|
||||
marker.propRef = INVALID_ENT_REFERENCE;
|
||||
marker.textRef = INVALID_ENT_REFERENCE;
|
||||
|
||||
int idx = g_alMarkers.PushArray(marker);
|
||||
SpawnMarkerProp(idx);
|
||||
PrintToChatAll("%s\x04%N\x01 placed a \x04%s\x01 marker.", PREFIX, client, skinName);
|
||||
PrintToChat(client, "%sType \x04`your text\x01 in chat to put a title on it.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
void Marker_PlaceBySpray(int client)
|
||||
{
|
||||
Marker_Place(client, SKIN_STAR);
|
||||
}
|
||||
|
||||
int FindMarkerByOwnerSkin(int userid, int skin)
|
||||
{
|
||||
Marker marker;
|
||||
for (int i = 0; i < g_alMarkers.Length; i++)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
if (marker.ownerUserId == userid && marker.skin == skin)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Marker_GetTitle(int client, int skin, char[] buf, int maxlen)
|
||||
{
|
||||
int idx = FindMarkerByOwnerSkin(GetClientUserId(client), skin);
|
||||
if (idx == -1)
|
||||
return false;
|
||||
|
||||
Marker marker;
|
||||
g_alMarkers.GetArray(idx, marker);
|
||||
if (marker.title[0] == '\0')
|
||||
return false;
|
||||
|
||||
strcopy(buf, maxlen, marker.title);
|
||||
return true;
|
||||
}
|
||||
|
||||
int CountMarkersByOwner(int userid)
|
||||
{
|
||||
int count = 0;
|
||||
Marker marker;
|
||||
for (int i = 0; i < g_alMarkers.Length; i++)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
if (marker.ownerUserId == userid)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Titles
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Titles typed entirely in lowercase are uppercased; mixed-case titles are kept as typed.
|
||||
void UppercaseAllLowerTitle(char[] text)
|
||||
{
|
||||
bool hasLower = false;
|
||||
for (int i = 0; text[i] != '\0'; i++)
|
||||
{
|
||||
int bytes = IsCharMB(text[i]);
|
||||
if (bytes > 1)
|
||||
{
|
||||
i += bytes - 1;
|
||||
continue;
|
||||
}
|
||||
if (IsCharUpper(text[i]))
|
||||
return;
|
||||
if (IsCharLower(text[i]))
|
||||
hasLower = true;
|
||||
}
|
||||
|
||||
if (!hasLower)
|
||||
return;
|
||||
|
||||
for (int i = 0; text[i] != '\0'; i++)
|
||||
{
|
||||
int bytes = IsCharMB(text[i]);
|
||||
if (bytes > 1)
|
||||
{
|
||||
i += bytes - 1;
|
||||
continue;
|
||||
}
|
||||
text[i] = CharToUpper(text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Marker_SetTitle(int client, const char[] text)
|
||||
{
|
||||
int userid = GetClientUserId(client);
|
||||
|
||||
// Most recently placed marker owned by this leader.
|
||||
int lastIdx = -1;
|
||||
float lastTime = -1.0;
|
||||
Marker marker;
|
||||
for (int i = 0; i < g_alMarkers.Length; i++)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
if (marker.ownerUserId == userid && marker.placedAt > lastTime)
|
||||
{
|
||||
lastTime = marker.placedAt;
|
||||
lastIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastIdx == -1)
|
||||
{
|
||||
PrintToChat(client, "%sYou have no marker to title. Place one first.", PREFIX);
|
||||
return;
|
||||
}
|
||||
|
||||
g_alMarkers.GetArray(lastIdx, marker);
|
||||
strcopy(marker.title, sizeof(marker.title), text);
|
||||
UppercaseAllLowerTitle(marker.title);
|
||||
g_alMarkers.SetArray(lastIdx, marker);
|
||||
Marker_UpdateText(lastIdx);
|
||||
|
||||
char skinName[16];
|
||||
SkinName(marker.skin, skinName, sizeof(skinName));
|
||||
|
||||
if (text[0] == '\0')
|
||||
{
|
||||
PrintToChat(client, "%sTitle removed from your \x04%s\x01 marker.", PREFIX, skinName);
|
||||
return;
|
||||
}
|
||||
PrintToChatAll("%s\x04%N\x01 marked \x04%s\x01: \x04%s", PREFIX, client, skinName, marker.title);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Entity lifecycle
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void SpawnMarkerProp(int idx)
|
||||
{
|
||||
Marker marker;
|
||||
g_alMarkers.GetArray(idx, marker);
|
||||
|
||||
int ent = CreateEntityByName("prop_dynamic");
|
||||
if (ent == -1)
|
||||
return;
|
||||
|
||||
SetEntityModel(ent, MARKER_MODEL);
|
||||
DispatchKeyValueFloat(ent, "modelscale", MARKER_MODEL_SCALE);
|
||||
|
||||
char skin[4];
|
||||
IntToString(marker.skin, skin, sizeof(skin));
|
||||
SetVariantString(skin);
|
||||
AcceptEntityInput(ent, "Skin");
|
||||
|
||||
DispatchSpawn(ent);
|
||||
TeleportEntity(ent, marker.pos, NULL_VECTOR, NULL_VECTOR);
|
||||
|
||||
marker.propRef = EntIndexToEntRef(ent);
|
||||
g_alMarkers.SetArray(idx, marker);
|
||||
}
|
||||
|
||||
void SpawnMarkerText(int idx)
|
||||
{
|
||||
Marker marker;
|
||||
g_alMarkers.GetArray(idx, marker);
|
||||
|
||||
if (marker.title[0] == '\0' || g_bWorldTextBroken)
|
||||
return;
|
||||
|
||||
int ent = CreateEntityByName("point_worldtext");
|
||||
if (ent == -1)
|
||||
{
|
||||
// Engine build without point_worldtext: titles stay chat-only for this map.
|
||||
g_bWorldTextBroken = true;
|
||||
LogMessage("point_worldtext could not be created; marker titles will not render in the world.");
|
||||
return;
|
||||
}
|
||||
|
||||
char text[192];
|
||||
Format(text, sizeof(text), "%s", marker.title);
|
||||
|
||||
DispatchKeyValue(ent, "message", text);
|
||||
DispatchKeyValue(ent, "font", "10");
|
||||
DispatchKeyValue(ent, "textsize", "12");
|
||||
DispatchKeyValue(ent, "color", "255 255 255");
|
||||
DispatchKeyValue(ent, "orientation", "1");
|
||||
DispatchKeyValue(ent, "rainbow", "1");
|
||||
DispatchSpawn(ent);
|
||||
|
||||
float pos[3];
|
||||
ComputeMarkerTextPos(marker, pos);
|
||||
TeleportEntity(ent, pos, NULL_VECTOR, NULL_VECTOR);
|
||||
|
||||
marker.textRef = EntIndexToEntRef(ent);
|
||||
g_alMarkers.SetArray(idx, marker);
|
||||
}
|
||||
|
||||
void ComputeMarkerTextPos(const Marker marker, float pos[3])
|
||||
{
|
||||
pos = marker.pos;
|
||||
// unloze_ping.mdl is 80 units tall and spawned at modelscale 1.5 (120 units).
|
||||
pos[2] += MARKER_MODEL_HEIGHT * MARKER_MODEL_SCALE + 8.0;
|
||||
|
||||
// Markers placed in low rooms would put the text inside the ceiling: trace up
|
||||
// from the marker base and keep the text MARKER_TEXT_CLEARANCE below any hit.
|
||||
float up[3];
|
||||
up = marker.pos;
|
||||
up[2] = pos[2] + MARKER_TEXT_CLEARANCE;
|
||||
Handle trace = TR_TraceRayFilterEx(marker.pos, up, MASK_SOLID_BRUSHONLY, RayType_EndPoint, TraceFilter_NoPlayers);
|
||||
if (TR_DidHit(trace))
|
||||
{
|
||||
float hit[3];
|
||||
TR_GetEndPosition(hit, trace);
|
||||
pos[2] = hit[2] - MARKER_TEXT_CLEARANCE;
|
||||
}
|
||||
delete trace;
|
||||
}
|
||||
|
||||
// Teleports the existing marker prop to the stored position, spawning one only if the
|
||||
// reference went stale.
|
||||
void Marker_MoveProp(int idx)
|
||||
{
|
||||
Marker marker;
|
||||
g_alMarkers.GetArray(idx, marker);
|
||||
|
||||
int ent = LiveEntRef(marker.propRef, "prop_dynamic");
|
||||
if (ent == -1)
|
||||
{
|
||||
SpawnMarkerProp(idx);
|
||||
return;
|
||||
}
|
||||
|
||||
TeleportEntity(ent, marker.pos, NULL_VECTOR, NULL_VECTOR);
|
||||
}
|
||||
|
||||
// Retitles/moves the existing point_worldtext in place (SetText is networked via
|
||||
// SendPropString) instead of respawning it, so retitles and moves don't churn edicts.
|
||||
void Marker_UpdateText(int idx)
|
||||
{
|
||||
Marker marker;
|
||||
g_alMarkers.GetArray(idx, marker);
|
||||
|
||||
if (marker.title[0] == '\0')
|
||||
{
|
||||
KillEntRef(marker.textRef, "point_worldtext");
|
||||
marker.textRef = INVALID_ENT_REFERENCE;
|
||||
g_alMarkers.SetArray(idx, marker);
|
||||
return;
|
||||
}
|
||||
|
||||
int ent = LiveEntRef(marker.textRef, "point_worldtext");
|
||||
if (ent == -1)
|
||||
{
|
||||
SpawnMarkerText(idx);
|
||||
return;
|
||||
}
|
||||
|
||||
SetVariantString(marker.title);
|
||||
AcceptEntityInput(ent, "SetText");
|
||||
|
||||
float pos[3];
|
||||
ComputeMarkerTextPos(marker, pos);
|
||||
TeleportEntity(ent, pos, NULL_VECTOR, NULL_VECTOR);
|
||||
}
|
||||
|
||||
void Marker_KillEnts(Marker marker)
|
||||
{
|
||||
KillEntRef(marker.propRef, "prop_dynamic");
|
||||
KillEntRef(marker.textRef, "point_worldtext");
|
||||
}
|
||||
|
||||
void KillEntRef(int ref, const char[] expectedClass)
|
||||
{
|
||||
int ent = LiveEntRef(ref, expectedClass);
|
||||
if (ent != -1)
|
||||
AcceptEntityInput(ent, "Kill");
|
||||
}
|
||||
|
||||
// Resolves an entity reference, returning -1 unless it is still a live edict of the
|
||||
// expected class.
|
||||
int LiveEntRef(int ref, const char[] expectedClass)
|
||||
{
|
||||
if (ref == INVALID_ENT_REFERENCE)
|
||||
return -1;
|
||||
|
||||
int ent = EntRefToEntIndex(ref);
|
||||
if (ent == INVALID_ENT_REFERENCE || !IsValidEdict(ent))
|
||||
return -1;
|
||||
|
||||
char classname[64];
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
return strcmp(classname, expectedClass) == 0 ? ent : -1;
|
||||
}
|
||||
|
||||
// Round transitions delete runtime entities: rebuild every marker from the stored data.
|
||||
void Markers_OnRoundStart()
|
||||
{
|
||||
Marker marker;
|
||||
for (int i = 0; i < g_alMarkers.Length; i++)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
marker.propRef = INVALID_ENT_REFERENCE;
|
||||
marker.textRef = INVALID_ENT_REFERENCE;
|
||||
g_alMarkers.SetArray(i, marker);
|
||||
|
||||
SpawnMarkerProp(i);
|
||||
SpawnMarkerText(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Markers_ClearAll(bool killEnts = true)
|
||||
{
|
||||
if (killEnts)
|
||||
{
|
||||
Marker marker;
|
||||
for (int i = 0; i < g_alMarkers.Length; i++)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
Marker_KillEnts(marker);
|
||||
}
|
||||
}
|
||||
g_alMarkers.Clear();
|
||||
}
|
||||
|
||||
void Marker_ClearByOwner(int client, bool announce = true)
|
||||
{
|
||||
int userid = GetClientUserId(client);
|
||||
int removed = 0;
|
||||
|
||||
Marker marker;
|
||||
for (int i = g_alMarkers.Length - 1; i >= 0; i--)
|
||||
{
|
||||
g_alMarkers.GetArray(i, marker);
|
||||
if (marker.ownerUserId != userid)
|
||||
continue;
|
||||
|
||||
Marker_KillEnts(marker);
|
||||
g_alMarkers.Erase(i);
|
||||
removed++;
|
||||
}
|
||||
|
||||
if (!announce)
|
||||
return;
|
||||
|
||||
if (removed > 0)
|
||||
PrintToChat(client, "%sRemoved %d of your markers.", PREFIX, removed);
|
||||
else
|
||||
PrintToChat(client, "%sYou have no markers.", PREFIX);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Commands
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
public Action Cmd_Marker(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s Cannot use this from the console.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!IsLeader(client))
|
||||
{
|
||||
ReplyToCommand(client, "%s Only leaders can place markers.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (args < 1)
|
||||
{
|
||||
ReplyToCommand(client, "%s Usage: sm_marker <star|skull|moon|cross>", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char arg[16];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
int skin = SkinFromName(arg);
|
||||
if (skin == -1)
|
||||
{
|
||||
ReplyToCommand(client, "%s Unknown marker type '%s'. Use star, skull, moon or cross.", PREFIX_PLAIN, arg);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
Marker_Place(client, skin);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Cmd_MarkerTitle(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s Cannot use this from the console.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!IsLeader(client))
|
||||
{
|
||||
ReplyToCommand(client, "%s Only leaders can title markers.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char text[128];
|
||||
GetCmdArgString(text, sizeof(text));
|
||||
StripQuotes(text);
|
||||
TrimString(text);
|
||||
|
||||
Marker_SetTitle(client, text);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Cmd_ClearMarkers(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
Markers_ClearAll();
|
||||
ReplyToCommand(client, "%s All markers cleared.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (args >= 1 && CheckCommandAccess(client, "sm_admin", ADMFLAG_GENERIC, false))
|
||||
{
|
||||
char arg[65];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
if (StrEqual(arg, "all", false))
|
||||
{
|
||||
Markers_ClearAll();
|
||||
PrintToChatAll("%sAll markers have been cleared by \x04%N\x01.", PREFIX, client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char targetName[MAX_TARGET_LENGTH];
|
||||
int targets[MAXPLAYERS];
|
||||
bool tnIsMl;
|
||||
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;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
Marker_ClearByOwner(targets[i]);
|
||||
ReplyToCommand(client, "%s Cleared markers of %s.", PREFIX_PLAIN, targetName);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!IsLeader(client))
|
||||
{
|
||||
ReplyToCommand(client, "%s Only leaders can clear markers.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
Marker_ClearByOwner(client);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Aim trace (port of Leader2's TracePlayerAngles)
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool TracePlayerAngles(int client, float vecResult[3])
|
||||
{
|
||||
if (!IsClientInGame(client))
|
||||
return false;
|
||||
|
||||
float vecEyeAngles[3], vecEyeOrigin[3];
|
||||
GetClientEyeAngles(client, vecEyeAngles);
|
||||
GetClientEyePosition(client, vecEyeOrigin);
|
||||
|
||||
Handle trace = TR_TraceRayFilterEx(vecEyeOrigin, vecEyeAngles, MASK_SHOT_HULL, RayType_Infinite, TraceFilter_NoPlayers);
|
||||
|
||||
// AllSolid: eyes clipped inside a solid (crowd pushes); endpos degenerates to the eye.
|
||||
bool hit = TR_DidHit(trace) && !TR_AllSolid(trace);
|
||||
int hitEnt = hit ? TR_GetEntityIndex(trace) : -1;
|
||||
if (hit)
|
||||
TR_GetEndPosition(vecResult, trace);
|
||||
delete trace;
|
||||
|
||||
if (!hit)
|
||||
return false;
|
||||
|
||||
// NaN or out-of-grid components reach the client as 0 (WriteBitCoord encodes them
|
||||
// as zero), which draws the beam to the world origin. Catch them before the TE.
|
||||
if (vecResult[0] != vecResult[0] || vecResult[1] != vecResult[1] || vecResult[2] != vecResult[2]
|
||||
|| FloatAbs(vecResult[0]) > 16384.0 || FloatAbs(vecResult[1]) > 16384.0 || FloatAbs(vecResult[2]) > 16384.0)
|
||||
{
|
||||
LogSuspectTrace(client, hitEnt, vecResult, vecEyeOrigin, vecEyeAngles, "insane endpos");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ZE maps park "removed" but still solid props at the world origin; their hulls
|
||||
// catch the ray there. Hits on the world itself near origin stay legitimate.
|
||||
if (hitEnt > 0 && GetVectorLength(vecResult) < 64.0)
|
||||
{
|
||||
LogSuspectTrace(client, hitEnt, vecResult, vecEyeOrigin, vecEyeAngles, "entity hull at world origin");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// One line per 5s so a leader holding the tracer on a culprit doesn't flood the log.
|
||||
static float g_fNextSuspectTraceLog;
|
||||
|
||||
static void LogSuspectTrace(int client, int ent, const float end[3], const float eye[3], const float ang[3], const char[] why)
|
||||
{
|
||||
float now = GetGameTime();
|
||||
if (now < g_fNextSuspectTraceLog)
|
||||
return;
|
||||
g_fNextSuspectTraceLog = now + 5.0;
|
||||
|
||||
char classname[64] = "worldspawn";
|
||||
char model[PLATFORM_MAX_PATH];
|
||||
if (ent > 0 && IsValidEdict(ent))
|
||||
{
|
||||
GetEdictClassname(ent, classname, sizeof(classname));
|
||||
GetEntPropString(ent, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||
}
|
||||
|
||||
LogMessage("Aim trace rejected (%s) for %L: end (%.1f %.1f %.1f), hit ent %d (%s, model '%s'), eye (%.1f %.1f %.1f), ang (%.1f %.1f %.1f)",
|
||||
why, client, end[0], end[1], end[2], ent, classname, model,
|
||||
eye[0], eye[1], eye[2], ang[0], ang[1], ang[2]);
|
||||
}
|
||||
|
||||
public bool TraceFilter_NoPlayers(int entity, int contentsMask)
|
||||
{
|
||||
return entity > MaxClients;
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 menus. Opened with !leader or the flashlight key (impulse 100).
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
bool CanManageLeaders(int client)
|
||||
{
|
||||
return IsPrimaryLeader(client) || CheckCommandAccess(client, "sm_admin", ADMFLAG_GENERIC, false);
|
||||
}
|
||||
|
||||
void AddMarkerMenuItem(Menu menu, int client, const char[] info, const char[] name, int skin)
|
||||
{
|
||||
char title[128], display[160];
|
||||
if (Marker_GetTitle(client, skin, title, sizeof(title)))
|
||||
{
|
||||
Format(display, sizeof(display), "%s: %s", name, title);
|
||||
menu.AddItem(info, display);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddItem(info, name);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowLeaderMenu(int client)
|
||||
{
|
||||
if (!IsLeader(client))
|
||||
return;
|
||||
|
||||
Menu menu = new Menu(MenuHandler_Main);
|
||||
|
||||
menu.SetTitle("Leader Menu (%s)\nBeacon: %s | Xray: %s",
|
||||
IsPrimaryLeader(client) ? "PRIMARY" : "SUB-LEADER",
|
||||
(g_iEffectFlags[client] & EFFECT_BEACON) ? "On" : "Off",
|
||||
(g_iEffectFlags[client] & EFFECT_XRAY) ? "On" : "Off");
|
||||
|
||||
AddMarkerMenuItem(menu, client, "skull", "Skull", SKIN_SKULL);
|
||||
AddMarkerMenuItem(menu, client, "cross", "Cross", SKIN_CROSS);
|
||||
AddMarkerMenuItem(menu, client, "moon", "Moon", SKIN_MOON);
|
||||
AddMarkerMenuItem(menu, client, "star", "Star", SKIN_STAR);
|
||||
|
||||
menu.AddItem("clear", "Clear markers");
|
||||
|
||||
char buf[32];
|
||||
switch (g_iSpriteChoice[client])
|
||||
{
|
||||
case SPRITE_CHOICE_FOLLOW: buf = "Sprite: Follow me";
|
||||
case SPRITE_CHOICE_DEFEND: buf = "Sprite: Defend here";
|
||||
default: buf = "Sprite: Off";
|
||||
}
|
||||
menu.AddItem("sprite", buf);
|
||||
|
||||
menu.AddItem("settings", "Settings");
|
||||
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
public int MenuHandler_Main(Menu menu, MenuAction action, int client, int position)
|
||||
{
|
||||
if (action == MenuAction_End)
|
||||
{
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action != MenuAction_Select || !IsLeader(client))
|
||||
return 0;
|
||||
|
||||
char info[32];
|
||||
menu.GetItem(position, info, sizeof(info));
|
||||
|
||||
// Zombie leaders may still use the settings menu and clear their own markers;
|
||||
// the other main-menu items act in the world, so don't act and just let the menu close.
|
||||
if (IsLivingZombie(client) && !StrEqual(info, "settings") && !StrEqual(info, "clear"))
|
||||
{
|
||||
PrintToChat(client, "%sYou cannot use this as a zombie.", PREFIX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skin = SkinFromName(info);
|
||||
if (skin != -1)
|
||||
{
|
||||
Marker_Place(client, skin);
|
||||
ShowLeaderMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "clear"))
|
||||
{
|
||||
Marker_ClearByOwner(client);
|
||||
ShowLeaderMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "sprite"))
|
||||
{
|
||||
// Rotate Off -> Follow me -> Defend here -> Off.
|
||||
switch (g_iSpriteChoice[client])
|
||||
{
|
||||
case SPRITE_NONE: Sprite_Set(client, SPRITE_CHOICE_FOLLOW);
|
||||
case SPRITE_CHOICE_FOLLOW: Sprite_Set(client, SPRITE_CHOICE_DEFEND);
|
||||
default: Sprite_Set(client, SPRITE_NONE);
|
||||
}
|
||||
ShowLeaderMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "settings"))
|
||||
{
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Settings / Extra
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void ShowSettingsMenu(int client)
|
||||
{
|
||||
Menu menu = new Menu(MenuHandler_Settings);
|
||||
menu.SetTitle("Leader Settings");
|
||||
|
||||
char buf[64];
|
||||
Format(buf, sizeof(buf), "Beacon: %s", (g_iEffectFlags[client] & EFFECT_BEACON) ? "On" : "Off");
|
||||
menu.AddItem("beacon", buf);
|
||||
Format(buf, sizeof(buf), "Xray: %s", (g_iEffectFlags[client] & EFFECT_XRAY) ? "On" : "Off");
|
||||
menu.AddItem("xray", buf);
|
||||
switch (g_iTracerMode[client])
|
||||
{
|
||||
case TRACER_MODE_TAP: buf = "Tracer: Hold + Tap-toggle";
|
||||
case TRACER_MODE_HOLD: buf = "Tracer: Hold only";
|
||||
default: buf = "Tracer: Off";
|
||||
}
|
||||
menu.AddItem("tracer", buf);
|
||||
menu.AddItem("resign", "Resign from Leader");
|
||||
|
||||
if (CanManageLeaders(client))
|
||||
{
|
||||
menu.AddItem("manage", "Manage Leaders");
|
||||
menu.AddItem("clearall", "Clear ALL Markers");
|
||||
Format(buf, sizeof(buf), "Force-Mute: %s", g_bForceLeaderMute ? "On" : "Off");
|
||||
menu.AddItem("forcemute", buf);
|
||||
}
|
||||
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
public int MenuHandler_Settings(Menu menu, MenuAction action, int client, int position)
|
||||
{
|
||||
if (action == MenuAction_End)
|
||||
{
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action == MenuAction_Cancel && position == MenuCancel_ExitBack)
|
||||
{
|
||||
ShowLeaderMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action != MenuAction_Select || !IsLeader(client))
|
||||
return 0;
|
||||
|
||||
char info[32];
|
||||
menu.GetItem(position, info, sizeof(info));
|
||||
|
||||
if (StrEqual(info, "beacon"))
|
||||
{
|
||||
Beacon_Toggle(client);
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "xray"))
|
||||
{
|
||||
Xray_Toggle(client);
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "tracer"))
|
||||
{
|
||||
Tracer_CycleMode(client);
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "resign"))
|
||||
{
|
||||
RemoveLeaderClient(client, "resigned");
|
||||
}
|
||||
else if (StrEqual(info, "manage") && CanManageLeaders(client))
|
||||
{
|
||||
ShowManageMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "clearall") && CanManageLeaders(client))
|
||||
{
|
||||
Markers_ClearAll();
|
||||
PrintToChatAll("%sAll markers have been cleared by \x04%N\x01.", PREFIX, client);
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
else if (StrEqual(info, "forcemute") && CanManageLeaders(client))
|
||||
{
|
||||
// Enabling is reserved for whitelisted leaders; disabling is not.
|
||||
if (!g_bForceLeaderMute && !Whitelist_IsWhitelisted(client))
|
||||
{
|
||||
PrintToChat(client, "%sOnly players on the leader whitelist can enable force mute.", PREFIX);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_bForceLeaderMute = !g_bForceLeaderMute;
|
||||
PrintToChatAll("%s\x04%N\x01 %s force mute while a leader is speaking!", PREFIX, client,
|
||||
g_bForceLeaderMute ? "activated" : "deactivated");
|
||||
}
|
||||
ShowSettingsMenu(client);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Manage leaders (primary leader or admin)
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void ShowManageMenu(int client)
|
||||
{
|
||||
Menu menu = new Menu(MenuHandler_Manage);
|
||||
menu.SetTitle("Manage Leaders\n[P] primary, [L] sub-leader");
|
||||
|
||||
char info[12], display[MAX_NAME_LENGTH + 8];
|
||||
bool any = false;
|
||||
|
||||
// Active leaders first, in roster order (index 0 = primary).
|
||||
for (int i = 0; i < g_alLeaders.Length; i++)
|
||||
{
|
||||
int leader = GetClientOfUserId(g_alLeaders.Get(i));
|
||||
if (leader <= 0)
|
||||
continue;
|
||||
|
||||
any = true;
|
||||
IntToString(GetClientUserId(leader), info, sizeof(info));
|
||||
Format(display, sizeof(display), "[%s] %N", i == 0 ? "P" : "L", leader);
|
||||
menu.AddItem(info, display);
|
||||
}
|
||||
|
||||
// Then everyone else who could become one.
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_bIsLeader[i] || !IsClientInGame(i) || IsFakeClient(i))
|
||||
continue;
|
||||
|
||||
if (!IsLivingHuman(i))
|
||||
continue;
|
||||
|
||||
any = true;
|
||||
IntToString(GetClientUserId(i), info, sizeof(info));
|
||||
Format(display, sizeof(display), "%N", i);
|
||||
menu.AddItem(info, display);
|
||||
}
|
||||
|
||||
if (!any)
|
||||
menu.AddItem("", "No players available", ITEMDRAW_DISABLED);
|
||||
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
public int MenuHandler_Manage(Menu menu, MenuAction action, int client, int position)
|
||||
{
|
||||
if (action == MenuAction_End)
|
||||
{
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action == MenuAction_Cancel && position == MenuCancel_ExitBack)
|
||||
{
|
||||
ShowSettingsMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action != MenuAction_Select || !CanManageLeaders(client))
|
||||
return 0;
|
||||
|
||||
char info[12];
|
||||
menu.GetItem(position, info, sizeof(info));
|
||||
|
||||
int target = GetClientOfUserId(StringToInt(info));
|
||||
if (target == 0)
|
||||
{
|
||||
PrintToChat(client, "%sThat player is no longer available.", PREFIX);
|
||||
ShowManageMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ShowManageActionMenu(client, target);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ShowManageActionMenu(int client, int target)
|
||||
{
|
||||
Menu menu = new Menu(MenuHandler_ManageAction);
|
||||
menu.SetTitle("Manage: %N", target);
|
||||
|
||||
char info[16];
|
||||
IntToString(GetClientUserId(target), info, sizeof(info));
|
||||
|
||||
if (g_bIsLeader[target])
|
||||
{
|
||||
if (!IsPrimaryLeader(target))
|
||||
menu.AddItem(info, "Make Primary Leader");
|
||||
menu.AddItem(info, "Demote (remove leader)");
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddItem(info, "Promote to Sub-Leader");
|
||||
}
|
||||
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
menu.Display(client, MENU_TIME_FOREVER);
|
||||
}
|
||||
|
||||
public int MenuHandler_ManageAction(Menu menu, MenuAction action, int client, int position)
|
||||
{
|
||||
if (action == MenuAction_End)
|
||||
{
|
||||
delete menu;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action == MenuAction_Cancel && position == MenuCancel_ExitBack)
|
||||
{
|
||||
ShowManageMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (action != MenuAction_Select || !CanManageLeaders(client))
|
||||
return 0;
|
||||
|
||||
char info[16], display[64];
|
||||
int style;
|
||||
menu.GetItem(position, info, sizeof(info), style, display, sizeof(display));
|
||||
|
||||
int target = GetClientOfUserId(StringToInt(info));
|
||||
if (target == 0)
|
||||
{
|
||||
PrintToChat(client, "%sThat player is no longer available.", PREFIX);
|
||||
ShowManageMenu(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (StrContains(display, "Primary") != -1)
|
||||
{
|
||||
Core_MakePrimary(target, client);
|
||||
}
|
||||
else if (StrContains(display, "Demote") != -1)
|
||||
{
|
||||
char reason[80];
|
||||
Format(reason, sizeof(reason), "demoted by \x04%N\x01", client);
|
||||
RemoveLeaderClient(target, reason);
|
||||
}
|
||||
else if (StrContains(display, "Promote") != -1)
|
||||
{
|
||||
if (!IsLivingHuman(target))
|
||||
PrintToChat(client, "%sThat player has to be a living human.", PREFIX);
|
||||
else
|
||||
AddLeader(target, false, client);
|
||||
}
|
||||
|
||||
ShowManageMenu(client);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 vote: !voteleader / !vl <name>. Passes at sm_leader_vote_ratio of eligible voters.
|
||||
// Eligible = in-game, not a bot, not AFK (AFKManager, when loaded).
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
int g_iVoteCount[MAXPLAYERS + 1]; // votes received, per candidate
|
||||
int g_iVotedForSerial[MAXPLAYERS + 1]; // per voter: client serial of their candidate
|
||||
|
||||
public Action Cmd_VoteLeader(int client, int args)
|
||||
{
|
||||
if (client == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s Cannot use this from the console.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (!g_cvAllowVotes.BoolValue)
|
||||
{
|
||||
ReplyToCommand(client, "%s Voting for leader is disabled.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (args < 1)
|
||||
{
|
||||
ReplyToCommand(client, "%s Usage: sm_voteleader <player>", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char arg[65];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
int target = FindTarget(client, arg, true, false);
|
||||
if (target == -1)
|
||||
return Plugin_Handled;
|
||||
|
||||
if (g_bIsLeader[target])
|
||||
{
|
||||
ReplyToCommand(client, "%s %N is already a leader!", PREFIX_PLAIN, target);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (g_alLeaders.Length >= g_cvMaxLeaders.IntValue)
|
||||
{
|
||||
ReplyToCommand(client, "%s The maximum of %d leaders is already reached.", PREFIX_PLAIN, g_cvMaxLeaders.IntValue);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
int previous = GetClientFromSerial(g_iVotedForSerial[client]);
|
||||
if (previous == target)
|
||||
{
|
||||
ReplyToCommand(client, "%s You have already voted for this player!", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if (previous != 0)
|
||||
g_iVoteCount[previous]--;
|
||||
|
||||
g_iVoteCount[target]++;
|
||||
g_iVotedForSerial[client] = GetClientSerial(target);
|
||||
|
||||
PrintToChatAll("%s\x04%N\x01 voted for \x04%N\x01 to become a leader (\x04%d/%d\x01 votes).",
|
||||
PREFIX, client, target, g_iVoteCount[target], Vote_Needed());
|
||||
|
||||
Vote_Check(target);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
int Vote_EligibleCount()
|
||||
{
|
||||
int afkTime = g_cvVoteAfkTime.IntValue;
|
||||
int count = 0;
|
||||
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (!IsClientInGame(i) || IsFakeClient(i))
|
||||
continue;
|
||||
if (g_bAFKManager && afkTime > 0 && GetClientIdleTime(i) >= afkTime)
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int Vote_Needed()
|
||||
{
|
||||
int needed = RoundToCeil(float(Vote_EligibleCount()) * g_cvVoteRatio.FloatValue);
|
||||
return needed < 2 ? 2 : needed;
|
||||
}
|
||||
|
||||
void Vote_Check(int candidate)
|
||||
{
|
||||
if (!IsValidClient(candidate) || g_iVoteCount[candidate] <= 0)
|
||||
return;
|
||||
|
||||
if (g_iVoteCount[candidate] < Vote_Needed())
|
||||
return;
|
||||
|
||||
Vote_ClearCandidate(candidate);
|
||||
PrintToChatAll("%s\x04%N\x01 has been voted to become a leader!", PREFIX, candidate);
|
||||
if (AddLeader(candidate))
|
||||
ShowLeaderMenu(candidate);
|
||||
}
|
||||
|
||||
void Vote_ClearCandidate(int candidate)
|
||||
{
|
||||
g_iVoteCount[candidate] = 0;
|
||||
|
||||
int serial = GetClientSerial(candidate);
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_iVotedForSerial[i] == serial)
|
||||
g_iVotedForSerial[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// A shrinking voter pool can push a pending candidate over the threshold.
|
||||
void Vote_RecheckAll()
|
||||
{
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_iVoteCount[i] > 0)
|
||||
Vote_Check(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Vote_OnClientDisconnect(int client)
|
||||
{
|
||||
int previous = GetClientFromSerial(g_iVotedForSerial[client]);
|
||||
if (previous != 0)
|
||||
g_iVoteCount[previous]--;
|
||||
g_iVotedForSerial[client] = 0;
|
||||
|
||||
if (g_iVoteCount[client] > 0)
|
||||
Vote_ClearCandidate(client);
|
||||
|
||||
Vote_RecheckAll();
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Leader3 whitelist: players allowed to self-assign leader without a vote or admin flags.
|
||||
// Stored in the SourceMod database ("sourcemod" config when defined, else storage-local).
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
void Whitelist_OnPluginStart()
|
||||
{
|
||||
char conf[64];
|
||||
strcopy(conf, sizeof(conf), SQL_CheckConfig("sourcemod") ? "sourcemod" : "storage-local");
|
||||
Database.Connect(Whitelist_OnDBConnected, conf);
|
||||
}
|
||||
|
||||
public void Whitelist_OnDBConnected(Database db, const char[] error, any data)
|
||||
{
|
||||
if (db == null)
|
||||
{
|
||||
LogError("[Leader3] Whitelist database connection failed: %s", error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hWhitelistDB = db;
|
||||
g_hWhitelistDB.SetCharset("utf8");
|
||||
|
||||
g_hWhitelistDB.Query(Whitelist_OnTableCreated,
|
||||
"CREATE TABLE IF NOT EXISTS leader_whitelist ("
|
||||
... "steam_id VARCHAR(32) NOT NULL PRIMARY KEY, "
|
||||
... "name VARCHAR(64) NOT NULL DEFAULT '', "
|
||||
... "added_by VARCHAR(96) NOT NULL DEFAULT '', "
|
||||
... "added_at INTEGER NOT NULL DEFAULT 0)");
|
||||
}
|
||||
|
||||
public void Whitelist_OnTableCreated(Database db, DBResultSet results, const char[] error, any data)
|
||||
{
|
||||
if (results == null)
|
||||
{
|
||||
LogError("[Leader3] Failed to create leader_whitelist table: %s", error);
|
||||
delete g_hWhitelistDB;
|
||||
return;
|
||||
}
|
||||
|
||||
// Late load / slow connect: check everyone already on.
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (IsClientInGame(i) && IsClientAuthorized(i))
|
||||
Whitelist_CheckClient(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Whitelist_CheckClient(int client)
|
||||
{
|
||||
g_bLeaderWhitelisted[client] = false;
|
||||
|
||||
char auth[32];
|
||||
if (g_hWhitelistDB == null || IsFakeClient(client) || !GetClientAuthId(client, AuthId_Steam2, auth, sizeof(auth)))
|
||||
return;
|
||||
|
||||
char query[128];
|
||||
g_hWhitelistDB.Format(query, sizeof(query), "SELECT 1 FROM leader_whitelist WHERE steam_id = '%s'", auth);
|
||||
g_hWhitelistDB.Query(Whitelist_OnCheckDone, query, GetClientUserId(client));
|
||||
}
|
||||
|
||||
public void Whitelist_OnCheckDone(Database db, DBResultSet results, const char[] error, any userid)
|
||||
{
|
||||
if (results == null)
|
||||
{
|
||||
LogError("[Leader3] Whitelist lookup failed: %s", error);
|
||||
return;
|
||||
}
|
||||
|
||||
int client = GetClientOfUserId(userid);
|
||||
if (client > 0)
|
||||
g_bLeaderWhitelisted[client] = results.FetchRow();
|
||||
}
|
||||
|
||||
bool Whitelist_IsWhitelisted(int client)
|
||||
{
|
||||
return client >= 1 && client <= MaxClients && g_bLeaderWhitelisted[client];
|
||||
}
|
||||
|
||||
void Whitelist_Add(int admin, int target)
|
||||
{
|
||||
if (g_hWhitelistDB == null)
|
||||
{
|
||||
ReplyToCommand(admin, "%s The whitelist database is not available.", PREFIX_PLAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
char auth[32];
|
||||
if (!GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth)))
|
||||
{
|
||||
ReplyToCommand(admin, "%s %N is not authorized with Steam yet, try again shortly.", PREFIX_PLAIN, target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_bLeaderWhitelisted[target])
|
||||
{
|
||||
ReplyToCommand(admin, "%s %N is already on the leader whitelist.", PREFIX_PLAIN, target);
|
||||
return;
|
||||
}
|
||||
|
||||
char name[MAX_NAME_LENGTH], adminAuth[96];
|
||||
GetClientName(target, name, sizeof(name));
|
||||
if (admin > 0)
|
||||
GetClientAuthId(admin, AuthId_Steam2, adminAuth, sizeof(adminAuth));
|
||||
else
|
||||
strcopy(adminAuth, sizeof(adminAuth), "CONSOLE");
|
||||
|
||||
char query[512];
|
||||
g_hWhitelistDB.Format(query, sizeof(query),
|
||||
"REPLACE INTO leader_whitelist (steam_id, name, added_by, added_at) VALUES ('%s', '%s', '%s', %d)",
|
||||
auth, name, adminAuth, GetTime());
|
||||
g_hWhitelistDB.Query(Whitelist_OnWriteDone, query);
|
||||
|
||||
g_bLeaderWhitelisted[target] = true;
|
||||
|
||||
ReplyToCommand(admin, "%s Added %N (%s) to the leader whitelist.", PREFIX_PLAIN, target, auth);
|
||||
PrintToChat(target, "%sYou are now on the \x04leader whitelist\x01! Type \x04!leader\x01 to lead at any time.", PREFIX);
|
||||
LogAction(admin, target, "\"%L\" added \"%L\" to the leader whitelist", admin, target);
|
||||
}
|
||||
|
||||
// Normalize a typed SteamID to canonical upper-case Steam2 form (STEAM_X:Y:Z).
|
||||
// The steam_id column comparison is case-sensitive, so lower-case input must not reach the DB.
|
||||
static bool Whitelist_NormalizeSteam2(const char[] input, char[] auth, int maxlen)
|
||||
{
|
||||
strcopy(auth, maxlen, input);
|
||||
for (int i = 0; auth[i] != '\0'; i++)
|
||||
auth[i] = CharToUpper(auth[i]);
|
||||
|
||||
if (StrContains(auth, "STEAM_", true) != 0)
|
||||
return false;
|
||||
if (!IsCharNumeric(auth[6]) || auth[7] != ':' || (auth[8] != '0' && auth[8] != '1') || auth[9] != ':' || !IsCharNumeric(auth[10]))
|
||||
return false;
|
||||
|
||||
for (int i = 11; auth[i] != '\0'; i++)
|
||||
{
|
||||
if (!IsCharNumeric(auth[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add a possibly offline player by SteamID (sm_addleaderwhitelist STEAM_0:X:XXXX [name]).
|
||||
void Whitelist_AddAuth(int admin, const char[] authArg, const char[] name)
|
||||
{
|
||||
if (g_hWhitelistDB == null)
|
||||
{
|
||||
ReplyToCommand(admin, "%s The whitelist database is not available.", PREFIX_PLAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
char auth[32];
|
||||
if (!Whitelist_NormalizeSteam2(authArg, auth, sizeof(auth)))
|
||||
{
|
||||
ReplyToCommand(admin, "%s '%s' is not a valid SteamID (expected STEAM_0:X:XXXX).", PREFIX_PLAIN, authArg);
|
||||
return;
|
||||
}
|
||||
|
||||
char adminAuth[96];
|
||||
if (admin > 0)
|
||||
GetClientAuthId(admin, AuthId_Steam2, adminAuth, sizeof(adminAuth));
|
||||
else
|
||||
strcopy(adminAuth, sizeof(adminAuth), "CONSOLE");
|
||||
|
||||
char query[512];
|
||||
g_hWhitelistDB.Format(query, sizeof(query),
|
||||
"REPLACE INTO leader_whitelist (steam_id, name, added_by, added_at) VALUES ('%s', '%s', '%s', %d)",
|
||||
auth, name, adminAuth, GetTime());
|
||||
g_hWhitelistDB.Query(Whitelist_OnWriteDone, query);
|
||||
|
||||
// Update the cache should that player be connected under this ID.
|
||||
char clientAuth[32];
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (!g_bLeaderWhitelisted[i] && IsClientInGame(i) && !IsFakeClient(i)
|
||||
&& GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth))
|
||||
&& StrEqual(auth, clientAuth))
|
||||
{
|
||||
g_bLeaderWhitelisted[i] = true;
|
||||
PrintToChat(i, "%sYou are now on the \x04leader whitelist\x01! Type \x04!leader\x01 to lead at any time.", PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
ReplyToCommand(admin, "%s Added %s to the leader whitelist.", PREFIX_PLAIN, auth);
|
||||
LogAction(admin, -1, "\"%L\" added \"%s\" to the leader whitelist", admin, auth);
|
||||
}
|
||||
|
||||
// Remove a connected client from the whitelist. Returns true if they were on it.
|
||||
bool Whitelist_Remove(int admin, int target)
|
||||
{
|
||||
if (!g_bLeaderWhitelisted[target])
|
||||
return false;
|
||||
|
||||
g_bLeaderWhitelisted[target] = false;
|
||||
|
||||
char auth[32];
|
||||
if (GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth)))
|
||||
Whitelist_DeleteAuth(auth);
|
||||
|
||||
ReplyToCommand(admin, "%s Removed %N from the leader whitelist.", PREFIX_PLAIN, target);
|
||||
LogAction(admin, target, "\"%L\" removed \"%L\" from the leader whitelist", admin, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove an offline player by SteamID (sm_removeleaderwhitelist STEAM_0:X:XXXX).
|
||||
void Whitelist_RemoveAuth(int admin, const char[] authArg)
|
||||
{
|
||||
if (g_hWhitelistDB == null)
|
||||
{
|
||||
ReplyToCommand(admin, "%s The whitelist database is not available.", PREFIX_PLAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
char auth[32];
|
||||
if (!Whitelist_NormalizeSteam2(authArg, auth, sizeof(auth)))
|
||||
{
|
||||
ReplyToCommand(admin, "%s '%s' is not a valid SteamID (expected STEAM_0:X:XXXX).", PREFIX_PLAIN, authArg);
|
||||
return;
|
||||
}
|
||||
|
||||
Whitelist_DeleteAuth(auth);
|
||||
|
||||
// Clear the cache should that player be connected under this ID.
|
||||
char clientAuth[32];
|
||||
for (int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if (g_bLeaderWhitelisted[i] && IsClientInGame(i)
|
||||
&& GetClientAuthId(i, AuthId_Steam2, clientAuth, sizeof(clientAuth))
|
||||
&& StrEqual(auth, clientAuth))
|
||||
{
|
||||
g_bLeaderWhitelisted[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
ReplyToCommand(admin, "%s Removed %s from the leader whitelist (if present).", PREFIX_PLAIN, auth);
|
||||
LogAction(admin, -1, "\"%L\" removed \"%s\" from the leader whitelist", admin, auth);
|
||||
}
|
||||
|
||||
void Whitelist_DeleteAuth(const char[] auth)
|
||||
{
|
||||
if (g_hWhitelistDB == null)
|
||||
return;
|
||||
|
||||
char query[128];
|
||||
g_hWhitelistDB.Format(query, sizeof(query), "DELETE FROM leader_whitelist WHERE steam_id = '%s'", auth);
|
||||
g_hWhitelistDB.Query(Whitelist_OnWriteDone, query);
|
||||
}
|
||||
|
||||
public void Whitelist_OnWriteDone(Database db, DBResultSet results, const char[] error, any data)
|
||||
{
|
||||
if (results == null)
|
||||
LogError("[Leader3] Whitelist write failed: %s", error);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Commands
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
public Action Cmd_AddLeaderWhitelist(int client, int args)
|
||||
{
|
||||
if (args < 1)
|
||||
{
|
||||
ReplyToCommand(client, "%s Usage: sm_addleaderwhitelist <target|steamid> [name] - adds a player to the leader whitelist.", 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_CONNECTED | COMMAND_FILTER_NO_BOTS, targetName, sizeof(targetName), tnIsMl);
|
||||
if (count <= 0)
|
||||
{
|
||||
// Offline whitelist add by SteamID.
|
||||
if (StrContains(arg, "STEAM_", false) == 0)
|
||||
{
|
||||
char name[MAX_NAME_LENGTH];
|
||||
if (args >= 2)
|
||||
GetCmdArg(2, name, sizeof(name));
|
||||
Whitelist_AddAuth(client, arg, name);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
ReplyToTargetError(client, count);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
Whitelist_Add(client, targets[i]);
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Cmd_RemoveLeaderWhitelist(int client, int args)
|
||||
{
|
||||
if (args < 1)
|
||||
{
|
||||
ReplyToCommand(client, "%s Usage: sm_removeleaderwhitelist <target|steamid> - removes a player from the leader whitelist.", 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_CONNECTED | COMMAND_FILTER_NO_BOTS, targetName, sizeof(targetName), tnIsMl);
|
||||
if (count <= 0)
|
||||
{
|
||||
// Offline whitelist removal by SteamID.
|
||||
if (StrContains(arg, "STEAM_", false) == 0)
|
||||
{
|
||||
Whitelist_RemoveAuth(client, arg);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
ReplyToTargetError(client, count);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!Whitelist_Remove(client, targets[i]))
|
||||
ReplyToCommand(client, "%s %N is not on the leader whitelist.", PREFIX_PLAIN, targets[i]);
|
||||
}
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public Action Cmd_LeaderWhitelist(int client, int args)
|
||||
{
|
||||
if (g_hWhitelistDB == null)
|
||||
{
|
||||
ReplyToCommand(client, "%s The whitelist database is not available.", PREFIX_PLAIN);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
g_hWhitelistDB.Query(Whitelist_OnListDone,
|
||||
"SELECT steam_id, name FROM leader_whitelist ORDER BY added_at DESC",
|
||||
client > 0 ? GetClientUserId(client) : 0);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
public void Whitelist_OnListDone(Database db, DBResultSet results, const char[] error, any userid)
|
||||
{
|
||||
int client = (userid == 0) ? 0 : GetClientOfUserId(userid);
|
||||
if (userid != 0 && client == 0)
|
||||
return;
|
||||
|
||||
if (results == null)
|
||||
{
|
||||
LogError("[Leader3] Whitelist listing failed: %s", error);
|
||||
ReplyToCommand(client, "%s Failed to read the leader whitelist.", PREFIX_PLAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.RowCount == 0)
|
||||
{
|
||||
ReplyToCommand(client, "%s The leader whitelist is empty.", PREFIX_PLAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
ReplyToCommand(client, "%s Leader whitelist (%d):", PREFIX_PLAIN, results.RowCount);
|
||||
|
||||
char auth[32], name[MAX_NAME_LENGTH];
|
||||
while (results.FetchRow())
|
||||
{
|
||||
results.FetchString(0, auth, sizeof(auth));
|
||||
results.FetchString(1, name, sizeof(name));
|
||||
ReplyToCommand(client, " %s - %s", auth, name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user