#pragma semicolon 1 #pragma newdecls required //#define DEBUG #define PLUGIN_VERSION "1.0" #include #include //#include public Plugin myinfo = { name = "L4D2 Tank Priority", author = "jackzmc", description = "", version = PLUGIN_VERSION, url = "https://github.com/Jackzmc/sourcemod-plugins" }; #define TANK_CLASS_ID 8 static int tankChooseVictimTicks[MAXPLAYERS+1]; //Per tank static int tankChosenVictim[MAXPLAYERS+1]; static int totalTankDamage[MAXPLAYERS+1]; //Per survivor static ArrayList clients; public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { SetFailState("This plugin is for L4D/L4D2 only."); } clients = new ArrayList(2); HookEvent("player_hurt", Event_PlayerHurt); HookEvent("tank_spawn", Event_TankSpawn); } public Action L4D2_OnChooseVictim(int attacker, int &curTarget) { int class = GetEntProp(attacker, Prop_Send, "m_zombieClass"); if(class != TANK_CLASS_ID) return Plugin_Continue; //Find a new victim if(++tankChooseVictimTicks[attacker] >= 200) { tankChooseVictimTicks[attacker] = 0; clients.Clear(); static float tankPos[3], clientPos[3]; GetClientAbsOrigin(attacker, tankPos); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) && !IsPlayerIncapacitated(i)) { //If a player does less than 50 damage, and has green health add them to list if(totalTankDamage[i] < 100 && GetClientHealth(i) > 40) { GetClientAbsOrigin(i, clientPos); float dist = GetVectorDistance(clientPos, tankPos); // Only add targets who are far enough away from tank if(dist > 3000.0) { PrintToConsoleAll("[TankPriority/debug] Adding player %N to possible victim list. Dist=%f Dmg=%d", i, dist, totalTankDamage[i]); int index = clients.Push(i); clients.Set(index, dist, 1); } } } } if(clients.Length == 0) return Plugin_Continue; clients.SortCustom(Sort_TankTargetter); curTarget = clients.Get(0); tankChosenVictim[attacker] = curTarget; PrintToConsoleAll("[TankPriority] Player Selected to target: %N", curTarget); //TODO: Possibly clear totalTankDamage return Plugin_Changed; } if(tankChosenVictim[attacker] > 0) { if(IsClientConnected(tankChosenVictim[attacker]) && IsClientInGame(tankChosenVictim[attacker]) && IsPlayerAlive(tankChosenVictim[attacker]) && !IsPlayerIncapacitated(tankChosenVictim[attacker])) { curTarget = tankChosenVictim[attacker]; return Plugin_Changed; } else { tankChosenVictim[attacker] = 0; } } return Plugin_Continue; } int Sort_TankTargetter(int index1, int index2, Handle array, Handle hndl) { int client1 = GetArrayCell(array, index1); int client2 = GetArrayCell(array, index2); float distance1 = GetArrayCell(array, index2, 0); float distance2 = GetArrayCell(array, index2, 1); /*500 units away, 0 damage vs 600 units away, 0 damage -> target closest 500 500 units away, 10 damage, vs 600 units away 0 damage 500 - 10 = 450 vs 600 */ return (totalTankDamage[client1] + RoundFloat(distance1)) - (totalTankDamage[client2] + RoundFloat(distance2)); } public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) { int victim = GetClientOfUserId(event.GetInt("userid")); int attacker = GetClientOfUserId(event.GetInt("attacker")); int dmg = event.GetInt("dmg_health"); if(dmg > 0 && attacker > 0 && victim > 0 && IsFakeClient(victim) && GetEntProp(victim, Prop_Send, "m_zombieClass") == TANK_CLASS_ID) { if(GetClientTeam(victim) == 3 && GetClientTeam(attacker) == 2) { totalTankDamage[victim] += dmg; } } } public void Event_TankSpawn(Event event, const char[] name, bool dontBroadcast) { int tank = GetClientOfUserId(GetEventInt(event, "userid")); if(tank > 0 && IsFakeClient(tank)) { tankChooseVictimTicks[tank] = -20; } } bool IsPlayerIncapacitated(int client) { return (GetEntProp(client, Prop_Send, "m_isIncapacitated") == 1); } public void OnClientDisconnect(int client) { tankChosenVictim[client] = 0; for(int i = 1; i <= MaxClients; i++) { if(tankChosenVictim[i] == client) { tankChosenVictim[i] = 0; } } }