/**
 * Checks for clients near AoE Towers
 *
 * @param hTimer 		Timer Handle
 * @param iTower 		The tower
 * @noreturn
 */
public Action Timer_ClientNearAoETower(Handle hTimer) {
	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsClientInGame(iClient) && IsPlayerAlive(iClient)) {
			if (IsTower(iClient)) {
				int		  iTower   = iClient;
				TDTowerId iTowerId = GetTowerId(iTower);

				if (iTowerId == TDTower_AoE_Engineer) {
					float fAreaScale = 80.0 * Tower_GetAreaScale(GetTowerId(iTower));
					CreateBeamBoxAroundClient(iTower, fAreaScale, true, 0.2, { 231, 76, 60, 25 });
					Tower_ObjectNearAoEEngineer(iTower, iClient);
				}
				if (iTowerId == TDTower_Medic) {
					float fAreaScale = 160.0 * Tower_GetAreaScale(GetTowerId(iTower));
					CreateBeamBoxAroundClient(iTower, fAreaScale, true, 0.2, { 46, 204, 113, 25 });
					Tower_ObjectNearAoEMedic(iTower);
				}
				if (iTowerId == TDTower_Kritz_Medic) {
					float fAreaScale = 60.0 * Tower_GetAreaScale(GetTowerId(iTower));
					CreateBeamBoxAroundClient(iTower, fAreaScale, true, 0.2, { 41, 128, 185, 25 });
					Tower_ObjectNearKritzMedic(iTower);
				}
				if (iTowerId == TDTower_Slow_Spy) {
					float fAreaScale = 100.0 * Tower_GetAreaScale(GetTowerId(iTower));
					CreateBeamBoxAroundClient(iTower, fAreaScale, false, 0.2, { 44, 62, 80, 25 });
					Tower_ObjectNearAoESpy(iTower);
				}
			}
		}
	}
	return Plugin_Continue;
}

/**
 * Player is close to AoEMedic
 *
 * @param iTower 		The tower
 * @param iClient 		The player
 * @noreturn
 */
public void Tower_ObjectNearAoEMedic(int iTower) {
	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsDefender(iClient) && IsClientInZone(iClient, g_fBeamPoints[iTower])) {
			Player_AddHealth(iClient, 5);

			// Check if there is no Beam
			if (g_iHealBeamIndex[iClient][0] == 0 && g_iHealBeamIndex[iClient][1] == 0) {
				AttachHealBeam(iTower, iClient);
			}

		} else {
			Tower_ObjectNotNearAoEMedic(iClient);
		}
	}
}

/**
 * Player isn't close to AoEMedic
 *
 * @param iTower 		The tower
 * @param iClient 		The player
 * @noreturn
 */
public void Tower_ObjectNotNearAoEMedic(int iClient) {
	// Remove Beam if there is one
	if (g_iHealBeamIndex[iClient][0] != 0) {
		if (IsValidEdict(g_iHealBeamIndex[iClient][0])) {
			RemoveEdict(g_iHealBeamIndex[iClient][0]);
			g_iHealBeamIndex[iClient][0] = 0;
		}
	}

	if (g_iHealBeamIndex[iClient][1] != 0) {
		if (IsValidEdict(g_iHealBeamIndex[iClient][1])) {
			RemoveEdict(g_iHealBeamIndex[iClient][1]);
			g_iHealBeamIndex[iClient][1] = 0;
		}
	}
}

/**
 * Player is close to AoEEngineer
 *
 * @param iTower 		The tower
 * @param iClient 		The player
 * @noreturn
 */
public void Tower_ObjectNearAoEEngineer(int iTower, int iClient) {
	if (iAoEEngineerTimer < 3 && !IsTowerAttached(iTower)) {
		iAoEEngineerTimer++;
		return;
	} else if (!IsTowerAttached(iTower)) {
		iAoEEngineerTimer = 0;
	}
	int	  iSentry = -1, iDispenser = -1;
	float fLocation[3];

	// Sentries
	while ((iSentry = FindEntityByClassname(iSentry, "obj_sentrygun")) != -1) {
		if (!IsValidEntity(iSentry)) {
			continue;
		}

		GetEntPropVector(iSentry, Prop_Send, "m_vecOrigin", fLocation);
		fLocation[2] += 20.0;

		if (IsPointInZone(fLocation, g_fBeamPoints[iTower])) {
			int iShellsMax, iHealthMax, iRocketsMax, iMetalMax;

			if (IsMiniSentry(iSentry)) {
				iShellsMax	= 150;
				iHealthMax	= 150;
				iRocketsMax = 0;
				iMetalMax	= 0;
			} else {
				switch (GetBuildingLevel(iSentry)) {
					case 1: {
						iShellsMax	= 150;
						iHealthMax	= 150;
						iRocketsMax = 0;
						iMetalMax	= 1000;
					}

					case 2: {
						iShellsMax	= 200;
						iHealthMax	= 180;
						iRocketsMax = 0;
						iMetalMax	= 1000;
					}

					case 3: {
						iShellsMax	= 200;
						iHealthMax	= 216;
						iRocketsMax = 20;
						iMetalMax	= 0;
					}
				}
			}

			int iState = GetEntProp(iSentry, Prop_Send, "m_iState");

			// If is not building up
			if (iState != 0) {
				int iShells	 = GetEntProp(iSentry, Prop_Send, "m_iAmmoShells");
				int iHealth	 = GetEntProp(iSentry, Prop_Send, "m_iHealth");
				int iRockets = GetEntProp(iSentry, Prop_Send, "m_iAmmoRockets");
				int iMetal	 = GetEntProp(iSentry, Prop_Send, "m_iUpgradeMetal");

				if (IsMiniSentry(iSentry)) {
					int iShellsPerHit = 20;	   // Default: 40

					if (iShells + iShellsPerHit < iShellsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoShells", iShells + iShellsPerHit);
					} else if (iShells < iShellsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoShells", iShellsMax);
					}
				} else {
					int iMetalPerHit   = 10;	// Default: 25
					int iHealthPerHit  = 50;	// Default: 105
					int iShellsPerHit  = 20;	// Default: 40
					int iRocketsPerHit = 4;		// Default: 8

					if (iMetal < iMetalMax) {
						SetEntProp(iSentry, Prop_Send, "m_iUpgradeMetal", iMetal + iMetalPerHit);

						// Upgrade to the next level
					} else if (iMetalMax != 0 && !g_bAoEEngineerAttack) {
						float fLocationTower[3];
						GetClientAbsOrigin(iTower, fLocationTower);

						TeleportEntity(iTower, fLocation, NULL_VECTOR, NULL_VECTOR);

						g_bAoEEngineerAttack = true;

						DataPack hPack		 = new DataPack();
						hPack.WriteFloat(float(iTower));
						hPack.WriteFloat(fLocationTower[0]);
						hPack.WriteFloat(fLocationTower[1]);
						hPack.WriteFloat(fLocationTower[2]);

						CreateTimer(0.2, Timer_TeleportAoEEngineerBack, hPack, _);
					}

					if (iHealth + iHealthPerHit < iHealthMax) {
						SetEntProp(iSentry, Prop_Send, "m_iHealth", iHealth + iHealthPerHit);
					} else if (iHealth < iHealthMax) {
						SetEntProp(iSentry, Prop_Send, "m_iHealth", iHealthMax);
					}

					if (iShells + iShellsPerHit < iShellsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoShells", iShells + iShellsPerHit);
					} else if (iShells < iShellsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoShells", iShellsMax);
					}
					if (iRockets + iRocketsPerHit < iRocketsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoRockets", iRockets + iRocketsPerHit);
					} else if (iRockets < iRocketsMax) {
						SetEntProp(iSentry, Prop_Send, "m_iAmmoRockets", iRocketsMax);
					}
				}
			}
		}
	}

	// Dispensers
	while ((iDispenser = FindEntityByClassname(iDispenser, "obj_dispenser")) != -1) {
		if (!IsValidEntity(iDispenser)) {
			continue;
		}

		GetEntPropVector(iDispenser, Prop_Send, "m_vecOrigin", fLocation);

		fLocation[2] += 20.0;

		if (IsPointInZone(fLocation, g_fBeamPoints[iTower])) {
			int iHealthMax, iMetalMax;

			switch (GetBuildingLevel(iDispenser)) {
				case 1: {
					iHealthMax = 150;
					iMetalMax  = 500;
				}

				case 2: {
					iHealthMax = 180;
					iMetalMax  = 500;
				}

				case 3: {
					iHealthMax = 216;
					iMetalMax  = 0;
				}
			}

			int iBuildingUp = GetEntProp(iDispenser, Prop_Send, "m_bBuilding");
			if (iBuildingUp != 1) {	   // Is not building up
				int iHealth		  = GetEntProp(iDispenser, Prop_Send, "m_iHealth");
				int iMetal		  = GetEntProp(iDispenser, Prop_Send, "m_iUpgradeMetal");
				int iMetalPerHit  = 10;	   // Default: 25
				int iHealthPerHit = 50;	   // Default: 105

				if (iMetal < iMetalMax) {
					SetEntProp(iDispenser, Prop_Send, "m_iUpgradeMetal", iMetal + iMetalPerHit);
				} else if (iMetalMax != 0 && !g_bAoEEngineerAttack) {	 // Upgrade to the next level
					float fLocationTower[3];
					GetClientAbsOrigin(iTower, fLocationTower);

					TeleportEntity(iTower, fLocation, NULL_VECTOR, NULL_VECTOR);

					g_bAoEEngineerAttack = true;

					DataPack hPack		 = new DataPack();
					hPack.WriteFloat(float(iTower));
					hPack.WriteFloat(fLocationTower[0]);
					hPack.WriteFloat(fLocationTower[1]);
					hPack.WriteFloat(fLocationTower[2]);

					CreateTimer(0.5, Timer_TeleportAoEEngineerBack, hPack, _);
				}

				if (iHealth + iHealthPerHit < iHealthMax) {
					SetEntProp(iDispenser, Prop_Send, "m_iHealth", iHealth + iHealthPerHit);
				} else if (iHealth < iHealthMax) {
					SetEntProp(iDispenser, Prop_Send, "m_iHealth", iHealthMax);
				}
			}
		}
	}
}

public Action Timer_TeleportAoEEngineerBack(Handle hTimer, DataPack hPack) {
	hPack.Reset();

	g_bAoEEngineerAttack = false;
	int	  iTower		 = RoundToZero(ReadPackFloat(hPack));
	float fLocation[3];
	fLocation[0] = hPack.ReadFloat();
	fLocation[1] = hPack.ReadFloat();
	fLocation[2] = hPack.ReadFloat();
	TeleportEntity(iTower, fLocation, NULL_VECTOR, NULL_VECTOR);
	return Plugin_Continue;
}

/**
 * Player is close to KritzMedic
 *
 * @param iTower 		The tower
 * @noreturn
 */
public void Tower_ObjectNearKritzMedic(int iTower) {
	// If Kritz Time is up
	if (TF2_GetUberLevel(iTower) <= 0.0)
		g_bKritzMedicCharged = false;

	// If Bot is fully charged, charge player
	if (g_bKritzMedicCharged && TF2_GetUberLevel(iTower) > 0.0) {
		TF2_SetUberLevel(iTower, TF2_GetUberLevel(iTower) - 0.02);
		for (int iClient = 1; iClient <= MaxClients; iClient++) {
			if (IsValidClient(iClient))
				TF2_AddCondition(iClient, TFCond_Kritzkrieged, 0.3);
		}
		// If Bot is fully charged
	} else if (TF2_GetUberLevel(iTower) >= 1.0) {
		g_bKritzMedicCharged = true;
	} else if (iAoEKritzMedicTimer < 3 && !IsTowerAttached(iTower)) {
		iAoEKritzMedicTimer++;
		return;
	} else if (!IsTowerAttached(iTower)) {
		iAoEKritzMedicTimer = 0;
	}
	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsDefender(iClient) && IsClientInZone(iClient, g_fBeamPoints[iTower])) {
			float fUberLevel = TF2_GetUberLevel(iTower);
			TF2_SetUberLevel(iTower, fUberLevel + 0.01);
		}
	}
}

/**
 * Player is close to AoESpy
 *
 * @param iTower 		The tower
 * @param iClient 		The player
 * @noreturn
 */
public void Tower_ObjectNearAoESpy(int iTower) {
	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		if (IsAttacker(iClient) && IsClientInZone(iClient, g_fBeamPoints[iTower]) && !g_iSlowAttacker[iClient])
			g_iSlowAttacker[iClient] = true;
		else if (g_iSlowAttacker[iClient] && IsClientInZone(iClient, g_fBeamPoints[iTower]))
			TF2_AddCondition(iClient, TFCond_TeleportedGlow, 1.0);
		else if (g_iSlowAttacker[iClient])
			g_iSlowAttacker[iClient] = false;
	}
}

/**
 * Gets the level of an building.
 *
 * @param iEntity		The buildings entity index.
 * @return				The buildings level.
 */
public int GetBuildingLevel(int iEntity) {
	return GetEntProp(iEntity, Prop_Send, "m_iUpgradeLevel");
}

/**
 * Checks wheter a sentry is a Mini-Sentry or not.
 *
 * @param iSentry		The sentries entity index.
 * @return				True if mini, false otherwise.
 */
public bool IsMiniSentry(int iSentry) {
	return ((GetEntProp(iSentry, Prop_Send, "m_bMiniBuilding") == 0) ? false : true);
}

/**
 * Attach a beam between two entities
 *
 * @param iEntity 		The start point
 * @param iTarget		The target
 * @noreturn
 */
stock void AttachHealBeam(int iEntity, int iTarget) {
	int iParticleIndex1 = CreateEntityByName("info_particle_system");
	int iParticleIndex2 = CreateEntityByName("info_particle_system");

	if (IsValidEdict(iParticleIndex1)) {
		char sEntityName[128];
		Format(sEntityName, sizeof(sEntityName), "target%i", iEntity);
		DispatchKeyValue(iEntity, "targetname", sEntityName);

		char sTargetName1[128];
		Format(sTargetName1, sizeof(sTargetName1), "target%i", iTarget);
		DispatchKeyValue(iTarget, "targetname", sTargetName1);

		char sTargetName2[128];
		Format(sTargetName2, sizeof(sTargetName2), "tf2particle%i", iTarget);

		DispatchKeyValue(iParticleIndex2, "targetname", sTargetName2);
		DispatchKeyValue(iParticleIndex2, "parentname", sTargetName1);

		SetVariantString(sTargetName1);
		AcceptEntityInput(iParticleIndex2, "SetParent");

		SetVariantString("flag");
		AcceptEntityInput(iParticleIndex2, "SetParentAttachment");

		DispatchKeyValue(iParticleIndex1, "targetname", "tf2particle");
		DispatchKeyValue(iParticleIndex1, "parentname", sEntityName);
		DispatchKeyValue(iParticleIndex1, "effect_name", "dispenser_heal_blue");
		DispatchKeyValue(iParticleIndex1, "cpoint1", sTargetName2);

		DispatchSpawn(iParticleIndex1);

		SetVariantString(sEntityName);
		AcceptEntityInput(iParticleIndex1, "SetParent");

		SetVariantString("flag");
		AcceptEntityInput(iParticleIndex1, "SetParentAttachment");

		ActivateEntity(iParticleIndex1);
		AcceptEntityInput(iParticleIndex1, "start");

		g_iHealBeamIndex[iTarget][0] = iParticleIndex1;
		g_iHealBeamIndex[iTarget][1] = iParticleIndex2;
	}
}

stock float TF2_GetUberLevel(int iClient) {
	int iIndex = GetPlayerWeaponSlot(iClient, 1);
	if (iIndex > 0)
		return GetEntPropFloat(iIndex, Prop_Send, "m_flChargeLevel");
	else
		return 0.0;
}

stock void TF2_SetUberLevel(int iClient, float fUberLevel) {
	int iIndex = GetPlayerWeaponSlot(iClient, 1);
	if (iIndex > 0)
		SetEntPropFloat(iIndex, Prop_Send, "m_flChargeLevel", fUberLevel);
}
