#include "stinc.h"
#include "stmisc.h"
#include "stplayer.h"
#include "stgame.h"
#include "stanticheat.h"
#include "stchat.h"
#include "stconsolecommands.h"
#include "stvehicle.h"
#include "sttranslate.h"


// Forward
class gzChat_vehBind;
class gzChat_vehLock;
class gzChat_vehUnbind;
class gzChat_vehUnlock;
class gzChat_vehKick;
class gzChat_vehList;
class gzChat_vehStatus;
class gzChat_HarvStatus;


void gzSettingsVehicleClass::Delete()
{
	for (unsigned int i = 0; i < this->m_wreckageList.Count(); i++)
		delete this->m_wreckageList[i];
	this->m_wreckageList.Clear();

	for (unsigned int i = 0; i < this->m_destroyAnimList.Count(); i++)
		delete this->m_destroyAnimList[i];
	this->m_destroyAnimList.Clear();

	for (unsigned int i = 0; i < this->m_boneList.Count(); i++)
		delete this->m_boneList[i];
	this->m_boneList.Clear();
}
void gzSettingsVehicleClass::Load()
{
	struct stat attrib;
	stat("Vehicles.ini", &attrib);
	if (this->m_confModTime != attrib.st_mtime)
	{
		if (this->m_confModTime != 0)
			stConsole::Out("[Config] Vehicles.ini changed. Reloading...\n");

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

		// Cleanups
		this->Delete();

		// General
		this->m_maxEnemyVehicle          = this->m_cfg->GetData("General.MaxEnemyVehicle", 4).ToLong();
		this->m_maxTeamVehicle           = this->m_cfg->GetData("General.MaxTeamVehicle", 1).ToLong();
		this->m_kickTime                 = (float)this->m_cfg->GetData("General.vKickTime", 15.0f).ToDouble();
		this->m_bindOnPurchase           = this->m_cfg->GetData("General.BindOnPurchase", true).ToLong();
		this->m_bindWreckageToMostRepair = this->m_cfg->GetData("General.BindWreckageToMostRepair", false).ToLong();
		this->m_bindIcon                 = this->m_cfg->GetData("General.BindIcon", "o_em_apc");
		this->m_lockIcon                 = this->m_cfg->GetData("General.LockIcon", "p_keycrd_red");

		for (int i = 1; i <= this->m_cfg->GetSubCount("BoneList"); i++)
		{
			aString vehName = this->m_cfg->GetItem(aString::Format("BoneList.[%d]", i), "");
			aString boneList = this->m_cfg->GetData("BoneList." + vehName, "");
			if (!boneList.IsEmpty())
			{
				aToken boneTok(boneList);
				bool Found = false;
				for (unsigned int j = 0; j < this->m_boneList.Count(); j++)
				{
					if (this->m_boneList[j]->Vehicle == vehName)
					{
						for (int j = 1; j <= boneTok.numtok(' '); j++)
							this->m_boneList[j]->Bone.Add(boneTok.gettok(j,' ').GetData());
						Found = true;
						break;
					}
				}
				if (!Found)
				{
					gzVehicleBoneData *newBone = new gzVehicleBoneData;
					for (int j = 1; j <= boneTok.numtok(' '); j++)
						newBone->Bone.Add(boneTok.gettok(j,' ').GetData());
					newBone->Vehicle = vehName;
					this->m_boneList.Add(newBone);
				}
			}
		}

		// Wreckage
		for (int i = 1; i <= this->m_cfg->GetSubCount("WreckagePreset"); i++)
		{
			gzVehicleWreckageStruct* wreckage = new gzVehicleWreckageStruct;
			wreckage->vehicle = this->m_cfg->GetItem(aString::Format("WreckagePreset.[%d]", i), "");
			wreckage->wreckage = this->m_cfg->GetData("WreckagePreset." + wreckage->vehicle, "");
			if (!wreckage->vehicle.IsEmpty() && !wreckage->wreckage.IsEmpty())
			{
				nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Find_Named_Definition(wreckage->wreckage.GetString(), true);
				if (Definition && Definition->Get_Class_ID() == 0x3010)
					this->m_wreckageList.Add(wreckage);
				else
					delete wreckage;
			}
			else
				delete wreckage;
		}

		// Destroy animation
		for (int i = 1; i <= this->m_cfg->GetSubCount("DestroyAnimation"); i++)
		{
			gzVehicleDestroyAnimStruct* anim = new gzVehicleDestroyAnimStruct;
			anim->vehicle = this->m_cfg->GetItem(aString::Format("DestroyAnimation.[%d]", i), "");
			anim->anim = this->m_cfg->GetData("DestroyAnimation." + anim->vehicle, "");
			if (!anim->vehicle.IsEmpty() && !anim->anim.IsEmpty())
			{
				nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Find_Named_Definition(anim->anim.GetString(), true);
				if (Definition && Definition->Get_Class_ID() == 0x3010)
					this->m_destroyAnimList.Add(anim);
				else
					delete anim;
			}
			else
				delete anim;
		}

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


/***********/
/* Manager */
/***********/
gzManagerVehicle *gzVehicleMgr = NULL;
gzManagerVehicle::gzManagerVehicle()
{
	this->RegisterEvent(EVT_GAME_LEVEL_LOADED);
	this->RegisterEvent(EVT_OBJECT_CREATE);
	this->RegisterEvent(EVT_OBJECT_KILL);

	this->m_settings = new gzSettingsVehicleClass;

	gzChatCommand_Reg<gzChat_vehBind> Chat_vehBind_Reg("!bind", MSG_ALL | MSG_TEAM, "VehBind");
	gzChatCommand_Reg<gzChat_vehLock> Chat_vehLock_Reg("!lock,!bl", MSG_ALL | MSG_TEAM, "VehBL");
	gzChatCommand_Reg<gzChat_vehUnbind> Chat_vehUnbind_Reg("!unbind,!ub");
	gzChatCommand_Reg<gzChat_vehUnlock> Chat_vehUnlock_Reg("!unlock");
	gzChatCommand_Reg<gzChat_vehKick> Chat_vehKick_Reg("!vkick,!vk");
	gzChatCommand_Reg<gzChat_vehList> Chat_vehList_Reg("!vlist");
	gzChatCommand_Reg<gzChat_vehStatus> Chat_vehStatus_Reg("!vstats,!vstatus,!vehstats,!vehstatus", MSG_ALL | MSG_TEAM, "VehStatus");
	gzChatCommand_Reg<gzChat_HarvStatus> gzChat_HarvStatus_Reg("!harvstats,!harvstatus,!hstats,!hstatus", MSG_ALL | MSG_TEAM, "HarvUplink");
}
void gzManagerVehicle::Delete()
{
	aListNode<gzVehicleData> *vList = this->m_vehList.GetHead();
	while (vList)
	{
		vList->GetData()->SetDeletePending();
		vList = vList->GetNext();
	}
	this->m_vehList.RemoveAll();
}
void gzManagerVehicle::Level_Loaded()
{
	this->Delete();

	for (nc_GenericSLNode<nc_BaseGameObj> *objList = nc_GameObjManager::GameObjList->HeadNode; objList != NULL; objList = objList->NodeNext)
	{
		if (objList->NodeData->As_VehicleGameObj() && gzStatic_Cast(nc_VehicleGameObjDef, objList->NodeData->definition)->VehicleType == 4)
			objList->NodeData->As_ScriptableGameObj()->Insert_Observer(nc_ScriptManager::Create_Script("gzObserverVehicle"));
	}
}
void gzManagerVehicle::Object_Created(gzEventObjectCreate &evt)
{
	if (evt.m_obj->As_VehicleGameObj() && evt.m_obj->As_VehicleGameObj()->SeatsList.Length() > 0 && !evt.m_obj->As_PhysicalGameObj()->Physics->As_DecorationPhysClass())
	{
		gzVehicleData *data = new gzVehicleData(evt.m_obj->As_VehicleGameObj());
		this->m_vehList.AddHead(data);
		evt.m_obj->As_VehicleGameObj()->VehicleCanEnemySeen = true;
	}
}
void gzManagerVehicle::Object_Killed(gzEventObjectKill &evt)
{
	if (evt.m_defender->As_VehicleGameObj())
	{
		nc_Vector3 deathPos;
		evt.m_defender->Get_Position(&deathPos);
		if (gzGameMgr->m_settings->m_EnableVehicleWreckages)
		{
			for (unsigned int i = 0; i < this->m_settings->m_wreckageList.Count(); i++)
			{
				if (this->m_settings->m_wreckageList[i]->vehicle == evt.m_defender->definition->Get_Name())
				{
					nc_ScriptableGameObj *newObj = nc_ObjectLibraryManager::Create_Object(this->m_settings->m_wreckageList[i]->wreckage.GetString());
					newObj->As_PhysicalGameObj()->Set_Position(deathPos);
					gzCommands->Set_Facing(newObj, gzCommands->Get_Facing(evt.m_defender));
					newObj->As_PhysicalGameObj()->PlayerType = -2;
					gzCommands->Disable_Physical_Collisions(newObj);
					newObj->As_VehicleGameObj()->Defense.HealthMax += newObj->As_VehicleGameObj()->Defense.ShieldStrengthMax.Get();
					newObj->As_VehicleGameObj()->Defense.ShieldStrengthMax = 0.0f;

					// 10% of total health
					newObj->As_VehicleGameObj()->Defense.Health = (newObj->As_VehicleGameObj()->Defense.HealthMax.Get() + newObj->As_VehicleGameObj()->Defense.ShieldStrengthMax.Get()) * 0.1f;
					newObj->Start_Observers();

					nc_ScriptImpClass *observer = nc_ScriptManager::Create_Script("gzObserverVehicleWreckage");
					observer->Set_Parameters_String(evt.m_defender->definition->Get_Name());
					newObj->Insert_Observer(observer);
					break;
				}
			}
		}
	}
}
void gzManagerVehicle::Object_FlipKill(gzEventObjectFlipKill &evt)
{
	for (int i = 0; i < evt.m_vehicle->Observers.Count(); i++)
		evt.m_vehicle->Observers[i]->Custom(evt.m_vehicle, 309475, 0, NULL);
}
void gzManagerVehicle::ChildObjDeletion(gzBase *obj)
{
	aListNode<gzVehicleData> *vehList = this->m_vehList.GetHead();
	while (vehList)
	{
		if (vehList->GetData()->GetVehicleId() == gzStatic_Cast(gzVehicleData, obj)->GetVehicleId())
		{
			this->m_vehList.Remove(*vehList);
			break;
		}
		vehList = vehList->GetNext();
	}
}
gzVehicleData *gzManagerVehicle::Find(unsigned long ID)
{
	aListNode<gzVehicleData> *vehList = this->m_vehList.GetHead();
	while (vehList)
	{
		if (vehList->GetData()->GetVehicleId() == ID)
			return vehList->GetData();
		vehList = vehList->GetNext();
	}
	return NULL;
}
gzSettingsVehicleClass *gzManagerVehicle::GetSettings()
{
	return this->m_settings;
}


/****************/
/* Vehicle data */
/****************/
void gzVehicleData::Delete()
{
	if (this->m_owner)
		this->m_owner->GetVehicleData()->Unset(this->m_vehId, VO_BIND);

	for (int i = 0; i < 2; i++)
	{
		if (this->m_boneObj[i] != 0)
		{
			nc_PhysicalGameObj *boneObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_boneObj[i]);
			if (boneObj)
			{
				boneObj->Set_Delete_Pending();
				this->m_boneObj[i] = 0;
			}
		}
	}
	this->m_bans.Clear();
}
gzVehicleData::gzVehicleData(nc_VehicleGameObj *vehicle)
{
	this->RegisterEvent(EVT_GAME_THINK);
	this->RegisterEvent(EVT_OBJECT_DAMAGE, 1);
	this->RegisterEvent(EVT_OBJECT_KILL);
	this->RegisterEvent(EVT_OBJECT_TRANSITION);

	this->m_owner = NULL;
	this->SetChildOwner(gzVehicleMgr);

	this->m_vehId = vehicle->NetworkID;
	this->m_bindType = VO_NONE;
	this->m_boneObj[0] = 0;
	this->m_boneObj[1] = 0;
	this->m_purchaser = NULL;
	this->m_canSetPurchaser = true;
#if ISDEV()
	this->m_sell = NULL;
#endif

	for (unsigned int i = 0; i < gzVehicleMgr->GetSettings()->m_boneList.Count(); i++)
	{
		if (gzVehicleMgr->GetSettings()->m_boneList[i]->Vehicle ==vehicle->definition->Get_Name())
		{
			for (unsigned int j = 0; j < gzVehicleMgr->GetSettings()->m_boneList[i]->Bone.Count() && j < 2; j++)
			{
				nc_PhysicalGameObj *boneObj = nc_ObjectLibraryManager::Create_Object("Invisible_Object")->As_PhysicalGameObj();
				boneObj->Attach_To_Object_Bone(vehicle, gzVehicleMgr->GetSettings()->m_boneList[i]->Bone[j].GetString());
				this->m_boneObj[j] = boneObj->NetworkID;
			}
			break;
		}
	}
}
void gzVehicleData::Think()
{
	for (unsigned int i = 0; i < this->m_bans.Count(); i++)
	{
		if (this->m_bans[i].Counter > -99999.0f)
		{
			SubFrameTime(this->m_bans[i].Counter, true);
			if (this->m_bans[i].Counter < 0.0f)
			{
				this->m_bans.Delete(i);
				i--;
			}
		}
	}
	nc_SmartGameObj *smartObj = nc_GameObjManager::Find_SmartGameObj(this->m_vehId);
	if (smartObj)
	{
		if (this->m_canSetPurchaser)
		{
			nc_VehicleGameObj *veh = smartObj->As_VehicleGameObj();
			if (veh->OwnerExpireCounter > 0.0f && veh->Owner.Reference && veh->Owner.Reference->obj->As_SoldierGameObj()->Player)
			{
				this->m_purchaser = gzPlayerManager::Find(veh->Owner.Reference->obj->As_SoldierGameObj()->Player);
				if (this->m_purchaser)
				{
#ifdef VOMSG
					stConsole::Out("[VO] Purchaser is set for %s: %ls\n",
						veh->definition->Get_Name(),
						veh->Owner.Reference->obj->As_SoldierGameObj()->Player->PlayerName.m_Buffer
					);
#endif
					this->m_canSetPurchaser = false;
					if (gzVehicleMgr->GetSettings()->m_bindOnPurchase)
					{
						int team = 0, enemy = 0;
						this->m_purchaser->GetVehicleData()->GetCount(team, enemy);
						if (team < gzVehicleMgr->GetSettings()->m_maxTeamVehicle)
						{
							this->m_owner = this->m_purchaser;
							for (int i = 0; i < 2; i++)
							{
								nc_PhysicalGameObj *boneObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_boneObj[i]);
								if (boneObj)
								{
									boneObj->Physics->Set_Model_By_Name(gzVehicleMgr->GetSettings()->m_bindIcon.GetString());
									boneObj->Set_Object_Dirty_Bit(nc_DB_RARE, true);
								}
							}
							this->m_bindType = VO_BIND;
							this->m_owner->GetVehicleData()->GetBoundList().Add(this);
						}
					}
				}
			}
			if (veh->OwnerExpireCounter <= 0.0f)
				this->m_canSetPurchaser = false;
		}
	}
	else
		this->SetDeletePending();
}
void gzVehicleData::Object_Damaged(gzEventObjectDamage &evt)
{
	if (evt.m_damage > 0.0f && evt.m_defender->NetworkID == this->m_vehId)
	{
		if (evt.m_attacker && evt.m_attacker->As_SoldierGameObj() && evt.m_attacker->As_SoldierGameObj()->Player)
		{
			if (this->m_owner && (this->m_bindType & VO_LOCK) != 0)
			{
				if (evt.m_attacker->PlayerType == this->m_owner->GetPlayerData()->PlayerType.Get() && evt.m_attacker != this->m_owner->GetSoldier())
				{
					evt.Skip();
					return;
				}
			}
		}
	}
}
void gzVehicleData::Object_Killed(gzEventObjectKill &evt)
{
	if (evt.m_defender->NetworkID == this->m_vehId)
	{
//#ifdef VOMSG
//		stConsole::Out("[VO] %d(%s) is being destroyed, removing from list...\n",
//			this->m_vehId,
//			evt.m_defender->definition->Get_Name()
//		);
//#endif
		if (this->m_owner)
		{
			if (evt.m_attacker && evt.m_attacker->As_SoldierGameObj() && evt.m_attacker->As_SoldierGameObj()->Player)
			{
				PagePlayer(this->m_owner->GetPlayerData()->PlayerId, "Your bound vehicle(%s) was destroyed by %ls.\n",
					gzTranslator->Get(evt.m_defender->definition->Get_Name()),
					evt.m_attacker->As_SoldierGameObj()->Player->PlayerName.m_Buffer
				);
			}
			else
			{
				PagePlayer(this->m_owner->GetPlayerData()->PlayerId, "Your bound vehicle(%s) was destroyed.\n",
					gzTranslator->Get(evt.m_defender->definition->Get_Name())
				);
			}
			this->m_owner->GetVehicleData()->GetBoundList().Delete(this->m_owner->GetVehicleData()->GetBoundList().Index(this));
		}
		this->m_owner = NULL;
		this->SetDeletePending();
	}
}
void gzVehicleData::Object_Transition(gzEventObjectTransition &evt)
{
	if (evt.m_instance->Target.Reference && evt.m_instance->Target.Reference->obj->NetworkID == this->m_vehId)
	{
		if (evt.m_instance->Target.Reference->obj->NetworkID == this->m_vehId && evt.m_soldier->Player)
		{
			if (!evt.m_soldier->Vehicle)
			{
				nc_VehicleGameObj *Vehicle = nc_GameObjManager::Find_SmartGameObj(this->m_vehId)->As_VehicleGameObj();
				gzPlayer *gzData = gzPlayerManager::Find(evt.m_soldier->Player);
				if (gzData)
				{
					if (this->m_purchaser && this->m_purchaser->GetPlayerData()->PlayerType.Get() != evt.m_soldier->Player->PlayerType.Get())
					{
#ifdef VOMSG
						stConsole::Out("[VO] Purchaser has been removed because %ls is an enemy of %ls who entered the vehicle.\n",
							evt.m_soldier->Player->PlayerName.m_Buffer,
							this->m_purchaser->GetPlayerData()->PlayerName.m_Buffer
						);
#endif
						this->m_purchaser = NULL;
					}
					if (this->m_owner)
					{
#ifdef VOMSG
						stConsole::Out("[VO] %ls attempted to enter vehicle: %s; Owner: %ls; bindType: %d\n",
							evt.m_soldier->Player->PlayerName.m_Buffer,
							nc_GameObjManager::Find_SmartGameObj(this->m_vehId)->definition->Get_Name(),
							this->m_owner->GetPlayerData()->PlayerName.m_Buffer,
							this->m_bindType
						);
#endif
						if (this->m_owner && this->m_owner->GetPlayerData()->PlayerType.Get() != evt.m_soldier->PlayerType)
						{
							gzVehicleData *vehData = gzVehicleMgr->Find(this->m_vehId);
							if (vehData)
							{
								PagePlayer(this->m_owner->GetPlayerData()->PlayerId, "Enemy has stolen your vehicle! (%s)",
									gzTranslator->Get(Vehicle->definition->Get_Name())
								);
								this->m_owner->GetVehicleData()->Unset(this->m_vehId, VO_BIND);
								vehData->m_owner = NULL;
							}
							return;
						}
						for (unsigned int i = 0; i < this->m_bans.Count(); i++)
						{
							if (this->m_bans[i].m_owner->GetPlayerData() == evt.m_soldier->Player)
							{
								PagePlayer(this->m_bans[i].m_owner->GetPlayerData()->PlayerId,
									"Your ban for vehicle %s will be lifted in %.0f second(s).",
									gzTranslator->Get(Vehicle->definition->Get_Name()),
									this->m_bans[i].Counter
								);
								evt.Skip();
								return;
							}
						}
						if (evt.m_soldier->Player != this->m_owner->GetPlayerData() &&
							evt.m_soldier->Player->PlayerType.Get() == this->m_owner->GetPlayerData()->PlayerType.Get() &&
							!Vehicle->SeatsList[0])
						{
							if ((this->m_bindType & VO_LOCK) == VO_LOCK)
							{
								bool seatAvailable = false;
								for (int i = 1; i < Vehicle->SeatsList.Length(); i++)
								{
									if (!Vehicle->SeatsList[i])
									{
										seatAvailable = true;
										Vehicle->Add_Occupant(evt.m_soldier, i);
										stConsole::In("pamsg %d The vehicle you entered is locked and belongs to %ls. You're being moved to passanger seat.",
											evt.m_soldier->Player->PlayerId,
											this->m_owner->GetPlayerData()->PlayerName.m_Buffer
										);
										break;
									}
								}
								if (!seatAvailable)
								{
									stConsole::In("pamsg %d The vehicle you tried to enter cannot be entered because it's locked and belongs to %S. In addition, it has no passanger seat available for you.",
										evt.m_soldier->Player->PlayerId,
										this->m_owner->GetPlayerData()->PlayerName.m_Buffer
									);
								}
								evt.Skip();
								return;
							}
							if (this->m_bindType == VO_BIND)
							{
								PagePlayer(this->m_owner->GetPlayerData()->PlayerId,
									"Warning! %ls has entered your bound vehicle(%s). If you wish to remove him, type !vkick %ls (You may enter part his the nickname).",
									evt.m_soldier->Player->PlayerName.m_Buffer,
									gzTranslator->Get(nc_GameObjManager::Find_SmartGameObj(this->m_vehId)->definition->Get_Name()),
									evt.m_soldier->Player->PlayerName.m_Buffer
								);
#ifdef VOMSG
								stConsole::Out("[VO] %ls entered bound vehicle. Setting Last-Enter data...Owner: %ls; Vehicle: %s(%d)\n",
									evt.m_soldier->Player->PlayerName.m_Buffer,
									this->m_owner->GetPlayerData()->PlayerName.m_Buffer,
									nc_GameObjManager::Find_SmartGameObj(this->m_vehId)->definition->Get_Name(),
									this->m_vehId
								);
#endif
								this->m_owner->GetVehicleData()->SetLastEnter(gzData, this->m_vehId);
								//this->m_owner->GetVehicleData()->m_lastEnter = pData;
								//this->m_owner->GetVehicleData()->m_lastEnterVehId = this->m_vehId;
							}
						}
					}
				}
			}
		}
	}
}
gzPlayer *gzVehicleData::GetOwner()
{
	return this->m_owner;
}
void gzVehicleData::SetOwner(gzPlayer *owner)
{
	this->m_owner = owner;
}
void gzVehicleData::SetBindType(int type)
{
	this->m_bindType = type;
}
unsigned int gzVehicleData::GetVehicleId()
{
	return this->m_vehId;
}
unsigned int gzVehicleData::GetBoneObjId(unsigned int index)
{
	if (index > 2)
		return 0;
	return this->m_boneObj[index];
}
aVector<gzVehicleOwnerBanData> &gzVehicleData::GetBanList()
{
	return this->m_bans;
}


/***************/
/* Player data */
/***************/
gzVehicleOwnerClass::gzVehicleOwnerClass(gzPlayer *owner)
{
	this->RegisterEvent(EVT_GAME_THINK);

	this->m_owner = owner;
	this->m_lastEnter = NULL;
	this->m_lastEnterVehId = 0;
}
void gzVehicleOwnerClass::Think()
{
	if (this->m_lastEnter)
	{
		if (!this->m_lastEnter->GetPlayerData()->Owner.Reference ||
			!this->m_lastEnter->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle ||
			this->m_lastEnter->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID != this->m_lastEnterVehId)
		{
#ifdef VOMSG
			if (!this->m_lastEnter->GetPlayerData()->Owner.Reference)
				stConsole::Out("[VO] %ls soldier object not found. Issue Last-Enter cleanup...\n", this->m_lastEnter->GetPlayerName().GetString());

			else if (!this->m_lastEnter->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
				stConsole::Out("[VO] %ls is not in vehicle. Issue Last-Enter cleanup...\n", this->m_lastEnter->GetPlayerName().GetString());

			else if (this->m_lastEnter->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID != this->m_lastEnterVehId)
				stConsole::Out("[VO] %ls is not in the last entered vehicle. Issue Last-Enter cleanup...\n",this->m_lastEnter->GetPlayerName().GetString());
#endif
			this->m_lastEnter = NULL;
			this->m_lastEnterVehId = 0;
		}
		else
		{
			bool stillDrive = false;
			for (unsigned int i = 0; i < this->m_boundList.Count(); i++)
			{
				if (this->m_boundList[i]->GetVehicleId() == this->m_lastEnter->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID)
				{
					stillDrive = true;
					break;
				}
			}
			if (!stillDrive)
			{
#ifdef VOMSG
				stConsole::Out("[VO] %ls no longer drive the vehicle %s(%d). Issue Last-Enter cleanup...\n",
					this->m_lastEnter->GetPlayerName().GetString(),
					nc_GameObjManager::Find_SmartGameObj(this->m_lastEnterVehId)->definition->Get_Name(),
					this->m_lastEnterVehId
				);
#endif
				this->m_lastEnter = NULL;
				this->m_lastEnterVehId = 0;
			}
		}
	}
}
int gzVehicleOwnerClass::Set(int Type, unsigned int vehicleId)
{
	if (this->m_owner->GetSoldier() != NULL)
	{
		nc_VehicleGameObj *vehicle = this->m_owner->GetSoldier()->Vehicle;
		if (vehicleId > 0)
		{
			vehicle = (nc_GameObjManager::Find_SmartGameObj(vehicleId) ? nc_GameObjManager::Find_SmartGameObj(vehicleId)->As_VehicleGameObj() : NULL);
			if (vehicle == NULL)
				return VORESP_ERROR;
		}

		else
		{
			if (this->m_owner->GetSoldier()->Vehicle == NULL)
				return VORESP_NOT_IN_VEHICLE;

			if (vehicle->SeatsList[0] != this->m_owner->GetSoldier())
				return VORESP_NOT_DRIVER;
		}

		gzVehicleData *data = gzVehicleMgr->Find(vehicle->NetworkID);
		if (data)
		{
			if (data->GetOwner() && data->GetOwner() != this->m_owner)
				return VORESP_NOT_OWNER;

			int teamCount, enemyCount;
			this->GetCount(teamCount, enemyCount);

			if (!data->GetOwner() &&
				((gzStatic_Cast(nc_VehicleGameObjDef, vehicle->definition)->PlayerType == this->m_owner->GetPlayerData()->PlayerType.Get() && teamCount >= gzVehicleMgr->GetSettings()->m_maxTeamVehicle) ||
				 (gzStatic_Cast(nc_VehicleGameObjDef, vehicle->definition)->PlayerType != this->m_owner->GetPlayerData()->PlayerType.Get() && enemyCount >= gzVehicleMgr->GetSettings()->m_maxEnemyVehicle)))
			{
				return VORESP_QUOTA_MAXXED;
			}
			for (int i = 0; i <= 1; i++)
			{
				if (data->GetBoneObjId(i) != 0)
				{
					nc_PhysicalGameObj *boneObj = nc_GameObjManager::Find_PhysicalGameObj(data->GetBoneObjId(i));
					if (boneObj)
					{
						if (Type == VO_BIND)
							boneObj->Physics->Set_Model_By_Name(gzVehicleMgr->GetSettings()->m_bindIcon.GetString());

						else if ((Type & VO_LOCK) == VO_LOCK)
							boneObj->Physics->Set_Model_By_Name(gzVehicleMgr->GetSettings()->m_lockIcon.GetString());
						
						boneObj->Set_Object_Dirty_Bit(nc_DB_RARE, true);
					}
				}
			}
			data->SetBindType(Type);
			data->SetOwner(this->m_owner);
			if (this->m_owner->GetVehicleData()->m_boundList.Index(data) == -1)
				this->m_owner->GetVehicleData()->m_boundList.Add(data);
			return VORESP_SUCCESS;
		}
	}
	return VORESP_ERROR;
}
int gzVehicleOwnerClass::Unset(unsigned int vehID, int Type)
{
	gzVehicleData *vehData = gzVehicleMgr->Find(vehID);
	if (!vehData)
		return VORESP_ERROR;

	if (vehData->GetOwner() && vehData->GetOwner()->GetVehicleData() != this)
		return VORESP_NOT_OWNER;

	for (unsigned int i = 0; i < this->m_boundList.Count(); i++)
	{
		if (this->m_boundList[i]->GetVehicleId() == vehID)
		{
			const char *model = NULL;
			switch (Type)
			{
				case VO_BIND:
					model = "null";
					break;

				case VO_LOCK:
					model = gzVehicleMgr->GetSettings()->m_bindIcon.GetString();
					break;
			}
			if (model != NULL)
			{
				for (int j = 0; j < 2; j++)
				{
					if (this->m_boundList[i]->GetBoneObjId(j) != 0)
					{
						nc_PhysicalGameObj *boneObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_boundList[i]->GetBoneObjId(j));
						if (boneObj)
						{
							boneObj->Physics->Set_Model_By_Name(model);
							boneObj->Set_Object_Dirty_Bit(nc_DB_RARE, true);
						}
					}
				}
			}
			this->m_boundList[i]->SetOwner(NULL);
			this->m_boundList.Delete(i);
			break;
		}
	}
	return VORESP_SUCCESS;
}
void gzVehicleOwnerClass::UnsetAll()
{
	for (unsigned int i = 0; i < this->m_boundList.Count(); i++)
	{
		for (unsigned int j = 0; j < 2; j++)
		{
			nc_PhysicalGameObj *boneObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_boundList[i]->GetBoneObjId(j));
			if (boneObj)
			{
				boneObj->Physics->Set_Model_By_Name("null");
				boneObj->Set_Object_Dirty_Bit(nc_DB_RARE, true);
			}
		}
		this->m_boundList[i]->SetOwner(NULL);
	}
	this->m_boundList.Clear();
}
void gzVehicleOwnerClass::GetCount(int &team, int &enemy)
{
	team = 0;
	enemy = 0;
	for (unsigned int i = 0; i < this->m_boundList.Count(); i++)
	{
		if (nc_GameObjManager::Find_PhysicalGameObj(this->m_boundList[i]->GetVehicleId()))
		{
			nc_VehicleGameObjDef *Def = (nc_VehicleGameObjDef *)nc_GameObjManager::Find_PhysicalGameObj(this->m_boundList[i]->GetVehicleId())->definition;
			if (Def->PlayerType == this->m_owner->GetPlayerData()->PlayerType.Get())
				team++;
			else
				enemy++;
		}
	}
}
gzPlayer *gzVehicleOwnerClass::GetOwner()
{
	return this->m_owner;
}
gzPlayer *gzVehicleOwnerClass::GetLastEnter()
{
	return this->m_lastEnter;
}
void gzVehicleOwnerClass::SetLastEnter(gzPlayer *player, unsigned int vehicle)
{
	this->m_lastEnter = player;
	this->m_lastEnterVehId = vehicle;
}
aVector<gzVehicleData *> &gzVehicleOwnerClass::GetBoundList()
{
	return this->m_boundList;
}


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

		gzPlayer *pData = gzPlayerManager::Find(sender);
		if (!pData || !pData->GetPlayerData()->Owner.Reference)
			return;
		if (!pData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
		{
			PagePlayer(sender, "You must be in a vehicle to use this command.");
			return;
		}
		int ret = pData->GetVehicleData()->Set(VO_BIND | VO_LOCK);
		switch (ret)
		{
			case VORESP_SUCCESS:
				PagePlayer(sender, "The vehicle has locked to you.");
				break;
			case VORESP_NOT_IN_VEHICLE:
				PagePlayer(sender, "You must be in a vehicle to use this command.");
				break;
			case VORESP_NOT_DRIVER:
				PagePlayer(sender, "You must be the vehicle driver to use this command.");
				break;
			case VORESP_NOT_OWNER:
				PagePlayer(sender, "The vehicle you tried to bind is already bound to %ls.",
					gzVehicleMgr->Find(pData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID)->GetOwner()->GetPlayerData()->PlayerName.m_Buffer
				);
				break;
			case VORESP_QUOTA_MAXXED:
				PagePlayer(sender, "You have reached the vehicle bind limit.");
				break;
		}
	}
};

class gzChat_vehBind : 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_INFONLY:
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
			case GAMEMODE_DM:
				PagePlayer(sender, "Vehicle commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		gzPlayer *gzData = gzPlayerManager::Find(sender);
		if (!gzData || !gzData->GetPlayerData()->Owner.Reference)
			return;
		if (!gzData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
		{
			PagePlayer(sender, "You must be in a vehicle to use this command.", sender);
			return;
		}
		switch (gzData->GetVehicleData()->Set(VO_BIND))
		{
			case VORESP_SUCCESS:
				PagePlayer(sender, "The vehicle has bound to you.");
				break;
			case VORESP_NOT_IN_VEHICLE:
				PagePlayer(sender, "You must be in a vehicle to use this command.");
				break;
			case VORESP_NOT_DRIVER:
				PagePlayer(sender, "You must be the vehicle driver to use this command.");
				break;
			case VORESP_NOT_OWNER:
				PagePlayer(sender, "The vehicle you tried to bind is already bound to %ls.",
					gzVehicleMgr->Find(gzData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID)->GetOwner()->GetPlayerName().GetString()
				);
				break;
			case VORESP_QUOTA_MAXXED:
				PagePlayer(sender, "You have reached the vehicle bind limit.");
		}
	}
};

class gzChat_vehUnbind : 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_INFONLY:
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
			case GAMEMODE_DM:
				PagePlayer(sender, "Vehicle commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

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

		int vehIndex = -1;
		if (*msg)
		{
			if (!wcsicmp(msg, L"all"))
			{
				if (gzData->GetVehicleData()->GetBoundList().Count() == 0)
					PagePlayer(sender, "No vehicles had been bound to you.");
				else
				{
					int count = gzData->GetVehicleData()->GetBoundList().Count();
					gzData->GetVehicleData()->UnsetAll();
					PagePlayer(sender, "Your vehicles have been unbound.");
				}
				return;
			}
			if (_wtoi(msg) <= 0 || _wtoi(msg) > (gzVehicleMgr->GetSettings()->m_maxEnemyVehicle + gzVehicleMgr->GetSettings()->m_maxTeamVehicle))
			{
				PagePlayer(sender, "Invalid vehicle index. For the list of your bound vehicle, type !vlist.");
				return;
			}
			if ((unsigned)(_wtoi(msg) -1) >= gzData->GetVehicleData()->GetBoundList().Count())
			{
				PagePlayer(sender, "Vehicle #%d does not exist. For the list of your bound vehicle, type !vlist.", _wtoi(msg));
				return;
			}
			vehIndex = _wtoi(msg) -1;
		}
		else
		{
			if (gzData->GetSoldier() == NULL)
				return;

			if (gzData->GetSoldier()->Vehicle == NULL)
			{
				PagePlayer(sender, "You must be in your bound vehicle to use this command or specify vehicle ID.");
				return;
			}
			for (unsigned int i = 0; i < gzData->GetVehicleData()->GetBoundList().Count(); i++)
			{
				if (gzData->GetVehicleData()->GetBoundList()[i]->GetVehicleId() == gzData->GetSoldier()->Vehicle->NetworkID)
				{
					vehIndex = i;
					break;
				}
			}
			if (vehIndex == -1)
			{
				PagePlayer(sender, "You're not the owner of the vehicle.");
				return;
			}
		}
		if (vehIndex != -1)
		{
			nc_SmartGameObj *vehObj = nc_GameObjManager::Find_SmartGameObj(gzData->GetVehicleData()->GetBoundList()[vehIndex]->GetVehicleId());
			if (vehObj)
			{
				gzData->GetVehicleData()->Unset(vehObj->NetworkID, VO_BIND);
				PagePlayer(sender, "Your vehicle(%s) has been unbound.",
					gzTranslator->Get(vehObj->definition->Get_Name())
				);
			}
		}
	}
};

class gzChat_vehUnlock : 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_INFONLY:
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
			case GAMEMODE_DM:
				PagePlayer(sender, "Vehicle commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		gzPlayer *pData = gzPlayerManager::Find(sender);
		if (!pData)
			return;
		int vehIndex = -1;
		if (*msg)
		{
			if (_wtoi(msg) <= 0 || _wtoi(msg) > (gzVehicleMgr->GetSettings()->m_maxEnemyVehicle + gzVehicleMgr->GetSettings()->m_maxTeamVehicle))
			{
				PagePlayer(sender, "Invalid vehicle index. For the list of your bound vehicle, type !vlist.");
				return;
			}
			if ((unsigned)(_wtoi(msg) -1) >= pData->GetVehicleData()->GetBoundList().Count())
			{
				PagePlayer(sender, "Vehicle #%d does not exist. For the list of your bound vehicle, type !vlist.", _wtoi(msg));
				return;
			}
			vehIndex = _wtoi(msg) -1;
		}
		else
		{
			if (!pData->GetPlayerData()->Owner.Reference)
				return;
			if (!pData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
			{
				PagePlayer(sender, "You must be in your bound vehicle to use this command or specify vehicle ID.");
				return;
			}
			for (unsigned int i = 0; i < pData->GetVehicleData()->GetBoundList().Count(); i++)
			{
				if (pData->GetVehicleData()->GetBoundList()[i]->GetVehicleId() == pData->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID)
				{
					vehIndex = i;
					break;
				}
			}
			if (vehIndex == -1)
			{
				PagePlayer(sender, "You're not the owner of the vehicle.");
				return;
			}
		}
		int vehID = pData->GetVehicleData()->GetBoundList()[vehIndex]->GetVehicleId();
		pData->GetVehicleData()->Unset(vehID, VO_LOCK);
		PagePlayer(sender, "Your vehicle(%s) has been unlocked.",
			gzTranslator->Get(nc_GameObjManager::Find_SmartGameObj(vehID)->definition->Get_Name())
		);
	}
};

class gzChat_vehKick : 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_INFONLY:
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
			case GAMEMODE_DM:
				PagePlayer(sender, "Vehicle commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		gzPlayer *pData = gzPlayerManager::Find(sender);
		if (!pData)
			return;
		gzPlayer *kicked = NULL;
		if (!*msg)
		{
			if (pData->GetVehicleData()->GetLastEnter())
				kicked = pData->GetVehicleData()->GetLastEnter();
			else
			{
				PagePlayer(sender, "Currently none is in your bound vehicle(s) as driver.");
				return;
			}
		}
		else
		{
			for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList; pList = pList->NodeNext)
			{
				if (pList && pList->NodeData->IsActive)
				{
					aWideString pName = pList->NodeData->PlayerName.m_Buffer;
					if (pName.Compare(msg) || pName.Contain(msg))
					{
						kicked = gzPlayerMgr->Find(pList->NodeData);
						break;
					}
				}
			}
		}
		if (!kicked)
			PagePlayer(sender, "%ls not found.", msg);

		else if (!kicked->GetPlayerData()->Owner.Reference || !kicked->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle)
			PagePlayer(sender, "%ls is not in any vehicle.", kicked->GetPlayerName().GetString());

		else if (pData == kicked)
			PagePlayer(sender, "You can not kick yourself from your bound vehicle.");

		else
		{
			gzVehicleData *vehData = gzVehicleMgr->Find(kicked->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj()->Vehicle->NetworkID);
			if (vehData)
			{
				if (vehData->GetOwner() != pData)
					PagePlayer(sender, "%ls is not in your bound vehicle.", kicked->GetPlayerName().GetString());
				else
				{
					nc_TransitionManager::Check(kicked->GetPlayerData()->Owner.Reference->obj->As_SoldierGameObj(), true);
					gzVehicleOwnerBanData newBan;
					newBan.Counter = gzVehicleMgr->GetSettings()->m_kickTime;
					newBan.m_owner = kicked;
					vehData->GetBanList().Add(newBan);
					PagePlayer(sender, "%ls has been kicked from your vehicle he was in and banned for %.0f second(s).",
						kicked->GetPlayerName().GetString(),
						gzVehicleMgr->GetSettings()->m_kickTime
					);
					PagePlayer(kicked->GetPlayerData()->PlayerId, "You're being kicked and banned from your previously entered vehicle for %.0f second(s).",
						gzVehicleMgr->GetSettings()->m_kickTime
					);
				}
			}
		}
	}
};

class gzChat_vehList : public gzChatCommandBase {
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		gzPlayer *pData = gzPlayerManager::Find(sender);
		if (!pData)
			return;
		aString Team, Enemy;
		int teamCount = 0, enemyCount = 0;
		for (unsigned int i = 0; i < pData->GetVehicleData()->GetBoundList().Count(); i++)
		{
			if (nc_GameObjManager::Find_SmartGameObj(pData->GetVehicleData()->GetBoundList()[i]->GetVehicleId()))
			{
				nc_VehicleGameObjDef *Def = (nc_VehicleGameObjDef *)nc_GameObjManager::Find_SmartGameObj(pData->GetVehicleData()->GetBoundList()[i]->GetVehicleId())->definition;
				if (Def->PlayerType == pData->GetPlayerData()->PlayerType.Get())
				{
					Team += aString::Format("%s(ID: %d), ", gzTranslator->Get(Def->Get_Name()), i + 1);
					teamCount++;
				}
				else
				{
					Enemy += aString::Format("%s(ID: %d), ", gzTranslator->Get(Def->Get_Name()), i + 1);
					enemyCount++;
				}
			}
		}
		if ((teamCount + enemyCount) > 0)
		{
			if (teamCount > 0)
			{
				Team.Resize(Team.GetLength() -2);
				PagePlayer(sender, "Your bound team vehicle(%d/%d): %s.",
					teamCount,
					gzVehicleMgr->GetSettings()->m_maxTeamVehicle,
					Team.GetString()
				);
			}
			else
			{
				PagePlayer(sender, "Your bound team vehicle(%d/%d): None",
					teamCount,
					gzVehicleMgr->GetSettings()->m_maxTeamVehicle
				);
			}
			if (enemyCount > 0)
			{
				Enemy.Resize(Enemy.GetLength() -2);
				PagePlayer(sender, "Your bound enemy vehicle(%d/%d): %s.",
					enemyCount,
					gzVehicleMgr->GetSettings()->m_maxEnemyVehicle,
					Enemy.GetString()
				);
			}
			else
			{
				PagePlayer(sender, "Your bound enemy vehicle(%d/%d): None",
					enemyCount,
					gzVehicleMgr->GetSettings()->m_maxEnemyVehicle
				);
			}
		}
		else
			PagePlayer(sender, "You have no bound vehicle.");
	}
};

class gzChat_vehStatus : 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_INFONLY:
			case GAMEMODE_SNIPER:
			case GAMEMODE_500SNIP:
			case GAMEMODE_DM:
				PagePlayer(sender, "Vehicle commands are not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		nc_SoldierGameObj *soldier = nc_GameObjManager::Find_Soldier_Of_Client_ID(sender);
		if (!soldier)
			return;
		if (!soldier->Vehicle)
		{
			PagePlayer(sender, "You must be in a vehicle to use this command.");
			return;
		}
		aString reply = aString::Format("Vehicle status: %s - Health: %.0f/%.0f - Armor: %.0f/%.0f - ",
			gzTranslator->Get(soldier->Vehicle->definition->Get_Name()),
			soldier->Vehicle->Defense.Health.Get(),
			soldier->Vehicle->Defense.HealthMax.Get(),
			soldier->Vehicle->Defense.ShieldStrength.Get(),
			soldier->Vehicle->Defense.ShieldStrengthMax.Get()
		);
		for (int i = 0; i < soldier->Vehicle->SeatsList.Length(); i++)
		{
			if (soldier->Vehicle->SeatsList[i] && soldier->Vehicle->SeatsList[i]->Player)
				reply += aString::Format("Seat #%d: %S - ", i +1, soldier->Vehicle->SeatsList[i]->Player->PlayerName.m_Buffer);
			else
				reply += aString::Format("Seat #%d: Empty - ", i +1);
		}
		reply += aString::Format("Driver: %S - Gunner: %S",
			(soldier->Vehicle->SeatsList[0] && soldier->Vehicle->SeatsList[0]->Player) ? soldier->Vehicle->SeatsList[0]->Player->PlayerName.m_Buffer : L"None",
			(soldier->Vehicle->Get_Actual_Gunner() && soldier->Vehicle->Get_Actual_Gunner()->Player) ? soldier->Vehicle->Get_Actual_Gunner()->Player->PlayerName.m_Buffer : L"None"
		);
		PagePlayer(sender, reply.GetString());
	}
};

class gzChat_HarvStatus : public gzChatCommandBase {
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		static const char *stateName[] = {
			"Going to harvest Tiberium",
			"Harvesting Tiberium",
			"Returning to Refinery to unload Tiberium",
			"Unloading Tiberium",
		};

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

		nc_BaseControllerClass *base = nc_BaseControllerClass::Find_Base(gzData->GetPlayerData()->PlayerType.Get());
		if (!base)
			return;

		nc_RefineryGameObj *refinery = (base->Find_Building(nc_REFINERY) ? base->Find_Building(nc_REFINERY)->As_RefineryGameObj() : NULL);
		if (!refinery)
			PagePlayer(sender, "The Refinery for your team can not be found.");
		else if (refinery->Destroyed)
			PagePlayer(sender, "The Refinery for your team has been destroyed.");
		else if (!refinery->Harvester)
			PagePlayer(sender, "The Harvester for the Refinery of your team can not be found.");
		else
		{
			aString harvMsg = aString::Format("Harvester status: %s - Health: %.0f/%.0f - Current objective: %s",
				(gzStatic_Cast(nc_TrackedVehicleDefClass, refinery->Harvester->Vehicle->Physics->Definition)->MaxEngineTorque == 0.0f ? "Stopped" : "Working"),
				(refinery->Harvester->Vehicle->Defense.Health.Get() + refinery->Harvester->Vehicle->Defense.ShieldStrength.Get()),
				(refinery->Harvester->Vehicle->Defense.HealthMax.Get() + refinery->Harvester->Vehicle->Defense.ShieldStrengthMax.Get()),
				stateName[refinery->Harvester->State - 1]
			);
			if (refinery->Harvester->State == 2)
				harvMsg += aString::Format(" (Estimated time left: %.2f second(s))", refinery->Harvester->HarvestTimeCounter);
			else if (refinery->Harvester->State == 4)
				harvMsg += aString::Format(" (Estimated time left: %.2f second(s))", refinery->UnloadTimeCounter);
			PagePlayer(sender, harvMsg.GetString());
		}
	}
};


/*************/
/* Observers */
/*************/
void gzObserverVehicle::Created(nc_ScriptableGameObj *obj)
{
	this->m_vehicle = obj->As_VehicleGameObj();

	if (((nc_VehicleGameObjDef *)obj->definition)->VehicleType == 4 || obj->As_PhysicalGameObj()->Physics->As_DecorationPhysClass())
	{
		this->Destroy_Script();
		return;
	}

	this->m_flipKilled = false;

	if (gzGameMgr->m_settings->m_LogVehiclePurchase && gzStatic_Cast(nc_VehicleGameObjDef, obj->definition)->VehicleType != 4)
		gzLogger("_VEHICLE", "%s created", gzTranslator->Get(obj->definition->Get_Name()));
}
void gzObserverVehicle::Custom(nc_ScriptableGameObj *obj, int message, int param, nc_ScriptableGameObj *sender)
{
	switch (message)
	{
		case 309475:
			this->m_flipKilled = true;
			break;
	}
}
void gzObserverVehicle::Killed(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter)
{
	if (obj->As_VehicleGameObj()->SeatsList.Length() > 0 || gzStatic_Cast(nc_VehicleGameObjDef, obj->definition)->VehicleType == 4)
	{
		if (!shooter)
		{
			if (this->m_flipKilled)
				gzLogger("_VEHKILL", "%s was flip-killed", gzTranslator->Get(obj));
			else
				gzLogger("_VEHKILL", "%s was destroyed", gzTranslator->Get(obj));
		}
		else if (shooter->As_SoldierGameObj() && shooter->As_SoldierGameObj()->Player)
		{
			gzPlayer *gzData = gzPlayerManager::Find(shooter->As_SoldierGameObj()->Player);
			aString preset = ::GetPresetInfo(shooter->As_PhysicalGameObj(), true);
			if (gzData && gzData->GetHitLog() && gzData->GetHitLog()->GetHitCount(obj->NetworkID, 1) > 0)
			{
				gzHitLogStruct *hitData = gzData->GetHitLog()->GetLast(obj->NetworkID);
				gzLogger("_VEHKILL", "%s destroyed thanks to %ls (%s); LP: %c; RD: %.2f; Hits: %d",
					gzTranslator->Get(obj),
					shooter->As_SoldierGameObj()->Player->PlayerName.m_Buffer,
					preset.GetString(),
					gzPlayerManager::Find(shooter->As_SoldierGameObj()->Player)->IsLastPrimaryFire() ? 'P' : 'S',
					hitData->m_damage,
					gzData->GetHitLog()->GetHitCount(obj->NetworkID, 1)
				);
			}
			else
			{
				gzLogger("_VEHKILL", "%s destroyed thanks to %ls (%s)",
					gzTranslator->Get(obj),
					shooter->As_SoldierGameObj()->Player->PlayerName.m_Buffer,
					preset.GetString()
				);
			}
		}
		else
			gzLogger("_VEHKILL", "%s destroyed thanks to the %s", gzTranslator->Get(obj), gzTranslator->Get(shooter));
	}
}
nc_ScriptRegistrant<gzObserverVehicle> gzObserverVehicle_Registrant("gzObserverVehicle", "");

void gzObserverVehicleWreckage::Created(nc_ScriptableGameObj *obj)
{
	for (int i = 0; i < 128; i++)
		this->m_damage[i] = 0.0f;
}
void gzObserverVehicleWreckage::Damaged(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *damager, float damage)
{
	if (damage < 0.0f && damage >= -5.0f)
	{
		if (damager && damager->As_SoldierGameObj() && damager->As_SoldierGameObj()->Player)
			this->m_damage[damager->As_SoldierGameObj()->Player->PlayerId] += -damage;

		if (obj->As_DamageableGameObj()->Defense.Health.Get() == obj->As_DamageableGameObj()->Defense.HealthMax.Get())
		{
			const char	*vehName	= this->Get_Parameter("Vehicle");
			nc_Vector3	movePos		= { 4000.0f, 4000.0f, -999.0f },
						spawnPos;
			obj->Get_Position(&spawnPos);
			spawnPos.Z += 3.0f;
			obj->As_PhysicalGameObj()->Set_Position(movePos);

			// Clean objects to prevent stuck
			for (nc_GenericSLNode<nc_SmartGameObj> *objList = nc_GameObjManager::SmartGameObjList->HeadNode; objList != NULL; objList = objList->NodeNext)
			{
				nc_Vector3 objPos;
				objList->NodeData->Get_Position(&objPos);
				if (gzCommands->Get_Distance(spawnPos, objPos) <= 5.0f)
					gzCommands->Apply_Damage(objList->NodeData, 99999.0f, "Laser_NoBuilding", NULL);
			}

			// Create new vehicle
			nc_ScriptableGameObj *newVeh = nc_ObjectLibraryManager::Create_Object(vehName);
			newVeh->As_PhysicalGameObj()->Set_Position(spawnPos);
			gzCommands->Set_Facing(newVeh, gzCommands->Get_Facing(obj));
			newVeh->As_PhysicalGameObj()->Defense.Health = 1.0f;
			newVeh->As_PhysicalGameObj()->Defense.ShieldStrength = 0.0f;
			newVeh->As_PhysicalGameObj()->PlayerType = -2;
			newVeh->Start_Observers();

			// Most repaired player bind to him automatically if limit is not reached
			if (gzVehicleMgr->GetSettings()->m_bindWreckageToMostRepair)
			{
				int mostRepairId = -1;
				float mostRepair = 0.0f;
				for (int i = 1; i <= 127; i++)
				{
					if (this->m_damage[i] > mostRepair)
					{
						mostRepairId = i;
						mostRepair = this->m_damage[i];
					}
				}
				if (mostRepairId != -1)
				{
					gzPlayer *gzData = gzPlayerManager::Find(mostRepairId);
					if (gzData)
					{
						if (gzData->GetVehicleData()->Set(VO_BIND, newVeh->NetworkID) == VORESP_SUCCESS)
							PagePlayer(mostRepairId, "The vehicle has bound to you because you repaired the most.");
					}
				}
			}
			obj->Set_Delete_Pending();
		}
	}
}
nc_ScriptRegistrant<gzObserverVehicleWreckage> gzObserverVehicleWreckage_Registrant("gzObserverVehicleWreckage", "Vehicle:string");


/********************/
/* Console commands */
/********************/
class UnbindConsoleFunction : public gzConsoleCommand {
public:
	char *Get_Name(void)
	{
		return "unbind";
	}
	char *Get_Help(void)
	{
		return "UNBIND <player> - Unbind all vehicles that has bound to <player>.";
	}
	void Activate(char *text)
	{
		if (!*text)
			return;
		gzPlayer *pData = gzPlayerManager::Find(atoi(text));
		if (pData)
		{
			pData->GetVehicleData()->UnsetAll();
			PagePlayer(pData->GetPlayerData()->PlayerId, "You've been forced to unbind your bound vehicles.");
		}
	}
};
UnbindConsoleFunction unbind;
