diff --git a/200IQBots_FlyYouFools.smx b/200IQBots_FlyYouFools.smx new file mode 100644 index 0000000..5dd0f7c Binary files /dev/null and b/200IQBots_FlyYouFools.smx differ diff --git a/200IQBots_FlyYouFools.sp b/200IQBots_FlyYouFools.sp new file mode 100644 index 0000000..5ec7dbf --- /dev/null +++ b/200IQBots_FlyYouFools.sp @@ -0,0 +1,172 @@ +#pragma semicolon 1 +#include +#include +#define PLUGIN_VERSION "1.4" + +static bool bEscapeReady = false; +static int TankClient; +//static ConVar hTankDangerDistance; + +public Plugin myinfo = +{ + name = "Fly You Fools", + author = "ConnerRia & Jackzmc", + description = "Survivor bots will retreat from tank. ", + version = PLUGIN_VERSION, + url = "N/A" +} + +public OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) + { + SetFailState("Plugin supports Left 4 Dead series only."); + } + + CreateConVar("FlyYouFools_Version", PLUGIN_VERSION, "FlyYouFools Version", FCVAR_NOTIFY|FCVAR_REPLICATED|FCVAR_DONTRECORD); + //hTankDangerDistance = CreateConVar("200IQBots_TankDangerRange", "800.0", "The range by which survivors bots will detect the presence of tank and retreat. ", FCVAR_NOTIFY|FCVAR_REPLICATED); + + HookEvent("map_transition", Event_RoundStart, EventHookMode_Pre); + HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); + HookEvent("tank_spawn", Event_TankSpawn); + HookEvent("tank_killed", Event_RoundStart); + HookEvent("finale_vehicle_incoming", Event_FinaleArriving); + + AutoExecConfig(true, "200IQBots_FlyYouFools"); + + FindExistingTank(); +} + +public OnMapStart() { + TankClient = -1; + bEscapeReady = false; +} + +public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { + TankClient = -1; + bEscapeReady = false; +} + +public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) { + TankClient = GetClientOfUserId(GetEventInt(event, "userid")); + CreateTimer(0.1, BotControlTimer, _, TIMER_REPEAT); +} +public void Event_FinaleArriving(Event event, const char[] name, bool dontBroadcast) { + bEscapeReady = true; +} +public Action BotControlTimer(Handle timer) +{ + //remove timer once tank no longer exists, is dead, or finale escape vehicle arrived + if(bEscapeReady || TankClient == -1 || !IsClientInGame(TankClient) || !IsPlayerAlive(TankClient)) { + return Plugin_Stop; + } + //Once an AI tank is awakened, m_lookatPlayer is set to a player ID + //Possible props: m_lookatPlayer, m_zombieState, m_hasVisibleThreats + int tank_target = GetEntPropEnt(TankClient, Prop_Send, "m_lookatPlayer", 0); + //bool hasVisibleThreats = GetEntProp(TankClient, Prop_Send, "m_hasVisibleThreats", 1) == 1; + //char targetted_name[64]; + if(tank_target > -1) { + //debug shit: + //GetClientName(tank_target, targetted_name, sizeof(targetted_name)); + //ShowHintToAll("tank_target: %d (%s) | visible threats: %b", tank_target, targetted_name, hasVisibleThreats); + for (new i = 1; i <= MaxClients; i++) + { + if (IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == 2 && IsFakeClient(i)) + { + //If distance between bot and tank is less than 200IQBots_TankDangerRange's float value + //if not tank target, and tank != visible threats, then attack + if(tank_target == i) { + L4D2_RunScript("CommandABot({cmd=2,bot=GetPlayerFromUserID(%i),target=GetPlayerFromUserID(%i)})", GetClientUserId(i), GetClientUserId(TankClient)); + }else { + float TankPosition[3]; + float BotPosition[3]; + + GetClientAbsOrigin(TankClient, TankPosition); + GetClientAbsOrigin(i, BotPosition); + + float distance = GetVectorDistance(BotPosition, TankPosition); + if(distance < 200) { + L4D2_RunScript("CommandABot({cmd=2,bot=GetPlayerFromUserID(%i),target=GetPlayerFromUserID(%i)})", GetClientUserId(i), GetClientUserId(TankClient)); + //do not attack if super close. + } else { + L4D2_RunScript("CommandABot({cmd=0,bot=GetPlayerFromUserID(%i),target=GetPlayerFromUserID(%i)})", GetClientUserId(i), GetClientUserId(TankClient)); + } + } + + } + } + } + return Plugin_Continue; +} + + + +public void FindExistingTank() { + for (int i = 1; i < MaxClients+1 ;i++) { + if (!IsClientInGame(i)) continue; + if (!IsFakeClient(i)) continue; + char name[16]; + GetClientName(i, name, sizeof(name)); + + if(StrContains(name,"Tank",true) > -1) { + PrintToServer("Found existing tank with id %d", i); + TankClient = i; + CreateTimer(0.1, BotControlTimer, _, TIMER_REPEAT); + break; + } + } +} + +//Credits to Timocop for the stock :D +/** +* Runs a single line of vscript code. +* NOTE: Dont use the "script" console command, it starts a new instance and leaks memory. Use this instead! +* +* @param sCode The code to run. +* @noreturn +*/ +stock void L4D2_RunScript(const char[] sCode, any ...) { + static iScriptLogic = INVALID_ENT_REFERENCE; + if(iScriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(iScriptLogic)) { + iScriptLogic = EntIndexToEntRef(CreateEntityByName("logic_script")); + if(iScriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(iScriptLogic)) + SetFailState("Could not create 'logic_script'"); + + DispatchSpawn(iScriptLogic); + } + + static char sBuffer[512]; + VFormat(sBuffer, sizeof(sBuffer), sCode, 2); + + SetVariantString(sBuffer); + AcceptEntityInput(iScriptLogic, "RunScriptCode"); +} +stock void ShowHintToAll(const char[] format, any ...) { + char buffer[254]; + VFormat(buffer, sizeof(buffer), format, 2); + static int hintInt = 0; + if(hintInt >= 9) { + PrintHintTextToAll("%s",buffer); + hintInt = 0; + } + hintInt++; +} +/** +* Get the classname of an item in a slot +* +* @param client The client to check inventory from +* @param slot The item slot index +* @param buffer The char[] buffer to set text to +* @param bufferSize The size of the buffer +* @return True if item, false if no item +*/ +stock bool GetItemClassSlot(int client, int slot, char[] buffer, int bufferSize) { + int item = GetPlayerWeaponSlot(client, slot); + if(item > -1) { + GetEdictClassname(item, buffer, bufferSize); + return true; + }else{ + return false; + } +} \ No newline at end of file diff --git a/L4D2Tools.smx b/L4D2Tools.smx new file mode 100644 index 0000000..2032d7d Binary files /dev/null and b/L4D2Tools.smx differ diff --git a/L4D2Tools.sp b/L4D2Tools.sp new file mode 100644 index 0000000..247d16e --- /dev/null +++ b/L4D2Tools.sp @@ -0,0 +1,142 @@ +#pragma semicolon 1 +#pragma newdecls required + +#define DEBUG + +#define PLUGIN_NAME "Misc Tools" +#define PLUGIN_DESCRIPTION "Includes: Notice on laser use, Timer for gauntlet runs" +#define PLUGIN_AUTHOR "jackzmc" +#define PLUGIN_VERSION "1.0" +#define PLUGIN_URL "" + +#include +#include +//#include + +#pragma newdecls required + +bool bLasersUsed[2048]; +ConVar hLaserNotice, hFinaleTimer; +int iFinaleStartTime; + +public Plugin myinfo = +{ + name = PLUGIN_NAME, + author = PLUGIN_AUTHOR, + description = PLUGIN_DESCRIPTION, + version = PLUGIN_VERSION, + url = PLUGIN_URL +}; + +public void OnPluginStart() +{ + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) + { + SetFailState("This plugin is for L4D/L4D2 only."); + } + hLaserNotice = CreateConVar("sm_laser_use_notice", "1.0", "Enable notification of a laser box being used", FCVAR_NONE, true, 0.0, true, 1.0); + hFinaleTimer = CreateConVar("sm_time_finale", "2.0", "Record the time it takes to complete finale. 0 -> OFF, 1 -> Gauntlets Only, 2 -> All finales", FCVAR_NONE, true, 0.0, true, 2.0); + + HookEvent("player_use", Event_PlayerUse); + HookEvent("round_end", Event_RoundEnd); + HookEvent("gauntlet_finale_start", Event_GauntletStart); + HookEvent("finale_start", Event_FinaleStart); + HookEvent("finale_vehicle_leaving", Event_FinaleEnd); +} + +#if 1 +//laserNotice +public void Event_PlayerUse(Event event, const char[] name, bool dontBroadcast) { + if(hLaserNotice.BoolValue) { + char player_name[32]; + char entity_name[64]; + int player_id = GetClientOfUserId(event.GetInt("userid")); + int target_id = event.GetInt("targetid"); + + GetClientName(player_id, player_name, sizeof(player_name)); + GetEntityClassname(target_id, entity_name, sizeof(entity_name)); + + if(StrEqual(entity_name,"upgrade_laser_sight")) { + if(!bLasersUsed[target_id]) { + bLasersUsed[target_id] = true; + PrintToChatAll("%s picked up laser sights",player_name); + } + } + } +} +public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { + for (int i = 1; i < sizeof(bLasersUsed) ;i++) { + bLasersUsed[i] = false; + } +} +#endif + + +#if 1 +//finaletimer +public void Event_GauntletStart(Event event, const char[] name, bool dontBroadcast) { + if(hFinaleTimer.IntValue > 0) { + iFinaleStartTime = GetTime(); + PrintToChatAll("The finale timer has been started"); + } +} +public void Event_FinaleStart(Event event, const char[] name, bool dontBroadcast) { + if(hFinaleTimer.IntValue == 2) { + iFinaleStartTime = GetTime(); + PrintToChatAll("The finale timer has been started"); + } +} +public void Event_FinaleEnd(Event event, const char[] name, bool dontBroadcast) { + if(hFinaleTimer.IntValue != 0) { + int difference = GetTime() - iFinaleStartTime; + iFinaleStartTime = 0; + + char time[32]; + FormatMs(difference, time, sizeof(time)); + PrintToChatAll("Finale took %s to complete", time); + } +} +#endif +/** + * Prints human readable duration from milliseconds + * + * @param ms The duration in milliseconds + * @param str The char array to use for text + * @param strSize The size of the string + */ +stock void FormatMs(int ms, char[] str, int strSize) { + int sec = ms / 1000; + int h = sec / 3600; + int m = (sec -(3600*h))/60; + int s = (sec -(3600*h)-(m*60)); + if(h >= 1) { + Format(str, strSize, "%d hour, %d.%d minutes", h, m, s); + }else if(m >= 1) { + Format(str, strSize, "%d minutes and %d seconds", m, s); + }else { + float raw_seconds = float(ms) / 1000; + Format(str, strSize, "%0.1f seconds", raw_seconds); + } + +} +stock void ShowHintToAll(const char[] format, any ...) { + char buffer[254]; + VFormat(buffer, sizeof(buffer), format, 2); + static int hintInt = 0; + if(hintInt >= 10) { + PrintHintTextToAll("%s",buffer); + hintInt = 0; + } + hintInt++; +} +stock void ShowHint(int client, const char[] format, any ...) { + char buffer[254]; + VFormat(buffer, sizeof(buffer), format, 2); + static int hintInt = 0; + if(hintInt >= 10) { + PrintHintText(client, "%s",buffer); + hintInt = 0; + } + hintInt++; +} \ No newline at end of file diff --git a/README.md b/README.md index 7224da9..750f88a 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,17 @@ All my sourcemod plugins... shitty probably ## Descriptions * `csgo-knifehp` - On knife kill, gives the player 100 HP (configurable) * **Convars:** - * `knifehp_enable` - Enable regaining health on knife kill - * `knifehp_max_health` - Maximum health to set an attacker to - * `knifehp_amount` - Amount of health to give attacker + * `knifehp_enable <0/1>` - Enable regaining health on knife kill + * `knifehp_max_health <#>` - Maximum health to set an attacker to + * `knifehp_amount <#>` - Amount of health to give attacker * `l4d2-manual-director` - Probably going to be posted publicly sometime. allows you to spawn specials on cursor, or via director, forcefully, bypassing limits * **Convars:** * `manual_director_version|mandirector_version` - ... gets version - * `mandirector_notify_spawn` - Should spawning specials notify on use? - * `mandirector_announce_level` - Announcement types. 0 - None, 1 - Only bosses, 2 - Only specials+, 3 - Everything - * `mandirector_enable_tank` - Should tanks be allowed to be spawned? - * `mandirector_enable_witch` - Should witches be allowed to be spawned? - * `mandirector_enable_mob` - Should mobs be allowed to be spawned + * `mandirector_notify_spawn <1/0>` - Should spawning specials notify on use? + * `mandirector_announce_level <0/1/2/3>` - Announcement types. 0 - None, 1 - Only bosses, 2 - Only specials+, 3 - Everything + * `mandirector_enable_tank <0/1>` - Should tanks be allowed to be spawned? + * `mandirector_enable_witch <0/1>` - Should witches be allowed to be spawned? + * `mandirector_enable_mob <0/1>` - Should mobs be allowed to be spawned * **Commands:** * `sm_spawnspecial` - Spawn a special via director * `sm_forcespecial` - Force spawn a special via director, bypassing spawn limits @@ -36,4 +36,12 @@ All my sourcemod plugins... shitty probably 4,Francis,1,76,alive,0,,,,Francis 5,Louis,1,90,alive,0,,first_aid_kit,,Louis ``` -* `AutoWarpBot` - simple l4d2 plugin that will auto teleport everyone to checkpoint once all real players have reached the saferoom \ No newline at end of file +* `AutoWarpBot` - simple l4d2 plugin that will auto teleport everyone to checkpoint once all real players have reached the saferoom +* `L4D2Tools` - A group of misc tools for l4d2. Including: Notify on lasers use, and a finale timer (gauntlets or all finales) + * **Convars:** + * `sm_laser_use_notice <1/0>` - Enable notification of a laser box being used + * `sm_time_finale <0/1/2>` - Record the time it takes to complete finale. 0 -> OFF, 1 -> Gauntlets Only, 2 -> All finales +* `200IQBots_FlyYouFools` - Updated version of ConnerRia's plugin. Improves bots avoidance of tanks. + * Change from original is updated source syntax, some optimizations/cleanup, and fixes such as bots avoiding tank that has not been activated, or not escaping in vehicle due to presence of tank + * **Convars:** + * `FlyYouFools_Version` - Prints the version of plugin \ No newline at end of file