#include "stinc.h"
#include "stmisc.h"
#include "stsetting.h"
#include "stplayer.h"
#include "stanticheat.h"

void gzSettingsAntiCheatClass::Load()
{
	struct stat attrib;
	stat("AntiCheat.ini", &attrib);
	if (this->m_confModTime != attrib.st_mtime)
	{
		this->m_confModTime = attrib.st_mtime;

		this->m_cfg = new aTextConfig("AntiCheat.ini");
		this->m_enableSpectate       = this->Load_Bool("Spectate", "Enable", false);
		this->m_ammoLive             = this->Load_Float("General", "AmmoLive", 10.0f);
		this->RoF.Enable             = this->Load_Bool("RateOfFire", "Enable", false);
		this->RoF.VariationAllowance = this->Load_Float("RateOfFire", "VariationAllowance", 1.1f);
		delete this->m_cfg;
		this->m_cfg = NULL;
	}
}

/*******************/
/* Anti-cheat base */
/*******************/
gzAntiCheatBaseClass::gzAntiCheatBaseClass(gzPlayer *owner)
{
	this->RegisterEvent(EVT_GAME_LEVEL_LOADED);
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_PLAYER_RAWDAMAGE);

	this->m_owner = owner;
}
void gzAntiCheatBaseClass::WriteLog(const char *msg, ...)
{
	aDateTime now = aDateTime::Now();
	aFile logFile(aString::Format("cheat_%d-%d-%04d.txt", now.GetMonth(), now.GetDay(), now.GetYear()), "a");
	if (logFile.IsOpened())
	{
		// Formsg string
		va_list args;
		va_start(args, msg);
		aString fmtMsg;
		fmtMsg.Format_Args(msg, args);
		va_end(args);

		logFile.Write("[" + now.Format("%X") + "] " + fmtMsg);
	}
}

/********************************/
/* RGH Spectator mode detection */
/********************************/
gzAntiCheatSpec::gzAntiCheatSpec(gzPlayer *owner)
	: gzAntiCheatBaseClass(owner)
{
	this->RegisterEvent(EVT_OBJECT_CREATE);

	this->m_checkTimeCounter = 0.0f;
}
void gzAntiCheatSpec::Delete()
{
	for (unsigned int i = 0; i < this->m_data.Count(); i++)
		delete this->m_data[i];
	this->m_data.Clear();
}
void gzAntiCheatSpec::Level_Loaded()
{
	this->Delete();
}
void gzAntiCheatSpec::Think()
{
	if (!gzAntiCheatMgr->m_settings->m_enableSpectate)
		return;

	SubFrameTime(this->m_checkTimeCounter, true);
	if (this->m_checkTimeCounter < 0.0f && this->m_owner->GetPlayerData()->IsActive)
	{
		for (unsigned int i = 0; i < this->m_data.Count(); i++)
		{
			nc_PhysicalGameObj *seen = nc_GameObjManager::Find_PhysicalGameObj(this->m_data[i]->objId);
			if (seen)
			{
				if (this->m_owner->GetPlayerData()->Owner.Reference && this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->Is_Object_Visible(seen))
					this->m_data[i]->lastSeen = GetTickCount();
				else
				{
					delete this->m_data[i];
					this->m_data.Delete(i);
					i--;
				}
			}
		}
		this->m_checkTimeCounter = ANTICHEAT_RGHSPEC_INTERVAL;
	}
}
void gzAntiCheatSpec::Player_RawDamage(gzEventObjectDamage &evt)
{
	if (!gzAntiCheatMgr->m_settings->m_enableSpectate)
		return;

	if (evt.m_attacker->As_SoldierGameObj()->Player == this->m_owner->GetPlayerData())
	{
		if (this->m_owner->GetPlayerData()->Owner.Reference && evt.m_defender)
		{
			if (evt.m_attacker->NetworkID == this->m_owner->GetPlayerData()->Owner.Reference->obj->NetworkID)
			{
				for (unsigned int i = 0; i < this->m_data.Count(); i++)
				{
					if (this->m_data[i]->objId == evt.m_defender->NetworkID)
					{
						nc_PhysicalGameObj *seen = nc_GameObjManager::Find_PhysicalGameObj(this->m_data[i]->objId);
						if (seen)
						{
							nc_Vector3 pos[2];
							this->m_owner->GetPlayerData()->Owner.Reference->obj->Get_Position(&pos[0]);
							seen->Get_Position(&pos[1]);
							float distance = gzCommands->Get_Distance(pos[0], pos[1]);
							if (this->m_data[i]->lastSeen == 0)
							{
								if (evt.m_defender->As_SoldierGameObj() && evt.m_defender->As_SoldierGameObj()->Player)
								{
									this->WriteLog("%ls damaged player %ls which was never visible.\n",
										this->m_owner->GetPlayerName().GetString(),
										evt.m_defender->As_SmartGameObj()->Player->PlayerName.m_Buffer
									);
								}
								else
								{
									this->WriteLog("%ls damaged %s which was never visible.\n",
										this->m_owner->GetPlayerName().GetString(),
										evt.m_defender->definition->Get_Name()
									);
								}
								nc_WeaponBagClass *Bag = this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->WeaponBag;
								this->WriteLog("\tDamage: %f\n", evt.m_damage);
								this->WriteLog("\tWarhead: %d\n", evt.m_warhead);
								this->WriteLog("\tDistance: %f\n", distance);
								this->WriteLog("\tAttacker preset: %s/%s(%d); Vehicle: %s(%d)\n",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->definition->Get_Name(),
									(Bag->current && Bag->current < Bag->Vector.Count()) ? Bag->Vector[Bag->current]->WeaponDef->Get_Name() : "No_weapon",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->NetworkID,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle ? this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->definition->Get_Name() : "No_vehicle",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle ? this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID : 0
								);
								this->WriteLog("\tAttacker position: X;%f   Y;%f   Z;%f   F;%f\n",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosX,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosY,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosZ,
									gzCommands->Get_Facing(this->m_owner->GetPlayerData()->Owner.Reference->obj)
								);
								this->WriteLog("\tDefender preset: %s(%d)\n", evt.m_defender->definition->Get_Name(), evt.m_defender->NetworkID);
								this->WriteLog("\tDefender position: X;%f   Y;%f   Z;%f   F;%f\n",
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosX,
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosY,
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosZ,
									gzCommands->Get_Facing(seen)
								);
								this->WriteLog("\tPing: %d; Bandwidth: %d\n",
									nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping,
									PacketMgr->Get_Compressed_Bandwidth_Out(&nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Address) >> 10
								);
								this->WriteLog("-------------------------------------------------\n");
							}
							else if ((GetTickCount() - this->m_data[i]->lastSeen) > 10000)
							{
								if (evt.m_defender->As_VehicleGameObj() != NULL)
								{
									this->WriteLog("%ls damaged vehicle %s which was visible %s ago.\n",
										this->m_owner->GetPlayerName().GetString(),
										evt.m_defender->definition->Get_Name(),
										SecsToAscTime((GetTickCount() - this->m_data[i]->lastSeen) / 1000).GetString()
									);
								}
								else if (evt.m_defender->As_SoldierGameObj() != NULL)
								{
									if (evt.m_defender->As_SoldierGameObj()->Player == NULL)
									{
										this->WriteLog("%ls damaged soldier %s which was never visible.\n",
											this->m_owner->GetPlayerName().GetString(),
											evt.m_defender->definition->Get_Name()
										);
									}
									else
									{
										this->WriteLog("%ls damaged player %ls which was visible %s ago.\n",
											this->m_owner->GetPlayerName().GetString(),
											evt.m_defender->As_SoldierGameObj()->Player->PlayerName.m_Buffer,
											SecsToAscTime((GetTickCount() - this->m_data[i]->lastSeen) / 1000).GetString()
										);
									}
								}
								this->WriteLog("\tDamage: %f\n", evt.m_damage);
								this->WriteLog("\tWarhead: %d\n", evt.m_warhead);
								this->WriteLog("\tDistance: %f\n", distance);
								this->WriteLog("\tAttacker preset: %s(%d); Vehicle: %s(%d)\n",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->definition->Get_Name(),
									this->m_owner->GetPlayerData()->Owner.Reference->obj->NetworkID,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle ? this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->definition->Get_Name() : "No_vehicle",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle ? this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID : 0
								);
								this->WriteLog("\tAttacker position: X;%f   Y;%f   Z;%f   F;%f\n",
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosX,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosY,
									this->m_owner->GetPlayerData()->Owner.Reference->obj->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosZ,
									gzCommands->Get_Facing(this->m_owner->GetPlayerData()->Owner.Reference->obj)
								);
								this->WriteLog("\tDefender preset: %s(%d)\n", evt.m_defender->definition->Get_Name(), evt.m_defender->NetworkID);
								this->WriteLog("\tDefender position: X;%f   Y;%f   Z;%f   F;%f\n",
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosX,
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosY,
									evt.m_defender->As_PhysicalGameObj()->Physics->PhysRender->Transform.PosZ,
									gzCommands->Get_Facing(seen)
								);
								this->WriteLog("\tPing: %d; Bandwidth: %d\n",
									nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping,
									PacketMgr->Get_Compressed_Bandwidth_Out(&nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Address) >> 10
								);
								this->WriteLog("-------------------------------------------------\n");
							}
						}
					}
				}
			}
		}
	}
}
void gzAntiCheatSpec::Object_Created(gzEventObjectCreate &evt)
{
	if (!gzAntiCheatMgr->m_settings->m_enableSpectate)
		return;

	if (evt.m_obj->As_SmartGameObj())
	{
		gzRGHSpecDataStruct *RGHData = new gzRGHSpecDataStruct;
		RGHData->lastSeen = 0;
		RGHData->objId = evt.m_obj->NetworkID;
		this->m_data.Add(RGHData);
	}
}


/**************************/
/* Weapons hack detection */
/**************************/
void gzAntiCheatWeapons::Delete()
{
	for (unsigned int i = 0; i < this->m_data.Count(); i++)
	{
		for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
			delete this->m_data[i]->ammoList[j];
		this->m_data[i]->ammoList.Clear();
		delete this->m_data[i];
	}
	this->m_data.Clear();
}
gzAntiCheatWeapons::gzAntiCheatWeapons(gzPlayer *owner)
	: gzAntiCheatBaseClass(owner)
{
	this->RegisterEvent(EVT_OBJECT_TRANSITION, -1);
}
void gzAntiCheatWeapons::Level_Loaded()
{
	this->Delete();
}
void gzAntiCheatWeapons::Think()
{
	if (!gzAntiCheatMgr->m_settings->RoF.Enable)
		return;

	if (this->m_owner->IsActive() && this->m_owner->GetSoldier())
	{
		if (this->m_owner->GetSoldier()->NetworkID != this->m_soldierId)
		{
			for (unsigned int i = 0; i < this->m_data.Count(); i++)
			{
				for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
				{
					nc_AmmoDefinitionClass *AmmoDef = this->m_data[i]->ammoList[j]->AmmoDef;
					this->m_data[i]->ammoList[j]->DeleteCounter = (((AmmoDef->Range.Get() / AmmoDef->Velocity.Get()) + AmmoDef->TurnRate) + gzAntiCheatMgr->m_settings->m_ammoLive);
#if ISDEV()
					stConsole::Out("[WeaponHack] Applying delete counter for ammo: %s [%f]\n", AmmoDef->Get_Name(), this->m_data[i]->ammoList[j]->DeleteCounter);
#endif
				}
			}
			this->m_soldierId = this->m_owner->GetSoldier()->NetworkID;
		}

		nc_SoldierGameObj *obj = this->m_owner->GetSoldier();

		if (obj->WeaponBag->Vector.Count() >= 2)
		{
			for (int i = 1; i < obj->WeaponBag->Vector.Count(); i++)
			{
				// Beacon or C4 doesn't need to be checked
				if (obj->WeaponBag->Vector[i]->WeaponDef->Style == 0 ||	obj->WeaponBag->Vector[i]->WeaponDef->Style == 6)
					continue;

				bool weaponFound = false;
				for (unsigned int j = 0; j < this->m_data.Count(); j++)
				{
					if (this->m_data[j]->WeaponDef == obj->WeaponBag->Vector[i]->WeaponDef)
					{
						bool ammoFound[2] = { false, false };
						for (unsigned int k = 0; k < this->m_data[j]->ammoList.Count(); k++)
						{
							if (this->m_data[j]->ammoList[k]->AmmoDef == obj->WeaponBag->Vector[i]->PrimaryAmmo)
								ammoFound[0] = true;
							if (obj->WeaponBag->Vector[i]->PrimaryAmmo != obj->WeaponBag->Vector[i]->SecondaryAmmo && this->m_data[j]->ammoList[k]->AmmoDef == obj->WeaponBag->Vector[i]->SecondaryAmmo)
								ammoFound[1] = true;

							if (ammoFound[0] && ammoFound[1])
								break;
						}

						if (!ammoFound[0])
							this->AddAmmo(obj->WeaponBag->Vector[i]->PrimaryAmmo, *this->m_data[j]);
						if (!ammoFound[1])
							this->AddAmmo(obj->WeaponBag->Vector[i]->SecondaryAmmo, *this->m_data[j]);

						if (obj->WeaponBag->current > 0 && this->m_data[j]->WeaponDef == obj->WeaponBag->Vector[obj->WeaponBag->current]->WeaponDef)
							this->m_data[j]->lastSelectTime = ((float)(GetTickCount() - SystemTime) / 1000.0f);
						weaponFound = true;
					}
				}

				if (!weaponFound)
				{
					gzRoFWeaponDataStruct *rofWeapon = new gzRoFWeaponDataStruct;
					rofWeapon->lastSelectTime = -1.0f;
					rofWeapon->WeaponDef = obj->WeaponBag->Vector[i]->WeaponDef;
					this->m_data.Add(rofWeapon);

#ifdef ROFMSG
					stConsole::Out("[WeaponsHack] New weapon detected from player %ls: %s\n",
						this->m_owner->GetPlayerName().GetString(),
						rofWeapon->WeaponDef->Get_Name()
					);
#endif
				}
			}
		}

		for (unsigned int i = 0; i < this->m_data.Count(); i++)
		{
			for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
			{
				gzRoFAmmoDataStruct *ammo = this->m_data[i]->ammoList[j];

				SubFrameTime(ammo->counter, true);
				AddFrameTime(ammo->measureTime, true);
				SubFrameTime(ammo->DeleteCounter, (ammo->DeleteCounter > 0.0f));

				if (ammo->counter < 0.0f)
				{
#ifdef ROFMSG
					//DebugLog("%ls; Weapon: %s; Ammo: %s; ROF: %f; Reset delay: %f; Meansure time: %f; Measure cycle: %d; Hits: %d/%d",
					//	this->m_owner->GetPlayerName().GetString(),
					//	this->m_data[i]->WeaponDef->Get_Name(),
					//	ammo->AmmoDef->Get_Name(),
					//	ammo->AmmoDef->RateOfFire,
					//	ammo->cycleInterval,
					//	ammo->measureTime,
					//	ammo->meansureCycleCount,
					//	ammo->hitCount,
					//	(ammo->maximumHit + ammo->additionMaxHit)
					//);
#endif

					if (ammo->DeleteCounter > 0.0f)
					{
						bool canReset = true;
						if (ammo->meansureCycle == 1 && ammo->hitCount > (ammo->maximumHit + ammo->additionMaxHit))
						{
							stConsole::Out("[ST] Rate Of Fire exceeded by %ls; Ammo: %s; Hits: %d/%d; Measure time: %f sec(s); Ping: %d\n",
								this->m_owner->GetPlayerName().GetString(),
								ammo->AmmoDef->Get_Name(),
								ammo->hitCount,
								ammo->maximumHit,
								ammo->measureTime,
								nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping
							);
							this->WriteLog("Rate Of Fire exceeded by %ls:\n", this->m_owner->GetPlayerName().GetString());
							this->WriteLog("\tAmmo: %s\n", ammo->AmmoDef->Get_Name());
							this->WriteLog("\tHits: %d/%d\n", ammo->hitCount, (ammo->maximumHit + ammo->additionMaxHit));
							this->WriteLog("\tMeansure time: %f sec(s)\n", ammo->measureTime);
							this->WriteLog("\tPing: %d\n", nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping);
							this->WriteLog("\tOwned ammo:\n");
							for (unsigned int i = 0; i < this->m_data.Count(); i++)
							{
								for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
								{
									aString log = aString::Format("\t\t%s - ", this->m_data[i]->ammoList[j]->AmmoDef->Get_Name());
									float lastSelect = this->GetWeaponLastSelectTime(this->m_data[i]);
									if (lastSelect == -1.0f)
										log += "never selected";
									else if (lastSelect <= 0.0f)
										log += "currently selected";
									else
										log += aString::Format("selected %f sec(s) ago", lastSelect);
									this->WriteLog("%s\n", log.GetString());
								}
							}
							this->WriteLog("-------------------------------------------------\n");
						}
						else if (ammo->hitCount > ((ammo->maximumHit * ammo->meansureCycle) + ammo->additionMaxHit))
						{
							if (ammo->meansureCycle >= 3)
							{
								stConsole::Out("[ST] Rate Of Fire exceeded by %ls; Ammo: %s; Hits: %d/%d; Measure time: %f sec(s); Ping: %d\n",
									this->m_owner->GetPlayerName().GetString(),
									ammo->AmmoDef->Get_Name(),
									ammo->hitCount,
									((ammo->maximumHit * ammo->meansureCycle) + ammo->additionMaxHit),
									ammo->measureTime,
									nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping
								);
								this->WriteLog("Rate Of Fire exceeded by %ls:\n", this->m_owner->GetPlayerName().GetString());
								this->WriteLog("\tAmmo: %s\n", ammo->AmmoDef->Get_Name());
								this->WriteLog("\tHits: %d/%d\n", ammo->hitCount, ((ammo->maximumHit * ammo->meansureCycle) + ammo->additionMaxHit));
								this->WriteLog("\tMeansure time: %f sec(s)\n", ammo->measureTime);
								this->WriteLog("\tPing: %d\n", nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping);
								this->WriteLog("\tOwned ammo:\n");
								for (unsigned int i = 0; i < this->m_data.Count(); i++)
								{
									for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
									{
										aString log = aString::Format("\t\t%s - ", this->m_data[i]->ammoList[j]->AmmoDef->Get_Name());
										float lastSelect = this->GetWeaponLastSelectTime(this->m_data[i]);
										if (lastSelect == -1.0f)
											log += "never selected";
										else if (lastSelect <= 0.0f)
											log += "currently selected";
										else
											log += aString::Format("selected %f sec(s) ago", lastSelect);
										this->WriteLog("%s\n", log.GetString());
									}
								}
								this->WriteLog("-------------------------------------------------\n");
							}
							else
								canReset = false;

							ammo->meansureCycle++;
							ammo->counter = ammo->cycleInterval;
						}

						if (canReset)
						{
							ammo->measureTime    = 0.0f;
							ammo->hitCount       = 0;
							ammo->meansureCycle  = 1;
							ammo->counter        = ammo->cycleInterval;
							ammo->additionMaxHit = 0;
						}
					}
					else
					{
#if ISDEV()
						stConsole::Out("[WeaponHack] Ammo \"%s\" expired. Removing...\n", ammo->AmmoDef->Get_Name());
#endif
						delete[] this->m_data[i]->ammoList[j];
						this->m_data[i]->ammoList.Delete(j);
						j--;
					}
				}
			}

			// In vehicle and is gunner
			if (obj->Vehicle && obj == obj->Vehicle->Get_Actual_Gunner())
			{
				for (int j = 1; j < obj->Vehicle->WeaponBag->Vector.Count(); j++)
				{
					if (this->m_data[i]->WeaponDef == obj->Vehicle->WeaponBag->Vector[j]->WeaponDef)
					{
						this->m_data[i]->lastSelectTime = ((float)(GetTickCount() - SystemTime) / 1000.0f);
						break;
					}
				}
			}
		}
	}
}
void gzAntiCheatWeapons::Player_RawDamage(gzEventObjectDamage &evt)
{
	if (!gzAntiCheatMgr->m_settings->RoF.Enable)
		return;

	if (evt.m_attacker->As_SoldierGameObj() && evt.m_attacker->As_SoldierGameObj()->Player == this->m_owner->GetPlayerData())
	{
		if (!evt.m_defender)
			return;
		
		nc_SmartGameObj *obj = evt.m_attacker->As_SoldierGameObj();
		if (obj->As_SoldierGameObj()->Vehicle)
			obj = obj->As_SoldierGameObj()->Vehicle;

		int dataId = -1;
		if (obj->WeaponBag->current && obj->WeaponBag->current < obj->WeaponBag->Vector.Count())
		{
			float AmmoDamage[2] = {
				obj->WeaponBag->Vector[obj->WeaponBag->current]->PrimaryAmmo->Damage.Get(),
				obj->WeaponBag->Vector[obj->WeaponBag->current]->SecondaryAmmo->Damage.Get()
			};
			int AmmoWarhead[2] = {
				obj->WeaponBag->Vector[obj->WeaponBag->current]->PrimaryAmmo->Warhead.Get(),
				obj->WeaponBag->Vector[obj->WeaponBag->current]->SecondaryAmmo->Warhead.Get()
			};

			// Compare ammo
			nc_AmmoDefinitionClass *detectedAmmo = NULL;
			if ((evt.m_damage == AmmoDamage[0] || evt.m_damage == (AmmoDamage[0] * 3.0f) || evt.m_damage == (AmmoDamage[0] * 5.0f)) && evt.m_warhead == AmmoWarhead[0])
				detectedAmmo = obj->WeaponBag->Vector[obj->WeaponBag->current]->PrimaryAmmo;
			else if ((evt.m_damage == AmmoDamage[1] || evt.m_damage == (AmmoDamage[1] * 3.0f) || evt.m_damage == (AmmoDamage[1] * 5.0f)) && evt.m_warhead == AmmoWarhead[1])
				detectedAmmo = obj->WeaponBag->Vector[obj->WeaponBag->current]->SecondaryAmmo;

			if (detectedAmmo != NULL)
			{
				for (unsigned int i = 0; i < this->m_data.Count(); i++)
				{
					if (this->m_data[i]->WeaponDef == obj->WeaponBag->Vector[obj->WeaponBag->current]->WeaponDef)
					{
						for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
						{
							if (this->m_data[i]->ammoList[j]->AmmoDef == detectedAmmo)
							{
#ifdef ROFMSG
								stConsole::Out("[WeaponHack] Weapon determined: %s; Ammo: %s\n",
									this->m_data[i]->WeaponDef->Get_Name(),
									this->m_data[i]->ammoList[j]->AmmoDef->Get_Name()
								);
#endif
								if (this->IsAmmoExpired(this->m_data[i]))
									evt.Skip();
								else
									this->AddHitCount(this->m_data[i]->ammoList[j], evt);
								goto weaponFound;
							}
						}
					}
				}
weaponFound:;
			}
			else
			{
				aVector<gzRoFHitDataStruct> possibleWeapons;
				for (unsigned int i = 0; i < this->m_data.Count(); i++)
				{
					for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
					{
						if (this->m_data[i]->ammoList[j]->AmmoDamage == evt.m_damage && this->m_data[i]->ammoList[j]->AmmoWarhead == evt.m_warhead)
						{
							gzRoFHitDataStruct hitData;
							hitData.weaponId = i;
							hitData.ammoId = j;
							possibleWeapons.Add(hitData);
#ifdef ROFMSG
							stConsole::Out("[WeaponHack] Possible weapon: %s; Ammo: %s; Damage: %f; Warhead: %d; Last select: %f sec(s) ago\n",
								this->m_data[i]->WeaponDef->Get_Name(),
								this->m_data[i]->ammoList[j]->AmmoDef->Get_Name(),
								this->m_data[i]->ammoList[j]->AmmoDamage,
								this->m_data[i]->ammoList[j]->AmmoWarhead,
								this->GetWeaponLastSelectTime(this->m_data[i])
							);
#endif
						}
					}
				}
				if (possibleWeapons.Count() == 0)
				{
					stConsole::Out("[ST] %ls used an unavailable weapon; Damage: %f; Warhead; %d\n",
						this->m_owner->GetPlayerName().GetString(),
						evt.m_damage,
						evt.m_warhead
					);
					this->WriteLog("%ls used an unavailable weapon.\n", this->m_owner->GetPlayerName().GetString());
					this->WriteLog("\tDamage: %f\n", evt.m_damage);
					this->WriteLog("\tWarhead: %d\n", evt.m_warhead);
					this->WriteLog("\tPing: %d\n", nc_cNetwork::PServerConnection->RemoteList[this->m_owner->GetPlayerData()->PlayerId]->Ping);
					this->WriteLog("\tOwned ammo:\n");
					for (unsigned int i = 0; i < this->m_data.Count(); i++)
					{
						for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
						{
							aString log = aString::Format("\t\t%s - ", this->m_data[i]->ammoList[j]->AmmoDef->Get_Name());
							float lastSelect = this->GetWeaponLastSelectTime(this->m_data[i]);
							if (lastSelect == -1.0f)
								log += "never selected";
							else if (lastSelect <= 0.0f)
								log += "currently selected";
							else
								log += aString::Format("selected %f sec(s) ago", lastSelect);
							this->WriteLog("%s\n", log.GetString());
						}
					}
					this->WriteLog("-------------------------------------------------\n");
					return;
				}
				else if (possibleWeapons.Count() == 1)
				{
#ifdef ROFMSG
					stConsole::Out("[WeaponHack] Weapon determined: %s; Ammo: %s; Last select: %f sec(s) ago\n",
						this->m_data[possibleWeapons[0].weaponId]->WeaponDef->Get_Name(),
						this->m_data[possibleWeapons[0].weaponId]->ammoList[possibleWeapons[0].ammoId]->AmmoDef->Get_Name(),
						this->GetWeaponLastSelectTime(this->m_data[possibleWeapons[0].weaponId])
					);
#endif
					if (this->IsAmmoExpired(this->m_data[possibleWeapons[0].weaponId]))
						evt.Skip();
					else
						this->AddHitCount(this->m_data[possibleWeapons[0].weaponId]->ammoList[possibleWeapons[0].ammoId], evt);
				}
				else
				{
					gzRoFHitDataStruct *hitData = NULL;
					float lastWeaponSelectTime = 0.0f;
					for (unsigned int i = 0; i < possibleWeapons.Count(); i++)
					{
						if (this->IsAmmoExpired(this->m_data[possibleWeapons[i].weaponId]))
						{
							lastWeaponSelectTime = this->m_data[possibleWeapons[i].weaponId]->lastSelectTime;
							hitData = &possibleWeapons[i];
						}
					}

					if (hitData != NULL)
					{
#ifdef ROFMSG
						stConsole::Out("[WeaponHack] Weapon determined: %s; Ammo: %s; Last select: %f sec(s) ago\n",
							this->m_data[hitData->weaponId]->WeaponDef->Get_Name(),
							this->m_data[hitData->weaponId]->ammoList[hitData->ammoId]->AmmoDef->Get_Name(),
							this->GetWeaponLastSelectTime(this->m_data[hitData->weaponId])
						);
#endif
						if (this->IsAmmoExpired(this->m_data[hitData->weaponId]))
							evt.Skip();
						else
							this->AddHitCount(this->m_data[hitData->weaponId]->ammoList[hitData->ammoId], evt);
					}
				}
			}
		}
	}
}
void gzAntiCheatWeapons::Object_Transition(gzEventObjectTransition &evt)
{
	if (evt.m_soldier == this->m_owner->GetSoldier())
	{
		if (evt.m_instance->Target.Reference && evt.m_instance->Target.Reference->obj->As_VehicleGameObj())
		{
			// Attempt to enter a vehicle
			if (!evt.m_soldier->Vehicle)
			{
				nc_VehicleGameObj *vehicle = evt.m_instance->Target.Reference->obj->As_VehicleGameObj();
				for (int i = 1; i < vehicle->WeaponBag->Vector.Count(); i++)
				{
					bool weaponFound = false;
					for (unsigned int j = 0; j < this->m_data.Count(); j++)
					{
						if (this->m_data[j]->WeaponDef == vehicle->WeaponBag->Vector[i]->WeaponDef)
						{
							weaponFound = true;
							break;
						}
					}

					if (!weaponFound)
					{
						gzRoFWeaponDataStruct *rofWeapon = new gzRoFWeaponDataStruct;
						rofWeapon->lastSelectTime = ((float)(GetTickCount() - SystemTime) / 1000.0f);
						rofWeapon->WeaponDef = vehicle->WeaponBag->Vector[i]->WeaponDef;
						this->m_data.Add(rofWeapon);

						this->AddAmmo(vehicle->WeaponBag->Vector[i]->PrimaryAmmo, *rofWeapon);

						if (vehicle->WeaponBag->Vector[i]->SecondaryAmmo && vehicle->WeaponBag->Vector[i]->PrimaryAmmo != vehicle->WeaponBag->Vector[i]->SecondaryAmmo)
							this->AddAmmo(vehicle->WeaponBag->Vector[i]->SecondaryAmmo, *rofWeapon);
					}
				}
			}
		}
	}
}
void gzAntiCheatWeapons::AddAmmo(nc_AmmoDefinitionClass *ammo, gzRoFWeaponDataStruct &weapon)
{
	// Existence check
	for (unsigned int i = 0; i < this->m_data.Count(); i++)
	{
		for (unsigned int j = 0; j < this->m_data[i]->ammoList.Count(); j++)
		{
			if (this->m_data[i]->ammoList[j]->AmmoDef == ammo)
			{
				this->m_data[i]->ammoList[j]->DeleteCounter = 999999.0f;
				return;
			}
		}
	}

	gzRoFAmmoDataStruct *rofAmmo = new gzRoFAmmoDataStruct;
	rofAmmo->Weapon         = &weapon;
	rofAmmo->AmmoDef        = ammo;
	rofAmmo->AmmoDamage     = ammo->Damage.Get();
	rofAmmo->AmmoWarhead    = ammo->Warhead.Get();
	rofAmmo->AmmoSprayCount = ammo->SprayCount.Get();
	rofAmmo->WeapClipSize   = weapon.WeaponDef->ClipSize.Get();
	rofAmmo->WeapReloadTime = weapon.WeaponDef->ReloadTime.Get();

	if (rofAmmo->WeapClipSize == 1)
	{
		rofAmmo->counter = ((1.0f / ammo->RateOfFire) + rofAmmo->WeapReloadTime) < 1.0f ? 1.0f : floor((1.0f / ammo->RateOfFire) + rofAmmo->WeapReloadTime);
		rofAmmo->maximumHit = (int)ceil(rofAmmo->AmmoSprayCount * gzAntiCheatMgr->m_settings->RoF.VariationAllowance);
	}
	else
	{
		rofAmmo->counter = (1.0f / ammo->RateOfFire) < 1.0f ? 1.0f : floor(1.0f / ammo->RateOfFire);
		rofAmmo->maximumHit = (int)ceil(ammo->RateOfFire * rofAmmo->AmmoSprayCount * gzAntiCheatMgr->m_settings->RoF.VariationAllowance);
		if (rofAmmo->maximumHit <= 0)
			rofAmmo->maximumHit = (int)ceil(gzAntiCheatMgr->m_settings->RoF.VariationAllowance);
	}

	rofAmmo->cycleInterval  = rofAmmo->counter;
	rofAmmo->hitCount       = 0;
	rofAmmo->meansureCycle  = 1;
	rofAmmo->measureTime    = 0.0f;
	rofAmmo->additionMaxHit = 0;
	rofAmmo->DeleteCounter  = 999999.0f;
	weapon.ammoList.Add(rofAmmo);

#ifdef ROFMSG
	stConsole::Out("[RoF] New ammo added to weapon: %s; Ammo: %s\n",
		weapon.WeaponDef->Get_Name(),
		ammo->Get_Name()
	);
#endif
}
bool gzAntiCheatWeapons::IsAmmoExpired(gzRoFWeaponDataStruct *weapon)
{
	if ((((float)(GetTickCount() - SystemTime) / 1000.0f) - weapon->lastSelectTime) > gzAntiCheatMgr->m_settings->m_ammoLive)
		return true;
	return false;
}
void gzAntiCheatWeapons::AddHitCount(gzRoFAmmoDataStruct *ammo, gzEventObjectDamage evt)
{
	// Repair
	if (evt.m_warhead == 15 && evt.m_damage < 0.0f)
	{
		// C4
		if (evt.m_defender && evt.m_defender->definition->Get_Class_ID() == 0x3006)
		{
			nc_C4GameObj *c4 = gzStatic_Cast(nc_C4GameObj, evt.m_defender);

			// Attached to an object and is not building because buildings are controlled server-side
			if (c4->Attached.Reference && c4->Attached.Reference->obj->As_BuildingGameObj() == NULL)
			{
				// Add an addition allowed hit because if C4 is attached to an object other building,
				// it repair the attached object too cause an another hit!
				ammo->additionMaxHit += 1;
			}
		}
	}
	ammo->hitCount++;
}
float gzAntiCheatWeapons::GetWeaponLastSelectTime(gzRoFWeaponDataStruct *weapon)
{
	nc_SoldierGameObj *soldier = this->m_owner->GetSoldier();
	if (soldier)
	{
		if (soldier->Vehicle)
		{
			for (int i = 1; i < soldier->Vehicle->WeaponBag->Vector.Count(); i++)
			{
				if (soldier->Vehicle->WeaponBag->Vector[i]->WeaponDef == weapon->WeaponDef)
					return 0.0f;
			}
		}

		if (soldier->WeaponBag->Vector.Count() > 1)
		{
			if (soldier->WeaponBag->Vector[soldier->WeaponBag->current]->WeaponDef == weapon->WeaponDef)
				return 0.0f;

			if (weapon->lastSelectTime > -1.0f)
				return (((float)(GetTickCount() - SystemTime) / 1000.0f) - weapon->lastSelectTime);
		}
	}
	return -1.0f;
}


/***********/
/* Hit log */
/***********/
gzAntiCheatHitLog::gzAntiCheatHitLog(gzPlayer *owner)
	: gzAntiCheatBaseClass(owner)
{
	this->m_checkCounter = 1.0f;
}
void gzAntiCheatHitLog::Delete()
{
	this->m_data.Clear();
}
void gzAntiCheatHitLog::Level_Loaded()
{
	this->Delete();
}
void gzAntiCheatHitLog::Think()
{
	SubFrameTime(this->m_checkCounter, true);
	if (this->m_checkCounter < 0.0f)
	{
		for (unsigned int i = 0; i < this->m_data.Count(); i++)
		{
			if ((GetTickCount() - this->m_data[i].m_time) >= (ANTICHEAT_HITLOG_LIVE * 1000) || !nc_GameObjManager::Find_PhysicalGameObj(this->m_data[i].m_obj))
			{
				this->m_data.Delete(i);
				i--;
			}
		}
		this->m_checkCounter = 1.0f;
	}
}
void gzAntiCheatHitLog::Player_RawDamage(gzEventObjectDamage &evt)
{
	if (evt.m_attacker->As_SoldierGameObj() && evt.m_attacker->As_SoldierGameObj()->Player == this->m_owner->GetPlayerData())
	{
		if (!evt.m_defender)
			return;

		// Log soldier and vehicle only
		if (!evt.m_defender->As_SoldierGameObj() && !evt.m_defender->As_VehicleGameObj())
			return;

		if (evt.m_damage > 0.0f)
		{
			gzHitLogStruct log;
			log.m_obj = evt.m_defender->NetworkID;
			log.m_damage = evt.m_damage;
			log.m_time = GetTickCount();
			this->m_data.Add(log);
		}
	}
}
gzHitLogStruct *gzAntiCheatHitLog::GetLast(unsigned int objId)
{
	for (int i = (this->m_data.Count() - 1); i >= 0; i--)
	{
		if (this->m_data[i].m_obj == objId)
			return &this->m_data[i];
	}
	return NULL;
}
int gzAntiCheatHitLog::GetHitCount(unsigned int objId, unsigned long interval)
{
	int count = 0;
	for (unsigned int i = 0; i < this->m_data.Count(); i++)
	{
		if (this->m_data[i].m_obj == objId && (GetTickCount() - this->m_data[i].m_time) < (interval * 1000))
			count++;
	}
	return count;
}


/**********************/
/* Anti-cheat Manager */
/**********************/
gzAntiCheatManager *gzAntiCheatMgr = NULL;
gzAntiCheatManager::gzAntiCheatManager()
{
	this->m_settings = new gzSettingsAntiCheatClass;
}
void gzAntiCheatManager::Delete()
{
}
