diff --git a/README.md b/README.md index 4f6d539..9a01fc0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ All my sourcemod plugins... shitty probably * [L4D2Tools](#L4D2Tools) * [l4d2_swarm](#l4d2_swarm) * [l4d2_feedthetrolls](#l4d2_feedthetrolls) +* [l4d2_autobotcrown](#l4d2_autobotcrown) ## Descriptions ### csgo-knifehp @@ -176,4 +177,7 @@ Troll Modes: (updated 1/2/2021) * `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. \ No newline at end of file + * `sm_ftl` - Lists all players that have a mode applied. + +### l4d2_autobotcrown +Makes any suitable bot (> 40 hp, has shotgun) automatically crown a witch. Supports multiple bots, but only one bot can crown one witch at a time. \ No newline at end of file diff --git a/plugins/l4d2_autobotcrown.smx b/plugins/l4d2_autobotcrown.smx new file mode 100644 index 0000000..0b3edac Binary files /dev/null and b/plugins/l4d2_autobotcrown.smx differ diff --git a/scripting/l4d2_autobotcrown.sp b/scripting/l4d2_autobotcrown.sp new file mode 100644 index 0000000..fd93d27 --- /dev/null +++ b/scripting/l4d2_autobotcrown.sp @@ -0,0 +1,185 @@ +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0" + +#include +#include +#include +#include "jutils.inc" + +public Plugin myinfo = +{ + name = "L4D2 Auto Bot Witch Crown", + author = "jackzmc", + description = "Makes bots automatically crown a witch in cases where a witch is blocking the way", + version = PLUGIN_VERSION, + url = "" +}; + + +static ArrayList WitchList; +static Handle timer = INVALID_HANDLE; +static bool lateLoaded = false, AutoCrownInPosition = false; +static int AutoCrownBot = -1, AutoCrownTarget; +static float CrownPos[3], CrownAng[3]; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + if(late) lateLoaded = true; +} + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + WitchList = new ArrayList(1, 1); + if(lateLoaded) { + char classname[32]; + for(int i = MaxClients; i < 2048; i++) { + if(IsValidEntity(i)) { + GetEntityClassname(i, classname, sizeof(classname)); + if(StrEqual(classname, "witch", false)) { + WitchList.Push(i); + if(timer == INVALID_HANDLE) { + timer = CreateTimer(0.4, Timer_BotControlTimer, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } + } + } + } + } + + HookEvent("witch_spawn", Event_WitchSpawn); + HookEvent("witch_killed", Event_WitchKilled); + +} + + +public Action Event_WitchSpawn(Event event, const char[] name, bool dontBroadcast) { + int witchID = event.GetInt("witchid"); + WitchList.Push(witchID); + #if defined DEBUG + PrintToServer("Witch spawned: %d", witchID); + #endif + if(timer == INVALID_HANDLE) { + timer = CreateTimer(0.4, Timer_BotControlTimer, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + } +} + +public Action Event_WitchKilled(Event event, const char[] name, bool dontBroadcast) { + int witchID = event.GetInt("witchid"); + int index = FindValueInArray(WitchList, witchID); + #if defined DEBUG + PrintToServer("Witched killed: %d", witchID); + #endif + if(index > -1) { + RemoveFromArray(WitchList, index); + } + if(WitchList.Length == 0) { + CloseHandle(timer); + } + if(AutoCrownTarget == witchID) { + ResetAutoCrown(); + #if defined DEBUG + PrintToServer("AutoCrownTarget has died"); + #endif + } +} + +public Action Timer_BotControlTimer(Handle hdl) { + float botPosition[3], witchPos[3]; + if(WitchList.Length == 0) { + #if defined DEBUG + PrintToServer("No witches detected, ending timer"); + #endif + return Plugin_Stop; + } + //TODO: Also check if startled and cancel it immediately. + if(AutoCrownBot > -1) { + GetEntPropVector(AutoCrownTarget, Prop_Send, "m_vecOrigin", witchPos); + GetClientAbsOrigin(AutoCrownBot, botPosition); + + if(!IsValidEntity(AutoCrownTarget)) { + ResetAutoCrown(); + + #if defined DEBUG + PrintToServer("Could not find valid AutoCrownTarget"); + #endif + }else if(!IsClientConnected(AutoCrownBot) || !IsPlayerAlive(AutoCrownBot)) { + AutoCrownBot = -1; + AutoCrownTarget = -1; + #if defined DEBUG + PrintToServer("Could not find valid AutoCrownBot"); + #endif + } + + float distance = GetVectorDistance(botPosition, witchPos); + if(distance <= 60) { + float botAngles[3]; + GetClientAbsAngles(AutoCrownBot, botAngles); + botAngles[0] = 60.0; + botAngles[1] = RadToDeg(ArcTangent2( botPosition[1] - witchPos[1], botPosition[0] - witchPos[0])) + 180.0; + //Is In Position + + ClientCommand(AutoCrownBot, "slot0"); + TeleportEntity(AutoCrownBot, NULL_VECTOR, botAngles, NULL_VECTOR); + AutoCrownInPosition = true; + }else{ + L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(AutoCrownBot), witchPos[0], witchPos[1], witchPos[2]); + } + return Plugin_Continue; + } + for(int bot = 1; bot < MaxClients+1; bot++) { + if(IsClientConnected(bot) && IsClientInGame(bot) && IsFakeClient(bot) && IsPlayerAlive(bot)) { + //Check if bot has a valid shotgun, with ammo (probably can skip: bot mostly will be full). + if(GetClientHealth(bot) > 40) { + char wpn[32]; + if(GetClientWeapon(bot, wpn, sizeof(wpn)) && StrEqual(wpn, "weapon_autoshotgun") || StrEqual(wpn, "weapon_shotgun_spas")) { + GetClientAbsOrigin(bot, botPosition); + + for(int i = 0; i < WitchList.Length; i++) { + int witchID = WitchList.Get(i); + if(IsValidEntity(witchID) && GetEntPropFloat(witchID, Prop_Send, "m_rage") <= 0.4) { + GetEntPropVector(witchID, Prop_Send, "m_vecOrigin", witchPos); + //TODO: Calculate closest witch + if(GetVectorDistance(botPosition, witchPos) <= 570) { + //GetEntPropVector(witchID, Prop_Send, "m_angRotation", witchAng); + + L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(bot), witchPos[0], witchPos[1], witchPos[2]); + AutoCrownTarget = witchID; + AutoCrownBot = bot; + AutoCrownInPosition = false; + return Plugin_Continue; + } + } + } + } + } + } + } + return Plugin_Continue; +} + +public Action Timer_StopFiring(Handle hdl) { + ResetAutoCrown(); +} + +public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { + if(AutoCrownInPosition && AutoCrownBot == client && !(buttons & IN_ATTACK)) { + buttons |= IN_ATTACK; + //CreateTimer(0.4, Timer_StopFiring); + return Plugin_Changed; + } + return Plugin_Continue; +} + +public void ResetAutoCrown() { + AutoCrownTarget = -1; + AutoCrownInPosition = false; + if(AutoCrownBot > -1) + L4D2_RunScript("CommandABot({cmd=3,bot=GetPlayerFromUserID(%i)})", GetClientUserId(AutoCrownBot)); + AutoCrownBot = -1; + +} \ No newline at end of file