Files
SMScripts/scripting/towerdefense.sp
2025-04-15 22:27:20 -04:00

1554 lines
40 KiB
SourcePawn

#pragma semicolon 1
#pragma newdecls required
#include <sourcemod>
#include <sdkhooks>
#include <sdktools>
#include <SteamWorks>
#include <tf2>
#include <tf2_stocks>
#include <tf2items>
#include <tf2attributes>
#include <morecolors>
/*=================================
= Constants =
=================================*/
#define PLUGIN_NAME "TF2 Tower Defense"
#define PLUGIN_AUTHOR "floube, benedevil, hurpdurp, dragonisser"
#define PLUGIN_DESC "Stop enemies from crossing a map by buying towers and building up defenses."
#define PLUGIN_VERSION "2.1.3"
#define PLUGIN_URL "https://github.com/tf2td/towerdefense"
#define PLUGIN_PREFIX "[TF2TD]"
/*==========================================
= Plugin Information =
==========================================*/
public Plugin myinfo =
{
name = PLUGIN_NAME,
author = PLUGIN_AUTHOR,
description = PLUGIN_DESC,
version = PLUGIN_VERSION,
url = PLUGIN_URL
};
/*=======================================
= Custom Includes =
=======================================*/
#include "towerdefense/info/constants.sp"
#include "towerdefense/info/enums.sp"
#include "towerdefense/info/variables.sp"
#include "towerdefense/info/convars.sp"
#include "towerdefense/util/log.sp"
#include "towerdefense/util/metal.sp"
#include "towerdefense/util/steamid.sp"
#include "towerdefense/util/tf2items.sp"
#include "towerdefense/util/zones.sp"
#include "towerdefense/handler/antiair.sp"
#include "towerdefense/handler/aoe.sp"
#include "towerdefense/handler/buttons.sp"
#include "towerdefense/handler/corners.sp"
#include "towerdefense/handler/metalpacks.sp"
#include "towerdefense/handler/panels.sp"
#include "towerdefense/handler/player.sp"
#include "towerdefense/handler/server.sp"
#include "towerdefense/handler/sounds.sp"
#include "towerdefense/handler/towers.sp"
#include "towerdefense/handler/waves.sp"
#include "towerdefense/handler/weapons.sp"
#include "towerdefense/database/general.sp"
#include "towerdefense/database/player.sp"
#include "towerdefense/database/server.sp"
#include "towerdefense/commands.sp"
#include "towerdefense/events.sp"
#include "towerdefense/timers.sp"
/*=======================================
= Public Forwards =
=======================================*/
public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int iMaxLength) {
if (GetEngineVersion() != Engine_TF2) {
Format(sError, iMaxLength, "This mod can only run under TF2.");
return APLRes_Failure;
}
if (SQL_CheckConfig("towerdefense"))
Database.Connect(ConnectToDB, "towerdefense");
else {
Format(sError, iMaxLength, "Unable to read database info from file");
return APLRes_Failure;
}
MarkNativeAsOptional("SteamWorks_SetGameDescription");
MarkNativeAsOptional("GetUserMessageType");
return APLRes_Success;
}
public void OnPluginStart() {
PrintToServer("%s Loaded %s %s by %s", PLUGIN_PREFIX, PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR);
LoadTranslations("towerdefense.phrases.txt");
Log_Initialize(TDLogLevel_Trace, TDLogType_Console);
CreateDataMap(g_hMapTowers);
CreateDataMap(g_hMapWeapons);
CreateDataMap(g_hMapWaves);
CreateDataMap(g_hMapMetalpacks);
CreateDataMap(g_hMultiplierType);
CreateDataMap(g_hMultiplier);
CreateDataMap(g_hServerData);
CreateDataMap(g_hPlayerData);
HookEvents();
RegisterCommands();
CreateConVars();
LoadConVars();
SetConVars();
SetPassword("WaitingForServerToInitialize", false);
}
public void OnPluginEnd() {
if (g_bSteamWorks) {
SteamWorks_SetGameDescription("Team Fortress");
}
if (g_hDatabase != null) {
CloseHandle(g_hDatabase);
g_hDatabase = null;
}
if (g_hMapTowers != null) {
CloseHandle(g_hMapTowers);
g_hMapTowers = null;
}
if (g_hMapWeapons != null) {
CloseHandle(g_hMapWeapons);
g_hMapWeapons = null;
}
if (g_hMapWaves != null) {
CloseHandle(g_hMapWaves);
g_hMapWaves = null;
}
if (g_hMapMetalpacks != null) {
CloseHandle(g_hMapMetalpacks);
g_hMapMetalpacks = null;
}
if (g_hMultiplierType != null) {
CloseHandle(g_hMultiplierType);
g_hMultiplierType = null;
}
if (g_hMultiplier != null) {
CloseHandle(g_hMultiplier);
g_hMultiplier = null;
}
if (g_hServerData != null) {
CloseHandle(g_hServerData);
g_hServerData = null;
}
if (g_hPlayerData != null) {
CloseHandle(g_hPlayerData);
g_hPlayerData = null;
}
FindConVar("sv_cheats").SetInt(0, true, false);
}
public void OnMapStart() {
g_bTowerDefenseMap = IsTowerDefenseMap();
PrecacheModels();
PrecacheSounds();
g_iHealthBar = GetHealthBar();
g_bConfigsExecuted = false;
}
public void OnMapEnd() {
g_bMapRunning = false;
bool bWasEnabled = g_bEnabled;
g_bEnabled = false;
if (bWasEnabled) {
UpdateGameDescription();
}
AddConVarFlag("sv_cheats", FCVAR_NOTIFY);
AddConVarFlag("sv_tags", FCVAR_NOTIFY);
AddConVarFlag("tf_bot_count", FCVAR_NOTIFY);
AddConVarFlag("sv_password", FCVAR_NOTIFY);
}
public void OnConfigsExecuted() {
g_bEnabled = g_hEnabled.BoolValue && g_bTowerDefenseMap && g_bSteamWorks && g_bTF2Attributes;
g_bMapRunning = true;
UpdateGameDescription();
if (!g_bEnabled) {
if (!g_bTowerDefenseMap) {
char sCurrentMap[PLATFORM_MAX_PATH];
GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
Log(TDLogLevel_Info, "Map \"%s\" is not supported, Tower Defense has been disabled.", sCurrentMap);
SetPassword("", false);
} else {
Log(TDLogLevel_Info, "Tower Defense is disabled.");
}
return;
}
g_bServerInitialized = false;
CreateTimer(5.0, InitializeDelay, INVALID_HANDLE, TIMER_FLAG_NO_MAPCHANGE);
}
public Action InitializeDelay(Handle hTimer, any iTime) {
Server_Initialize();
return Plugin_Stop;
}
public void OnAllPluginsLoaded() {
g_bSteamWorks = LibraryExists("SteamWorks");
if (g_bSteamWorks) {
UpdateGameDescription();
Log(TDLogLevel_Debug, "Found SteamWorks on startup");
}
g_bTF2Attributes = LibraryExists("tf2attributes");
if (g_bTF2Attributes) {
Log(TDLogLevel_Debug, "Found TF2Attributes on startup");
}
}
public void OnLibraryAdded(const char[] sName) {
if (StrEqual(sName, "SteamWorks", false)) {
g_bSteamWorks = true;
UpdateGameDescription();
Log(TDLogLevel_Debug, "SteamWorks loaded");
} else if (StrEqual(sName, "tf2attributes", false)) {
g_bTF2Attributes = true;
Log(TDLogLevel_Debug, "TF2Attributes loaded");
}
}
public void OnLibraryRemoved(const char[] sName) {
if (StrEqual(sName, "SteamWorks", false)) {
g_bSteamWorks = false;
Log(TDLogLevel_Debug, "SteamWorks unloaded");
} else if (StrEqual(sName, "tf2attributes", false)) {
g_bTF2Attributes = false;
Log(TDLogLevel_Debug, "TF2Attributes unloaded");
}
}
public void OnClientPutInServer(int iClient) {
for (int i = 0; i < sizeof(g_fBeamPoints[]); i++) {
g_fBeamPoints[iClient][i][0] = -1.0;
g_fBeamPoints[iClient][i][1] = -1.0;
g_fBeamPoints[iClient][i][2] = -1.0;
}
}
public void OnClientPostAdminCheck(int iClient) {
if (!g_bEnabled) {
return;
}
char sSteamId[32];
GetClientAuthId(iClient, AuthId_Steam2, sSteamId, sizeof(sSteamId));
if (!StrEqual(sSteamId, "BOT")) {
char sCommunityId[32];
GetClientCommunityId(iClient, sCommunityId, sizeof(sCommunityId));
char sIp[32];
GetClientIP(iClient, sIp, sizeof(sIp));
Player_Connected(GetClientUserId(iClient), iClient, GetClientNameShort(iClient), sSteamId, sCommunityId, sIp);
}
SDKHook(iClient, SDKHook_OnTakeDamage, OnTakeDamage);
SDKHook(iClient, SDKHook_OnTakeDamagePost, OnTakeDamagePost);
}
public void OnClientDisconnect(int iClient) {
if (!g_bEnabled) {
return;
}
if (IsTower(g_iAttachedTower[iClient])) {
Tower_OnCarrierDisconnected(g_iAttachedTower[iClient], iClient);
}
if (IsDefender(iClient)) {
int iMetal = GetClientMetal(iClient);
if (iMetal > 0) {
float fLocation[3];
GetClientEyePosition(iClient, fLocation);
fLocation[2] = fLocation[2] - GetDistanceToGround(fLocation) + 10.0;
SpawnMetalPack(TDMetalPack_Medium, fLocation, iMetal);
}
UpdateGameDescription();
}
}
public Action OnPlayerRunCmd(int iClient, int &iButtons, int &iImpulse, float fVelocity[3], float fAngles[3], int &iWeapon) {
if (!g_bEnabled) {
return Plugin_Continue;
}
// Force towers to shoot
if (IsTower(iClient)) {
TDTowerId iTowerId = GetTowerId(iClient);
// Refill ammo for airblast tower
if (iTowerId == TDTower_Airblast_Pyro) {
int iOffset = FindSendPropInfo("CTFPlayer", "m_iAmmo");
if (GetEntData(iClient, iOffset + 4) <= 0) {
SetEntData(iClient, iOffset + 4, 100);
}
}
if (iTowerId == TDTower_AoE_Engineer && g_bAoEEngineerAttack) {
iButtons |= IN_ATTACK;
}
if (Tower_GetAttackPrimary(iTowerId)) {
iButtons |= IN_ATTACK;
}
if (Tower_GetAttackSecondary(iTowerId)) {
iButtons |= IN_ATTACK2;
}
if (Tower_GetRotate(iTowerId) && g_bTowersLocked) {
float fClientEyePosition[3];
GetClientEyePosition(iClient, fClientEyePosition);
int iClosest = GetClosestClient(iClient);
if (!IsValidClient(iClosest) || !IsClientConnected(iClosest) || !IsClientInGame(iClosest))
return Plugin_Continue;
float fClosestLocation[3];
GetClientAbsOrigin(iClosest, fClosestLocation);
float fVector[3];
MakeVectorFromPoints(fClosestLocation, fClientEyePosition, fVector);
float fAngle[3];
GetVectorAngles(fVector, fAngle);
fAngle[0] = (fAngle[0] * -1.0) + 355;
fAngle[1] += 180.0;
TeleportEntity(iClient, NULL_VECTOR, fAngle, NULL_VECTOR);
}
}
if (IsAttacker(iClient) && g_bBoostWave) {
fVelocity[0] = 500.0;
}
if (IsAttacker(iClient) && g_iSlowAttacker[iClient]) {
fVelocity[0] = -10000.0;
}
if (IsDefender(iClient)) {
// Attach/detach tower on right-click
if (IsButtonReleased(iClient, iButtons, IN_ATTACK2)) {
char sActiveWeapon[64];
GetClientWeapon(iClient, sActiveWeapon, sizeof(sActiveWeapon));
if (StrEqual(sActiveWeapon, "tf_weapon_wrench") || StrEqual(sActiveWeapon, "tf_weapon_robot_arm")) {
if (IsTower(g_iAttachedTower[iClient])) {
Tower_Drop(iClient);
} else {
Tower_Pickup(iClient);
}
}
}
// Show tower info on left-click
if (IsButtonReleased(iClient, iButtons, IN_ATTACK)) {
char sActiveWeapon[64];
GetClientWeapon(iClient, sActiveWeapon, sizeof(sActiveWeapon));
if (StrEqual(sActiveWeapon, "tf_weapon_wrench") || StrEqual(sActiveWeapon, "tf_weapon_robot_arm")) {
Tower_ShowInfo(iClient);
}
}
float fLocation[3], fViewAngles[3];
GetClientEyePosition(iClient, fLocation);
GetClientEyeAngles(iClient, fViewAngles);
TR_TraceRayFilter(fLocation, fViewAngles, MASK_VISIBLE, RayType_Infinite, TraceRayEntities, iClient);
if (TR_DidHit()) {
int iAimEntity = TR_GetEntityIndex();
if (IsValidEntity(iAimEntity)) {
char sClassname[64];
GetEntityClassname(iAimEntity, sClassname, sizeof(sClassname));
if (StrEqual(sClassname, "func_breakable")) {
char sName[64];
GetEntPropString(iAimEntity, Prop_Data, "m_iName", sName, sizeof(sName));
if (StrContains(sName, "break_tower_") != -1) {
float fEntityLocation[3];
GetEntPropVector(iAimEntity, Prop_Send, "m_vecOrigin", fEntityLocation);
if (GetVectorDistance(fLocation, fEntityLocation) <= 512.0) {
TDTowerId iTowerId;
if (StrContains(sName, "break_tower_tp_") != -1) {
char sNameParts[4][32];
ExplodeString(sName, "_", sNameParts, sizeof(sNameParts), sizeof(sNameParts[]));
iTowerId = view_as<TDTowerId>(StringToInt(sNameParts[3]));
Tower_GetName(iTowerId, sName, sizeof(sName));
} else {
char sNameParts[3][32];
ExplodeString(sName, "_", sNameParts, sizeof(sNameParts), sizeof(sNameParts[]));
iTowerId = view_as<TDTowerId>(StringToInt(sNameParts[2]));
Tower_GetName(iTowerId, sName, sizeof(sName));
}
char sDescription[1024];
if (Tower_GetDescription(iTowerId, sDescription, sizeof(sDescription))) {
char sDamagetype[64];
if (Tower_GetDamagetype(iTowerId, sDamagetype, sizeof(sDamagetype))) {
PrintToHud(iClient, "\
%s \n\
--------------- \n\
Price: %d metal (%d metal/player)\n\
Damagetype: %s \n\
Number of Levels: %d \n\
--------------- \n\
%s",
sName, Tower_GetPrice(iTowerId), Tower_GetPrice(iTowerId) / GetRealClientCount(true), sDamagetype, Tower_GetMaxLevel(iTowerId), sDescription);
}
}
}
}
}
}
}
}
if (g_bPickupSentry[iClient]) {
iButtons |= IN_ATTACK2;
g_bPickupSentry[iClient] = false;
}
g_iLastButtons[iClient] = iButtons;
return Plugin_Continue;
}
public Action OnTakeDamage(int iClient, int &iAttacker, int &iInflictor, float &fDamage, int &iDamageType, int &iWeapon, float fDamageForce[3], float fDamagePosition[3]) {
if (!g_bEnabled) {
return Plugin_Continue;
}
// Block tower taking damage
if (IsTower(iClient)) {
return Plugin_Handled;
}
if (IsClientInGame(iClient)) {
if (!IsValidEdict(iInflictor)) {
return Plugin_Continue;
}
char sAttackerObject[128];
GetEdictClassname(iInflictor, sAttackerObject, sizeof(sAttackerObject));
// Sentry Damage
if (StrEqual(sAttackerObject, "obj_sentrygun")) {
fDamage *= fMultiplier[Multiplier_GetInt("sentry")] + 1.0;
// Register Damage For Stats
if (IsValidClient(iAttacker) && IsDefender(iAttacker)) {
Player_CAddValue(iAttacker, PLAYER_DAMAGE, RoundToZero(fDamage));
}
return Plugin_Changed;
}
// Blast Damage
if (iDamageType & DMG_BLAST) {
fDamage *= fMultiplier[Multiplier_GetInt("explosion")] + 1.0;
// Register Damage For Stats
if (IsValidClient(iAttacker) && IsDefender(iAttacker)) {
Player_CAddValue(iAttacker, PLAYER_DAMAGE, RoundToZero(fDamage));
}
return Plugin_Changed;
}
// Fire Damage
if (iDamageType & DMG_BURN) {
fDamage *= fMultiplier[Multiplier_GetInt("fire")] + 1.0;
// Register Damage For stats
if (IsValidClient(iAttacker) && IsDefender(iAttacker)) {
Player_CAddValue(iAttacker, PLAYER_DAMAGE, RoundToZero(fDamage));
}
return Plugin_Changed;
}
// Bullet Damage
if (iDamageType & DMG_BULLET) {
fDamage *= fMultiplier[Multiplier_GetInt("bullet")] + 1.0;
// Register Damage For Stats
if (IsValidClient(iAttacker) && IsDefender(iAttacker)) {
Player_CAddValue(iAttacker, PLAYER_DAMAGE, RoundToZero(fDamage));
}
return Plugin_Changed;
}
}
if (IsValidClient(iAttacker) && IsDefender(iAttacker)) {
Player_CAddValue(iAttacker, PLAYER_DAMAGE, RoundToZero(fDamage));
}
if (IsDefender(iClient)) {
if (IsValidClient(iAttacker)) {
if (iClient == iAttacker || GetClientTeam(iClient) != GetClientTeam(iAttacker)) {
if (fDamage >= GetClientHealth(iClient)) {
int iMetal = GetClientMetal(iClient) / 2;
if (iMetal > 0) {
float fLocation[3];
GetClientEyePosition(iClient, fLocation);
fLocation[2] = fLocation[2] - GetDistanceToGround(fLocation) + 10.0;
SpawnMetalPack(TDMetalPack_Medium, fLocation, iMetal);
}
}
}
}
}
return Plugin_Continue;
}
public void OnTakeDamagePost(int iClient, int iAttacker, int iInflictor, float fDamage, int iDamageType) {
if (!g_bEnabled) {
return;
}
if (IsAttacker(iClient)) {
Wave_OnTakeDamagePost(iClient, iAttacker, iInflictor, fDamage, iDamageType);
}
}
public void OnEntityCreated(int iEntity, const char[] sClassname) {
if (StrEqual(sClassname, "tf_ammo_pack") || StrEqual(sClassname, "tf_dropped_weapon")) {
SDKHook(iEntity, SDKHook_SpawnPost, OnEntitySpawnKill);
} else if (StrEqual(sClassname, "func_breakable")) {
SDKHook(iEntity, SDKHook_SpawnPost, OnButtonSpawned);
} else if (StrEqual(sClassname, "tf_projectile_rocket") || StrEqual(sClassname, "tf_projectile_flare")) {
SDKHook(iEntity, SDKHook_SpawnPost, OnAntiAirProjectileSpawned);
} else if (StrContains(sClassname, "tf_projectile_") != -1) {
SDKHook(iEntity, SDKHook_SpawnPost, OnProjectileSpawned);
} else if (StrEqual(sClassname, "trigger_multiple")) {
SDKHook(iEntity, SDKHook_StartTouchPost, Wave_OnTouchCorner);
}
}
public void OnEntitySpawnKill(int iEntity) {
AcceptEntityInput(iEntity, "Kill");
}
public void OnButtonSpawned(int iEntity) {
if (!g_bEnabled) {
return;
}
char sName[64];
GetEntPropString(iEntity, Prop_Data, "m_iName", sName, sizeof(sName));
if (StrEqual(sName, "wave_start")) {
g_iWaveStartButton = iEntity;
GetEntPropVector(iEntity, Prop_Send, "m_vecOrigin", g_fWaveStartButtonLocation);
}
}
public void OnAntiAirProjectileSpawned(int iEntity) {
if (!g_bEnabled) {
return;
}
SDKHook(iEntity, SDKHook_ShouldCollide, OnProjectileCollide);
int iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity");
if (IsTower(iOwner)) {
AntiAir_ProjectileTick(iEntity, iOwner);
}
}
public void OnProjectileSpawned(int iEntity) {
if (!g_bEnabled) {
return;
}
SDKHook(iEntity, SDKHook_ShouldCollide, OnProjectileCollide);
}
public bool OnProjectileCollide(int iEntity, int iCollisiongroup, int iContentsmask, bool bResult) {
if (!g_bEnabled) {
return true;
}
int iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hOwnerEntity");
if (IsValidEntity(iOwner)) {
int iTeam = GetEntProp(iOwner, Prop_Send, "m_iTeamNum");
float fLocation[3], fAngles[3];
GetEntPropVector(iEntity, Prop_Data, "m_vecOrigin", fLocation);
GetEntPropVector(iEntity, Prop_Data, "m_angAbsRotation", fAngles);
TR_TraceRayFilter(fLocation, fAngles, MASK_PLAYERSOLID, RayType_Infinite, TraceRayPlayers);
if (TR_DidHit()) {
int iTarget = TR_GetEntityIndex();
if (IsValidClient(iTarget)) {
if (GetClientTeam(iTarget) != iTeam) {
SetEntProp(iEntity, Prop_Data, "m_CollisionGroup", 0);
} else {
SetEntProp(iEntity, Prop_Data, "m_CollisionGroup", 24);
}
}
}
}
return true;
}
public Action OnNobuildEnter(int iEntity, int iClient) {
if (!g_bEnabled) {
return Plugin_Continue;
}
if (IsDefender(iClient)) {
g_bInsideNobuild[iClient] = true;
} else if (IsTower(iClient)) {
if (IsDefender(g_iLastMover[iClient])) {
Tower_OnTouchNobuild(iClient);
}
}
return Plugin_Continue;
}
public Action OnNobuildExit(int iEntity, int iClient) {
if (!g_bEnabled) {
return Plugin_Continue;
}
if (IsDefender(iClient)) {
g_bInsideNobuild[iClient] = false;
}
return Plugin_Continue;
}
/*=========================================
= Utility Functions =
=========================================*/
/**
* Checks if a client is a valid client.
*
* @param iClient The clients index.
* @return True on success, false otherwise.
*/
stock bool IsValidClient(int iClient) {
return (iClient > 0 && iClient <= MaxClients);
}
/**
* Checks if a client is a defender.
*
* @param iClient The client.
* @return True on success, false otherwise.
*/
stock bool IsDefender(int iClient) {
return (IsValidClient(iClient) && IsClientConnected(iClient) && IsClientInGame(iClient) && !IsFakeClient(iClient) && GetClientTeam(iClient) == TEAM_DEFENDER);
}
/**
* Checks if a client is a tower.
*
* @param iClient The client.
* @return True on success, false otherwise.
*/
stock bool IsTower(int iClient) {
return (IsValidClient(iClient) && IsClientConnected(iClient) && IsClientInGame(iClient) && IsFakeClient(iClient) && GetClientTeam(iClient) == TEAM_DEFENDER);
}
/**
* Checks if a client is an attacker.
*
* @param iClient The client.
* @return True on success, false otherwise.
*/
stock bool IsAttacker(int iClient) {
return (IsValidClient(iClient) && IsClientConnected(iClient) && IsClientInGame(iClient) && IsFakeClient(iClient) && GetClientTeam(iClient) == TEAM_ATTACKER);
}
/**
* Checks if the current map is a Tower Defense map,
* which can either start with td_ or tf2td_.
*
* @return True if the current map is a Tower Defense map, false otherwise.
*/
stock bool IsTowerDefenseMap() {
char sCurrentMap[PLATFORM_MAX_PATH];
GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
return (strncmp(sCurrentMap, "td_", 3) == 0 || strncmp(sCurrentMap, "tf2td_", 6) == 0);
}
/**
* Changes the 'Game' tab in the server browser, according to the plugins state.
*
* @noreturn
*/
stock void UpdateGameDescription() {
if (!g_bSteamWorks) {
return;
}
char sGamemode[64];
if (g_bEnabled) {
Format(sGamemode, sizeof(sGamemode), "%s (%s)", GAME_DESCRIPTION, PLUGIN_VERSION);
} else {
strcopy(sGamemode, sizeof(sGamemode), "Team Fortress");
}
SteamWorks_SetGameDescription(sGamemode);
}
/**
* Precaches needed models.
*
* @noreturn
*/
stock void PrecacheModels() {
PrecacheModel("models/bots/scout/bot_scout.mdl");
PrecacheModel("models/bots/sniper/bot_sniper.mdl");
PrecacheModel("models/bots/soldier/bot_soldier.mdl");
PrecacheModel("models/bots/demo/bot_demo.mdl");
PrecacheModel("models/bots/medic/bot_medic.mdl");
PrecacheModel("models/bots/heavy/bot_heavy.mdl");
PrecacheModel("models/bots/pyro/bot_pyro.mdl");
PrecacheModel("models/bots/spy/bot_spy.mdl");
PrecacheModel("models/bots/engineer/bot_engineer.mdl");
PrecacheModel("models/items/ammopack_large.mdl");
PrecacheModel("models/items/ammopack_medium.mdl");
PrecacheModel("models/items/ammopack_small.mdl");
g_iLaserMaterial = PrecacheModel("materials/sprites/laserbeam.vmt");
g_iHaloMaterial = PrecacheModel("materials/sprites/halo01.vmt");
}
/**
* Precaches needed sounds.
*
* @noreturn
*/
stock void PrecacheSounds() {
char sRoboPath[64];
for (int i = 1; i <= 18; i++) {
Format(sRoboPath, sizeof(sRoboPath), "mvm/player/footsteps/robostep_%s%i.mp3", (i < 10) ? "0" : "", i);
PrecacheSound(sRoboPath);
}
PrecacheSound("items/ammo_pickup.wav");
PrecacheSound("items/gunpickup2.wav");
PrecacheSound("vo/announcer_begins_5sec.mp3");
PrecacheSound("vo/announcer_begins_4sec.mp3");
PrecacheSound("vo/announcer_begins_3sec.mp3");
PrecacheSound("vo/announcer_begins_2sec.mp3");
PrecacheSound("vo/announcer_begins_1sec.mp3");
PrecacheSound("vo/mvm_mannup_wave_end01.mp3");
PrecacheSound("vo/mvm_mannup_wave_end02.mp3");
PrecacheSound("vo/mvm_wave_end01.mp3");
PrecacheSound("vo/mvm_wave_end02.mp3");
PrecacheSound("vo/mvm_wave_end03.mp3");
PrecacheSound("vo/mvm_wave_end04.mp3");
PrecacheSound("vo/mvm_wave_end05.mp3");
PrecacheSound("vo/mvm_wave_end06.mp3");
PrecacheSound("vo/mvm_wave_end07.mp3");
PrecacheSound("vo/engineer_no01.mp3");
PrecacheSound("vo/engineer_no02.mp3");
PrecacheSound("vo/engineer_no03.mp3");
PrecacheSound("music/mvm_start_mid_wave.wav");
PrecacheSound("music/mvm_start_last_wave.wav");
PrecacheSound("music/mvm_end_last_wave.wav");
}
/**
* Counts non-fake clients connected to the game.
*
* @param bInGameOnly Only count clients which are in-game.
* @return The client count.
*/
stock int GetRealClientCount(bool bInGameOnly = false) {
int iClients = 0;
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (((bInGameOnly) ? IsClientInGame(iClient) : IsClientConnected(iClient)) && !IsFakeClient(iClient)) {
iClients++;
}
}
return iClients;
}
/**
* Checks if a button is being pressed/was pressed.
*
* @param iClient The client.
* @param iButtons The clients current buttons.
* @param iButton The button to check for.
* @return True if pressed, false otherwise.
*/
stock bool IsButtonPressed(int iClient, int iButtons, int iButton) {
return ((iButtons & iButton) == iButton && (g_iLastButtons[iClient] & iButton) != iButton);
}
/**
* Checks if a button is being released/was released.
*
* @param iClient The client.
* @param iButtons The clients current buttons.
* @param iButton The button to check for.
* @return True if released, false otherwise.
*/
stock bool IsButtonReleased(int iClient, int iButtons, int iButton) {
return ((g_iLastButtons[iClient] & iButton) == iButton && (iButtons & iButton) != iButton);
}
/**
* Get the target a client is aiming at.
*
* @param iClient The client.
* @return The targets or -1 if no target is being aimed at.
*/
stock int GetAimTarget(int iClient) {
float fLocation[3], fAngles[3];
GetClientEyePosition(iClient, fLocation);
GetClientEyeAngles(iClient, fAngles);
TR_TraceRayFilter(fLocation, fAngles, MASK_PLAYERSOLID, RayType_Infinite, TraceRayPlayers, iClient);
if (TR_DidHit()) {
int iEntity = TR_GetEntityIndex();
if (IsValidEntity(iEntity) && IsValidClient(iEntity)) {
return iEntity;
}
}
return -1;
}
public bool TraceRayPlayers(int iEntity, int iMask, any iData) {
return (iEntity != iData) && IsValidClient(iEntity);
}
/**
* Get the entity a client is aiming at.
*
* @param iClient The client.
* @return The entity or -1 if no entity is being aimed at.
*/
stock int GetAimEntity(int iClient) {
float fLocation[3], fAngles[3];
GetClientEyePosition(iClient, fLocation);
GetClientEyeAngles(iClient, fAngles);
TR_TraceRayFilter(fLocation, fAngles, MASK_PLAYERSOLID, RayType_Infinite, TraceRayEntities, iClient);
if (TR_DidHit()) {
int iEntity = TR_GetEntityIndex();
if (IsValidEntity(iEntity)) {
return iEntity;
}
}
return -1;
}
public bool TraceRayEntities(int iEntity, int iMask, any iData) {
return (iEntity != iData) && iEntity > MaxClients && IsValidEntity(iEntity);
}
/**
* Gets a client by name.
*
* @param iClient The executing clients index.
* @param sName The clients name.
* @return The clients client index, or -1 on error.
*/
stock int GetClientByName(int iClient, char[] sName) {
char sNameOfIndex[MAX_NAME_LENGTH + 1];
int iLen = strlen(sName);
int iResults = 0;
int iTarget = 0;
for (int i = 1; i <= MaxClients; i++) {
if (IsClientInGame(i)) {
GetClientName(i, sNameOfIndex, sizeof(sNameOfIndex));
Substring(sNameOfIndex, sizeof(sNameOfIndex), sNameOfIndex, sizeof(sNameOfIndex), 0, iLen);
if (StrEqual(sName, sNameOfIndex)) {
iResults++;
iTarget = i;
}
}
}
if (iResults == 1) {
return iTarget;
} else if (iResults == 0) {
if (IsDefender(iClient)) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidCantFindPlayer", sName);
}
} else {
if (IsDefender(iClient)) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidTooUnspecific");
}
}
return -1;
}
/**
* Gets a substring of an string.
*
* @param sDest The string to copy to.
* @param iDestLength The length of the destination string.
* @param sSource The string to copy from.
* @param iSourceLength The length of the source string.
* @param iStart The positon to start the copy from.
* @param iEnd The postion to end the copy.
* @return True on success, false otherwise.
*/
stock bool Substring(char[] sDest, int iDestLength, char[] sSource, int iSourceLength, int iStart, int iEnd) {
if (iEnd < iStart || iEnd > (iSourceLength - 1)) {
strcopy(sDest, iDestLength, NULL_STRING);
return false;
} else {
strcopy(sDest, (iEnd - iStart + 1), sSource[iStart]);
return true;
}
}
/**
* Checks if a string is numeric.
*
* @param sText The string which should be checked.
* @return True if number, false otherwise.
*/
stock bool IsStringNumeric(char[] sText) {
for (int iChar = 0; iChar < strlen(sText); iChar++) {
if (!IsCharNumeric(sText[iChar])) {
return false;
}
}
return true;
}
/**
* Gets the distance between a location and the ground.
*
* @param fLocation The location vector.
* @return Distance to the ground.
*/
stock float GetDistanceToGround(float fLocation[3]) {
float fGround[3];
TR_TraceRayFilter(fLocation, view_as<float>({ 90.0, 0.0, 0.0 }), MASK_PLAYERSOLID, RayType_Infinite, TraceRayNoPlayers, 0);
if (TR_DidHit()) {
TR_GetEndPosition(fGround);
return GetVectorDistance(fLocation, fGround);
}
return 0.0;
}
public bool TraceRayNoPlayers(int iEntity, int iMask, any iData) {
return !(iEntity == iData || IsValidClient(iEntity));
}
/**
* Sets the model of a client to the robot model.
*
* @param iClient The client.
* @noreturn
*/
stock void SetRobotModel(int iClient) {
char sClass[16], sModelPath[PLATFORM_MAX_PATH];
GetClientClassName(iClient, sClass, sizeof(sClass));
Format(sModelPath, sizeof(sModelPath), "models/bots/%s/bot_%s.mdl", sClass, sClass);
SetVariantString(sModelPath);
AcceptEntityInput(iClient, "SetCustomModel");
SetEntProp(iClient, Prop_Send, "m_bUseClassAnimations", 1);
}
/**
* Gets the name of the class a client is playing.
*
* @param iClient The client.
* @param sBuffer The destination string buffer.
* @param iMaxLength The maximum length of the output string buffer.
* @noreturn
*/
stock void GetClientClassName(int iClient, char[] sBuffer, int iMaxLength) {
switch (TF2_GetPlayerClass(iClient)) {
case TFClass_Scout: {
strcopy(sBuffer, iMaxLength, "scout");
}
case TFClass_Sniper: {
strcopy(sBuffer, iMaxLength, "sniper");
}
case TFClass_Soldier: {
strcopy(sBuffer, iMaxLength, "soldier");
}
case TFClass_DemoMan: {
strcopy(sBuffer, iMaxLength, "demo");
}
case TFClass_Medic: {
strcopy(sBuffer, iMaxLength, "medic");
}
case TFClass_Heavy: {
strcopy(sBuffer, iMaxLength, "heavy");
}
case TFClass_Pyro: {
strcopy(sBuffer, iMaxLength, "pyro");
}
case TFClass_Spy: {
strcopy(sBuffer, iMaxLength, "spy");
}
case TFClass_Engineer: {
strcopy(sBuffer, iMaxLength, "engineer");
}
}
}
/**
* Check if a client is allowed to build an object.
*
* @param iClient The client.
* @param iType The building type.
* @return True if allowed, false otherwise.
*/
public bool CanClientBuild(int iClient, TDBuildingType iType) {
if (!IsValidClient(iClient)) {
return false;
}
if (TF2_IsPlayerInCondition(iClient, TFCond_Taunting)) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidNoBuildWhileTaunting");
return false;
}
if (g_bCarryingObject[iClient]) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidNoBuildWhileCarrying");
return false;
}
switch (iType) {
case TDBuilding_Sentry: {
if (GetClientMetal(iClient) < 130) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidInsufficientMetal", 130);
return false;
}
int iEntity = -1, iCount = 0, iOwner = -1;
while ((iEntity = FindEntityByClassname(iEntity, "obj_sentrygun")) != -1) {
iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hBuilder");
if (iOwner == iClient) {
iCount++;
}
if (iCount > g_iBuildingLimit[TDBuilding_Sentry]) {
AcceptEntityInput(iEntity, "Kill");
}
}
// Client can build Sentry
if (iCount < g_iBuildingLimit[TDBuilding_Sentry]) {
return true;
} else {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidSentryLimit", g_iBuildingLimit[TDBuilding_Sentry]);
return false;
}
}
case TDBuilding_Dispenser: {
if (GetClientMetal(iClient) < 100) {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidInsufficientMetal", 100);
return false;
}
int iEntity = -1, iCount = 0, iOwner = -1;
while ((iEntity = FindEntityByClassname(iEntity, "obj_dispenser")) != -1) {
iOwner = GetEntPropEnt(iEntity, Prop_Send, "m_hBuilder");
if (iOwner == iClient) {
iCount++;
}
if (iCount > g_iBuildingLimit[TDBuilding_Dispenser]) {
AcceptEntityInput(iEntity, "Kill");
}
}
// Client can build Dispenser
if (iCount < g_iBuildingLimit[TDBuilding_Dispenser]) {
Player_CAddValue(iClient, PLAYER_OBJECTS_BUILT, 1);
return true;
} else {
Forbid(iClient, true, "%s %t", PLUGIN_PREFIX, "forbidDispenserLimit", g_iBuildingLimit[TDBuilding_Dispenser]);
return false;
}
}
}
return false;
}
/**
* Prints a colored (default red) forbid message to a client and plays by default a sound.
*
* @param iClient The client.
* @param bPlaySound Play the sound or not.
* @param sMessage The message.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void Forbid(int iClient, bool bPlaySound = true, const char[] sMessage, any...) {
char sFormattedMessage[512];
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 4);
CPrintToChat(iClient, "%s", sFormattedMessage);
if (bPlaySound) {
PlaySound("Forbid", iClient);
}
}
/**
* Reloads the current map.
*
* @noreturn
*/
stock void ReloadMap() {
char sCurrentMap[PLATFORM_MAX_PATH];
GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
ServerCommand("changelevel %s", sCurrentMap);
}
/**
* Removes a flag from a cvar.
*
* @param sCvar The cvar which is affected.
* @param iFlag The flag to remove.
* @noreturn
*/
stock void StripConVarFlag(char[] sCvar, int iFlag) {
FindConVar(sCvar).Flags &= ~iFlag;
}
/**
* Adds a flag to a cvar.
*
* @param sCvar The cvar which is affected.
* @param iFlag The flag to add.
* @noreturn
*/
stock void AddConVarFlag(char[] sCvar, int iFlag) {
FindConVar(sCvar).Flags &= iFlag;
}
/**
* Checks if a client inside another client.
*
* @param iClient The client to check.
* @return True if inside, false otherwise.
*/
stock bool IsInsideClient(int iClient) {
float fMinBounds[3], fMaxBounds[3], fLocation[3];
GetClientMins(iClient, fMinBounds);
GetClientMaxs(iClient, fMaxBounds);
GetClientAbsOrigin(iClient, fLocation);
TR_TraceHullFilter(fLocation, fLocation, fMinBounds, fMaxBounds, MASK_SOLID, TraceRayPlayers, iClient);
return TR_DidHit();
}
/**
* Shows a custom HUD message to a client.
*
* @param iClient The client.
* @param sMessage The message to show.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void PrintToHud(int iClient, const char[] sMessage, any...) {
if (!IsValidClient(iClient) || !IsClientInGame(iClient)) {
return;
}
char sFormattedMessage[256];
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 3);
Handle hBuffer = StartMessageOne("KeyHintText", iClient);
BfWriteByte(hBuffer, 1);
BfWriteString(hBuffer, sFormattedMessage);
EndMessage();
}
/**
* Shows a custom HUD message to all clients.
*
* @param sMessage The message to show.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void PrintToHudAll(const char[] sMessage, any...) {
char sFormattedMessage[256];
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsClientInGame(iClient) && !IsFakeClient(iClient)) {
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 2);
PrintToHud(iClient, sFormattedMessage);
}
}
}
/**
* Gets a client by name.
*
* @param sName The clients name.
* @param iTeam The clients team, -1 for both teams.
* @return The client, or -1 on error.
*/
stock int GetClientByNameExact(char[] sName, int iTeam = -1) {
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsClientInGame(iClient)) {
if (iTeam == -1) {
if (StrEqual(sName, GetClientNameShort(iClient))) {
return iClient;
}
} else if (GetClientTeam(iClient) == iTeam) {
if (StrEqual(sName, GetClientNameShort(iClient))) {
return iClient;
}
}
}
}
return -1;
}
/**
* Attaches a annotation to an entity.
*
* @param iEntity The entity.
* @param fLifetime The lifetime of the annotation.
* @param sMessage The message to show.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void AttachAnnotation(int iEntity, float fLifetime, char[] sMessage, any...) {
Handle hEvent = CreateEvent("show_annotation");
if (hEvent == null) {
return;
}
SetEventInt(hEvent, "follow_entindex", iEntity);
SetEventInt(hEvent, "id", iEntity);
SetEventFloat(hEvent, "lifetime", fLifetime);
SetEventString(hEvent, "play_sound", "misc/null.wav");
char sFormattedMessage[256];
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 4);
SetEventString(hEvent, "text", sFormattedMessage);
FireEvent(hEvent);
}
/**
* Hides the annotation which is attached to an entity.
*
* @param iEntity The entity.
* @noreturn
*/
stock void HideAnnotation(int iEntity) {
Handle hEvent = CreateEvent("hide_annotation");
if (hEvent == null) {
return;
}
SetEventInt(hEvent, "id", iEntity);
FireEvent(hEvent);
}
/**
* Attaches a annotation to an entity.
*
* @param iClient The client.
* @param iEntity The entity.
* @param fLifetime The lifetime of the annotation.
* @param sMessage The message to show.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void AttachAdvancedAnnotation(int iClient, int iEntity, float fLifetime, char[] sMessage, any...) {
Handle hEvent = CreateEvent("show_annotation");
if (hEvent == null) {
return;
}
SetEventInt(hEvent, "follow_entindex", iEntity);
SetEventInt(hEvent, "id", iClient * iEntity);
SetEventFloat(hEvent, "lifetime", fLifetime);
SetEventString(hEvent, "play_sound", "misc/null.wav");
char sFormattedMessage[256];
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 5);
SetEventString(hEvent, "text", sFormattedMessage);
SetEventInt(hEvent, "visibilityBitfield", GetVisibilityBitfield(iClient));
FireEvent(hEvent);
}
/**
* Hides the annotation which is attached to an entity.
*
* @param iEntity The client.
* @param iEntity The entity.
* @noreturn
*/
stock void HideAdvancedAnnotation(int iClient, int iEntity) {
Handle hEvent = CreateEvent("hide_annotation");
if (hEvent == null) {
return;
}
SetEventInt(hEvent, "id", iClient * iEntity);
FireEvent(hEvent);
}
/**
* Shows an annotation at a given location.
*
* @param iId The id (use this to hide).
* @param fLocation The location vector.
* @param fLifetime The lifetime of the annotation.
* @param sMessage The message to show.
* @param ... Message formatting parameters.
* @noreturn
*/
stock void ShowAnnotation(int iId, float fLocation[3], float fOffsetZ, float fLifetime, char[] sMessage, any...) {
Handle hEvent = CreateEvent("show_annotation");
if (hEvent == null) {
return;
}
SetEventFloat(hEvent, "worldPosX", fLocation[0]);
SetEventFloat(hEvent, "worldPosY", fLocation[1]);
SetEventFloat(hEvent, "worldPosZ", fLocation[2] + fOffsetZ);
SetEventInt(hEvent, "id", iId);
SetEventFloat(hEvent, "lifetime", fLifetime);
SetEventString(hEvent, "play_sound", "misc/null.wav");
char sFormattedMessage[256];
VFormat(sFormattedMessage, sizeof(sFormattedMessage), sMessage, 4);
SetEventString(hEvent, "text", sFormattedMessage);
FireEvent(hEvent);
}
/**
* Converts an integer value to its absolute value.
*
* @param iValue The value.
* @return The absolute value.
*/
stock int Abs(int iValue) {
return (iValue < 0 ? -iValue : iValue);
}
/**
* Sets the servers password.
*
* @param sPassword The password to set.
* @param bDatabase Save the password in the database.
* @param bReloadMap Reload map afterwards.
* @noreturn
*/
stock void SetPassword(const char[] sPassword, bool bDatabase = true, bool bReloadMap = false) {
ServerCommand("sv_password \"%s\"", sPassword);
if (bDatabase) {
Database_SetServerPassword(sPassword, bReloadMap);
} else {
Log(TDLogLevel_Debug, "Set server password to \"%s\"", sPassword);
}
}
/**
* Initializes a map (trie) handle.
*
* @param hMapHandle The map handle to initialize.
* @noreturn
*/
stock void CreateDataMap(Handle &hMapHandle) {
if (hMapHandle != null) {
CloseHandle(hMapHandle);
hMapHandle = null;
}
hMapHandle = CreateTrie();
}
/**
* Gets the entity index of the health bar.
*
* @return The entity index of the health bar.
*/
stock int GetHealthBar() {
int iHealthBar = FindEntityByClassname(-1, "monster_resource");
if (!IsValidEntity(iHealthBar)) {
iHealthBar = CreateEntityByName("monster_resource");
if (IsValidEntity(iHealthBar)) {
DispatchSpawn(iHealthBar);
}
}
SetEntProp(iHealthBar, Prop_Send, "m_iBossHealthPercentageByte", 0);
return iHealthBar;
}
stock int GetClosestClient(int iClient) {
float fClientLocation[3];
GetClientAbsOrigin(iClient, fClientLocation);
float fEntityOrigin[3];
int iClosestEntity = -1;
float fClosestDistance = -1.0;
for (int i = 1; i < MaxClients; i++)
if (IsValidClient(i)) {
if (IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) != GetClientTeam(iClient) && i > 0) {
GetClientAbsOrigin(i, fEntityOrigin);
float fEntityDistance = GetVectorDistance(fClientLocation, fEntityOrigin);
if ((fEntityDistance < fClosestDistance) || fClosestDistance == -1.0) {
fClosestDistance = fEntityDistance;
iClosestEntity = i;
}
}
}
return iClosestEntity;
}
stock char[] GetClientNameShort(int iClient) {
// Steam username is limited to 32 chars, sourcemod says 128?
char sName[MAX_NAME_LENGTH];
GetClientName(iClient, sName, sizeof(sName));
return sName;
}