#include "stinc.h"
#include "stmisc.h"
#include "stgame.h"
#include "stplayer.h"
#include "stweapon.h"
#include "sttibcrystal.h"

void gzSettingsTibCrystal::Delete()
{
	this->m_modelList.Clear();
	this->m_posList.Clear();
}
void gzSettingsTibCrystal::Load()
{
	this->Delete();

	this->m_cfg = new aTextConfig("TibCrystal.ini");
	if (!this->m_cfg->IsLoaded())
		stConsole::Out("[Error] TibCrystal.ini can not be loaded or not found.\n");

	this->m_enable					= this->Load_Bool("Enable", false);
	this->m_successAmount			= this->Load_Float("SuccessAmount", 0.0f);
	this->m_successCarrierAmount	= this->Load_Float("SuccessCarrierAmount", 0.0f);

#if ISDEV()
	this->m_spawnMin				= 5;
	this->m_spawnMax				= 5;
#else
	this->m_spawnMin				= this->Load_Int("MinimumSpawn", 5) * 60;
	this->m_spawnMax				= this->Load_Int("MaximumSpawn", 10) * 60;
#endif

	this->m_growMin					= this->Load_Int("MinimumGrow", 60);
	this->m_growMax					= this->Load_Int("MaximumGrow", 180);
	this->m_liveTime				= this->Load_Float("LiveTime", 600.0f) * 60.0f;
	this->m_dropCountMax			= this->Load_Int("MaximumDropCount", 3);
	this->m_dropLive				= this->Load_Float("DropLive", 30.0f);
	this->m_tibPoisonDamage			= this->Load_Float("TibPoisonDamage", 1.0f);
	this->m_tibPoisonDamageTime		= this->Load_Float("TibPoisonTime", 2.0f);

	if (this->m_successAmount <= 0.0f && this->m_successCarrierAmount <= 0.0f)
	{
		this->m_enable = false;
		stConsole::Out("Tiberium Crystal Harvest feature has been disabled because no amount will be given upon success.\n");
	}

	if (this->m_enable)
	{
		for (int id = 1; ; id++)
		{
			nc_Vector3 pos;
			aString posName[3];
			posName[0].Printf("PosX_%d",id);
			posName[1].Printf("PosY_%d",id);
			posName[2].Printf("PosZ_%d",id);
			pos.X = this->Load_Float(posName[0].GetString(), 999.0f, true, false);
			pos.Y = this->Load_Float(posName[1].GetString(), 999.0f, true, false);
			pos.Z = this->Load_Float(posName[2].GetString(), 999.0f, true, false);
			if (pos.X != 999.0f && pos.Y != 999.0f && pos.Z != -50.0f)
				this->m_posList.Add(pos);
			else
				break;
		}
	}
	if (this->m_posList.Count() == 0)
		this->m_enable = false;
	else
	{
		for (int id = 1; ; id++)
		{
			aString model = this->Load_String("General", aString::Format("Model_%d", id).GetString(), "");

			if (model.IsEmpty())
				break;

			this->m_modelList.Add(model);
		}
	}

	delete this->m_cfg;
	this->m_cfg = NULL;

	switch (gzGameMgr->m_settings->m_GameMode)
	{
		case GAMEMODE_SNIPER:
		case GAMEMODE_500SNIP:
		case GAMEMODE_DM:
			if (this->m_enable)
			{
				stConsole::Out("[TibCrystal] Error - Tiberium Crystal can not be enabled in %s game mode.\n", gzGameMgr->GetGame()->ModeName());
				this->m_enable = false;
			}
	}
}

gzTibCrystalManager *gzTibCrystalMgr = NULL;
gzTibCrystalManager::gzTibCrystalManager()
{
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_OBJECT_POWERUP_GRANT, -1);

	this->m_crystal		= NULL;
	this->m_settings	= new gzSettingsTibCrystal;
	this->m_settings->SetOwner(this);
}
void gzTibCrystalManager::Think()
{
	if (this->m_settings->m_enable && this->m_settings->m_posList.Count() > 0 && cGame->Is_Gameplay_Permitted())
	{
		SubFrameTime(this->m_nextSpawnCheck, true);
		if (this->m_nextSpawnCheck < 0.0f)
		{
			if (this->m_crystal == NULL)
			{
				gzTiberiumCrystal *newCrystal = new gzTiberiumCrystal;
				newCrystal->SetPosId(stRandom.Get_Int(0, this->m_settings->m_posList.Count() - 1));
				nc_PhysicalGameObj	*boneObj = nc_ObjectLibraryManager::Create_Object("Invisible_Object")->As_PhysicalGameObj(),
									*crystalObj = nc_ObjectLibraryManager::Create_Object("POW_Neuro_Link")->As_PhysicalGameObj();
				boneObj->Set_Position(this->m_settings->m_posList[newCrystal->GetPosId()]);
				crystalObj->Attach_To_Object_Bone(boneObj, "neck");
				newCrystal->SetCrystalBoneId(boneObj->NetworkID);
				newCrystal->SetCrystalId(crystalObj->NetworkID);
				newCrystal->SetLiveTime(this->m_settings->m_liveTime);
				this->m_crystal = newCrystal;
#ifdef TIBMSG
				DebugLog("[TibCrystal] created at position #%d; ID: %d",
					this->m_crystal->GetPosId(),
					this->m_crystal->GetCrystalId()
				);
#endif
			}
			this->m_nextSpawnCheck = (float)stRandom.Get_Int(this->m_settings->m_spawnMin, this->m_settings->m_spawnMax);
#ifdef TIBMSG
			DebugLog("[TibCrystal] Next spawn check in %.0f second(s)", this->m_nextSpawnCheck);
#endif
		}
	}
}
void gzTibCrystalManager::Object_PowerupGrant(gzEventObjectPowerupGrant &evt)
{
	if (evt.m_powerup == this->m_flags[0] || evt.m_powerup == this->m_flags[1])
		evt.Skip();
}
void gzTibCrystalManager::ChildObjDeletion(gzBase *obj)
{
	this->m_crystal = NULL;
}
void gzTibCrystalManager::Settings_Loaded()
{
	if (this->m_settings->m_enable)
	{
		// Dump location is in HarvesterClass
		for (int i = 0; i < 2; i++)
		{
			nc_BaseControllerClass *Base = nc_BaseControllerClass::Find_Base(i);
			if (Base && Base->Find_Building(nc_REFINERY))
			{
				this->m_flags[i] = gzStatic_Cast(nc_PowerUpGameObj, nc_ObjectLibraryManager::Create_Object("POW_Data_Disc"));
				this->m_flags[i]->As_PhysicalGameObj()->Set_Transform(&Base->Find_Building(nc_REFINERY)->As_RefineryGameObj()->DockPosition);
				this->m_flags[i]->Set_Player_Type(i);
				this->m_flags[i]->Set_Object_Dirty_Bit(nc_DB_CREATION, false);
#ifdef TIBMSG
				nc_Vector3 pos;
				this->m_flags[i]->As_PhysicalGameObj()->Get_Position(&pos);
				DebugLog("[TibCrystal] Flag(%d) has been set to dump zone of Refinery of team %d. X; %f  Y; %f  Z; %f",
					this->m_flags[i]->NetworkID,
					i,
					pos.X,
					pos.Y,
					pos.Z
				);
#endif
			}
		}
	}
}
nc_PowerUpGameObj *gzTibCrystalManager::GetFlag(int team)
{
	if (team <= -1 || team >= 2)
		return NULL;
	return this->m_flags[team];
}

gzTiberiumCrystal::gzTiberiumCrystal()
{
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_GAME_LEVEL_ENDED);
	this->RegisterEvent(EVT_PLAYER_LEFT);
	this->RegisterEvent(EVT_PLAYER_PURCHASE);
	this->RegisterEvent(EVT_OBJECT_KILL);
	this->RegisterEvent(EVT_OBJECT_TRANSITION, 1);
	this->RegisterEvent(EVT_OBJECT_POWERUP_GRANT);

	this->SetChildOwner(gzTibCrystalMgr);

	this->m_crystalId			= 0;
	this->m_crystalBoneId		= 0;
	this->m_posId				= -1;
	this->m_state				= 0;
	this->m_dropCount			= 0;
	this->m_dropSpoilCounter	= -1.0f;
	this->m_damagePickerCounter	= 0.0f;
	this->m_liveCounter			= 0.0f;
	this->m_nextGrowCounter		= 0.0f;
	this->m_harvestable			= false;
	this->m_picker				= NULL;
	this->m_pickerSoldierId		= 0;

#if VERC(1, 0, 1)
	for (int i = 1; i <= 127; i++)
		this->m_pickupBlockCounter[i] = 0.0f;
#endif
}
void gzTiberiumCrystal::Delete()
{
	nc_PhysicalGameObj	*crystalObj	= nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalId),
						*boneObj	= nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalBoneId);
	if (crystalObj)
		crystalObj->Set_Delete_Pending();
	if (boneObj)
		boneObj->Set_Delete_Pending();

	this->m_crystalId		= 0;
	this->m_crystalBoneId	= 0;
	this->m_state			= 0;
	this->m_dropCount		= 0;
	this->m_picker			= NULL;
	this->m_pickerWeapons.Clear();
	this->m_flag			= NULL;
}
void gzTiberiumCrystal::Level_Ended()
{
	this->SetDeletePending();
}
void gzTiberiumCrystal::Think()
{

#if VERC(1, 0, 1)
		for (int i = 1; i <= 127; i++)
			SubFrameTime(this->m_pickupBlockCounter[i], this->m_pickupBlockCounter[i] >= 0.0f);
#endif

	SubFrameTime(this->m_liveCounter, (this->m_liveCounter >= 0.0f));
	if (this->m_liveCounter < 0.0f)
	{
		if (this->GetPicker())
		{
			this->m_picker->SetTibCrystal(NULL);
			if (this->GetPicker()->GetSoldier())
				this->RestoreWeapons();
		}
#if VERC(1, 0, 1)
		if (this->m_picker != NULL)
			PagePlayer(this->m_picker->GetPlayerData()->PlayerId, "[TibCrystal]: The life of Tiberium Crystal has ended because it has been exposed too long.");
#endif
		this->NotifyFlagDeletion();
		this->SetDeletePending();
#ifdef TIBMSG
		DebugLog("[TibCrystal] Life for %d has ended.", this->m_crystalId);
#endif
	}
	else
	{
		SubFrameTime(this->m_nextGrowCounter, (this->m_nextGrowCounter >= 0.0f));
		if (this->m_nextGrowCounter < 0.0f)
		{
			if (this->m_state < gzTibCrystalMgr->m_settings->m_modelList.Count())
			{
				nc_PhysicalGameObj *crystalObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalId);
				if (crystalObj)
				{
					crystalObj->Physics->Set_Model_By_Name(gzTibCrystalMgr->m_settings->m_modelList[this->m_state]);
					crystalObj->Set_Object_Dirty_Bit(nc_DB_RARE, true);
					this->m_nextGrowCounter = (float)stRandom.GetInt(gzTibCrystalMgr->m_settings->m_growMin, gzTibCrystalMgr->m_settings->m_growMax);
					this->m_state++;
					if (this->m_state == gzTibCrystalMgr->m_settings->m_modelList.Count())
						this->m_harvestable = true;
#ifdef TIBMSG
					DebugLog("[TibCrystal] %d has grown to level: %d/%d [%s]; Harvestable: %d",
						this->m_crystalId,
						this->m_state,
						gzTibCrystalMgr->m_settings->m_modelList.Count(),
						gzTibCrystalMgr->m_settings->m_modelList[this->m_state - 1].GetString(),
						this->m_harvestable
					);
#endif
				}
			}
		}

		if (this->m_picker)
		{
			SubFrameTime(this->m_damagePickerCounter, (this->m_damagePickerCounter >= 0.0f));
			if (this->m_damagePickerCounter < 0.0f)
			{
#ifdef TIBMSG
				DebugLog("[TibCrystal] Applying Tiberium Poison to %ls...", this->m_picker->GetPlayerName().GetString());
#endif
				gzCommands->Apply_Damage(this->m_picker->GetPlayerData()->Owner.Reference->obj, gzTibCrystalMgr->m_settings->m_tibPoisonDamage, "Earth", nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalId));
				this->m_damagePickerCounter = gzTibCrystalMgr->m_settings->m_tibPoisonDamageTime;
			}

			if (!this->m_picker->GetPlayerData()->Owner.Reference || this->m_picker->GetPlayerData()->Owner.Reference->obj->NetworkID != this->m_pickerSoldierId)
			{
#ifdef TIBMSG
				DebugLog("[TibCrystal] Soldier of %ls was destroyed without being killed.", this->m_picker->GetPlayerName().GetString());
#endif
				this->Drop();
			}

			//SubFrameTime(this->m_clearWeaponsCounter, (this->m_clearWeaponsCounter >= 0.0f));
			//if (this->m_clearWeaponsCounter < 0.0f)
			//{
			//	if (this->m_picker && this->m_picker->GetSoldier())
			//		this->m_picker->GetSoldier()->WeaponBag->Clear_Weapons();
			//	this->m_clearWeaponsCounter = 1.0f;
			//}
		}
		else
		{
			if (this->m_dropSpoilCounter > 1.0f)
			{
				SubFrameTime(this->m_dropSpoilCounter, true);
				if (this->m_dropSpoilCounter < 0.0f)
				{
#ifdef TIBMSG
					DebugLog("[TibCrystal] %d has spoiled.", this->m_crystalId);
#endif
					this->SetDeletePending();
				}
			}
		}
	}
}
void gzTiberiumCrystal::Player_Left(gzEventPlayerBase &evt)
{
	if (this->GetPicker() && evt.GetPlayer() == this->GetPicker()->GetPlayerData())
		this->Drop();
}
void gzTiberiumCrystal::Player_Purchase(gzEventPlayerPurchase &evt)
{
	if (this->m_picker && evt.GetPlayer() == this->m_picker->GetPlayerData())
	{
		evt.m_retCode = 1;
		evt.Skip();
	}
}
void gzTiberiumCrystal::Object_Killed(gzEventObjectKill &evt)
{
	if (evt.m_attacker && evt.m_attacker->NetworkID == this->m_crystalId && evt.m_defender->As_SoldierGameObj())
	{
		if (evt.m_defender->NetworkID == this->m_pickerSoldierId)
		{
#ifdef TIBMSG
			DebugLog("[TibCrystal] Carrier %ls was killed. (Crystal: %d)", this->m_picker->GetPlayerName().GetString(), this->m_crystalId);
#endif
			this->Drop();
		}
	}
}
void gzTiberiumCrystal::Object_Transition(gzEventObjectTransition &evt)
{
	if (this->m_picker && this->m_picker->GetPlayerData() == evt.m_soldier->Player)
	{
#if VERC(1, 0, 1)
		int pId = this->m_picker->GetPlayerData()->PlayerId;
		nc_GameObjManager::Find_PhysicalGameObj(this->GetCrystalId())->Attach_To_Object_Bone(NULL, "neck");
		this->RestoreWeapons();
		this->Drop();
		PagePlayer(pId, "Apperantly the vehicle you tried to enter is not big enough for Tiberium Crystal and it has been dropped.");
#else
		PagePlayer(this->m_picker->GetPlayerData()->PlayerId, "You can not enter any vehicle while carrying Tiberium Crystal.");
#ifdef TIBMSG
		DebugLog("[TibCrystal] %ls attempted to enter a vehicle while carrying Tiberium Crystal.", this->m_picker->GetPlayerName().GetString());
#endif
		evt.Skip();
#endif
	}
}
void gzTiberiumCrystal::Object_PowerupGrant(gzEventObjectPowerupGrant &evt)
{
	if (evt.m_powerup->NetworkID == this->m_crystalId)
	{
		if (!this->m_picker)
		{
			if (!this->m_harvestable) // Not ready for harvest
			{
				evt.Skip();
				return;
			}

			if (evt.m_soldier->Vehicle) // Can not pick Tiberium Crystal while in vehicle
			{
				evt.Skip();
				return;
			}

#if VERC(1, 0, 1)
			if (evt.m_soldier->Player && this->m_pickupBlockCounter[evt.m_soldier->Player->PlayerId] > 0.0f)
			{
				evt.Skip();
				return;
			}
#endif

			nc_PhysicalGameObj	*crystalObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalId),
						*crystalBoneObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalBoneId);

			if (crystalObj)
			{
				gzPlayer *gzData = gzPlayerManager::Find(evt.m_soldier->Player);
				if (gzData) // Must be pick up by player
				{
					if (crystalBoneObj)
					{
						crystalBoneObj->Set_Delete_Pending();
						this->m_crystalBoneId = 0;
					}
					crystalObj->Attach_To_Object_Bone(evt.m_soldier, "bone for bag");

					// Remove weapons of picker
					nc_WeaponBagClass *wBag = evt.m_soldier->WeaponBag;
					for (int i = 1; i < wBag->Vector.Count(); i++)
					{
						gzPickerWeaponData weapon;
						weapon.Inventory = wBag->Vector[i]->InventoryBullets.Get();
						weapon.Loaded = wBag->Vector[i]->LoadedBullets.Get();
						weapon.WeaponDef = wBag->Vector[i]->WeaponDef;
						this->m_pickerWeapons.Add(weapon);
					}
					wBag->Clear_Weapons();

					this->m_picker = gzData;
					this->m_picker->SetTibCrystal(this);
					this->m_dropSpoilCounter = -1.0f;
					this->m_damagePickerCounter = gzTibCrystalMgr->m_settings->m_tibPoisonDamageTime;
					this->m_pickerSoldierId = evt.m_soldier->NetworkID;
					this->m_flag = gzTibCrystalMgr->GetFlag(this->m_picker->GetPlayerData()->PlayerType.Get());
					this->m_flag->Set_Object_Dirty_Bit(this->m_picker->GetPlayerData()->PlayerId, nc_DB_CREATION, true);
					PagePlayer(this->m_picker->GetPlayerData()->PlayerId, "You're now the carrier of Tiberium Crystal. Please return to the \"disc\" in Refinery of your team on foot for credits!!!");
					gzLogger("_TIBCRYSTAL", "%ls picked up the Tiberium Crystal.", this->m_picker->GetPlayerName().GetString());
#ifdef TIBMSG
					DebugLog("[TibCrystal] %ls picked up.", this->m_picker->GetPlayerName().GetString());
#endif
				}
			}
		}
	}
	else if (this->m_picker && this->m_flag && evt.m_powerup->NetworkID == this->m_flag->NetworkID)
	{
		if (evt.m_soldier->Player == this->m_picker->GetPlayerData())
		{
#ifdef TIBMSG
			DebugLog("[TibCrystal] %ls returned %d.", this->m_picker->GetPlayerName().GetString(), this->m_crystalId);
#endif
			for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList; pList = pList->NodeNext)
			{
				if (pList && pList->NodeData->IsActive && pList->NodeData->PlayerType.Get() == this->m_picker->GetPlayerData()->PlayerType.Get())
				{
					pList->NodeData->Increment_Money(gzTibCrystalMgr->m_settings->m_successAmount / (float)(this->m_dropCount + 1));
					if (pList->NodeData->PlayerId != this->m_picker->GetPlayerData()->PlayerId)
					{
						PagePlayer(pList->NodeData->PlayerId, "You've received %.0f credits because %ls successfully returned the Tiberium Crystal to Refinery.",
							gzTibCrystalMgr->m_settings->m_successAmount / (float)(this->m_dropCount + 1),
							this->m_picker->GetPlayerName().GetString()
						);
					}
					else
					{
						pList->NodeData->Increment_Money(gzTibCrystalMgr->m_settings->m_successCarrierAmount);
						PagePlayer(this->m_picker->GetPlayerData()->PlayerId, "You've successfully carried Tiberium Crystal to the Refinery of your team. Your team has received %.0f credits and you've received an addition of %.0f for carrying Tiberium Crystal.",
							gzTibCrystalMgr->m_settings->m_successAmount / (float)(this->m_dropCount + 1),
							gzTibCrystalMgr->m_settings->m_successCarrierAmount
						);
					}
				}
			}
			this->m_picker->SetTibCrystal(NULL);
			this->RestoreWeapons();
			this->NotifyFlagDeletion();
			gzLogger("_TIBCRYSTAL", "%ls successfully returned Tiberium Crystal to their Tiberium Refinery.", this->m_picker->GetPlayerName().GetString());
			this->SetDeletePending();
		}
	}
}
void gzTiberiumCrystal::Drop()
{
	if (this->m_picker && this->m_crystalBoneId == 0)
	{
		nc_PhysicalGameObj *crystalObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_crystalId);
		if (crystalObj)
		{
			this->m_dropCount++;
			this->m_picker->SetTibCrystal(NULL);
			if (nc_cNetwork::PServerConnection->RemoteList[this->m_picker->GetPlayerData()->PlayerId])
				this->NotifyFlagDeletion();
			this->m_flag = NULL;
			if (this->m_dropCount >= gzTibCrystalMgr->m_settings->m_dropCountMax)
			{
#ifdef TIBMSG
				DebugLog("[TibCrystal] %d has spoiled because max drop count reached.", this->m_crystalId);
#endif
				this->SetDeletePending();
			}
			else
			{
				nc_ScriptableGameObj *boneObj = nc_ObjectLibraryManager::Create_Object("Invisible_Object");
				boneObj->As_PhysicalGameObj()->Attach_To_Object_Bone(crystalObj, "neck");
				this->m_crystalBoneId = boneObj->NetworkID;
				this->m_dropSpoilCounter = gzTibCrystalMgr->m_settings->m_dropLive;
				this->m_picker = NULL;
				this->m_pickerSoldierId = 0;
				this->m_pickerWeapons.Clear();
			}
		}
	}
}
void gzTiberiumCrystal::RestoreWeapons()
{
	if (this->m_picker)
	{
		nc_WeaponBagClass *wBag = this->m_picker->GetPlayerData()->Owner.Reference->obj->As_SmartGameObj()->WeaponBag;
		for (unsigned int i = 0; i < this->m_pickerWeapons.Count(); i++)
		{
			int bullets = this->m_pickerWeapons[i].Inventory + this->m_pickerWeapons[i].Loaded;
			if (this->m_pickerWeapons[i].Inventory == -1)
				bullets = -1;
			wBag->Add_Weapon(this->m_pickerWeapons[i].WeaponDef, bullets, true);
		}

		if (wBag->Vector.Count() > 1)
			wBag->Select_Next();
	}
}
void gzTiberiumCrystal::NotifyFlagDeletion()
{
	if (this->m_picker != NULL)
	{
		this->m_flag->DeletePending = true;
		nc_cNetwork::Send_Object_Update(this->m_flag, this->m_picker->GetPlayerData()->PlayerId);
		this->m_flag->DeletePending = false;
		this->m_flag->Set_Object_Dirty_Bit(this->m_picker->GetPlayerData()->PlayerId, nc_DB_CREATION, false);
	}
}
unsigned int gzTiberiumCrystal::GetCrystalId()
{
	return this->m_crystalId;
}
void gzTiberiumCrystal::SetCrystalId(unsigned int id)
{
	this->m_crystalId = id;
}
unsigned int gzTiberiumCrystal::GetCrystalBoneId()
{
	return this->m_crystalBoneId;
}
void gzTiberiumCrystal::SetCrystalBoneId(unsigned int id)
{
	this->m_crystalBoneId = id;
}
int gzTiberiumCrystal::GetPosId()
{
	return this->m_posId;
}
void gzTiberiumCrystal::SetPosId(int id)
{
	this->m_posId = id;
}
void gzTiberiumCrystal::SetLiveTime(float secs)
{
	this->m_liveCounter = secs;
}
gzPlayer *gzTiberiumCrystal::GetPicker()
{
	return this->m_picker;
}
