diff --git a/gamedata/l4d2_turret.txt b/gamedata/l4d2_turret.txt new file mode 100644 index 0000000..b2d19e6 --- /dev/null +++ b/gamedata/l4d2_turret.txt @@ -0,0 +1,31 @@ +"Games" +{ + "left4dead2" + { + "Offsets" + { + "CBaseAnimating::StudioHdr" + { + "linux" "68" + } + } + "Signatures" + { + "CBaseAnimating::LookupPoseParameter" + { + "library" "server" + "linux" "@_ZN14CBaseAnimating19LookupPoseParameterEP10CStudioHdrPKc" + } + "ModelSoundsCache_LoadModel" + { + "library" "server" + "linux" "@_Z26ModelSoundsCache_LoadModelPKc" + } + "ModelSoundsCache_FinishModel" + { + "library" "server" + "linux" "@_Z28ModelSoundsCache_FinishModelP10CStudioHdr" + } + } + } +} \ No newline at end of file diff --git a/plugins/abm.smx b/plugins/abm.smx new file mode 100644 index 0000000..b316310 Binary files /dev/null and b/plugins/abm.smx differ diff --git a/plugins/l4d2_hats.smx b/plugins/l4d2_hats.smx index b5ce368..f047dd4 100644 Binary files a/plugins/l4d2_hats.smx and b/plugins/l4d2_hats.smx differ diff --git a/plugins/l4d2_turret.smx b/plugins/l4d2_turret.smx index 6170352..b0d25d5 100644 Binary files a/plugins/l4d2_turret.smx and b/plugins/l4d2_turret.smx differ diff --git a/scripting/abm.sp b/scripting/abm.sp new file mode 100644 index 0000000..aa50dc1 --- /dev/null +++ b/scripting/abm.sp @@ -0,0 +1,3774 @@ +//# vim: set filetype=cpp : + +/* +ABM a SourceMod L4D2 Plugin +Copyright (C) 2016-2017 Victor "NgBUCKWANGS" Gonzalez + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the + +Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#undef REQUIRE_EXTENSIONS +#include +#undef REQUIRE_PLUGIN +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "0.1.97q" +#define LOGFILE "addons/sourcemod/logs/abm.log" // TODO change this to DATE/SERVER FORMAT? + +Handle g_GameData = null; +ArrayList g_sQueue; +ArrayList g_iQueue; + +// menu parameters +#define menuArgs g_menuItems[client] // Global argument tracking for the menu system +#define menuArg0 g_menuItems[client][0] // GetItem(1...) +#define menuArg1 g_menuItems[client][1] // GetItem(2...) +int g_menuItems[MAXPLAYERS + 1][2]; + +// menu tracking +#define g_callBacks g_menuStack[client] +ArrayStack g_menuStack[MAXPLAYERS + 1]; +Function callBack; + +char g_QKey[64]; // holds players by STEAM_ID +StringMap g_QDB; // holds player records linked by STEAM_ID +StringMap g_QRecord; // changes to an individual STEAM_ID mapping +StringMap g_QRtmp; // temporary QRecord holder for checking records without changing the main +StringMap g_Cvars; // locked cvars end up here + +char g_InfectedNames[6][] = {"Boomer", "Smoker", "Hunter", "Spitter", "Jockey", "Charger"}; +char g_SurvivorNames[8][] = {"Nick", "Rochelle", "Coach", "Ellis", "Bill", "Zoey", "Francis", "Louis"}; +char g_SurvivorPaths[8][] = { + "models/survivors/survivor_gambler.mdl", + "models/survivors/survivor_producer.mdl", + "models/survivors/survivor_coach.mdl", + "models/survivors/survivor_mechanic.mdl", + "models/survivors/survivor_namvet.mdl", + "models/survivors/survivor_teenangst.mdl", + "models/survivors/survivor_biker.mdl", + "models/survivors/survivor_manager.mdl", +}; + +int g_OS; // 0: Linux, 1: Windows (Prevent crash on Windows /w Zoey) +char g_sB[512]; // generic catch all string buffer +char g_pN[128]; // a dedicated buffer to storing a temporary name +char g_name[128], g_tmpName[128]; // g_QDB client name +int g_client, g_tmpClient; // g_QDB client id +int g_target, g_tmpTarget; // g_QDB player (human or bot) id +int g_lastid, g_tmpLastid; // g_QDB client's last known bot id +int g_onteam, g_tmpOnteam = 1; // g_QDB client's team +bool g_queued, g_tmpQueued; // g_QDB client's takeover state +bool g_inspec, g_tmpInspec; // g_QDB check client's specator mode +bool g_status, g_tmpStatus; // g_QDB client life state +bool g_update, g_tmpUpdate; // g_QDB should we update this record? +char g_model[32], g_tmpModel[32]; // g_QDB client's model +char g_ghost[32], g_tmpGhost[32]; // g_QDB queued model for SwitchTeam +float g_origin[3], g_tmpOrigin[3]; // g_QDB client's origin vector +float g_rdelay, g_tmpRdelay; // g_QDB delay time for SI respawn +Handle g_rDelays[MAXPLAYERS + 1]; // Individual respawn SI timers +int g_models[8]; + +Handle g_MkBotsTimer; +Handle g_ADAssistant; // Tries to make sure the AD starts +Handle g_AD; // Assistant Director Timer +int g_survivorSet; // 0 = l4d2 survivors, 4 = l4d1 survivors +bool g_survivorSetScan; // Check to discover the survivor set + +bool g_IsVs; +bool g_IsCoop; +bool g_AssistedSpawning = false; +bool g_ADFreeze = true; +bool g_AutoWave; +int g_ADInterval; + +ConVar g_cvVersion; // abm_version +ConVar g_cvLogLevel; int g_LogLevel; // abm_loglevel +ConVar g_cvMinPlayers; int g_MinPlayers; // abm_minplayers +ConVar g_cvPrimaryWeapon; char g_PrimaryWeapon[64]; // abm_primaryweapon +ConVar g_cvSecondaryWeapon; char g_SecondaryWeapon[64]; // abm_secondaryweapon +ConVar g_cvThrowable; char g_Throwable[64]; // abm_throwable +ConVar g_cvHealItem; char g_HealItem[64]; // abm_healitem +ConVar g_cvConsumable; char g_Consumable[64]; // abm_consumable +ConVar g_cvTankChunkHp; int g_TankChunkHp; // abm_tankchunkhp +ConVar g_cvSpawnInterval; int g_SpawnInterval; // abm_spawninterval +ConVar g_cvAutoHard; int g_AutoHard; // abm_autohard +ConVar g_cvUnlockSI; int g_UnlockSI; // abm_unlocksi +ConVar g_cvJoinMenu; int g_JoinMenu; // abm_joinmenu +ConVar g_cvTeamLimit; int g_TeamLimit; // abm_teamlimit +ConVar g_cvOfferTakeover; int g_OfferTakeover; // abm_offertakeover +ConVar g_cvStripKick; int g_StripKick; // abm_stripkick +ConVar g_cvAutoModel; int g_AutoModel; // abm_automodel +ConVar g_cvKeepDead; int g_KeepDead; // abm_keepdead +ConVar g_cvIdentityFix; int g_IdentityFix; // abm_identityfix +ConVar g_cvZoey; int g_Zoey; // abm_zoey +ConVar g_cvRespawnDelay; float g_RespawnDelay; // abm_respawndelay +ConVar g_cvWrapSwitch; int g_WrapSwitch; // _abm_wrapswitch +ConVar g_cvMaxMinions; // z_minion_limit +ConVar g_cvMaxSurvivors; // survivor_limit +ConVar g_cvMaxInfecteds; // z_max_player_zombies +int g_MaxMates; // X vs X +int g_SILimits; + +ConVar g_cvTankHealth; +ConVar g_cvMaxSwitches; +ConVar g_cvGameMode; +ConVar g_cvVDOUHandle; +ConVar g_cvVDOUOrigin; +ConVar g_cvConsistency; char g_consistency[2]; +char g_VDOUCurVal[2048]; +char g_VDOUOrigin[2048]; + +public Plugin myinfo= { + name = "ABM", + author = "Victor \"NgBUCKWANGS\" Gonzalez", + description = "A 5+ Player Enhancement Plugin for L4D2", + version = PLUGIN_VERSION, + url = "https://gitlab.com/vbgunz/ABM" +} + +public void OnPluginStart() { + Echo(2, "OnPluginStart"); + + g_GameData = LoadGameConfigFile("abm"); + if (g_GameData == null) { + SetFailState("[ABM] Game data missing!"); + } + + HookEvent("player_first_spawn", OnSpawnHook); + HookEvent("player_spawn", OnAllSpawnHook); + HookEvent("player_death", OnDeathHook, EventHookMode_Pre); + HookEvent("player_disconnect", CleanQDBHook); + HookEvent("player_afk", GoIdleHook); + HookEvent("player_team", QTeamHook); + HookEvent("player_bot_replace", QAfkHook); + HookEvent("bot_player_replace", QBakHook); + HookEvent("player_activate", PlayerActivateHook, EventHookMode_Pre); + HookEvent("player_connect", PlayerActivateHook, EventHookMode_Pre); + HookEvent("round_end", RoundFreezeEndHook, EventHookMode_Pre); + HookEvent("mission_lost", RoundFreezeEndHook, EventHookMode_Pre); + HookEvent("round_freeze_end", RoundFreezeEndHook, EventHookMode_Pre); + HookEvent("map_transition", RoundFreezeEndHook, EventHookMode_Pre); + HookEvent("round_start", RoundStartHook, EventHookMode_Pre); + + // base the following on a cvar + HookUserMessage(GetUserMessageId("SayText2"), UserMessageHook, true); + + RegAdminCmd("abm", MainMenuCmd, ADMFLAG_GENERIC); + RegAdminCmd("abm-menu", MainMenuCmd, ADMFLAG_GENERIC, "Menu: (Main ABM menu)"); + RegAdminCmd("abm-join", SwitchTeamCmd, ADMFLAG_GENERIC, "Menu/Cmd: | "); + RegAdminCmd("abm-takeover", SwitchToBotCmd, ADMFLAG_GENERIC, "Menu/Cmd: | "); + RegAdminCmd("abm-respawn", RespawnClientCmd, ADMFLAG_GENERIC, "Menu/Cmd: [ID]"); + RegAdminCmd("abm-model", AssignModelCmd, ADMFLAG_GENERIC, "Menu/Cmd: | "); + RegAdminCmd("abm-strip", StripClientCmd, ADMFLAG_GENERIC, "Menu/Cmd: [SLOT]"); + RegAdminCmd("abm-teleport", TeleportClientCmd, ADMFLAG_GENERIC, "Menu/Cmd: "); + RegAdminCmd("abm-cycle", CycleBotsCmd, ADMFLAG_GENERIC, "Menu/Cmd: | "); + RegAdminCmd("abm-reset", ResetCmd, ADMFLAG_GENERIC, "Cmd: (Use only in case of emergency)"); + RegAdminCmd("abm-info", QuickClientPrintCmd, ADMFLAG_GENERIC, "Cmd: (Print some diagnostic information)"); + RegAdminCmd("abm-mk", MkBotsCmd, ADMFLAG_GENERIC, "Cmd: "); + RegAdminCmd("abm-rm", RmBotsCmd, ADMFLAG_GENERIC, "Cmd: | "); + RegAdminCmd("abm-debug", DebugCmd, ADMFLAG_GENERIC, ""); + RegConsoleCmd("takeover", SwitchToBotCmd, "Menu/Cmd: | "); + RegConsoleCmd("join", SwitchTeamCmd, "Menu/Cmd: | "); + + g_OS = GetOS(); // 0: Linux 1: Windows + g_QDB = new StringMap(); + g_QRecord = new StringMap(); + g_QRtmp = new StringMap(); + g_Cvars = new StringMap(); + g_sQueue = new ArrayList(2); + g_iQueue = new ArrayList(2); + + char zoeyId[2]; + switch(g_OS) { + case 0: zoeyId = "5"; // 5 is the real Zoey + case 1: zoeyId = "1"; // Zoey crashes Windows servers, 1 is Rochelle + } + + // remember vscript director options unlocker settings between reloads + g_cvVDOUHandle = FindConVar("l4d2_directoroptions_overwrite"); + SetupCvar(g_cvVDOUOrigin, "_abm_vdouorigin", ";", "DO NOT EDIT"); + + if (g_cvVDOUHandle != null) { + HookConVarChange(g_cvVDOUHandle, UpdateConVarsHook); + GetConVarString(g_cvVDOUOrigin, g_VDOUOrigin, sizeof(g_VDOUOrigin)); + GetConVarString(g_cvVDOUHandle, g_VDOUCurVal, sizeof(g_VDOUCurVal)); + + if(g_VDOUOrigin[0] != ';') + UpdateConVarsHook(g_cvVDOUHandle, g_VDOUOrigin, g_VDOUOrigin); + else + UpdateConVarsHook(g_cvVDOUHandle, g_VDOUCurVal, g_VDOUCurVal); + } + + g_cvTankHealth = FindConVar("z_tank_health"); + g_cvMaxSwitches = FindConVar("vs_max_team_switches"); + g_cvConsistency = FindConVar("sv_consistency"); + HookConVarChange(g_cvConsistency, UpdateConVarsHook); + g_cvGameMode = FindConVar("mp_gamemode"); + HookConVarChange(g_cvGameMode, UpdateConVarsHook); + UpdateGameMode(); + + float maxClients = float(MaxClients); + g_cvMaxMinions = FindConVar("z_minion_limit"); + SetConVarFloat(g_cvMaxMinions, maxClients); + + g_cvMaxInfecteds = FindConVar("z_max_player_zombies"); + SetConVarBounds(g_cvMaxInfecteds, ConVarBound_Upper, true, maxClients); + SetConVarFloat(g_cvMaxInfecteds, maxClients); + + g_cvMaxSurvivors = FindConVar("survivor_limit"); + SetConVarBounds(g_cvMaxSurvivors, ConVarBound_Upper, true, maxClients); + SetConVarFloat(g_cvMaxSurvivors, maxClients); + + // thanks to bl4nk + int flags = GetConVarFlags(g_cvMaxSurvivors); + flags &= ~FCVAR_NOTIFY; + SetConVarFlags(g_cvMaxSurvivors, flags); + + SetupCvar(g_cvVersion, "abm_version", PLUGIN_VERSION, "ABM plugin version"); + SetupCvar(g_cvLogLevel, "abm_loglevel", "-1", "Development logging level -1: Off, 6: Max"); + SetupCvar(g_cvMinPlayers, "abm_minplayers", "4", "Pruning extra survivors stops at this size"); + SetupCvar(g_cvPrimaryWeapon, "abm_primaryweapon", "shotgun_chrome", "5+ survivor primary weapon"); + SetupCvar(g_cvSecondaryWeapon,"abm_secondaryweapon", "baseball_bat", "5+ survivor secondary weapon"); + SetupCvar(g_cvThrowable, "abm_throwable", "", "5+ survivor throwable item"); + SetupCvar(g_cvHealItem, "abm_healitem", "", "5+ survivor healing item"); + SetupCvar(g_cvConsumable, "abm_consumable", "adrenaline", "5+ survivor consumable item"); + SetupCvar(g_cvTankChunkHp, "abm_tankchunkhp", "2500", "Health chunk per survivor on 5+ missions"); + SetupCvar(g_cvSpawnInterval, "abm_spawninterval", "36", "SI full team spawn in (5 x N)"); + SetupCvar(g_cvAutoHard, "abm_autohard", "1", "0: Off 1: Non-Vs > 4 2: Non-Vs >= 1"); + SetupCvar(g_cvUnlockSI, "abm_unlocksi", "0", "0: Off 1: Use Left 4 Downtown 2 2: Use VScript Director Options Unlocker"); + SetupCvar(g_cvJoinMenu, "abm_joinmenu", "1", "0: Off 1: Admins only 2: Everyone"); + SetupCvar(g_cvTeamLimit, "abm_teamlimit", "16", "Humans on team limit"); + SetupCvar(g_cvOfferTakeover, "abm_offertakeover", "1", "0: Off 1: Survivors 2: Infected 3: All"); + SetupCvar(g_cvStripKick, "abm_stripkick", "0", "0: Don't strip removed bots 1: Strip removed bots"); + SetupCvar(g_cvAutoModel, "abm_automodel", "1", "1: Full set of survivors 0: Map set of survivors"); + SetupCvar(g_cvKeepDead, "abm_keepdead", "0", "0: The dead return alive 1: the dead return dead"); + SetupCvar(g_cvIdentityFix, "abm_identityfix", "1", "0: Do not assign identities 1: Assign identities"); + SetupCvar(g_cvZoey, "abm_zoey", zoeyId, "0:Nick 1:Rochelle 2:Coach 3:Ellis 4:Bill 5:Zoey 6:Francis 7:Louis"); + SetupCvar(g_cvRespawnDelay, "abm_respawndelay", "1.0", "SI respawn delay time in non-competitive modes"); + SetupCvar(g_cvWrapSwitch, "abm_wrapswitch", "0", "Intercept chooseteam/jointeam commands"); + + // clean out client menu stacks + for (int i = 1; i <= MaxClients; i++) { + g_menuStack[i] = new ArrayStack(128); + } + + // Register everyone that we can find + for (int i = 1; i <= MaxClients; i++) { + SetQRecord(i, true); + } + + AddCommandListener(TeamSwitchIntercept, "jointeam"); + AddCommandListener(CmdIntercept, "z_spawn"); + AddCommandListener(CmdIntercept, "z_spawn_old"); + AddCommandListener(CmdIntercept, "z_add"); + AutoExecConfig(true, "abm"); + StartAD(); +} + +public void OnPluginEnd() { + Echo(2, "OnPluginEnd"); + + for (int i = 1; i <= MaxClients; i++) { + if (GetQRecord(i)) { + SetClientName(i, g_name); + } + } +} + +public Action UserMessageHook(UserMsg MsgId, Handle hBitBuffer, const int[] iPlayers, int iNumPlayers, bool bReliable, bool bInit) { + Echo(2, "UserMessageHook: %d %d %d", iNumPlayers, bReliable, bInit); + // thanks https://forums.alliedmods.net/showpost.php?p=914509&postcount=10 + + // Skip the first two bytes + BfReadByte(hBitBuffer); + BfReadByte(hBitBuffer); + + // Read the message + static char strMessage[1024]; + BfReadString(hBitBuffer, strMessage, sizeof(strMessage)); + + // If the message equals to the string we want to filter, skip. + if (StrEqual(strMessage, "#Cstrike_Name_Change")) { + return Plugin_Handled; + } + + return Plugin_Continue; +} + +public Action TeamSwitchIntercept(int client, const char[] cmd, int args) { + Echo(2, "TeamSwitchIntercept: %d %s %d", client, cmd, args); + + if (g_WrapSwitch) { + GetCmdArg(1, g_sB, sizeof(g_sB)); + + if (IsClientValid(client)) { + if (StrEqual(g_sB, "survivor", false) || StrEqual(g_sB, "2")) { + SwitchTeam(client, 2); + } + + else if (StrEqual(g_sB, "infected", false) || StrEqual(g_sB, "3")) { + if (g_IsVs || IsAdmin(client)) { + SwitchTeam(client, 3); + } + } + + else if (StrEqual(g_sB, "0") || StrEqual(g_sB, "1")) { + SwitchTeam(client, StringToInt(g_sB)); + } + + else { + SwitchTeamCmd(client, 0); + } + + return Plugin_Handled; + } + } + + return Plugin_Continue; +} + +void SetupCvar(Handle &cvHandle, char[] name, char[] value, char[] details) { + Echo(2, "SetupCvar: %s %s %s", name, value, details); + + cvHandle = CreateConVar(name, value, details); + HookConVarChange(cvHandle, UpdateConVarsHook); + UpdateConVarsHook(cvHandle, value, value); +} + +Action CmdIntercept(int client, const char[] cmd, int args) { + Echo(2, "CmdIntercept: %d %s %d", client, cmd, args); + + if (g_AssistedSpawning) { + GhostsModeProtector(); + } + return Plugin_Continue; +} + +public void OnMapStart() { + Echo(2, "OnMapStart:"); + PrecacheModels(); +} + +public void OnMapEnd() { + Echo(2, "OnMapEnd:"); + + StopAD(); + StringMapSnapshot keys = g_QDB.Snapshot(); + g_iQueue.Clear(); + g_sQueue.Clear(); + +// if (!g_IsVs) { +// SetConVarInt(g_cvMaxSurvivors, g_MinPlayers); +// } + + for (int i; i < keys.Length; i++) { + keys.GetKey(i, g_sB, sizeof(g_sB)); + g_QDB.GetValue(g_sB, g_QRecord); + + g_QRecord.GetValue("onteam", g_onteam); + g_QRecord.GetValue("client", g_client); + g_QRecord.GetValue("target", g_target); + g_QRecord.SetValue("status",true,true); + + if (g_onteam >= 2) { + g_QRecord.SetValue("inspec", false, true); + } + + if (g_IsVs) { + g_QRecord.SetString("model", "", true); + } + + else if (g_onteam == 3) { + //SwitchToSpec(g_client); + g_QRecord.SetValue("status",false, true); + g_QRecord.SetString("model", "", true); + QueueUp(g_client,3); + } + } + + delete keys; +} + +public void OnEntityCreated(int ent, const char[] clsName) { + Echo(2, "OnEntityCreated: %d %s", ent, clsName); + + if (clsName[0] == 'f') { + bool gClip = !StrEqual(clsName, "func_playerghostinfected_clip", false); + bool iClip = !StrEqual(clsName, "func_playerinfected_clip", false); + + if (!(gClip && iClip)) { + CreateTimer(2.0, KillEntTimer, EntIndexToEntRef(ent)); + } + } + + else if (clsName[0] == 's' && StrEqual(clsName, "survivor_bot")) { + SDKHook(ent, SDKHook_SpawnPost, AutoModel); + } +} + +public Action KillEntTimer(Handle timer, any ref) { + Echo(2, "KillEntTimer: %d", ref); + + int ent = EntRefToEntIndex(ref); + if (ent != INVALID_ENT_REFERENCE || IsEntityValid(ent)) { + AcceptEntityInput(ent, "kill"); + } + + return Plugin_Stop; +} + +public Action L4D_OnGetScriptValueInt(const char[] key, int &retVal) { + Echo(5, "L4D_OnGetScriptValueInt: %s, %d", key, retVal); + + // see UpdateConVarsHook "g_UnlockSI" for VScript Director Options Unlocker + + if (g_UnlockSI == 1 && g_MaxMates > 4) { + int val = retVal; + + if (StrEqual(key, "DominatorLimit")) val = g_MaxMates; + else if (StrEqual(key, "MaxSpecials")) val = g_MaxMates; + else if (StrEqual(key, "BoomerLimit")) val = g_SILimits; + else if (StrEqual(key, "SmokerLimit")) val = g_SILimits; + else if (StrEqual(key, "HunterLimit")) val = g_SILimits; + else if (StrEqual(key, "ChargerLimit")) val = g_SILimits; + else if (StrEqual(key, "SpitterLimit")) val = g_SILimits; + else if (StrEqual(key, "JockeyLimit")) val = g_SILimits; + + if (val != retVal) { + retVal = val; + return Plugin_Handled; + } + } + + return Plugin_Continue; +} + +public void RoundFreezeEndHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "RoundFreezeEndHook: %s", name); + OnMapEnd(); +} + +public void PlayerActivateHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "PlayerActivateHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + PlayerActivate(client); +} + +void PlayerActivate(int client) { + Echo(2, "PlayerActivate: %d", client); + + if (GetQRecord(client)) { + g_QRecord.SetString("model", "", true); + StartAD(); + + if (!g_IsVs && g_onteam == 3) { + //SwitchTeam(client, 3); + QueueUp(client, 3); + AddInfected(); + } + } +} + +int GetRealClient(int client) { + Echo(2, "GetRealClient: %d", client); + + if (IsClientValid(client, 0)) { + if (HasEntProp(client, Prop_Send, "m_humanSpectatorUserID")) { + int userid = GetEntProp(client, Prop_Send, "m_humanSpectatorUserID"); + int target = GetClientOfUserId(userid); + + if (IsClientValid(target)) { + client = target; + } + + else { + for (int i = 1; i <= MaxClients; i++) { + if (GetQRtmp(i) && g_tmpTarget == client) { + client = i; + break; + } + } + } + } + } + + else { + client = 0; + } + + return client; +} + +Action LifeCheckTimer(Handle timer, int target) { + Echo(2, "LifeCheckTimer: %d", target); + + if (GetQRecord(GetRealClient(target))) { + int status = IsPlayerAlive(target); + + if(g_model[0] != EOS) { + AssignModel(target, g_model, g_IdentityFix); + } else { + GetBotCharacter(target, g_model); + g_QRecord.SetString("model", g_model, true); + } + + g_QRecord.SetValue("status", status, true); + AssignModel(g_client, g_model, g_IdentityFix); + } + return Plugin_Handled; +} + +public void OnAllSpawnHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "OnAllSpawnHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + + if (GetQRtmp(client)) { + CreateTimer(0.5, LifeCheckTimer, client); + + if (!g_IsVs && g_tmpOnteam == 3 && !g_tmpStatus) { + g_QRtmp.SetValue("status", 1, true); + State_TransitionSig(client, 8); + } + } +} + +public void RoundStartHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "RoundStartHook: %s", name); + StartAD(); + + for (int i = 1; i <= MaxClients; i++) { + if (GetQRtmp(i)) { + if (!g_IsVs && g_tmpOnteam == 3) { + //SwitchTeam(i, 3); + QueueUp(i, 3); + AddInfected(); + } + } + } +} + +bool StopAD() { + Echo(2, "StopAD"); + + if (g_AD != null) { + g_ADFreeze = true; + g_AssistedSpawning = false; + g_survivorSetScan = true; + g_ADInterval = 0; + + for (int i = 1; i <= MaxClients; i++) { + if (g_rDelays[i] != null) { + KillTimer(g_rDelays[i]); + g_rDelays[i] = null; + } + } + + if (g_MkBotsTimer != null) { + KillTimer(g_MkBotsTimer); + g_MkBotsTimer = null; + } + + KillTimer(g_AD); // delete causes errors? + g_AD = null; + } + + return g_AD == null; +} + +bool StartAD(float interval=0.5) { + Echo(2, "StartAD"); + + if (g_ADAssistant == null) { + g_ADAssistant = CreateTimer(0.1, StartADTimer, _, TIMER_REPEAT); + } + + if (StopAD()) { + g_AD = CreateTimer(interval, ADTimer, _, TIMER_REPEAT); + } + + return g_AD != null; +} + +public Action StartADTimer(Handle timer) { + Echo(6, "StartADTimer"); + + if (g_AD == null) { + StartAD(); + return Plugin_Continue; + } + + g_ADAssistant = null; + return Plugin_Stop; +} + +bool AllClientsLoadedIn() { + Echo(2, "AllClientsLoadedIn"); + + for (int i = 1; i <= MaxClients; i++) { + if (IsClientConnected(i) && !IsClientInGame(i)) { + return false; + } + } + + return true; +} + +public Action ADTimer(Handle timer) { + Echo(6, "ADTimer"); + + g_MaxMates = CountTeamMates(2); + + if (g_MaxMates == 0) { + return Plugin_Continue; + } + + static bool takeover; + takeover = !g_IsVs || (g_IsVs && !g_ADFreeze); + + for (int i = 1; i <= MaxClients; i++) { + if (g_ADFreeze) { + g_ADInterval = 0; + + if (g_MaxMates >= 4 || AllClientsLoadedIn()) { + while (CountTeamMates(2) < g_MinPlayers) { + AddSurvivor(); + } + + if (AllClientsLoadedIn() && StartAD(5.0)) { + RmBots(g_MinPlayers * -1, 2); + //SetConVarInt(g_cvMaxSurvivors, MaxClients); + g_AssistedSpawning = false; + g_ADFreeze = false; + } + } + } + + if (IsClientValid(GetRealClient(i), 2, 0)) { + if (IsPlayerAlive(i)) { + _AutoModel(i); + } + } + + else if (GetQRtmp(i)) { + + //if (IsClientValid(g_tmpTarget, 2, 0)) { + // ResetClientSpecUserId(i, g_tmpTarget); + //} + + if (g_tmpOnteam == 3) { + if (!g_IsVs) { + g_AssistedSpawning = true; + + if (!g_tmpInspec && GetClientTeam(i) <= 1) { + QueueUp(i, 3); + AddInfected(); + } + } + } + + else if (!g_tmpInspec && GetClientTeam(i) <= 1) { + if (takeover) { + g_QRtmp.SetValue("onteam", 0,true); + CreateTimer(0.1, TakeoverTimer, i); + } + } + } + } + + static int lastSize; + + if (lastSize != g_MaxMates) { + lastSize = g_MaxMates; + + g_SILimits = 1; + while (g_SILimits * 6 < g_MaxMates) { + g_SILimits++; + } + + VDOUnlocker(); + g_AutoWave = false; + + if (g_IsCoop) { + g_AutoWave = (g_AutoHard == 2 || g_MaxMates > 4 && g_AutoHard == 1); + AutoSetTankHp(); + } + } + + if (g_AutoWave || g_AssistedSpawning) { + if (g_SpawnInterval > 0 && g_ADInterval >= g_SpawnInterval) { + if (g_ADInterval % g_SpawnInterval == 0) { + Echo(2, " -- Assisting SI %d: Matching Full Team", g_ADInterval); + MkBots(g_MaxMates * -1, 3); + } + + else if (g_ADInterval % (g_SpawnInterval / 2) == 0) { + Echo(2, " -- Assisting SI %d: Matching Half Team", g_ADInterval); + MkBots((g_MaxMates / 2) * -1, 3); + } + + else if (g_ADInterval % (g_SpawnInterval / 3) == 0) { + Echo(2, " -- Assisting SI %d: Matching a Quarter", g_ADInterval); + MkBots((g_MaxMates / 4) * -1, 3); + } + } + } + + g_ADInterval++; + return Plugin_Continue; +} + +public void UpdateConVarsHook(Handle convar, const char[] oldCv, const char[] newCv) { + GetConVarName(convar, g_sB, sizeof(g_sB)); + Echo(2, "UpdateConVarsHook: %s %s %s", g_sB, oldCv, newCv); + + static char name[32]; name = ""; + static char value[2048]; value = ""; + + Format(name, sizeof(name), g_sB); + Format(value, sizeof(value), "%s", newCv); + TrimString(value); + + if (StrContains(newCv, "-l") == 0) { + strcopy(value, sizeof(value), value[2]); + TrimString(value); + g_Cvars.SetString(name, value, true); + } + + else if (StrContains(newCv, "-u") == 0) { + strcopy(value, sizeof(value), value[2]); + TrimString(value); + g_Cvars.Remove(name); + } + + g_Cvars.GetString(name, value, sizeof(value)); + if (!StrEqual(newCv, value)) { + SetConVarString(convar, value); + return; + } + + if (name[0] == 'a') { + if (name[4] == 'a') { + if (name[8] == 'h' && StrEqual(name, "abm_autohard")) { + g_AutoHard = GetConVarInt(g_cvAutoHard); + AutoSetTankHp(); + } + + else if (name[8] == 'm' && StrEqual(name, "abm_automodel")) { + g_AutoModel = GetConVarInt(g_cvAutoModel); + } + } + + else if (name[4] == 'c' && StrEqual(name, "abm_consumable")) { + GetConVarString(g_cvConsumable, g_Consumable, sizeof(g_Consumable)); + } + + else if (name[4] == 'h' && StrEqual(name, "abm_healitem")) { + GetConVarString(g_cvHealItem, g_HealItem, sizeof(g_HealItem)); + } + + else if (name[4] == 'i' && StrEqual(name, "abm_identityfix")) { + g_IdentityFix = GetConVarInt(g_cvIdentityFix); + } + + else if (name[4] == 'j' && StrEqual(name, "abm_joinmenu")) { + g_JoinMenu = GetConVarInt(g_cvJoinMenu); + } + + else if (name[4] == 'k' && StrEqual(name, "abm_keepdead")) { + g_KeepDead = GetConVarInt(g_cvKeepDead); + } + + else if (name[4] == 'l' && StrEqual(name, "abm_loglevel")) { + g_LogLevel = GetConVarInt(g_cvLogLevel); + } + + else if (name[4] == 'm' && StrEqual(name, "abm_minplayers")) { + g_MinPlayers = GetConVarInt(g_cvMinPlayers); + } + + else if (name[4] == 'o' && StrEqual(name, "abm_offertakeover")) { + g_OfferTakeover = GetConVarInt(g_cvOfferTakeover); + } + + else if (name[4] == 'p' && StrEqual(name, "abm_primaryweapon")) { + GetConVarString(g_cvPrimaryWeapon, g_PrimaryWeapon, sizeof(g_PrimaryWeapon)); + } + + else if (name[4] == 'r' && StrEqual(name, "abm_respawndelay")) { + g_RespawnDelay = GetConVarFloat(g_cvRespawnDelay); + + StringMapSnapshot keys = g_QDB.Snapshot(); + for (int i; i < keys.Length; i++) { + keys.GetKey(i, g_sB, sizeof(g_sB)); + g_QDB.GetValue(g_sB, g_QRecord); + g_QRecord.SetValue("rdelay", g_RespawnDelay, true); + } + + delete keys; + } + + else if (name[4] == 's') { + if (name[5] == 'e' && StrEqual(name, "abm_secondaryweapon")) { + GetConVarString(g_cvSecondaryWeapon, g_SecondaryWeapon, sizeof(g_SecondaryWeapon)); + } + + else if (name[5] == 'p' && StrEqual(name, "abm_spawninterval")) { + g_SpawnInterval = GetConVarInt(g_cvSpawnInterval); + } + + else if (name[5] == 't' && StrEqual(name, "abm_stripkick")) { + g_StripKick = GetConVarInt(g_cvStripKick); + } + } + + else if (name[4] == 't') { + if (name[5] == 'a' && StrEqual(name, "abm_tankchunkhp")) { + g_TankChunkHp = GetConVarInt(g_cvTankChunkHp); + AutoSetTankHp(); + } + + else if (name[5] == 'e' && StrEqual(name, "abm_teamlimit")) { + g_TeamLimit = GetConVarInt(g_cvTeamLimit); + } + + else if (name[5] == 'h' && StrEqual(name, "abm_throwable")) { + GetConVarString(g_cvThrowable, g_Throwable, sizeof(g_Throwable)); + } + } + + else if (name[4] == 'u' && StrEqual(name, "abm_unlocksi")) { + g_UnlockSI = GetConVarInt(g_cvUnlockSI); + + switch (g_UnlockSI) { + case 2: VDOUnlocker(); + default: RestoreVDOU(); + } + } + + else if (name[4] == 'z' && StrEqual(name, "abm_zoey")) { + g_Zoey = GetConVarInt(g_cvZoey); + } + } + + else if (name[0] == 'l' && StrEqual(name, "l4d2_directoroptions_overwrite")) { + g_VDOUCurVal = value; + + if (g_UnlockSI != 2) { + g_VDOUOrigin = value; + SetConVarString(g_cvVDOUOrigin, value); + } + } + + else if (name[0] == 'm' && StrEqual(name, "mp_gamemode")) { + UpdateGameMode(); + } + + else if (name[0] == 's' && StrEqual(name, "sv_consistency")) { + GetConVarString(g_cvConsistency, g_consistency, sizeof(g_consistency)); + } + + else if (name[5] == 'w' && StrEqual(name, "_abm_wrapswitch")) { + g_WrapSwitch = GetConVarInt(g_cvWrapSwitch); + } +} + +int GetGameType() { + Echo(2, "GetGameType"); + + // 0: coop 1: versus 2: scavenge 3: survival + GetConVarString(g_cvGameMode, g_sB, sizeof(g_sB)); + + switch (g_sB[0]) { + case 'c': { + if (StrEqual(g_sB, "coop")) return 0; + else if (StrEqual(g_sB, "community1")) return 0; // Special Delivery + else if (StrEqual(g_sB, "community2")) return 0; // Flu Season + else if (StrEqual(g_sB, "community3")) return 1; // Riding My Survivor + else if (StrEqual(g_sB, "community4")) return 3; // Nightmare + else if (StrEqual(g_sB, "community5")) return 0; // Death's Door + } + + case 'm': { + if (StrEqual(g_sB, "mutation1")) return 0; // Last Man on Earth + else if (StrEqual(g_sB, "mutation2")) return 0; // Headshot! + else if (StrEqual(g_sB, "mutation3")) return 0; // Bleed Out + else if (StrEqual(g_sB, "mutation4")) return 0; // Hard Eight + else if (StrEqual(g_sB, "mutation5")) return 0; // Four Swordsmen + else if (StrEqual(g_sB, "mutation7")) return 0; // Chainsaw Massacre + else if (StrEqual(g_sB, "mutation8")) return 0; // Ironman + else if (StrEqual(g_sB, "mutation9")) return 0; // Last Gnome on Earth + else if (StrEqual(g_sB, "mutation10")) return 0; // Room for One + else if (StrEqual(g_sB, "mutation11")) return 1; // Healthpackalypse + else if (StrEqual(g_sB, "mutation12")) return 1; // Realism Versus + else if (StrEqual(g_sB, "mutation13")) return 2; // Follow the Liter + else if (StrEqual(g_sB, "mutation14")) return 0; // Gib Fest + else if (StrEqual(g_sB, "mutation15")) return 1; // Versus Survival + else if (StrEqual(g_sB, "mutation16")) return 0; // Hunting Party + else if (StrEqual(g_sB, "mutation17")) return 0; // Lone Gunman + else if (StrEqual(g_sB, "mutation18")) return 1; // Bleed Out Versus + else if (StrEqual(g_sB, "mutation19")) return 1; // Taaannnkk! + else if (StrEqual(g_sB, "mutation20")) return 0; // Healing Gnome + } + + case 'r': { + if (StrEqual(g_sB, "realism")) return 0; + } + + case 's': { + if (StrEqual(g_sB, "scavenge")) return 2; + else if (StrEqual(g_sB, "survival")) return 3; + } + + case 't': { + if (StrEqual(g_sB, "teamscavenge")) return 2; + else if (StrEqual(g_sB, "teamversus")) return 1; + } + + case 'v': { + if (StrEqual(g_sB, "versus")) return 1; + } + } + + return -1; +} + +void UpdateGameMode() { + Echo(2, "UpdateGameMode"); + + switch (GetGameType()) { + case 0: { + g_IsCoop = true; + g_IsVs = false; + } + + case 1, 2: { + g_IsVs = true; + g_IsCoop = false; + } + + case 3: { + g_IsVs = false; + g_IsCoop = false; + } + } +} + +void SetVDOU(char[] val, any ...) { + Echo(2, "SetVDOU"); + + static const char tmp[128] = "\ + DominatorLimit=%s;\ + MaxSpecials=%s;\ + BoomerLimit=%s;\ + SmokerLimit=%s;\ + HunterLimit=%s;\ + ChargerLimit=%s;\ + SpitterLimit=%s;\ + JockeyLimit=%s;"; + + VFormat(val, sizeof(tmp), tmp, 2); +} + +void VDOUnlocker() { + Echo(2, "VDOUnlocker"); + + if (g_cvVDOUHandle != null) { + static bool restore = false; + g_MaxMates = CountTeamMates(2); + + if (g_UnlockSI == 2 && g_MaxMates > 4) { + static char m[3]; static char l[3]; + Format(m, sizeof(m), "%d", g_MaxMates); + Format(l, sizeof(l), "%d", g_SILimits); + SetVDOU(g_VDOUCurVal, m, m, l, l, l, l, l, l); + SetConVarString(g_cvVDOUHandle, g_VDOUCurVal); + restore = true; + } + + else if (restore) { + RestoreVDOU(); + restore = false; + } + } +} + +void RestoreVDOU() { + Echo(2, "RestoreVDOU"); + + if (!g_ADFreeze && g_cvVDOUHandle != null) { + static char origin[2048]; + origin = g_VDOUOrigin; + + SetVDOU(g_VDOUCurVal, "","","","","","","",""); + SetConVarString(g_cvVDOUHandle, g_VDOUCurVal); + SetConVarString(g_cvVDOUHandle, origin); + } +} + +void AutoSetTankHp() { + Echo(2, "AutoSetTankHp"); + + static int tankHp; + GetConVarDefault(g_cvTankHealth, g_sB, sizeof(g_sB)); + tankHp = StringToInt(g_sB); + + if (g_TankChunkHp != 0 && (g_IsCoop || g_IsVs)) { + if (g_AutoHard == 2 || g_MaxMates > 4 && g_AutoHard == 1) { + tankHp = g_MaxMates * g_TankChunkHp; + } + } + + SetConVarInt(g_cvTankHealth, tankHp); +} + +public void OnConfigsExecuted() { + Echo(2, "OnConfigsExecuted"); + + // extend the base cfg with a map specific cfg + GetCurrentMap(g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "cfg/sourcemod/abm/%s.cfg", g_sB); + + if (FileExists(g_sB, true)) { + strcopy(g_sB, sizeof(g_sB), g_sB[4]); + ServerCommand("exec \"%s\"", g_sB); + Echo(1, "Extending ABM: %s", g_sB); + } + + // some servers don't pick up on this automatically + else if (FileExists("cfg/sourcemod/abm.cfg", true)) { + ServerCommand("exec \"sourcemod/abm.cfg\""); + } +} + +public void OnClientPostAdminCheck(int client) { + Echo(2, "OnClientPostAdminCheck: %d", client); + + if (!IsFakeClient(client)) { + if (GetQRecord(client) && !g_update) { + return; + } + + if (IsAdmin(client) || !GetQRecord(client)) { + SetQRecord(client, true); + } + + else { + int status = g_status; + int onteam = g_onteam; + + if (SetQRecord(client, true) >= 0) { + g_QRecord.SetValue("status", status, true); + g_QRecord.SetValue("onteam", onteam, true); + } + } + + if (g_JoinMenu == 2 || g_JoinMenu == 1 && IsAdmin(client)) { + GoIdle(client, 1); + menuArg0 = client; + SwitchTeamHandler(client, 1, ""); + } + + else if (CountTeamMates(2) >= 1) { + CreateTimer(0.1, TakeoverTimer, client); + CreateTimer(0.5, AutoIdleTimer, client, TIMER_REPEAT); + } + } +} + +public Action AutoIdleTimer(Handle timer, int client) { + Echo(2, "AutoIdleTimer: %d", client); + + if (g_IsVs || !IsClientValid(client)) { + return Plugin_Stop; + } + + static int onteam; + onteam = GetClientTeam(client); + + if (onteam >= 2) { + if (onteam == 2) { + GoIdle(client, 0); + } + + return Plugin_Stop; + } + + return Plugin_Continue; +} + +public void GoIdleHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "GoIdleHook: %s", name); + int player = GetEventInt(event, "player"); + int client = GetClientOfUserId(player); + + if (GetQRecord(client)) { + switch (g_onteam) { + case 2: GoIdle(client); + case 3: SwitchTeam(client, 3); + } + } +} + +void GoIdle(int client, int onteam=0) { + Echo(2, "GoIdle: %d %d", client, onteam); + + if (GetQRecord(client)) { + int spec_target; + + // going from idle survivor to infected, leaves an icon behind + if (IsClientValid(g_target, 2, 0)) { + SwitchToBot(client, g_target); + } + + if (g_onteam == 2) { + SwitchToSpec(client); + + if (onteam == 0) { + SetHumanSpecSig(g_target, client); + } + + if (onteam == 1) { + SwitchToSpec(client); + Unqueue(client); + } + + AssignModel(g_target, g_model, g_IdentityFix); + } + + else { + SwitchToSpec(client); + } + + if (g_onteam == 3 && onteam <= 1) { + g_QRecord.SetString("model", "", true); + } + + spec_target = IsClientValid(g_target, 0, 0) ? g_target : GetSafeSurvivor(client); + + if (IsClientValid(spec_target)) { + SetEntPropEnt(client, Prop_Send, "m_hObserverTarget", spec_target); + SetEntProp(client, Prop_Send, "m_iObserverMode", 5); + } + } +} + +public void CleanQDBHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "CleanQDBHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + RemoveQDBKey(client); +} + +void RemoveQDBKey(int client) { + Echo(2, "RemoveQDBKey: %d", client); + + if (GetQRecord(client)) { + SetClientName(client, g_name); + g_QRecord.SetValue("update", true, true); + g_QRecord.SetString("model", "", true); + + if (CountTeamMates(2) > g_MinPlayers) { + CreateTimer(1.0, RmBotsTimer, 1); + } + } +} + +Action RmBotsTimer(Handle timer, any asmany) { + Echo(4, "RmBotsTimer: %d", asmany); + + if (!g_IsVs) { + RmBots(asmany, 2); + } + return Plugin_Handled; +} + +bool IsAdmin(int client) { + Echo(2, "IsAdmin: %d", client); + return CheckCommandAccess( + client, "generic_admin", ADMFLAG_GENERIC, false + ); +} + +bool IsEntityValid(int ent) { + Echo(2, "IsEntityValid: %d", ent); + return (ent > MaxClients && ent <= 2048 && IsValidEntity(ent)); +} + +bool IsClientValid(int client, int onteam=0, int mtype=2) { + Echo(6, "IsClientValid: %d, %d, %d", client, onteam, mtype); + + if (client >= 1 && client <= MaxClients) { + if (IsClientConnected(client)) { + if (IsClientInGame(client)) { + + if (onteam != 0 && GetClientTeam(client) != onteam) { + return false; + } + + switch (mtype) { + case 0: return IsFakeClient(client); + case 1: return !IsFakeClient(client); + } + + return true; + } + } + } + + return false; +} + +bool CanClientTarget(int client, int target) { + Echo(2, "CanClientTarget: %d %d", client, target); + + if (client == target) { + return true; + } + + else if (!IsClientValid(client) || !IsClientValid(target)) { + return false; + } + + else if (IsFakeClient(target)) { + int manager = GetClientManager(target); + + if (manager != -1) { + if (manager == 0) { + return true; + } + + else { + return CanClientTarget(client, manager); + } + } + } + + return CanUserTarget(client, target); +} + +int GetPlayClient(int client) { + Echo(3, "GetPlayClient: %d", client); + + if (GetQRecord(client)) { + return g_target; + } + + else if (IsClientValid(client)) { + return client; + } + + return -1; +} + +int ClientHomeTeam(int client) { + Echo(2, "ClientHomeTeam: %d", client); + + if (GetQRecord(client)) { + return g_onteam; + } + + else if (IsClientValid(client)) { + return GetClientTeam(client); + } + + return -1; +} + +// ================================================================== // +// g_QDB MANAGEMENT +// ================================================================== // + +bool SetQKey(int client) { + Echo(3, "SetQKey: %d", client); + + if (IsClientValid(client, 0, 1)) { + if (GetClientAuthId(client, AuthId_Steam2, g_QKey, sizeof(g_QKey), true)) { + return true; + } + } + + return false; +} + +bool GetQRtmp(int client) { + Echo(3, "GetQRtmp: %d", client); + + bool result; + static char QKey[64]; + QKey = g_QKey; + + if (SetQKey(client)) { + if (g_QDB.GetValue(g_QKey, g_QRtmp)) { + + if (IsClientValid(client) && IsPlayerAlive(client)) { + GetClientAbsOrigin(client, g_tmpOrigin); + g_QRtmp.SetArray("origin", g_tmpOrigin, sizeof(g_tmpOrigin), true); + } + + g_QRtmp.GetValue("client", g_tmpClient); + g_QRtmp.GetValue("target", g_tmpTarget); + g_QRtmp.GetValue("lastid", g_tmpLastid); + g_QRtmp.GetValue("onteam", g_tmpOnteam); + g_QRtmp.GetValue("queued", g_tmpQueued); + g_QRtmp.GetValue("inspec", g_tmpInspec); + g_QRtmp.GetValue("status", g_tmpStatus); + g_QRtmp.GetValue("update", g_tmpUpdate); + g_QRtmp.GetValue("rdelay", g_tmpRdelay); + g_QRtmp.GetString("ghost", g_tmpGhost, sizeof(g_tmpGhost)); + g_QRtmp.GetString("model", g_tmpModel, sizeof(g_tmpModel)); + g_QRtmp.GetString("name", g_tmpName, sizeof(g_tmpName)); + + if (g_tmpModel[0] == EOS || g_tmpOnteam == 3) { + GetBotCharacter(g_tmpTarget, g_tmpModel); + g_QRtmp.SetString("model", g_tmpModel, true); + + if (!g_IsVs && g_tmpOnteam == 3 && IsPlayerAlive(client)) { + SetClientName(client, g_tmpModel); + } + } + + result = true; + } + } + + g_QKey = QKey; + return result; +} + +bool GetQRecord(int client) { + Echo(3, "GetQRecord: %d", client); + + if (SetQKey(client)) { + if (g_QDB.GetValue(g_QKey, g_QRecord)) { + + if (IsClientValid(client) && IsPlayerAlive(client)) { + GetClientAbsOrigin(client, g_origin); + g_QRecord.SetArray("origin", g_origin, sizeof(g_origin), true); + } + + g_QRecord.GetValue("client", g_client); + g_QRecord.GetValue("target", g_target); + g_QRecord.GetValue("lastid", g_lastid); + g_QRecord.GetValue("onteam", g_onteam); + g_QRecord.GetValue("queued", g_queued); + g_QRecord.GetValue("inspec", g_inspec); + g_QRecord.GetValue("status", g_status); + g_QRecord.GetValue("update", g_update); + g_QRecord.GetValue("rdelay", g_rdelay); + g_QRecord.GetString("ghost", g_ghost, sizeof(g_ghost)); + g_QRecord.GetString("model", g_model, sizeof(g_model)); + g_QRecord.GetString("name", g_name, sizeof(g_name)); + + if (g_model[0] == EOS || g_onteam == 3) { + GetBotCharacter(g_target, g_model); + g_QRecord.SetString("model", g_model, true); + + if (!g_IsVs && g_onteam == 3 && IsPlayerAlive(client)) { + SetClientName(client, g_model); + } + } + + return true; + } + } + + return false; +} + +bool NewQRecord(int client) { + Echo(3, "NewQRecord: %d", client); + + g_QRecord = new StringMap(); + + GetClientAbsOrigin(client, g_origin); + g_QRecord.SetArray("origin", g_origin, sizeof(g_origin), true); + g_QRecord.SetValue("client", client, true); + g_QRecord.SetValue("target", client, true); + g_QRecord.SetValue("lastid", client, true); + g_QRecord.SetValue("onteam", GetClientTeam(client), true); + g_QRecord.SetValue("queued", false, true); + g_QRecord.SetValue("inspec", false, true); + g_QRecord.SetValue("status", true, true); + g_QRecord.SetValue("update", false, true); + g_QRecord.SetValue("rdelay", g_RespawnDelay, true); + g_QRecord.SetString("ghost", "", true); + g_QRecord.SetString("model", "", true); + + GetClientName(client, g_name, sizeof(g_name)); + g_QRecord.SetString("name", g_name, true); + return true; +} + +int SetQRecord(int client, bool update=false) { + Echo(3, "SetQRecord: %d %d", client, update); + + int result = -1; + + if (SetQKey(client)) { + if (g_QDB.GetValue(g_QKey, g_QRecord) && !update) { + result = 0; + } + + else if (NewQRecord(client)) { + GetClientName(client, g_pN, sizeof(g_pN)); + Echo(0, "AUTH ID: %s, (%s) ADDED TO QDB.", g_QKey, g_pN); + g_QDB.SetValue(g_QKey, g_QRecord, true); + result = 1; + } + + GetQRecord(client); + } + + return result; +} + +void QueueUp(int client, int onteam) { + Echo(2, "QueueUp: %d %d", client, onteam); + + if (onteam >= 2 && onteam <= 3 && GetQRecord(client)) { + Unqueue(client); + + switch (onteam) { + case 2: g_sQueue.Push(client); + case 3: g_iQueue.Push(client); + } + + g_QRecord.SetValue("target", client, true); + g_QRecord.SetValue("inspec", false, true); + g_QRecord.SetValue("onteam", onteam, true); + g_QRecord.SetValue("queued", true, true); + } +} + +void Unqueue(int client) { + Echo(2, "Unqueue: %d", client); + + if (GetQRecord(client)) { + g_QRecord.SetValue("queued", false, true); + + int iLength = g_iQueue.Length; + int sLength = g_sQueue.Length; + + if (iLength > 0) { + for (int i = iLength - 1; i > -1; i--) { + if (g_iQueue.Get(i) == client) { + g_iQueue.Erase(i); + } + } + } + + if (sLength > 0) { + for (int i = sLength - 1; i > -1; i--) { + if (g_sQueue.Get(i) == client) { + g_sQueue.Erase(i); + } + } + } + } +} + +public Action OnSpawnHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "OnSpawnHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int target = GetClientOfUserId(userid); + + GetClientName(target, g_pN, sizeof(g_pN)); + if (g_pN[0] == 'A' && StrContains(g_pN, "ABMclient") >= 0) { + return Plugin_Handled; + } + + int onteam = GetClientTeam(target); + int client; + + if (onteam == 3) { + int zClass = GetEntProp(target, Prop_Send, "m_zombieClass"); + + if (g_iQueue.Length == 0 && g_UnlockSI != 0) { + if (CountTeamMates(3) > g_MaxMates && zClass != 8) { + if (IsFakeClient(target)) { + KickClient(target); + return Plugin_Handled; + } + } + } + + if (!g_IsVs) { + if (g_AssistedSpawning) { + if (zClass == 8) { + + int j = 1; + static int i = 1; + + for (; i <= MaxClients + 1; i++) { + if (j++ == MaxClients + 1) { // join 3 Tank requires +1 + return Plugin_Handled; + } + + if (i > MaxClients) { + i = 1; + } + + if (GetQRecord(i) && g_onteam == 3 && !g_inspec) { + if (GetEntProp(i, Prop_Send, "m_zombieClass") != 8) { + client = i; + i++; + break; + } + } + } + + if(IsClientValid(client)) { + SwitchToBot(client, target); + } else { + CreateTimer(1.0, TankAssistTimer, target, TIMER_REPEAT); + } + } + } + + if (g_iQueue.Length > 0) { + client = g_iQueue.Get(0); + if (IsClientValid(client) && !IsPlayerAlive(client)) { + SwitchToBot(client, target); + } + + return Plugin_Handled; + } + } + } + + if (onteam == 2) { + // AutoModeling now takes place in OnEntityCreated + CreateTimer(0.4, OnSpawnHookTimer, target); + return Plugin_Handled; + } + + return Plugin_Continue; +} + +public Action TankAssistTimer(Handle timer, any client) { + Echo(4, "TankAssistTimer: %d", client); + + /* + * Human players on the infected team in modes that do not officially + * support them, can get Tanks stuck in "stasis" until they die. This + * function works around the issue by watching Tanks for movement. If + * a Tank does not move in 11 seconds, it is replaced with another. + */ + + float origin[3]; + static const float nullOrigin[3]; + static int times[MAXPLAYERS + 1] = {11, ...}; + static float origins[MAXPLAYERS + 1][3]; + static int i; + + if (IsClientValid(client)) { + i = times[client]--; + + if (i == 11) { + GetClientAbsOrigin(client, origins[client]); + return Plugin_Continue; + } + + else if (i >= 0) { + GetClientAbsOrigin(client, origin); + + if (origin[0] == origins[client][0]) { + if (i == 0) { + TeleportEntity(client, nullOrigin, NULL_VECTOR, NULL_VECTOR); + ForcePlayerSuicide(client); + AddInfected("tank"); + } + + return Plugin_Continue; + } + } + } + + i = times[client] = 11; + return Plugin_Stop; +} + +public Action ForceSpawnTimer(Handle timer, any client) { + Echo(4, "ForceSpawnTimer: %d", client); + + static int times[MAXPLAYERS + 1] = {20, ...}; + static int i; + + if (IsClientValid(client)) { + i = times[client]--; + + if (GetEntProp(client, Prop_Send, "m_zombieClass") != 8) { + i = times[client] = 20; + return Plugin_Stop; + } + + if (GetEntProp(client, Prop_Send, "m_isGhost") == 1) { + if (i >= 1) { + PrintHintText(client, "FORCING SPAWN IN: %d", i); + return Plugin_Continue; + } + + if (GetEntProp(client, Prop_Send, "m_ghostSpawnState") <= 2) { + SetEntProp(client, Prop_Send, "m_isGhost", 0); + } + + return Plugin_Continue; + } + } + + if (!IsEntityValid(GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"))) { + i = CreateEntityByName("weapon_tank_claw"); + if (IsEntityValid(i) && DispatchSpawn(i)) { + EquipPlayerWeapon(client, i); + } + } + + i = times[client] = 20; + PrintHintText(client, "KILL ALL HUMANS"); + return Plugin_Stop; +} + +Action OnSpawnHookTimer(Handle timer, any target) { + Echo(2, "OnSpawnHookTimer: %d", target); + + if (g_sQueue.Length > 0) { + SwitchToBot(g_sQueue.Get(0), target); + } + return Plugin_Handled; +} + +public void OnDeathHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(4, "OnDeathHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + + if (GetQRecord(client)) { + GetClientAbsOrigin(client, g_origin); + g_QRecord.SetValue("target", client, true); + g_QRecord.SetArray("origin", g_origin, sizeof(g_origin), true); + g_QRecord.SetValue("status", false, true); + bool offerTakeover; + + switch (g_onteam) { + case 3: { + g_QRecord.SetString("model", "", true); + + if (!g_IsVs) { + QueueSI(client, g_rdelay); + + switch (g_OfferTakeover) { + case 2, 3: { + GoIdle(client, 1); + offerTakeover = true; + } + + default: SwitchTeam(client, 3); + } + } + } + + case 2: { + switch (g_OfferTakeover) { + case 1, 3: offerTakeover = true; + } + } + } + + if (offerTakeover) { + GenericMenuCleaner(client); + menuArg0 = client; + SwitchToBotHandler(client, 1); + } + } + + else if (GetQRecord(GetRealClient(client))) { + g_QRecord.SetValue("status", false, true); + } +} + +public void QTeamHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "QTeamHook: %s", name); + + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + int onteam = GetEventInt(event, "team"); + + if (GetQRecord(client)) { + + if (!g_IsVs && g_onteam == 3) { + SetClientName(client, g_name); + } + + if (onteam >= 2) { + g_QRecord.SetValue("inspec", false, true); + g_QRecord.SetValue("target", client, true); + g_QRecord.SetValue("onteam", onteam, true); + + // attempt to apply a model asap + if (g_ADFreeze && onteam == 2 && g_model[0] != EOS) { + AssignModel(client, g_model, g_IdentityFix); + } + } + + if (onteam <= 1) { // cycling requires 0.2 or higher? + CreateTimer(0.2, QTeamHookTimer, client); + } + } +} + +Action QTeamHookTimer(Handle timer, any client) { + Echo(2, "QTeamHookTimer: %d", client); + + if (GetQRecord(client) && !g_inspec) { + if (g_onteam == 2) { + if (IsClientValid(g_target) && g_target != client) { + SetHumanSpecSig(g_target, client); + } + } + } + return Plugin_Handled; +} + +public void QAfkHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "QAfkHook: %s", name); + + int client = GetClientOfUserId(GetEventInt(event, "player")); + int target = GetClientOfUserId(GetEventInt(event, "bot")); + int clientTeam = GetClientTeam(client); + int targetTeam = GetClientTeam(target); + + if (GetQRecord(client)) { + int onteam = GetClientTeam(client); + + if (onteam == 2) { + g_QRecord.SetValue("target", target, true); + AssignModel(target, g_model, g_IdentityFix); + } + } + + if (targetTeam == 2 && IsClientValid(client)) { + if (IsClientInKickQueue(client)) { + if (client && target && clientTeam == targetTeam) { + int safeClient = GetSafeSurvivor(target); + RespawnClient(target, safeClient); + } + } + } +} + +public void QBakHook(Handle event, const char[] name, bool dontBroadcast) { + Echo(2, "QBakHook: %s", name); + + int client = GetClientOfUserId(GetEventInt(event, "player")); + int target = GetClientOfUserId(GetEventInt(event, "bot")); + + if (GetQRecord(client)) { + if (g_target != target) { + g_QRecord.SetValue("lastid", target); + g_QRecord.SetValue("target", client); + } + + if (GetClientTeam(client) == 2) { + AssignModel(client, g_model, g_IdentityFix); + } + } +} + +// ================================================================== // +// UNORGANIZED AS OF YET +// ================================================================== // + +void StripClient(int client) { + Echo(2, "StripClient: %d", client); + + if (IsClientValid(client)) { + if (GetClientTeam(client) == 2) { + for (int i = 4; i >= 0; i--) { + StripClientSlot(client, i); + } + } + } +} + +void StripClientSlot(int client, int slot) { + Echo(2, "StripClientSlot: %d %d", client, slot); + + client = GetPlayClient(client); + + if (IsClientValid(client)) { + if (GetClientTeam(client) == 2) { + int ent = GetPlayerWeaponSlot(client, slot); + if (IsEntityValid(ent)) { + RemovePlayerItem(client, ent); + AcceptEntityInput(ent,"kill"); + } + } + } +} + +void RespawnClient(int client, int target=0) { + Echo(2, "RespawnClient: %d %d", client, target); + + if (!IsClientValid(client)) { + return; + } + + else if (GetQRecord(client)) { + if (g_onteam == 3) { + Takeover(client, 3); + return; + } + } + + client = GetPlayClient(client); + target = GetPlayClient(target); + bool weaponizePlayer = true; + static const float pos0[3]; + static float pos1[3]; + pos1 = pos0; + + if (client != GetRealClient(target) && IsClientValid(target)) { + GetClientAbsOrigin(target, pos1); + } + + else if (GetQRtmp(client)) { + pos1 = g_origin; + if (pos1[0] != 0 && pos1[1] != 0 && pos1[2] != 0) { + weaponizePlayer = false; + } + } + + if (pos1[0] != 0 && pos1[1] != 0 && pos1[2] != 0) { + RoundRespawnSig(client); + + if (!g_ADFreeze && weaponizePlayer) { + QuickCheat(client, "give", g_PrimaryWeapon); + QuickCheat(client, "give", g_SecondaryWeapon); + QuickCheat(client, "give", g_Throwable); + QuickCheat(client, "give", g_HealItem); + QuickCheat(client, "give", g_Consumable); + } + + TeleportEntity(client, pos1, NULL_VECTOR, NULL_VECTOR); + } +} + +void TeleportClient(int client, int target) { + Echo(2, "TeleportClient: %d %d", client, target); + + float origin[3]; + client = GetPlayClient(client); + target = GetPlayClient(target); + + if (IsClientValid(client) && IsClientValid(target)) { + GetClientAbsOrigin(target, origin); + TeleportEntity(client, origin, NULL_VECTOR, NULL_VECTOR); + } +} + +int GetSafeSurvivor(int client) { + Echo(2, "GetSafeSurvivor: %d", client); + + + float lowestIntensity; + int lowestClient = -1; + + for (int i = 1; i <= MaxClients; i++) { + if (IsClientValid(i) && i != client && IsPlayerAlive(i) && GetClientTeam(i) == 2) { + + // Skip if incapped or on a ledge + if (GetEntProp(i, Prop_Send, "m_isHangingFromLedge") || GetEntProp(i, Prop_Send, "m_isIncapacitated")) { + continue; + } + + float intensity = L4D_GetPlayerIntensity(i); + if(intensity < lowestIntensity || lowestClient == -1) { + lowestIntensity = intensity; + lowestClient = i; + } + } + } + + return lowestClient; +} + +bool AddSurvivor() { + Echo(2, "AddSurvivor"); + + if (GetClientCount(false) >= MaxClients - 1) { + return false; + } + + bool result = false; + if(GetFeatureStatus(FeatureType_Native, "NextBotCreatePlayerBotSurvivorBot") != FeatureStatus_Available) { + // Fallback to manual kick trick if no CreateSurvivorBot API + int i = CreateFakeClient("ABMclient2"); + if (IsClientValid(i)) { + if (DispatchKeyValue(i, "classname", "SurvivorBot")) { + ChangeClientTeam(i, 2); + + if (DispatchSpawn(i)) { + result = true; + } + } + KickClient(i); + } + } else { + result = CreateSurvivorBot() > 0; + } + return result; +} + +bool AddInfected(char model[32]="", int version=0) { + Echo(2, "AddInfected: '%s' %d", model, version); + + if (GetClientCount(false) >= MaxClients - 1) { + return false; + } + + CleanSIName(model); + int i = CreateFakeClient("ABMclient3"); + + if (IsClientValid(i)) { + ChangeClientTeam(i, 3); + Format(g_sB, sizeof(g_sB), "%s auto area", model); + + switch (version) { + case 0: QuickCheat(i, "z_spawn_old", g_sB); + case 1: QuickCheat(i, "z_spawn", g_sB); + } + + KickClient(i); + return true; + } + + return false; +} + +void GhostsModeProtector(int state=0) { + Echo(2, "GhostsModeProtector: %d", state); + // CAREFUL: 0 starts this function and you must close it with 1 or + // risk breaking things. Close this with 1 immediately when done. + + // e.g., + // GhostsModeProtector(0); + // z_spawn_old tank auto; + // GhostsModeProtector(1); + + static int ghosts[MAXPLAYERS + 1]; + static int lifeState[MAXPLAYERS + 1]; // prevent early rise from the dead + + switch (state) { + case 0: { + for (int i = 1; i <= MaxClients; i++) { + if (GetQRtmp(i) && g_tmpOnteam == 3) { + if (GetEntProp(i, Prop_Send, "m_isGhost") == 1 || g_tmpQueued) { + SetEntProp(i, Prop_Send, "m_isGhost", 0); + ghosts[i] = 1; + } + + if (GetEntProp(i, Prop_Send, "m_lifeState") == 1) { + SetEntProp(i, Prop_Send, "m_lifeState", 0); + g_QRtmp.SetValue("status", 1, true); + lifeState[i] = 1; + } + } + } + } + + case 1: { + for (int i = 1; i <= MaxClients; i++) { + if (ghosts[i] == 1) { + SetEntProp(i, Prop_Send, "m_isGhost", 1); + } + + if (lifeState[i] == 1) { + SetEntProp(i, Prop_Send, "m_lifeState", 1); + } + + ghosts[i] = 0; + lifeState[i] = 0; + } + } + } + + if (state == 0) { + RequestFrame(GhostsModeProtector, 1); + } +} + +void CleanSIName(char model[32]) { + Echo(2, "CleanSIName: %s", model); + + int i; + static char tmpModel[32]; + + if (model[0] != EOS) { + for (i = 0; i < sizeof(g_InfectedNames); i++) { + strcopy(tmpModel, sizeof(tmpModel), g_InfectedNames[i]); + if (StrContains(tmpModel, model, false) == 0) { + model = tmpModel; + return; + } + } + + if (StrContains("Tank", model, false) == 0) { + model = "Tank"; + return; + } + } + + i = GetRandomInt(0, sizeof(g_InfectedNames) - 1); + strcopy(model, sizeof(model), g_InfectedNames[i]); +} + +void SwitchToSpec(int client, int onteam=1) { + Echo(2, "SwitchToSpec: %d %d", client, onteam); + + if (GetQRecord(client)) { + // clearparent jockey bug switching teams (thanks to Lux) + AcceptEntityInput(client, "clearparent"); + g_QRecord.SetValue("inspec", true, true); + ChangeClientTeam(client, onteam); + + if (GetRealClient(g_target) == client) { + if (g_onteam == 2) { + AssignModel(g_target, g_model, g_IdentityFix); + } + + if (HasEntProp(g_target, Prop_Send, "m_humanSpectatorUserID")) { + SetEntProp(g_target, Prop_Send, "m_humanSpectatorUserID", 0); + } + } + } +} + +void QuickCheat(int client, char [] cmd, char [] arg) { + Echo(2, "QuickCheat: %d %s %s", client, cmd, arg); + + int flags = GetCommandFlags(cmd); + SetCommandFlags(cmd, flags & ~FCVAR_CHEAT); + FakeClientCommand(client, "%s %s", cmd, arg); + SetCommandFlags(cmd, flags); +} + +void SwitchToBot(int client, int target, bool si_ghost=true) { + Echo(2, "SwitchToBot: %d %d %d", client, target, si_ghost); + + if (IsClientValid(target, 0, 0)) { + Unqueue(client); + + switch (GetClientTeam(target)) { + case 2: TakeoverBotSig(client, target); + case 3: TakeoverZombieBotSig(client, target, si_ghost); + } + } +} + +void Takeover(int client, int onteam) { + Echo(2, "Takeover: %d %d", client, onteam); + + if (GetQRecord(client)) { + if (IsClientValid(g_target, 0, 0)) { + if (client != g_target && GetClientTeam(g_target) == onteam) { + SwitchToBot(client, g_target); + return; + } + } + + int nextBot; + nextBot = GetNextBot(onteam, 0, true); + + if (IsClientValid(nextBot)) { + SwitchToBot(client, nextBot); + return; + } + + switch (onteam) { + case 2: { + if (g_KeepDead == 1 && !g_status) { + if (g_onteam == 2 && CountTeamMates(2, 0) == 0) { + ChangeClientTeam(client, 2); + ForcePlayerSuicide(client); // without this player may spawn if survivors are close to start + } + + return; + } + + QueueUp(client, 2); + AddSurvivor(); + } + + case 3: { + QueueUp(client, 3); + AddInfected(); + } + } + } +} + +public Action TakeoverTimer(Handle timer, any client) { + Echo(4, "TakeoverTimer: %d", client); + + if (CountTeamMates(2) <= 0) { + return Plugin_Handled; + } + + static int team2; + static int team3; + static int teamX; + + if (GetQRecord(client)) { + if (GetClientTeam(client) >= 2) { + return Plugin_Handled; + } + + teamX = 2; + if (g_onteam == 3) { + teamX = 3; + } + + if (g_IsVs && g_onteam <= 1) { + team2 = CountTeamMates(2, 1); + team3 = CountTeamMates(3, 1); + + if (team3 < team2) { + teamX = 3; + } + } + + if (CountTeamMates(teamX, 1) < g_TeamLimit) { + Takeover(client, teamX); + } + } + + return Plugin_Handled; +} + +int CountTeamMates(int onteam, int mtype=2) { + Echo(2, "CountTeamMates: %d %d", onteam, mtype); + + // mtype 0: counts only bots + // mtype 1: counts only humans + // mtype 2: counts all players on team + + static int clients, bots, humans; + clients = bots = humans = 0; + + for (int i = 1; i <= MaxClients; i++) { + if (IsClientValid(i, onteam)) { + clients++; + + if(IsFakeClient(GetRealClient(i))) { + bots++; + } else { + humans++; + } + } + } + + switch (mtype) { + case 0: clients = bots; + case 1: clients = humans; + } + + return clients; +} + +int GetClientManager(int target) { + Echo(4, "GetClientManager: %d", target); + + int result = -1; + target = GetRealClient(target); + + if (IsClientValid(target)) { + result = IsFakeClient(target) ? 0 : target; + } + + return result; +} + +int GetNextBot(int onteam, int start=1, bool alive=false) { + Echo(2, "GetNextBot: %d %d %d", onteam, start, alive); + + static int bot, j; + bot = 0; + j = start; + + if (onteam == 3) { + alive = true; + } + + for (int i = 1; i <= MaxClients; i++) { + if (j > 32) { + j = 1; + } + + if (IsClientValid(j, onteam, 0)) { + if (GetClientManager(j) == 0) { + if (onteam == 2) { + bot = j; + } + + if (alive && IsPlayerAlive(j)) { + return j; + } + + else if (!alive) { + return j; + } + } + } + + j++; + } + + return bot; +} + +void CycleBots(int client, int onteam) { + Echo(2, "CycleBots: %d %d", client, onteam); + + if (onteam <= 1) { + return; + } + + if (GetQRecord(client)) { + int bot = GetNextBot(onteam, g_lastid, true); + if (IsClientValid(bot, onteam, 0)) { + SwitchToBot(client, bot, false); + } + } +} + +void SwitchTeam(int client, int onteam, char model[32]="") { + Echo(2, "SwitchTeam: %d %d", client, onteam); + + if (GetQRecord(client)) { + if (GetClientTeam(client) >= 2) { + if (onteam == 2 && onteam == g_onteam) { + return; // keep survivors from rejoining survivors + } + } + + if (g_onteam == 2 && onteam <= 1) { + if (IsClientValid(g_target, 0, 0)) { + SwitchToBot(client, g_target); + } + } + + switch (onteam) { + case 0: GoIdle(client, 0); + case 1: GoIdle(client, 1); + //case 4: ChangeClientTeam(client, 4); + default: { + if (onteam <= 3 && onteam >= 2) { + if (g_onteam != onteam) { + GoIdle(client, 1); + } + + g_QRecord.SetString("model", model, true); + + if (onteam == 3) { + if (g_IsVs) { // see if a proper way to get on team 2 exist + static int switches; // A Lux idea + switches = GetConVarInt(g_cvMaxSwitches); + SetConVarInt(g_cvMaxSwitches, 9999); + ChangeClientTeam(client, onteam); + SetConVarInt(g_cvMaxSwitches, switches); + return; + } + + g_QRecord.SetValue("onteam", onteam,true); + g_QRecord.SetString("ghost", model, true); + QueueSI(client, g_rdelay); + return; + } + + Takeover(client, onteam); + } + } + } + } +} + +void QueueSI(int client, float delay=1.0) { + Echo(2, "QueueSI: %d %f", client, delay); + + if (g_rDelays[client] != null) { + KillTimer(g_rDelays[client]); + g_rDelays[client] = null; + } + + g_rDelays[client] = CreateTimer( + delay, QueueSITimer, client, TIMER_REPEAT + ); +} + +Action QueueSITimer(Handle Timer, int client) { + Echo(2, "QueueSITimer: %d", client); + + if (GetQRecord(client) && g_onteam == 3) { + QueueUp(client, 3); + + if (AddInfected(g_ghost, 1)) { + g_QRecord.SetValue("rdelay", g_RespawnDelay, true); + g_QRecord.SetString("ghost", "", true); + } + } + + g_rDelays[client] = null; + return Plugin_Stop; +} + +Action MkBotsCmd(int client, int args) { + Echo(2, "MkBotsCmd: %d", client); + + switch(args) { + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + int asmany = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + int onteam = StringToInt(g_sB); + + if (onteam >= 2 || onteam <= 3) { + MkBots(asmany, onteam); + } + } + } + return Plugin_Handled; +} + +void MkBots(int asmany, int onteam) { + Echo(2, "MkBots: %d %d", asmany, onteam); + + if (asmany < 0) { + asmany = asmany * -1 - CountTeamMates(onteam); + } + + float rate; + DataPack pack = new DataPack(); + + switch (onteam) { + case 2: rate = 0.2; + case 3: rate = 0.4; + } + + g_MkBotsTimer = CreateDataTimer(rate, MkBotsTimer, pack, TIMER_REPEAT); + pack.WriteCell(asmany); + pack.WriteCell(onteam); +} + +public Action MkBotsTimer(Handle timer, Handle pack) { + Echo(2, "MkBotsTimer"); + + static int i; + + ResetPack(pack); + int asmany = ReadPackCell(pack); + int onteam = ReadPackCell(pack); + + if (i++ < asmany) { + switch (onteam) { + case 2: AddSurvivor(); + case 3: AddInfected(); + } + + return Plugin_Continue; + } + + i = 0; + g_MkBotsTimer = null; + return Plugin_Stop; +} + +Action RmBotsCmd(int client, int args) { + Echo(2, "RmBotsCmd: %d", client); + + int asmany; + int onteam; + + switch(args) { + case 1: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + onteam = StringToInt(g_sB); + asmany = MaxClients; + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + asmany = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + onteam = StringToInt(g_sB); + } + } + + if (onteam >= 2 || onteam <= 3) { + RmBots(asmany, onteam); + } + return Plugin_Handled; +} + +void RmBots(int asmany, int onteam) { + Echo(2, "RmBots: %d %d", asmany, onteam); + + int j; + + if (onteam == 0) { + onteam = asmany; + asmany = MaxClients; + } + + else if (asmany == -0) { + return; + } + + else if (asmany < 0) { + asmany += CountTeamMates(onteam); + if (asmany <= 0) { + return; + } + } + + for (int i = MaxClients; i >= 1; i--) { + if (GetClientManager(i) == 0 && GetClientTeam(i) == onteam) { + + j++; + if (g_StripKick == 1) { + StripClient(i); + } + + KickClient(i); + + if (j >= asmany) { + break; + } + } + } +} + +// ================================================================== // +// MODEL FEATURES +// ================================================================== // + + +void AutoModel(int client) { + Echo(5, "AutoModel: %d", client); + RequestFrame(_AutoModel, client); +} + +public void _AutoModel(int client) { + Echo(5, "_AutoModel: %d", client); + + if (IsClientValid(client, 2)) { + SDKUnhook(client, SDKHook_SpawnPost, AutoModel); + } + + if (g_AutoModel && IsClientValid(client, 2)) { + static int realClient; + realClient = GetRealClient(client); + if (GetQRecord(realClient) && g_model[0] != EOS) { + return; + } + + static int set, survivors, character; + set = GetSurvivorSet(client); + GetAllSurvivorModels(client); + + survivors = CountTeamMates(2); + character = g_models[GetClientModelIndex(client)]; + if (character == 0 || character < survivors / 8) { + return; + } + + for (int i = 0; i < 4; i++) { + for (int index = set; index < sizeof(g_models); index++) { + if (g_models[index] <= i) { + g_models[index]++; + AssignModel(client, g_SurvivorNames[index], g_IdentityFix); + i = 4; // we want to fall through + break; + } + + if (set != 0 && index + 1 == sizeof(g_models)) { + index=-1; set=0; + } + } + } + } +} + +int GetSurvivorSet(int client) { + Echo(6, "GetSurvivorSet: %d", client); + + if (g_survivorSetScan && IsClientValid(client, 2)) { + g_survivorSetScan = false; + g_survivorSet = GetClientModelIndex(client); + + if (g_survivorSet >= 0 && g_survivorSet <= 3) { + g_survivorSet = 0; + } else { + g_survivorSet = 4; + } + } + + return g_survivorSet; +} + +void GetAllSurvivorModels(int client=-1) { + Echo(2, "GetAllSurvivorModels"); + + static int index; + static const int models[8]; + g_models = models; + + for (int i = 1; i <= MaxClients; i++) { + if (client == i) { + continue; + } + + index = -1; + + if (GetQRecord(i) && g_onteam == 2 && g_model[0] != EOS) { + index = GetModelIndexByName(g_model, 2); + } + + else if (IsClientValid(i, 2, 0) && GetRealClient(i) == i) { + index = GetClientModelIndex(i); + } + + if (index >= 0) { + g_models[index]++; + } + } +} + +void PrecacheModels() { + Echo(2, "PrecacheModels"); + + for (int i = 0; i < sizeof(g_SurvivorPaths); i++) { + PrecacheModel(g_SurvivorPaths[i]); + } +} + +void AssignModel(int client, char [] model, int identityFix) { + Echo(2, "AssignModel: %d %s %d", client, model, identityFix); + + if (identityFix == 1 && IsClientValid(client, 2)) { + if (IsClientsModel(client, model)) { + return; + } + + int i = GetModelIndexByName(model); + int realClient = GetRealClient(client); + + if (i >= 0 && i < sizeof(g_SurvivorPaths)) { + if(i == 5) { + SetEntProp(client, Prop_Send, "m_survivorCharacter", g_Zoey); + } else { + SetEntProp(client, Prop_Send, "m_survivorCharacter", i); + } + + SetEntityModel(client, g_SurvivorPaths[i]); + Format(g_pN, sizeof(g_pN), "%s", g_SurvivorNames[i]); + + if (IsFakeClient(client)) { + SetClientInfo(client, "name", g_pN); + } + + if (GetQRecord(realClient)) { + g_QRecord.SetString("model", g_pN, true); + } + } + } +} + +int GetClientModelIndex(int client) { + Echo(3, "GetClientModelIndex: %d", client); + + if (!IsClientValid(client)) { + return -2; + } + + char modelName[64]; + + GetEntPropString(client, Prop_Data, "m_ModelName", modelName, sizeof(modelName)); + for (int i = 0; i < sizeof(g_SurvivorPaths); i++) { + if (StrEqual(modelName, g_SurvivorPaths[i], false)) { + return i; + } + } + + return -1; +} + +int GetModelIndexByName(char [] name, int onteam=2) { + Echo(2, "GetModelIndexByName: %s %d", name, onteam); + + if (onteam == 2) { + for (int i; i < sizeof(g_SurvivorNames); i++) { + if (StrContains(name, g_SurvivorNames[i], false) != -1) { + return i; + } + } + } + + else if (onteam == 3) { + for (int i; i < sizeof(g_InfectedNames); i++) { + if (StrContains(g_InfectedNames[i], name, false) != -1) { + return i; + } + } + } + + return -1; +} + +bool IsClientsModel(int client, char [] name) { + Echo(2, "IsClientsModel: %d %s", client, name); + + int modelIndex = GetClientModelIndex(client); + Format(g_sB, sizeof(g_sB), "%s", g_SurvivorNames[modelIndex]); + return StrEqual(name, g_sB); +} + +void GetBotCharacter(int client, char strBuffer[32]) { + Echo(2, "GetBotCharacter: %d", client); + + if (IsClientValid(client)) { + strBuffer = ""; + + switch (GetClientTeam(client)) { + case 2: GetSurvivorCharacter(client, strBuffer); + case 3: GetInfectedCharacter(client, strBuffer); + } + } +} + +void GetSurvivorCharacter(int client, char strBuffer[32]) { + Echo(2, "GetSurvivorCharacter: %d %s", client, strBuffer); + + GetEntPropString(client, Prop_Data, "m_ModelName", g_sB, sizeof(g_sB)); + for (int i = 0; i < sizeof(g_SurvivorPaths); i++) { + if (StrEqual(g_SurvivorPaths[i], g_sB)) { + Format(strBuffer, sizeof(strBuffer), g_SurvivorNames[i]); + break; + } + } +} + +void GetInfectedCharacter(int client, char strBuffer[32]) { + Echo(2, "GetInfectedCharacter: %d %s", client, strBuffer); + + switch (GetEntProp(client, Prop_Send, "m_zombieClass")) { + case 1: strBuffer = "Smoker"; + case 2: strBuffer = "Boomer"; + case 3: strBuffer = "Hunter"; + case 4: strBuffer = "Spitter"; + case 5: strBuffer = "Jockey"; + case 6: strBuffer = "Charger"; + case 8: strBuffer = "Tank"; + } +} + +// ================================================================== // +// BLACK MAGIC SIGNATURES. SOME SPOOKY SHIT. +// ================================================================== // + +int GetOS() { + Echo(2, "GetOS"); + return GameConfGetOffset(g_GameData, "OS"); +} + +void RoundRespawnSig(int client) { + Echo(2, "RoundRespawnSig: %d", client); + + static Handle hRoundRespawn; + if (hRoundRespawn == null) { + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(g_GameData, SDKConf_Signature, "RoundRespawn"); + hRoundRespawn = EndPrepSDKCall(); + } + + if (hRoundRespawn != null) { + SDKCall(hRoundRespawn, client); + } + + else { + PrintToChat(client, "[ABM] RoundRespawnSig Signature broken."); + SetFailState("[ABM] RoundRespawnSig Signature broken."); + } +} + +void SetHumanSpecSig(int bot, int client) { + Echo(2, "SetHumanSpecSig: %d %d", bot, client); + + static Handle hSpec; + if (hSpec == null) { + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(g_GameData, SDKConf_Signature, "SetHumanSpec"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + hSpec = EndPrepSDKCall(); + } + + if (IsClientValid(client) && IsClientValid(bot)) { + if(hSpec != null) { + SDKCall(hSpec, bot, client); + ResetClientSpecUserId(client, bot); + } + + else { + PrintToChat(client, "[ABM] SetHumanSpecSig Signature broken."); + SetFailState("[ABM] SetHumanSpecSig Signature broken."); + } + } +} + +void ResetClientSpecUserId(int client, int target) { + Echo(2, "ResetClientSpecUserId: %d %d", client, target); + + if (!IsClientValid(client) || !IsClientValid(target)) { + return; + } + + static int spec, userid; + userid = GetClientUserId(client); + + for (int i = 1; i <= MaxClients; i++) { + if (IsClientValid(i, 2, 0)) { + if (HasEntProp(i, Prop_Send, "m_humanSpectatorUserID")) { + spec = GetEntProp(i, Prop_Send, "m_humanSpectatorUserID"); + if (userid == spec && i != target) { + SetEntProp(i, Prop_Send, "m_humanSpectatorUserID", 0); + } + } + } + } +} + +void State_TransitionSig(int client, int mode) { + Echo(2, "State_TransitionSig: %d %d", client, mode); + + static Handle hSpec; + if (hSpec == null) { + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(g_GameData, SDKConf_Signature, "State_Transition"); + PrepSDKCall_AddParameter(SDKType_PlainOldData , SDKPass_Plain); + hSpec = EndPrepSDKCall(); + } + + if(hSpec != null) { + SDKCall(hSpec, client, mode); // mode 8, press 8 to get closer + } + + else { + PrintToChat(client, "[ABM] State_TransitionSig Signature broken."); + SetFailState("[ABM] State_TransitionSig Signature broken."); + } +} + +bool TakeoverBotSig(int client, int target) { + Echo(2, "TakeoverBotSig: %d %d", client, target); + + if (!GetQRecord(client)) { + return false; + } + + static Handle hSwitch; + + if (hSwitch == null) { + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(g_GameData, SDKConf_Signature, "TakeOverBot"); + PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); + hSwitch = EndPrepSDKCall(); + } + + if (hSwitch != null) { + if (IsClientInKickQueue(target)) { + KickClient(target); + } + + else if (IsClientValid(target, 2, 0)) { + SwitchToSpec(client); + + if (GetRealClient(target) != client) { + GetBotCharacter(target, g_model); + g_QRecord.SetString("model", g_model, true); + } + + SetHumanSpecSig(target, client); + SDKCall(hSwitch, client, true); + + GetConVarString(g_cvGameMode, g_sB, sizeof(g_sB)); + SendConVarValue(client, g_cvGameMode, g_sB); + return true; + } + } + + else { + PrintToChat(client, "[ABM] TakeoverBotSig Signature broken."); + SetFailState("[ABM] TakeoverBotSig Signature broken."); + } + + g_QRecord.SetValue("lastid", target, true); + if (GetClientTeam(client) == 1) { + QueueUp(client, 2); + } + + return false; +} + +bool TakeoverZombieBotSig(int client, int target, bool si_ghost) { + Echo(2, "TakeoverZombieBotSig: %d %d %d", client, target, si_ghost); + + if (!GetQRecord(client)) { + return false; + } + + static Handle hSwitch; + + if (hSwitch == null) { + StartPrepSDKCall(SDKCall_Player); + PrepSDKCall_SetFromConf(g_GameData, SDKConf_Signature, "TakeOverZombieBot"); + PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); + hSwitch = EndPrepSDKCall(); + } + + if (hSwitch != null) { + if (IsClientInKickQueue(target)) { + KickClient(target); + } + + else if (IsClientValid(target, 3, 0) && IsPlayerAlive(target)) { + SwitchToSpec(client); + SDKCall(hSwitch, client, target); + + if (si_ghost) { + State_TransitionSig(client, 8); + if (GetEntProp(client, Prop_Send, "m_zombieClass") == 8) { + CreateTimer(1.0, ForceSpawnTimer, client, TIMER_REPEAT); + } + } + + // trade off, see ladders, not survivors + SendConVarValue(client, g_cvGameMode, "versus"); + SendConVarValue(client, g_cvConsistency, g_consistency); + return true; + } + } + + else { + PrintToChat(client, "[ABM] TakeoverZombieBotSig Signature broken."); + SetFailState("[ABM] TakeoverZombieBotSig Signature broken."); + } + + g_QRecord.SetValue("lastid", target, true); + if (GetClientTeam(client) == 1) { + QueueUp(client, 3); + } + + return false; +} + +// ================================================================== // +// PUBLIC INTERFACE AND MENU HANDLERS +// ================================================================== // + +public Action TeleportClientCmd(int client, int args) { + Echo(2, "TeleportClientCmd: %d", client); + + int level; + + switch(args) { + case 1: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + } + + if (args) { + level = 2; + } + + TeleportClientHandler(client, level); + return Plugin_Handled; +} + +public void TeleportClientHandler(int client, int level) { + Echo(2, "TeleportClientHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "TeleportClientHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Teleport Client", 2, 1); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Teleporting", g_sB); + TeamMatesMenu(client, g_sB, 2, 1); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (GetClientTeam(menuArg0) <= 1) { + menuArg0 = GetPlayClient(menuArg0); + } + + TeleportClient(menuArg0, menuArg1); + } + + GenericMenuCleaner(client); + } + } +} + +public Action SwitchTeamCmd(int client, int args) { + Echo(2, "SwitchTeamCmd: %d", client); + + int level; + + char model[32]; + GetCmdArg(args, model, sizeof(model)); + int result = StringToInt(model); + CleanSIName(model); + + if (args == 1 || args == 2 && result == 0) { + menuArg0 = client; + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + + else if (args >= 2) { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + + if (args) { + level = 2; + } + + else if (!IsAdmin(client)) { + menuArg0 = client; + level = 1; + } + + if (menuArg1 != 3) { + model = ""; + } + + SwitchTeamHandler(client, level, model); + return Plugin_Handled; +} + +public void SwitchTeamHandler(int client, int level, char model[32]) { + Echo(2, "SwitchTeamHandler: %d %d %s", client, level, model); + + if (!RegMenuHandler(client, "SwitchTeamHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Switch Client's Team", 1); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Switching", g_sB); + TeamsMenu(client, g_sB); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (!g_IsVs && !IsAdmin(client) && menuArg1 == 3) { + GenericMenuCleaner(client); + return; + } + + if (GetQRecord(menuArg0)) { + g_QRecord.SetValue("rdelay", 0.1, true); + SwitchToSpec(menuArg0); + } + + SwitchTeam(menuArg0, menuArg1, model); + } + + GenericMenuCleaner(client); + } + } +} + +public Action AssignModelCmd(int client, int args) { + Echo(2, "AssignModelCmd: %d", client); + + int level; + + switch(args) { + case 1: { + menuArg0 = client; + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = GetModelIndexByName(g_sB); + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = GetModelIndexByName(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + } + } + + if (args) { + level = 2; + } + + AssignModelHandler(client, level); + return Plugin_Handled; +} + +public void AssignModelHandler(int client, int level) { + Echo(2, "AssignModelHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "AssignModelHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Change Client's Model", 2, 0, false); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Modeling", g_sB); + ModelsMenu(client, g_sB); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (GetClientTeam(menuArg0) <= 1) { + menuArg0 = GetPlayClient(menuArg0); + } + + AssignModel(menuArg0, g_SurvivorNames[menuArg1], 1); + } + + GenericMenuCleaner(client); + } + } +} + +public Action SwitchToBotCmd(int client, int args) { + Echo(2, "SwitchToBotCmd: %d", client); + + int level; + + switch(args) { + case 1: { + menuArg0 = client; + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + } + + if (args) { + level = 2; + } + + else if (!IsAdmin(client)) { + menuArg0 = client; + level = 1; + } + + SwitchToBotHandler(client, level); + return Plugin_Handled; +} + +public void SwitchToBotHandler(int client, int level) { + Echo(2, "SwitchToBotHandler: %d %d", client, level); + + int homeTeam = ClientHomeTeam(client); + if (!RegMenuHandler(client, "SwitchToBotHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Takeover Bot", 1); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Takeover", g_sB); + TeamMatesMenu(client, g_sB, 0, 0, true, false, homeTeam); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (IsClientValid(menuArg1)) { + if (homeTeam != 3 && GetClientTeam(menuArg1) == 3) { + if (!IsAdmin(client)) { + GenericMenuCleaner(client); + return; + } + } + + if (GetClientManager(menuArg1) == 0) { + SwitchToBot(menuArg0, menuArg1, false); + } + } + } + + GenericMenuCleaner(client); + } + } +} + +public Action RespawnClientCmd(int client, int args) { + Echo(2, "RespawnClientCmd: %d", client); + + int level; + + switch(args) { + case 1: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + menuArg1 = menuArg0; + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + } + + if (args) { + level = 2; + } + + RespawnClientHandler(client, level); + return Plugin_Handled; +} + +public void RespawnClientHandler(int client, int level) { + Echo(2, "RespawnClientHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "RespawnClientHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Respawn Client"); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Respawning", g_sB); + TeamMatesMenu(client, g_sB); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (GetClientTeam(menuArg0) <= 1) { + menuArg0 = GetPlayClient(menuArg0); + } + + RespawnClient(menuArg0, menuArg1); + } + + GenericMenuCleaner(client); + } + } +} + +public Action CycleBotsCmd(int client, int args) { + Echo(2, "CycleBotsCmd: %d", client); + + int level; + + switch(args) { + case 1: { + menuArg0 = client; + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + } + + if (args) { + if (menuArg1 > 3 || menuArg1 < 2) { + return Plugin_Handled; + } + + level = 2; + } + + CycleBotsHandler(client, level); + return Plugin_Handled; +} + +public void CycleBotsHandler(int client, int level) { + Echo(2, "CycleBotsHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "CycleBotsHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Cycle Client", 1); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Cycling", g_sB); + TeamsMenu(client, g_sB, false); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (!IsAdmin(client) && menuArg1 == 3) { + GenericMenuCleaner(client); + return; + } + + CycleBots(menuArg0, menuArg1); + menuArg1 = 0; + } + + CycleBotsHandler(client, 1); + } + } +} + +public Action StripClientCmd(int client, int args) { + Echo(2, "StripClientCmd: %d", client); + + int target; + int level; + + switch(args) { + case 1: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + target = StringToInt(g_sB); + target = GetPlayClient(target); + + if (CanClientTarget(client, target)) { + StripClient(target); + } + + return Plugin_Handled; + } + + case 2: { + GetCmdArg(1, g_sB, sizeof(g_sB)); + menuArg0 = StringToInt(g_sB); + GetCmdArg(2, g_sB, sizeof(g_sB)); + menuArg1 = StringToInt(g_sB); + } + } + + if (args) { + level = 2; + } + + StripClientHandler(client, level); + return Plugin_Handled; +} + +public void StripClientHandler(int client, int level) { + Echo(2, "StripClientHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "StripClientHandler", level, 0)) { + return; + } + + switch(level) { + case 0: TeamMatesMenu(client, "Strip Client", 2, 1); + case 1: { + GetClientName(menuArg0, g_sB, sizeof(g_sB)); + Format(g_sB, sizeof(g_sB), "%s: Stripping", g_sB); + InvSlotsMenu(client, menuArg0, g_sB); + } + + case 2: { + if (CanClientTarget(client, menuArg0)) { + if (GetClientTeam(menuArg0) <= 1) { + menuArg0 = GetPlayClient(menuArg0); + } + + StripClientSlot(menuArg0, menuArg1); + menuArg1 = 0; + StripClientHandler(client, 1); + } + } + } +} + +public Action ResetCmd(int client, int args) { + Echo(2, "ResetCmd: %d", client); + + for (int i = 1; i <= MaxClients; i++) { + GenericMenuCleaner(i); + if (GetQRecord(i)) { + CancelClientMenu(i, true, null); + } + } + return Plugin_Handled; +} + +bool RegMenuHandler(int client, char [] handler, int level, int clearance=0) { + Echo(2, "RegMenuHandler: %d %s %d %d", client, handler, level, clearance); + + g_callBacks.PushString(handler); + if (!IsAdmin(client) && level <= clearance) { + GenericMenuCleaner(client); + return false; + } + + return true; +} + +public Action MainMenuCmd(int client, int args) { + Echo(2, "MainMenuCmd: %d", client); + + GenericMenuCleaner(client); + MainMenuHandler(client, 0); + return Plugin_Handled; +} + +public void MainMenuHandler(int client, int level) { + Echo(2, "MainMenuHandler: %d %d", client, level); + + if (!RegMenuHandler(client, "MainMenuHandler", level, 0)) { + return; + } + + int cmd = menuArg0; + menuArg0 = 0; + + char title[32]; + Format(title, sizeof(title), "ABM Menu %s", PLUGIN_VERSION); + + switch(level) { + case 0: MainMenu(client, title); + case 1: { + switch(cmd) { + case 0: TeleportClientCmd(client, 0); + case 1: SwitchTeamCmd(client, 0); + case 2: AssignModelCmd(client, 0); + case 3: SwitchToBotCmd(client, 0); + case 4: RespawnClientCmd(client, 0); + case 5: CycleBotsCmd(client, 0); + case 6: StripClientCmd(client, 0); + } + } + } +} + +// ================================================================== // +// MENUS BACKBONE +// ================================================================== // + +void GenericMenuCleaner(int client, bool clearStack=true) { + Echo(2, "GenericMenuCleaner: %d %d", client, clearStack); + + for (int i = 0; i < sizeof(g_menuItems[]); i++) { + g_menuItems[client][i] = 0; + } + + if (clearStack == true) { + if (g_callBacks != null) { + delete g_callBacks; + } + + g_callBacks = new ArrayStack(128); + } +} + +public int GenericMenuHandler(Menu menu, MenuAction action, int param1, int param2) { + Echo(2, "GenericMenuHandler: %d %d", param1, param2); + + int client = param1; + int i; // -1; + char sB[128]; + + if (IsClientValid(param1)) { + for (i = 0; i < sizeof(g_menuItems[]); i++) { + if (menuArgs[i] == 0) { + break; + } + } + } + + switch(action) { + case MenuAction_Select: { + menu.GetItem(param2, g_sB, sizeof(g_sB)); + menuArgs[i] = StringToInt(g_sB); + i = i + 1; + } + + case MenuAction_Cancel: { + if (param2 == MenuCancel_ExitBack) { + if (i > 0) { + i = i - 1; + menuArgs[i] = 0; + } + + else if (i == 0) { + + if (g_callBacks.Empty) { + GenericMenuCleaner(param1); + return 0; + } + + g_callBacks.PopString(g_sB, sizeof(g_sB)); + GenericMenuCleaner(param1, false); + + while (!g_callBacks.Empty) { + g_callBacks.PopString(sB, sizeof(sB)); + + if (!StrEqual(g_sB, sB)) { + g_callBacks.PushString(sB); + break; + } + } + + if (g_callBacks.Empty) { + GenericMenuCleaner(param1); + return 0; + } + } + } + + else { + return 0; + } + } + + case MenuAction_End: { + delete menu; + return 0; + } + } + + if (g_callBacks == null || g_callBacks.Empty) { + GenericMenuCleaner(param1); + return 0; + } + + g_callBacks.PopString(g_sB, sizeof(g_sB)); + callBack = GetFunctionByName(null, g_sB); + + Call_StartFunction(null, callBack); + Call_PushCell(param1); + Call_PushCell(i); + Call_Finish(); + return 0; +} + +// ================================================================== // +// MENUS +// ================================================================== // + +void MainMenu(int client, char [] title) { + Echo(2, "MainMenu: %d %s", client, title); + + Menu menu = new Menu(GenericMenuHandler); + menu.SetTitle(title); + menu.AddItem("0", "Teleport Client"); // "Telespiznat"); // teleport + menu.AddItem("1", "Switch Client Team"); //"Swintootle"); // switch team + menu.AddItem("2", "Change Client Model"); //"Changdangle"); // makeover + menu.AddItem("3", "Switch Client Bot"); //"Inbosnachup"); // takeover + menu.AddItem("4", "Respawn Client"); //"Respiggle"); // respawn + menu.AddItem("5", "Cycle Client"); //"Cycolicoo"); // cycle + menu.AddItem("6", "Strip Client"); //"Upsticky"); // strip + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, 120); +} + +void InvSlotsMenu(int client, int target, char [] title) { + Echo(2, "InvSlotsMenu: %d %d %s", client, target, title); + + int ent; + char weapon[64]; + Menu menu = new Menu(GenericMenuHandler); + menu.SetTitle(title); + + for (int i; i < 5; i++) { + IntToString(i, g_sB, sizeof(g_sB)); + ent = GetPlayerWeaponSlot(target, i); + + if (IsEntityValid(ent)) { + GetEntityClassname(ent, weapon, sizeof(weapon)); + menu.AddItem(g_sB, weapon); + } + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, 120); +} + +void ModelsMenu(int client, char [] title) { + Echo(2, "ModelsMenu: %d %s", client, title); + + Menu menu = new Menu(GenericMenuHandler); + menu.SetTitle(title); + + for (int i; i < sizeof(g_SurvivorNames); i++) { + IntToString(i, g_sB, sizeof(g_sB)); + menu.AddItem(g_sB, g_SurvivorNames[i]); + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, 120); +} + +void TeamsMenu(int client, char [] title, bool all=true) { + Echo(2, "TeamsMenu: %d %s %d", client, title, all); + + Menu menu = new Menu(GenericMenuHandler); + menu.SetTitle(title); + + if (all) { + menu.AddItem("0", "Idler"); + menu.AddItem("1", "Spectator"); + } + + menu.AddItem("2", "Survivor"); + if (g_IsVs || IsAdmin(client)) { + menu.AddItem("3", "Infected"); + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, 120); +} + +void TeamMatesMenu(int client, char [] title, int mtype=2, int target=0, bool incDead=true, + bool repeat=false, int homeTeam=0) { + Echo(2, "TeamMatesMenu: %d %s %d %d %d %d %d", client, title, mtype, target, incDead, repeat, homeTeam); + + Menu menu = new Menu(GenericMenuHandler); + menu.SetTitle(title); + int isAdmin = IsAdmin(client); + char health[32]; + bool mflag = false; + int isAlive; + int playClient; + int bossClient; + int targetClient; + int manager; + + for (int i = 1; i <= MaxClients; i++) { + bossClient = i; + playClient = i; + + if (GetQRecord(i)) { + + if (mtype == 0) { + continue; + } + + if (mtype == 1 || mtype == 2) { + mflag = true; + } + + if (IsClientValid(g_target) && g_target != i) { + isAlive = IsPlayerAlive(g_target); + playClient = g_target; + } + + else { + isAlive = IsPlayerAlive(i); + } + } + + else if (IsClientValid(i)) { + isAlive = IsPlayerAlive(i); + + if (mtype == 0 || mtype == 2) { + mflag = true; + } + + manager = GetClientManager(i); + + if (manager != 0) { + if (target == 0 || !repeat) { + mflag = false; + continue; + } + + bossClient = manager; + } + } + + else { + continue; + } + + // at this point the client is valid. + // bossClient is the human (if there is one) + // playClient is the bot (or human if not idle) + + if (!isAlive && !incDead) { + continue; + } + + if (GetClientTeam(playClient) != homeTeam && !isAdmin) { + continue; + } + + switch(target) { + case 0: targetClient = bossClient; + case 1: targetClient = playClient; + } + + if (mflag) { + mflag = false; + + Format(health, sizeof(health), "%d", GetClientHealth(playClient)); + if (!IsPlayerAlive(playClient)) { + Format(health, sizeof(health), "DEAD"); + } + + else if (GetEntProp(playClient, Prop_Send, "m_isIncapacitated")) { + Format(health, sizeof(health), "DOWN"); + } + + GetClientName(bossClient, g_pN, sizeof(g_pN)); + Format(g_pN, sizeof(g_pN), "%s (%s)", g_pN, health); + IntToString(targetClient, g_sB, sizeof(g_sB)); + + if(bossClient == client && menu.ItemCount > 0) { + menu.InsertItem(0, g_sB, g_pN); + } else { + menu.AddItem(g_sB, g_pN); + } + } + } + + menu.ExitBackButton = true; + menu.ExitButton = true; + menu.Display(client, 120); +} + +// ================================================================== // +// MISC STUFF USEFUL FOR TROUBLESHOOTING +// ================================================================== // + +void Echo(int level, char [] format, any ...) { + static char g_dB[512]; + + if (g_LogLevel >= level) { + VFormat(g_dB, sizeof(g_dB), format, 3); + LogToFile(LOGFILE, g_dB); + PrintToServer("%s", g_dB); + } +} + +void QDBCheckCmd(int client) { + Echo(2, "QDBCheckCmd"); + + PrintToConsole(client, "-- STAT: QDB Size is %d", g_QDB.Size); + PrintToConsole(client, "-- MinPlayers is %d", g_MinPlayers); + + for (int i = 1; i <= MaxClients; i++) { + if (GetQRtmp(i)) { + PrintToConsole(client, "\n -"); + GetClientName(i, g_pN, sizeof(g_pN)); + + float x = g_origin[0]; + float y = g_origin[1]; + float z = g_origin[2]; + + PrintToConsole(client, " - Name: %s", g_pN); + PrintToConsole(client, " - Origin: {%d.0, %d.0, %d.0}", x, y, z); + PrintToConsole(client, " - Status: %d", g_tmpStatus); + PrintToConsole(client, " - Client: %d", g_tmpClient); + PrintToConsole(client, " - Target: %d", g_tmpTarget); + PrintToConsole(client, " - LastId: %d", g_tmpLastid); + PrintToConsole(client, " - OnTeam: %d", g_tmpOnteam); + PrintToConsole(client, " - Queued: %d", g_tmpQueued); + PrintToConsole(client, " - InSpec: %d", g_tmpInspec); + PrintToConsole(client, " - Model: %s", g_tmpModel); + PrintToConsole(client, " -\n"); + } + } +} + +Action QuickClientPrintCmd(int client, int args) { + Echo(2, "QuickClientPrintCmd: %d", client); + + int onteam; + int state; + int manager; + + PrintToConsole(client, "\nTeam\tState\tId\tManager\tName"); + + for (int i = 1; i <= MaxClients; i++) { + if (IsClientValid(i)) { + manager = i; + GetClientName(i, g_pN, sizeof(g_pN)); + onteam = GetClientTeam(i); + state = IsPlayerAlive(i); + + + if (IsFakeClient(i)) { + manager = GetClientManager(i); + } + + PrintToConsole(client, + "%d, \t%d, \t%d, \t%d, \t%s", onteam, state, i, manager, g_pN + ); + } + } + + QDBCheckCmd(client); + return Plugin_Handled; +} +Action DebugCmd(int client, int args) { + ReplyToCommand(client, "CreateSurvivorBot: %b", GetFeatureStatus(FeatureType_Native, "NextBotCreatePlayerBotSurvivorBot") == FeatureStatus_Available); + return Plugin_Handled; +} \ No newline at end of file diff --git a/scripting/include/epi/director.sp b/scripting/include/epi/director.sp index df372c7..a3e836e 100644 --- a/scripting/include/epi/director.sp +++ b/scripting/include/epi/director.sp @@ -454,13 +454,11 @@ Action Timer_DirectorWitch(Handle h) { void DirectorSpawn(specialType special, int player = -1) { if(player <= 0) player = GetSuitableVictim(); - // PrintDebug(DEBUG_SPAWNLOGIC, "Director: spawning %s(%d) around %N (cnt=%d,lim=%d)", SPECIAL_IDS[view_as(special)], special, player, g_spawnCount[view_as(special)], g_spawnLimit[view_as(special)]); if(special != Special_Witch && special != Special_Tank) { // Bypass director int bot = CreateFakeClient("EPI_BOT"); if (bot != 0) { ChangeClientTeam(bot, 3); - CreateTimer(0.1, Timer_Kick, bot); } } diff --git a/scripting/include/hats/hat_presets.sp b/scripting/include/hats/hat_presets.sp index 2f0e258..5e96687 100644 --- a/scripting/include/hats/hat_presets.sp +++ b/scripting/include/hats/hat_presets.sp @@ -17,8 +17,14 @@ enum struct HatPreset { ArrayList locations; int Spawn() { - PrecacheModel(this.model); + if(!PrecacheModel(this.model)) { + LogError("[Hats] Failed to precache preset model: \"%s\"", this.model); + } int entity = CreateEntityByName(this.type); + if(entity == -1) { + LogError("[Hats] Failed to spawn hat for type %s: \"%s\"", this.type, this.model); + } + DispatchKeyValue(entity, "solid", "6"); DispatchKeyValue(entity, "model", this.model); if(HasEntProp(entity, Prop_Send, "m_flModelScale")) SetEntPropFloat(entity, Prop_Send, "m_flModelScale", this.size); @@ -29,8 +35,8 @@ enum struct HatPreset { } int Apply(int client) { - int entity = this.Spawn(); float offset[3], angles[3]; + int entity = this.Spawn(offset); EquipHat(client, entity, this.type, HAT_PRESET); this.GetLocation(client, offset, angles); hatData[client].offset = offset; diff --git a/scripting/include/jutils.inc b/scripting/include/jutils.inc index 0854459..06f2e9a 100644 --- a/scripting/include/jutils.inc +++ b/scripting/include/jutils.inc @@ -266,38 +266,6 @@ stock bool GetGround(int client, float vPos[3], float vAng[3]) { GetClientAbsAngles(client, vAng); return true; } -//Taken from https://forums.alliedmods.net/showthread.php?p=1741099 -stock bool SpawnMinigun(const float vPos[3], const float vAng[3]) { - float vDir[3], newPos[3]; - GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR); - vDir[0] = vPos[0] + (vDir[0] * 50); - vDir[1] = vPos[1] + (vDir[1] * 50); - vDir[2] = vPos[2] + 20.0; - newPos = vDir; - newPos[2] -= 40.0; - - Handle trace = TR_TraceRayFilterEx(vDir, newPos, MASK_SHOT, RayType_EndPoint, TraceFilter); - if(TR_DidHit(trace)) { - TR_GetEndPosition(vDir, trace); - - int minigun = CreateEntityByName("prop_mounted_machine_gun"); - minigun = EntIndexToEntRef(minigun); - SetEntityModel(minigun, MODEL_MINIGUN); - DispatchKeyValue(minigun, "targetname", "louis_holdout"); - DispatchKeyValueFloat(minigun, "MaxPitch", 360.00); - DispatchKeyValueFloat(minigun, "MinPitch", -360.00); - DispatchKeyValueFloat(minigun, "MaxYaw", 90.00); - newPos[2] += 0.1; - TeleportEntity(minigun, vDir, vAng, NULL_VECTOR); - DispatchSpawn(minigun); - delete trace; - return true; - }else{ - LogError("Spawn minigun trace failure"); - delete trace; - return false; - } -} stock int GiveClientWeaponLasers(int client, const char[] wpnName) { int entity = GiveClientWeapon(client, wpnName); diff --git a/scripting/l4d2_ai_minigun.sp b/scripting/l4d2_ai_minigun.sp index a92c898..b0a47bd 100644 --- a/scripting/l4d2_ai_minigun.sp +++ b/scripting/l4d2_ai_minigun.sp @@ -216,6 +216,38 @@ stock int SpawnSurvivor(const float vPos[3], const float vAng[3], const char[] m CreateTimer(6.0, spawn_minigun ? Timer_MoveMinigun : Timer_Move, bot_user_id); return bot_client_id; } +//Taken from https://forums.alliedmods.net/showthread.php?p=1741099 +stock bool SpawnMinigun(const float vPos[3], const float vAng[3]) { + float vDir[3], newPos[3]; + GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR); + vDir[0] = vPos[0] + (vDir[0] * 50); + vDir[1] = vPos[1] + (vDir[1] * 50); + vDir[2] = vPos[2] + 20.0; + newPos = vDir; + newPos[2] -= 40.0; + + Handle trace = TR_TraceRayFilterEx(vDir, newPos, MASK_SHOT, RayType_EndPoint, TraceFilter); + if(TR_DidHit(trace)) { + TR_GetEndPosition(vDir, trace); + + int minigun = CreateEntityByName("prop_mounted_machine_gun"); + minigun = EntIndexToEntRef(minigun); + SetEntityModel(minigun, MODEL_MINIGUN); + DispatchKeyValue(minigun, "targetname", "louis_holdout"); + DispatchKeyValueFloat(minigun, "MaxPitch", 360.00); + DispatchKeyValueFloat(minigun, "MinPitch", -360.00); + DispatchKeyValueFloat(minigun, "MaxYaw", 90.00); + newPos[2] += 0.1; + TeleportEntity(minigun, vDir, vAng, NULL_VECTOR); + DispatchSpawn(minigun); + delete trace; + return true; + }else{ + LogError("Spawn minigun trace failure"); + delete trace; + return false; + } +} void AvoidCharacter(int type, bool avoid) { for( int i = 1; i <= MaxClients; i++ ) { diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index 34e4a59..596a2dd 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -555,6 +555,7 @@ Action Command_EpiVal(int client, int args) { PrintToConsole(client, "realSurvivorCount = %d", g_realSurvivorCount); PrintToConsole(client, "restCount = %d", g_restCount); PrintToConsole(client, "extraFinaleTankEnabled = %b", g_extraFinaleTankEnabled); + PrintToConsole(client, "g_areItemsPopulated = %b", g_areItemsPopulated); ReplyToCommand(client, "Values printed to console"); return Plugin_Handled; } @@ -1187,10 +1188,11 @@ void IncreaseKits() { PrintToServer("[EPI] Warn: No kit spawns (weapon_first_aid_kit_spawn) found"); return; } - + int count = 0; while(g_extraKitsAmount > 0) { GetEntPropVector(entity, Prop_Data, "m_vecOrigin", pos); if(L4D_IsPositionInLastCheckpoint(pos)) { + count++; // Give it a little chance to nudge itself pos[2] += 0.3; SpawnItem("first_aid_kit", pos); @@ -1200,6 +1202,10 @@ void IncreaseKits() { // Loop around if(entity == INVALID_ENT_REFERENCE) { entity = -1; + // If we did not find any suitable kits, stop here. + if(count == 0) { + break; + } } } } @@ -1208,12 +1214,15 @@ void IncreaseFinaleKits() { float pos[3]; int entity = -1; int spawnCount = g_survivorCount - 4; + int count = 0; + PrintDebug(DEBUG_SPAWNLOGIC, "Spawning %d finale kits", spawnCount); while(spawnCount > 0) { GetEntPropVector(entity, Prop_Data, "m_vecOrigin", pos); Address address = L4D_GetNearestNavArea(pos); if(address != Address_Null) { int attributes = L4D_GetNavArea_SpawnAttributes(address); if(attributes & NAV_SPAWN_FINALE) { + count++; pos[2] += 0.3; SpawnItem("first_aid_kit", pos); spawnCount--; @@ -1223,6 +1232,10 @@ void IncreaseFinaleKits() { // Loop around if(entity == INVALID_ENT_REFERENCE) { entity = -1; + // If we did not find any suitable kits, stop here. + if(count == 0) { + break; + } } } } diff --git a/scripting/l4d2_turret.sp b/scripting/l4d2_turret.sp index f6cb857..5701ae5 100644 --- a/scripting/l4d2_turret.sp +++ b/scripting/l4d2_turret.sp @@ -17,12 +17,19 @@ #define _TURRET_PHASE_TICKS TURRET_NORMAL_PHASE_TICKS + TURRET_COMMON_PHASE_TICKS -#define PLUGIN_VERSION "1.0" +// Taken from l4d_machine, thanks +#define SOUND_IMPACT_HIT "physics/flesh/flesh_impact_bullet1.wav" +#define SOUND_IMPACT_MISS "physics/concrete/concrete_impact_bullet1.wav" +#define SOUND_FIRE "weapons/50cal/50cal_shoot.wav" +#define PARTICLE_WEAPON_TRACER "weapon_tracers_50cal" + +#define PLUGIN_VERSION "2.0" #include #include #include #include +#include // #include @@ -35,11 +42,39 @@ // #define SOUND_LASER_FIRE "level/puck_impact.wav" #include +enum MountedGun { + MountedGun_Minigun, + MountedGun_50Cal +} +char MountedGunClassname[2][32] = { "prop_minigun_l4d1", "prop_minigun" }; +char MountedGunModel[2][64] = { "models/w_models/weapons/w_minigun.mdl", "models/w_models/weapons/50cal.mdl" }; +float MOUNTED_HEAT_MIN[2] = { 0.01, 0.0 }; +float MOUNTED_HEAT_INCREASE_RATE[2] = { 0.0003333333, 0.0075}; +float MOUNTED_DAMAGE[2] = { 10.0, 100.0 }; +float MOUNTED_FIRE_RATE[2] = { 0.0, 0.25 }; // Only can fire every value game ticks +enum struct MountedTurret { + MountedGun type; + int entity; + float heat; + int target; + bool cooling; + float nextFire; + int poseParamYaw; + int poseParamPitch; + int poseController; //TODO: kill +} +#define MAX_MOUNTED_TURRETS 6 +MountedTurret MTurret[MAX_MOUNTED_TURRETS]; +int MTurretCount; +#define HEAT_DECREASE_RATE 0.01 + int g_iLaserIndex; int g_iBeamSprite; int g_iHaloSprite; +int g_iTracerIndex; int manualTargetter; +int g_debugTracer; Handle thinkTimer; ConVar cv_autoBaseDamage; @@ -51,6 +86,9 @@ int manualTarget = -1; #define MANUAL_TARGETNAME "turret_target_manual" ArrayList turretIds; +Handle SDKCall_LookupPoseParameter; +Handle SDKCall_LoadModel, SDKCall_DeleteModel; +int Animating_StudioHdr; /* TODO: Entity_ChangeOverTime` @@ -91,6 +129,52 @@ public void OnPluginStart() { SetFailState("This plugin is for L4D/L4D2 only."); } + GameData gameData = LoadGameConfigFile("l4d2_turret"); + if(!gameData) { + LogError("Missing gamedata l4d2_turret.txt, mounted turret disabled"); + } else { + // = + StartPrepSDKCall(SDKCall_Entity); + // CBaseAnimating::LookupPoseParameter(CStudioHdr*, char const*) + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "CBaseAnimating::LookupPoseParameter"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + SDKCall_LookupPoseParameter = EndPrepSDKCall(); + if(SDKCall_LookupPoseParameter == null) { + SetFailState("Failed to load SDK call \"CBaseAnimating::LookupPoseParameter\". Update signature in \"plugin.turret\""); + } + + // Taken from https://github.com/Natanel-Shitrit/StudioHdr/blob/d95e93134729361e06a0381a163de8b0b5625bc4/include/studio_hdr.inc#L4722 + // CStudioHdr *ModelSoundsCache_LoadModel( const char *filename ) + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "ModelSoundsCache_LoadModel"); + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + if (!(SDKCall_LoadModel = EndPrepSDKCall())) { + SetFailState("Missing signature 'ModelSoundsCache_LoadModel'"); + } + + // void ModelSoundsCache_FinishModel( CStudioHdr *hdr ) + StartPrepSDKCall(SDKCall_Static); + PrepSDKCall_SetFromConf(gameData, SDKConf_Signature, "ModelSoundsCache_FinishModel"); + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + if (!(SDKCall_DeleteModel = EndPrepSDKCall())) { + SetFailState("Missing signature 'ModelSoundsCache_FinishModel'"); + } + + // TODO: REMOVE + Animating_StudioHdr = gameData.GetOffset("CBaseAnimating::StudioHdr"); + if(Animating_StudioHdr == -1) + SetFailState("Failed to get offset: \"CBaseAnimating::StudioHdr\""); + int iOffset_hLightingOrigin = FindSendPropInfo("CBaseAnimating", "m_hLightingOrigin"); + if (iOffset_hLightingOrigin < 1) + SetFailState("Failed to find send prop: \"m_hLightingOrigin\""); + Animating_StudioHdr += iOffset_hLightingOrigin; + + delete gameData; + } + turretIds = new ArrayList(); FindTurrets(); @@ -103,8 +187,10 @@ public void OnPluginStart() { RegAdminCmd("sm_turret", Command_SpawnTurret, ADMFLAG_CHEATS); RegAdminCmd("sm_rmturrets", Command_RemoveTurrets, ADMFLAG_CHEATS); + RegAdminCmd("sm_rmlaser", Command_RemoveLaserTurret, ADMFLAG_CHEATS); RegAdminCmd("sm_rmturret", Command_RemoveTurret, ADMFLAG_CHEATS); RegAdminCmd("sm_manturret", Command_ManualTarget, ADMFLAG_CHEATS); + RegAdminCmd("sm_turret_debug", Command_Debug, ADMFLAG_CHEATS); for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i)) { SDKHook(i, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); @@ -126,6 +212,7 @@ enum TurretState { TurretState turretState[2048]; int turretActivatorParticle[2048]; int entityActiveTurret[2048]; // mapping the turret thats active on victim. [victim] = turret +int entityActiveMounted[2048]; int turretActiveEntity[2048]; int turretPhaseOffset[2048]; // slight of offset so they dont all enter the same phase at same time bool turretIsActiveLaser[2048]; @@ -135,18 +222,89 @@ float turretDamage[2048]; int turretCount; void FindTurrets() { - int entity = INVALID_ENT_REFERENCE; + int entity = -1; char targetname[32]; while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrEqual(targetname, "turret")) { - SetupTurret(entity); - PrintToServer("Found existing turret: %d", entity); + SetupLaserTurret(entity); + PrintToServer("Found existing laser turret: %d", entity); } } + entity = -1; + while ((entity = FindEntityByClassname(entity, "prop_minigun")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrContains(targetname, "turret_") > -1) + AddMountedGun(entity, MountedGun_50Cal); + } + entity = -1; + while ((entity = FindEntityByClassname(entity, "prop_minigun_l4d1")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrContains(targetname, "turret_") > -1) + AddMountedGun(entity, MountedGun_Minigun); + } } -void SetupTurret(int turret, float time = 0.0) { +int AddMountedGun(int entity, MountedGun type) { + MTurret[MTurretCount].entity = EntIndexToEntRef(entity); + MTurret[MTurretCount].type = type; + MTurret[MTurretCount].target = INVALID_ENT_REFERENCE; + MTurret[MTurretCount].heat = 0.0; + MTurret[MTurretCount].cooling = false; + MTurret[MTurretCount].nextFire = GetGameTime(); + + char buffer[64]; + Format(buffer, sizeof(buffer), "turret_%d", entity); + SetEntPropString(entity, Prop_Data, "m_iName", buffer); + + int poseCtrl = CreateEntityByName("point_posecontroller"); + DispatchKeyValue(poseCtrl, "PropName", buffer); + Format(buffer, sizeof(buffer), "turret_%d_posectrl", entity); + DispatchKeyValue(poseCtrl, "targetname", buffer); + DispatchKeyValue(poseCtrl, "PoseParameterName", "MiniGun_Horizontal"); + DispatchKeyValue(poseCtrl, "CycleFrequency", "0"); + DispatchSpawn(poseCtrl); + SetParent(poseCtrl, entity); + MTurret[MTurretCount].poseController = EntIndexToEntRef(poseCtrl); + + // TODO: do on start + // char buffer[PLATFORM_MAX_PATH]; + // GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer)); + + + // Address pStudioHdrClass = view_as
(GetEntData(entity, 0x13E0)); + // // Address pStudioHdrClass = //GetStudioHdr(buffer); + // PrintToServer("MG%d '%s' pStudioHdrClass=%d", MTurretCount, buffer, pStudioHdrClass); + // MTurret[MTurretCount].poseParamYaw = SDKCall(SDKCall_LookupPoseParameter, entity, pStudioHdrClass, "MiniGun_Horizontal"); + // // MTurret[MTurretCount].poseParamPitch = SDKCall(SDKCall_LookupPoseParameter, entity, pStudioHdrClass, "MiniGun_Vertical"); + PrintToServer("MG%d poseParamYaw=%d poseParamPitch=%d", MTurretCount, MTurret[MTurretCount].poseParamYaw, MTurret[MTurretCount].poseParamPitch); + + MTurretCount++; + PrintToServer("Added mounted gun #%d (type=%d)", MTurretCount, type); + if(thinkTimer == null) { + PrintToServer("Created turret think timer"); + thinkTimer = CreateTimer(0.1, Timer_Think, _, TIMER_REPEAT); + } + return MTurretCount; +} + +void RemoveMounted(int index) { + // Shift everything from [index, MTurretCount] down + for(int i = index; i < MTurretCount; i++) { + if(!IsValidEntity(MTurret[i+1].entity)) { + break; + } + MTurret[i].entity = MTurret[i+1].entity; + MTurret[i].type = MTurret[i+1].type; + MTurret[i].target = MTurret[i+1].target; + MTurret[i].heat = MTurret[i+1].heat; + MTurret[i].cooling = MTurret[i+1].cooling; + MTurret[i].nextFire = MTurret[i+1].nextFire; + } + MTurretCount--; +} + +void SetupLaserTurret(int turret, float time = 0.0) { float pos[3]; GetEntPropVector(turret, Prop_Send, "m_vecOrigin", pos); turretState[turret] = Turret_Disabled; @@ -179,7 +337,7 @@ void DeactivateTurret(int turret) { int ClearTurrets(bool fullClear = true) { turretIds.Clear(); - int entity = INVALID_ENT_REFERENCE; + int entity = -1; int count; char targetname[32]; if(fullClear) { @@ -198,28 +356,47 @@ int ClearTurrets(bool fullClear = true) { AcceptEntityInput(entity, "Kill"); } } - entity = INVALID_ENT_REFERENCE; + entity = -1; + while ((entity = FindEntityByClassname(entity, "prop_minigun*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrContains(targetname, "turret_") > -1) { + RemoveEntity(entity); + count++; + } + } + MTurretCount = 0; } + entity = -1; while ((entity = FindEntityByClassname(entity, "env_laser")) != INVALID_ENT_REFERENCE) { if(turretIsActiveLaser[entity]) { AcceptEntityInput(entity, "TurnOff"); AcceptEntityInput(entity, "Kill"); } } - entity = INVALID_ENT_REFERENCE; + entity = -1; + while ((entity = FindEntityByClassname(entity, "point_posecontroller")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrContains(targetname, "turret_") > -1) { + RemoveEntity(entity); + } + } + + entity = -1; while ((entity = FindEntityByClassname(entity, "info_target")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrContains(targetname, "turret_target_") > -1 || StrEqual(targetname, MANUAL_TARGETNAME)) { - AcceptEntityInput(entity, "Kill"); + RemoveEntity(entity); } } + for(int i = 1; i < 2048; i++) { entityActiveTurret[i] = 0; + entityActiveMounted[i] = 0; pendingDeletion[i] = false; } turretCount = 0; - if(IsValidHandle(thinkTimer)) { + if(thinkTimer != null) { delete thinkTimer; } return count; @@ -261,6 +438,8 @@ public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast } entityActiveTurret[index] = 0; entityActiveTurret[client] = 0; + entityActiveMounted[index] = 0; + entityActiveMounted[client] = 0; } public void OnEntityDestroyed(int entity) { @@ -271,17 +450,52 @@ public void OnEntityDestroyed(int entity) { DeactivateTurret(turret); } entityActiveTurret[entity] = 0; + entityActiveMounted[entity] = 0; } } public Action Command_SpawnTurret(int client, int args) { + if(args == 0) { + ReplyToCommand(client, "Usage: /mkturret "); + return Plugin_Handled; + } + char arg[16]; + GetCmdArg(1, arg, sizeof(arg)); + float pos[3]; GetClientEyePosition(client, pos); + float ang[3]; + GetClientEyeAngles(client, ang); + ang[0] = 0.0; + ang[2] = 0.0; // pos[2] += 10.0; - int base = CreateParticleNamed(ENT_PORTAL_NAME, PARTICLE_ELMOS, pos, NULL_VECTOR); - SetupTurret(base, TURRET_ACTIVATION_TIME); - ReplyToCommand(client, "New turret (%d) will activate in %.0f seconds", base, TURRET_ACTIVATION_TIME); + if(StrEqual(arg, "laser")) { + int base = CreateParticleNamed(ENT_PORTAL_NAME, PARTICLE_ELMOS, pos, NULL_VECTOR); + SetupLaserTurret(base, TURRET_ACTIVATION_TIME); + ReplyToCommand(client, "New laser turret (%d) will activate in %.0f seconds", base, TURRET_ACTIVATION_TIME); + } else { + GetCursorLocation(client, pos); + MountedGun type = MountedGun_Minigun; + if(StrEqual(arg, "minigun")) { + type = MountedGun_Minigun; + } else if(StrEqual(arg, "50cal", false)) { + type = MountedGun_50Cal; + } else { + ReplyToCommand(client, "Unknown turret type. Usage: /mkturret "); + return Plugin_Handled; + } + // TODO: create minigun + int gun = CreateEntityByName(MountedGunClassname[type]); + DispatchKeyValue(gun, "targetname", "turret"); + DispatchKeyValue(gun, "model", MountedGunModel[type]); + TeleportEntity(gun, pos, ang, NULL_VECTOR); + DispatchSpawn(gun); + AddMountedGun(gun, type); + ReplyToCommand(client, "New mounted gun spawned."); + return Plugin_Handled; + } + return Plugin_Handled; } @@ -310,25 +524,23 @@ public Action Command_ManualTarget(int client, int args) { return Plugin_Handled; } -public Action Command_RemoveTurrets(int client, int args) { - int count = ClearTurrets(); - /*int entity = INVALID_ENT_REFERENCE; - char targetname[32]; - int count; - while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { - GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(targetname, ENT_PORTAL_NAME)) { - AcceptEntityInput(entity, "Kill"); - count++; - } else if(StrEqual(targetname, "turret_activate")) { - AcceptEntityInput(entity, "Kill"); - } - }*/ +Action Command_RemoveTurrets(int client, int args) { + int count = ClearTurrets(true); ReplyToCommand(client, "Removed %d turrets", count); return Plugin_Handled; } -public Action Command_RemoveTurret(int client, int args) { +Action Command_Debug(int client, int args) { + if(g_debugTracer == client) { + g_debugTracer = 0; + ReplyToCommand(client, "Debug mode off"); + } else { + g_debugTracer = client; + ReplyToCommand(client, "Debug mode on"); + } +} + +Action Command_RemoveLaserTurret(int client, int args) { if(turretIds.Length == 0) { ReplyToCommand(client, "No turrets to remove"); } else { @@ -338,73 +550,225 @@ public Action Command_RemoveTurret(int client, int args) { return Plugin_Handled; } +Action Command_RemoveTurret(int client, int args) { + int target = GetClientAimTarget(client, false); + if(target >= MaxClients) { + int targetRef = EntIndexToEntRef(target); + for(int i = 0; i < MTurretCount; i++) { + if(MTurret[i].entity == targetRef) { + RemoveMounted(i); + ReplyToCommand(client, "Removed mounted turret #%d", i); + return Plugin_Handled; + } + } + ReplyToCommand(client, "Not a valid turret"); + } else { + ReplyToCommand(client, "You are not looking at a turret"); + } + return Plugin_Handled; +} + + +bool IsTargetValid(int targetRef) { + if(!IsValidEntity(targetRef)) return false; + return GetEntProp(targetRef, Prop_Data, "m_iHealth") > 0; +} + +void GetAngles(const float pos[3], const float endPos[3], float angles[3]) { + float result[3]; + MakeVectorFromPoints(endPos, pos, result); + GetVectorAngles(result, angles); + if(angles[0] >= 270){ + angles[0] -= 270; + angles[0] = (90-angles[0]); + }else{ + if(angles[0] <= 90){ + angles[0] *= -1; + } + } + angles[1] -= 180; +} + +void ShowTracer(float pos[3], float endPos[3]) { + TE_SetupParticle(g_iTracerIndex, pos, endPos); + TE_SendToAll(); +} + +Address GetStudioHdr(const char[] model) { + if (!model[0]) { + LogError("empty model path"); + } + // Create a new CStudioHdr variable based on the model path. + Address CStudioHdr = SDKCall(SDKCall_LoadModel, model); + if (!CStudioHdr) { + return Address_Null; + } + // Load 'studiohdr_t *m_pStudioHdr' from 'CStudioHdr' pointer. (can be treated as if it was a studiohdr_t **) + Address m_pStudioHdr = view_as
(LoadFromAddress(CStudioHdr, NumberType_Int32)); + // Delete the CStudioHdr variable to not leak memory. + SDKCall(SDKCall_DeleteModel, CStudioHdr); + return m_pStudioHdr; +} + +void SetPoseParameter(int entity, int iParameter, float flStart, float flEnd, float flValue) { + float ctlValue = (flValue - flStart) / (flEnd - flStart); + if (ctlValue < 0) ctlValue = 0.0; + if (ctlValue > 1) ctlValue = 1.0; + + SetEntPropFloat(entity, Prop_Send, "m_flPoseParameter", ctlValue, iParameter); +} + + +void SetPoseControllerParameter(int entity, const char[] parameter, float flStart, float flEnd, float flValue) { + float ctlValue = (flValue - flStart) / (flEnd - flStart); + if (ctlValue < 0) ctlValue = 0.0; + if (ctlValue > 1) ctlValue = 1.0; + + SetVariantString(parameter); + AcceptEntityInput(entity, "SetPoseParameterName"); + SetVariantFloat(ctlValue); + AcceptEntityInput(entity, "SetPoseValue"); + PrintToServer("SetPoseControllerParameter: ent=%d param=%s value=%f", entity, parameter, ctlValue); +} + + public Action Timer_Think(Handle h) { if( manualTargetter > 0) return Plugin_Continue; // Probably better to just store from CreateParticle static int entity; - entity = INVALID_ENT_REFERENCE; + entity = -1; // static char targetname[32]; - static float pos[3]; + static float pos[3], targetPos[3], angles[3], turretAngles[3]; static int count, target, tick; + for(int i = 0; i < MTurretCount; i++) { + GetEntPropVector(MTurret[i].entity, Prop_Send, "m_vecOrigin", pos); + GetEntPropVector(MTurret[i].entity, Prop_Send, "m_angRotation", turretAngles); + if(!IsTargetValid(MTurret[i].target)) { + MTurret[i].target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, MTurret[i].entity); + } - while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { - // GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); - // if(StrEqual(targetname, ENT_PORTAL_NAME)) { - if(view_as(turretState[entity]) > 0) { - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); - if(turretState[entity] == Turret_Active) { - // Keep targetting if can view - target = EntRefToEntIndex(turretActiveEntity[entity]); - if(target > 0 && IsValidEntity(target)) { - if(target <= MaxClients) { - if(IsPlayerAlive(target) && GetEntProp(target, Prop_Data, "m_bClientSideRagdoll") == 0 && CanSeeEntity(pos, target)) { + if(MTurret[i].target > 0 && !MTurret[i].cooling) { + // Map to a bogus value: + entityActiveMounted[MTurret[i].target] = i; + MTurret[i].heat += MOUNTED_HEAT_INCREASE_RATE[MTurret[i].type]; + // PrintToServer("mg%d warming - heat:%f min:%f", i, MTurret[i].heat, MOUNTED_HEAT_MIN[MTurret[i].type]); + if(MTurret[i].heat >= 1.0) { + MTurret[i].heat = 1.0; + MTurret[i].cooling = true; + SetEntProp(MTurret[i].entity, Prop_Send, "m_firing", 0); + SetEntProp(MTurret[i].entity, Prop_Send, "m_overheated", 1); + } + else if(MTurret[i].heat >= MOUNTED_HEAT_MIN[MTurret[i].type] && GetGameTime() >= MTurret[i].nextFire) { + // Can fire now + SetEntProp(MTurret[i].entity, Prop_Send, "m_firing", 1); + // TODO: look at + if(MTurret[i].type == MountedGun_50Cal) + EmitSoundToAll(SOUND_FIRE, 0, SNDCHAN_WEAPON, SNDLEVEL_NORMAL, SND_NOFLAGS, 1.0, SNDPITCH_NORMAL, -1, pos, NULL_VECTOR, true, 0.0); + GetEntPropVector(MTurret[i].target, Prop_Send, "m_vecOrigin", targetPos); + targetPos[2] += 40.0; // hit infected better + GetAngles(pos, targetPos, angles); + float angle = 0.5 + (angles[1] - turretAngles[1]) / FLOAT_PI; + SetPoseControllerParameter(MTurret[i].poseController, "MiniGun_Vertical", -90.0, 90.0, angle); + angle = 0.5 + (angles[0] - turretAngles[0]) / FLOAT_PI; + SetPoseControllerParameter(MTurret[i].poseController, "MiniGun_Horizontal", -90.0, 90.0, angle); + // SetPoseParameter(MTurret[i].entity, 0, -90.0, 90.0, angles[0]); + // SetPoseParameter(MTurret[i].entity, 1, -90.0, 90.0, angles[1]); + TR_TraceRay(pos, targetPos, 0, RayType_EndPoint); + if(TR_DidHit()) { + // Obstacle + TR_GetEndPosition(targetPos); + EmitSoundToAll(SOUND_IMPACT_MISS, 0, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_NOFLAGS, 1.0, SNDPITCH_NORMAL, -1, targetPos, NULL_VECTOR, true, 0.0); + } else { + EmitSoundToAll(SOUND_IMPACT_HIT, 0, SNDCHAN_AUTO, SNDLEVEL_NORMAL, SND_NOFLAGS, 1.0, SNDPITCH_NORMAL, -1, targetPos, NULL_VECTOR, true, 0.0); + // TODO: improve damage + SDKHooks_TakeDamage(MTurret[i].target, MTurret[i].entity, MTurret[i].entity, MOUNTED_DAMAGE[MTurret[i].type], DMG_BLAST); + } + TE_SetupTracerSound(pos, targetPos); + TE_SendToAll(); + // Get the end of the barrel + GetHorizontalPositionFromOrigin(pos, turretAngles, 10.0, pos); + pos[2] += 45.0; + ShowTracer(pos, targetPos); + TE_SetupMuzzleFlash(pos, turretAngles, 1.0, 1); + TE_SendToAll(); + // For now, make 50 cal swap target (for commons) + if(MTurret[i].type == MountedGun_50Cal) { + MTurret[i].target = INVALID_ENT_REFERENCE; + } + MTurret[i].nextFire = GetGameTime() + MOUNTED_FIRE_RATE[MTurret[i].type]; + } + } else { + MTurret[i].heat -= HEAT_DECREASE_RATE; + SetEntProp(MTurret[i].entity, Prop_Send, "m_firing", 0); + if(MTurret[i].heat < 0.0) { + MTurret[i].heat = 0.0; + MTurret[i].cooling = false; + SetEntProp(MTurret[i].entity, Prop_Send, "m_overheated", 0); + } + } + SetEntPropFloat(MTurret[i].entity, Prop_Send, "m_heat", MTurret[i].heat); + } + if(turretCount > 0) { + entity = -1; + while ((entity = FindEntityByClassname(entity, "info_particle_system")) != INVALID_ENT_REFERENCE) { + // GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + // if(StrEqual(targetname, ENT_PORTAL_NAME)) { + if(view_as(turretState[entity]) > 0) { + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); + if(turretState[entity] == Turret_Active) { + // Keep targetting if can view + target = EntRefToEntIndex(turretActiveEntity[entity]); + if(target > 0 && IsValidEntity(target)) { + if(target <= MaxClients) { + if(IsPlayerAlive(target) && GetEntProp(target, Prop_Data, "m_bClientSideRagdoll") == 0 && CanSeeEntity(pos, target)) { + FireTurretAuto(pos, target, turretDamage[entity]); + continue; + } + } else if(CanSeeEntity(pos, target)) { FireTurretAuto(pos, target, turretDamage[entity]); continue; } - } else if(CanSeeEntity(pos, target)) { - FireTurretAuto(pos, target, turretDamage[entity]); - continue; } + DeactivateTurret(entity); + } + // Skip activation if a survivor is too close + if(FindNearestClient(TEAM_SURVIVORS, pos, TURRET_MAX_RANGE_HUMANS_OPTIMIZED) > 0) { + continue; } - DeactivateTurret(entity); - } - // Skip activation if a survivor is too close - if(FindNearestClient(TEAM_SURVIVORS, pos, TURRET_MAX_RANGE_HUMANS_OPTIMIZED) > 0) { - continue; - } - bool inNormalPhase = ((tick + turretPhaseOffset[entity]) % _TURRET_PHASE_TICKS) <= TURRET_NORMAL_PHASE_TICKS; + bool inNormalPhase = ((tick + turretPhaseOffset[entity]) % _TURRET_PHASE_TICKS) <= TURRET_NORMAL_PHASE_TICKS; - // Find a target, in this order: Tank Rock -> Specials -> Infected - float damage = cv_autoBaseDamage.FloatValue; - target = -1; - if(inNormalPhase) { - target = FindNearestVisibleEntity("tank_rock", pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED, entity); + // Find a target, in this order: Tank Rock -> Specials -> Infected + float damage = cv_autoBaseDamage.FloatValue; + target = -1; + if(inNormalPhase) { + target = FindNearestVisibleEntity("tank_rock", pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED, entity); + if(target > 0) { + CreateTimer(1.2, Timer_KillRock, EntIndexToEntRef(target)); + damage = 1000.0; + } + if(target <= 0) target = FindNearestVisibleClient(TEAM_SPECIALS, pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED); + } + if(target <= 0) target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, entity); if(target > 0) { - CreateTimer(1.2, Timer_KillRock, EntIndexToEntRef(target)); - damage = 1000.0; + turretDamage[entity] = damage; + entityActiveTurret[target] = entity; + turretActiveEntity[entity] = EntIndexToEntRef(target); + turretActivatorParticle[entity] = EntIndexToEntRef(CreateParticleNamed("turret_activate", PARTICLE_TES1, pos, NULL_VECTOR)); + // AcceptEntityInput(turretActivatorParticle[entity], "Start"); + FireTurretAuto(pos, target, turretDamage[entity]); + turretState[entity] = Turret_Active; + } + // Optimization incase there's multiple info_particle_system + if(++count > turretCount) { + count = 0; + break; } - if(target <= 0) target = FindNearestVisibleClient(TEAM_SPECIALS, pos, TURRET_MAX_RANGE_SPECIALS_OPTIMIZED); - } - if(target <= 0) target = FindNearestVisibleEntity("infected", pos, TURRET_MAX_RANGE_INFECTED_OPTIMIZED, entity); - if(target > 0) { - turretDamage[entity] = damage; - entityActiveTurret[target] = entity; - turretActiveEntity[entity] = EntIndexToEntRef(target); - turretActivatorParticle[entity] = EntIndexToEntRef(CreateParticleNamed("turret_activate", PARTICLE_TES1, pos, NULL_VECTOR)); - // AcceptEntityInput(turretActivatorParticle[entity], "Start"); - FireTurretAuto(pos, target, turretDamage[entity]); - turretState[entity] = Turret_Active; - } - // Optimization incase there's multiple info_particle_system - if(++count > turretCount) { - count = 0; - break; } } - } - if(++tick >= _TURRET_PHASE_TICKS) { - tick = 0; + if(++tick >= _TURRET_PHASE_TICKS) { + tick = 0; + } } return Plugin_Continue; } @@ -552,27 +916,47 @@ stock int FindNearestVisibleClient(int team, const float origin[3], float maxRan return client; } -stock int FindNearestVisibleEntity(const char[] classname, const float origin[3], float maxRange = 0.0, int turretIndex) { - int entity = INVALID_ENT_REFERENCE; +stock int FindNearVisibleEntityCone(const char[] classname, const float origin[3], const float angles[3], float maxAngles, float maxRange, int ignoreEntity) { + int entity = -1; static float pos[3]; while ((entity = FindEntityByClassname(entity, classname)) != INVALID_ENT_REFERENCE) { // Skip entity, it's already being targetted - if(entityActiveTurret[entity] > 0) continue; - bool ragdoll = GetEntProp(entity, Prop_Data, "m_bClientSideRagdoll") == 1; - if(ragdoll) continue; - // if(GetEntProp(entity, Prop_Send, "m_iHealth") <= 0) continue; + if(entityActiveTurret[entity] > 0 || entityActiveMounted[entity] > 0) continue; + bool ragdolled = GetEntProp(entity, Prop_Data, "m_bClientSideRagdoll") == 1; + if(ragdolled) continue; GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); if(maxRange > 0.0 && GetVectorDistance(origin, pos, true) > maxRange) continue; pos[2] += 40.0; - if(CanSeePoint(origin, pos)) { + // TODO: fail if the computed angles to reach 'pos' are > angles + maxAngles + if(CanSeePoint(origin, pos, ignoreEntity)) { return entity; } + return entity; } return -1; } -stock bool CanSeePoint(const float origin[3], const float point[3]) { - TR_TraceRay(origin, point, MASK_SHOT, RayType_EndPoint); +stock int FindNearestVisibleEntity(const char[] classname, const float origin[3], float maxRange = 0.0, int ignoreEntity = 0) { + int entity = -1; + static float pos[3]; + while ((entity = FindEntityByClassname(entity, classname)) != INVALID_ENT_REFERENCE) { + // Skip entity, it's already being targetted + if(entityActiveTurret[entity] > 0 || entityActiveMounted[entity] > 0) continue; + bool ragdolled = GetEntProp(entity, Prop_Data, "m_bClientSideRagdoll") == 1; + if(ragdolled) continue; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos); + if(maxRange > 0.0 && GetVectorDistance(origin, pos, true) > maxRange) continue; + pos[2] += 40.0; + if(CanSeePoint(origin, pos, ignoreEntity)) { + return entity; + } + return entity; + } + return -1; +} + +stock bool CanSeePoint(const float origin[3], const float point[3], int ignoreEntity = 0) { + TR_TraceRayFilter(origin, point, MASK_SHOT, RayType_EndPoint, Filter_CanSeeEntity, ignoreEntity); return !TR_DidHit(); // Can see point if no collisions } @@ -589,13 +973,23 @@ bool Filter_CanSeeEntity(int entity, int contentsMask, int data) { return entity != data; } +bool Filter_IgnoreEntityWorld(int entity, int contentsMask, int data) { + return entity != data && entity != 0; +} + public void OnMapStart() { PrecacheParticle(PARTICLE_ELMOS); PrecacheParticle(PARTICLE_TES1); + g_iTracerIndex = GetParticleIndex(PARTICLE_WEAPON_TRACER); g_iBeamSprite = PrecacheModel("sprites/laser.vmt", true); g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); PrecacheSound(SOUND_LASER_FIRE); + PrecacheSound(SOUND_FIRE); + PrecacheSound(SOUND_IMPACT_HIT); + PrecacheSound(SOUND_IMPACT_MISS); + PrecacheModel(MountedGunModel[0]); + PrecacheModel(MountedGunModel[1]); if(g_iLaserIndex == 0) { LogError("g_iLaserIndex failed"); } @@ -637,12 +1031,6 @@ stock int CreateParticleNamed(const char[] targetname, const char[] sParticle, c } return -1; } - -stock void SetParent(int child, int parent) { - SetVariantString("!activator"); - AcceptEntityInput(child, "SetParent", parent); -} - /*#define MAX_IGNORE_TRACE 2 static char IGNORE_TRACE[MAX_IGNORE_TRACE][] = { "env_physics_blocker", @@ -684,10 +1072,36 @@ bool Filter_ManualTargetSights(int entity, int contentsMask, int data) { return true; } +float HULL_DEBUG_MIN[3] = { -50.0, -50.0, -20.0 }; +float HULL_DEBUG_MAX[3] = { 50.0, 50.0, 20.0 }; - +bool DebugEnumerator(int entity, int data) { + if(entity == 0 || entity == data) return false; + GlowEntity(entity, 3.0); + return false; +} 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(client == g_debugTracer) { + float pos[3]; + float ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_EnumerateEntitiesSphere(pos, 100.0, PARTITION_NON_STATIC_EDICTS, DebugEnumerator, client); + // TR_TraceHullFilter(pos, ang, HULL_DEBUG_MIN, HULL_DEBUG_MAX, MASK_SOLID, Filter_IgnoreEntityWorld, client); + // if(TR_DidHit()) { + // TR_GetEndPosition(pos); + + // // TODO: use enumerator + // int ent = TR_GetEntityIndex(); + // PrintCenterText(client, "HIT %d - %.0f %.0f %.0f", ent, pos[0], pos[1], pos[2]); + // if(ent > 0) { + // GlowEntity(ent, 3.0); + // } + // } else { + // PrintCenterText(client, "MISS"); + // } + } if(client == manualTargetter && turretCount > 0 && tickcount % 3 == 0) { static float pos[3], aimPos[3], orgPos[3]; GetClientEyePosition(client, orgPos);