Files
2025-04-15 22:27:20 -04:00

782 lines
21 KiB
SourcePawn

#pragma semicolon 1
#include <sourcemod>
#if defined INFO_INCLUDES
#include "../info/constants.sp"
#include "../info/enums.sp"
#include "../info/variables.sp"
#endif
/**
* Called when the start button is being shot.
*
* @param iWave The incoming wave.
* @param iButton The button entity.
* @param iActivator The activator entity.
* @noreturn
*/
stock void Wave_OnButtonStart(int iWave, int iButton, int iActivator) {
if (!g_bEnabled) {
return;
}
if (!IsDefender(iActivator)) {
return;
}
char sName[64];
Format(sName, sizeof(sName), "wave_start_%d", iWave + 1);
DispatchKeyValue(iButton, "targetname", sName);
TeleportEntity(iButton, view_as<float>({ 0.0, 0.0, -9192.0 }), NULL_VECTOR, view_as<float>({ 0.0, 0.0, 0.0 }));
/*Translation Example
* Format: %s %t
* Values: PLUGIN_PREFIX "waveStart" GetClientNameShort(iActivator) (g_iCurrentWave + 1)
* Output: [TF2TD] [PrWh] Dragonisser started Wave 1
*/
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "waveStart", GetClientNameShort(iActivator), g_iCurrentWave + 1);
//Wave Health
int iWaveHealth;
int iPlayerCount = GetRealClientCount();
if (iPlayerCount > 1)
iWaveHealth = RoundToZero(float(Wave_GetHealth(g_iCurrentWave)) * (float(iPlayerCount) * 0.125 + 1.0));
else
iWaveHealth = Wave_GetHealth(g_iCurrentWave);
SetHudTextParams(-1.0, 0.6, 3.1, 255, 255, 255, 255, 1, 2.0);
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
if(Wave_GetType(g_iCurrentWave) == 0) {
ShowHudText(iClient, -1, "%t", "waveIncommingWithHealth", g_iCurrentWave + 1, iWaveHealth);
}
}
}
if (iWave == 0) {
Timer_NextWaveCountdown(null, 5);
} else {
g_bStartWaveEarly = true;
Wave_Spawn();
}
}
/**
* Called when an attacker spawned.
*
* @param iAttacker The attacker.
* @noreturn
*/
stock void Wave_OnSpawn(int iAttacker) {
if (!g_bEnabled) {
return;
}
g_bBoostWave = false;
SetRobotModel(iAttacker);
}
/**
* Called the frame after an attacker spawned.
*
* @param iAttacker The attacker.
* @noreturn
*/
public void Wave_OnSpawnPost(any iAttacker) {
if (!g_bEnabled) {
return;
}
int iMaxHealth = GetEntProp(iAttacker, Prop_Data, "m_iMaxHealth");
int iWaveHealth;
int iPlayerCount = GetRealClientCount();
if (iPlayerCount > 1)
iWaveHealth = RoundToZero(float(Wave_GetHealth(g_iCurrentWave)) * (float(iPlayerCount) * 0.125 + 1.0));
else
iWaveHealth = Wave_GetHealth(g_iCurrentWave);
TF2Attrib_SetByName(iAttacker, "max health additive bonus", float(iWaveHealth - iMaxHealth));
SetEntityHealth(iAttacker, iWaveHealth);
if (g_iNextWaveType & TDWaveType_Boss) {
// TODO(?)
}
if (g_iNextWaveType & TDWaveType_Rapid) {
g_bBoostWave = true;
}
if (g_iNextWaveType & TDWaveType_Regen) {
TF2Attrib_SetByName(iAttacker, "health regen", float(RoundFloat(iWaveHealth * 0.05)));
}
if (g_iNextWaveType & TDWaveType_KnockbackImmune) {
TF2Attrib_SetByName(iAttacker, "damage force reduction", 0.0);
}
if (g_iNextWaveType & TDWaveType_Air) {
TF2Attrib_SetByName(iAttacker, "damage force reduction", 0.0);
}
}
public void TF2_OnConditionAdded(int iClient, TFCond Condition) {
if (Condition == TFCond_Jarated && g_iNextWaveType & TDWaveType_JarateImmune)
TF2_RemoveCondition(iClient, TFCond_Jarated);
}
/**
* Called the frame after an attacker gets damaged.
*
* @param iVictim The victim.
* @param iAttacker The attacker.
* @param iInflictor The inflictor.
* @param fDamage The damage.
* @param iDamageType The damage type.
* @noreturn
*/
stock void Wave_OnTakeDamagePost(int iVictim, int iAttacker, int iInflictor, float fDamage, int iDamageType) {
if (!g_bEnabled) {
return;
}
if (IsValidEntity(g_iHealthBar)) {
int iWaveHealth;
int iPlayerCount = GetRealClientCount();
if (iPlayerCount > 1)
iWaveHealth = RoundToZero(float(Wave_GetHealth(g_iCurrentWave)) * (float(iPlayerCount) * 0.125 + 1.0));
else
iWaveHealth = Wave_GetHealth(g_iCurrentWave);
int iTotalHealth = iWaveHealth * g_iBotsToSpawn;
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsAttacker(iClient)) {
iTotalHealth += GetClientHealth(iClient);
}
}
int iTotalHealthMax = iWaveHealth * Wave_GetQuantity(g_iCurrentWave);
float fPercentage = float(iTotalHealth) / float(iTotalHealthMax);
SetEntProp(g_iHealthBar, Prop_Send, "m_iBossHealthPercentageByte", RoundToFloor(fPercentage * 255));
}
}
/**
* Called when an attacker dies.
*
* @param iAttacker The attacker.
* @noreturn
*/
stock void Wave_OnDeath(int iAttacker, float fPosition[3]) {
if (!g_bEnabled) {
return;
}
// TODO(hurp): Find a better way to make the ammo from air waves spawn on the ground,
// This method probably wont work for all maps
if (Wave_GetType(g_iCurrentWave) == TDWaveType_Air) {
fPosition[2] = fPosition[2] - 10.0;
}
// TODO(hurp): Customize metal ammount based off the wave in config files
fPosition[2] = fPosition[2] - GetDistanceToGround(fPosition) + 10.0;
SpawnRewardPack(TDMetalPack_Small, fPosition, 100);
CreateTimer(1.0, Delay_KickAttacker, iAttacker, TIMER_FLAG_NO_MAPCHANGE);
if (g_iBotsToSpawn >= 1) {
Wave_SpawnBots();
} else if (GetAliveAttackerCount() <= 1 && g_iBotsToSpawn <= 0) {
Wave_OnDeathAll();
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
Player_CAddValue(iClient, PLAYER_WAVES_PLAYED, g_iCurrentWave);
Player_CSetValue(iClient, PLAYER_WAVE_REACHED, g_iCurrentWave);
}
}
}
}
/**
* Called when all attackers died.
*
* @noreturn
*/
stock void Wave_OnDeathAll() {
if (!g_bEnabled) {
return;
}
if (g_iNextWaveType & TDWaveType_Boss) {
SpawnMetalPacks(TDMetalPack_Boss);
}
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient))
Player_CAddValue(iClient, PLAYER_WAVES_PLAYED, 1);
}
if (g_iCurrentWave + 1 >= iMaxWaves) {
PrintToServer("[TF2TD] Round won (Wave: %d)", g_iCurrentWave + 1);
Server_UAddValue(g_iServerId, SERVER_ROUNDS_WON, 1);
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
Player_CSetValue(iClient, PLAYER_ROUNDS_WON, 1);
}
}
PlaySound("Win", 0);
Wave_Win(TEAM_DEFENDER);
return;
} else {
PlaySound("WaveComplete", 0);
}
g_iTotalBotsLeft = 0;
g_bStartWaveEarly = false;
g_iCurrentWave++;
g_iNextWaveType = Wave_GetType(g_iCurrentWave);
TeleportEntity(g_iWaveStartButton, g_fWaveStartButtonLocation, NULL_VECTOR, view_as<float>({ 0.0, 0.0, 0.0 }));
Timer_NextWaveCountdown(null, g_iRespawnWaveTime);
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "wavePassed", g_iCurrentWave);
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "wavePrepareTime", g_iRespawnWaveTime);
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "waveTowersUnlocked");
g_bTowersLocked = false;
Log(TDLogLevel_Info, "Wave %d passed", g_iCurrentWave);
if (Panel_Remove(g_iCurrentWave)) {
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "waveBonusAvailable", g_iCurrentWave);
Log(TDLogLevel_Debug, "New bonus available (Wave: %d)", g_iCurrentWave);
}
}
/**
* Called when an attacker touches a corner.
*
* @param iCorner The corner trigger entity.
* @param iAttacker The attacker.
* @noreturn
*/
public void Wave_OnTouchCorner(int iCorner, int iAttacker) {
if (!g_bEnabled) {
return;
}
if (IsAttacker(iAttacker)) {
int iNextCorner = Corner_GetNext(iCorner);
if (iNextCorner != -1) {
float fAngles[3];
Corner_GetAngles(iCorner, iNextCorner, fAngles);
TeleportEntity(iAttacker, NULL_VECTOR, fAngles, NULL_VECTOR);
} else {
g_bBoostWave = false;
}
}
}
/**
* Spawns a wave.
*
* @noreturn
*/
stock void Wave_Spawn() {
// Delete ammo packs loot that have not been picked up
char buffer[64];
int entity = -1;
while ((entity = FindEntityByClassname(entity, "prop_dynamic")) != INVALID_ENT_REFERENCE) {
GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
if (StrEqual(buffer, "models/items/ammopack_small.mdl")) {
AcceptEntityInput(entity, "Kill");
g_iMetalPackCount--;
}
}
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "waveIncomming", g_iCurrentWave + 1);
CPrintToChatAll("%s %t", PLUGIN_PREFIX, "waveTowersLocked");
g_bTowersLocked = true;
g_iBotsToSpawn = Wave_GetQuantity(g_iCurrentWave);
SetEntProp(g_iHealthBar, Prop_Send, "m_iBossHealthPercentageByte", 255);
g_iTotalBotsLeft = Wave_GetQuantity(g_iCurrentWave);
Wave_SpawnBots();
}
stock void Wave_SpawnBots() {
if (g_iBotsToSpawn <= 0) {
return;
}
char sName[MAX_NAME_LENGTH];
if (!Wave_GetName(g_iCurrentWave, sName, sizeof(sName))) {
LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to spawn wave %d, could not read name!", g_iCurrentWave);
return;
}
char sClass[32];
if (!Wave_GetClassString(g_iCurrentWave, sClass, sizeof(sClass))) {
LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to spawn wave %d, could not read class!", g_iCurrentWave);
return;
}
int iTotalBots = Wave_GetQuantity(g_iCurrentWave);
int iAliveBots = GetAliveAttackerCount();
// If only less than g_iMaxBotsOnField bots in total
if (iTotalBots <= g_hMaxBotsOnField.IntValue) {
if (iTotalBots > 1) {
for (int i = 1; i <= iTotalBots; i++) {
ServerCommand("bot -team red -class %s -name %s%d", sClass, sName, i);
g_iBotsToSpawn--;
}
// If only 1 bot
} else {
ServerCommand("bot -team red -class %s -name %s", sClass, sName);
g_iBotsToSpawn = 0;
}
CreateTimer(1.0, TeleportWaveDelay, iTotalBots, TIMER_FLAG_NO_MAPCHANGE);
// Else more than g_iMaxBotsOnField bots
} else {
// If no bot alive
if (iAliveBots <= 0) {
for (int i = 1; i <= g_hMaxBotsOnField.IntValue; i++) {
g_iBotsToSpawn--;
ServerCommand("bot -team red -class %s -name %s%d", sClass, sName, -(g_iBotsToSpawn - iTotalBots));
}
CreateTimer(1.0, TeleportWaveDelay, g_hMaxBotsOnField.IntValue, TIMER_FLAG_NO_MAPCHANGE);
// If bots alive
} else {
int iBotsToSpawn = g_hMaxBotsOnField.IntValue - iAliveBots;
for (int i = 1; i <= iBotsToSpawn; i++) {
g_iBotsToSpawn--;
ServerCommand("bot -team red -class %s -name %s%d", sClass, sName, -(g_iBotsToSpawn - iTotalBots));
}
CreateTimer(1.0, TeleportWaveDelay, iBotsToSpawn, TIMER_FLAG_NO_MAPCHANGE);
}
}
}
public Action TeleportWaveDelay(Handle hTimer, any iNumber) {
if (iNumber <= 0) {
return Plugin_Stop;
}
int iTotalBots = Wave_GetQuantity(g_iCurrentWave);
char sName[MAX_NAME_LENGTH];
if (!Wave_GetName(g_iCurrentWave, sName, sizeof(sName))) {
LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to teleport wave %d, could not read name!", g_iCurrentWave);
return Plugin_Stop;
}
if (iTotalBots <= 1) {
Format(sName, sizeof(sName), "%s", sName);
} else if (iTotalBots > g_hMaxBotsOnField.IntValue) {
g_iTotalBotsLeft--;
Format(sName, sizeof(sName), "%s%d", sName, -(g_iTotalBotsLeft - iTotalBots));
} else if (iNumber > 0) {
Format(sName, sizeof(sName), "%s%d", sName, iNumber);
}
int iAttacker = GetClientByNameExact(sName, TEAM_ATTACKER);
Log(TDLogLevel_Trace, "Should teleport attacker %d (%d, %s) of wave %d (%d attackers)", iNumber, iAttacker, sName, g_iCurrentWave + 1, Wave_GetQuantity(g_iCurrentWave));
if (IsAttacker(iAttacker)) {
float fLocation[3];
if (!Wave_GetLocation(g_iCurrentWave, fLocation)) {
LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to teleport wave %d, could not read location!", g_iCurrentWave);
return Plugin_Stop;
}
float fAngles[3];
if (!Wave_GetAngles(g_iCurrentWave, fAngles)) {
LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to teleport wave %d, could not read angles!", g_iCurrentWave);
return Plugin_Stop;
}
TeleportEntity(iAttacker, fLocation, fAngles, view_as<float>({ 0.0, 0.0, 0.0 }));
Log(TDLogLevel_Trace, " -> Teleported attacker");
CreateTimer(1.0, TeleportWaveDelay, iNumber - 1, TIMER_FLAG_NO_MAPCHANGE);
}
return Plugin_Stop;
}
public void Wave_Win(int iTeam) {
int iEntity = -1;
iEntity = FindEntityByClassname2(iEntity, "team_control_point_master");
if (iEntity == -1 || !IsValidEntity(iEntity)) {
// No team_control_point_master either... lets create one.
iEntity = CreateEntityByName("team_control_point_master");
DispatchKeyValue(iEntity, "targetname", "master_control_point");
DispatchKeyValue(iEntity, "StartDisabled", "0");
DispatchSpawn(iEntity);
}
SetVariantInt(iTeam);
AcceptEntityInput(iEntity, "SetWinner");
}
/*=========================================
= Utility Functions =
=========================================*/
public Action Delay_KickAttacker(Handle hTimer, any iAttacker) {
if (IsAttacker(iAttacker)) {
KickClient(iAttacker);
}
return Plugin_Stop;
}
public Action Timer_NextWaveCountdown(Handle hTimer, any iTime) {
if (g_bStartWaveEarly) {
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
CPrintToChat(iClient, "%s %t", PLUGIN_PREFIX, "waveStartedEarly", (iTime + 1) * 10, iTime + 1);
AddClientMetal(iClient, (iTime + 1) * 10);
}
}
return Plugin_Stop;
}
switch (iTime) {
case 5: {
SetHudTextParams(-1.0, 0.6, 5.1, 255, 255, 255, 255, 2, 2.0);
// Wave Health
int iWaveHealth;
int iPlayerCount = GetRealClientCount();
if (iPlayerCount > 1)
iWaveHealth = RoundToZero(float(Wave_GetHealth(g_iCurrentWave)) * (float(iPlayerCount) * 0.125 + 1.0));
else
iWaveHealth = Wave_GetHealth(g_iCurrentWave);
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
char sType[256];
strcopy(sType, sizeof(sType), "");
if (g_iNextWaveType & TDWaveType_Rapid) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeRapid", iClient);
} else {
Format(sType, sizeof(sType), "%s + %T", sType, "waveTypeRapid", iClient);
}
}
if (g_iNextWaveType & TDWaveType_Regen) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeRegen", iClient);
} else {
Format(sType, sizeof(sType), "%s + %T", sType, "waveTypeRegen", iClient);
}
}
if (g_iNextWaveType & TDWaveType_KnockbackImmune) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeKnockbackImmune", iClient);
} else {
Format(sType, sizeof(sType), "%s + %T", sType, "waveTypeKnockbackImmune", iClient);
}
}
if (g_iNextWaveType & TDWaveType_Air) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeAir", iClient);
} else {
Format(sType, sizeof(sType), "%s + %T", sType, "waveTypeAir", iClient);
}
}
if (g_iNextWaveType & TDWaveType_JarateImmune) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeJarateImmune", iClient);
} else {
Format(sType, sizeof(sType), "%s + %T", sType, "waveTypeJarateImmune", iClient);
}
}
if (g_iNextWaveType & TDWaveType_Boss) {
if (StrEqual(sType, "")) {
Format(sType, sizeof(sType), "%T", "waveTypeBoss", iClient);
} else {
Format(sType, sizeof(sType), "%s %T", sType, "waveTypeBoss", iClient);
}
}
if (StrEqual(sType, "")) {
ShowHudText(iClient, -1, "%t", "waveIncommingWithHealth", g_iCurrentWave + 1, iWaveHealth);
} else {
ShowHudText(iClient, -1, "%t", "waveIncommingWithHealthAndType", sType, g_iCurrentWave + 1, iWaveHealth);
}
}
}
EmitSoundToAll("vo/announcer_begins_5sec.mp3");
}
case 4: {
EmitSoundToAll("vo/announcer_begins_4sec.mp3");
}
case 3: {
EmitSoundToAll("vo/announcer_begins_3sec.mp3");
}
case 2: {
EmitSoundToAll("vo/announcer_begins_2sec.mp3");
}
case 1: {
EmitSoundToAll("vo/announcer_begins_1sec.mp3");
TeleportEntity(g_iWaveStartButton, view_as<float>({ 0.0, 0.0, -9192.0 }), NULL_VECTOR, view_as<float>({ 0.0, 0.0, 0.0 }));
}
case 0: {
PlaySound("Music", 0);
Wave_Spawn();
return Plugin_Stop;
}
}
SetHudTextParams(-1.0, 0.85, 1.1, 255, 255, 255, 255);
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsDefender(iClient)) {
ShowHudText(iClient, -1, "%t", "waveArrivingIn", iTime);
}
}
CreateTimer(1.0, Timer_NextWaveCountdown, iTime - 1, TIMER_FLAG_NO_MAPCHANGE);
return Plugin_Stop;
}
/**
* Gets the count of alive attackers.
*
* @return Count of alive attackers.
*/
stock int GetAliveAttackerCount() {
int iAttackers = 0;
for (int iClient = 1; iClient <= MaxClients; iClient++) {
if (IsAttacker(iClient) && IsPlayerAlive(iClient)) {
iAttackers++;
}
}
return iAttackers;
}
/*======================================
= Data Functions =
======================================*/
/**
* Gets the name of a wave.
*
* @param iWave The wave.
* @param sBuffer The destination string buffer.
* @param iMaxLength The maximum length of the output string buffer.
* @return True on success, false if wave was not found.
*/
stock bool Wave_GetName(int iWave, char[] sBuffer, int iMaxLength) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_name", iWave);
return GetTrieString(g_hMapWaves, sKey, sBuffer, iMaxLength);
}
/**
* Gets the type of a wave.
*
* @param iWave The wave.
* @return The waves type bit field.
*/
stock int Wave_GetType(int iWave) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_type", iWave);
int iType = 0;
if (!GetTrieValue(g_hMapWaves, sKey, iType)) {
return -1;
}
return iType;
}
/**
* Gets the class of a wave.
*
* @param iWave The wave.
* @param sBuffer The destination string buffer.
* @param iMaxLength The maximum length of the output string buffer.
* @return True on success, false if wave was not found.
*/
stock bool Wave_GetClassString(int iWave, char[] sBuffer, int iMaxLength) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_class", iWave);
return GetTrieString(g_hMapWaves, sKey, sBuffer, iMaxLength);
}
/**
* Gets the class of a wave.
*
* @param iWave The wave.
* @return The waves class type, or TFClass_Unknown on error.
*/
stock TFClassType Wave_GetClass(int iWave) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_class", iWave);
char sClass[32];
GetTrieString(g_hMapWaves, sKey, sClass, sizeof(sClass));
if (StrEqual(sClass, "Scout")) {
return TFClass_Scout;
} else if (StrEqual(sClass, "Sniper")) {
return TFClass_Sniper;
} else if (StrEqual(sClass, "Soldier")) {
return TFClass_Soldier;
} else if (StrEqual(sClass, "Demoman")) {
return TFClass_DemoMan;
} else if (StrEqual(sClass, "Medic")) {
return TFClass_Medic;
} else if (StrEqual(sClass, "Heavy")) {
return TFClass_Heavy;
} else if (StrEqual(sClass, "Pyro")) {
return TFClass_Pyro;
} else if (StrEqual(sClass, "Spy")) {
return TFClass_Spy;
} else if (StrEqual(sClass, "Spy")) {
return TFClass_Engineer;
}
return TFClass_Unknown;
}
/**
* Gets the quantity of a wave.
*
* @param iWave The wave.
* @return The waves quantity, or -1 on failure.
*/
stock int Wave_GetQuantity(int iWave) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_quantity", iWave);
int iQuantity = 0;
if (!GetTrieValue(g_hMapWaves, sKey, iQuantity)) {
return -1;
}
return iQuantity;
}
/**
* Gets the health of a wave.
*
* @param iWave The wave.
* @return The waves health, or -1 on failure.
*/
stock int Wave_GetHealth(int iWave) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_health", iWave);
int iHealth = 0;
if (!GetTrieValue(g_hMapWaves, sKey, iHealth)) {
return -1;
}
return iHealth;
}
/**
* Gets the location of a wave.
*
* @param iWave The wave.
* @param fLocation The location vector.
* @return True on success, false if wave was not found.
*/
stock bool Wave_GetLocation(int iWave, float fLocation[3]) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_location", iWave);
char sLocation[64];
if (GetTrieString(g_hMapWaves, sKey, sLocation, sizeof(sLocation))) {
char sLocationParts[6][16];
ExplodeString(sLocation, " ", sLocationParts, sizeof(sLocationParts), sizeof(sLocationParts[]));
fLocation[0] = StringToFloat(sLocationParts[0]);
fLocation[1] = StringToFloat(sLocationParts[1]);
fLocation[2] = StringToFloat(sLocationParts[2]);
return true;
}
return false;
}
/**
* Gets the angles of a wave.
*
* @param iWave The wave.
* @param fAngles The angles vector.
* @return True on success, false if wave was not found.
*/
stock bool Wave_GetAngles(int iWave, float fAngles[3]) {
char sKey[32];
Format(sKey, sizeof(sKey), "%d_location", iWave);
char sAngles[64];
if (GetTrieString(g_hMapWaves, sKey, sAngles, sizeof(sAngles))) {
char sAnglesParts[6][16];
ExplodeString(sAngles, " ", sAnglesParts, sizeof(sAnglesParts), sizeof(sAnglesParts[]));
fAngles[0] = StringToFloat(sAnglesParts[3]);
fAngles[1] = StringToFloat(sAnglesParts[4]);
fAngles[2] = StringToFloat(sAnglesParts[5]);
return true;
}
return false;
}
/**
* Finds an entity by using its classname.
*
* @param iStartEnt The start entity entity index.
* @param sClassname The entity classname to look for.
* @return The entitys entity index, or -1 on failure.
*/
public int FindEntityByClassname2(int iStartEnt, const char[] sClassname) {
/* If iStartEnt isn't valid shifting it back to the nearest valid one */
while (iStartEnt > -1 && !IsValidEntity(iStartEnt))
iStartEnt--;
return FindEntityByClassname(iStartEnt, sClassname);
}