#pragma semicolon 1#include <sourcemod>#include <sdktools>#include "includes/constants.sp"#include "includes/survivorindex.sp"#define PLUGIN_VERSION "0.0"#define TANK_SPAWN_TIME 20.0#define TANK_SPAWN_RADIUS 750public Plugin:myinfo ={ name = "Ghost Tank", author = "Mr. Zero + CanadaRox", description = "Ghost Mode Tanks!", version = PLUGIN_VERSION, url = "..."};// Tank Variablesnew g_iTankClient; // Client number for the active tanknew Float:g_fSpawnTimer; // How long does the tank have to spawn?new bool:g_bTooClose = false; // Is the tank within TANK_SPAWN_RADIUS of the survivors?new bool:g_bActiveTank = false; // Is this tank already active?new bool:g_bPassedToAID = false; // Has it been spawned through ForceSpawn()?new bool:g_bSpawned = false; // Should we be blocking +attack and +attack2?// Plugin Variablesnew bool:g_bPluginIsEnabled; // Is the plugin enabled?new bool:g_bHooked = false; // Have the events been hooked?new Handle:g_hEnable;public OnPluginStart(){ SI_OnModuleStart(); // Start up the survivor indexing stuff g_hEnable = CreateConVar("l4d2_ghosttank_enable", "1", "Sets whether the plugin is active or not.", FCVAR_PLUGIN); HookConVarChange(g_hEnable, ConVarChange_Enable);}public OnMapStart(){ // Make the variable match the CVAR if (GetConVarInt(g_hEnable) != 0) g_bPluginIsEnabled = true; else g_bPluginIsEnabled = false; // Hook/unhook events if they need to be if (g_bPluginIsEnabled && !g_bHooked) PluginEnable(); else if (!g_bPluginIsEnabled && g_bHooked) PluginDisable(); // Reset all variables Reset();}////////////////////////////////////////////////////////// Toggle g_bPluginIsEnabled so it matches the //// current ConVar value (0 = false, not 0 = true) //// A check is added so events with hooks aren't //// hooked and events which are not hooked don't //// get unhooked. //////////////////////////////////////////////////////////public ConVarChange_Enable(Handle:convar, const String:oldValue[], const String:newValue[]){ //Make the variable match the CVAR and do any hooking/unhooking required if (StringToInt(newValue) == 0) { if (g_bHooked) PluginDisable(); g_bPluginIsEnabled = false; } else { if (!g_bHooked) PluginEnable(); g_bPluginIsEnabled = true; }}////////////////////////////////////////////////////////// Hooks the events and sets g_bHooked to true so the //// events do not get hooked twice. //////////////////////////////////////////////////////////PluginEnable(){ HookEvent("round_start", Event_RoundStart); HookEvent("player_death", Event_PlayerDeath); HookEvent("tank_spawn", Event_TankSpawn); g_bHooked = true;}////////////////////////////////////////////////////////// Unhooks the events and sets g_bHooked to false so //// the events do not get unhooked twice. //////////////////////////////////////////////////////////PluginDisable(){ UnhookEvent("round_start", Event_RoundStart); UnhookEvent("player_death", Event_PlayerDeath); UnhookEvent("tank_spawn", Event_TankSpawn); g_bHooked = false;}////////////////////////////////////////////////////////// Resets all variables back to their default values. //// Runs ResetAID after a 10 second delay so auto //// spawned tanks do not get ghost mode. //////////////////////////////////////////////////////////Reset(){ g_bActiveTank = false; g_bSpawned = false; g_fSpawnTimer = TANK_SPAWN_TIME; CreateTimer(10.0, ResetAID, 0);}////////////////////////////////////////////////////////// Resets g_bPassedToAID for the next tank //////////////////////////////////////////////////////////public Action:ResetAID(Handle:timer){ g_bPassedToAID = false;}////////////////////////////////////////////////////////// Runs Reset() to reset all variables on the start //// of a round. //////////////////////////////////////////////////////////public Action:Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast) Reset();////////////////////////////////////////////////////////// This is run when the event tank_spawn occurs. It //// occurs when the tank is initally spawned and every //// time the tank passes control from client to client //// including the AI tanks before the first player and //// after the second player. //// First it checks if the tank is active to see if a //// player already had control of this tank so when it //// passed control it does not gain ghost mode. //// Next it checks if it has been made through an //// autospawn so that if a tank can't spawn within the //// timer they will not get a second chance in ghost //// mode. //// The tank is then put into ghost mode. If it is AI //// it freezes, assuming it is not already active. //// If it is passing to a player is unfreezes, and the //// spawn message/timer is created. g_bActiveTank is //// set to true. //////////////////////////////////////////////////////////public Action:Event_TankSpawn(Handle:event, const String:name[], bool:dontBroadcast){ new client = GetClientOfUserId(GetEventInt(event, "userid")); g_iTankClient = client; if (g_bActiveTank) return; // Don't continue if the tank is already player controlled if (g_bPassedToAID) // Don't give the tank ghost mode if it was AID spawned { g_bSpawned = true; return; } Ghost(); if(!IsFakeClient(client)) { Unfreeze(); g_bActiveTank = true; CreateTimer(0.5, SpawnTimer, INVALID_HANDLE, TIMER_REPEAT); } else Freeze();}////////////////////////////////////////////////////////// Check if the tank is too close on every frame. //////////////////////////////////////////////////////////public OnGameFrame() g_bTooClose = CheckLoc();////////////////////////////////////////////////////////// Show the tank his spawn timer and count it down. //// If he hasn't spawned before the end, force it to. //////////////////////////////////////////////////////////public Action:SpawnTimer(Handle:timer){ if (g_fSpawnTimer < 0 && !g_bSpawned) // If he has run out of time and is not yet spawned, force it { ForceSpawn(); return Plugin_Stop; } else if (g_bSpawned == true) // If he has spawned, stop the timer. { return Plugin_Stop; } // Show the correct message if they are too close/able to spawn if (g_bTooClose) PrintHintText(g_iTankClient, "YOU HAVE %.0f SECONDS TO SPAWN THE TANK\nYOU ARE TOO CLOSE TO SURVIVORS", g_fSpawnTimer); else PrintHintText(g_iTankClient, "YOU HAVE %.0f SECONDS TO SPAWN THE TANK\nPRESS SHOOT TO SPAWN", g_fSpawnTimer); SetEntProp(g_iTankClient, Prop_Send, "m_frustration", 0); // Lock frustration at 100% during ghost mode g_fSpawnTimer -= 0.5; return Plugin_Continue;}////////////////////////////////////////////////////////// Since the tank_death event doesn't always work, //// the player death event is hooked with a check for //// if the player was the tank. If they were the tank //// it resets all the variables. //////////////////////////////////////////////////////////public Action:Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast){ if (!g_bActiveTank) return; new client = GetClientOfUserId(GetEventInt(event, "userid")); if (client != g_iTankClient) return; Reset();}////////////////////////////////////////////////////////// First it makes sure a tank is active so it wont //// block commands when there is no tank. Then it //// checks if the player is the client, that the tank //// is not spawned and if the button pressed is either //// +attack or +attack2. For +attack, check if the //// the tank has spawned, and if it has not, check its //// proximity to survivors. If he can spawn, then //// spawn him, but block the command either way so he //// doesn't spawn punching. And block +attack when he //// is not spawned. //////////////////////////////////////////////////////////public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon){ if (!g_bActiveTank) return Plugin_Continue; if (client == g_iTankClient && !g_bSpawned && (buttons & IN_ATTACK)) // Has an ghost mode tank pressed +attack? { if (g_bTooClose) // And is he too close? { g_bSpawned = false; return Plugin_Handled; } else { Spawn(); g_bSpawned = true; return Plugin_Handled; } } else if (client == g_iTankClient && !g_bSpawned && (buttons & IN_ATTACK2)) // Has a ghost moe tank pressed +attack2? { return Plugin_Handled; } return Plugin_Continue;}////////////////////////////////////////////////////////// Spawn the tank, then check if he is too close. //// if he is, tell the tank it is being autospawned, //// kill it, remove z_spawn's cheat flag, and spawn a //// tank allowing the director to select its spawn. //////////////////////////////////////////////////////////ForceSpawn(){ Spawn(); // Spawn it. if (g_bTooClose) // Is it too close? { new flags = GetCommandFlags("z_spawn"); g_bPassedToAID = true; g_bSpawned = true; PrintHintText(g_iTankClient, "TANK IS AUTOSPAWNING!"); ForcePlayerSuicide(g_iTankClient); SetCommandFlags("z_spawn", flags & ~FCVAR_CHEAT); FakeClientCommand(g_iTankClient, "z_spawn tank auto"); SetCommandFlags("z_spawn", flags); }}////////////////////////////////////////////////////////// Put the tank into ghost mode. //////////////////////////////////////////////////////////Ghost() SetEntProp(g_iTankClient, Prop_Send, "m_isGhost", 1, 1);////////////////////////////////////////////////////////// Remove the tank from ghost mode and tell him that //// he has been spawned. If he has been autospawned //// the message will immediately be replaced by the //// force spawn message. //////////////////////////////////////////////////////////public Spawn(){ SetEntProp(g_iTankClient, Prop_Send, "m_isGhost", 0, 1); PrintHintText(g_iTankClient, "YOU HAVE SPAWNED!"); g_bSpawned = true;}////////////////////////////////////////////////////////// Freeze for the initial AI tank. //////////////////////////////////////////////////////////Freeze() SetEntityMoveType(g_iTankClient, MOVETYPE_NONE);////////////////////////////////////////////////////////// Unfreeze for when the player gains control. //////////////////////////////////////////////////////////Unfreeze() SetEntityMoveType(g_iTankClient, MOVETYPE_CUSTOM);////////////////////////////////////////////////////////// Calculates the location of the tank, and returns //// if it is too close to the survivors or not. //////////////////////////////////////////////////////////bool:CheckLoc(){ decl Float:fTankLoc[3]; decl Float:fSurvLoc[4][3]; new Float:fDistance; GetClientAbsOrigin(g_iTankClient, fTankLoc); for (new i = 0; i < NUM_OF_SURVIVORS; i++) { GetClientAbsOrigin(iSurvivorIndex[i], fSurvLoc[i]); fDistance = GetVectorDistance(fTankLoc, fSurvLoc[i]); if (fDistance <= TANK_SPAWN_RADIUS) return true; } return false;}