#include "stinc.h"
#include "stsetting.h"
#include "stmisc.h"
#include "stchat.h"
#include "sttranslate.h"
#include "stplayer.h"
#include "stconsolecommands.h"
#include "stgame.h"
#include "stcrate.h"
#include "stteamcommander.h"
#include "sttibcrystal.h"
#include "stweapon.h"

class gzChat_C4;
class gzChat_WeaponList;
class gzChat_WeaponDrop;

/************/
/* Settings */
/************/
void gzSettingsWeaponClass::Delete()
{
	for (unsigned int i = 0; i < this->Weapons.Level.Count(); i++)
		delete this->Weapons.Level[i];
	this->Weapons.Level.Clear();

	for (unsigned int i = 0; i < this->Weapons.Ban.Count(); i++)
		delete this->Weapons.Ban[i];
	this->Weapons.Ban.Clear();
	
	for (unsigned int i = 0; i < this->Backpack.DropData.Count(); i++)
	{
		for (unsigned int j = 0; j < this->Backpack.DropData[i]->m_weapons.Count(); j++)
			delete this->Backpack.DropData[i]->m_weapons[j];
		this->Backpack.DropData[i]->m_weapons.Clear();
		delete this->Backpack.DropData[i];
	}
	this->Backpack.DropData.Clear();
}
void gzSettingsWeaponClass::Load()
{
	if (!this->Open("Weapons.ini"))
		stConsole::Out("[Error] Weapons.ini can not be loaded or not found.\n");

	this->Delete();

	this->m_BeaconLimit = this->Load_Int("General", "BeaconLimit", 1);
	this->Backpack.Live = this->Load_Float("General", "BackpackLive", 15.0f);
	this->Backpack.OwnTime = this->Load_Float("General", "OwnTime", 5.0f);

	// Remote C4
	this->C4.Output[0][0] = this->Load_Bool("C4", "Output_RC", false);
	this->C4.Output[0][1] = this->Load_Bool("C4", "Output_RD", false);
	this->C4.Output[0][2] = this->Load_Bool("C4", "Output_RE", false);

	// Timed C4
	this->C4.Output[1][0] = this->Load_Bool("C4", "Output_TC", false);
	this->C4.Output[1][1] = this->Load_Bool("C4", "Output_TD", false);
	this->C4.Output[1][2] = this->Load_Bool("C4", "Output_TE", false);

	// Proximity C4
	this->C4.Output[2][0] = this->Load_Bool("C4", "Output_PC", false);
	this->C4.Output[2][1] = this->Load_Bool("C4", "Output_PD", false);
	this->C4.Output[2][2] = this->Load_Bool("C4", "Output_PE", false);

	// C4 limit
	this->C4.Limit[0] = this->Load_Int("C4", "Own_Remote", -1);
	this->C4.Limit[1] = this->Load_Int("C4", "Own_Timed", 5);
	this->C4.Limit[2] = this->Load_Int("C4", "Own_Proximity", -1);
	this->C4.TeamLimit[0][0] = this->Load_Int("C4", "Team_Remote", 20);
	this->C4.TeamLimit[0][1] = this->Load_Int("C4", "Team_Timed", -1);
	this->C4.TeamLimit[0][2] = this->Load_Int("C4", "Team_Proximity", 30);
	this->C4.TeamLimit[1][0] = this->C4.TeamLimit[0][0];
	this->C4.TeamLimit[1][1] = this->C4.TeamLimit[0][1];
	this->C4.TeamLimit[1][2] = this->C4.TeamLimit[0][2];

	this->C4.CanFlaming = this->Load_Bool("C4", "AllowFlaming", false);

	// Powerup weapon
	for (int i = 1; i <= this->m_cfg->GetSubCount("PowerupWeapon"); i++)
	{
		aString powerup = this->m_cfg->GetItem(aString::Format("PowerupWeapon.[%d]", i), "");
		if (*powerup.GetString() != '\n' && this->Load_Int("PowerupWeapon", powerup.GetString(), -1) != -1)
		{
			aString weapon = this->Load_String("PowerupWeapon", powerup.GetString(), "");
			nc_WeaponDefinitionClass *weaponDef = gzStatic_Cast(nc_WeaponDefinitionClass, nc_DefinitionMgrClass::Find_Typed_Definition(weapon.GetString(), 0xB001, true));
			if (weaponDef)
			{
				for (nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Get_First(0x3003, true); Definition != NULL; Definition = nc_DefinitionMgrClass::Get_Next(Definition, 0x3003, true))
				{
					if (powerup == Definition->Get_Name())
					{
						nc_PowerUpGameObjDef *pDef = gzStatic_Cast(nc_PowerUpGameObjDef, Definition);
						pDef->GrantWeapon = true;
						pDef->GrantWeaponID = weaponDef->Get_ID();
						break;
					}
				}
			}
			else
				stConsole::Out("[PowerupWeapon]: Error - %s is not a valid weapon.\n", weapon.GetString());
		}
	}

	// Weapon settings
	this->Weapons.DropOnDeath					= this->Load_Bool("General", "DropWeaponOnDeath", true);
	this->Weapons.CarryOverOnCharacterPurchase	= this->Load_Bool("General", "WeaponCarryOverOnCharacterPurchase", true);
	this->Weapons.DropDelay						= this->Load_Float("General", "WeaponDropDelay", 5.0f);

	// Weapon levels
	for (int i = 1; i <= this->m_cfg->GetSubCount("WeaponLevel"); i++)
	{
		aString weapon = this->m_cfg->GetItem(aString::Format("WeaponLevel.[%d]", i), "");
		if (*weapon.GetString() != '\n' && this->Load_Int("WeaponLevel", weapon.GetString(), -1) != -1)
		{
			aString weaponLvl = this->Load_String("WeaponLevel", weapon.GetString(), "");
			if (!weaponLvl.IsEmpty())
			{
				bool Found = false;
				for (unsigned int j = 0; j < this->Weapons.Level.Count(); j++)
				{
					if (this->Weapons.Level[j]->Name == weaponLvl)
					{
						this->Weapons.Level[j]->Weapons.Add(weapon);
						Found = true;
						break;
					}
				}
				if (!Found)
				{
					gzWeaponLevelStruct *wls = new gzWeaponLevelStruct;
					wls->Name = weaponLvl;
					wls->Weapons.Add(weapon);
					this->Weapons.Level.Add(wls);
				}
			}
		}
	}

	// Weapon bans
	for (int i = 1; i <= this->m_cfg->GetSubCount("WeaponBan"); i++)
	{
		aString preset = this->m_cfg->GetItem(aString::Format("WeaponBan.[%d]", i), "");
		aString weapons = this->Load_String("WeaponBan", preset.GetString(), "");
		aToken wClass(weapons.GetString());

		gzWeaponBan *wb = new gzWeaponBan;
		wb->WeaponList.Clear();
		wb->Preset = preset;
		for (int j = 1; j <= wClass.numtok(' '); j++)
		{
			if (wClass.gettok(j,' ').GetData().Contain("Weapon_"))
			{
				if (nc_DefinitionMgrClass::Find_Named_Definition(wClass.gettok(j,' ').GetData().GetString(), true))
					wb->WeaponList.Add(wClass.gettok(j,' ').GetData());
			}
			else
			{
				for (unsigned int k = 0; k < this->Weapons.Level.Count(); k++)
				{
					if (this->Weapons.Level[k]->Name == wClass.gettok(j,' ').GetData())
					{
						for (unsigned int m = 0; m < this->Weapons.Level[k]->Weapons.Count(); m++)
							wb->WeaponList.Add(this->Weapons.Level[k]->Weapons[m]);
						break;
					}
				}
			}
		}
		if (wb->WeaponList.Count() > 0)
			this->Weapons.Ban.Add(wb);
		else
			delete wb;
	}

	// Dropped backpack from death soldiers
	for (int i = 1; i <= this->m_cfg->GetSubCount("SoldierBackpack"); i++)
	{
		aString preset = this->m_cfg->GetItem(aString::Format("SoldierBackpack.[%d]", i), "");
		aString weapons = this->Load_String("SoldierBackpack", preset.GetString(), "");
		if (*weapons.GetString() != '\n')
		{
			gzBackpackSoldierData *data = new gzBackpackSoldierData;
			data->preset = preset;
			data->totalpercent = 0;

			aToken weaponTok = weapons.GetString();
			for (int j = 1; j <= weaponTok.numtok(' '); j++)
			{
				aToken weaponDataTok = weaponTok.gettok(j, ' ').GetData();

				gzBackpackSoldierPercentData* weaponData = new gzBackpackSoldierPercentData;
				weaponData->powerup = weaponDataTok.gettok(1, ':').GetData();
				data->totalpercent += (weaponData->percent = weaponDataTok.gettok(2, ':').ToLong());
				weaponData->sound = weaponDataTok.gettok(3, ':').GetData();
				data->m_weapons.Add(weaponData);
			}
			this->Backpack.DropData.Add(data);
		}
	}

	this->m_pedWinMinScoreDiff = this->Load_Int("PedWinMinScoreDiff", -1);
	this->Close();

	// Sniper mode doesn't need weapon drops
	switch (gzGameMgr->m_settings->m_GameMode)
	{
		case GAMEMODE_SNIPER:
		case GAMEMODE_500SNIP:
		{
			if (this->Weapons.DropOnDeath)
			{
				stConsole::Out("[Weapon] Error - Weapon Drop on death can not be enabled in %s game mode.\n", gzGameMgr->GetGame()->ModeName());
				this->Weapons.DropOnDeath = false;
			}
		}
	}
}


/******************/
/* Weapon manager */
/******************/
gzWeaponManager *gzWeaponMgr = NULL;
gzWeaponManager::gzWeaponManager()
{
	this->RegisterEvent(EVT_OBJECT_C4_DETONATE, EVT_PRIORITY_LOWEST);
	this->RegisterEvent(EVT_OBJECT_POWERUP_GRANT);

	gzWeaponPack = new gzWeaponPackManager;
	gzBackpackMgr = new gzBackpackManager;

	this->m_settings = new gzSettingsWeaponClass;
	this->m_settings->SetOwner(this);

	gzChatCommand_Reg<gzChat_C4> gzChat_C4_Reg("!c4", MSG_ALL | MSG_TEAM, "C4Count");

#if ISDEV()
	gzChatCommand_Reg<gzChat_WeaponDrop> gzChat_WeaponDrop_Reg("!wdrop");
#endif
}
void gzWeaponManager::Object_C4Detonate(gzEventObjectC4Detonation &evt)
{
	if (evt.m_explodeBy)
	{
		// Spectator is not supposed to let proximity C4 detonate
		if (evt.m_C4->AmmoDef->AmmoType.Get() == 3)
		{
			for (int i = 0; i < evt.m_explodeBy->Observers.Count(); i++)
			{
				if (!stricmp(evt.m_explodeBy->Observers[i]->Get_Name(), "gzSpectator"))
				{
					evt.Skip();
					return;
				}
			}
		}
	}

	// Notify observers for C4 detonation
	for (int i = 0; i < evt.m_C4->Observers.Count(); i++)
		evt.m_C4->Observers[i]->Custom(evt.m_C4, 3101, 0, evt.m_explodeBy);
}


/************/
/* Backpack */
/************/
gzBackpackClass::gzBackpackClass()
{
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_OBJECT_POWERUP_GRANT);

	this->SetChildOwner(gzBackpackMgr);

	this->m_pack				= NULL;
	this->m_canExpire			= (gzWeaponMgr->m_settings->Backpack.Live == -1.0f);
	this->m_backpackId			= 0;
	this->m_effectId			= 0;
	this->m_owner				= NULL;
	this->m_ownerExpireCounter	= -1.0f;
}
void gzBackpackClass::Delete()
{
	if (this->m_pack)
		this->m_pack->SetDeletePending();
	if (gzCommands->Find_Object(this->m_backpackId))
		gzCommands->Find_Object(this->m_backpackId)->Set_Delete_Pending();
}
void gzBackpackClass::Think()
{
	if (this->m_backpackId != 0)
	{
		// Counters
		SubFrameTime(this->m_liveCounter, true);
		SubFrameTime(this->m_ownerExpireCounter, true);

		// Check for backpack owner expiration
		if (this->m_owner && (this->m_ownerExpireCounter < 0.0f || !this->m_owner->IsActive))
			this->m_owner = NULL;

		if (this->m_canExpire)
		{
			if (this->m_liveCounter >= 0.0f)
			{
				if (this->m_liveCounter < (gzWeaponMgr->m_settings->Backpack.Live / 2) && this->m_effectId == 0)
				{
					nc_PhysicalGameObj *effect = nc_ObjectLibraryManager::Create_Object("Spawner Created Special Effect")->As_PhysicalGameObj();
					effect->Set_Position(m_pos);
					this->m_effectId = effect->NetworkID;
				}
			}
			else
			{
#ifdef DEVMSG
				stConsole::Out("Backpack %d expired.\n", this->m_backpackId);
#endif
				this->SetDeletePending();
			}
		}
	}
}
void gzBackpackClass::Object_PowerupGrant(gzEventObjectPowerupGrant &evt)
{
	if (evt.m_powerup->NetworkID == this->m_backpackId)
	{
		if (evt.m_soldier->Player == NULL)
			return;

		if (this->m_owner && this->m_ownerExpireCounter > 0.0f && this->m_owner->PlayerType.Get() == evt.m_soldier->Player->PlayerType.Get() && this->m_owner != evt.m_soldier->Player)
		{
			evt.Skip();
			return;
		}

		if (evt.m_soldier->Vehicle)
		{
			evt.Skip();
			return;
		}

		gzPlayer *gzData = gzPlayerManager::Find(evt.m_soldier->Player);
		if (gzData)
		{

#if VERC(1, 0, 1)
			if (gzData->GetTibCrystal() != NULL)
			{
				int pId = gzData->GetPlayerData()->PlayerId;
				gzData->GetTibCrystal()->SetPickupBlock(gzData->GetPlayerData()->PlayerId, 5.0f);
				nc_GameObjManager::Find_PhysicalGameObj(gzData->GetTibCrystal()->GetCrystalId())->Attach_To_Object_Bone(NULL, "neck");
				gzData->GetTibCrystal()->RestoreWeapons();
				gzData->GetTibCrystal()->Drop();
				PagePlayer(pId, "Ah crap!!! The Tiberium Crystal blocked my weapon backpack.. Now I have to drop it to store my weapons! #%&*!#@$");
			}
#endif

			for (unsigned int i = 0; i < this->m_pack->m_list.Count(); i++)
			{
				bool canGrant = true;
				for (unsigned int j = 0; j < gzWeaponMgr->m_settings->Weapons.Ban.Count(); j++)
				{
					if (gzWeaponMgr->m_settings->Weapons.Ban[j]->Preset == evt.m_soldier->definition->Get_Name())
					{
						for (unsigned int k = 0; k < gzWeaponMgr->m_settings->Weapons.Ban[j]->WeaponList.Count(); k++)
						{
							if (this->m_pack->m_list[i].weapon != NULL)
							{
								if (gzWeaponMgr->m_settings->Weapons.Ban[j]->WeaponList[k] == this->m_pack->m_list[i].weapon->Get_Name())
								{
									canGrant = false;
									break;
								}
							}
						}
					}
				}
				if (canGrant)
				{
					if (this->m_pack->m_list[i].powerup != NULL)
					{
						if (gzData->GetPlayerData()->Owner.Reference)
							this->m_pack->m_list[i].powerup->Grant(gzData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj(), NULL, false);

						// Weapon bag
						if (this->m_pack->m_list[i].powerup->GrantWeaponID != 0)
						{
							nc_DefinitionClass *weaponDef = nc_DefinitionMgrClass::Find_Definition(this->m_pack->m_list[i].powerup->GrantWeaponID, true);
							if (weaponDef)
								gzData->GetWeaponPack()->Add_Weapon(gzStatic_Cast(nc_WeaponDefinitionClass, weaponDef));
						}
					}

					else if (this->m_pack->m_list[i].weapon)
					{
						if (canGrant)
							evt.m_soldier->WeaponBag->Add_Weapon(this->m_pack->m_list[i].weapon, this->m_pack->m_list[i].weapon->ClipSize.Get(), true);
						gzData->GetWeaponPack()->Add_Weapon(this->m_pack->m_list[i].weapon);
					}
				}
			}
			if (this->m_pack->m_backpackData)
			{
				gzCreate_2D_WAV_Sound_Player(evt.m_soldier, this->m_pack->m_backpackData->sound.GetString());
				this->m_pack->m_backpackData = NULL;
			}
		}
		this->SetDeletePending();
	}
}
void gzBackpackClass::ChildObjDeletion(gzBase *obj)
{
	if (this->m_pack == obj)
		this->m_pack = NULL;
}
void gzBackpackClass::Create()
{
	if (this->m_backpackId == 0)
	{
		nc_PhysicalGameObj *Powerup = nc_ObjectLibraryManager::Create_Object("POW_Neuro_Link")->As_PhysicalGameObj();
		Powerup->Set_Position(this->m_pos);
		Powerup->Physics->Set_Model_By_Name("p_backpack");
		this->m_backpackId = Powerup->NetworkID;
		this->m_effectId = 0;
		if (this->m_owner)
			this->m_ownerExpireCounter = gzWeaponMgr->m_settings->Backpack.OwnTime;
	}
}
gzWeaponPackClass *gzBackpackClass::GetWeaponPack()
{
	return this->m_pack;
}
void gzBackpackClass::SetWeaponPack(gzWeaponPackClass *pack)
{
	this->m_pack = pack;
}
nc_cPlayer *gzBackpackClass::GetOwner()
{
	return this->m_owner;
}
void gzBackpackClass::SetOwner(nc_cPlayer *pData)
{
	this->m_owner = pData;
}
void gzBackpackClass::SetCanExpire(bool canExpire)
{
	this->m_canExpire = canExpire;
}
void gzBackpackClass::SetLive(float live)
{
	this->m_liveCounter = live;
}
unsigned int gzBackpackClass::GetBackpackId()
{
	return this->m_backpackId;
}
nc_Vector3 &gzBackpackClass::GetPosition()
{
	return this->m_pos;
}

gzBackpackManager *gzBackpackMgr = NULL;
gzBackpackManager::gzBackpackManager()
{
	this->RegisterEvent(EVT_GAME_LEVEL_ENDED);
}
void gzBackpackManager::Delete()
{
	this->m_backpackList.Clear();
}
void gzBackpackManager::ChildObjDeletion(gzBase *backpack)
{
	for (unsigned int i = 0; i < this->m_backpackList.Count(); i++)
	{
		if (this->m_backpackList[i]->GetId() == backpack->GetId())
		{
#ifdef DEVMSG
			DebugLog("Removed backpack %d from backpackList.", backpack->GetId());
#endif
			this->m_backpackList.Delete(i);
			break;
		}
	}
}
void gzBackpackManager::Level_Ended()
{
	for (unsigned int i = 0; i < this->m_backpackList.Count(); i++)
	{
		if (this->m_backpackList[i]->GetOwner() == NULL)
		{
			this->m_backpackList[i]->SetDeletePending();
			this->m_backpackList.Delete(i);
			i--;
		}
	}
}


/***************/
/* Weapon pack */
/***************/
gzWeaponPackClass::gzWeaponPackClass(bool AddToManager) // No owner
{
	this->m_owner = NULL;
	this->m_backpackData = NULL;
	this->SetChildOwner(gzWeaponPack);
	if (AddToManager)
		gzWeaponPack->Add(this);
}
gzWeaponPackClass::gzWeaponPackClass(gzPlayer *owner) // Owned by gzPlayer, manage by gzPlayerManager
{
	this->m_owner = owner;
	this->m_backpackData = NULL;
}
void gzWeaponPackClass::Delete()
{
	this->m_list.Clear();
	this->m_owner = NULL;
}
void gzWeaponPackClass::Add_Weapon(const char *weapon)
{
	this->Add_Weapon(nc_DefinitionMgrClass::Find_Named_Definition(weapon, true));
}
void gzWeaponPackClass::Add_Weapon(nc_DefinitionClass *def)
{
	if (def)
	{
		gzWeaponPackData data;
		switch (def->Get_Class_ID())
		{
			case 0x3003: // PowerUpGameObjDef
				data.powerup = gzStatic_Cast(nc_PowerUpGameObjDef, def);
				break;

			case 0xB001:
				data.weapon = gzStatic_Cast(nc_WeaponDefinitionClass, def);
				break;
		}

		// Good data?
		if (data.powerup == NULL && data.weapon == NULL)
			return;

		// Check for weapon existence
		if (data.weapon)
		{
			for (unsigned int i = 0; i < this->m_list.Count(); i++)
			{
				if (this->m_list[i].weapon == data.weapon)
					return;
			}
		}

		this->m_list.Add(data);
	}
}
void gzWeaponPackClass::Grant()
{
	if (this->m_owner && this->m_owner->GetPlayerData()->Owner.Reference)
	{
		int weaponBanIndex = -1;
		for (unsigned int i = 0; i < gzWeaponMgr->m_settings->Weapons.Ban.Count(); i++)
		{
			if (gzWeaponMgr->m_settings->Weapons.Ban[i]->Preset == this->m_owner->GetPlayerData()->Owner.Reference->obj->definition->Get_Name())
			{
				weaponBanIndex = i;
				break;
			}
		}
		for (unsigned int i = 0; i < this->m_list.Count(); i++)
		{
			if (this->m_list[i].powerup)
				this->m_list[i].powerup->Grant(this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj(), NULL, true);

			else if (this->m_list[i].weapon)
			{
				bool canGrant = true;
				if (weaponBanIndex > -1)
				{
					for (unsigned int j = 0; j < gzWeaponMgr->m_settings->Weapons.Ban[weaponBanIndex]->WeaponList.Count(); j++)
					{
						if (gzWeaponMgr->m_settings->Weapons.Ban[weaponBanIndex]->WeaponList[j] == this->m_list[i].weapon->Get_Name())
						{
							canGrant = false;
							break;
						}
					}
					if (!canGrant)
						break;
				}
				if (canGrant)
				{
					this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->WeaponBag->Add_Weapon(
						this->m_list[i].weapon,
						this->m_list[i].weapon->ClipSize.Get() + this->m_list[i].weapon->MaxInventoryRounds.Get(),
						true
					);
				}
			}
		}
	}
}
void gzWeaponPackClass::Append(gzWeaponPackClass *pack, bool giveNow)
{
	for (unsigned int i = 0; i < pack->m_list.Count(); i++)
	{
		if (giveNow)
		{
			if (this->m_owner)
			{
				if (this->m_owner->GetPlayerData()->Owner.Reference)
				{
					if (pack->m_list[i].powerup)
						pack->m_list[i].powerup->Grant(this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj(), NULL, true);
					else if (pack->m_list[i].weapon)
						this->m_owner->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->WeaponBag->Add_Weapon(pack->m_list[i].weapon, pack->m_list[i].weapon->ClipSize.Get(), true);
				}
			}
		}
		this->m_list.Add(pack->m_list[i]);
	}
}
void gzWeaponPackClass::Copy(gzWeaponPackClass *pack, bool giveNow)
{
	this->m_list = pack->m_list;
}
int gzWeaponPackClass::GetWeaponsCount()
{
	int count = 0;
	for (unsigned int i = 0; i < this->m_list.Count(); i++)
	{
		if (this->m_list[i].weapon)
			count++;
	}
	return count;
}

gzWeaponPackManager *gzWeaponPack = NULL;
gzWeaponPackManager::gzWeaponPackManager()
{
	this->RegisterEvent(EVT_GAME_LEVEL_ENDED);

	gzChatCommand_Reg<gzChat_WeaponList> gzChat_WeaponList_Reg("!wlist", MSG_ALL | MSG_TEAM);
}
void gzWeaponPackManager::Delete()
{
	for (unsigned int i = 0; i < this->m_packList.Count(); i++)
	{
		this->m_packList[i]->m_list.Clear();
		if (this->m_packList[i]->m_owner == NULL)
		{
			this->m_packList[i]->SetDeletePending();
			this->m_packList.Delete(i);
			i--;
		}
	}
}
void gzWeaponPackManager::ChildObjDeletion(gzBase *pack)
{
	for (unsigned int i = 0; i < this->m_packList.Count(); i++)
	{
		if (this->m_packList[i]->GetId() == pack->GetId())
		{
			this->m_packList.Delete(i);
			break;
		}
	}
}
void gzWeaponPackManager::Level_Ended()
{
	for (unsigned int i = 0; i < this->m_packList.Count(); i++)
	{
		this->m_packList[i]->m_list.Clear();
		if (this->m_packList[i]->m_owner == NULL)
		{
			this->m_packList[i]->SetDeletePending();
			this->m_packList.Delete(i);
			i--;
		}
	}
}
void gzWeaponPackManager::Add(gzWeaponPackClass *pack)
{
	pack->m_list.Clear();
	pack->Id = this->nextPackId++;
	this->m_packList.Add(pack);
}


/***************/
/* Weapon drop */
/***************/
gzWeaponDropClass::gzWeaponDropClass()
{
	this->RegisterEvent(EVT_GAME_LEVEL_ENDED);
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_PLAYER_LEFT);
	this->RegisterEvent(EVT_OBJECT_POWERUP_GRANT);

	this->m_dropDelayCounter = gzWeaponMgr->m_settings->Weapons.DropDelay;
	gzWeaponDropMgr->m_list.Add(this);
}
void gzWeaponDropClass::Delete()
{
	gzWeaponDropMgr->m_list.Delete(gzWeaponDropMgr->m_list.Index(this));
	this->gzBackpackClass::Delete();
}
void gzWeaponDropClass::Level_Ended()
{
	this->SetDeletePending();
}
void gzWeaponDropClass::Think()
{
	if (this->GetOwner())
	{
		if (this->m_dropDelayCounter > 0.0f)
		{
			SubFrameTime(this->m_dropDelayCounter, true);
			if (this->GetBackpackId() == 0 && (!this->GetOwner()->IsActive || !this->GetOwner()->Owner.Reference || this->GetOwner()->Owner.Reference->obj->NetworkID != this->m_soldierId))
			{
				PagePlayer(this->GetOwner()->PlayerId, "Your Weapon Drop has been halted.");
				this->Halt();
			}
		}
		else
		{
			gzPlayer *gzData = gzPlayerManager::Find(this->GetOwner());
			nc_SoldierGameObj *soldier = this->GetOwner()->Owner.Reference->obj->As_SoldierGameObj();
			unsigned long defWeaponId = gzStatic_Cast(nc_ArmedGameObjDef, this->GetOwner()->Owner.Reference->obj->definition)->WeaponDefID;
			for (unsigned int i = 0; i < this->GetWeaponPack()->m_list.Count(); i++)
			{
				if (this->GetWeaponPack()->m_list[i].weapon != NULL)
				{
					for (int j = 1; j < soldier->WeaponBag->Vector.Count(); j++)
					{
						if (soldier->WeaponBag->Vector[j]->WeaponDef->Get_ID() == this->GetWeaponPack()->m_list[i].weapon->Get_ID())
						{
							// Default weapon can not be removed
							if (soldier->WeaponBag->Vector[j]->WeaponDef->Get_ID() == defWeaponId)
								continue;

							if (gzData && gzData->GetWeaponPack())
							{
								for (unsigned int k = 0; k < gzData->GetWeaponPack()->m_list.Count(); k++)
								{
									if (soldier->WeaponBag->Vector[j]->WeaponDef == gzData->GetWeaponPack()->m_list[k].weapon)
									{
										gzData->GetWeaponPack()->m_list.Delete(k);
										break;
									}
								}
							}
							soldier->WeaponBag->Remove_Weapon(j);
						}
					}
				}
			}
			this->Create();
			PagePlayer(this->GetOwner()->PlayerId, "Your request has been completed. The backpack will NOT expire until a player picks it up.");
			this->SetOwner(NULL);
		}
	}
}
void gzWeaponDropClass::Player_Left(gzEventPlayerBase &evt)
{
	if (evt.GetPlayer() == this->GetOwner())
	{
		if (this->m_dropDelayCounter > 0.0f)
			this->SetDeletePending();
	}
}
void gzWeaponDropClass::Object_PowerupGrant(gzEventObjectPowerupGrant &evt)
{
	if (evt.m_powerup->NetworkID == this->GetBackpackId())
	{
#if !ISDEV()
		// The requester himself can not pick it up!
		if (evt.m_soldier->Player == this->GetOwner())
		{
			evt.Skip();
			return;
		}
#endif
		
		// Real player?
		gzPlayer *gzData = gzPlayerManager::Find(evt.m_soldier->Player);
		if (!gzData)
		{
			evt.Skip();
			return;
		}

		gzData->GetWeaponPack()->Append(this->GetWeaponPack(), true);
		this->GetWeaponPack()->SetDeletePending();
		evt.Skip();
		this->SetDeletePending();
	}
}
void gzWeaponDropClass::Init(gzPlayer *owner)
{
	this->SetOwner(owner->GetPlayerData());
}
bool gzWeaponDropClass::Halt()
{
	if (this->m_dropDelayCounter < 0.0f)
		return false;
	this->SetDeletePending();
	return true;
}

gzWeaponDropManager *gzWeaponDropMgr = NULL;
void gzWeaponDropManager::Delete()
{
	for (unsigned int i = 0; i < this->m_list.Count(); i++)
		this->m_list[i]->SetDeletePending();
	this->m_list.Clear();
}
gzWeaponDropClass *gzWeaponDropManager::Find(nc_cPlayer *owner)
{
	for (unsigned int i = 0; i < this->m_list.Count(); i++)
	{
		if (this->m_list[i]->GetOwner() == owner)
			return this->m_list[i];
	}
	return NULL;
}

class gzChat_WeaponDrop : public gzChatCommandBase {
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		// Game mode check
		switch (gzGameMgr->m_settings->m_GameMode)
		{
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
				PagePlayer(sender, "Weapon commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
				return;
		}

		if (!*msg)
		{
			PagePlayer(sender, "Usage: !wdrop [all/item number] - For the list of weapons in your weapon bag, type !wlist.");
			return;
		}
		
		gzPlayer *gzData = gzPlayerManager::Find(sender);
		if (!gzData)
			return;

		aToken str(msg);

		if (!gzData->GetPlayerData()->Owner.Reference || gzData->GetPlayerData()->Owner.Reference->obj->As_DamageableGameObj()->Defense.Health.Get() <= 0.0f)
			PagePlayer(sender, "You can not start Weapon Drop while you're dead.");
		else if (gzData->GetWeaponPack()->m_list.Count() == 0)
			PagePlayer(sender, "Your weapon bag is empty.");
		else if (gzData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
			PagePlayer(sender, "You can not start Weapon Drop while you're in vehicle.");
		else if (str.gettok(1, ' ').GetData() == "halt")
		{
			gzWeaponDropClass *drop = gzWeaponDropMgr->Find(gzData->GetPlayerData());
			if (!drop)
				PagePlayer(sender, "You don't have any Weapon Drop in progress.");
			else if (drop->Halt())
			{
				drop->GetWeaponPack()->m_owner = gzData;
				PagePlayer(sender, "Your Weapon Drop has been halted.");
			}
		}
		else
		{
			gzWeaponDropClass *drop = gzWeaponDropMgr->Find(gzData->GetPlayerData());
			if (drop)
				PagePlayer(sender, "You can only have one active Weapon Drop request in progress.");
			else if (str.gettok(1, ' ').GetData() == "all")
			{
				gzWeaponDropClass *newDrop = new gzWeaponDropClass;
				gzData->GetPlayerData()->Owner.Reference->obj->Get_Position(&newDrop->GetPosition());
				newDrop->m_soldierId = gzData->GetPlayerData()->Owner.Reference->obj->NetworkID;
				newDrop->SetWeaponPack(new gzWeaponPackClass);
				newDrop->GetWeaponPack()->Copy(gzData->GetWeaponPack());
				newDrop->Init(gzData);
				PagePlayer(sender, "Your Weapon Drop request(All) has been approved. The backpack will appear in %.0f seconds at where you entered this request. You can type \"!wdrop halt\" to halt this request.",
					gzWeaponMgr->m_settings->Weapons.DropDelay
				);
			}
			else
			{
				unsigned int id = str.gettok(1, ' ').ToLong() - 1;
				if (id < 0)
					PagePlayer(sender, "Error: Invalid weapon ID. For the list of weapons, type \"!wlist\".");
				else if (id >= gzData->GetWeaponPack()->m_list.Count() || gzData->GetWeaponPack()->m_list[id].weapon == NULL)
					PagePlayer(sender, "Error: Weapon #%d does not exist in your weapon bag.", id + 1);
				else
				{
					gzWeaponDropClass *newDrop = new gzWeaponDropClass;
					gzData->GetPlayerData()->Owner.Reference->obj->Get_Position(&newDrop->GetPosition());
					newDrop->m_soldierId = gzData->GetPlayerData()->Owner.Reference->obj->NetworkID;
					newDrop->SetWeaponPack(new gzWeaponPackClass);
					newDrop->GetWeaponPack()->m_list.Add(gzData->GetWeaponPack()->m_list[id]);
					newDrop->Init(gzData);
					PagePlayer(sender, "Your Weapon Drop request(%s) has been approved. The backpack will appear in %.0f seconds at where you entered this request. You can type \"!wdrop halt\" to halt this request.",
						gzTranslator->Get(gzData->GetWeaponPack()->m_list[id].weapon->Get_Name()),
						gzWeaponMgr->m_settings->Weapons.DropDelay
					);
				}
			}
		}
	}
};


/*****************/
/* Chat commands */
/*****************/
class gzChat_WeaponList : public gzChatCommandBase {
public:
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		// Game mode check
		switch (gzGameMgr->m_settings->m_GameMode)
		{
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
				PagePlayer(sender, "Weapon commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		gzPlayer *gzData = gzPlayerManager::Find(sender);
		if (!gzData)
			return;

		if (!gzData->GetWeaponPack() || gzData->GetWeaponPack()->m_list.Count() == 0)
		{
			PagePlayer(sender, "Your Weapon Bag is empty.", sender);
			return;
		}

		aString Msg;
		for (unsigned int i = 0; i < gzData->GetWeaponPack()->m_list.Count(); i++)
		{
			if (gzData->GetWeaponPack()->m_list[i].weapon)
			{
				if (Msg.Len() > 0)
					Msg += " - ";
				Msg += aString::Format("(%d)%s", i + 1, gzTranslator->Get(gzData->GetWeaponPack()->m_list[i].weapon->Get_Name()));
				if (Msg.Len() > 200)
				{
					PagePlayer(sender, Msg.GetString());
					Msg.Clear();
				}
			}
		}
		if (Msg.GetLength() > 0)
			PagePlayer(sender, Msg.GetString());
	}
};


/**********/
/* Beacon */
/**********/
void gzObserverBeacon::Created(nc_ScriptableGameObj *obj)
{
	this->m_Beacon = (nc_BeaconGameObj *)obj;
	nc_BeaconGameObjDef *Def = (nc_BeaconGameObjDef *)obj->definition;

	if (!this->m_Beacon->Player)
	{
		this->Destroy_Script();
		return;
	}

	// ThinkTriggered is not set to true, force it to true
	obj->ThinkTriggered = true;
	
	// Limit check
	if (this->m_Beacon->Owner.Reference)
	{
		int bCount = 0;
		for (nc_GenericSLNode<nc_BaseGameObj> *objList = nc_GameObjManager::GameObjList->HeadNode; objList != NULL; objList = objList->NodeNext)
		{
			if (objList->NodeData->definition->Get_Class_ID() == 0x3016) // Beacon
			{
				nc_BeaconGameObj *Beacon = (nc_BeaconGameObj *)objList->NodeData;
				if (Beacon->Owner.Reference && Beacon->Owner.Reference->obj == this->m_Beacon->Owner.Reference->obj)
					bCount++;
			}
		}
		if (bCount > gzWeaponMgr->m_settings->m_BeaconLimit)
		{
			obj->Start_Observer_Timer(this->ID, 0.0001f, 3084);
			return;
		}
	}

	this->m_score = this->m_Beacon->Defense.DeathPoints.Get();
	this->m_Beacon->Defense.DeathPoints = 0.0f;
	for (int i = 1; i <= 127; i++)
		this->m_playerDamage[i] = 0.0f;

	obj->Start_Observer_Timer(this->ID, Def->ArmTime, 2000);
	obj->Start_Observer_Timer(this->ID, Def->ArmTime + Def->BroadcastToAllTime, 2001);
	obj->Start_Observer_Timer(this->ID, Def->ArmTime + Def->PreDetonateCinematicDelay, 2002);
	obj->Start_Observer_Timer(this->ID, Def->ArmTime + Def->DetonateTime, 2003);
}
void gzObserverBeacon::Custom(nc_ScriptableGameObj *obj, int message, int param, nc_ScriptableGameObj *sender)
{
	switch (message)
	{
		case 10001:
			this->m_playerDamage[param] = 0.0f;
			break;
	}
}
void gzObserverBeacon::Damaged(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *damager, float damage)
{
	if (damager && damager->As_SmartGameObj() && damager->As_SmartGameObj()->Player && damage > 0.0f)
		this->m_playerDamage[damager->As_SmartGameObj()->Player->PlayerId] += damage;
}
void gzObserverBeacon::Killed(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter)
{
	float maxDamage = 0.0f;
	int maxDamager = -1;
	for (int i = 1; i <= 127; i++)
	{
		if (this->m_playerDamage[i] > maxDamage)
		{
			maxDamage = this->m_playerDamage[i];
			maxDamager = i;
		}
	}
	if (maxDamager != -1)
	{
		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(maxDamager);
		if (pData)
		{
			if (pData->Owner.Reference)
			{
				if (pData->Owner.Reference->obj->As_DamageableGameObj()->PlayerType != this->m_Beacon->PlayerType)
					pData->Increment_Score(this->m_score);
				else
					pData->Increment_Score(-this->m_score);
			}
			else
			{
				if (pData->PlayerType.Get() != this->m_Beacon->PlayerType)
					pData->Increment_Score(this->m_score);
				else
					pData->Increment_Score(-this->m_score);
			}
		}
	}

	if (shooter->As_SoldierGameObj() && shooter->As_SoldierGameObj()->Player && this->m_Beacon->Player)
	{
		gzLogger("_BEACON", "%ls disarmed %ls's %s",
			shooter->As_SoldierGameObj()->Player->PlayerName.m_Buffer,
			this->m_Beacon->Player->PlayerName.m_Buffer,
			gzTranslator->Get(obj->definition->Get_Name())
		);
	}
}
void gzObserverBeacon::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	aString notify;
	switch (number)
	{
		case 2000: // Deployed
			if (this->m_Beacon->Get_Owner() && this->m_Beacon->Get_Owner()->Player)
			{
				gzLogger("_BEACON", "%ls deployed %s %s",
					this->m_Beacon->Get_Owner()->Player->PlayerName.m_Buffer,
					A_Or_An(gzTranslator->Get(obj)),
					gzTranslator->Get(obj)
				);
				if (nc_CollisionMath::Overlap_Test(nc_BaseControllerClass::Find_Base(this->m_Beacon->PlayerType ^ 1)->BeaconZone, gzCommands->Get_Position(obj)) == 2)
				{
					notify.Printf("ATTENTION: %s %s has been placed on the enemy Beacon Pedestal!",
						A_Or_An(gzTranslator->Get(obj), true),
						gzTranslator->Get(obj)
					);
				}
				else
				{
					struct hitInfo {
						nc_BuildingGameObj *Building;
						float Damage;
					};
					nc_ExplosionDefinitionClass *Explosion = (nc_ExplosionDefinitionClass *)nc_DefinitionMgrClass::Find_Definition(((nc_BeaconGameObjDef *)this->m_Beacon->definition)->ExplosionObj, true);
					int hitCount = 0;
					aVector<hitInfo> hitList;
					for (nc_GenericSLNode<nc_BuildingGameObj> *bList = nc_GameObjManager::BuildingGameObjList->HeadNode; bList != NULL; bList = bList->NodeNext)
					{
						if (bList->NodeData != NULL && bList->NodeData->Destroyed == false && ((nc_CombatManager::FriendlyFirePermitted == false && bList->NodeData->PlayerType != this->m_Beacon->Get_Owner()->PlayerType) || nc_CombatManager::FriendlyFirePermitted == true))
						{
							float PolyDistance = 0.0f;
							nc_Vector3 BeaconPos;
							this->m_Beacon->Get_Position(&BeaconPos);
							bList->NodeData->Find_Closest_Poly(BeaconPos,&PolyDistance);
							if (PolyDistance <= pow(Explosion->DamageRadius,2))
							{
								float Damage = 0.0f,
									RawDamageRate = 0.0f,
									PolyCircle = sqrt(PolyDistance);
								if ((PolyCircle / Explosion->DamageRadius) < 0.0f)
									RawDamageRate = PolyCircle / Explosion->DamageRadius + 1.0f;
								else
								{
									if ((PolyCircle / Explosion->DamageRadius) < 1.0f)
										RawDamageRate = PolyCircle / Explosion->DamageRadius;
									else
										RawDamageRate = 1.0f;
								}
								RawDamageRate = 1.0f - RawDamageRate;
								Damage = (RawDamageRate * Explosion->DamageStrength) * nc_ArmorWarheadManager::Get_Damage_Multiplier(bList->NodeData->Defense.Skin.Get(),Explosion->Warhead);
								hitInfo hi;
								hi.Building = bList->NodeData;
								hi.Damage = Damage;
								hitList.Add(hi);
								hitCount++;
							}
						}
					}
					if (hitCount == 0)
					{
						notify.Printf("ATTENTION: %s %s has been placed and will NOT damage any buildings.",
							A_Or_An(gzTranslator->Get(obj), true),
							gzTranslator->Get(obj)
						);
					}
					else
					{
						notify.Printf("ATTENTION: %s %s has been placed. ",
							A_Or_An(gzTranslator->Get(obj), true),
							gzTranslator->Get(obj)
						);
						if (hitCount == 1)
							notify += "The building that will be damaged is: ";
						else
							notify += "The buildings that will be damaged are: ";
						for (unsigned int i = 0; i < hitList.Count(); i++)
							notify += aString::Format("%s(%.0f), ", gzTranslator->Get(hitList[i].Building->definition->Get_Name()), hitList[i].Damage);
						notify.Resize(notify.GetLength() - 2);
						notify += (".");
					}
				}
				nc_cScTextObj *ScTextObj = nc_cScTextObj::Create();
				ScTextObj->Set_Object_Dirty_Bit(nc_DB_CREATION, false);
				ScTextObj->SenderId = this->m_Beacon->Get_Owner()->Player->PlayerId;
				ScTextObj->Type = 1;
				ScTextObj->IsPopup = false;
				ScTextObj->Message.Convert_From(notify.GetString());
				for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList != NULL; pList = pList->NodeNext)
				{
					if (pList->NodeData->IsActive)
					{
						if (pList->NodeData->PlayerType.Get() == this->m_Beacon->Get_Owner()->PlayerType)
						{
							ScTextObj->Set_Object_Dirty_Bit(pList->NodeData->PlayerId, nc_DB_CREATION, true);
							nc_cNetwork::Send_Object_Update(ScTextObj, pList->NodeData->PlayerId);
						}
					}
				}
				ScTextObj->Set_Delete_Pending();
			}
			break;

		case 2001: // Warning message
			for (aListNode<gzTranslateItem>* list = gzTranslator->m_beaconWarningList.GetHead(); list != NULL; list = list->GetNext())
			{
				if (list->GetData()->preset == obj->definition->Get_Name())
				{
					gzLogger("_BEACON", list->GetData()->translated.GetString());
					break;
				}
			}
			break;

		case 2002: // Initial
			for (aListNode<gzTranslateItem>* list = gzTranslator->m_beaconStrikeList.GetHead(); list != NULL; list = list->GetNext())
			{
				if (list->GetData()->preset == obj->definition->Get_Name())
				{
					gzLogger("_BEACON", "%s initiated",	list->GetData()->translated.GetString());
					break;
				}
			}
			break;

		case 2003: // Detonate
			gzLogger("_BEACON", "%s has detonated", gzTranslator->Get(obj));
			break;

		case 3084: // Add beacon to the weaponbag of owner because limit reached
			this->m_Beacon->Owner.Reference->obj->As_SoldierGameObj()->WeaponBag->Add_Weapon(this->m_Beacon->WeaponDef, 1, false);
			obj->Set_Delete_Pending();
			break;
	}
}
nc_ScriptRegistrant<gzObserverBeacon> gzObserverBeacon_Registrant("gzObserverBeacon", "");


/******/
/* C4 */
/******/
void gzObserverC4::Created(nc_ScriptableGameObj *obj)
{
	this->m_C4 = (nc_C4GameObj *)obj;
	int c4Mode = this->m_C4->AmmoDef->AmmoType.Get();

	// Is it really C4?
	if (c4Mode == 0)
	{
		this->Destroy_Script();
		return;
	}

	// Limit control
	int count = 0, ownCount = 0;
	for (nc_GenericSLNode<nc_BaseGameObj> *objList = nc_GameObjManager::GameObjList->HeadNode; objList != NULL; objList = objList->NodeNext)
	{
		if (objList->NodeData && objList->NodeData->definition->Get_Class_ID() == 0x3006 && objList->NodeData->As_PhysicalGameObj()->PlayerType == this->m_C4->PlayerType)
		{
			nc_C4GameObj *C4 = (nc_C4GameObj *)objList->NodeData;
			int mode = C4->AmmoDef->AmmoType.Get();

			if (c4Mode == mode)
			{
				// Stealth proximity crate
				bool listed = false;
				if (c4Mode == 3)
				{
					for (unsigned int i = 0; i < gzCrateMgr->m_data->StealthMine.Count(); i++)
					{
						for (unsigned int j = 0; j < gzCrateMgr->m_data->StealthMine[i]->activeList.Count(); j++)
						{
							if (gzCrateMgr->m_data->StealthMine[i]->activeList[j] == C4->NetworkID)
							{
								listed = true;
								goto procCount;
							}
						}
					}
				}
procCount:
				if (!listed)
					count++;

				// Personal count
				if (this->m_C4->Player == C4->Player)
					ownCount++;

				// Destroy C4
				if (gzWeaponMgr->m_settings->C4.TeamLimit[this->m_C4->PlayerType][c4Mode - 1] > -1 && count > gzWeaponMgr->m_settings->C4.TeamLimit[this->m_C4->PlayerType][c4Mode - 1])
					C4->Defuse();
			}
		}
	}
	if (gzWeaponMgr->m_settings->C4.Limit[c4Mode - 1] > -1 && ownCount > gzWeaponMgr->m_settings->C4.Limit[c4Mode - 1])
	{
		this->m_C4->Set_Delete_Pending();
		return;
	}

	// Set base
	this->m_base = nc_BaseControllerClass::Find_Base(this->m_C4->PlayerType);
	if (!this->m_base)
	{
		this->Destroy_Script();
		return;
	}

	// Remove default score and use my own score distribution
	this->m_score = this->m_C4->Defense.DeathPoints.Get();
	this->m_C4->Defense.DeathPoints = 0.0f;

	// Flaming check timer
	if (!gzWeaponMgr->m_settings->C4.CanFlaming && !nc_CombatManager::FriendlyFirePermitted)
		obj->Start_Observer_Timer(this->GetID(), 1.0f, 1900);

	// Log
	if (gzWeaponMgr->m_settings->C4.Output[this->m_C4->AmmoDef->AmmoType.Get() - 1][0])
	{
		const char *c4Name[] = { "Remote", "Timed", "Proximity" };
		gzLogger("_C4", "%s C4 creation (Planter: %ls)",
			c4Name[this->m_C4->AmmoDef->AmmoType.Get() - 1],
			this->m_C4->Player ? this->m_C4->Player->PlayerName.m_Buffer : L"(null)"
		);
	}
}
void gzObserverC4::Custom(nc_ScriptableGameObj *obj, int message, int param, nc_ScriptableGameObj *sender)
{
	switch (message)
	{
		case 3101:
			if (gzWeaponMgr->m_settings->C4.Output[this->m_C4->AmmoDef->AmmoType.Get() - 1][2])
			{
				const char *c4Name[] = { "Remote", "Timed", "Proximity" };
				gzLogger("_C4", "%s C4 explosion (Planter: %ls)",
					c4Name[this->m_C4->AmmoDef->AmmoType.Get() - 1],
					this->m_C4->Player ? this->m_C4->Player->PlayerName.m_Buffer : L"(null)"
				);
			}
			break;
		case 10001:
			this->m_playerDamage[param] = 0.0f;
			break;
	}
}
void gzObserverC4::Damaged(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *damager, float damage)
{
	if (damager && damager->As_SmartGameObj() && damager->As_SmartGameObj()->Player && damage > 0.0f)
		this->m_playerDamage[damager->As_SmartGameObj()->Player->PlayerId] += damage;
}
void gzObserverC4::Killed(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter)
{
	float maxDamage = 0.0f;
	int maxDamager = -1;
	for (int i = 1; i <= 127; i++)
	{
		if (this->m_playerDamage[i] > maxDamage)
		{
			maxDamage = this->m_playerDamage[i];
			maxDamager = i;
		}
	}
	if (maxDamager != -1)
	{
		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(maxDamager);
		if (pData)
		{
			if (pData->Owner.Reference)
			{
				if (pData->Owner.Reference->obj->As_DamageableGameObj()->PlayerType != this->m_C4->PlayerType)
					pData->Increment_Score(this->m_score);
				else
					pData->Increment_Score(-this->m_score);
			}
			else
			{
				if (pData->PlayerType.Get() != this->m_C4->PlayerType)
					pData->Increment_Score(this->m_score);
				else
					pData->Increment_Score(-this->m_score);
			}
		}
	}

	if (gzWeaponMgr->m_settings->C4.Output[this->m_C4->AmmoDef->AmmoType.Get() - 1][1])
	{
		const char *c4Name[] = { "Remote", "Timed", "Proximity" };
		gzLogger("_C4", "%ls disarmed %ls's %s C4",
			shooter && shooter->As_SmartGameObj() && shooter->As_SmartGameObj()->Player ? shooter->As_SmartGameObj()->Player->PlayerName.m_Buffer : L"NotPlayer",
			this->m_C4->Player ? this->m_C4->Player->PlayerName.m_Buffer : L"NotPlayer",
			c4Name[this->m_C4->AmmoDef->AmmoType.Get() - 1]
		);
	}
}
void gzObserverC4::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	switch (number)
	{
		case 1900: // Checks for friendly unit attachment
			if (this->m_C4->Attached.Reference)
			{
				// Buildings is in exception
				if (this->m_C4->Attached.Reference->obj->As_BuildingGameObj())
					break;

				// Harvester is in exception
				nc_RefineryGameObj *refinery = (this->m_base->Find_Building(nc_REFINERY) ? this->m_base->Find_Building(nc_REFINERY)->As_RefineryGameObj() : NULL);
				if (refinery)
				{
					if (refinery->Harvester && refinery->Harvester->Vehicle->NetworkID == this->m_C4->Attached.Reference->obj->NetworkID)
						break;
				}

				if (this->m_C4->Get_Player_Type() == this->m_C4->Attached.Reference->obj->As_DamageableGameObj()->Get_Player_Type())
				{
					if (this->m_C4->Attached.Reference->obj->As_SoldierGameObj() || this->m_C4->Attached.Reference->obj->As_VehicleGameObj())
						this->m_C4->Defuse();
				}
			}
			obj->Start_Observer_Timer(this->GetID(), 1.0f, number);
			break;
	}
}
nc_ScriptRegistrant<gzObserverC4> gzObserverC4_Registrant("gzObserverC4", "");

class gzChat_C4 : public gzChatCommandBase {
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		// Game mode check
		switch (gzGameMgr->m_settings->m_GameMode)
		{
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
				PagePlayer(sender, "Weapon commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(sender);
		if (!pData)
			return;
		int count[] = { 0, 0, 0 };
		int Team = pData->PlayerType.Get();

		// Team Commander stuff
		struct C4_Info {
			nc_BuildingGameObj *Building;
			int Proximity;
		};
		bool haveMines = false;
		aVector<C4_Info> BuildingList;
		for (nc_GenericSLNode<nc_BuildingGameObj> *Building = nc_GameObjManager::BuildingGameObjList->HeadNode; Building != NULL; Building = Building->NodeNext)
		{
			if (Building->NodeData->PlayerType == Team)
			{
				C4_Info ci;
				ci.Building = Building->NodeData;
				ci.Proximity = 0;
				BuildingList.Add(ci);
			}
		}

		for (nc_GenericSLNode<nc_BaseGameObj> *obj = nc_GameObjManager::GameObjList->HeadNode; obj != NULL; obj = obj->NodeNext)
		{
			if (obj->NodeData && obj->NodeData->definition->Get_Class_ID() == 0x3006 && obj->NodeData->As_PhysicalGameObj()->PlayerType == Team)
			{
				nc_C4GameObj *C4 = (nc_C4GameObj *)obj->NodeData;
				int Mode = C4->AmmoDef->AmmoType.Get();

				// Stealth proximity crate
				bool listed = false;
				for (unsigned int i = 0; i < gzCrateMgr->m_data->StealthMine.Count(); i++)
				{
					for (unsigned int j = 0; j < gzCrateMgr->m_data->StealthMine[i]->activeList.Count(); j++)
					{
						if (gzCrateMgr->m_data->StealthMine[i]->activeList[j] == C4->NetworkID)
						{
							listed = true;
							goto procCount;
						}
					}
				}
procCount:
				if (!listed)
					count[Mode - 1]++;

				if (Mode == 3) // Proximity
				{
					nc_ExplosionDefinitionClass *Explosion = (nc_ExplosionDefinitionClass *)nc_DefinitionMgrClass::Find_Definition(C4->AmmoDef->ExplosionDefID,true);
					nc_Vector3 C4pos;
					C4->Get_Position(&C4pos);
					for (unsigned int i = 0; i < BuildingList.Count(); i++)
					{
						float PolyDistance = 9999.0f;
						BuildingList[i].Building->Find_Closest_Poly(C4pos,&PolyDistance);
						if (PolyDistance <= (Explosion->DamageRadius * Explosion->DamageRadius))
						{
							haveMines = true;
							BuildingList[i].Proximity++;
							break;
						}
					}
				}
			}
		}
		PagePlayer(sender, "Remote: %d/%d - Timed: %d/%d - Proximity: %d/%d",
			count[0],
			gzWeaponMgr->m_settings->C4.TeamLimit[Team][0],
			count[1],
			gzWeaponMgr->m_settings->C4.TeamLimit[Team][1],
			count[2],
			gzWeaponMgr->m_settings->C4.TeamLimit[Team][2]
		);

		// Team Commander
		if (gzTeamCommander->IsCommander(pData->PlayerType.Get(), pData->PlayerId))
		{
			if (haveMines)
			{
				aString msg = "[Commander]: Mined buildings: ";
				for (unsigned int i = 0; i < BuildingList.Count(); i++)
					msg += aString::Format("%s(%d), ", gzTranslator->Get(BuildingList[i].Building->definition->Get_Name()), BuildingList[i].Proximity);
				msg.Resize(msg.GetLength() - 2);
				msg += ".";
				PagePlayer(sender, msg.GetString());
			}
			else
				PagePlayer(sender, "[Commander]: Your team doesn't have any mined buildings.");
		}
	};
};


/********************/
/* Console commands */
/********************/
class ClearWeaponConsoleFunction : public gzConsoleCommand {
public:
	char *Get_Name(void)
	{
		return "clearweapon";
	}
	char *Get_Alias()
	{
		return "cwep";
	}
	char *Get_Help(void)
	{
		return "CLEARWEAPON <player> - Remove all weapons from <player>.";
	}
	void Activate(char *text)
	{
		if (!*text)
			return;
		aToken tok(text);
		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(tok.gettok(1, ' ').ToLong());
		if (!pData)
		{
			stConsole::Out("Player #%d not found\n", tok.gettok(1, ' ').ToLong());
			return;
		}
		if (!pData->Owner.Reference)
			return;
		pData->Owner.Reference->obj->As_SoldierGameObj()->WeaponBag->Clear_Weapons();
	}
};
ClearWeaponConsoleFunction clearweapon;

class GiveWeaponConsoleFunction : public gzConsoleCommand {
public:
	char *Get_Name(void) {
		return "giveweapon";
	}
	char *Get_Help(void) {
		return "GIVEWEAPON <player> <weapon> - Give <weapon> to <player>.";
	}
	void Activate(char *text) {
		if (!*text)
			return;

		aToken tok(text);
		if (tok.numtok(' ') != 2)
			return;

		// Real player and has soldier object?
		gzPlayer *gzData = gzPlayerManager::Find(tok.gettok(1, ' ').ToLong());
		if (!gzData || !gzData->GetPlayerData()->Owner.Reference)
			return;

		// Definition exist?
		nc_DefinitionClass *def = nc_DefinitionMgrClass::Find_Named_Definition(tok.gettok(2, ' ').GetData().GetString(), true);
		if (!def)
		{
			stConsole::Out("Invalid preset: %s\n", tok.gettok(2, ' ').GetData().GetString());
			return;
		}

		// Power-up contains weapon?
		if (def->Get_Class_ID() == 0x3003)
		{
			if (gzStatic_Cast(nc_PowerUpGameObjDef, def)->GrantWeaponID == 0)
			{
				stConsole::Out("Powerup \"%s\" does not contain a weapon.", tok.gettok(2, ' ').GetData().GetString());
				return;
			}
			
			def = nc_DefinitionMgrClass::Find_Definition(gzStatic_Cast(nc_PowerUpGameObjDef, def)->GrantWeaponID, true);
		}

		// Real actions here.
		gzData->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->WeaponBag->Add_Weapon(gzStatic_Cast(nc_WeaponDefinitionClass, def), gzStatic_Cast(nc_WeaponDefinitionClass, def)->ClipSize.Get(), true);
		gzData->GetWeaponPack()->Add_Weapon(def);
	}
};
GiveWeaponConsoleFunction givewep;


/********/
/* Misc */
/********/
bool IsBeaconPedApproved(int attemptTeam)
{
	if (gzWeaponMgr->m_settings->m_pedWinMinScoreDiff != -1)
	{
		nc_cTeam *Team[2] = {
			nc_cTeamManager::Find_Team(0),
			nc_cTeamManager::Find_Team(1),
		};
		if ((Team[attemptTeam ^ 1]->Score - Team[attemptTeam]->Score) >= (float)gzWeaponMgr->m_settings->m_pedWinMinScoreDiff)
			return true;
	}
	nc_BaseControllerClass *Base[2] = {
		nc_BaseControllerClass::Find_Base(0),
		nc_BaseControllerClass::Find_Base(1),
	};
	int buildingCount[2] = { 0, 0 };
	for (int i = 0; i <= 1; i++)
	{
		for (int j = 0; j < Base[i]->BuildingList.Count(); j++)
		{
			if (Base[i]->BuildingList[j]->Destroyed == false)
				buildingCount[i]++;
		}
	}
	if (buildingCount[attemptTeam ^ 1] > buildingCount[attemptTeam])
		return true;
	return false;
}
