1554 lines
40 KiB
SourcePawn
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;
|
|
} |