diff --git a/plugins/l4d2_feedthetrolls.smx b/plugins/l4d2_feedthetrolls.smx index 1003165..1d530e5 100644 Binary files a/plugins/l4d2_feedthetrolls.smx and b/plugins/l4d2_feedthetrolls.smx differ diff --git a/scripting/include/feedthetrolls/base.inc b/scripting/include/feedthetrolls/base.inc index cc3a067..ffd8d1c 100644 --- a/scripting/include/feedthetrolls/base.inc +++ b/scripting/include/feedthetrolls/base.inc @@ -4,9 +4,11 @@ //Allow MAX_TROLLS to be defined elsewhere #if defined MAX_TROLLS #else - #define MAX_TROLLS 55 + #define MAX_TROLLS 56 #endif +Troll t_metaReverse; + enum trollModifier { TrollMod_Invalid = 0, TrollMod_Instant = 1 << 0, @@ -20,6 +22,10 @@ enum TrollEffectResponse { TE_Menu // Switching menus / etc, don't continue menu } +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 trollIds[MAX_TROLLS+1][MAX_TROLL_NAME_LENGTH]; char DEFAULT_FLAG_PROMPT_MULTIPLE[] = "Enable options (Multiple)"; @@ -27,7 +33,6 @@ 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" @@ -35,11 +40,17 @@ char SPECIAL_NAMES[][] = { 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); @@ -51,19 +62,12 @@ enum struct TrollFlagPrompt { } } - -// 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 TrollOptionData { + char name[MAX_TROLL_FLAG_LENGTH]; + int data; // can also be float } -enum struct Troll { +enum struct TrollData { int id; // The id or the index into the global Trolls[] array int categoryID; // The category this troll belongs in @@ -71,11 +75,15 @@ enum struct Troll { 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 @@ -84,140 +92,6 @@ enum struct Troll { 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); @@ -245,25 +119,22 @@ enum struct Troll { PrintToServer("FTT Debug: Disabling timer for %N", client); delete this.timerHandles[client]; } + if(this.resetFn != null) { + Call_StartForward(this.resetFn); + Call_PushCell(0); + Call_PushCell(client); + Call_PushCell(0); + Call_Finish(); + } } 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]; +TrollData Trolls[MAX_TROLLS+1]; ArrayList categories; static int categoryID = -1; @@ -271,10 +142,11 @@ 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); + 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); @@ -286,42 +158,312 @@ void ResetClient(int victim, bool wipe = true) { 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; +// TrollInstance of TrollData +methodmap Troll { + public Troll(int index) { + return view_as(index); } - 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++; + public static Troll FromName(const char[] name) { + int i = GetTrollID(name); + if(i == -1) + LogError("Unknown troll \"%s\"", name); + return view_as(i); + } + 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(this); } + } + property bool HasOptions { + public get() { return this.TotalOptionsCount > 0; } + } + + public bool IsActive(int client) { + return Trolls[this.Id].activeFlagClients[client] != -1; + } + + 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(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) { + PrintToServer("Activate: act:%d vic:%d", activator, victim); + if(modifier == TrollMod_Invalid) modifier = this.GetDefaultMod(); + return ApplyTroll(victim, this, activator, modifier, flags, silent); + } + public void Reset(int victim) { + Trolls[this.Id].activeFlagClients[victim] = -1; + if(Trolls[this.Id].resetFn != null) { + Call_StartForward(Trolls[this.Id].resetFn); + Call_PushCell(this); + Call_PushCell(0); + Call_PushCell(victim); + Call_PushCell(0); + 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(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(TrollMod_Instant)) return TrollMod_Instant; + else if(Trolls[this.Id].mods == view_as(TrollMod_Constant)) return TrollMod_Constant; + else return TrollMod_Invalid; + } } -// 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; +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(i); } - ThrowError("GetTroll: Troll was not found \"%s\"", name); - return -1; + + property int Id { + public get() { return view_as(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) { + Trolls[this.Id].timerInterval = interval; + Trolls[this.Id].timerFunction = timer; + Trolls[this.Id].timerRequiredFlags = requiredFlags; + for(int i = 0; i <= MAXPLAYERS; i++) { + Trolls[this.Id].timerHandles[i] = null; + } + return this; + } + + 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(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 SetActivationFunction(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 SetResetFunction(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; - if(trollKV.GetValue(name, i)) { + 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); @@ -334,11 +476,6 @@ bool IsAnyTrollActive(int victim) { } 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); @@ -349,19 +486,15 @@ void SetTrollFlags(int client, const char[] name, int flags = -1) { } -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; - } +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 = Trolls[trollIndex].activeFlagClients[victim] > -1; + bool isActive = troll.IsActive(victim); // Clear troll specific timer: - if(Trolls[trollIndex].timerInterval > 0.0) { + if(troll.HasTimer) { 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); @@ -374,14 +507,10 @@ TrollEffectResponse ApplyTroll(int victim, const char[] name, int activator, tro 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) { + if(activator > 0 && t_metaReverse.IsActive(activator)) { + float chance; + t_metaReverse.GetPromptDataFloat(activator, 0, chance); + if(GetURandomFloat() <= chance) { victim = activator; } } @@ -391,9 +520,9 @@ TrollEffectResponse ApplyTroll(int victim, const char[] name, int activator, tro int player = GetSpectatorClient(victim); if(player > 0) { // If there is an idle player, apply troll to them - ApplyTroll(player, name, activator, modifier, flags, silent); + ApplyTroll(player, troll, activator, modifier, flags, silent); // And continue IF there is TrollMod_PlayerOnly mod - if(troll.mods & view_as(TrollMod_PlayerOnly)) return TE_Success; + if(troll.HasMod(TrollMod_PlayerOnly)) return TE_Success; // Don't want to show two logs, so just ignore the bot silent = true; } @@ -409,63 +538,77 @@ TrollEffectResponse ApplyTroll(int victim, const char[] name, int activator, tro 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); + if(Trolls[trollIndex].activateFn != null) { + Call_StartForward(Trolls[trollIndex].activateFn); + Call_PushCell(instance); + Call_PushCell(activator); + Call_PushCell(victim); + Call_PushCell(flags); + Call_PushCell(modifier); + Call_Finish(); + } + + // 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; i++) { + 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(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_PushCell(modifier); + 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. ", troll.name, victim); - LogAction(activator, victim, "\"%L\" deactivated \"%s\" on \"%L\"", activator, troll.name, victim); + 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[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); - // } + char flagName[50]; + if(flags > 0 && troll.GetFlagNames(victim, flags, flagName, sizeof(flagName))) { 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); + 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. ", troll.name, flagName, victim); + CShowActivityEx(activator, "[FTT] ", "activated {yellow}%s{default}%s for %N. ", name, flagName, victim); } - LogAction(activator, victim, "\"%L\" activated \"%s\" (%d) for \"%L\"", activator, troll.name, flags, 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", troll.name, victim, flags); + CReplyToCommand(activator, "[FTT] Applied silently {yellow}\"%s\"{default} on %N with flags=%d", 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); @@ -496,12 +639,8 @@ public int Native_ApplyTroll(Handle plugin, int numParams) { 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); - } + Troll troll = Troll.FromName(name); + troll.Activate(victim, activator, modifier, flags, GetNativeCell(6)); return 0; } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/combos.inc b/scripting/include/feedthetrolls/combos.inc index acb9e90..aecc340 100644 --- a/scripting/include/feedthetrolls/combos.inc +++ b/scripting/include/feedthetrolls/combos.inc @@ -11,10 +11,10 @@ enum struct TrollCombo { ArrayList trolls; void AddTroll(const char[] name, int flags = 0, trollModifier mod = TrollMod_Invalid) { - int id = GetTrollID(name); - if(mod == TrollMod_Invalid) mod = Trolls[id].GetDefaultMod(); + Troll instance = Troll.FromName(name); + if(mod == TrollMod_Invalid) mod = instance.GetDefaultMod(); SpecifiedTroll troll; - troll.id = id; + troll.id = instance.Id; troll.mod = mod; troll.flags = flags; this.trolls.PushArray(troll, sizeof(troll)); @@ -24,7 +24,7 @@ enum struct TrollCombo { for(int i = 0; i < this.trolls.Length; i++) { SpecifiedTroll troll; this.trolls.GetArray(i, troll, sizeof(troll)); - Trolls[troll.id].Activate(target, client, troll.mod, troll.flags); + Troll(troll.id).Activate(target, client, troll.mod, troll.flags); } } } diff --git a/scripting/include/feedthetrolls/commands.inc b/scripting/include/feedthetrolls/commands.inc index 6d844f2..c23dbba 100644 --- a/scripting/include/feedthetrolls/commands.inc +++ b/scripting/include/feedthetrolls/commands.inc @@ -130,9 +130,12 @@ Action Command_InstaSpecialFace(int client, int args) { Action Command_WitchAttack(int client, int args) { - if(args < 1) { + if(!g_actionsAvailable) { + ReplyToCommand(client, "Unavailable: Missing \"actions\""); + return Plugin_Handled; + } else if(args < 1) { ReplyToCommand(client, "Usage: sm_witch_attack [# of witches or 0 for all]"); - }else{ + } else{ char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); char target_name[MAX_TARGET_LENGTH]; @@ -377,6 +380,7 @@ public Action Command_ListTheTrolls(int client, int args) { return Plugin_Handled; } + char buffer[50]; for(int p = 0; p < target_count; p++) { int target = target_list[p]; if(IsPlayerAlive(target)) @@ -388,25 +392,18 @@ public Action Command_ListTheTrolls(int client, int args) { int player = GetRealClient(target); if(player != -1) target = player; } - - for(int j = 1; j <= MAX_TROLLS; j++) { - if(Trolls[j].hidden) continue; - if(trollIds[j][0] != '\0' && IsTrollActive(target, trollIds[j])) { - if(Trolls[j].activeFlagClients[target] > 0) { - static char list[MAX_TROLL_FLAG_LENGTH*8]; //May in future need to up magic number 8 (supports 8 active flags ) - static char buffer[MAX_TROLL_FLAG_LENGTH]; - for(int i = 0; i < Trolls[j].flagNames.Length; i++) { - int a = (1 << i); - if(Trolls[j].activeFlagClients[target] & a) { - Trolls[j].flagNames.GetString(i, buffer, sizeof(buffer)); - Format(list, sizeof(list), "%s%s;", list, buffer); - } else { - Trolls[j].flagNames.GetString(i, buffer, sizeof(buffer)); - } - } - ReplyToCommand(client, "\"%s\" Flags: %s", trollIds[j], list); + + for(int j = 1; j < MAX_TROLLS; j++) { + Troll troll = Troll(j); + if(troll.Hidden) continue; + if(troll.IsActive(target)) { + int flags = troll.GetFlags(target); + if(flags > 0) { + buffer[0] = '\0'; + troll.GetFlagNames(target, flags, buffer, sizeof(buffer)); + ReplyToCommand(client, "\"%s\" Flags: %s", Trolls[troll.Id].name, buffer); } else - ReplyToCommand(client, trollIds[j]); + ReplyToCommand(client, "%s", Trolls[troll.Id].name); } } } @@ -424,8 +421,9 @@ public Action Command_ListTheTrolls(int client, int args) { } int modeCount = 0; for(int j = 1; j <= MAX_TROLLS; j++) { - if(trollIds[j][0] != '\0' && IsTrollActive(i, trollIds[j])) { - if(Trolls[j].activeFlagClients[i] > 0) + Troll troll = Troll(j); + if(troll.IsActive(i)) { + if(troll.GetFlags(i) > 0) Format(modeListArr[modeCount], MAX_TROLL_NAME_LENGTH, "%s(%d)", trollIds[j], Trolls[j].activeFlagClients[i]); else strcopy(modeListArr[modeCount], MAX_TROLL_NAME_LENGTH, trollIds[j]); @@ -729,7 +727,7 @@ Action Command_SetReverseFF(int client, int args) { } } - ApplyTroll(target, "Reverse FF", client, TrollMod_Constant, flag); + ApplyTroll(target, Troll.FromName("Reverse FF"), client, TrollMod_Constant, flag); return Plugin_Handled; } @@ -749,7 +747,7 @@ Action Command_SetMagnetShortcut(int client, int args) { } Action Command_CarSplat(int client, int args) { if(args < 1) { - ReplyToCommand(client, "Usage: sm_magnet [top/front/back]"); + ReplyToCommand(client, "Usage: sm_carsplat [top/front/back]"); return Plugin_Handled; } char arg[32]; @@ -779,9 +777,21 @@ Action Command_CarSplat(int client, int args) { LogAction(client, target, "spawned car on/in %s of \"%L\"", arg, target); ShowActivity(client, "spawned car (%s) of %N", arg, target); } else { - Troll troll; - GetTroll("Car Splat", troll); + Troll troll = Troll.FromName("Car Splat"); ShowSelectFlagMenu(client, GetClientUserId(target), view_as(TrollMod_Instant), troll); } return Plugin_Handled; +} + +Action Command_AddTypo(int client, int args) { + if(args == 2) { + char src[32], replacement[32]; + GetCmdArg(1, src, sizeof(src)); + GetCmdArg(2, replacement, sizeof(replacement)); + AddTypo(src, replacement, true); + ShowActivity(client, "added typo \"%s\" -> \"%s\"", src, replacement); + } else { + ReplyToCommand(client, "Syntax: /typo "); + } + return Plugin_Handled; } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/events.inc b/scripting/include/feedthetrolls/events.inc index fb85a37..5885944 100644 --- a/scripting/include/feedthetrolls/events.inc +++ b/scripting/include/feedthetrolls/events.inc @@ -22,13 +22,13 @@ public void OnMapStart() { g_spSpawnQueue.Clear(); spIsActive = false; - //CreateTimer(30.0, Timer_AutoPunishCheck, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } + public void OnClientPutInServer(int client) { pdata[client].pendingTrollBan = 0; pdata[client].shootAtTarget = 0; fAntiRushFrequencyCounter[client] = 0.0; - if(IsTrollActive(client, "Voice Mute")) + if(t_voiceMute.IsActive(client)) BaseComm_SetClientMute(client, true); SDKHook(client, SDKHook_OnTakeDamage, Event_TakeDamage); SDKHook(client, SDKHook_OnTakeDamageAlive, NerfGun_OnTakeDamage); @@ -63,7 +63,7 @@ void OnCarHitByTank(const char[] output, int caller, int activator, float delay) } void EntityCreateCallback(int entity) { - if(!HasEntProp(entity, Prop_Send, "m_hOwnerEntity") || !IsValidEntity(entity)) return; + if(!IsValidEntity(entity) || !HasEntProp(entity, Prop_Send, "m_hOwnerEntity")) return; static char class[16]; static int badThrowID; @@ -126,9 +126,8 @@ enum ProjectileMagnetType { ProjType_Cars = 4, } - - -public void Event_DoorToggle(Event event, const char[] name, bool dontBroadcast) { +void Event_DoorToggle(Event event, const char[] name, bool dontBroadcast) { + // TODO: hook OnOpen entity output? int client = GetClientOfUserId(event.GetInt("userid")); if(client && Trolls[t_slipperyShoesIndex].IsActive(client) && Trolls[t_slipperyShoesIndex].activeFlagClients[client] & 2) { L4D_StaggerPlayer(client, client, NULL_VECTOR); @@ -185,14 +184,25 @@ Action Timer_CheckSpecial(Handle h, int specialID) { } return Plugin_Handled; } -public void Frame_Boom(int special) { +void Frame_Boom(int special) { SDKHooks_TakeDamage(special, special, special, 1000.0); } -public void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) { +void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(client > 0) ResetClient(client, true); } +void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + for(int i = 0 ; i < MAX_TROLLS; i++) { + Trolls[i].activeFlagClients[client] = 0; + if(Trolls[i].timerHandles[client] != null) { + delete Trolls[i].timerHandles[client]; + } + } + } +} public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int userid = event.GetInt("userid"); @@ -216,7 +226,7 @@ public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast } public Action Event_WeaponReload(int weapon) { int client = GetEntPropEnt(weapon, Prop_Send, "m_hOwner"); - if(client > 0 && IsTrollActive(client, "Gun Jam")) { + if(client > 0 && t_gunJam.IsActive(client)) { if(GetRandomFloat() < 0.10) { //10% chance gun jams return Plugin_Stop; } @@ -385,7 +395,8 @@ public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { return Plugin_Continue; } -bool WillMagnetRun(const Troll troll, int i) { +// TODO: migrate to Troll +bool WillMagnetRun(const TrollData troll, int i) { // In the case none of the flags are set, return true (100% chance) // Some systems may give magnet w/ no flags if(troll.activeFlagClients[i] == 0) return true; @@ -451,7 +462,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] } PrintToServer("%N: %s", client, sArgs); return Plugin_Handled; - } else if(IsTrollActive(client, "Reversed")) { + } else if(Troll.FromName("Reversed").IsActive(client)) { int length = strlen(sArgs); char[] message = new char[length+1]; int j = 0; @@ -462,7 +473,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] CPrintToChatAll("{blue}%N {default}: %s", client, message); PrintToServer("%N: %s", client, sArgs); return Plugin_Handled; - }else if(IsTrollActive(client, "iCantSpellNoMore")) { + }else if(Troll.FromName("iCantSpellNoMore").IsActive(client)) { int type = GetRandomInt(1, 14 + 3); char letterSrc, replaceChar; switch(type) { @@ -585,18 +596,21 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] PrintToServer("%N: %s", client, sArgs); return Plugin_Handled; } else if(Trolls[typooId].IsActive(client)) { - char strings[32][MAX_TYPOS_LENGTH]; - int words = ExplodeString(sArgs, " ", strings, 32, MAX_TYPOS_LENGTH); - // Replace all typos - static char typoReplacement[32]; - for(int i = 0; i < words; i++) { - if(TYPOS_DICT.GetString(strings[i], typoReplacement, sizeof(typoReplacement))) { - strcopy(strings[i], MAX_TYPOS_LENGTH, typoReplacement); - } - } - int length = MAX_TYPOS_LENGTH * words; - char[] message = new char[length]; - ImplodeStrings(strings, 32, " ", message, length); + int len = strlen(sArgs) + 40; + char[] message = new char[len]; + ReplaceWithTypos(sArgs, message, len); + // char strings[32][MAX_TYPOS_LENGTH]; + // int words = ExplodeString(sArgs, " ", strings, 32, MAX_TYPOS_LENGTH); + // // Replace all typos + // static char typoReplacement[32]; + // for(int i = 0; i < words; i++) { + // if(TYPOS_DICT.GetString(strings[i], typoReplacement, sizeof(typoReplacement))) { + // strcopy(strings[i], MAX_TYPOS_LENGTH, typoReplacement); + // } + // } + // int length = MAX_TYPOS_LENGTH * words; + // char[] message = new char[length]; + // ImplodeStrings(strings, 32, " ", message, length); CPrintToChatAll("{blue}%N {default}: %s", client, message); PrintToServer("%N: %s", client, sArgs); @@ -636,7 +650,7 @@ public Action Event_ItemPickup(int client, int weapon) { } else if(flags & 32 && GetEntityClassname(weapon, wpnName, sizeof(wpnName)) && StrEqual(wpnName, "weapon_gascan")) { return Plugin_Handled; } - } else if(Trolls[UziRulesIndex].IsActive(client) || IsTrollActive(client, "Primary Disable")) { + } else if(Trolls[UziRulesIndex].IsActive(client) || Troll.FromName("Primary Disable").IsActive(client)) { GetEdictClassname(weapon, wpnName, sizeof(wpnName)); if(strcmp(wpnName[7], "rifle") >= 0 || strcmp(wpnName[7], "smg") >= 0 @@ -664,7 +678,7 @@ public Action Event_ItemPickup(int client, int weapon) { SetCommandFlags("give", flags); return Plugin_Stop; } - } else if(IsTrollActive(client, "Primary Disable")) { + } else if(Troll.FromName("Primary Disable").IsActive(client)) { return Plugin_Stop; } } @@ -793,9 +807,9 @@ Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage if(isSameTeam && Trolls[t_reverseFFIndex].IsActive(attacker)) { // Should this be applied? (as in no FF granted) bool disableFF = false; - if(damagetype == DMG_BURN) { + if(damagetype & DMG_BURN) { disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 32 != 0; - } else if(damagetype == DMG_BLAST) { + } else if(damagetype & DMG_BLAST) { disableFF = Trolls[t_reverseFFIndex].activeFlagClients[attacker] & 64 != 0; } else { // Does not run if DMG_BURN or DMG_BLAST @@ -932,11 +946,13 @@ public Action SoundHook(int clients[MAXPLAYERS], int& numClients, char sample[PL if(Trolls[honkID].IsActive(entity)) { if(Trolls[honkID].activeFlagClients[entity] & 1) strcopy(sample, sizeof(sample), "player/footsteps/clown/concrete1.wav"); - else if(Trolls[honkID].activeFlagClients[entity] & 2) + else if(Trolls[honkID].activeFlagClients[entity] & 2) { strcopy(sample, sizeof(sample), "custom/meow1.mp3"); - else if(Trolls[honkID].activeFlagClients[entity] & 4) + volume += 0.2; + } else if(Trolls[honkID].activeFlagClients[entity] & 4) { strcopy(sample, sizeof(sample), "custom/woof1.mp3"); - else + volume += 0.6; + } else return Plugin_Continue; return Plugin_Changed; } else if(Trolls[vocalGagID].IsActive(entity)) { @@ -1050,14 +1066,9 @@ public void L4D2_CInsectSwarm_CanHarm_Post(int acid, int spitter, int entity) { public void Event_EnteredSpit(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); - if(Trolls[t_stickyGooIndex].IsActive(client)) { - int flags = Trolls[t_stickyGooIndex].activeFlagClients[client]; - float movement = 0.0; - if(flags & 1) movement = 0.9; - else if(flags & 2) movement = 0.8; - else if(flags & 4) movement = 0.7; - else if(flags & 8) movement = 0.5; - else if(flags & 16) movement = 0.3; + if(t_stickyGoo.IsActive(client)) { + float movement; + t_stickyGoo.GetPromptDataFloat(client, 0, movement); SetEntPropFloat(client, Prop_Send, "m_flLaggedMovementValue", movement); pdata[client].lastInSpitTime = GetGameTime(); if(~pdata[client].flags & view_as(Flag_HasSpitTimer)) { @@ -1065,22 +1076,18 @@ public void Event_EnteredSpit(Event event, const char[] name, bool dontBroadcast pdata[client].flags |= view_as(Flag_HasSpitTimer); } } - } public void Event_BotPlayerSwap(Event event, const char[] name, bool dontBroadcast) { //Player replaced their idle bot int client = GetClientOfUserId(event.GetInt("player")); if(client > 0) { - bool debug_hadTroll = false; for(int i = 1; i <= MAX_TROLLS; i++) { - if(Trolls[i].IsActive(client) && Trolls[i].HasMod(TrollMod_Constant)) { //Add activeFlagClients >= 0 check possibly? - ApplyAffect(client, Trolls[i], -1, TrollMod_Constant, Trolls[i].activeFlagClients[client]); - debug_hadTroll = true; + Troll troll = Troll(i); + if(troll.IsActive(client) && troll.HasMod(TrollMod_Constant)) { //Add activeFlagClients >= 0 check possibly? + ApplyAffect(client, troll, -1, TrollMod_Constant, troll.GetFlags(client)); } } - if(debug_hadTroll) - PrintToServer("[FTT] Re-applied trolls for was-idle player %N", client); } } diff --git a/scripting/include/feedthetrolls/menus.inc b/scripting/include/feedthetrolls/menus.inc index 5c62874..cb6992b 100644 --- a/scripting/include/feedthetrolls/menus.inc +++ b/scripting/include/feedthetrolls/menus.inc @@ -158,7 +158,7 @@ public int ChooseCategoryHandler(Menu menu, MenuAction action, int param1, int p // Reset troll: if(category == -1) { - ApplyTroll(GetClientOfUserId(userid), "Reset User", param1, TrollMod_Instant); + Troll.FromName("Reset User").Activate(param1, victim, TrollMod_Instant); return 0; } @@ -182,22 +182,22 @@ public int ChooseModeMenuHandler(Menu menu, MenuAction action, int param1, int p static char str[2][8]; ExplodeString(info, "|", str, 2, 8, false); int userid = StringToInt(str[0]); - int client = GetClientOfUserId(userid); - if(client == 0) { + int victim = GetClientOfUserId(userid); + if(victim == 0) { ReplyToCommand(param1, "FTT: Could not acquire player"); return 0; } int keyIndex = StringToInt(str[1]); - static Troll troll; - GetTrollByKeyIndex(keyIndex, troll); + Troll troll = Troll(keyIndex); //If troll has multiple flags, prompt: - if(StrEqual(troll.name, "Throw It All")) { + if(troll == t_throwItAll) { // Setup menu to call itself, but with an extra data point ShowThrowItAllMenu(param1, userid); - } else if(!troll.IsActive(client) && troll.HasMod(TrollMod_Instant) && troll.HasMod(TrollMod_Constant)) { + } else if(!troll.IsActive(victim) && troll.HasMod(TrollMod_Instant) && troll.HasMod(TrollMod_Constant)) { Menu modiferMenu = new Menu(ChooseTrollModiferHandler); - Format(info, sizeof(info), "%s: Choose Modifier", troll.name); + // sadly cannot use methodmap easily to return name + Format(info, sizeof(info), "%s: Choose Modifier", Trolls[troll.Id].name); modiferMenu.SetTitle(info); Format(info, sizeof(info), "%d|%d|1", userid, keyIndex); @@ -209,13 +209,13 @@ public int ChooseModeMenuHandler(Menu menu, MenuAction action, int param1, int p modiferMenu.ExitButton = true; modiferMenu.Display(param1, 0); - } else if(!troll.IsActive(client) && troll.HasFlags()) { + } else if(!troll.IsActive(victim) && troll.HasOptions) { ShowSelectFlagMenu(param1, userid, -1, troll); } else { - TrollEffectResponse response = troll.Activate(client, param1); + TrollEffectResponse response = troll.Activate(param1, victim); // Only show menu if success or error, not TE_Menu if(response != TE_Menu) - ShowTrollsForCategory(param1, userid, troll.categoryID); + ShowTrollsForCategory(param1, userid, troll.CategoryId); } } else if (action == MenuAction_End) @@ -263,29 +263,27 @@ public int ChooseTrollModiferHandler(Menu menu, MenuAction action, int param1, i static char str[3][8]; ExplodeString(info, "|", str, 3, 8, false); int userid = StringToInt(str[0]); - int client = GetClientOfUserId(userid); + int victim = GetClientOfUserId(userid); int keyIndex = StringToInt(str[1]); int modifiers = StringToInt(str[2]); - if(client == 0) { + if(victim == 0) { ReplyToCommand(param1, "FTT: Could not acquire player"); return 0; } - static Troll troll; - GetTrollByKeyIndex(keyIndex, troll); - - if(!troll.IsActive(client) && troll.HasFlags()) { + Troll troll = Troll(keyIndex); + if(!troll.IsActive(victim) && troll.HasOptions) { // Show flag selection if troll is not enabled already ShowSelectFlagMenu(param1, userid, modifiers, troll); } else { TrollEffectResponse response; if(modifiers == 1 || modifiers == 3) - response = troll.Activate(client, param1, TrollMod_Instant); + response = troll.Activate(param1, victim, TrollMod_Instant); if(modifiers == 2 || modifiers == 3) - response = troll.Activate(client, param1, TrollMod_Constant); + response = troll.Activate(param1, victim, TrollMod_Constant); if(response != TE_Menu) - ShowTrollsForCategory(param1, userid, troll.categoryID); + ShowTrollsForCategory(param1, userid, troll.CategoryId); } } else if (action == MenuAction_End) @@ -300,20 +298,19 @@ public int ChooseTrollFlagHandler(Menu menu, MenuAction action, int param1, int static char str[6][8]; ExplodeString(info, "|", str, 6, 8, false); int userid = StringToInt(str[0]); - int client = GetClientOfUserId(userid); + int victim = GetClientOfUserId(userid); int keyIndex = StringToInt(str[1]); int modifiers = StringToInt(str[2]); int flags = StringToInt(str[3]); int index = StringToInt(str[4]); bool isDone = StringToInt(str[5]) == 1; // 0 = cont, 1 = done - if(client == 0) { + if(victim == 0) { ReplyToCommand(param1, "FTT: Could not acquire player"); return 0; } - static Troll troll; - GetTrollByKeyIndex(keyIndex, troll); + Troll troll = Troll(keyIndex); // If told to go to next prompt, find the next VALID prompt // Valid prompt is one where the required flags for it, are active @@ -321,7 +318,7 @@ public int ChooseTrollFlagHandler(Menu menu, MenuAction action, int param1, int if(isDone || index == -1) { int nextIndex = GetNextPrompt(troll, flags, index); // If there is a prompt available, show it, else fall down - if(nextIndex != -1) { + if(nextIndex >= 0) { ShowSelectFlagMenu(param1, userid, modifiers, troll, flags, nextIndex); return 0; } @@ -335,15 +332,15 @@ public int ChooseTrollFlagHandler(Menu menu, MenuAction action, int param1, int // Done with prompts, apply flags & modifiers if(modifiers > 0) { if(modifiers & view_as(TrollMod_Instant)) - response = troll.Activate(client, param1, TrollMod_Instant, flags); + response = troll.Activate(param1, victim, TrollMod_Instant, flags); if(modifiers & view_as(TrollMod_Constant)) - response = troll.Activate(client, param1, TrollMod_Constant, flags); + response = troll.Activate(param1, victim, TrollMod_Constant, flags); } else { - response = troll.Activate(client, param1, TrollMod_Invalid, flags); + response = troll.Activate(param1, victim, TrollMod_Invalid, flags); } // Jump back to selection screen if(response != TE_Menu) - ShowTrollsForCategory(param1, userid, troll.categoryID); + ShowTrollsForCategory(param1, userid, troll.CategoryId); } else if (action == MenuAction_End) delete menu; return 0; @@ -426,23 +423,21 @@ void ShowTrollsForCategory(int client, int userid, int category) { Format(info, sizeof(info), "Category: %s", info); trollMenu.SetTitle(info); - static Troll troll; - int victim = GetClientOfUserId(userid); // Add all menus that have same category ID to list - static char name[MAX_TROLL_NAME_LENGTH+8]; + char name[MAX_TROLL_NAME_LENGTH+8]; for(int i = 0; i < trollKV.Size; i++) { - GetTrollByKeyIndex(i, troll); + Troll troll = Troll(i); // If troll is hidden and using normal menu, do not show - if(troll.hidden && !SilentMenuSelected[client]) continue; - if(troll.categoryID == category) { + if(troll.Hidden && !SilentMenuSelected[client]) continue; + if(troll.CategoryId == category) { Format(info, sizeof(info), "%d|%d", userid, i); if(troll.IsActive(victim)) { - Format(name, sizeof(name), "%s (Active)", troll.name); + Format(name, sizeof(name), "%s (Active)", Trolls[i].name); trollMenu.AddItem(info, name); } else - trollMenu.AddItem(info, troll.name); + trollMenu.AddItem(info, Trolls[i].name); } } trollMenu.ExitButton = true; @@ -452,51 +447,44 @@ void ShowTrollsForCategory(int client, int userid, int category) { // Called with defaults on start, then recalled by ChooseTrollFlagHandler until prompt selection finished void ShowSelectFlagMenu(int activator, int victimUserID, int modifiers, Troll troll, int prevFlags = -1, int promptIndex = 0) { - static char info[MAX_TROLL_NAME_LENGTH+16]; //victimUSERID|trollID|modifiers|flags||flagIndex - static char name[32]; + char info[MAX_TROLL_NAME_LENGTH+16]; //victimUSERID|trollID|modifiers|flags||flagIndex + char name[32]; Menu flagMenu = new Menu(ChooseTrollFlagHandler); TrollFlagPrompt prompt; - troll.GetFlagPrompt(promptIndex, prompt); + troll.GetPrompt(promptIndex, prompt); prompt.GetPromptText(info, sizeof(info)); + flagMenu.SetTitle("%s", info); - Format(info, sizeof(info), "%s: %s", troll.name, info); - flagMenu.SetTitle(info); + if(prevFlags == -1) prevFlags = prompt.defaults; + + Format(info, sizeof(info), "%d|%d|%d|%d|%d|1", victimUserID, troll.Id, modifiers, prevFlags, promptIndex); if(prompt.multiselect) { - if(prevFlags == -1) prevFlags = prompt.defaults; - - Format(info, sizeof(info), "%d|%d|%d|%d|%d|1", victimUserID, troll.id, modifiers, prevFlags, promptIndex); + Format(info, sizeof(info), "%d|%d|%d|%d|%d|1", victimUserID, troll.Id, modifiers, prevFlags, promptIndex); flagMenu.AddItem(info, "Apply / Next Prompt"); - - for(int i = 0; i < troll.flagNames.Length; i++) { - int a = 1 << i; - if(prompt.flags & a) { - troll.flagNames.GetString(i, name, sizeof(name)); - // If flag is enabled, show indication (On) - if(prevFlags > 0 && prevFlags & a) - Format(name, sizeof(name), "%s ✓", name); - int newFlags = prevFlags ^ a; //Toggle the flag instead of setting like below, as it's toggleable here - Format(info, sizeof(info), "%d|%d|%d|%d|%d|0", victimUserID, troll.id, modifiers, newFlags, promptIndex); - flagMenu.AddItem(info, name); - } - } } else { - // Single choice only if(prevFlags == -1) prevFlags = 0; - for(int i = 0; i < troll.flagNames.Length; i++) { - int a = 1 << i; - if(prompt.flags & a) { - troll.flagNames.GetString(i, name, sizeof(name)); - // Add (default) indicator - - if(prompt.defaults & a) + } + for(int i = 0; i < troll.TotalOptionsCount; i++) { + int bit = 1 << i; + // Does prompt have bit + if(prompt.flags & bit) { + troll.GetOptionName(i, name, sizeof(name)); + // If flag is enabled, show indication (On) + int newFlags; + if(prompt.multiselect) { + if(prevFlags & bit) + Format(name, sizeof(name), "%s ✓", name); + newFlags = prevFlags ^ bit; //Toggle the flag instead of setting like below, as it's toggleable here + } else { + if(prompt.defaults & bit) Format(name, sizeof(name), "%s (default)", name); - int newFlags = prevFlags | a; //Set flag with any from previous prompts - Format(info, sizeof(info), "%d|%d|%d|%d|%d|1", victimUserID, troll.id, modifiers, newFlags, promptIndex); - flagMenu.AddItem(info, name); + newFlags = prevFlags | bit; } + Format(info, sizeof(info), "%d|%d|%d|%d|%d|%b", victimUserID, troll.Id, modifiers, newFlags, promptIndex, !prompt.multiselect); + flagMenu.AddItem(info, name); } } flagMenu.ExitButton = true; @@ -535,11 +523,12 @@ void ShowThrowItAllMenu(int client, int userid) { } int GetNextPrompt(Troll troll, int flags, int currentPrompt = 0) { - static TrollFlagPrompt prompt; + TrollFlagPrompt prompt; + // Check if we at the end of all possible prompts: + if(currentPrompt + 1 == troll.PromptCount) return -2; //If this prompt requires flags but they don't exist, skip to next that is valid or be done: - if(currentPrompt + 1 == troll.flagPrompts.Length) return -1; - for(int i = currentPrompt + 1; i < troll.flagPrompts.Length; i++) { - troll.GetFlagPrompt(i, prompt); + for(int i = currentPrompt + 1; i < troll.PromptCount; i++) { + troll.GetPrompt(i, prompt); if(flags & prompt.requireFlags == prompt.requireFlags) { return i; } diff --git a/scripting/include/feedthetrolls/misc.inc b/scripting/include/feedthetrolls/misc.inc index d3336c3..e0bbabc 100644 --- a/scripting/include/feedthetrolls/misc.inc +++ b/scripting/include/feedthetrolls/misc.inc @@ -4,13 +4,13 @@ void ActivateAutoPunish(int client) { if(hAutoPunish.IntValue & 2 == 2) - ApplyTroll(client, "Special Magnet", 0, TrollMod_Constant); + Troll.FromName("Special Magnet").Activate(0, client, TrollMod_Constant); if(hAutoPunish.IntValue & 1 == 1) - ApplyTroll(client, "Tank Magnet", 0, TrollMod_Constant); + Troll.FromName("Tank Magnet").Activate(0, client, TrollMod_Constant); if(hAutoPunish.IntValue & 8 == 8) - ApplyTroll(client, "Vomit Player", 0, TrollMod_Instant); + Troll.FromName("Vomit Player").Activate(0, client, TrollMod_Instant); else if(hAutoPunish.IntValue & 4 == 4) - ApplyTroll(client, "Swarm", 0, TrollMod_Instant); + Troll.FromName("Swarm").Activate(0, client, TrollMod_Instant); if(hAutoPunishExpire.IntValue > 0) { CreateTimer(60.0 * hAutoPunishExpire.FloatValue, Timer_ResetAutoPunish, GetClientOfUserId(client)); } @@ -82,7 +82,7 @@ stock bool IsPlayerIncapped(int client) { #define MAX_TYPOS_LENGTH 16 StringMap TYPOS_DICT; void LoadTypos() { - TYPOS_DICT.Clear(); + TYPOS_DICT = new StringMap(); char sPath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sPath, sizeof(sPath), "data/ftt_typos.txt"); @@ -100,10 +100,72 @@ void LoadTypos() { char buffer[140], key[32]; while(file.ReadLine(buffer, sizeof(buffer))) { int index = SplitString(buffer, " ", key, sizeof(key)); - TYPOS_DICT.SetString(key, buffer[index]); + AddTypo(key, buffer[index]); } - file.Close(); + delete file; +} + +ArrayList SplitStringList(const char[] message, char separator, int wordSize = 64) { + ArrayList words = new ArrayList(ByteCountToCells(wordSize)); + char[] word = new char[wordSize]; + int len = strlen(message); + int prevIndex; + for(int i = 0; i < len; i++) { + if(message[i] == separator) { + // Only copy the length of the string. The len includes space, which is used as null term + int wordLen = (i - prevIndex); + if(wordSize < wordLen) wordLen = wordSize; + strcopy(word, wordLen, message[prevIndex]); + words.PushString(word); + prevIndex = i; + } + } + // End of string, copy the remainder + strcopy(word, len, message[prevIndex]); + words.PushString(word); + return words; +} +void ReplaceWithTypos(const char[] message, char[] output, int maxlen) { + ArrayList words = SplitStringList(message, ' '); + message[0] = '\0'; + char word[64]; + ArrayList replaceList; + for(int i = 0; i < words.Length; i++) { + words.GetString(i, word, sizeof(word)); + if(TYPOS_DICT.GetValue(word, replaceList)) { + int index = GetRandomInt(0, replaceList.Length - 1); + replaceList.GetString(index, word, sizeof(word)); + if(i == 0) + Format(output, maxlen, "%s", word); + else + Format(output, maxlen, "%s %s", message, word); + } + } + delete words; +} + +void AddTypo(const char[] src, const char[] typo, bool save = false) { + ArrayList list; + TYPOS_DICT.GetValue(src, list); + if(list == null) { + list = new ArrayList(ByteCountToCells(MAX_TYPOS_LENGTH)); + } + list.PushString(typo); + TYPOS_DICT.SetValue(src, list); + if(save) { + char sPath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, sPath, sizeof(sPath), "data/ftt_typos.txt"); + File file = OpenFile(sPath, "a", false, NULL_STRING); + if(file == null) { + PrintToServer("[FTT] Cannot open for saving: data/ftt_typos.txt"); + return; + } + file.Seek(SEEK_END, 0); + file.WriteLine("%s %s", src, typo); + file.Flush(); + delete file; + } } #define MAX_PHRASES_PER_WORD 8 @@ -577,4 +639,25 @@ void SetSlot(int client, int slot) { static char slotStr[8]; Format(slotStr, sizeof(slotStr), "slot%d", slot); ClientCommand(client, slotStr); +} + +void RewindPlayer(int client) { + float curFlow = L4D2Direct_GetFlowDistance(client); + ArrayList navs = new ArrayList(); + L4D_GetAllNavAreas(navs); + navs.Sort(Sort_Random, Sort_Integer); + float minFlow = curFlow - 300.0; + float maxFlow = curFlow - 150.0; + // This finds the first nav area in range, usually closer + for(int i = 0; i < navs.Length; i++) { + float flow = L4D2Direct_GetTerrorNavAreaFlow(navs.Get(i)); + if(flow >= minFlow && flow <= maxFlow) { + float pos[3]; + L4D_FindRandomSpot(navs.Get(i), pos); + TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); + L4D_WarpToValidPositionIfStuck(client); + break; + } + } + delete navs; } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/timers.inc b/scripting/include/feedthetrolls/timers.inc index cb0712e..d664b48 100644 --- a/scripting/include/feedthetrolls/timers.inc +++ b/scripting/include/feedthetrolls/timers.inc @@ -1,10 +1,7 @@ Action Timer_ThrowTimer(Handle timer, int client) { - if(!IsClientInGame(client)) { - Trolls[t_throwItAllIndex].timerHandles[client] = null; - return Plugin_Stop; - } - ThrowAllItems(client); + if(IsClientInGame(client)) + ThrowAllItems(client); return Plugin_Continue; } int instantCommonRef[MAXPLAYERS+1]; @@ -481,4 +478,11 @@ Action Timer_RestoreHud(Handle h, int userid) { SetEntProp(client, Prop_Send, "m_iHideHUD", 0); } return Plugin_Handled; +} +Action Timer_RandomRewind(Handle h, int client) { + if(IsClientInGame(client) && GetURandomFloat() > 0.3) { + RewindPlayer(client); + } + return Plugin_Handled; + } \ No newline at end of file diff --git a/scripting/include/feedthetrolls/trolls.inc b/scripting/include/feedthetrolls/trolls.inc index feca0cb..9a7f578 100644 --- a/scripting/include/feedthetrolls/trolls.inc +++ b/scripting/include/feedthetrolls/trolls.inc @@ -1,7 +1,7 @@ // UP THE VALUE 'MAX_TROLLS' in base.inc before adding new ones! int t_slipperyShoesIndex = 0; -int t_stickyGooIndex = 0; +Troll t_stickyGoo; int t_invertedTrollIndex; int t_randomizeAnglesIndex; int t_randomizeVelocityIndex; @@ -10,272 +10,270 @@ int t_shakeyCameraIndex; int t_slotRouletteIndex; int t_damageBoostIndex; int t_reverseFFIndex; -int t_throwItAllIndex; int t_hideHUDIndex; +Troll t_throwItAll; +Troll t_voiceMute; +Troll t_gunJam; void SetupTrolls() { trollKV = new StringMap(); categories = new ArrayList(ByteCountToCells(16)); - gRandomClients = new ArrayList(); - int index; - SetupTroll("Reset User", "Resets the user, removes all troll effects", TrollMod_Instant); + TrollBuilder("Reset User", "Resets the user, removes all troll effects", TrollMod_Instant); /// CATEGORY: Magnets + TrollBuilder troll; SetCategory("Magnets"); - index = SetupTroll("Special Magnet", "Attracts ALL specials to any alive target with this troll enabled", TrollMod_Constant); - AddMagnetFlags(index); - index = SetupTroll("Tank Magnet", "Attracts ALL tanks to any alive target with this troll enabled", TrollMod_Constant); - AddMagnetFlags(index); + troll = TrollBuilder("Special Magnet", "Attracts ALL specials to any alive target with this troll enabled", TrollMod_Constant); + AddMagnetFlags(troll); + troll = TrollBuilder("Tank Magnet", "Attracts ALL tanks to any alive target with this troll enabled", TrollMod_Constant); + AddMagnetFlags(troll); #if defined _actions_included - index = SetupTroll("Witch Magnet", "All witches when startled will target any player with this troll", TrollMod_Constant); + TrollBuilder("Witch Magnet", "All witches when startled will target any player with this troll", TrollMod_Constant); #endif - index = SetupTroll("Projectile Magnet", "Makes all projectiles (biles, molotovs, pipes, tank rocks) go to player", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Target Sources", true); - // Tied to: ProjectileMagnetType - Trolls[index].AddFlag("Infected (rocks/goo)", true); - Trolls[index].AddFlag("Teammates (grenades)", false); - Trolls[index].AddFlag("Thrown Tank Objects", false); + TrollBuilder("Projectile Magnet", "Makes all projectiles (biles, molotovs, pipes, tank rocks) go to player", TrollMod_Constant) + .AddPromptMulti("Target Sources") + // Tied to: ProjectileMagnetType + .AddOption("Infected (rocks/goo)", true) + .AddOption("Teammates (grenades)") + .AddOption("Thrown Tank Objects"); /// CATEGORY: Infected SetCategory("Infected"); - SetupTroll("Swarm", "Swarms a player with zombies. Requires swarm plugin", TrollMod_Instant | TrollMod_Constant); - t_vomitPlayerIndex = SetupTroll("Vomit Player", "Shortcut to sm_vomitplayer. vomits the player.", TrollMod_Instant | TrollMod_Constant); - index = SetupTroll("Insta Special", "Shortcut to sm_insta", TrollMod_Instant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Around them (Director)", true); - Trolls[index].AddFlag("On top / in-face", false); - SetupTroll("Goo", "Spawns a spitter puddle underneath them", TrollMod_Instant); - index = SetupTroll("Sticky Goo", "Slows player down in goo", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("90% Movement Speed", true); - Trolls[index].AddFlag("80% Movement Speed", false); - Trolls[index].AddFlag("70% Movement Speed", false); - Trolls[index].AddFlag("50% Movement Speed", false); - Trolls[index].AddFlag("30% Movement Speed", false); - Trolls[index].AddFlag("0% Movement Speed", false); - t_stickyGooIndex = index; - index = SetupTroll("Vocalize Specials", "Spawn commons on special vocals", TrollMod_Constant) - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Mute Vocalization", true); - Trolls[index].AddFlag("Do not mute", false); - index = SetupTroll("Instant Commons", "Spawns commons behind or infront", TrollMod_Instant | TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("In Back", true); - Trolls[index].AddFlag("In Front", false); - index = SetupTroll("Smart Charge", "Waits until coast is clear to charge", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Attempt Timeout", false); - Trolls[index].AddFlag("15 Seconds", true); - Trolls[index].AddFlag("30 Seconds", false); - Trolls[index].AddFlag("1 minute", false); - Trolls[index].AddFlag("5 minutes", false); + TrollBuilder("Swarm", "Swarms a player with zombies. Requires swarm plugin", TrollMod_Instant | TrollMod_Constant); + t_vomitPlayerIndex = TrollBuilder("Vomit Player", "Shortcut to sm_vomitplayer. vomits the player.", TrollMod_Instant | TrollMod_Constant).Id; + TrollBuilder("Insta Special", "Shortcut to sm_insta", TrollMod_Instant) + .AddPrompt() + .AddOption("Around them (Director)", true) + .AddOption("On top / in-face"); + TrollBuilder("Goo", "Spawns a spitter puddle underneath them", TrollMod_Instant); + t_stickyGoo = TrollBuilder("Sticky Goo", "Slows player down in goo", TrollMod_Constant) + .AddPrompt() + .AddOptionFloat("90% Movement Speed", true, 0.9) + .AddOptionFloat("80% Movement Speed", false, 0.8) + .AddOptionFloat("70% Movement Speed", false, 0.7) + .AddOptionFloat("50% Movement Speed", false, 0.5) + .AddOptionFloat("30% Movement Speed", false, 0.3) + .AddOptionFloat("0% Movement Speed", false, 0.0) + .Build(); + TrollBuilder("Vocalize Specials", "Spawn commons on special vocals", TrollMod_Constant) + .AddPrompt() + .AddOption("Mute Vocalization", true) + .AddOption("Do not mute", false) + TrollBuilder("Instant Commons", "Spawns commons behind or infront", TrollMod_Instant | TrollMod_Constant) + .AddPrompt() + .AddOption("In Back", true) + .AddOption("In Front", false); + TrollBuilder("Smart Charge", "Waits until coast is clear to charge", TrollMod_Constant) + .AddPrompt("Attempt Timeout") + .OnPromptActivate(Activate_SmartCharge) + .AddOptionInt("15 Seconds", true, 15) + .AddOptionInt("30 Seconds", false, 30) + .AddOptionInt("1 minute", false, 60) + .AddOptionInt("5 minutes", false, 300); // CATEGORY: Projectiles SetCategory("Projectiles"); - index = SetupTroll("Rock Dropper", "Drops on a rock. On their head.", TrollMod_Instant); - // Trolls[index].AddFlagPrompt(false); - // Trolls[index].AddFlag("Drop From Above", true); - // Trolls[index].AddFlag("From behind", false); - index = SetupTroll("Car Splat", "Car. splats.", TrollMod_Instant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("On Top", true); - Trolls[index].AddFlag("Into (Infront)", false); - Trolls[index].AddFlag("Into (Behind)", false); - index = SetupTroll("Bad Throw", "Player drops throwables on throw, and biles/molotovs themselves", TrollMod_Constant); - Trolls[index].AddFlagPrompt(true); - Trolls[index].AddFlag("Biles", true); - Trolls[index].AddFlag("Molotovs", true); - Trolls[index].AddFlag("Pipebombs", true); - index = SetupTroll("Molotov Bath", "Throws a molotov on their feet", TrollMod_Instant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Normal", true); - Trolls[index].AddFlag("Set the town ablaze", false); + TrollBuilder("Rock Dropper", "Drops on a rock. On their head.", TrollMod_Instant); + TrollBuilder("Car Splat", "Car. splats.", TrollMod_Instant) + .AddPrompt() + .AddOption("On Top", true) + .AddOption("Into (Infront)", false) + .AddOption("Into (Behind)", false); + TrollBuilder("Bad Throw", "Player drops throwables on throw, and biles/molotovs themselves", TrollMod_Constant) + .AddPromptMulti() + .AddOption("Biles", true) + .AddOption("Molotovs", true) + .AddOption("Pipebombs", true) + TrollBuilder("Molotov Bath", "Throws a molotov on their feet", TrollMod_Instant) + .AddPrompt() + .AddOption("Normal", true) + .AddOption("Set the town ablaze", false); // CATEGORY: Items SetCategory("Items"); - index = SetupTroll("Throw It All", "Player throws their item(s) periodically to a nearby player", TrollMod_Instant); - Trolls[index].SetTimer(THROWITALL_INTERVAL, Timer_ThrowTimer); - index = SetupTroll("Spicy Gas", "Gascans player picks up just ignite. Magic.", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Always (100%)", false); - Trolls[index].AddFlag("Half Time (50%)", true); - Trolls[index].AddFlag("Rare (10%)", false); - index = SetupTroll("No Pickup", "Prevents a player from picking up ANY (new) item. Use ThrowItAll to make them drop", TrollMod_Constant); - Trolls[index].AddFlagPrompt(true); - Trolls[index].AddFlag("No Primary", false); - Trolls[index].AddFlag("No Melee", false); - Trolls[index].AddFlag("No Throwables", true); - Trolls[index].AddFlag("No Kits", true); - Trolls[index].AddFlag("No Pills / Adr", true); - Trolls[index].AddFlag("No GASCANS", true); - index = SetupTroll("UziRules / AwpSmells", "Picking up a weapon gives them a UZI or AWP instead", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("UZI Only", true); - Trolls[index].AddFlag("AWP Only", false); - SetupTroll("Primary Disable", "Player cannot pickup any weapons, only melee/pistols", TrollMod_Constant); - index = SetupTroll("Dull Melee", "Player's melee weapon does 0 damage (based on %). Headshots still work", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Always (100%)", false); - Trolls[index].AddFlag("Half Time (50%)", true); - Trolls[index].AddFlag("Rare (10%)", false); - SetupTroll("Nerf Gun", "When they shoot it does no damage.", TrollMod_Constant); - SetupTroll("Randomize Clip Ammo", "Randomly changes their clip ammo downwards", TrollMod_Constant | TrollMod_Instant); - SetupTroll("CameTooEarly", "When they shoot, random chance they empty whole clip", TrollMod_Constant); - index = SetupTroll("Slot Roulette", "Randomize their slots", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Activations:", true); - Trolls[index].AddFlag("On Vomitted", false); // 1 - Trolls[index].AddFlag("On Damage", false); // 2 - Trolls[index].AddFlag("On Vocalize", false); // 4 - Trolls[index].AddFlag("Periodically", true); //8 - Trolls[index].AddCustomFlagPrompt("Frequency:", false, 8); - Trolls[index].AddFlag("Subtle", false); // 16 - Trolls[index].AddFlag("Confusing", false); // 32 - Trolls[index].AddFlag("Annoying", false); // 64 - Trolls[index].AddFlag("Unusable", false); // 128 - Trolls[index].SetTimer(0.2, Timer_SlotRoulette, 8); - t_slotRouletteIndex = index; + t_throwItAll = TrollBuilder("Throw It All", "Player throws their item(s) periodically to a nearby player", TrollMod_Instant) + .SetTimer(THROWITALL_INTERVAL, Timer_ThrowTimer) + .Build(); + TrollBuilder("Spicy Gas", "Gascans player picks up just ignite. Magic.", TrollMod_Constant) + .AddPrompt() + .AddOption("Always (100%)", false) + .AddOption("Half Time (50%)", true) + .AddOption("Rare (10%)", false); + TrollBuilder("No Pickup", "Prevents a player from picking up ANY (new) item. Use ThrowItAll to make them drop", TrollMod_Constant) + .AddPromptMulti() + .AddOption("No Primary", false) + .AddOption("No Melee", false) + .AddOption("No Throwables", true) + .AddOption("No Kits", true) + .AddOption("No Pills / Adr", true) + .AddOption("No GASCANS", true); + TrollBuilder("UziRules / AwpSmells", "Picking up a weapon gives them a UZI or AWP instead", TrollMod_Constant) + .AddPrompt() + .AddOption("UZI Only", true) + .AddOption("AWP Only", false) + TrollBuilder("Primary Disable", "Player cannot pickup any weapons, only melee/pistols", TrollMod_Constant); + TrollBuilder("Dull Melee", "Player's melee weapon does 0 damage (based on %). Headshots still work", TrollMod_Constant) + .AddPrompt() + .AddOption("Always (100%)", false) + .AddOption("Half Time (50%)", true) + .AddOption("Rare (10%)", false); + TrollBuilder("Nerf Gun", "When they shoot it does no damage.", TrollMod_Constant); + TrollBuilder("Randomize Clip Ammo", "Randomly changes their clip ammo downwards", TrollMod_Constant | TrollMod_Instant); + TrollBuilder("CameTooEarly", "When they shoot, random chance they empty whole clip", TrollMod_Constant); + t_slotRouletteIndex = TrollBuilder("Slot Roulette", "Randomize their slots", TrollMod_Constant) + .SetTimer(0.2, Timer_SlotRoulette, 8) + .AddPromptMulti("Activiations") + .AddOption("On Vomitted") // 1 << 0 + .AddOption("On Damage") // 1 << 1 + .AddOption("On Vocalize") // 1 << 2 + .AddOption("Periodically") // 1 << 3 + .AddPrompt("Frequency", 1 << 3) + .AddOption("Subtle") // 1 << 4 + .AddOption("Confusing") // 1 << 5 + .AddOption("Annoying") // 1 << 6 + .AddOption("Unusuable") // 1 << 7 + .Id; /// CATEGORY: Chat SetCategory("Chat"); - SetupTroll("Typoos", "", TrollMod_Constant); - SetupTroll("iCantSpellNoMore", "Chat messages letter will randomly changed with wrong letters", TrollMod_Constant); - index = SetupTroll("No Profanity", "Replaces some words with random phrases", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Only Replace Swears", false); - Trolls[index].AddFlag("Replace Full Messages", true); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Show Modified to Them", true); - Trolls[index].AddFlag("Show Original to Them", false); - index = SetupTroll("Vocalize Gag", "Prevents player from sending any vocalizations (even automatic)", TrollMod_Constant); - // Trolls[index].AddFlagPrompt(false); - // Trolls[index].AddFlag("Mute for All", true); - // Trolls[index].AddFlag("Mute For All But Them", false); - index = SetupTroll("Honk / Meow / Woof", "Custom sounds", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Choose Sound:"); - Trolls[index].AddFlag("Honk", true); - Trolls[index].AddFlag("Meow", false); - Trolls[index].AddFlag("Woof", false); - Trolls[index].AddCustomFlagPrompt("Choose Chat modifier:", false, 1); - Trolls[index].AddFlag("Show Modified to Them", true); - Trolls[index].AddFlag("Show Original to Them", false); - Trolls[index].AddFlag("Show Modified Only To Them", false); - SetupTroll("Reversed", "Reserves their message", TrollMod_Constant); - SetupTroll("Voice Mute", "Mutes from voice", TrollMod_Constant); - SetupTroll("No Rushing Us", "Decreases player speed everytime they yell hurry up", TrollMod_Constant); + TrollBuilder("Typoos", "", TrollMod_Constant); + TrollBuilder("iCantSpellNoMore", "Chat messages letter will randomly changed with wrong letters", TrollMod_Constant); + TrollBuilder("No Profanity", "Replaces some words with random phrases", TrollMod_Constant) + .AddPrompt() + .AddOption("Only Replace Swears") + .AddOption("Replace Full Messages", true) + .AddPrompt() + .AddOption("Show Modified to Them", true) + .AddOption("Show Original to Them"); + TrollBuilder("Vocalize Gag", "Prevents player from sending any vocalizations (even automatic)", TrollMod_Constant); + // .AddPrompt() + // .AddOption("Mute for All", true); + // .AddOption("Mute For All But Them", false); + TrollBuilder("Honk / Meow / Woof", "Custom sounds", TrollMod_Constant) + .AddPrompt("Choose Sound:") + .AddOption("Honk", true) + .AddOption("Meow", false) + .AddOption("Woof", false) + .AddPrompt("Choose Chat modifier:", 1) + .AddOption("Show Modified to Them", true) + .AddOption("Show Original to Them", false) + .AddOption("Show Modified Only To Them", false); + TrollBuilder("Reversed", "Reserves their message", TrollMod_Constant); + t_voiceMute = TrollBuilder("Voice Mute", "Mutes from voice", TrollMod_Constant).Build(); + TrollBuilder("No Rushing Us", "Decreases player speed everytime they yell hurry up", TrollMod_Constant); /// CATEGORY: Health SetCategory("Health"); - t_damageBoostIndex = SetupTroll("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant); - SetupTroll("Temp Health Quick Drain", "Makes a player's temporarily health drain very quickly", TrollMod_Constant); - SetupTroll("Slow Drain", "Will make the player slowly lose health over time", TrollMod_Constant); - SetupTroll("KillMeSoftly", "Make player eat or waste pills whenever possible", TrollMod_Instant | TrollMod_Constant); - index = SetupTroll("Reverse FF", "All damage dealt to a player is reversed", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Choose Reverse FF", false); - Trolls[index].AddFlag("1:1 Ratio", true); //1 - Trolls[index].AddFlag("2x Ratio", false); //2 - Trolls[index].AddFlag("0.5x Ratio", false); //4 - Trolls[index].AddFlag("0.0x Ratio (None)", false); //8 - Trolls[index].AddFlag("3x Ratio", false); //16 - Trolls[index].AddCustomFlagPrompt("Modes", true); - Trolls[index].AddFlag("Reverse Fire Damage", false); //32 - Trolls[index].AddFlag("Reverse Explosions", false); //64 - t_reverseFFIndex = index; + t_damageBoostIndex = TrollBuilder("Damage Boost", "Makes a player take more damage than normal", TrollMod_Constant).Id; + TrollBuilder("Temp Health Quick Drain", "Makes a player's temporarily health drain very quickly", TrollMod_Constant); + TrollBuilder("Slow Drain", "Will make the player slowly lose health over time", TrollMod_Constant); + TrollBuilder("KillMeSoftly", "Make player eat or waste pills whenever possible", TrollMod_Instant | TrollMod_Constant); + t_reverseFFIndex = TrollBuilder("Reverse FF", "All damage dealt to a player is reversed", TrollMod_Constant) + .AddPrompt("Choose Reverse FF", false) + .AddOption("1:1 Ratio", true) //1 + .AddOption("2x Ratio", false) //2 + .AddOption("0.5x Ratio", false) //4 + .AddOption("0.0x Ratio (None)", false) //8 + .AddOption("3x Ratio", false) //16 + .AddPromptMulti("Modes") + .AddOption("Reverse Fire Damage", false) //32 + .AddOption("Reverse Explosions", false) //64 + .Id; - index = SetupTroll("Dep Bots", "Makes bots heal a player. At any cost", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Do not spawn extra", true); // 1 - Trolls[index].AddFlag("Spawn extra bots (broke)", false); // 2 - Trolls[index].AddCustomFlagPrompt("# Of Bots", false); - Trolls[index].AddFlag("1", false); // 4 - Trolls[index].AddFlag("2", false); // 8 - Trolls[index].AddFlag("3", false); // 16 - Trolls[index].AddFlag("4", true); // 32 - Trolls[index].AddFlag("5", false); // 64 - Trolls[index].AddCustomFlagPrompt("Auto Timeout", false, 0); - Trolls[index].AddFlag("Until Healed / Map Change", false); // 128 - Trolls[index].AddFlag("15 seconds", true); // 255 - Trolls[index].AddFlag("30 seconds", false); // 512 - Trolls[index].AddFlag("1 minute", false); //1024 - Trolls[index].AddFlag("5 minutes", false); //2048 + TrollBuilder("Dep Bots", "Makes bots heal a player. At any cost", TrollMod_Constant) + .AddPrompt() + .AddOption("Do not spawn extra", true) // 1 + .AddOption("Spawn extra bots (broke)", false) // 2 + .AddPrompt("# Of Bots") + .AddOption("1", false) // 4 + .AddOption("2", false) // 8 + .AddOption("3", false) // 16 + .AddOption("4", true) // 32 + .AddOption("5", false) // 64 + .AddPrompt("Auto Timeout") + .AddOption("Until Healed / Map Change", false) // 128 + .AddOption("15 seconds", true) // 255 + .AddOption("30 seconds", false) // 512 + .AddOption("1 minute", false) //1024 + .AddOption("5 minutes", false); //2048 /// CATEGORY: Movement SetCategory("Movement"); - index = SetupTroll("Slow Speed", "Sets player speed to 0.8x of normal speed", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("90% Movement Speed", true); - Trolls[index].AddFlag("80% Movement Speed", false); - Trolls[index].AddFlag("70% Movement Speed", false); - Trolls[index].AddFlag("50% Movement Speed", false); - Trolls[index].AddFlag("30% Movement Speed", false); - Trolls[index].AddFlag("0% Movement Speed", false); - SetupTroll("Higher Gravity", "Sets player gravity to 1.3x of normal gravity", TrollMod_Constant); - t_invertedTrollIndex = SetupTroll("Inverted Controls", "Well, aint it obvious", TrollMod_Constant); - SetupTroll("Stagger", "Like a slap, but different", TrollMod_Instant); - index = SetupTroll("Slippery Shoes", "Periodically stumbles around.", TrollMod_Constant); - Trolls[index].AddFlagPrompt(true); - Trolls[index].AddFlag("Periodically", true); - Trolls[index].AddFlag("When using doors", false); - Trolls[index].AddFlag("On throwable use", false); - Trolls[index].AddFlag("On pills/adrenaline use", false); - Trolls[index].AddFlag("On zombie bite", false); - t_slipperyShoesIndex = index; - index = SetupTroll("Randomize Angles", "Randomly change their angles", TrollMod_Constant); - Trolls[index].AddCustomFlagPrompt("Frequency:", false); - Trolls[index].AddFlag("Once in a while", true); //1 - Trolls[index].AddFlag("Periodically", false); //2 - Trolls[index].AddFlag("A lot", false); //4 - Trolls[index].AddFlag("Painful", false); //8 - Trolls[index].AddFlag("Seizure", false); //16 - t_randomizeAnglesIndex = index; - index = SetupTroll("Randomize Velocity", "Randomly change their velocity", TrollMod_Constant); - Trolls[index].SetTimer(0.1, Timer_RandomVelocity); - Trolls[index].AddCustomFlagPrompt("Frequency:", false); - Trolls[index].AddFlag("Loose", true); //1 - Trolls[index].AddFlag("Slippery", false); //2 - Trolls[index].AddFlag("Earthquake", false); //4 - Trolls[index].AddFlag("Severe Earthquake", false); //8 - Trolls[index].AddFlag("Bouncy Castle", false); //16 - t_randomizeVelocityIndex = index; + TrollBuilder("Slow Speed", "Sets player speed to 0.8x of normal speed", TrollMod_Constant) + .AddPrompt() + .AddOptionFloat("90% Movement Speed", true, 0.9) + .AddOptionFloat("80% Movement Speed", false, 0.8) + .AddOptionFloat("70% Movement Speed", false, 0.7) + .AddOptionFloat("50% Movement Speed", false, 0.5) + .AddOptionFloat("30% Movement Speed", false, 0.3) + .AddOptionFloat("0% Movement Speed", false, 0.0); + TrollBuilder("Higher Gravity", "Sets player gravity to 1.3x of normal gravity", TrollMod_Constant); + t_invertedTrollIndex = TrollBuilder("Inverted Controls", "Well, aint it obvious", TrollMod_Constant).Id; + t_slipperyShoesIndex = TrollBuilder("Slippery Shoes", "Periodically stumbles around.", TrollMod_Constant | TrollMod_Instant) + .AddPromptMulti() + .AddOption("Periodically", true) // 1 << 0 + .AddOption("When using doors") // 1 << 1 + .AddOption("On throwable use") + .AddOption("On pills/adrenaline use") + .AddOption("On zombie bite") + .Id + t_randomizeAnglesIndex = TrollBuilder("Randomize Angles", "Randomly change their angles", TrollMod_Constant) + .AddPrompt("Frequency:") + .AddOption("Once in a while", true) //1 + .AddOption("Periodically", false) //2 + .AddOption("A lot", false) //4 + .AddOption("Painful", false) //8 + .AddOption("Seizure", false) //16 + .Id; + t_randomizeVelocityIndex = TrollBuilder("Randomize Velocity", "Randomly change their velocity", TrollMod_Constant) + .SetTimer(0.1, Timer_RandomVelocity) + .AddPrompt("Frequency:") + .AddOption("Loose", true) //1 + .AddOption("Slippery", false) //2 + .AddOption("Earthquake", false) //4 + .AddOption("Severe Earthquake", false) //8 + .AddOption("Bouncy Castle", false) //16 + .Id; + TrollBuilder("Rewind", "Teleports player backwards", TrollMod_Instant | TrollMod_Constant) + .SetTimer(10.0, Timer_RandomRewind); /// CATEGORY: MISC SetCategory("Misc"); - SetupTroll("Gun Jam", "On reload, small chance their gun gets jammed - Can't reload.", TrollMod_Constant); - SetupTroll("No Shove", "Prevents a player from shoving", TrollMod_Constant); - index = SetupTroll("No Button Touchie", "Stops people from pressing buttons", TrollMod_Constant); - Trolls[index].AddFlagPrompt(true); - Trolls[index].AddFlag("Prevent Use", true); - Trolls[index].AddFlag("Vomit On Touch", false); - Trolls[index].AddFlag("Incap On Touch", false); - Trolls[index].AddFlag("Slay On Touch", false); - Trolls[index].AddFlag("0.8x Speed", false); + t_gunJam = TrollBuilder("Gun Jam", "On reload, small chance their gun gets jammed - Can't reload.", TrollMod_Constant).Build(); + TrollBuilder("No Shove", "Prevents a player from shoving", TrollMod_Constant); + TrollBuilder("No Button Touchie", "Stops people from pressing buttons", TrollMod_Constant) + .AddPromptMulti() + .AddOption("Prevent Use", true) + .AddOption("Vomit On Touch", false) + .AddOption("Incap On Touch", false) + .AddOption("Slay On Touch", false) + .AddOption("0.8x Speed", false); // TODO: setup instant - index = SetupTroll("Shakey Camera", "Horrible", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); + t_shakeyCameraIndex = TrollBuilder("Shakey Camera", "Horrible", TrollMod_Constant) + .AddPrompt() // add flag: vomit on touch - Trolls[index].AddFlag("Annoying but playable", false); - Trolls[index].AddFlag("Bad", true); - Trolls[index].AddFlag("Sickness", false); - Trolls[index].AddFlag("Violent", false); - Trolls[index].AddFlag("Violent XX", false); - t_shakeyCameraIndex = index; - index = SetupTroll("Hide HUD", "Horrible", TrollMod_Constant); - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("Rare & Short", false); - Trolls[index].AddFlag("Sometimes & Medium", false); - Trolls[index].AddFlag("Constantly", true); - t_hideHUDIndex = index; - index = SetupTroll("Meta: Random", "Picks a random troll", TrollMod_Instant); - index = SetupTroll("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant); - Trolls[index].hidden = true; - Trolls[index].AddFlagPrompt(false); - Trolls[index].AddFlag("100%", true); - Trolls[index].AddFlag("50%", false); - Trolls[index].AddFlag("10%", false); - - - - + .AddOption("Annoying but playable", false) + .AddOption("Bad", true) + .AddOption("Sickness", false) + .AddOption("Violent", false) + .AddOption("Violent XX", false) + .Id; + t_hideHUDIndex = TrollBuilder("Hide HUD", "Horrible", TrollMod_Constant) + .AddPrompt() + .AddOption("Rare & Short", false) + .AddOption("Sometimes & Medium", false) + .AddOption("Constantly", true) + .Id; + TrollBuilder("Meta: Random", "Picks a random troll", TrollMod_Instant); + t_metaReverse = TrollBuilder("Meta: Inverse", "Uhm you are not supposed to see this...", TrollMod_Instant) + .Hide() + .AddPrompt() + .AddOptionFloat("100%", true, 1.0) + .AddOptionFloat("50%", false, 0.5) + .AddOptionFloat("10%", false, 0.1) + .Build(); // Initialize the default flag values to -1 for(int i = 0; i <= MAX_TROLLS; i++) { @@ -287,16 +285,31 @@ void SetupTrolls() { } -void AddMagnetFlags(int index) { - Trolls[index].AddCustomFlagPrompt("Choose Magnet Chance:", false); - Trolls[index].AddFlag("Always (100%)", true); - Trolls[index].AddFlag("Half Time (50%)", false); - Trolls[index].AddFlag("Rare (10%)", false); +TrollEffectResponse Activate_SmartCharge(Troll troll, int activator, int victim, int timeout, int flags, trollModifier mod) { + if(pdata[victim].smartChargeActivator > 0) { + ReplyToCommand(activator, "Target already has smart charge enabled"); + return TE_Error; + } + pdata[victim].smartChargeAttempts = 0; + pdata[victim].smartChargeMaxAttempts = timeout; + pdata[victim].smartChargeActivator = GetClientUserId(activator); + CreateTimer(1.0, Timer_CheckForChargerOpportunity, GetClientUserId(victim), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + return TE_Success; } -TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, trollModifier modifier, int flags) { - bool toActive = IsTrollActiveByRawID(victim, troll.id); - if(StrEqual(troll.name, "Reset User")) { +void AddMagnetFlags(TrollBuilder troll) { + PrintToServer("adding: %d", troll.Id); + troll.AddPrompt("Choose Magnet Chance:") + .AddOptionFloat("Always (100%)", true, 1.0) + .AddOptionFloat("Half Time (50%)", false, 0.5) + .AddOptionFloat("Rare (10%)", false, 0.1); +} + +TrollEffectResponse ApplyAffect(int victim, Troll troll, int activator, trollModifier modifier, int flags) { + bool toActive = troll.IsActive(victim); + char name[MAX_TROLL_NAME_LENGTH]; + troll.GetName(name, sizeof(name)); + if(StrEqual(name, "Reset User")) { LogAction(activator, victim, "\"%L\" reset all effects for \"%L\"", activator, victim); ShowActivityEx(activator, "[FTT] ", "reset effects for %N. ", victim); // for(int i = 0; i <= MAX_TROLLS; i++) { @@ -306,7 +319,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr // SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); ResetClient(victim, true); return TE_Error; // Not an error, but don't want to show activation - } else if(StrEqual(troll.name, "Slow Speed")) { + } else if(StrEqual(name, "Slow Speed")) { if(toActive) { float movement = 0.0; if(flags & 1) movement = 0.9; @@ -317,22 +330,22 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", movement); } else SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); - } else if(StrEqual(troll.name, "Higher Gravity")) + } else if(StrEqual(name, "Higher Gravity")) SetEntityGravity(victim, toActive ? 1.3 : 1.0); - else if(StrEqual(troll.name, "UziRules / AwpSmells")) { + else if(StrEqual(name, "UziRules / AwpSmells")) { DisableTroll(victim, "No Pickup"); DisableTroll(victim, "Primary Disable"); - } else if(StrEqual(troll.name, "Primary Disable")) { + } else if(StrEqual(name, "Primary Disable")) { DisableTroll(victim, "UziRules / AwpSmells"); DisableTroll(victim, "No Pickup"); SDKHook(victim, SDKHook_WeaponCanUse, Event_ItemPickup); - } else if(StrEqual(troll.name, "No Pickup")) { + } else if(StrEqual(name, "No Pickup")) { DisableTroll(victim, "UziRules / AwpSmells"); DisableTroll(victim, "Primary Disable"); SDKHook(victim, SDKHook_WeaponCanUse, Event_ItemPickup); - } else if(StrEqual(troll.name, "CameTooEarly")) { + } else if(StrEqual(name, "CameTooEarly")) { ReplyToCommand(activator, "This troll mode is not implemented."); - } else if(StrEqual(troll.name, "KillMeSoftly")) { + } else if(StrEqual(name, "KillMeSoftly")) { static char wpn[32]; GetClientWeaponName(victim, 4, wpn, sizeof(wpn)); if(StrEqual(wpn, "weapon_adrenaline") || StrEqual(wpn, "weapon_pain_pills")) { @@ -344,17 +357,17 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr } //TODO: Implement TrollMod_Constant return TE_Error; - } else if(StrEqual(troll.name, "Throw It All")) { + } else if(StrEqual(name, "Throw It All")) { if(modifier & TrollMod_Instant) { if(flags & 1) { // Hacky, just throw their kit ThrowItemToPlayer(victim, activator, 3); } else ThrowAllItems(victim); } - } else if(StrEqual(troll.name, "Swarm")) { + } else if(StrEqual(name, "Swarm")) { if(modifier & TrollMod_Instant) { L4D2_RunScript("RushVictim(GetPlayerFromUserID(%d), %d)", victim, 15000); } - } else if(StrEqual(troll.name, "Gun Jam")) { + } else if(StrEqual(name, "Gun Jam")) { int wpn = GetClientWeaponEntIndex(victim, 0); if(wpn > -1) SDKHook(wpn, SDKHook_Reload, Event_WeaponReload); @@ -362,25 +375,25 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr ReplyToCommand(activator, "Victim does not have a primary weapon."); return TE_Error; } - } else if(StrEqual(troll.name, "Vomit Player")) + } else if(StrEqual(name, "Vomit Player")) L4D_CTerrorPlayer_OnVomitedUpon(victim, victim); - else if(StrEqual(troll.name, "Insta Special")) { + else if(StrEqual(name, "Insta Special")) { int mode = 0; if(flags & 2) mode = 1; ShowInstaSpecialChooser(activator, GetClientUserId(victim), mode); return TE_Menu; - } else if(StrEqual(troll.name, "Goo")) { + } else if(StrEqual(name, "Goo")) { static float pos[3], ang[3]; GetClientAbsOrigin(victim, pos); GetClientAbsAngles(victim, ang); L4D2_SpitterPrj(victim, pos, ang); - } else if(StrEqual(troll.name, "Stagger")) { + } else if(StrEqual(name, "Stagger")) { L4D_StaggerPlayer(victim, victim, NULL_VECTOR); - } else if(StrEqual(troll.name, "Voice Mute")) { + } else if(StrEqual(name, "Voice Mute")) { BaseComm_SetClientMute(victim, toActive); - } else if(StrEqual(troll.name, "Spicy Gas")) { + } else if(StrEqual(name, "Spicy Gas")) { SDKHook(victim, SDKHook_WeaponCanUse, Event_ItemPickup); - } else if(StrEqual(troll.name, "Car Splat")) { + } else if(StrEqual(name, "Car Splat")) { if(flags & 1) { if(!SpawnCarOnPlayer(victim)) { ReplyToCommand(activator, "Could not find a suitable area to spawn a car. Requires vertical space above victim."); @@ -397,7 +410,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr return TE_Error; } } - } else if(StrEqual(troll.name, "Instant Commons")) { + } else if(StrEqual(name, "Instant Commons")) { if(modifier & TrollMod_Instant) { float pos[3]; GetHorizontalPositionFromClient(victim, flags & 1 ? -40.0 : 40.0, pos); @@ -407,7 +420,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr L4D2_RunScript("CommandABot({cmd=0,bot=EntIndexToHScript(%i),target=GetPlayerFromUserID(%i)})", c, victimId); } } - } else if(StrEqual(troll.name, "Randomize Clip Ammo")) { + } else if(StrEqual(name, "Randomize Clip Ammo")) { if(modifier & TrollMod_Instant) { int primaryWpn = GetPlayerWeaponSlot(victim, 0); if(primaryWpn > 0) { @@ -415,7 +428,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr SetEntProp(primaryWpn, Prop_Send, "m_iClip1", GetRandomInt(0, maxCap)); } } - } else if(StrEqual(troll.name, "Rock Dropper")) { + } else if(StrEqual(name, "Rock Dropper")) { float pos[3], dropPos[3]; GetClientEyePosition(victim, pos); dropPos = pos; @@ -428,7 +441,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr float ang[3]; ang[0] = 90.0; L4D_TankRockPrj(0, dropPos, ang); - } else if(StrEqual(troll.name, "Molotov Bath")) { + } else if(StrEqual(name, "Molotov Bath")) { int count = 1; if(flags & 2) count = 8; float pos[3], dropPos[3]; @@ -449,7 +462,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr for(int i = 0; i < count; i++) { L4D_MolotovPrj(victim, dropPos, vel); } - } else if(StrEqual(troll.name, "Dep Bots")) { + } else if(StrEqual(name, "Dep Bots")) { if(!toActive) { StopHealingBots(); return TE_Success; @@ -513,7 +526,7 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr wasSbFixEnabled = hSbFixEnabled.BoolValue; hSbFixEnabled.BoolValue = false; } - } else if(StrEqual(troll.name, "Smart Charge")) { + } else if(StrEqual(name, "Smart Charge")) { if(pdata[victim].smartChargeActivator > 0) { ReplyToCommand(activator, "Target already has smart charge enabled"); return TE_Error; @@ -526,32 +539,36 @@ TrollEffectResponse ApplyAffect(int victim, const Troll troll, int activator, tr pdata[victim].smartChargeMaxAttempts = timeout; pdata[victim].smartChargeActivator = GetClientUserId(activator); CreateTimer(1.0, Timer_CheckForChargerOpportunity, GetClientUserId(victim), TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - } else if(StrEqual(troll.name, "No Rushing Us")) { + } else if(StrEqual(name, "No Rushing Us")) { SetEntPropFloat(victim, Prop_Send, "m_flLaggedMovementValue", 1.0); - } else if(StrEqual(troll.name, "Hide HUD")) { + } else if(StrEqual(name, "Hide HUD")) { if(toActive) HideHUDRandom(victim); else SetEntProp(victim, Prop_Send, "m_iHideHUD", 0); - } else if(StrEqual(troll.name, "Meta: Random")) { - int rndTroll = GetRandomInt(0, MAX_TROLLS); + } else if(StrEqual(name, "Rewind")) { + if(modifier & TrollMod_Instant) { + RewindPlayer(victim); + } + } else if(StrEqual(name, "Meta: Random")) { + Troll rndTroll = Troll(GetRandomInt(0, MAX_TROLLS)); int rndFlags = 0; - int maxFlags = Trolls[rndTroll].GetFlagCount(); + int maxFlags = rndTroll.TotalOptionsCount; int numFlags = GetRandomInt(0, maxFlags); while(numFlags > 0) { // Apply a random flag rndFlags |= GetRandomInt(0, maxFlags) numFlags--; } - trollModifier rndMod = Trolls[rndTroll].GetDefaultMod(); - if(Trolls[rndTroll].HasMod(TrollMod_Constant) && GetURandomFloat() > 0.5) { + trollModifier rndMod = rndTroll.GetDefaultMod(); + if(rndTroll.HasMod(TrollMod_Constant) && GetURandomFloat() > 0.5) { rndMod = TrollMod_Instant; - } else if(Trolls[rndTroll].HasMod(TrollMod_Instant) && GetURandomFloat() > 0.5) { + } else if(rndTroll.HasMod(TrollMod_Instant) && GetURandomFloat() > 0.5) { rndMod = TrollMod_Constant; } - Trolls[rndTroll].Activate(victim, activator, rndMod, rndFlags); + rndTroll.Activate(victim, activator, rndMod, rndFlags); } else if(~modifier & TrollMod_Constant) { - PrintToServer("[FTT] Warn: Possibly invalid troll, no apply action defined for \"%s\"", troll.name); + PrintToServer("[FTT] Warn: Possibly invalid troll, no apply action defined for \"%s\"", name); #if defined DEBUG ReplyToCommand(activator, "[FTT/Debug] If nothing occurs, this troll possibly was not implemented correctly. "); #endif diff --git a/scripting/include/ftt.inc b/scripting/include/ftt.inc index 7242a69..6ba9cd2 100644 --- a/scripting/include/ftt.inc +++ b/scripting/include/ftt.inc @@ -11,6 +11,8 @@ enum L4D2Infected L4D2Infected_Tank = 8 }; +bool g_actionsAvailable; + GlobalForward g_PlayerMarkedForward; GlobalForward g_TrollAppliedForward; Handle g_hWitchAttack; diff --git a/scripting/l4d2_feedthetrolls.sp b/scripting/l4d2_feedthetrolls.sp index b4bea0b..92fd432 100644 --- a/scripting/l4d2_feedthetrolls.sp +++ b/scripting/l4d2_feedthetrolls.sp @@ -10,13 +10,16 @@ #include #include #include -#include #include +#include +#undef REQUIRE_PLUGIN #tryinclude +#undef REQUIRE_PLUGIN #tryinclude #include #include #include +#undef REQUIRE_PLUGIN #tryinclude public Plugin myinfo = @@ -32,7 +35,20 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("ApplyTroll", Native_ApplyTroll); return APLRes_Success; } - +public void OnLibraryAdded(const char[] name) { + if(StrEqual(name, "actionslib")) { + g_actionsAvailable = true; + } +} +public void OnLibraryRemoved(const char[] name) { + if(StrEqual(name, "actionslib")) { + g_actionsAvailable = false; + } +} +public void OnAllPluginsLoaded() { + g_actionsAvailable = LibraryExists("actionslib"); +} + public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); @@ -47,7 +63,6 @@ public void OnPluginStart() { // Load core things (trolls & phrases): REPLACEMENT_PHRASES = new StringMap(); - TYPOS_DICT = new StringMap(); LoadPhrases(); LoadTypos(); SetupTrolls(); @@ -102,9 +117,11 @@ public void OnPluginStart() { RegAdminCmd("sm_rff", Command_SetReverseFF, ADMFLAG_KICK, "Set reverse FF on player"); RegAdminCmd("sm_magnet", Command_SetMagnetShortcut, ADMFLAG_KICK, ""); RegAdminCmd("sm_csplat", Command_CarSplat, ADMFLAG_KICK, ""); + RegAdminCmd("sm_typo", Command_AddTypo, ADMFLAG_GENERIC); HookEvent("player_spawn", Event_PlayerSpawn); HookEvent("player_first_spawn", Event_PlayerFirstSpawn); + HookEvent("player_disconnect", Event_PlayerDisconnect); HookEvent("player_death", Event_PlayerDeath); HookEvent("triggered_car_alarm", Event_CarAlarm); HookEvent("witch_harasser_set", Event_WitchVictimSet); @@ -198,6 +215,7 @@ bool IsPlayerFarDistance(int client, float distance) { return client == farthestClient && difference > distance; } +#if defined _actions_included BehaviorAction CreateWitchAttackAction(int target = 0) { BehaviorAction action = ActionsManager.Allocate(18556); SDKCall(g_hWitchAttack, action, target); @@ -211,6 +229,7 @@ Action OnWitchActionUpdate(BehaviorAction action, int actor, float interval, Act result.SetReason("FTT"); return Plugin_Handled; } +#endif void HideHUD(int victim, float timeout = 0.0) { SetEntProp(victim, Prop_Send, "m_iHideHUD", 64);