sourcemod-plugins/scripting/include/feedthetrolls/base.inc
2024-03-06 17:59:55 -06:00

667 lines
No EOL
25 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 55
#endif
Troll t_metaReverse;
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
}
enum TrollEffectResponse {
TE_Success, // Success, continue menu
TE_Error, // Error, continue menu (retry)
TE_Menu // Switching menus / etc, don't continue menu
}
typeset PromptActivateFunction {
function TrollEffectResponse (Troll troll, int activator, int victim, any data, int flags, trollModifier mod)
function void (Troll troll, int activator, int victim, any data, int flags, trollModifier mod)
}
typedef ActivateFunction = function void (Troll troll, int activator, int victim, int flags, trollModifier mod);
typedef ResetFunction = function void (Troll troll, int activator, int victim);
// typedef PromptActivateFunction = function TrollEffectResponse (Troll troll, int activator, int victim, any data, int flags, trollModifier mod);
StringMap trollKV;
char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)";
char DEFAULT_FLAG_PROMPT[] = "Select an option";
bool SilentMenuSelected[MAXPLAYERS+1];
static int g_trollAddPromptIndex;
char SPECIAL_NAMES[][] = {
"Smoker", "Boomer", "Hunter", "Spitter", "Jockey", "Charger", "Witch", "Tank"
};
enum struct TrollFlagPrompt {
char promptText[MAX_TROLL_FLAG_LENGTH];
// enabled flags
int flags;
// default values
int defaults;
// is multiple flags selectable?
bool multiselect;
// flags that need to be active to show this prompt
int requireFlags;
PrivateForward activateFn;
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);
}
}
}
enum struct TrollOptionData {
char name[MAX_TROLL_FLAG_LENGTH];
int data; // can also be float
}
enum struct TrollData {
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;
PrivateForward activateFn;
PrivateForward resetFn;
int mods; // Combination of valid modifiers. Only two are ever supported
// Flags
int activeFlagClients[MAXPLAYERS+1];
ArrayList flagNames;
ArrayList promptOptions;
ArrayList flagPrompts;
// Custom timer
Timer timerFunction;
Handle timerHandles[MAXPLAYERS+1];
float timerInterval;
int timerRequiredFlags;
bool timerIsDataPack;
// TODO: REMOVE OLD
bool IsActive(int client) {
if(this.id == 0 || client == 0) return false; // bug fix
return this.activeFlagClients[client] >= 0;
}
}
TrollData 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 = 1; i <= MAX_TROLLS; i++) {
Troll(i).Reset(victim);
}
}
// TODO: move to reset functions!!
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);
}
// TrollInstance of TrollData
methodmap Troll {
public Troll(int index) {
return view_as<Troll>(index);
}
public static Troll FromName(const char[] name) {
int i = GetTrollID(name);
if(i == -1)
LogError("Unknown troll \"%s\"", name);
return view_as<Troll>(i);
}
public static bool TryFromName(const char[] name, Troll &troll) {
int i = GetTrollID(name);
if(i > -1)
troll = Troll(i);
return i > -1;
}
property bool Hidden {
public get() { return Trolls[this.Id].hidden; }
}
property int CategoryId {
public get() { return Trolls[this.Id].categoryID; }
}
property int PromptCount {
public get() { return Trolls[this.Id].flagPrompts.Length; }
}
property int TotalOptionsCount {
public get() {
return Trolls[this.Id].promptOptions == null ? -1 : Trolls[this.Id].promptOptions.Length;
}
}
property bool HasTimer {
public get() { return Trolls[this.Id].timerInterval > 0.0; }
}
property int Id {
public get() { return view_as<int>(this); }
}
property bool HasOptions {
public get() { return this.TotalOptionsCount > 0; }
}
/// Is troll active for client. If flags is > 0, will do bitwise and
public bool IsActive(int client, int flags = 0) {
if(this.Id == 0 || client == 0) return false; // bug fix
if(flags > 0) {
return (Trolls[this.Id].activeFlagClients[client] & flags) == flags;
} else
return Trolls[this.Id].activeFlagClients[client] >= 0;
}
public bool HasFlag(int client, int flag) {
return Trolls[this.Id].activeFlagClients[client] & flag != 0;
}
public int GetFlags(int client) {
return Trolls[this.Id].activeFlagClients[client];
}
public bool HasMod(trollModifier mod) {
return Trolls[this.Id].mods & view_as<int>(mod) != 0;
}
public void GetName(char[] output, int maxlen) {
strcopy(output, maxlen, Trolls[this.Id].name);
}
public TrollEffectResponse Activate(int activator, int victim, trollModifier modifier = TrollMod_Invalid, int flags = 0, bool silent = false) {
if(modifier == TrollMod_Invalid) modifier = this.GetDefaultMod();
if(victim == 0) ThrowError("Victim is invalid");
return ApplyTroll(victim, this, activator, modifier, flags, silent);
}
public void Reset(int victim) {
Trolls[this.Id].activeFlagClients[victim] = -1;
// Stop any running timer:
if(Trolls[this.Id].timerHandles[victim] != null) {
PrintToServer("FTT Debug: Disabling timer for %N", victim);
delete Trolls[this.Id].timerHandles[victim];
}
if(Trolls[this.Id].resetFn != null) {
Call_StartForward(Trolls[this.Id].resetFn);
Call_PushCell(Troll(this.Id));
Call_PushCell(0);
Call_PushCell(victim);
Call_Finish();
}
}
public bool GetOptionData(int optionIndex, TrollOptionData data) {
if(optionIndex < 0 || optionIndex >= Trolls[this.Id].promptOptions.Length) return false;
Trolls[this.Id].promptOptions.GetArray(optionIndex, data);
return true;
}
/// If prompt is NOT multiselect, returns the selected value from the option's data property
public bool GetPromptDataInt(int client, int promptIndex, int &out) {
if(promptIndex < 0 || promptIndex >= Trolls[this.Id].flagPrompts.Length) {
ThrowError(".GetPromptData called with invalid prompt index (%d, max %d) on troll #%d", promptIndex, Trolls[this.Id].flagPrompts.Length, this.Id);
}
TrollFlagPrompt prompt;
Trolls[this.Id].flagPrompts.GetArray(promptIndex, prompt);
if(prompt.multiselect) {
ThrowError(".GetPromptData: attempted to receive data for a multiselect prompt. Operation unspported. promptIndex:%d troll:%d", promptIndex, this.Id);
}
TrollOptionData option;
int flags = this.GetFlags(client);
for(int i = 0; i < Trolls[this.Id].promptOptions.Length; i++) {
int bit = 1 << i;
// If prompt has flag AND flag is active:
if(prompt.flags & bit && flags & bit) {
Trolls[this.Id].promptOptions.GetArray(i, option);
out = option.data;
return true;
}
}
return false;
}
public bool GetPromptDataFloat(int client, int promptIndex, float &out) {
int value;
if(this.GetPromptDataInt(client, promptIndex, value)) {
// We just retagged it as int, but it's float data
out = view_as<float>(value);
return true;
}
return false;
}
public bool GetPrompt(int promptIndex, TrollFlagPrompt prompt) {
if(promptIndex < 0 || promptIndex >= Trolls[this.Id].flagPrompts.Length) return false;
Trolls[this.Id].flagPrompts.GetArray(promptIndex, prompt);
return true;
}
public void GetOptionName(int optionIndex, char[] output, int maxlen) {
TrollOptionData option;
this.GetOptionData(optionIndex, option);
strcopy(output, maxlen, option.name);
}
public bool GetFlagNames(int client, int flags = -1, char[] output, int maxlength) {
if(this.TotalOptionsCount == 0) return false;
char buffer[32];
if(flags == -1) flags = Trolls[this.Id].activeFlagClients[client];
int count;
for(int i = 0; i < this.TotalOptionsCount; i++) {
int bit = 1 << i;
// If client has this flag:
if(flags & bit) {
this.GetOptionName(i, buffer, sizeof(buffer));
if(count == 0)
Format(output, maxlength, "%s", buffer);
else
Format(output, maxlength, "%s,%s", output, buffer);
count++;
}
}
return true;
}
/// Gets the default modifier to use
public trollModifier GetDefaultMod() {
// If the flags is equal to the 2^n flag, then it must be the only flag:
if(Trolls[this.Id].mods == view_as<int>(TrollMod_Instant)) return TrollMod_Instant;
else if(Trolls[this.Id].mods == view_as<int>(TrollMod_Constant)) return TrollMod_Constant;
else return TrollMod_Invalid;
}
public TrollEffectResponse _triggerActivateFn(int activator, int victim, int flags, trollModifier modifier) {
if(Trolls[this.Id].activateFn == null) return;
Call_StartForward(Trolls[this.Id].activateFn);
Call_PushCell(this);
Call_PushCell(activator);
Call_PushCell(victim);
Call_PushCell(flags);
Call_PushCell(modifier);
Call_Finish();
// TrollFlagPrompt prompt;
// for(int i = 0; i < Trolls[trollIndex].flagPrompts.Length; i++) {
// Trolls[trollIndex].flagPrompts.GetArray(i, prompt);
// if(!prompt.multiselect && prompt.activateFn != null) {
// int value;
// instance.GetPromptDataInt(victim, i, value);
// for(int j = 0; j < Trolls[trollIndex].promptOptions.Length; j++) {
// int bit = 1 << j;
// if(flags & bit && prompt.flags & bit) {
// Call_StartForward(prompt.activateFn);
// Call_PushCell(instance);
// Call_PushCell(activator);
// Call_PushCell(victim);
// Call_PushCell(value);
// Call_PushCell(flags);
// Call_PushCell(modifier);
// response = view_as<TrollEffectResponse>(Call_Finish());
// if(response != TE_Success) return response; // Let the menu handler deal with checking
// break;
// }
// }
// break;
// }
// }
// return
}
}
int g_iTrollIndex;
methodmap TrollBuilder {
public TrollBuilder(const char[] name, const char description[128], int mods) {
if(mods == 0) {
ThrowError("Troll \"%s\" has no modifiers defined.", name);
} else if(g_iTrollIndex == MAX_TROLLS + 1) {
ThrowError("Maximum number of trolls (%d) reached. Up MAX_TROLLS value.", MAX_TROLLS);
}
int i = g_iTrollIndex;
g_iTrollIndex++;
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));
char buffer[MAX_TROLL_NAME_LENGTH];
strcopy(buffer, sizeof(buffer), name);
StringToLower(buffer);
trollKV.SetValue(buffer, i);
return view_as<TrollBuilder>(i);
}
property int Id {
public get() { return view_as<int>(this); }
}
public TrollBuilder Hide() {
Trolls[this.Id].hidden = true;
}
public TrollBuilder SetDescription(const char description[128]) {
strcopy(Trolls[this.Id].description, 128, description);
}
public TrollBuilder SetTimer(float interval, Timer timer, int requiredFlags = 0, bool isDatapack = false) {
Trolls[this.Id].timerInterval = interval;
Trolls[this.Id].timerFunction = timer;
Trolls[this.Id].timerRequiredFlags = requiredFlags;
Trolls[this.Id].timerIsDataPack = isDatapack;
// Don't think this is necessary but whatever
for(int i = 0; i <= MAXPLAYERS; i++) {
Trolls[this.Id].timerHandles[i] = null;
}
return this;
}
public TrollBuilder SetAutoTimer(float interval, int requiredFlags = 0) {
this.SetTimer(interval, Timer_GenericTrollActivate, requiredFlags, true);
}
public TrollBuilder AddPrompt(const char[] customPrompt = "", int requiredFlags = 0) {
this._AddPrompt(false, requiredFlags, customPrompt);
return this;
}
public TrollBuilder AddPromptMulti(const char[] customPrompt = "", int requiredFlags = 0) {
this._AddPrompt(true, requiredFlags, customPrompt);
return this;
}
// Adds event handle for when an option for a non-multi prompt is selected. If current prompt is multi, will error
public TrollBuilder OnPromptActivate(PromptActivateFunction fn) {
TrollFlagPrompt prompt;
Trolls[this.Id].flagPrompts.GetArray(g_trollAddPromptIndex, prompt);
if(prompt.multiselect) ThrowError("Current prompt is multiselect");
if(prompt.activateFn == null) prompt.activateFn = new PrivateForward(ET_Single, Param_Cell, Param_Cell, Param_Cell, Param_Any, Param_Cell, Param_Cell);
prompt.activateFn.AddFunction(INVALID_HANDLE, fn);
Trolls[this.Id].flagPrompts.SetArray(g_trollAddPromptIndex, prompt);
return this;
}
public void _AddPrompt(bool multiselect, int requiredFlags = 0, const char[] customPrompt) {
TrollFlagPrompt prompt;
prompt.multiselect = multiselect;
prompt.requireFlags = requiredFlags;
if(customPrompt[0] != '\0')
strcopy(prompt.promptText, MAX_TROLL_FLAG_LENGTH, customPrompt);
int index = Trolls[this.Id].flagPrompts.PushArray(prompt);
g_trollAddPromptIndex = index;
}
public TrollBuilder AddOption(const char[] name, bool defaultOn = false) {
this._AddOption(name, defaultOn);
return this;
}
public TrollBuilder AddOptionInt(const char[] name, bool defaultOn = false, int data) {
this._AddOption(name, defaultOn, data);
return this;
}
public TrollBuilder AddOptionFloat(const char[] name, bool defaultOn = false, float data) {
// This is intentional - we do not want to convert float -> int, just change type
this._AddOption(name, defaultOn, view_as<int>(data));
return this;
}
public void _AddOption(const char[] name, bool defaultOn = false, int data = 0) {
if(Trolls[this.Id].promptOptions == null) {
Trolls[this.Id].promptOptions = new ArrayList(sizeof(TrollOptionData));
}
TrollOptionData option;
strcopy(option.name, MAX_TROLL_FLAG_LENGTH, name);
option.data = data;
int optionIndex = Trolls[this.Id].promptOptions.PushArray(option);
// Add option to current prompt
TrollFlagPrompt prompt;
if(g_trollAddPromptIndex >= Trolls[this.Id].flagPrompts.Length) {
ThrowError("No prompt added for troll \"%s\", for flag \"%s\"", this.Id, name);
}
Trolls[this.Id].flagPrompts.GetArray(g_trollAddPromptIndex, prompt);
prompt.flags |= ( 1 << optionIndex );
if(defaultOn) {
// If out of bounds, set to default -1 -> pick global prompt
if(Trolls[this.Id].flagPrompts.Length == 0) {
ThrowError("Troll \"%s\" does not have any flag prompts, thus a default value cannot be set. (flag=\"%s\")", Trolls[this.Id].name, name);
} else 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);
}
prompt.defaults |= (1 << optionIndex);
}
// Save changes to prompt
Trolls[this.Id].flagPrompts.SetArray(g_trollAddPromptIndex, prompt);
}
public TrollBuilder OnActivate(ActivateFunction fn) {
if(Trolls[this.Id].activateFn == null) {
Trolls[this.Id].activateFn = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
}
Trolls[this.Id].activateFn.AddFunction(INVALID_HANDLE, fn);
return this;
}
public TrollBuilder OnReset(ResetFunction fn) {
if(Trolls[this.Id].resetFn == null) {
Trolls[this.Id].resetFn = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
}
Trolls[this.Id].resetFn.AddFunction(INVALID_HANDLE, fn);
return this;
}
public Troll Build() {
return Troll(this.Id);
}
}
int GetTrollID(const char[] name) {
static int i = 0;
char buffer[MAX_TROLL_NAME_LENGTH];
strcopy(buffer, sizeof(buffer), name);
StringToLower(buffer);
if(trollKV.GetValue(buffer, i)) {
return i;
}
PrintToServer("GetTrollID: Troll was not found \"%s\"", name);
return -1;
}
bool IsAnyTrollActive(int victim) {
for(int i = 1; i <= MAX_TROLLS; i++) {
if(Troll(i).IsActive(victim)) return true;
}
return false;
}
TrollEffectResponse ApplyTroll(int victim, Troll troll, int activator, trollModifier modifier, int flags = 0, bool silent = false) {
char name[MAX_TROLL_NAME_LENGTH];
troll.GetName(name, sizeof(name));
int trollIndex = troll.Id;
bool isActive = troll.IsActive(victim);
// Clear troll specific timer:
if(troll.HasTimer) {
if(!isActive) {
if(modifier & TrollMod_Constant && (Trolls[trollIndex].timerRequiredFlags == 0 || Trolls[trollIndex].timerRequiredFlags & flags)) {
if(Trolls[trollIndex].timerIsDataPack) {
DataPack pack;
Trolls[trollIndex].timerHandles[victim] = CreateDataTimer(Trolls[trollIndex].timerInterval, Trolls[trollIndex].timerFunction, pack, TIMER_REPEAT);
pack.WriteCell(troll);
pack.WriteCell(activator);
pack.WriteCell(victim);
pack.WriteCell(flags);
} else {
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;
if(activator > 0 && t_metaReverse.IsActive(activator)) {
float chance;
t_metaReverse.GetPromptDataFloat(activator, 0, chance);
if(GetURandomFloat() <= chance) {
victim = activator;
}
}
// If victim is a survivor bot, check if has an idle player
if(IsFakeClient(victim) && GetClientTeam(victim) == 2) {
int player = GetRealClient(victim);
if(player > 0) {
// If there is an idle player, apply troll to them
ApplyTroll(player, troll, activator, modifier, flags, silent);
// And continue IF there is TrollMod_PlayerOnly mod
if(troll.HasMod(TrollMod_PlayerOnly)) return TE_Success;
// 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(isActive) {
Trolls[trollIndex].activeFlagClients[victim] = -1;
} else if(modifier & TrollMod_Constant) {
Trolls[trollIndex].activeFlagClients[victim] = flags;
}
// Applies any custom logic needed for a troll, mostly only used for TrollMod_Instant
TrollEffectResponse response = ApplyAffect(victim, troll, activator, modifier, flags);
if(response != TE_Success) return response; // Let the menu handler deal with checking
// Invoke Callbacks:
if(!isActive) {
Troll instance = Troll(trollIndex);
instance._triggerActivateFn(activator, victim, flags, modifier);
// Call the corresponding prompt callback if applicable
TrollFlagPrompt prompt;
for(int i = 0; i < Trolls[trollIndex].flagPrompts.Length; i++) {
Trolls[trollIndex].flagPrompts.GetArray(i, prompt);
if(!prompt.multiselect && prompt.activateFn != null) {
int value;
instance.GetPromptDataInt(victim, i, value);
for(int j = 0; j < Trolls[trollIndex].promptOptions.Length; j++) {
int bit = 1 << j;
if(flags & bit && prompt.flags & bit) {
Call_StartForward(prompt.activateFn);
Call_PushCell(instance);
Call_PushCell(activator);
Call_PushCell(victim);
Call_PushCell(value);
Call_PushCell(flags);
Call_PushCell(modifier);
response = view_as<TrollEffectResponse>(Call_Finish());
if(response != TE_Success) return response; // Let the menu handler deal with checking
break;
}
}
break;
}
}
} else if(isActive && Trolls[trollIndex].resetFn != null) {
Call_StartForward(Trolls[trollIndex].resetFn);
Call_PushCell(Troll(trollIndex));
Call_PushCell(activator);
Call_PushCell(victim);
Call_Finish();
}
// 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. ", name, victim);
LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, name, victim);
} else {
char flagName[50];
if(flags > 0 && troll.GetFlagNames(victim, flags, flagName, sizeof(flagName))) {
Format(flagName, sizeof(flagName), " (\x04%s|%d\x01)", flagName, flags);
}
if(modifier & TrollMod_Constant) {
CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default}%s for %N. ", name, flagName, victim);
} else {
CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default}%s for %N. ", name, flagName, victim);
}
LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, name, flags, victim);
}
} else {
CReplyToCommand(activator, "[FTT] Applied silently {yellow}\"%s\"{default} on %N with flags=%d", name, victim, flags);
}
return TE_Success;
}
void EnableTroll(int client, const char[] troll, int flags = 0) {
Troll.FromName(troll).Activate(0, client, TrollMod_Invalid, flags);
}
void DisableTroll(int client, const char[] troll) {
Troll.FromName(troll).Reset(client);
}
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);
Troll troll = Troll.FromName(name);
troll.Activate(activator, victim, modifier, flags, GetNativeCell(6));
return 0;
}