diff --git a/README.md b/README.md index da52744..4c428a8 100644 --- a/README.md +++ b/README.md @@ -180,17 +180,27 @@ Troll Modes: (updated 1/2/2021) 11. **ThrowItAll** - Makes a player throw all their items at any nearby players. Runs on the interval set by sm_ftt_throw_interval. 12. **GunJam** - On reload, small chance their gun gets jammed - Can't reload. 13. **NoPickup** - Prevents a player from picking up ANY (new) item. Use ThrowItAll to make them drop -14. **Swarm** - Swarms a player with zombies. Requires my [swarm plugin](#l4d2_swarm) +14. **Swarm** - Swarms a player with zombies. Requires my [swarm plugin](#l4d2_swarm) +15. **Honk** – Replaces player's chat messages with honk +16. **Special Magnet** – Attracts ALL specials to the closest alive trolled target with this troll enabled +17. **Tank Magnet** – Attracts ALL tanks to the closest alive trolled target with this troll enabled +18. **No Shove** – Prevents player from shoving at a % chance +19. **Damage Boost** – Will make the player take 2x more damage than normal +20. **Temp Quick Drain** – Will make a player’s temp health drain very quickly +21. **Vomit Player** – Shortcut to sm_vomitplayer. Vomits the player. * **Convars:** * `sm_ftt_victims` - A comma separated list of troll targets. Unused while new version is being implemented * `sm_ftt_throw_interval` - For troll mode 'ThrowItAll' (#11), how often players will re-throw all their items. 0 to disable * `sm_ftt_autopunish_mode` - (Not used, WIP) Sets the modes that auto punish will activate for. 1 -> Early crescendo activations + * `sm_ftt_magnet_chance <0.0 - 1.0>` - % of the time that the magnet will work on a player." + * `sm_ftt_shove_fail_chance <0.0 - 1.0>` - The % chance that a shove fails * **Commands:** * `sm_fta ` - Applies a mode to a set of users. See list above * `sm_fta` - No arguments: Shows a menu, choose player, mode, and modifiers all in one. * `sm_ftr ` - Removes & deactivates all trolls. * `sm_ftl` - Lists all players that have a mode applied. + * `sm_ftm` - Lists all troll options & their descriptions ### l4d2_autobotcrown Makes any suitable bot (> 40 hp, has shotgun) automatically crown a witch. Supports multiple bots and witches, but only one bot can crown one witch at a time. Plugin is obviously disabled in realism, and is really on suitable for coop or versus. Even works with idle players. diff --git a/plugins/l4d2_feedthetrolls.smx b/plugins/l4d2_feedthetrolls.smx index f00161e..e9bf964 100644 Binary files a/plugins/l4d2_feedthetrolls.smx and b/plugins/l4d2_feedthetrolls.smx differ diff --git a/scripting/l4d2_feedthetrolls.sp b/scripting/l4d2_feedthetrolls.sp index 72d5784..48a7f8c 100644 --- a/scripting/l4d2_feedthetrolls.sp +++ b/scripting/l4d2_feedthetrolls.sp @@ -11,6 +11,7 @@ #include #include "jutils.inc" #include +#include #undef REQUIRE_PLUGIN #include @@ -29,7 +30,7 @@ public Plugin myinfo = //HANDLES Handle hThrowTimer; //CONVARS -ConVar hVictimsList, hThrowItemInterval, hAutoPunish; +ConVar hVictimsList, hThrowItemInterval, hAutoPunish, hMagnetChance, hShoveFailChance; //BOOLS bool lateLoaded; //Is plugin late loaded bool bChooseVictimAvailable = false; //For charge player feature, is it available? @@ -57,18 +58,19 @@ public void OnPluginStart() { LoadTranslations("common.phrases"); g_iAmmoTable = FindSendPropInfo("CTerrorPlayer", "m_iAmmo"); - hVictimsList = CreateConVar("sm_ftt_victims", "", "Comma seperated list of steamid64 targets (ex: STEAM_0:0:75141700)", FCVAR_NONE); - hVictimsList.AddChangeHook(Change_VictimList); hThrowItemInterval = CreateConVar("sm_ftt_throw_interval", "30", "The interval in seconds to throw items. 0 to disable", FCVAR_NONE, true, 0.0); hThrowItemInterval.AddChangeHook(Change_ThrowInterval); - hAutoPunish = CreateConVar("sm_ftt_autopunish_mode", "0", "Setup automatic punishment of players. Add bits together. 0: Disabled, 1: Early Crescendos", FCVAR_NONE, true, 0.0); + hAutoPunish = CreateConVar("sm_ftt_autopunish_action", "0", "Setup automatic punishment of players. Add bits together\n0=Disabled, 1=Tank magnet, 2=Special magnet, 4=Swarm", FCVAR_NONE, true, 0.0); + hMagnetChance = CreateConVar("sm_ftt_magnet_chance", "1.0", "% of the time that the magnet will work on a player.", FCVAR_NONE, true, 0.0, true, 1.0); + hShoveFailChance = CreateConVar("sm_ftt_shove_fail_chance", "0.5", "The % chance that a shove fails", FCVAR_NONE, true, 0.0, true, 1.0); - RegAdminCmd("sm_ftl", Command_ListTheTrolls, ADMFLAG_ROOT, "Lists all the trolls currently ingame."); - RegAdminCmd("sm_ftm", Command_ListModes, ADMFLAG_ROOT, "Lists all the troll modes and their description"); - RegAdminCmd("sm_ftr", Command_ResetUser, ADMFLAG_ROOT, "Resets user of any troll effects."); - RegAdminCmd("sm_fta", Command_ApplyUser, ADMFLAG_ROOT, "Apply a troll mod to a player, or shows menu if no parameters."); + RegAdminCmd("sm_ftl", Command_ListTheTrolls, ADMFLAG_KICK, "Lists all the trolls currently ingame."); + RegAdminCmd("sm_ftm", Command_ListModes, ADMFLAG_KICK, "Lists all the troll modes and their description"); + RegAdminCmd("sm_ftr", Command_ResetUser, ADMFLAG_KICK, "Resets user of any troll effects."); + RegAdminCmd("sm_fta", Command_ApplyUser, ADMFLAG_KICK, "Apply a troll mod to a player, or shows menu if no parameters."); HookEvent("player_disconnect", Event_PlayerDisconnect); + HookEvent("player_death", Event_PlayerDeath); AutoExecConfig(true, "l4d2_feedthetrolls"); @@ -77,6 +79,11 @@ public void OnPluginStart() { HookEntityOutput("func_button", "OnPressed", Event_ButtonPress); } } + +/////////////////////////////////////////////////////////////////////////////// +// EVENTS +/////////////////////////////////////////////////////////////////////////////// + public void OnPluginEnd() { UnhookEntityOutput("func_button", "OnPressed", Event_ButtonPress); } @@ -86,216 +93,29 @@ public void OnMapEnd() { public void OnMapStart() { HookEntityOutput("func_button", "OnPressed", Event_ButtonPress); CreateTimer(MAIN_TIMER_INTERVAL_S, Timer_Main, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + //CreateTimer(30.0, Timer_AutoPunishCheck, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); } public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); g_iTrollUsers[client] = 0; + g_iAttackerTarget[client] = 0; } -public void OnClientAuthorized(int client, const char[] auth) { - if(StrContains(auth, "BOT", true) == -1) { - TestForTarget(client, auth); - } +public Action Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + g_iAttackerTarget[client] = 0; } -// #region evrnts -public void Change_VictimList(ConVar convar, const char[] oldValue, const char[] newValue) { -} -public void Change_ThrowInterval(ConVar convar, const char[] oldValue, const char[] newValue) { - //If a throw timer exists (someone has mode 11), destroy & recreate w/ new interval - if(hThrowTimer != INVALID_HANDLE) { - delete hThrowTimer; - PrintToServer("Reset new throw item timer"); - hThrowTimer = CreateTimer(convar.FloatValue, Timer_ThrowTimer, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - } -} -// #endregion -// #region commands -public Action Command_ResetUser(int client, int args) { - if(args < 1) { - ReplyToCommand(client, "Usage: sm_ftr "); - }else{ - char arg1[32]; - GetCmdArg(1, arg1, sizeof(arg1)); - char target_name[MAX_TARGET_LENGTH]; - int target_list[MAXPLAYERS], target_count; - bool tn_is_ml; - if ((target_count = ProcessTargetString( - arg1, - client, - target_list, - MAXPLAYERS, - COMMAND_FILTER_ALIVE, /* Only allow alive players */ - target_name, - sizeof(target_name), - tn_is_ml)) <= 0) - { - /* This function replies to the admin with a failure message */ - ReplyToTargetError(client, target_count); - return Plugin_Handled; - } - for (int i = 0; i < target_count; i++) - { - if(IsClientConnected(target_list[i]) && IsClientInGame(target_list[i]) && IsPlayerAlive(target_list[i]) && GetClientTeam(target_list[i]) == 2) { - ResetClient(target_list[i], true); - ShowActivity(client, "reset troll effects on \"%N\". ", target_list[i]); - } +public Action Event_WeaponReload(int weapon) { + int client = GetEntPropEnt(weapon, Prop_Send, "m_hOwner"); + if(HasTrollMode(client,Troll_GunJam)) { + float dec = GetRandomFloat(0.0, 1.0); + if(FloatCompare(dec, 0.50) == -1) { //10% chance gun jams + return Plugin_Stop; } } - return Plugin_Handled; -} -public Action Command_ApplyUser(int client, int args) { - if(args < 2) { - Menu menu = new Menu(ChoosePlayerHandler); - menu.SetTitle("Choose a player"); - for(int i = 1; i < MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { - char userid[8], display[16]; - Format(userid, sizeof(userid), "%d", GetClientUserId(i)); - GetClientName(i, display, sizeof(display)); - menu.AddItem(userid, display); - } - } - menu.ExitButton = true; - menu.Display(client, 0); - }else{ - char arg1[32], arg2[32]; - GetCmdArg(1, arg1, sizeof(arg1)); - GetCmdArg(2, arg2, sizeof(arg2)); - - int mode = StringToInt(arg2); - if(mode == 0) { - ReplyToCommand(client, "Not a valid mode. Must be greater than 0. Usage: sm_fta . Use sm_ftr to reset."); - }else{ - char target_name[MAX_TARGET_LENGTH]; - int target_list[MAXPLAYERS], target_count; - bool tn_is_ml; - if ((target_count = ProcessTargetString( - arg1, - client, - target_list, - MAXPLAYERS, - COMMAND_FILTER_ALIVE, /* Only allow alive players */ - target_name, - sizeof(target_name), - tn_is_ml)) <= 0) - { - /* This function replies to the admin with a failure message */ - ReplyToTargetError(client, target_count); - return Plugin_Handled; - } - for (int i = 0; i < target_count; i++) - { - if(GetClientTeam(target_list[i]) == 2) - ApplyModeToClient(client, target_list[i], view_as(mode), TrollMod_None); - } - } - } - return Plugin_Handled; -} -public Action Command_ListModes(int client, int args) { - for(int mode = 0; mode < TROLL_MODE_COUNT; mode++) { - ReplyToCommand(client, "%d. %s - %s", mode, TROLL_MODES_NAMES[mode], TROLL_MODES_DESCRIPTIONS[mode]); - } - return Plugin_Handled; -} -public Action Command_ListTheTrolls(int client, int args) { - int count = 0; - for(int i = 1; i < MaxClients; i++) { - if(IsClientConnected(i) && IsPlayerAlive(i) && g_iTrollUsers[i] > 0) { - int modes = g_iTrollUsers[i], modeCount = 0; - char modeListArr[TROLL_MODE_COUNT][32]; - for(int mode = 1; mode < TROLL_MODE_COUNT; mode++) { - //If troll mode exists: - bool hasTrollMode = HasTrollMode(i, view_as(mode)); - PrintToConsole(i, "[%d]: #%d %s value: %b", modes, mode, TROLL_MODES_NAMES[mode], hasTrollMode); - if(hasTrollMode) { - modeListArr[modeCount] = TROLL_MODES_NAMES[mode]; - modeCount++; - } - } - char modeList[255]; - ImplodeStrings(modeListArr, modeCount, ", ", modeList, sizeof(modeList)); - ReplyToCommand(client, "%N | %d | %s", i, modes, modeList); - count++; - } - } - if(count == 0) { - ReplyToCommand(client, "No clients have a mode applied."); - } - return Plugin_Handled; -} -public int ChoosePlayerHandler(Menu menu, MenuAction action, int param1, int param2) { - /* If an option was selected, tell the client about the item. */ - if (action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, sizeof(info)); - int userid = StringToInt(info); - - Menu trollMenu = new Menu(ChooseModeMenuHandler); - trollMenu.SetTitle("Choose a troll mode"); - for(int i = 0; i < TROLL_MODE_COUNT; i++) { - char id[8]; - Format(id, sizeof(id), "%d|%d", userid, i); - trollMenu.AddItem(id, TROLL_MODES_NAMES[i]); - } - trollMenu.ExitButton = true; - trollMenu.Display(param1, 0); - } else if (action == MenuAction_End) - delete menu; -} -public int ChooseModeMenuHandler(Menu menu, MenuAction action, int param1, int param2) { - /* If an option was selected, tell the client about the item. */ - if (action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, sizeof(info)); - char str[2][8]; - ExplodeString(info, "|", str, 2, 8, false); - int userid = StringToInt(str[0]); - int client = GetClientOfUserId(userid); - trollMode mode = view_as(StringToInt(str[1])); - //If mode has an option to be single-time fired/continous/both, prompt: - if(mode == Troll_Clumsy - || mode ==Troll_ThrowItAll - || mode == Troll_PrimaryDisable - || mode == Troll_CameTooEarly - || mode == Troll_Swarm - ) { - Menu modiferMenu = new Menu(ChooseTrollModiferHandler); - modiferMenu.SetTitle("Choose Troll Modifer Option"); - char singleUse[16], multiUse[16], bothUse[16]; - Format(singleUse, sizeof(singleUse), "%d|%d|1", userid, mode); - Format(multiUse, sizeof(multiUse), "%d|%d|2", userid, mode); - Format(bothUse, sizeof(bothUse), "%d|%d|3", userid, mode); - modiferMenu.AddItem(singleUse, "Activate once"); - modiferMenu.AddItem(multiUse, "Activate Periodically"); - modiferMenu.AddItem(bothUse, "Activate Periodically & Instantly"); - modiferMenu.ExitButton = true; - modiferMenu.Display(param1, 0); - } else { - ApplyModeToClient(param1, client, mode, TrollMod_None); - } - } else if (action == MenuAction_End) - delete menu; -} -public int ChooseTrollModiferHandler(Menu menu, MenuAction action, int param1, int param2) { - if (action == MenuAction_Select) { - char info[16]; - menu.GetItem(param2, info, sizeof(info)); - char str[3][8]; - ExplodeString(info, "|", str, 3, 8, false); - int client = GetClientOfUserId(StringToInt(str[0])); - trollMode mode = view_as(StringToInt(str[1])); - int modifier = StringToInt(str[2]); - if(modifier == 2 || modifier == 3) - ApplyModeToClient(param1, client, mode, TrollMod_Repeat); - else - ApplyModeToClient(param1, client, mode, TrollMod_InstantFire); - } else if (action == MenuAction_End) - delete menu; + return Plugin_Continue; } public Action Event_ButtonPress(const char[] output, int entity, int client, float delay) { - PrintToServer("Client %N pressed a func_button", client); - PrintToConsoleAll("Client %N pressed a func_button", client); - if(hAutoPunish.IntValue & 1 > 0) { + if(client > 0 && client <= MaxClients && hAutoPunish.IntValue & 1 > 0) { float closestDistance = -1.0, cPos[3], scanPos[3]; GetClientAbsOrigin(client, cPos); @@ -318,9 +138,64 @@ public Action Event_ButtonPress(const char[] output, int entity, int client, flo } return Plugin_Continue; } +public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { + // ========================= + // OVERRIDE VICTIM + // ========================= + if(hMagnetChance.FloatValue < GetRandomFloat()) return Plugin_Continue; + L4D2Infected class = view_as(GetEntProp(attacker, Prop_Send, "m_zombieClass")); + if(class != L4D2Infected_Tank) { + int existingTarget = GetClientOfUserId(g_iAttackerTarget[attacker]); + if(existingTarget > 0 && IsClientInGame(existingTarget) && IsPlayerAlive(existingTarget)) { + curTarget = existingTarget; + return Plugin_Changed; + } + + float closestDistance, survPos[3], spPos[3]; + GetClientAbsOrigin(attacker, spPos); + int closestClient = -1; + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { + if(class == L4D2Infected_Tank && HasTrollMode(i, Troll_TankMagnet) || (class != L4D2Infected_Tank && HasTrollMode(i, Troll_SpecialMagnet))) { + GetClientAbsOrigin(i, survPos); + float dist = GetVectorDistance(survPos, spPos, true); + if(closestClient == -1 || dist < closestDistance) { + closestDistance = dist; + closestClient = i; + } + } + } + } + + if(closestClient > 0) { + g_iAttackerTarget[attacker] = GetClientUserId(closestClient); + curTarget = closestClient; + return Plugin_Changed; + } + } + return Plugin_Continue; +} +public Action L4D2_OnEntityShoved(int client, int entity, int weapon, float vecDir[3], bool bIsHighPounce) { + if(client > 0 && client <= MaxClients && HasTrollMode(client, Troll_NoShove) && hShoveFailChance.FloatValue > GetRandomFloat()) { + return Plugin_Handled; + } + return Plugin_Continue; +} public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) { - if(HasTrollMode(client, Troll_iCantSpellNoMore)) { - int type = GetRandomInt(1, 24); + if(HasTrollMode(client, Troll_Honk)) { + char strings[32][7]; + int words = ExplodeString(sArgs, " ", strings, sizeof(strings), 5); + for(int i = 0; i < words; i++) { + if(GetRandomFloat() <= 0.8) strings[i] = "honk"; + else strings[i] = "squeak"; + } + int length = 7 * words; + char[] message = new char[length]; + ImplodeStrings(strings, 32, " ", message, length); + CPrintToChatAll("{blue}%N {default}: %s", client, message); + return Plugin_Handled; + }else if(HasTrollMode(client, Troll_iCantSpellNoMore)) { + int type = GetRandomInt(1, 33); char letterSrc, replaceChar; switch(type) { case 1: { @@ -371,6 +246,10 @@ public Action OnClientSayCommand(int client, const char[] command, const char[] letterSrc = 'o'; replaceChar = 'i'; } + case 13: { + letterSrc = 'e'; + replaceChar = 'r'; + } default: return Plugin_Continue; @@ -440,21 +319,225 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 } return Plugin_Continue; } +public Action Event_TakeDamage(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { + if(attacker > 0 && attacker <= MaxClients && HasTrollMode(attacker, Troll_DamageBoost)) { + damage * 2; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CVAR CHANGES +/////////////////////////////////////////////////////////////////////////////// + +public void Change_ThrowInterval(ConVar convar, const char[] oldValue, const char[] newValue) { + //If a throw timer exists (someone has mode 11), destroy & recreate w/ new interval + if(hThrowTimer != INVALID_HANDLE) { + delete hThrowTimer; + PrintToServer("Reset new throw item timer"); + hThrowTimer = CreateTimer(convar.FloatValue, Timer_ThrowTimer, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// COMMANDS +/////////////////////////////////////////////////////////////////////////////// + +public Action Command_ResetUser(int client, int args) { + if(args < 1) { + ReplyToCommand(client, "Usage: sm_ftr "); + }else{ + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg1, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, /* Only allow alive players */ + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + /* This function replies to the admin with a failure message */ + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + for (int i = 0; i < target_count; i++) + { + if(IsClientConnected(target_list[i]) && IsClientInGame(target_list[i]) && IsPlayerAlive(target_list[i])) { + ResetClient(target_list[i], true); + ShowActivity(client, "reset troll effects on \"%N\". ", target_list[i]); + } + } + } + return Plugin_Handled; +} +public Action Command_ApplyUser(int client, int args) { + if(args < 2) { + Menu menu = new Menu(ChoosePlayerHandler); + menu.SetTitle("Choose a player"); + for(int i = 1; i < MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { + char userid[8], display[16]; + Format(userid, sizeof(userid), "%d", GetClientUserId(i)); + GetClientName(i, display, sizeof(display)); + menu.AddItem(userid, display); + } + } + menu.ExitButton = true; + menu.Display(client, 0); + }else{ + char arg1[32], arg2[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + GetCmdArg(2, arg2, sizeof(arg2)); + + int mode = StringToInt(arg2); + if(mode == 0) { + ReplyToCommand(client, "Not a valid mode. Must be greater than 0. Usage: sm_fta . Use sm_ftr to reset."); + }else{ + char target_name[MAX_TARGET_LENGTH]; + int target_list[MAXPLAYERS], target_count; + bool tn_is_ml; + if ((target_count = ProcessTargetString( + arg1, + client, + target_list, + MAXPLAYERS, + COMMAND_FILTER_ALIVE, /* Only allow alive players */ + target_name, + sizeof(target_name), + tn_is_ml)) <= 0) + { + /* This function replies to the admin with a failure message */ + ReplyToTargetError(client, target_count); + return Plugin_Handled; + } + for (int i = 0; i < target_count; i++) + { + if(IsClientConnected(target_list[i]) && IsClientInGame(target_list[i]) && GetClientTeam(target_list[i]) == 2) + ApplyModeToClient(client, target_list[i], view_as(mode), TrollMod_None); + } + } + } + return Plugin_Handled; +} +public Action Command_ListModes(int client, int args) { + for(int mode = 0; mode < TROLL_MODE_COUNT; mode++) { + ReplyToCommand(client, "%d. %s - %s", mode, TROLL_MODES_NAMES[mode], TROLL_MODES_DESCRIPTIONS[mode]); + } + return Plugin_Handled; +} +public Action Command_ListTheTrolls(int client, int args) { + int count = 0; + for(int i = 1; i < MaxClients; i++) { + if(IsClientConnected(i) && IsPlayerAlive(i) && g_iTrollUsers[i] > 0) { + int modes = g_iTrollUsers[i], modeCount = 0; + char modeListArr[TROLL_MODE_COUNT][32]; + for(int mode = 1; mode < TROLL_MODE_COUNT; mode++) { + //If troll mode exists: + bool hasTrollMode = HasTrollMode(i, view_as(mode)); + PrintToConsole(i, "[%d]: #%d %s value: %b", modes, mode, TROLL_MODES_NAMES[mode], hasTrollMode); + if(hasTrollMode) { + modeListArr[modeCount] = TROLL_MODES_NAMES[mode]; + modeCount++; + } + } + char modeList[255]; + ImplodeStrings(modeListArr, modeCount, ", ", modeList, sizeof(modeList)); + ReplyToCommand(client, "%N | %d | %s", i, modes, modeList); + count++; + } + } + if(count == 0) { + ReplyToCommand(client, "No clients have a mode applied."); + } + return Plugin_Handled; +} + +/////////////////////////////////////////////////////////////////////////////// +// MENU HANDLER +/////////////////////////////////////////////////////////////////////////////// + +public int ChoosePlayerHandler(Menu menu, MenuAction action, int param1, int param2) { + /* If an option was selected, tell the client about the item. */ + if (action == MenuAction_Select) { + char info[16]; + menu.GetItem(param2, info, sizeof(info)); + int userid = StringToInt(info); + + Menu trollMenu = new Menu(ChooseModeMenuHandler); + trollMenu.SetTitle("Choose a troll mode"); + for(int i = 0; i < TROLL_MODE_COUNT; i++) { + char id[8]; + Format(id, sizeof(id), "%d|%d", userid, i); + trollMenu.AddItem(id, TROLL_MODES_NAMES[i]); + } + trollMenu.ExitButton = true; + trollMenu.Display(param1, 0); + } else if (action == MenuAction_End) + delete menu; +} +public int ChooseModeMenuHandler(Menu menu, MenuAction action, int param1, int param2) { + /* If an option was selected, tell the client about the item. */ + if (action == MenuAction_Select) { + char info[16]; + menu.GetItem(param2, info, sizeof(info)); + char str[2][8]; + ExplodeString(info, "|", str, 2, 8, false); + int userid = StringToInt(str[0]); + int client = GetClientOfUserId(userid); + trollMode mode = view_as(StringToInt(str[1])); + //If mode has an option to be single-time fired/continous/both, prompt: + if(mode == Troll_Clumsy + || mode ==Troll_ThrowItAll + || mode == Troll_PrimaryDisable + || mode == Troll_CameTooEarly + || mode == Troll_Swarm + ) { + Menu modiferMenu = new Menu(ChooseTrollModiferHandler); + modiferMenu.SetTitle("Choose Troll Modifer Option"); + char singleUse[16], multiUse[16], bothUse[16]; + Format(singleUse, sizeof(singleUse), "%d|%d|1", userid, mode); + Format(multiUse, sizeof(multiUse), "%d|%d|2", userid, mode); + Format(bothUse, sizeof(bothUse), "%d|%d|3", userid, mode); + modiferMenu.AddItem(singleUse, "Activate once"); + modiferMenu.AddItem(multiUse, "Activate Periodically"); + modiferMenu.AddItem(bothUse, "Activate Periodically & Instantly"); + modiferMenu.ExitButton = true; + modiferMenu.Display(param1, 0); + } else { + ApplyModeToClient(param1, client, mode, TrollMod_None); + } + } else if (action == MenuAction_End) + delete menu; +} +public int ChooseTrollModiferHandler(Menu menu, MenuAction action, int param1, int param2) { + if (action == MenuAction_Select) { + char info[16]; + menu.GetItem(param2, info, sizeof(info)); + char str[3][8]; + ExplodeString(info, "|", str, 3, 8, false); + int client = GetClientOfUserId(StringToInt(str[0])); + trollMode mode = view_as(StringToInt(str[1])); + int modifier = StringToInt(str[2]); + if(modifier == 2 || modifier == 3) + ApplyModeToClient(param1, client, mode, TrollMod_Repeat); + else + ApplyModeToClient(param1, client, mode, TrollMod_InstantFire); + } else if (action == MenuAction_End) + delete menu; +} + public void StopItemGive(int client) { g_bPendingItemGive[client] = false; } -public Action Event_WeaponReload(int weapon) { - int client = GetEntPropEnt(weapon, Prop_Send, "m_hOwner"); - if(HasTrollMode(client,Troll_GunJam)) { - float dec = GetRandomFloat(0.0, 1.0); - if(FloatCompare(dec, 0.50) == -1) { //10% chance gun jams - return Plugin_Stop; - } - } - return Plugin_Continue; -} -// #endregion -// #region timer +/////////////////////////////////////////////////////////////////////////////// +// TIMERS +/////////////////////////////////////////////////////////////////////////////// + public Action Timer_ThrowTimer(Handle timer) { int count = 0; for(int i = 1; i < MaxClients; i++) { @@ -467,16 +550,26 @@ public Action Timer_ThrowTimer(Handle timer) { } public Action Timer_Main(Handle timer) { static int loop; - for(int i = 1; i < MaxClients; i++) { + for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i)) { - switch(g_iTrollUsers[i]) { - case Troll_SlowDrain: - if(loop % 4 == 0) { - int hp = GetClientHealth(i); - if(hp > 50) { - SetEntProp(i, Prop_Send, "m_iHealth", hp - 1); - } + if(HasTrollMode(i, Troll_SlowDrain)) { + if(loop % 4 == 0) { + int hp = GetClientHealth(i); + if(hp > 50) { + SetEntProp(i, Prop_Send, "m_iHealth", hp - 1); } + } + }else if(HasTrollMode(i, Troll_TempHealthQuickDrain)) { + if(loop % 2 == 0) { + float bufferTime = GetEntPropFloat(i, Prop_Send, "m_healthBufferTime"); + float buffer = GetEntPropFloat(i, Prop_Send, "m_healthBuffer"); + float tempHealth = GetTempHealth(i); + if(tempHealth > 0.0) { + PrintToConsole(i, "%f | %f %f", tempHealth, buffer, bufferTime); + //SetEntPropFloat(i, Prop_Send, "m_healthBuffer", buffer - 10.0); + SetEntPropFloat(i, Prop_Send, "m_healthBufferTime", bufferTime - 7.0); + } + } } } } @@ -486,7 +579,48 @@ public Action Timer_Main(Handle timer) { return Plugin_Continue; } +public Action Timer_AutoPunishCheck(Handle timer) { + int punished = GetClientOfUserId(autoPunished); + if(punished > 0) { + PrintToConsoleAll("Auto Punish | Player=%N", punished); + //Check flow distance, drop if needed. + return Plugin_Continue; + } + int farthestClient = -1, secondClient = -1; + float highestFlow, secondHighestFlow; + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { + float flow = L4D2Direct_GetFlowDistance(i); + if(flow > highestFlow || farthestClient == -1) { + secondHighestFlow = highestFlow; + secondClient = farthestClient; + farthestClient = i; + highestFlow = flow; + } + } + } + //Incase the first player checked is the farthest: + if(secondClient == -1) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2) { + float flow = L4D2Direct_GetFlowDistance(i); + if(farthestClient != i && ((flow < highestFlow && flow > secondHighestFlow) || secondClient == -1)) { + secondClient = i; + secondHighestFlow = flow; + } + } + } + } + float difference = highestFlow - secondHighestFlow; + PrintToConsoleAll("Flow Check | Player=%N Flow=%f Delta=%f", farthestClient, highestFlow, difference); + PrintToConsoleAll("Flow Check | Player2=%N Flow2=%f", secondClient, secondHighestFlow); + if(difference > AUTOPUNISH_FLOW_MIN_DISTANCE) { + autoPunished = GetClientUserId(farthestClient); + autoPunishMode = GetAutoPunishMode(); + } + return Plugin_Continue; +} public Action Timer_GivePistol(Handle timer, int client) { int flags = GetCommandFlags("give"); @@ -494,6 +628,7 @@ public Action Timer_GivePistol(Handle timer, int client) { FakeClientCommand(client, "give pistol"); SetCommandFlags("give", flags|FCVAR_CHEAT); } + public Action Timer_ThrowWeapon(Handle timer, Handle pack) { ResetPack(pack); float dest[3]; @@ -520,6 +655,9 @@ public Action Timer_ThrowWeapon(Handle timer, Handle pack) { } } +/////////////////////////////////////////////////////////////////////////////// +// METHODS +/////////////////////////////////////////////////////////////////////////////// void ThrowAllItems(int victim) { float vicPos[3], destPos[3]; @@ -548,23 +686,15 @@ void ThrowAllItems(int victim) { } } - -bool TestForTarget(int client, const char[] auth) { - char targets[32][8]; - char raw_targets[64]; - hVictimsList.GetString(raw_targets, sizeof(raw_targets)); - ExplodeString(raw_targets, ",", targets, 8, 32, false); - for(int i = 0; i < 8; i++) { - if(StrEqual(targets[i], auth, true)) { - #if defined debug - PrintToServer("[Debug] Troll target detected with id %d and steamid %s", client, auth); - #endif - return true; - } +int GetAutoPunishMode() { + int number = 2 ^ GetRandomInt(0, AUTOPUNISH_MODE_COUNT - 1); + if(hAutoPunish.IntValue & number == 0) { + return GetAutoPunishMode(); + }else{ + return number; } - return false; } -// #endregion + stock int GetPrimaryReserveAmmo(int client) { int weapon = GetPlayerWeaponSlot(client, 0); if(weapon > -1) { @@ -585,7 +715,6 @@ stock bool SetPrimaryReserveAmmo(int client, int amount) { } } - stock void SendChatToAll(int client, const char[] message) { char nameBuf[MAX_NAME_LENGTH]; @@ -598,4 +727,36 @@ stock void SendChatToAll(int client, const char[] message) { FormatActivitySource(client, i, nameBuf, sizeof(nameBuf)); PrintToChat(i, "\x03 %s : \x01%s", nameBuf, message); } +} +stock float GetTempHealth(int client) { + //First filter -> Must be a valid client, successfully in-game and not an spectator (The dont have health). + if(!client || !IsValidEntity(client) || !IsClientInGame(client)|| !IsPlayerAlive(client) || IsClientObserver(client)) { + return -1.0; + } + + //If the client is not on the survivors team, then just return the normal client health. + if(GetClientTeam(client) != 2) { + return 0.0; + } + + //First, we get the amount of temporal health the client has + float buffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer"); + + //In case the buffer is 0 or less, we set the temporal health as 0, because the client has not used any pills or adrenaline yet + if(buffer > 0.0) { + //This is the difference between the time we used the temporal item, and the current time + float difference = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); + + //We get the decay rate from this convar (Note: Adrenaline uses this value) + float decay = GetConVarFloat(FindConVar("pain_pills_decay_rate")); + + //This is a constant we create to determine the amount of health. This is the amount of time it has to pass + //before 1 Temporal HP is consumed. + float constant = 1.0 / decay; + + //Then we do the calcs + return buffer - (difference / constant); + }else{ + return 0.0; + } } \ No newline at end of file