sourcemod-plugins/scripting/include/feedthetrolls/base.inc
2023-10-09 20:58:44 -05:00

499 lines
No EOL
17 KiB
SourcePawn

#define MAX_TROLL_NAME_LENGTH 32
#define MAX_TROLL_FLAG_LENGTH 32
//Allow MAX_TROLLS to be defined elsewhere
#if defined MAX_TROLLS
#else
#define MAX_TROLLS 56
#endif
enum trollModifier {
TrollMod_Invalid = 0,
TrollMod_Instant = 1 << 0,
TrollMod_Constant = 1 << 1,
TrollMod_PlayerOnly = 1 << 2, // Does the troll only work on players, not bots? If set, troll only applied on real user. If not, troll applied to both bot and idler
}
StringMap trollKV;
char trollIds[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH];
char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)";
char DEFAULT_FLAG_PROMPT[] = "Select an option";
bool SilentMenuSelected[MAXPLAYERS+1];
static int g_trollAddPromptIndex;
ArrayList gRandomClients;
char SPECIAL_NAMES[][] = {
"Smoker", "Boomer", "Hunter", "Spitter", "Jockey", "Charger", "Witch", "Tank"
};
enum struct TrollFlagPrompt {
char promptText[MAX_TROLL_FLAG_LENGTH];
int flags;
int defaults;
bool multiselect;
int requireFlags;
void GetPromptText(char[] prompt, int maxlength) {
if(this.promptText[0] != '\0') {
strcopy(prompt, maxlength, this.promptText);
} else if(this.multiselect) {
strcopy(prompt, maxlength, DEFAULT_FLAG_PROMPT_MULTIPLE);
} else {
strcopy(prompt, maxlength, DEFAULT_FLAG_PROMPT);
}
}
}
// Very hacky but map a power (say 16, 32) to the index in a list
// Used to get the name of a flag (flag #16 for example) in ArrayList<string> of flag names
int GetIndexFromPower(int powerOfTwo) {
for(int i = 0; i < 16; i++) {
if(1 << i == powerOfTwo) {
return i;
}
}
return -1;
}
enum struct Troll {
int id; // The id or the index into the global Trolls[] array
int categoryID; // The category this troll belongs in
char name[MAX_TROLL_NAME_LENGTH];
char description[128];
bool hidden;
int mods; // Combination of valid modifiers. Only two are ever supported
// Flags
int activeFlagClients[MAXPLAYERS+1];
ArrayList flagNames;
ArrayList flagPrompts;
// Custom timer
Timer timerFunction;
Handle timerHandles[MAXPLAYERS+1];
float timerInterval;
int timerRequiredFlags;
void SetTimer(float interval, Timer timer, int requiredFlags = 0) {
this.timerInterval = interval;
this.timerFunction = timer;
this.timerRequiredFlags = requiredFlags;
for(int i = 0; i <= MAXPLAYERS; i++) {
this.timerHandles[i] = null;
}
}
bool HasMod(trollModifier mod) {
return ((this.mods >> (view_as<int>(mod)) - 1) & 1) == 1;
}
// Gets the default modifier to use
trollModifier GetDefaultMod() {
// If the flags is equal to the 2^n flag, then it must be the only flag:
if(this.mods == view_as<int>(TrollMod_Instant)) return TrollMod_Instant;
else if(this.mods == view_as<int>(TrollMod_Constant)) return TrollMod_Constant;
else return TrollMod_Invalid;
}
/////// FLAGS
bool GetFlagName(int index, char[] buffer, int maxlength) {
if(this.flagNames == null) return false;
this.flagNames.GetString(index, buffer, maxlength);
return true;
}
int AddCustomFlagPrompt(const char[] promptText, bool multiselect = false, int requireFlags = 0) {
TrollFlagPrompt prompt;
prompt.multiselect = multiselect;
prompt.requireFlags = requireFlags;
strcopy(prompt.promptText, MAX_TROLL_FLAG_LENGTH, promptText);
int index = this.flagPrompts.PushArray(prompt);
g_trollAddPromptIndex = index;
return index;
}
int AddFlagPrompt(bool multiselect = false, int requireFlags = 0) {
//g_trollAddPromptIndex
TrollFlagPrompt prompt;
prompt.multiselect = multiselect;
prompt.requireFlags = requireFlags;
int index = this.flagPrompts.PushArray(prompt);
g_trollAddPromptIndex = index;
return index;
}
int AddFlag(const char[] name, bool defaultOn) {
if(this.flagNames == null) {
this.flagNames = new ArrayList(MAX_TROLL_FLAG_LENGTH);
}
// Check if flag already added
int flagIndex = this.GetFlagIndex(name);
if(flagIndex == -1) flagIndex = this.flagNames.PushString(name);
// Grab the prompt
static TrollFlagPrompt prompt;
// TODO: CHECK IF MISSING PROMPT
if(g_trollAddPromptIndex >= this.flagPrompts.Length) {
ThrowError("No prompt added for troll \"%s\", for flag \"%s\"", this.id, name);
}
this.flagPrompts.GetArray(g_trollAddPromptIndex, prompt);
prompt.flags |= (1 << flagIndex);
if(defaultOn) {
// If out of bounds, set to default -1 -> pick global prompt
if(this.flagPrompts.Length == 0) {
ThrowError("Troll \"%s\" does not have any flag prompts, thus a default value cannot be set. (flag=\"%s\")", this.name, name);
return -1;
}
if(!prompt.multiselect && prompt.defaults > 0) {
ThrowError("Flag \"%s\" cannot be set as default flag in single select mode, as one has already been set for prompt %d", name, g_trollAddPromptIndex);
return -1;
}
prompt.defaults |= (1 << flagIndex);
}
this.flagPrompts.SetArray(g_trollAddPromptIndex, prompt); //May not be required
return flagIndex;
}
int GetFlagIndex(const char[] flagName) {
static char comprFlag[MAX_TROLL_FLAG_LENGTH];
for(int i = 0; i < this.flagNames.Length; i++) {
this.flagNames.GetString(i, comprFlag, sizeof(comprFlag));
if(StrEqual(comprFlag, flagName)) {
return i;
}
}
return -1;
}
bool HasFlags() {
return this.flagNames != null && this.flagNames.Length > 0 && this.flagPrompts.Length > 0;
}
int GetFlagCount() {
return this.flagNames != null ? this.flagNames.Length : 0;
}
int GetClientFlags(int client) {
return this.activeFlagClients[client];
}
void GetFlagPrompt(int index, TrollFlagPrompt prompt) {
this.flagPrompts.GetArray(index, prompt);
}
/////// TROLL ACTIVATION
void Activate(int client, int activator, trollModifier modifier = TrollMod_Invalid, int flags = 0, bool silent = false) {
if(modifier == TrollMod_Invalid) modifier = this.GetDefaultMod();
// Sadly, unable to pass in <this> to ApplyTroll, so it has to do unnecessary lookup via string
ApplyTroll(client, this.name, activator, modifier, flags, silent);
}
void Toggle(int client, int flags) {
if(this.IsActive(client)) {
this.Disable(client);
} else {
this.Enable(client, flags);
}
}
void Enable(int client, int flags) {
this.activeFlagClients[client] = flags;
// If a timer is assigned, start it:
if(this.timerHandles[client] != null) {
delete this.timerHandles[client];
PrintToServer("FTT Debug: Old timer for %N, killing", client);
}
if(this.timerInterval > 0.0) {
this.timerHandles[client] = CreateTimer(this.timerInterval, this.timerFunction, GetClientUserId(client), TIMER_REPEAT);
}
}
void Disable(int client) {
this.activeFlagClients[client] = -1;
// Stop any running timer:
if(this.timerHandles[client] != null) {
PrintToServer("FTT Debug: Disabling timer for %N", client);
delete this.timerHandles[client];
}
}
bool IsActive(int client) {
return this.activeFlagClients[client] != -1;
}
int GetRandomClient(int start = 0) {
gRandomClients.Clear();
for(int i = start + 1; i <= MaxClients; i++) {
if(this.activeFlagClients[i] != -1) {
gRandomClients.Push(i);
}
}
if(gRandomClients.Length == 0) return -1;
return GetRandomInt(0, gRandomClients.Length);
}
}
Troll Trolls[MAX_TROLLS+1];
ArrayList categories;
static int categoryID = -1;
void ResetClient(int victim, bool wipe = true) {
if(victim == 0 || !IsClientConnected(victim)) return;
if(wipe) {
for(int i = 0; i <= MAX_TROLLS; i++) {
Trolls[i].Disable(victim);
}
}
noRushingUsSpeed[victim] = 1.0;
BaseComm_SetClientMute(victim, false);
SetEntityGravity(victim, 1.0);
SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0);
SetEntProp(victim, Prop_Send, "m_iHideHUD", 0)
SDKUnhook(victim, SDKHook_WeaponCanUse, Event_ItemPickup);
int wpn = GetClientWeaponEntIndex(victim, 0);
if(wpn > -1)
SDKUnhook(wpn, SDKHook_Reload, Event_WeaponReload);
}
int SetupTroll(const char[] name, const char description[128], int mods) {
static int i = 0;
if(mods == 0) {
ThrowError("Troll \"%s\" has no modifiers defined.", name);
return -1;
} else if(i == MAX_TROLLS + 1) {
ThrowError("Maximum number of trolls (%d) reached. Up MAX_TROLLS value.", MAX_TROLLS);
return -1;
}
g_trollAddPromptIndex = 0;
Trolls[i].id = i;
strcopy(Trolls[i].name, MAX_TROLL_NAME_LENGTH, name);
strcopy(Trolls[i].description, 128, description);
Trolls[i].categoryID = categoryID;
Trolls[i].mods = mods;
Trolls[i].flagPrompts = new ArrayList(sizeof(TrollFlagPrompt));
strcopy(trollIds[i], MAX_TROLL_NAME_LENGTH, name);
trollKV.SetValue(name, i);
return i++;
}
// Gets the Troll enum struct via name
// Returns index of troll enum
int GetTroll(const char[] name, Troll troll) {
static int i = 0;
if(trollKV.GetValue(name, i)) {
troll = Trolls[i];
return i;
}
ThrowError("GetTroll: Troll was not found \"%s\"", name);
return -1;
}
int GetTrollID(const char[] name) {
static int i = 0;
if(trollKV.GetValue(name, i)) {
return i;
}
PrintToServer("GetTrollID: Troll was not found \"%s\"", name);
return -1;
}
bool IsAnyTrollActive(int victim) {
for(int i = 0; i <= MAX_TROLLS; i++) {
if(Trolls[i].activeFlagClients[victim] >= 0) return true;
}
return false;
}
// Gets the Troll enum struct via key index
// Returns index of troll enum
void GetTrollByKeyIndex(int index, Troll troll) {
troll = Trolls[index];
}
void SetTrollFlags(int client, const char[] name, int flags = -1) {
int index = GetTrollID(name);
if(flags == -1)
Trolls[index].Disable(client);
else
Trolls[index].Enable(client, flags);
}
void ApplyTroll(int victim, const char[] name, int activator, trollModifier modifier, int flags = 0, bool silent = false) {
static Troll troll;
int trollIndex = GetTroll(name, troll);
if(trollIndex == -1) {
ReplyToCommand(activator, "Unknown troll \"%s\"", name);
PrintToServer("[FTT] %N attempted to apply unknown troll: %s", activator, name);
return;
}
bool isActive = Trolls[trollIndex].activeFlagClients[victim] > -1;
// Clear troll specific timer:
if(Trolls[trollIndex].timerInterval > 0.0) {
if(!isActive) {
if(modifier & TrollMod_Constant && (Trolls[trollIndex].timerRequiredFlags == 0 || Trolls[trollIndex].timerRequiredFlags & flags)) {
Trolls[trollIndex].timerHandles[victim] = CreateTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, victim, TIMER_REPEAT);
}
} else if(Trolls[trollIndex].timerHandles[victim] != null) {
delete Trolls[trollIndex].timerHandles[victim];
}
}
if(!silent && SilentMenuSelected[activator]) silent = true;
static int MetaInverseTrollID;
if(!MetaInverseTrollID) MetaInverseTrollID = GetTrollID("Meta: Inverse");
if(activator > 0 && Trolls[MetaInverseTrollID].IsActive(activator)) {
float max = 1.0;
if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 2) max = 0.5;
else if(Trolls[MetaInverseTrollID].activeFlagClients[activator] & 4) max = 0.1;
if(GetURandomFloat() <= max) {
victim = activator;
}
}
// If victim is a survivor bot, check if has an idle player
if(IsFakeClient(victim) && GetClientTeam(victim) == 2) {
int player = GetSpectatorClient(victim);
if(player > 0) {
// If there is an idle player, apply troll to them
ApplyTroll(player, name, activator, modifier, flags, silent);
// And continue IF there is TrollMod_PlayerOnly mod
if(troll.mods & view_as<int>(TrollMod_PlayerOnly)) return;
// Don't want to show two logs, so just ignore the bot
silent = true;
}
}
// Toggle on flags for client, if it's not a single run.
if(modifier & TrollMod_Constant) {
Trolls[trollIndex].activeFlagClients[victim] = isActive ? -1 : flags;
}
// Applies any custom logic needed for a troll, mostly only used for TrollMod_Instant
if(!ApplyAffect(victim, troll, activator, modifier, flags)) {
return;
}
// Log all actions, indicating if constant or single-fire, and if any flags
if(!silent) {
if(isActive) {
CShowActivityEx(activator, "[FTT] ", "deactivated {yellow}%s{default} on %N. ", troll.name, victim);
LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, troll.name, victim);
} else {
char flagName[MAX_TROLL_FLAG_LENGTH];
// strcopy(flagName, sizeof(flagName), troll.name)
// Call_StartForward(g_TrollAppliedForward);
// Call_PushCell(victim);
// Call_PushString(flagName);
// Call_PushCell(flags);
// Call_PushCell(activator);
// Call_Finish();
for(int i = 0; i < 32; i++) {
if(flags & (1 << i)) {
// If at least one flag already, reset to none:
if(flagName[0] != '\0') {
flagName[0] = '\0';
break;
}
troll.GetFlagName(i, flagName, sizeof(flagName));
}
}
if(flags > 0) {
// Checks if there is not more than one flag set on the bitfield
if(flags & flags - 1 == 0 && flags & flags + 1 == 0) {
// Get the flag name if there is only one flag set
troll.GetFlagName(GetIndexFromPower(flags), flagName, sizeof(flagName));
} else {
Format(flagName, sizeof(flagName), "%d", flags);
}
}
if(modifier & TrollMod_Constant) {
if(flags > 0) {
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim);
} else
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default} for %N. ", troll.name, victim);
} else if(flags > 0) {
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} ({yellow}%s{default}) for %N. ", troll.name, flagName, victim);
} else {
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default} for %N. ", troll.name, victim);
}
LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, troll.name, flags, victim);
}
} else {
CReplyToCommand(activator, "[FTT] Applied silently {yellow}\"%s\"{default} on %N with flags=%d", troll.name, victim, flags);
}
}
bool IsTrollActive(int client, const char[] troll) {
if(troll[0] == '\0') {
ThrowError("Troll name is empty");
return false;
}
static int i = 0;
if(trollKV.GetValue(troll, i)) {
return Trolls[i].activeFlagClients[client] != -1;
}
ThrowError("Troll \"%s\" does not exist", troll);
return false; //errors instead but compiler no like
}
bool IsTrollActiveByRawID(int client, int id) {
return Trolls[id].activeFlagClients[client] != -1;
}
void EnableTroll(int client, const char[] troll, int flags = 0) {
SetTrollFlags(client, troll, flags);
}
void DisableTroll(int client, const char[] troll) {
SetTrollFlags(client, troll, -1);
}
public void SetCategory(const char[] newCat) {
categoryID = categories.FindString(newCat);
if(categoryID == -1)
categoryID = categories.PushString(newCat);
}
void GetCategory(int category, char[] buffer, int size) {
categories.GetString(category, buffer, size);
}
public int Native_ApplyTroll(Handle plugin, int numParams) {
int victim = GetNativeCell(1);
char name[MAX_TROLL_NAME_LENGTH];
GetNativeString(2, name, sizeof(name));
trollModifier modifier = view_as<trollModifier>(GetNativeCell(3));
if(view_as<int>(modifier) < 0) {
ThrowNativeError(SP_ERROR_NATIVE, "Provided modifier is invalid (out of range)");
}
int flags = GetNativeCell(4);
int activator = GetNativeCell(5);
int index = GetTrollID(name);
if(index > 0) {
Trolls[index].Activate(victim, activator, modifier, flags, GetNativeCell(6));
} else {
ThrowNativeError(SP_ERROR_NATIVE, "Could not find troll with name \"%s\"", name);
}
return 0;
}