#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 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 } 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 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(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(TrollMod_Instant)) return TrollMod_Instant; else if(this.mods == view_as(TrollMod_Constant)) return TrollMod_Constant; else return TrollMod_Invalid; } /////// FLAGS bool GetFlagName(int index, char[] buffer, int maxlength) { if(this.flagNames == null || index >= this.flagNames.Length) return false; this.flagNames.GetString(index, buffer, maxlength); return true; } bool GetFlagNames(int client, char[] output, int maxlength) { if(this.flagNames == null) return false; char buffer[16]; for(int i = 0; i < this.flagNames.Length; i++) { int value = 1 << i; // If client has this flag: if(this.activeFlagClients[client] & value) { this.flagNames.GetString(i, buffer, sizeof(buffer)); Format(output, maxlength, "%s%s,", output, buffer); } } 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 TrollEffectResponse 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 to ApplyTroll, so it has to do unnecessary lookup via string return 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); } TrollEffectResponse 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 TE_Error; } 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(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(modifier & TrollMod_Constant) { Trolls[trollIndex].activeFlagClients[victim] = isActive ? -1 : 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 // 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(); if(flags > 0) { troll.GetFlagNames(victim, flagName, sizeof(flagName)); // 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); // } Format(flagName, sizeof(flagName), " (\x04%s|%d\x01)", flagName, flags); // CFormatColor(flagName, sizeof(flagName)); } if(modifier & TrollMod_Constant) { CShowActivityEx(activator, "[FTT] ", "activated constant {yellow}%s{default}%s for %N. ", troll.name, flagName, victim); } else { CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default}%s for %N. ", troll.name, flagName, 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); } return TE_Success; } 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(GetNativeCell(3)); if(view_as(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; }