#include "stinc.h"
#include "stsetting.h"
#include "stmisc.h"
#include "stchat.h"
#include "sttranslate.h"
#include "stconsolecommands.h"
#include "stplayer.h"
#include "stgame.h"
#include "stgameaow.h"
#include "stgamectf.h"
#include "stgameinfonly.h"
#include "stgamesniper.h"
#include "stgamedm.h"

// Forward
#if ISDEV()
class gzChat_UseFDSCmd;
#endif
class gzChat_SetSpeed;
class gzChat_Time;
class gzChat_Donate;
class gzChat_TeamDonate;
class gzChat_Parachute;


/************/
/* Settings */
/************/
gzSettingsGameClass::gzSettingsGameClass()
{
	this->m_confModTime = 0;

	this->m_GameMode = 1;
	this->m_ForceTeam = -1;
	this->m_EnableVehicleWreckages = false;
	this->m_EnableInvincibleBuildings = false;
	this->m_EnableBeacon = true;
	this->m_EnableWeaponDrop = false;
	this->m_SuicideResetNegativeCredits = false;

	this->Load();
}
void gzSettingsGameClass::Delete()
{
	this->m_SpawnChar[0].Free_String();
	this->m_SpawnChar[1].Free_String();
	this->m_DisablePreset.Clear();
	this->m_LogRoot.Free_String();

	this->m_DisableSpawner.Clear();
}
void gzSettingsGameClass::Load()
{
	if (!this->Open("STGM.ini"))
		stConsole::Out("[Warning] STGM.ini can not be loaded or not found. Default settings will be used.");

	// Cleanups
	this->Delete();

	// Log settings
	this->m_LogRoot            = this->Load_String("General", "LogRoot", "gzaow"); // This config must be loaded before everything
	this->m_EnableGamelog      = this->Load_Bool("EnableGamelog", false, false, true);
	this->m_LogSoldierPurchase = this->Load_Bool("LogSoldierPurchase", true, false, true);
	this->m_LogSoldierDeath    = this->Load_Bool("LogSoldierDeath", true, false, true);
	this->m_LogVehiclePurchase = this->Load_Bool("LogVehiclePurchase", true, false, true);
	this->m_LogVehicleDeath    = this->Load_Bool("LogVehicleDeath", true, false, true);

	// Game settings
	this->m_GameMode					= this->Load_Int("GameMode", 1);
	this->m_ForceTeam					= this->Load_Int("ForceTeam", -1);
	this->m_EnableVehicleWreckages		= this->Load_Bool("EnableVehicleWreckages", false);
	this->m_EnableInvincibleBuildings	= this->Load_Bool("EnableInvincibleBuildings", false);
	this->m_EnableBeacon				= this->Load_Bool("EnableBeacon", false);
	this->m_EnableWeaponDrop			= this->Load_Bool("EnableWeaponDrop", false);
	this->m_SuicideResetNegativeCredits = this->Load_Bool("SuicideResetNegativeCredits", false, false, true);
	this->m_EnableInfAmmo				= this->Load_Bool("InfiniteAmmo", false);
	this->m_NoReload					= this->Load_Bool("NoReload", false);
	this->m_SpawnChar[0] = this->Load_String("SpawnCharNod", "CnC_Nod_Minigunner_0", "CnC_Nod_Minigunner_0");
	this->m_SpawnChar[1] = this->Load_String("SpawnCharGDI", "CnC_GDI_Minigunner_0", "CnC_GDI_Minigunner_0");

	aString disPreset = this->Load_String("General", "DisablePreset", "");
	aToken disPresetTok(disPreset.GetString());
	for (int i = 1; i <= disPresetTok.numtok(' '); i++)
		this->m_DisablePreset.Add(disPresetTok.gettok(i, ' ').GetData().GetString());

	this->m_RefineryIncomeTick = this->Load_Float("RefineryIncomeTick", 0.0f);
	this->m_RefineryIncomeDump = this->Load_Float("RefineryIncomeDump", 0.0f);
	this->m_AllowAfkSeconds    = this->Load_Float("AllowAFKMinutes", 10.0f, false, true) * 60.0f;
	this->m_AllowHillAttack    = this->Load_Bool("AllowHillAttack", true, true, false);
	this->m_AlwaysStart        = this->Load_Bool("AlwaysStart", false, false, true);
	this->m_MinimumBandwidth   = this->Load_Int("MinimumBandwidth", 56000, false, true);
	this->m_InvincibleSecs      = this->Load_Int("InvincibleSpawn", 0, false, true);

	// Weather
	this->m_EnableWeather = this->Load_Bool("EnableWeather", true, true, false);
	aString weatherType = this->Load_String("WeatherType", "NOWEATHER");
	if (weatherType == "NOWEATHER")
	{
		this->m_EnableWeather = false;
		this->m_WeatherType = WEATHER_NONE;
	}
	else if (weatherType == "Ash")
		this->m_WeatherType = WEATHER_ASH;
	else if (weatherType == "Rain")
		this->m_WeatherType = WEATHER_RAIN;
	else if (weatherType == "Snow")
		this->m_WeatherType = WEATHER_SNOW;
	else
	{
		this->m_EnableWeather = false;
		this->m_WeatherType = WEATHER_NONE;
		stConsole::Out("Error - Unknown weather type: %s; Weather has been disabled.\n", weatherType.GetString());
	}
	weatherType.Free_String();

	this->m_AllowHillAttack = this->Load_Bool("AllowHillAttack", true, false, true);

	if (!this->m_EnableInfAmmo)
		this->m_NoReload = false;
	if (this->m_AllowAfkSeconds < 60.0f)
	{
		this->m_AllowAfkSeconds = 60.0f;
		stConsole::Out("Error - AllowAfkMinutes must be equal or larger than 1. AllowAFKMinutes has been set to 1.\n");
	}

	if (this->m_AlwaysStart)
	{
		unsigned char Bytes[] = { 0xBA, 0x01, 0x00, 0x00, 0x00 };
		PatchByte(0xEB, 1, 0x478A93);
		PatchData(&Bytes, 5, 0x472C95);
		PatchByte(0x90, 1, 0x472C9A);
	}
	else
	{
		unsigned char Bytes[] = { 0x8B, 0x97, 0xF8, 0x01, 0x00, 0x00 };
		PatchByte(0x7E, 1, 0x478A93);
		PatchData(&Bytes, 6, 0x472C95);
	}

	this->m_DisableSpawner.Clear();
	aString disSpawner = this->Load_String("DisableSpawner", "", true, false);
	aToken spawnerToken(disSpawner.GetString());
	if (!disSpawner.IsEmpty())
	{
		for (int i = 1; i <= spawnerToken.numtok(' '); i++)
			this->m_DisableSpawner.Add(spawnerToken.gettok(i, ' ').ToLong());
	}

#if VERC(1, 0, 1)
	this->m_disableBuildings.Clear();
	aToken disBuilding(this->Load_String("DisableBuildings", "", true, false));
	for (int i = 1; i <= disBuilding.numtok(' '); i++)
		this->m_disableBuildings.Add(disBuilding.gettok(i, ' ').GetData());
#endif

	this->Close();
}


/****************/
/* Game manager */
/****************/
gzGameManager *gzGameMgr = NULL;
gzGameManager::gzGameManager()
{
	this->RegisterEvent(EVT_GAME_LEVEL_LOADED);
	this->RegisterEvent(EVT_GAME_LEVEL_ENDED);

	this->AddObserver("Nod_Obelisk", "gzObelisk_Powerup", NULL);
	this->AddObserver(0x3001, "gzGamelogObserver", NULL);
	this->AddObserver(0x3006, "gzGamelogObserver", NULL);
	this->AddObserver(0x3010, "gzGamelogObserver", NULL);
	this->AddObserver(0x3016, "gzGamelogObserver", NULL);
	this->AddObserver(0xD001, "gzGamelogObserver", NULL);

	this->AddObserver(0x3001, "gzObserverSoldier");
	this->AddObserver(0x3006, "gzObserverC4");
	this->AddObserver(0x3010, "gzObserverVehicle");
	this->AddObserver(0x3016, "gzObserverBeacon");
	this->AddObserver(0xD001, "gzObserverBuilding");

#if ISDEV()
	this->AddObserver("Nod_Obelisk", "gzObserver_Nod_Obelisk", NULL);
#endif

	this->m_settings = new gzSettingsGameClass;
	this->m_settings->SetOwner(this);
	this->m_gameMode	= NULL;
	this->m_weatherMgr	= NULL;
	this->m_gamelogFile	= NULL;
	remove(GAMELOG_FILENAME);

	gzChatCommand_Reg<gzChat_SetSpeed>   gzChat_SetSpeed_Reg("!setspeed,!ss");
	gzChatCommand_Reg<gzChat_Time>       gzChat_Time_Reg("!time");
	gzChatCommand_Reg<gzChat_Donate>     gzChat_Donate_Reg("!d,!donate");
	gzChatCommand_Reg<gzChat_TeamDonate> gzChat_TeamDonate_Reg("!tdonate");
	gzChatCommand_Reg<gzChat_Parachute> gzChat_Parachute_Reg("!p,!para,!parachute", MSG_ALL | MSG_TEAM, "Parachute");

#if ISDEV()
	gzChatCommand_Reg<gzChat_UseFDSCmd> gzChat_UseFDSCmd_Reg("!fds");
#endif
}
void gzGameManager::Delete()
{
	if (this->m_gameMode)
		this->m_gameMode->SetDeletePending();

	this->m_settings = NULL;
	if (this->m_gamelogFile)
	{
		fclose(this->m_gamelogFile);
		this->m_gamelogFile = NULL;
		gzGamelogBase::RenameLog();
	}
}
void gzGameManager::Settings_Loaded()
{
	if (this->m_settings->m_EnableGamelog)
		this->m_gamelogFile = fopen(GAMELOG_FILENAME, "w");
	gzGamelogBase::Log("2.03;%s", cGame->MapName.m_Buffer);
	gzGamelogBase::Log("CONFIG;%u;%ls", cGame->TimeLimit_Minutes, cGame->GameTitle.m_Buffer);

	if (this->m_settings->m_DisableSpawner.Count() > 0)
	{
		for (int i = 0; i < nc_SpawnManager::SpawnerList.Count(); i++)
		{
			for (unsigned int j = 0; j < this->m_settings->m_DisableSpawner.Count(); j++)
			{
				if (nc_SpawnManager::SpawnerList[i]->ID == this->m_settings->m_DisableSpawner[j])
				{
#ifdef DEVMSG
					stConsole::Out("[Spawner] Deleted spawner: %d; Team: %d; Primary: %s\n",
						nc_SpawnManager::SpawnerList[i]->ID,
						nc_SpawnManager::SpawnerList[i]->Definition->PlayerType,
						(nc_SpawnManager::SpawnerList[i]->Definition->IsPrimary ? "Yes" : "No")
					);
#endif
					nc_SpawnManager::SpawnerList.Delete(i);
					i--;
					break;
				}
			}
		}
	}

	if (this->m_gameMode != NULL)
		this->m_gameMode->SetDeletePending();
	switch (this->m_settings->m_GameMode)
	{
		//case GAMEMODE_CTF:
		//	this->m_gameMode = new gzGameCTF;
		//	break;

		case GAMEMODE_INFONLY:
			this->m_gameMode = new gzGameInfOnly;
			break;

		case GAMEMODE_SNIPER:
			this->m_gameMode = new gzGameSniper;
			break;

		case GAMEMODE_500SNIP:
			this->m_gameMode = new gzGame500Snip;
			break;

		//case GAMEMODE_DM:
		//	this->m_gameMode = new gzGameDM;
		//	break;

		// Default is AOW
		case GAMEMODE_AOW:
		default:
			this->m_gameMode = new gzGameAow;
			break;
	}
	gzLogger("_GENERAL", "%s is using game mode: %s",
		cGame->MapName.m_Buffer,
		this->m_gameMode->ModeName()
	);
}
void gzGameManager::Level_Loaded()
{
	for (unsigned int i = 0; i < this->m_observerList.Count(); i++)
	{
		if (this->m_observerList[i].preset)
		{
			nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Find_Named_Definition(this->m_observerList[i].preset, true);
			if (Definition && this->m_observerList[i].name && *this->m_observerList[i].name)
			{
				nc_StringClass Observer, Parameter;
				Observer.Get_String(0, false);
				Observer.Format("%s", this->m_observerList[i].name);
				gzStatic_Cast(nc_ScriptableGameObjDef, Definition)->Scripts.Add(Observer);
				Observer.Free_String();

				Parameter.Get_String(0, false);
				if (this->m_observerList[i].param)
					Parameter.Format("%s", this->m_observerList[i].param);
				else
					Parameter = "";

				gzStatic_Cast(nc_ScriptableGameObjDef, Definition)->Parameters.Add(Parameter);
				Parameter.Free_String();
			}
		}
		else if (this->m_observerList[i].type > 0)
		{
			for (nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Get_First(this->m_observerList[i].type, true); Definition != NULL; Definition = nc_DefinitionMgrClass::Get_Next(Definition, this->m_observerList[i].type, true))
			{
				if (this->m_observerList[i].name && *this->m_observerList[i].name)
				{
					nc_StringClass Observer, Parameter;
					Observer.Get_String(0, false);
					Observer.Format("%s", this->m_observerList[i].name);
					gzStatic_Cast(nc_ScriptableGameObjDef, Definition)->Scripts.Add(Observer);
					Observer.Free_String();

					Parameter.Get_String(0, false);
					if (this->m_observerList[i].param && *this->m_observerList[i].param)
						Parameter.Format("%s", this->m_observerList[i].param);
					else
						Parameter = "";

					gzStatic_Cast(nc_ScriptableGameObjDef, Definition)->Parameters.Add(Parameter);
					Parameter.Free_String();
				}
			}
		}
	}
	for (nc_GenericSLNode<nc_BuildingGameObj> *bList = nc_GameObjManager::BuildingGameObjList->HeadNode; bList != NULL; bList = bList->NodeNext)
	{
		for (unsigned int i = 0; i < this->m_observerList.Count(); i++)
		{
			if (this->m_observerList[i].type == 0xD001)
			{
				nc_GameObjObserverClass *observer = nc_ScriptManager::Create_Script(this->m_observerList[i].name);
				if (observer)
					bList->NodeData->Insert_Observer(observer);
			}
		}

		if (bList->NodeData->As_RefineryGameObj())
		{
			switch (gzGameMgr->m_settings->m_GameMode)
			{
				case GAMEMODE_AOW:
				case GAMEMODE_CTF:
				case GAMEMODE_INFONLY:
				{
					// Lower than 0.0f is not valid
					if (this->m_settings->m_RefineryIncomeTick > 0.0f)
						((nc_RefineryGameObjDef *)bList->NodeData->definition)->FundsDistribedPerSec = this->m_settings->m_RefineryIncomeTick;

					// Lower than 0.0f is not valid
					if (this->m_settings->m_RefineryIncomeDump > 0.0f)
						((nc_RefineryGameObjDef *)bList->NodeData->definition)->FundsGathered = this->m_settings->m_RefineryIncomeDump;
					break;
				}
			}
		}
	}

	// Weather
	if (this->m_weatherMgr)
	{
		this->m_weatherMgr->SetDeletePending();
		this->m_weatherMgr = NULL;
	}
	if (this->m_settings->m_EnableWeather)
		this->m_weatherMgr = new gzWeatherManager;

	// Hourglass hill attack blocker
	if (!stricmp(cGame->MapName.m_Buffer, "C&C_Hourglass.mix") && !this->m_settings->m_AllowHillAttack)
	{
		// First laser wall
		nc_Vector3 pos = { -36.698486, 197.356888, 26.152885 };
		nc_SimpleGameObj *LaserWall = gzStatic_Cast(nc_SimpleGameObj, nc_ObjectLibraryManager::Create_Object("Simple_Sydney_SandM_Wall"));
		LaserWall->Set_Position(pos);
		LaserWall->Set_Object_Dirty_Bit(nc_DB_CREATION, false);

		// Second laser wall
		pos.X = -36.698486; pos.Y = 197.356888; pos.Z = 22.152885;
		LaserWall = gzStatic_Cast(nc_SimpleGameObj, nc_ObjectLibraryManager::Create_Object("Simple_Sydney_SandM_Wall"));
		LaserWall->Set_Position(pos);
		LaserWall->Set_Object_Dirty_Bit(nc_DB_CREATION, false);
	}

	this->m_isWaitingNextMap = false;

	// Change maximum Proximity C4 ammo to 6 to the weapon (Clip: 1; Inventory: 5)
	nc_DefinitionClass *proxyC4Pow = nc_DefinitionMgrClass::Find_Definition(81950132, true);
	if (proxyC4Pow)
	{
		nc_WeaponDefinitionClass *proxyC4WeaponDef = gzStatic_Cast(nc_WeaponDefinitionClass, nc_DefinitionMgrClass::Find_Definition(gzStatic_Cast(nc_PowerUpGameObjDef, proxyC4Pow)->GrantWeaponID, true));
		if (proxyC4WeaponDef)
			gzStatic_Cast(nc_PowerUpGameObjDef, proxyC4Pow)->GrantWeaponRounds = (proxyC4WeaponDef->ClipSize.Get() + proxyC4WeaponDef->MaxInventoryRounds.Get());
	}
}
void gzGameManager::Level_Ended()
{
	this->m_isWaitingNextMap = true;

	const char *winType[] = {
		"Server shutdown",
		"",
		"High score when time limit expired",
		"Building Destruction",
		"Pedestal Beacon",
	};
	if (this->m_gamelogFile)
	{
		nc_cTeam *Teams[2] = {
			nc_cTeamManager::Find_Team(0),
			nc_cTeamManager::Find_Team(1),
		};
		gzGamelogBase::Log("WIN;%s;%s;%d;%d",
			((nc_cTeamManager::Team_Array[0]->TeamId == 1) ? "GDI" : "Nod"),
			winType[cGame->WinType],
			Teams[0]->Score,
			Teams[1]->Score
		);
		fclose(this->m_gamelogFile);
		this->m_gamelogFile = NULL;
		gzGamelogBase::RenameLog();
	}
	gzLogger("_GENERAL", "Current game on map %s has ended. %s won by %s after %s of gameplay",
		cGame->MapName.m_Buffer,
		((nc_cTeamManager::Team_Array[0]->TeamId == 1) ? "GDI" : "Nod"),
		winType[cGame->WinType],
		SecsToAscTime(cGame->GameDuration_Seconds).GetString()
	);
}
void gzGameManager::AddObserver(unsigned long type, const char *name, const char *param)
{
	stObserverDataStruct ob;
	ob.type = type;
	ob.preset = NULL;
	ob.name = name;
	ob.param = (param ? param : "");
	this->m_observerList.Add(ob);
}
void gzGameManager::AddObserver(const char *preset, const char *name, const char *param)
{
	stObserverDataStruct ob;
	ob.type = 0;
	ob.preset = preset;
	ob.name = name;
	ob.param = param;
	this->m_observerList.Add(ob);
}


/*******************/
/* Weather manager */
/*******************/
gzWeatherManager::gzWeatherManager()
{
	this->RegisterEvent(EVT_GAME_THINK);

	this->m_timer = 0.5f;
	this->m_clouds = stRandom.Get_Float(0.0f, 1.0f);
}
void gzWeatherManager::Think()
{
	SubFrameTime(this->m_timer, true);

	// Times up
	if (this->m_timer < 0.0f)
	{
		// Set clouds
		if (this->m_clouds < this->m_range)
		{
			if ((this->m_clouds + 0.001f) >= this->m_range)
				this->m_clouds = this->m_range;
			else
				this->m_clouds += 0.001f;
		}
		else if (this->m_clouds > this->m_range)
		{
			if ((this->m_clouds - 0.001f) <= this->m_range)
				this->m_clouds = this->m_range;
			else
				this->m_clouds -= 0.001f;
		}
		gzCommands->Set_Clouds(this->m_clouds, this->m_clouds, 1.0f);

		// Set winds
		float winds = (this->m_clouds * 10.0f);
		gzCommands->Set_Wind(0.0f, winds, 1.0f, true);

		// Fog - Increase 2-5kbps bandwidth usage per player
		//gzCommands->Set_Fog_Enable(true);
		//gzCommands->Set_Fog_Range(((-175.0f * this->m_clouds) + 275.0f), 300.0f, 1.0f);

		// Change weather status
		switch (gzGameMgr->m_settings->m_WeatherType)
		{
			case WEATHER_RAIN:
				if (this->m_clouds >= 0.625f)
				{
					float rain = ((2.0f * this->m_clouds) - 0.2f);
					gzCommands->Set_Rain(rain, rain, true);
				}
				else
					gzCommands->Set_Rain(0.0f, 0.0f, true);

				if (this->m_clouds >= 0.875f)
				{
					float lightning = ((8.0f * (this->m_clouds + 0.125f)) - 8.0f);
					gzCommands->Set_Lightning(lightning, 0.0f, 1.0f, 0.0f, 1.0f, true);
				}
				else
					gzCommands->Set_Lightning(0.0f, 0.0f, 1.0f, 0.0f, 1.0f, true);
				break;
				
			case WEATHER_SNOW:
				if (this->m_clouds >= 0.625f)
				{
					float snow = (((8.0f * this->m_clouds) - 5.0f) / 3.0f);
					gzCommands->Set_Snow(snow, snow, true);
				}
				else
					gzCommands->Set_Snow(0.0f, 0.0f, true);
				break;

			case WEATHER_ASH:
				if (this->m_clouds >= 0.625f)
				{
					float ash = (((8.0f * this->m_clouds) - 5.0f) / 3.0f);
					gzCommands->Set_Ash(ash, ash, true);
				}
				else
					gzCommands->Set_Ash(0.0f, 0.0f, true);
				break;
		}

		// Stop weather
		if (this->m_clouds == this->m_range)
			this->m_range = stRandom.Get_Float(0.0f, 1.0f);

		this->m_timer = 0.5f;
	}
}


/******************/
/* Game mode base */
/******************/
gzGameModeBase::gzGameModeBase()
{
	this->RegisterEvent(EVT_OBJECT_SQUISH);
	this->RegisterEvent(EVT_OBJECT_JUMP_START);

	this->m_settings = gzGameMgr->m_settings;
}
void gzGameModeBase::Object_JumpStart(gzEventObjectJump &evt)
{
	for (int i = 0; i < evt.m_soldier->Observers.Count(); i++)
	{
		if (evt.m_soldier->Observers[i])
			evt.m_soldier->Observers[i]->Custom(evt.m_soldier, 306491, 0, NULL);
	}
}
void gzGameModeBase::Object_SoldierSquished(gzEventObjectKill &evt)
{
	if (evt.m_defender->As_SoldierGameObj())
	{
		for (int i = 0; i < evt.m_defender->Observers.Count(); i++)
		{
			if (evt.m_defender->Observers[i])
				evt.m_defender->Observers[i]->Custom(evt.m_defender, 306492, 1, NULL);
		}
	}
}
bool gzGameModeBase::IsPresetDisabled(const char *preset)
{
	for (unsigned int i = 0; i < this->m_settings->m_DisablePreset.Count(); i++)
	{
		if (this->m_settings->m_DisablePreset[i] == preset)
			return true;
	}
	return false;
}


/***********/
/* Gamelog */
/***********/
const char *gzGamelogBase::m_typeString[] = {
	"",
	"BUILDING",
	"SOLDIER",
	"VEHICLE",
	"OBJECT",
	"OBJECT",
	"CRATE",
	"SCORE",
};
gzGamelogBase::gzGamelogBase()
{
	this->m_objType = 0;
}
void gzGamelogBase::Log(const char *message, ...)
{
	if (gzGameMgr->GetGamelogFileHandler())
	{
		va_list args;
		va_start(args, message);
		int size = _vscprintf(message, args);
		char *fmsg = new char[size + 1];
		vsprintf(fmsg, message, args);
		va_end(args);
		aDateTime now = aDateTime::Now();
		fputs(aString::Format("[%02d:%02d:%02d] %s\n", now.GetHour(), now.GetMinute(), now.GetSecond(), fmsg).GetString(), gzGameMgr->GetGamelogFileHandler());
		delete[] fmsg;
		fflush(gzGameMgr->GetGamelogFileHandler());
	}
}
void gzGamelogBase::RenameLog()
{
	aDateTime now = aDateTime::Now();
	rename(
		GAMELOG_FILENAME,
		aString::Format("gamelog_%s_%d-%d-%04d_%02d_%02d_%02d.txt",
			cGame->MapName.m_Buffer,
			now.GetMonth(),
			now.GetDay(),
			now.GetYear(),
			now.GetHour(),
			now.GetMinute(),
			now.GetSecond()
		).GetString()
	);
}

void gzGamelogObserver::Created(nc_ScriptableGameObj *obj)
{
	nc_Vector3 pos;
	obj->Get_Position(&pos);

	if (obj->As_BuildingGameObj())
		this->m_objType = GAMELOG_BUILDING;

	else if (obj->As_SoldierGameObj())
	{
		if (obj->As_SoldierGameObj()->Player)
			this->Timer_Expired(obj, 100);
		this->m_objType = GAMELOG_SOLDIER;
	}

	else if (obj->As_VehicleGameObj())
	{
		if (((nc_VehicleGameObjDef *)obj->definition)->VehicleType == 4)
			this->m_objType = GAMELOG_BUILDING;
		else
			this->m_objType = GAMELOG_VEHICLE;
	}

	else if (obj->definition->Get_Class_ID() == 0x3006) // C4
		this->m_objType = GAMELOG_C4;

	else if (obj->definition->Get_Class_ID() == 0x3016) // Beacon
		this->m_objType = GAMELOG_BEACON;

	switch (this->m_objType)
	{
		case GAMELOG_SOLDIER:
			this->Log("CREATED;SOLDIER;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%f;%d;%ls",
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z,
				gzCommands->Get_Facing(obj),
				obj->As_DamageableGameObj()->Defense.HealthMax.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrengthMax.Get(),
				obj->As_DamageableGameObj()->PlayerType,
				obj->As_SoldierGameObj()->Player ? obj->As_SoldierGameObj()->Player->PlayerName.m_Buffer : L"(null)"
			);
			break;

		case GAMELOG_BUILDING:
		case GAMELOG_VEHICLE:
		case GAMELOG_CRATE:
			this->Log("CREATED;%s;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%f;%d",
				this->m_typeString[this->m_objType],
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z,
				gzCommands->Get_Facing(obj),
				obj->As_DamageableGameObj()->Defense.HealthMax.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrengthMax.Get(),
				obj->As_DamageableGameObj()->PlayerType
			);
			break;

		case GAMELOG_BEACON:
			this->Log("CREATED;OBJECT;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%f;%d;%d",
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z,
				gzCommands->Get_Facing(obj),
				obj->As_DamageableGameObj()->Defense.HealthMax.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrengthMax.Get(),
				obj->As_DamageableGameObj()->PlayerType,
				(((nc_BeaconGameObj *)obj)->Owner.Reference ? ((nc_BeaconGameObj *)obj)->Owner.Reference->obj->NetworkID : 0)
			);
			break;

		case GAMELOG_C4:
			this->Log("CREATED;OBJECT;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%f;%d;%d",
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z,
				gzCommands->Get_Facing(obj),
				obj->As_DamageableGameObj()->Defense.HealthMax.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrengthMax.Get(),
				obj->As_DamageableGameObj()->PlayerType,
				(((nc_C4GameObj *)obj)->Owner.Reference ? ((nc_C4GameObj *)obj)->Owner.Reference->obj->NetworkID : 0)
			);
			break;
	}
	obj->Start_Observer_Timer(this->GetID(), 2.0f, 101);
}
void gzGamelogObserver::Custom(nc_ScriptableGameObj *obj, int message, int param, nc_ScriptableGameObj *sender)
{
	nc_Vector3 pos[2];
	obj->Get_Position(&pos[0]);
	if (sender)
		sender->Get_Position(&pos[1]);
	switch (this->m_objType)
	{
		case GAMELOG_VEHICLE:
			switch (message)
			{
				case CUSTOM_EVENT_VEHICLE_ENTER:
					this->Log("ENTER;%d;%s;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f",
						obj->NetworkID,
						obj->definition->Get_Name(),
						pos[0].Y,
						pos[0].X,
						pos[0].Z,
						(sender ? sender->NetworkID : 0),
						(sender ? sender->definition->Get_Name() : "(null)"),
						(sender ? pos[1].Y : 0.0f),
						(sender ? pos[1].X : 0.0f),
						(sender ? pos[1].Z : 0.0f)
					);
					break;

				case CUSTOM_EVENT_VEHICLE_EXIT:
					this->Log("EXIT;%d;%s;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f",
						obj->NetworkID,
						obj->definition->Get_Name(),
						pos[0].Y,
						pos[0].X,
						pos[0].Z,
						(sender ? sender->NetworkID : 0),
						(sender ? sender->definition->Get_Name() : "(null)"),
						(sender ? pos[1].Y : 0.0f),
						(sender ? pos[1].X : 0.0f),
						(sender ? pos[1].Z : 0.0f)
					);
					break;
			}
			break;
	}
}
void gzGamelogObserver::Damaged(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *damager, float damage)
{
	nc_Vector3 pos[2];
	obj->Get_Position(&pos[0]);
	if (damager)
		damager->Get_Position(&pos[1]);
	switch (this->m_objType)
	{
		case GAMELOG_BUILDING:
			this->Log("DAMAGED;%s;%d;%s;%.0f;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%.0f;%.0f;%.0f",
				this->m_typeString[this->m_objType],
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos[0].Y,
				pos[0].X,
				pos[0].Z,
				gzCommands->Get_Facing(obj),
				(damager ? damager->NetworkID : 0),
				(damager ? damager->definition->Get_Name() : NULL),
				pos[1].Y,
				pos[1].X,
				pos[1].Z,
				gzCommands->Get_Facing(damager),
				damage,
				obj->As_DamageableGameObj()->Defense.Health.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrength.Get(),
				(obj->As_SoldierGameObj() && obj->As_SoldierGameObj()->Player) ? obj->As_SoldierGameObj()->Player->Score.Get() : 0.0f
			);
			break;
			
		case GAMELOG_SOLDIER:
		case GAMELOG_VEHICLE:
			this->Log("DAMAGED;%s;%d;%s;%.0f;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%.0f;%.0f;%.0f",
				this->m_typeString[this->m_objType],
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos[0].Y,
				pos[0].X,
				pos[0].Z,
				gzCommands->Get_Facing(obj),
				(damager ? damager->NetworkID : 0),
				(damager ? damager->definition->Get_Name() : NULL),
				pos[1].Y,
				pos[1].X,
				pos[1].Z,
				gzCommands->Get_Facing(damager),
				damage,
				obj->As_DamageableGameObj()->Defense.Health.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrength.Get(),
				((damager && damager->As_SoldierGameObj() && damager->As_SoldierGameObj()->Player) ? damager->As_SoldierGameObj()->Player->Score.Get() : 0.0f)
			);
			break;

		case GAMELOG_BEACON:
		case GAMELOG_C4:
		case GAMELOG_CRATE:
			this->Log("DAMAGED;%s;%d;%s;%.0f;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f;%.0f;%f;%.0f;%.0f",
				this->m_typeString[this->m_objType],
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos[0].Y,
				pos[0].X,
				pos[0].Z,
				gzCommands->Get_Facing(obj),
				(damager ? damager->NetworkID : 0),
				(damager ? damager->definition->Get_Name() : NULL),
				pos[1].Y,
				pos[1].X,
				pos[1].Z,
				gzCommands->Get_Facing(damager),
				damage,
				obj->As_DamageableGameObj()->Defense.Health.Get(),
				obj->As_DamageableGameObj()->Defense.ShieldStrength.Get()
			);
			break;
	}
}
void gzGamelogObserver::Destroyed(nc_ScriptableGameObj *obj)
{
	nc_Vector3 pos;
	obj->Get_Position(&pos);

	switch (this->m_objType)
	{
		case GAMELOG_BUILDING:
		case GAMELOG_VEHICLE:
		case GAMELOG_BEACON:
		case GAMELOG_C4:
		case GAMELOG_CRATE:
			this->Log("DESTROYED;OBJECT;%d;%s;%.0f;%.0f;%.0f",
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z
			);
			break;

		case GAMELOG_SOLDIER:
			this->Log("DESTROYED;OBJECT;%d;%s;%.0f;%.0f;%.0f;%.0f",
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos.Y,
				pos.X,
				pos.Z,
				obj->As_SoldierGameObj()->Player->Score.Get()
			);
			break;
	}
}
void gzGamelogObserver::Killed(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter)
{
	nc_Vector3 pos[2];
	obj->Get_Position(&pos[0]);
	nc_PhysicalGameObj *shooterPhys = NULL;
	if (shooter)
	{
		shooter->Get_Position(&pos[1]);
		shooterPhys = shooter->As_PhysicalGameObj();
	}
	const char *weapon = NULL;
	if (shooterPhys)
	{
		nc_ArmedGameObj *shooterArm = shooterPhys->As_ArmedGameObj();
		if (shooterArm)
		{
			if (shooterArm->WeaponBag->current > 0)
				weapon = shooterArm->WeaponBag->Vector[shooterArm->WeaponBag->current]->WeaponDef->Get_Name();
		}
	}

	aString preset[] = {
		GetPresetInfo(obj->As_PhysicalGameObj(), false),
		GetPresetInfo(shooterPhys, true)
	};

	switch (this->m_objType)
	{
		case GAMELOG_BUILDING:
		case GAMELOG_SOLDIER:
		case GAMELOG_VEHICLE:
		case GAMELOG_BEACON:
		case GAMELOG_C4:
		case GAMELOG_CRATE:
			this->Log("KILLED;%s;%d;%s;%.0f;%.0f;%.0f;%.0f;%d;%s;%.0f;%.0f;%.0f;%.0f;%s;%s;%s",
				this->m_typeString[this->m_objType],
				obj->NetworkID,
				obj->definition->Get_Name(),
				pos[0].Y,
				pos[0].X,
				pos[0].Z,
				gzCommands->Get_Facing(obj),
				(shooter ? shooter->NetworkID : 0),
				(shooter ? shooter->definition->Get_Name() : NULL),
				pos[1].Y,
				pos[1].X,
				pos[1].Z,
				gzCommands->Get_Facing(shooter),
				weapon,
				preset[0].GetString(),
				preset[1].GetString()
			);
			break;
	}
}
void gzGamelogObserver::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	switch (number)
	{
		// Player status
		case 100:
			if (!obj->As_SoldierGameObj() || !obj->As_SoldierGameObj()->Player)
				break;

			this->Log("SCORE;%u;%.0f;%.0f",
				obj->NetworkID,
				obj->As_SoldierGameObj()->Player->Score.Get(),
				obj->As_SoldierGameObj()->Player->Money.Get()
			);
			obj->Start_Observer_Timer(this->GetID(), 60.0f, 100);
			break;

		// Object position
		case 101:
			obj->Start_Observer_Timer(this->GetID(), 2.0f, 101);
			break;
	}
}
nc_ScriptRegistrant<gzGamelogObserver> gzGamelogObserver_Reg("gzGamelogObserver", "");


/*************/
/* Observers */
/*************/
void gzObserverBuilding::Created(nc_ScriptableGameObj *obj)
{
	this->m_Building = obj->As_BuildingGameObj();

#if VERC(1, 0, 1)
		for (unsigned int i = 0; i < gzGameMgr->m_settings->m_disableBuildings.Count(); i++)
		{
			
			if (gzGameMgr->m_settings->m_disableBuildings[i] == this->m_Building->definition->Get_Name())
			{
				gzCommands->Apply_Damage(obj, 99999.0f, "Death", NULL);
				break;
			}
		}
#endif
}
void gzObserverBuilding::Damaged(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter, float damage)
{
	if (this->PlayHealth && (gzCommands->Get_Health(obj) + gzCommands->Get_Shield_Strength(obj)) <= ((gzCommands->Get_Max_Health(obj) + gzCommands->Get_Max_Shield_Strength(obj)) * 0.2f)) {
		this->PlayHealth = false;
		char *Announcement = "ERROR";
		if (strstr(obj->definition->Get_Name(), "mp_GDI_Advanced_Guard_Tower"))
			Announcement = "M00BGAT_HLTH0001I1EVAG_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_GDI_Barracks"))
			Announcement = "M00BGIB_HLTH0001I1EVAG_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_GDI_War_Factory"))
			Announcement = "M00BGWF_HLTH0001I1EVAG_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_GDI_Power_Plant"))
			Announcement = "M00BGPP_HLTH0001I1EVAG_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_GDI_Refinery"))
			Announcement = "M00BGTR_HLTH0001I1EVAG_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_Nod_Obelisk"))
			Announcement = "M00BNOL_HLTH0001I1EVAN_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_Hand_of_Nod"))
			Announcement = "M00BNHN_HLTH0001I1EVAN_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_Nod_Airstrip"))
			Announcement = "M00BNAF_HLTH0001I1EVAN_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_Nod_Power_Plant"))
			Announcement = "M00BNPP_HLTH0001I1EVAN_SND.wav";
		else if (strstr(obj->definition->Get_Name(), "mp_Nod_Refinery"))
			Announcement = "M00BNTR_HLTH0001I1EVAN_SND.wav";

		if (!obj->As_BuildingGameObj()->Destroyed)
		{
			gzCommands->Create_2D_Sound(Announcement);
			gzCommands->Start_Timer(obj,this, 30.0f, 22);
		}
	}
}
void gzObserverBuilding::Killed(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *shooter)
{
	if (shooter)
	{
		aString pInfo = ::GetPresetInfo(shooter->As_PhysicalGameObj(), true);
		gzLogger(
			"_BUILDING",
			"%s destroyed thanks to %ls (%s)",
			gzTranslator->Get(obj->definition->Get_Name()),
			(shooter->As_SoldierGameObj() && shooter->As_SmartGameObj()->Player) ? shooter->As_SmartGameObj()->Player->PlayerName.m_Buffer : L"NotPlayer",
			pInfo.GetString()
		);

		if (shooter->As_SoldierGameObj() && shooter->As_SoldierGameObj()->Player)
		{
			nc_WideStringClass reward;
			reward.Format(L"The %S has been destroyed thanks to you!", gzTranslator->Get(obj->definition->Get_Name()));
			Send_Text_Msg(reward, 2, false, -1, shooter->As_SoldierGameObj()->Player->PlayerId);
			reward.Free_String();
		}

		// Remove the spy crate ability of "shooter" after building destruction
		if (shooter->As_SoldierGameObj() && gzGameMgr->m_settings->m_GameMode == 1)
			shooter->As_SoldierGameObj()->IsVisible = true;
	}
	else
		gzLogger("_BUILDING", "%s was destroyed", gzTranslator->Get(obj->definition->Get_Name()));
}
void gzObserverBuilding::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	if (number == 22)
		this->PlayHealth = true;
}
nc_ScriptRegistrant<gzObserverBuilding> gzBuilding_Registrant("gzObserverBuilding", "");


/**********************/
/* Obelisk effect fix */
/**********************/
void gzObelisk_Powerup::Created(nc_ScriptableGameObj *obj)
{
	this->m_isCharging = false;
}
void gzObelisk_Powerup::Custom(nc_ScriptableGameObj *obj, int message, int param, nc_ScriptableGameObj *sender)
{
	if (message == 2 && !this->m_isCharging)
	{
		this->m_isCharging = true;
		nc_Vector3 pos;
		obj->Get_Position(&pos);
		nc_PowerUpGameObj *effectObj = gzStatic_Cast(nc_PowerUpGameObj, nc_ObjectLibraryManager::Create_Object("Obelisk Effect"));
		effectObj->Set_Position(pos);
		this->m_effectObjId = effectObj->NetworkID;
		gzCommands->Create_Sound("Obelisk_Warm_Up", pos, NULL);
		obj->Start_Observer_Timer(this->GetID(), 3.0f, 1);
	}
}
void gzObelisk_Powerup::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	if (number == 1)
	{
		nc_PhysicalGameObj *effectObj = nc_GameObjManager::Find_PhysicalGameObj(this->m_effectObjId);
		if (effectObj)
			effectObj->Set_Delete_Pending();
		this->m_isCharging = false;
	}
}
nc_ScriptRegistrant<gzObelisk_Powerup> gzObelisk_Powerup_Reg("gzObelisk_Powerup", "");

/*************/
/* Parachute */
/*************/
void gzObserver_Parachute::Created(nc_ScriptableGameObj *obj)
{
	if (obj->As_SoldierGameObj() && obj->As_SoldierGameObj()->Player)
	{
		if (obj->As_SoldierGameObj()->Vehicle)
			nc_TransitionManager::Check(obj->As_SoldierGameObj(), true);
		nc_ScriptableGameObj *floater = nc_ObjectLibraryManager::Create_Object("CnC_Beacon_IonCannon");
		floater->As_DamageableGameObj()->Set_Player_Type(-4);
		floater->As_PhysicalGameObj()->Physics->Set_Model_By_Name("null");
		floater->As_PhysicalGameObj()->Set_Position(gzCommands->Get_Position(obj));

		// Only the player who deploy the parachute will able to see the bound object
		floater->Set_Object_Dirty_Bit(nc_DB_CREATION, false);
		floater->Set_Object_Dirty_Bit(obj->As_SoldierGameObj()->Player->PlayerId, nc_DB_CREATION, true);

		nc_ScriptableGameObj *para = nc_ObjectLibraryManager::Create_Object("Invisible_Object");
		para->As_PhysicalGameObj()->Physics->Set_Model_By_Name("X5D_Parachute");
		gzCommands->Set_Facing(para, gzCommands->Get_Facing(obj));
		gzCommands->Disable_All_Collisions(para);

		obj->As_PhysicalGameObj()->Attach_To_Object_Bone(floater->As_PhysicalGameObj(), "Origin");
		para->As_PhysicalGameObj()->Attach_To_Object_Bone(obj->As_PhysicalGameObj(), "Origin");
		obj->Add_Observer(nc_ScriptManager::Create_Script("M00_No_Falling_Damage_DME"));

		this->m_floaterId   = floater->NetworkID;
		this->m_parachuteId = para->NetworkID;
		obj->Get_Position(&this->m_floaterPos);
		this->m_floaterPos.Z += 0.02f;

		this->Timer_Expired(obj, 1);
	}
	else
		this->Destroy_Script();
}
void gzObserver_Parachute::Destroyed(nc_ScriptableGameObj *obj)
{
	this->Timer_Expired(obj, 2);
}
void gzObserver_Parachute::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	if (number == 1)
	{
		nc_PhysicalGameObj *floater = nc_GameObjManager::Find_PhysicalGameObj(this->m_floaterId);
		if (floater)
		{
			nc_PhysicalGameObj *para = nc_GameObjManager::Find_PhysicalGameObj(this->m_parachuteId);
			nc_Vector3 curFloatPos;
			floater->Get_Position(&curFloatPos);
			if ((this->m_floaterPos.Z - curFloatPos.Z) <= 0.01f)
			{
				obj->Start_Observer_Timer(this->GetID(), 0.0001f, 2);
				return;
			}
			else
				floater->Get_Position(&this->m_floaterPos);
		}
		obj->Start_Observer_Timer(this->GetID(), 0.05f, 1);
	}
	else if (number == 2)
	{
		if (nc_GameObjManager::Find_PhysicalGameObj(this->m_floaterId))
		nc_GameObjManager::Find_PhysicalGameObj(this->m_floaterId)->Set_Delete_Pending();
		if (nc_GameObjManager::Find_PhysicalGameObj(this->m_parachuteId))
			nc_GameObjManager::Find_PhysicalGameObj(this->m_parachuteId)->Set_Delete_Pending();
		obj->Start_Observer_Timer(this->GetID(), 0.5f, 3);
	}
	else if (number == 3)
	{
		for (int i = 0; i < obj->Observers.Count(); i++)
		{
			if (!stricmp(obj->Observers[i]->Get_Name(), "M00_No_Falling_Damage_DME"))
			{
				obj->Remove_Observer(obj->Observers[i]);
				break;
			}
		}
		this->Destroy_Script();
	}
}
nc_ScriptRegistrant<gzObserver_Parachute> gzObserver_Parachute_Reg("gzObserver_Parachute", "");


#if ISDEV()
void gzObserver_Nod_Obelisk::Created(nc_ScriptableGameObj *obj)
{
	this->m_currTarget = 0;
}
void gzObserver_Nod_Obelisk::Enemy_Seen(nc_ScriptableGameObj *obj, nc_ScriptableGameObj *seen)
{
	if (this->m_currTarget == 0)
	{
		this->m_currTarget = seen->NetworkID;
		obj->Start_Observer_Timer(this->GetID(), 0.1f, 1);
		obj->Start_Observer_Timer(this->GetID(), 3.0f, 2);
	}
}
void gzObserver_Nod_Obelisk::Timer_Expired(nc_ScriptableGameObj *obj, int number)
{
	if (number == 1)
	{
		nc_SmartGameObj *target = nc_GameObjManager::Find_SmartGameObj(this->m_currTarget);
		if (target)
		{
			if (obj->As_SmartGameObj()->Is_Object_Visible(target))
				obj->Start_Observer_Timer(this->GetID(), 0.1f, 1);
			else
			{
				this->m_currTarget = 0;
			}
		}
	}

	// Charged
	else if (number == 2)
	{
	}
}
nc_ScriptRegistrant<gzObserver_Nod_Obelisk> gzObserver_Nod_Obelisk_Reg("gzObserver_Nod_Obelisk", "");
#endif

/*****************/
/* Chat commands */
/*****************/
class gzChat_SetSpeed : public gzChatCommandBase {
public:
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(sender);
		if (!pData || !pData->Owner.Reference)
			return;
		bool isSpec = false;
		for (int i = 0; i < pData->Owner.Reference->obj->Observers.Count(); i++)
		{
			if (!stricmp(pData->Owner.Reference->obj->Observers[i]->Get_Name(), "gzSpectator"))
			{
				isSpec = true;
				break;
			}
		}
#if !ISDEV()
		if (isSpec)
		{
#endif
			aToken token(msg);
			int speed = token.gettok(1, ' ').ToLong();
			if (speed < 1 || speed > 50)
			{
				PagePlayer(sender, "Speed out of range.");
				return;
			}
			pData->Owner.Reference->obj->As_SoldierGameObj()->Set_Max_Speed((float)speed);
			PagePlayer(sender, "Your speed has been changed to %.0f.", pData->Owner.Reference->obj->As_SoldierGameObj()->Get_Max_Speed());
#if !ISDEV()
		}
#endif
	}
};

class gzChat_Time : public gzChatCommandBase {
public:
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		PagePlayer(sender, "Current map(%s) has been elapsed %s.", cGame->MapName, SecsToAscTime(cGame->GameDuration_Seconds).GetString());
	}
};

class gzChat_Donate : 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:
			case GAMEMODE_DM:
				PagePlayer(sender, "Donate is not available in %s game mode.", gzGameMgr->GetGame()->ModeName());
		}

		aToken token(msg);

		if (token.numtok(' ') == 1)
		{
			nc_cPlayer *pData = nc_cPlayerManager::Find_Player(sender);
			float amount = 0.0f;
			int dstCount = (nc_cPlayerManager::Tally_Team_Size(pData->PlayerType.Get()) - 1);

			// Donor is the only player in the team
			if (dstCount == 0)
			{
				PagePlayer(sender, "You can NOT donate credit(s) to your team because you are the only one.");
				return;
			}

			// All credits?
			if (!wcsicmp(msg, L"all"))
				amount = pData->Money.Get();
			else
			{
				amount = (float)_wtof(msg);

				// Minimum check
				if (amount < (float)dstCount)
				{
					PagePlayer(sender, "Minimum amount is %d credit(s).", dstCount);
					return;
				}

				// Maximum check
				if (amount > pData->Money.Get())
				{
					PagePlayer(sender, "You don't have %.0f credit(s).", amount);
					return;
				}
			}

			// All checks passed!
			float eachAmount = (amount / (float)dstCount);
			int srcTeam = pData->PlayerType.Get();
			for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList != NULL; pList = pList->NodeNext)
			{
				if (pList->NodeData->IsActive && pList->NodeData != pData)
				{
					if (pList->NodeData->PlayerType.Get() == srcTeam)
					{
						pList->NodeData->Increment_Money(eachAmount);
						PagePlayer(pList->NodeData->PlayerId, "You have received %.0f credit(s) from %ls.", eachAmount, pData->PlayerName.m_Buffer);
					}
				}
			}
			pData->Increment_Money(-amount);
			PagePlayer(pData->PlayerId, "You have distirbuted %.0f credits to your team. (%.0f per player, total %d player(s))", amount, eachAmount, dstCount);
			return;
		}

		// Parameters check
		if (token.numtok(' ') != 2)
		{
			PagePlayer(sender, "Usage: !donate [player] [amount]");
			return;
		}

		// Variables initial
		nc_cPlayer *pData = nc_cPlayerManager::Find_Player(sender);
		float amount = (float)token.gettok(2, ' ').ToDouble();
		
		// Amount check
		if (token.gettok(2, ' ').GetData() == "all")
			amount = pData->Money.Get();

		if (amount < 1.0f)
		{
			PagePlayer(sender, "Minimum amount is 1 credit and submitted amount is less than 1 credit.");
			return;
		}
		if (pData->Money.Get() < amount)
		{
			PagePlayer(sender, "You don't have %.0f credit(s).", amount);
			return;
		}

		// Nickname unique check
		int nickCount = gzPlayerManager::FindByPartNameCount(token.gettok(1, ' ').GetData());
		if (nickCount >= 2 && !gzPlayerManager::Find(token.gettok(1, ' ').GetData()))
		{
			PagePlayer(sender, "Found %d result(s) for \"%s\". Please specify a unique string for the nickname.", nickCount, token.gettok(1, ' ').GetData().GetString());
			return;
		}

		// Destination check
		gzPlayer *dst = gzPlayerManager::FindByPartName(token.gettok(1, ' ').GetData());
		if (!dst)
		{
			PagePlayer(sender, "%s not found.", token.gettok(1, ' ').GetData().GetString());
			return;
		}

		// Self donate check
		if (dst->GetPlayerData() == pData)
		{
			PagePlayer(sender, "You can not donate to yourself!");
			return;
		}

		// Team check
		if (dst->GetPlayerData()->PlayerType.Get() != pData->PlayerType.Get())
		{
			PagePlayer(sender, "You can not donate to enemy!");
			return;
		}

		pData->Increment_Money(-amount);
		dst->GetPlayerData()->Increment_Money(amount);
		PagePlayer(dst->GetPlayerData()->PlayerId, "You have received %.0f credits from %ls.", amount, pData->PlayerName.m_Buffer);
		PagePlayer(sender, "You have donated %.0f credits to %ls.", amount, dst->GetPlayerName().GetString());
	}
};
class gzChat_TeamDonate : public gzChatCommandBase {
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		for (unsigned int i = 0; i < gzChatManagerClass::m_chatCommandList.Count(); i++)
		{
			for (unsigned int j = 0; j < gzChatManagerClass::m_chatCommandList[i]->m_commandList.Count(); j++)
			{
				if (gzChatManagerClass::m_chatCommandList[i]->m_commandList[j] == L"!donate")
				{
					gzChatManagerClass::m_chatCommandList[i]->Activate(L"all", type, sender, receiver, ret);
					return;
				}
			}
		}
	}
};

class gzChat_Parachute : public gzChatCommandBase {
public:
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		gzPlayer *gzData = gzPlayerManager::Find(sender);

		if (gzData == NULL || gzData->GetSoldier() == NULL)
			return;

		if (gzData->GetSoldier()->Vehicle == NULL)
			PagePlayer(sender, "You must be in a vehicle to use this command.");
		else if (gzStatic_Cast(nc_VehicleGameObjDef, gzData->GetSoldier()->Vehicle->definition)->VehicleType != 3)
			PagePlayer(sender, "You must be in a VTOL vehicle to use this command.");
		else
		{
			if (*msg)
			{
			}
			else
			{
				for (int i = 0; i < gzData->GetSoldier()->Observers.Count(); i++)
				{
					if (!stricmp(gzData->GetSoldier()->Observers[i]->Get_Name(), "gzObserver_Parachute"))
						return;
				}
				gzData->GetSoldier()->Add_Observer(nc_ScriptManager::Create_Script("gzObserver_Parachute"));
			}
		}
	}
};

#if ISDEV()
class gzChat_UseFDSCmd : public gzChatCommandBase {
public:
	void Activate(const wchar_t *msg, int type, int sender, int receiver, aWideString &ret)
	{
		aToken c(msg);
		aString cmd = c.gettok(1, ' ').GetData();
		for (int i = 0; i < nc_ConsoleFunctionManager::FunctionList.Count(); i++)
		{
			if (cmd == nc_ConsoleFunctionManager::FunctionList[i]->Get_Name() || (nc_ConsoleFunctionManager::FunctionList[i]->Get_Alias() != NULL && cmd == nc_ConsoleFunctionManager::FunctionList[i]->Get_Alias()))
			{
				nc_ConsoleFunctionManager::FunctionList[i]->Activate((char *)c.gettok(2, c.numtok(' ') -1, ' ').GetData().GetString());
				break;
			}
			if (i == (nc_ConsoleFunctionManager::FunctionList.Count() -1))
				PagePlayer(sender, "Command \"%s\" not found.", c.gettok(1,' ').GetData().GetString());
		}
		//nc_cPlayer *pData = nc_cPlayerManager::Find_Player(sender);
		//aToken c(msg);
		//char *cmd = new char[strlen(c.gettok(1,' ').ToChar()) +1];
		//strcpy(cmd, c.gettok(1, ' ').ToChar());
		//for (int i = 0; i < nc_ConsoleFunctionManager::FunctionList.Count(); i++)
		//{
		//	if (!stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Name(), cmd) || (nc_ConsoleFunctionManager::FunctionList[i]->Get_Alias() != NULL && !stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Alias(), cmd)))
		//	{
		//		nc_ConsoleFunctionManager::FunctionList[i]->Activate((char *)c.gettok(2, c.numtok(' ') -1, ' ').ToChar());
		//		break;
		//	}
		//	if (i == (nc_ConsoleFunctionManager::FunctionList.Count() -1))
		//		PagePlayer(sender, "Command \"%s\" not found.", c.gettok(1,' ').ToChar());
		//}
		//delete [] cmd;
	}
};
#endif


/********************/
/* Console commands */
/********************/
class BuildingsInfoConsoleFunction : public gzConsoleCommand {
public:
	char *Get_Name(void)
	{
		return "buildinginfo";
	}
	char *Get_Alias(void)
	{
		return "bi";
	}
	char *Get_Help(void)
	{
		return "BUILDINGINFO - Shows the health of all GDI/Nod buildings.";
	}
	void Activate(char *text)
	{
		aString msg[2];
		for (nc_GenericSLNode<nc_BuildingGameObj> *bList = nc_GameObjManager::BuildingGameObjList->HeadNode; bList != NULL; bList = bList->NodeNext)
		{
			msg[bList->NodeData->PlayerType] += gzTranslator->Get(bList->NodeData);
			msg[bList->NodeData->PlayerType] += ' ';
			if (bList->NodeData->Destroyed)
				msg[bList->NodeData->PlayerType] += " 0 0 . ";
			else
			{
				int health = (int)(bList->NodeData->Defense.Health.Get() + bList->NodeData->Defense.ShieldStrength.Get());
				int maxHealth = (int)(bList->NodeData->Defense.HealthMax.Get() + bList->NodeData->Defense.ShieldStrengthMax.Get());
				msg[bList->NodeData->PlayerType] += health;
				msg[bList->NodeData->PlayerType] += ' ';
				msg[bList->NodeData->PlayerType] += maxHealth;
				msg[bList->NodeData->PlayerType] += " . ";
			}
		}
		msg[0].Resize(msg[0].GetLength() - 3);
		msg[1].Resize(msg[1].GetLength() - 3);
		gzLogger("_BI", msg[1].GetString());
		gzLogger("_BI", msg[0].GetString());
	}
};
BuildingsInfoConsoleFunction buildinginfo;

class PlayerLimitConsoleFunction : public gzConsoleCommand {
public:
	char *Get_Name(void)
	{
		return "plimit";
	}
	char *Get_Help(void)
	{
		return "PLIMIT [0-127] - Set the max player count.";
	}
	void Activate(char *text)
	{
		if (!*text)
			return;

		aToken str(text);
		unsigned int max = str.ToLong();
		if (max > 127)
		{
			stConsole::Out("Usage: %s [0-127]\n", this->Get_Name());
			return;
		}
		cGame->MaxPlayers = max;
		stConsole::Out("Max players limit has been set to %u.\n", max);
	}
};
PlayerLimitConsoleFunction playerlimit;
