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

#pragma warning(disable: 4740)

aVector<HookBase *> gzHookManager::m_list;

/********************/
/* Game event hooks */
/********************/
void __stdcall Hook_LevelLoaded() {
	if (!gzManagerClass::m_initial)
	{
		gzInit = new gzInitClass;
		gzManagerClass::m_initial = true;
	}

	gzEventBase evt;
	evt.SetEventType(EVT_GAME_LEVEL_LOADED);
	EventManager::ProcessEvent(evt);

	_asm {
		mov eax, [0x81FF84]
		mov eax, [eax]
	}
}
HookCallClass llh("Level loaded hook", 0x40244B, &Hook_LevelLoaded);

void __stdcall Proc_GameOver()
{
	gzEventBase evt;
	evt.SetEventType(EVT_GAME_LEVEL_ENDED);
	EventManager::ProcessEvent(evt);
}
__declspec(naked) void Hook_GameOver()
{
	_asm {
		push ecx
		call Proc_GameOver
		pop ecx
		sub esp, 0x10
		push ebx
		push ebp
		push esi
		mov esi, 0x4749B6
		jmp esi
	}
}
HookJumpClass hgo("Game over hook", 0x4749B0, &Hook_GameOver);

void __stdcall Hook_FrameUpdate() {
	if (nc_cNetwork::PServerConnection)
	{
		gzEventBase evt;
		evt.SetEventType(EVT_GAME_THINK);
		EventManager::ProcessEvent(evt);
	}
	nc_GameModeManager::Think();
}
HookCallClass fuh("Frame update hook", 0x43B9CD, &Hook_FrameUpdate);


/**********************/
/* Player event hooks */
/**********************/
void __stdcall Proc_PlayerJoin(nc_cPlayer *pData, nc_WideStringClass *pName)
{
	if (pName->Get_Length() > 20)
	{
		stConsole::Out("[ST] Notice: Nickname \"%ls\" is longer than 20 characters and has been truncated to 20 characters.\n",pName->m_Buffer);
		pName->Resize(21);
		pName->m_Buffer[20] = '\0';
		pData->PlayerName.Resize(21);
		pData->PlayerName.m_Buffer[20] = '\0';
	}
	bool noticeShown = false;

	// Check for invalid characters in nickname
	wchar_t *pNamePtr = pName->m_Buffer;
	while (*pName->m_Buffer)
	{
		switch (*pName->m_Buffer)
		{
			case ' ':
			case ';':
			case ':':
			case '"':
				if (!noticeShown)
				{
					stConsole::Out("[ST] Notice: Nickname \"%ls\" contains disallowed character(s) and has been replaced with underscore.\n",pNamePtr);
					noticeShown = true;
				}
				*pName->m_Buffer = '_';
				break;

			default:
				break;
		}
		*pName->m_Buffer++;
	}

#if VERC(1, 1, 0)
	// Unnamed replacement
	if (!wcsicmp(pData->PlayerName.m_Buffer, L"Unnamed"))
	{
		nc_cRemoteHost* rData = nc_cNetwork::PServerConnection->RemoteList[pData->PlayerId];
		pName->Format(L"%ls", aWideString::Format(L"stGuest_%d", stRandom.GetInt(10000, 99999)).GetString());
		pNamePtr = pName->m_Buffer;
		stConsole::Out("[ST] %s has connected with reserved nickname and has renamed to %ls\n", inet_ntoa(rData->Address.sin_addr), pNamePtr);
	}
#endif

	// Duplicate nickname check after replaced
	pName->m_Buffer = pNamePtr;
	aWideString changedName = pNamePtr;
	for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList != NULL; pList = pList->NodeNext)
	{
		if (pList->NodeData->IsActive && pList->NodeData != pData && !wcsicmp(pNamePtr, pList->NodeData->PlayerName.m_Buffer))
		{
			for (int i = 1; i <= 128; i++)
			{
				pName->Format(L"%ls%d", changedName.GetString(), i);
				if (!nc_cPlayerManager::Find_Player(*pName))
					break;
			}
		}
	}
	pData->PlayerName.Format(L"%ls", pNamePtr);

	gzEventPlayerBase evt;
	evt.SetPlayer(pData);
	evt.SetEventType(EVT_PLAYER_JOIN);
	EventManager::ProcessEvent(evt);

#if VERC(1, 1, 0)
	gzEventPlayerBandwidth bwevt(nc_cNetwork::PServerConnection->RemoteList[pData->PlayerId]->Bandwidth);
	bwevt.SetPlayer(pData);
	bwevt.SetEventType(EVT_PLAYER_BANDWIDTH);
	EventManager::ProcessEvent(bwevt);
	nc_cNetwork::PServerConnection->RemoteList[pData->PlayerId]->Bandwidth = bwevt.GetInt();
#endif
}
__declspec(naked) void Hook_PlayerJoin()
{
	_asm {
		push [esp+8]
		push esi
		call Proc_PlayerJoin
		mov ecx, 0x81FF84
		mov ecx, [ecx]
		mov edx, 0x4B4517
		jmp edx
	}
}
HookJumpClass pjh("Player Join hook", 0x4B4511, &Hook_PlayerJoin);

void __stdcall Proc_PlayerLeft(int pID, unsigned long callFrom)
{
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(pID);
	gzPlayerLeftEnum Type = LEFT_UNKNOWN;
	switch (callFrom)
	{
		case 0x4B50A0: // cClientGoodbyeEvent::Act(void)
			Type = LEFT_NORMAL;
			break;

		case 0x457FCA: // cNetwork::Server_Broken_Connection_Handler(int)
			Type = LEFT_DISCONNECT;
			break;

		case 0x42F9F4: // WolGameModeClass::Kick_Player(wchar_t *)
			Type = LEFT_WOLKICK;
			break;

		case 0x4E46C5: // cGameSpyBanList::Begin_Player_Kick(int)
			Type = LEFT_GAMESPYKICK;
			break;

		case 0x4E3FC1: // cGameSpyAuthMgr::Evict_Player(int)
			Type = LEFT_GAMESPYEVICT;
			break;
	}
	if (pData && Type != LEFT_UNKNOWN)
	{
		gzEventPlayerBase evt;
		evt.SetPlayer(pData);
		evt.SetInt(Type);
		evt.SetEventType(EVT_PLAYER_LEFT);
		EventManager::ProcessEvent(evt);
	}
}
__declspec(naked) void Hook_PlayerLeft()
{
	_asm {
		push [esp] // Call from
		push [esp+8] // Player ID
		call Proc_PlayerLeft
		sub esp, 0x10
		push ebx
		push ebp
		push esi
		mov esi, 0x461B36
		jmp esi
	}
}
HookJumpClass plh("Player Left hook", 0x461B30, &Hook_PlayerLeft);

void __stdcall Hook_PlayerBandwidthChange() {
	nc_cClientBboEvent *Bbo;
	_asm {
		mov Bbo, ecx
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(Bbo->ID);
	if (!pData)
	{
		Bbo->Set_Delete_Pending();
		return;
	}

	gzEventPlayerBandwidth evt(Bbo->Bandwidth);
	evt.SetPlayer(pData);
	evt.SetInt(2);
	evt.SetEventType(EVT_PLAYER_BANDWIDTH);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] BBO request refused by class \"%s\".\n", __FUNCTION__, retObj->GetName());
#endif
		Bbo->Set_Delete_Pending();
		return;
	}
	Bbo->Act();
}
HookCallClass cbr("Client bandwidth change hook", 0x4B4F20, &Hook_PlayerBandwidthChange);

void __stdcall Hook_PlayerSuicide() {
	nc_cSuicideEvent *Suicide;
	nc_cPacket *packet;
	_asm {
		mov Suicide, esi
		mov packet, edi
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(Suicide->ID);
	if (!pData)
	{
		Suicide->Set_Delete_Pending();
		return;
	}
	if (pData->Owner.Reference)
		pData->Owner.Reference->obj->Set_Delete_Pending();
	if (pData)
	{
		if (gzGameMgr->m_settings->m_SuicideResetNegativeCredits || (!gzGameMgr->m_settings->m_SuicideResetNegativeCredits && pData->Money.Get() > 0.0f))
		{
			pData->Money = 0.0f;
			pData->Set_Object_Dirty_Bit(nc_DB_OCCASIONAL, true);
		}

		gzEventPlayerBase evt;
		evt.SetPlayer(pData);
		evt.SetEventType(EVT_PLAYER_SUICIDE);
		EventManager::ProcessEvent(evt);
	}
	nc_WideStringClass wstr;
	wstr.Format(L"%ls committed suicide.", pData->PlayerName.m_Buffer);
	Send_Text_Msg(wstr,All, false, -1, -1);
	wstr.Free_String();
	Suicide->Set_Delete_Pending();
}
HookCallClass suicideh("Player suicide hook [replace]", 0x4BA298, &Hook_PlayerSuicide);

bool __stdcall Proc_cCsTextObj(nc_cCsTextObj *TextObj)
{
	if (TextObj->Type == 2)
	{
		// Poke
		if (TextObj->Receiver == -2)
		{
			aToken pokeMsg(TextObj->Message.m_Buffer);
			nc_PhysicalGameObj *pokedObj = nc_GameObjManager::Find_PhysicalGameObj(pokeMsg.gettok(2, '\n').ToLong());
			gzPlayer *gzData = gzPlayerManager::Find(TextObj->Sender);
			if (pokedObj && gzData)
			{
				gzEventBhsPoke evt(pokedObj);
				evt.SetEventType(EVT_BHS_POKE);
				evt.SetPlayer(gzData);
				EventManager::ProcessEvent(evt);
				return true;
			}
		}
		else if (TextObj->Receiver == -3)
		{
			aToken verMsg(TextObj->Message.m_Buffer);

			// Client bhs.dll version
			if (verMsg.gettok(1, '\n').GetData() == "j")
			{
				gzEventBhsVersion evt((float)verMsg.gettok(3, '\n').ToDouble());
				evt.SetEventType(EVT_BHS_VERSION);
				evt.SetInt(verMsg.gettok(2, '\n').ToLong());
				EventManager::ProcessEvent(evt);
				return true;
			}
		}
	}
	gzEventPlayerChat evt;
	evt.m_sender = TextObj->Sender;
	evt.m_receiver = TextObj->Receiver;
	evt.m_type = TextObj->Type;
	evt.m_message = TextObj->Message.m_Buffer;
	evt.SetEventType(EVT_PLAYER_CHAT);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] Chat message from %ls refused by class \"%s\".\n",
			__FUNCTION__,
			nc_cPlayerManager::Find_Player(TextObj->Sender)->PlayerName.m_Buffer,
			retObj->GetName()
		);
#endif
		TextObj->Set_Delete_Pending();
		return false;
	}

	TextObj->Sender		= evt.m_sender;
	TextObj->Receiver	= evt.m_receiver;
	TextObj->Type		= evt.m_type;
	TextObj->Message.Resize(evt.m_message.GetLength() + 1);
	TextObj->Message.Format(L"%ls", evt.m_message.GetString());

	return true;
}
__declspec(naked) void Hook_cCsTextObj()
{
	_asm {
		push esi
		push esi
		call Proc_cCsTextObj
		pop esi
		test al, al
		jz no_chat
		mov ecx, esi
		mov edx, [esi]
		call [edx+0x68]
no_chat:
		mov edi, 0x4B5D96
		jmp edi
	}
}
HookJumpClass cto("Chat hook", 0x4B5D91, &Hook_cCsTextObj);

bool __stdcall Proc_Radio(nc_CSAnnouncement *Radio)
{
	gzEventPlayerRadio evt;
	evt.m_team = Radio->Team;
	evt.m_sender = Radio->Sender;
	evt.m_key = Radio->Key;
	evt.m_radioID = Radio->RadioID;
	evt.m_toTeamOnly = Radio->SendToTeamOnly;
	evt.SetEventType(EVT_PLAYER_RADIO);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] Radio from %ls refused by class \"%s\".\n",
			__FUNCTION__,
			nc_cPlayerManager::Find_Player(Radio->Sender)->PlayerName.m_Buffer,
			retObj->GetName()
		);
#endif
		Radio->Set_Delete_Pending();
		return false;
	}

	Radio->Team				= evt.m_team;
	Radio->Sender			= evt.m_sender;
	Radio->Key				= evt.m_key;
	Radio->RadioID			= evt.m_radioID;
	Radio->SendToTeamOnly	= evt.m_toTeamOnly;

	return true;
}
__declspec(naked) void Hook_Radio()
{
	_asm {
		push ecx
		push esi
		call Proc_Radio
		test al, al
		pop ecx
		jz no_radio
		mov edx, [esi]
		call [edx+0x68]
no_radio:
		pop edi
		pop esi
		retn 4
	}
}
HookJumpClass rh("Radio hook", 0x4B386F, &Hook_Radio);

void __stdcall Hook_ChangeTeam()
{
	nc_cChangeTeamEvent *ChangeTeam;
	nc_cPacket *packet;
	_asm
	{
		mov ChangeTeam, esi
		mov packet, edi
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(ChangeTeam->ID);
	if (!cGame->IsTeamChangingAllowed) {
		PagePlayer(ChangeTeam->ID, "Team changing is disabled.");
		ChangeTeam->Set_Delete_Pending();
		return;
	}
	int Type = (pData->PlayerType.Get() == 1) ? 0 : 1,
		Time = GetTickCount() - SystemTime - pData->JoinTime;
	if (pData->Score.Get() > 0.0f)
		pData->Score = 0.0f;
	if (pData->Money.Get() > 0.0f && Time >= 30000)
		pData->Money = 0.0f;
	pData->Deaths -= 1;
	pData->PlayerType.Set(Type);
	pData->Set_Object_Dirty_Bit(nc_DB_RARE, true);
	pData->Set_Object_Dirty_Bit(nc_DB_OCCASIONAL, true);
	for (nc_GenericSLNode<nc_BaseGameObj> *objList = nc_GameObjManager::GameObjList->HeadNode; objList != NULL; objList = objList->NodeNext)
	{
		nc_BeaconGameObj *Beacon = As_Beacon((GameObject *)objList->NodeData);
		nc_C4GameObj *C4 = As_C4((GameObject *)objList->NodeData);
		nc_OffenseObjectClass *offense = NULL;
		if (Beacon && Beacon->Get_Owner()->Player->PlayerId == ChangeTeam->ID)
			Beacon->Completely_Damaged(*offense);
		else if (C4 && C4->Owner.Reference->obj->As_SmartGameObj()->Player->PlayerId == ChangeTeam->ID)
			C4->Completely_Damaged(*offense);
	}
	if (pData->Owner.Reference->obj)
		pData->Owner.Reference->obj->Set_Delete_Pending();
	nc_WideStringClass wstr;
	wstr.Get_String(0, false);
	wstr.Format(L"%s has changed to team %s!", pData->PlayerName.m_Buffer, (pData->PlayerType.Get() == 0) ? L"Nod" : L"GDI");
	Send_Text_Msg(wstr, 0, false, -1, -1);
	wstr.Free_String();
	ChangeTeam->Set_Delete_Pending();

	gzEventPlayerBase evt;
	evt.SetPlayer(pData);
	evt.SetEventType(EVT_PLAYER_TEAMCHANGE);
	EventManager::ProcessEvent(evt);
}
HookCallClass tch("Team Change hook", 0x4B4C98, &Hook_ChangeTeam);

void __stdcall Hook_SerialHash()
{
	nc_cPacket *packet;
	nc_cGameSpyCsChallengeResponseEvent *GameSpy;
	_asm
	{
		mov GameSpy, esi
		mov packet, edi
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(GameSpy->ID);
	if (pData)
	{
		gzEventPlayerSerialHash evt;
		evt.SetPlayer(pData);
		evt.m_hash = GameSpy->Hash.m_Buffer;
		evt.SetEventType(EVT_PLAYER_SERIALHASH);
		EventManager::ProcessEvent(evt);
	}
	GameSpy->Act();
}
HookCallClass shh("Serial Hash hook", 0x4B712C, &Hook_SerialHash);

void __stdcall Hook_PlayerRawDamage() {
	nc_cCsDamageEvent *CsDamage;
	_asm
	{
		mov CsDamage, esi
	}

	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(CsDamage->Damager);
	if (pData)
	{
		gzEventObjectDamage evt;
		evt.m_attacker = (pData->Owner.Reference) ? pData->Owner.Reference->obj->As_DamageableGameObj() : NULL;
		evt.m_defender = nc_GameObjManager::Find_PhysicalGameObj(CsDamage->DamagedObj);
		evt.m_damage = CsDamage->Damage;
		evt.m_warhead = CsDamage->Warhead;
		evt.SetEventType(EVT_PLAYER_RAWDAMAGE);
		EventManager::ProcessEvent(evt);

		if (evt.m_defender && (evt.m_defender->Defense.Health.Get() + evt.m_defender->Defense.ShieldStrength.Get()) > 0.0f)
		{
			if (!evt.CanContinue())
			{
				CsDamage->Set_Delete_Pending();
				return;
			}
			else
				CsDamage->Act();
		}
		else
			CsDamage->Set_Delete_Pending();
	}
	else
		CsDamage->Set_Delete_Pending();
}
HookCallClass prdh("Player raw damage hook", 0x6F4152, &Hook_PlayerRawDamage);

void __stdcall Hook_PlayerPurchase() {
	nc_cPacket *packet;
	nc_cPurchaseRequestEvent *Request;
	_asm {
		mov Request, esi
		mov packet, edi
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(Request->ID);
	if (!pData || !pData->Owner.Reference)
	{
		Request->Set_Delete_Pending();
		return;
	}

	gzEventPlayerPurchase evt;
	evt.SetPlayer(pData);
	evt.SetInt(Request->Type);
	evt.m_base			= nc_BaseControllerClass::Find_Base(pData->PlayerType.Get());
	evt.m_originalCost	= 0;
	evt.m_cost			= 0;
	evt.m_presetId		= 0;
	nc_VendorClass::Get_Merchandise_Information(pData->Owner.Reference->obj->As_SoldierGameObj(), Request->Type, Request->Item, Request->Alt_Id, evt.m_originalCost, evt.m_presetId);

	// Powerups doesn't need to duplicate the price if Power Plant is down
	if (Request->Type != PURCHASE_POWERUP)
		evt.m_originalCost	*= (int)evt.m_base->OperationTimeFactor;

	evt.m_cost			= evt.m_originalCost;
	evt.m_retCode		= -1;
	evt.SetEventType(EVT_PLAYER_PURCHASE);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
		if (!evt.CanContinue())
		{
#ifdef DEVMSG
			stConsole::Out("[PurchaseHook] Purchase refused in \"%s\". Code: %d; Purchaser %ls\n",
				retObj->GetName(),
				evt.m_retCode,
				pData->PlayerName.m_Buffer
			);
#endif
			Send_Purchase_Response(pData->PlayerId, evt.m_retCode);
			return;
		}
	}

	int ResponseType = 0;

	// Not enough credits
	if (evt.m_cost > 0 && pData->Money.Get() < evt.m_cost)
		ResponseType = 2;

	// Preset does not exist
	else if (evt.m_presetId != 0 && !nc_DefinitionMgrClass::Find_Definition(evt.m_presetId, true))
		ResponseType = 4;

	else
	{
		nc_SoldierGameObj *Soldier = evt.GetPlayer()->Owner.Reference->obj->As_SoldierGameObj();
		nc_BuildingGameObj *vehFactory = evt.m_base->Find_Building(nc_VEHICLE_FACTORY);
		switch (Request->Type)
		{
			case PURCHASE_INFANTRY:
			case PURCHASE_SECRET_INFANTRY:
				if (evt.m_base->Find_Building(nc_SOLDIER_FACTORY)->Destroyed)
				{
					ResponseType = 3;
					break;
				}

				// Block secret-characters purchase
				if (Request->Type == PURCHASE_SECRET_INFANTRY && cGame->IsLaddered)
				{
					ResponseType = 4;
					break;
				}
			case PURCHASE_FREE_INFANTRY:
				Soldier->Re_Init(*(nc_SoldierGameObjDef *)nc_DefinitionMgrClass::Find_Definition(evt.m_presetId, true));
				Soldier->Post_Re_Init();
				break;

			case PURCHASE_SECRET_VEHICLE:
				if (!vehFactory->As_VehicleFactoryGameObj())
				{
					ResponseType = 4;
					break;
				}
				if (cGame->IsLaddered)
				{
					ResponseType = 4;
					break;
				}
			case PURCHASE_VEHICLE:
				if (!vehFactory || vehFactory->Destroyed || vehFactory->As_VehicleFactoryGameObj()->IsGenerating)
				{
					ResponseType = 3;
					break;
				}
				vehFactory->As_VehicleFactoryGameObj()->Request_Vehicle(evt.m_presetId, 5.0f, Soldier);
				break;

			case PURCHASE_POWERUP:
				((nc_PowerUpGameObjDef *)nc_DefinitionMgrClass::Find_Definition(evt.m_presetId, true))->Grant(Soldier, NULL, true);
				break;

			case PURCHASE_REFILL:
				for (int i = 1; i < Soldier->WeaponBag->Vector.Count(); i++)
				{
					if (Soldier->WeaponBag->Vector[i]->WeaponDef->CanRecieveGenericCnCAmmo)
					{
						Soldier->WeaponBag->Vector[i]->InventoryBullets = Soldier->WeaponBag->Vector[i]->WeaponDef->MaxInventoryRounds.Get();
						Soldier->WeaponBag->Vector[i]->LoadedBullets = Soldier->WeaponBag->Vector[i]->WeaponDef->ClipSize.Get();
					}
				}
				Soldier->Defense.Health = Soldier->Defense.HealthMax;
				Soldier->Defense.ShieldStrength = Soldier->Defense.ShieldStrengthMax;
				Soldier->Set_Object_Dirty_Bit(nc_DB_OCCASIONAL,true);
				break;
		}
		if (ResponseType == 0 && evt.m_cost > 0)
			pData->Increment_Money(-(float)evt.m_cost);
	}
	Send_Purchase_Response(Request->ID, ResponseType);
}
HookCallClass pph("Player purchase hook", 0x4B8520, &Hook_PlayerPurchase);


/**********************/
/* Object event hooks */
/**********************/
nc_PersistClass *__stdcall Proc_CreateObjectByID(unsigned long ID)
{
	nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Find_Definition(ID, true);
	if (!Definition)
		return 0;
	int VerifyID = Definition->Get_Class_ID() - 0x1000;
	VerifyID = (((VerifyID + (VerifyID & 0xFFF)) >> 0xC) + 1) << 0xC;
	if (VerifyID != 0x3000)
		return NULL;
	nc_StringClass ErrorMsg;
	ErrorMsg.Get_String(0, false);
	if (!Definition->Is_Valid_Config(ErrorMsg.m_Buffer))
	{
		stConsole::Out("Error creating object %s: %s\n",Definition->Get_Name(), ErrorMsg.m_Buffer);
		ErrorMsg.Free_String();
		return NULL;
	}
	ErrorMsg.Free_String();
	nc_PersistClass *NewObject = Definition->Create();

	gzEventObjectCreate evt;
	evt.m_obj = gzStatic_Cast(nc_ScriptableGameObj, NewObject);
	evt.SetEventType(EVT_OBJECT_CREATE);
	EventManager::ProcessEvent(evt);

	return NewObject;
}
__declspec(naked) void Hook_CreateObjectByID(unsigned long ID)
{
	_asm {
		push ebp
		mov ebp, esp
		push ID
		call Proc_CreateObjectByID
		mov esp, ebp
		pop ebp
		retn
	}
}
HookJumpClass cobih("Object create hook [ID] [replace]", 0x6C5C30, &Hook_CreateObjectByID);

nc_PersistClass *__stdcall Proc_CreateObjectByPresetName(const char *preset)
{
	nc_DefinitionClass *Definition = nc_DefinitionMgrClass::Find_Typed_Definition(preset,0x3000, true);
	if (!Definition)
		return NULL;
	nc_StringClass ErrorMsg;
	ErrorMsg.Get_String(0, false);
	if (!Definition->Is_Valid_Config(ErrorMsg.m_Buffer))
	{
		stConsole::Out("Error creating object %s: %s\n",Definition->Get_Name(), ErrorMsg.m_Buffer);
		ErrorMsg.Free_String();
		return NULL;
	}
	ErrorMsg.Free_String();
	nc_PersistClass *NewObject = Definition->Create();

	gzEventObjectCreate evt;
	evt.m_obj = gzStatic_Cast(nc_ScriptableGameObj, NewObject);
	evt.SetEventType(EVT_OBJECT_CREATE);
	EventManager::ProcessEvent(evt);

	return NewObject;
}
__declspec(naked) void Hook_CreateObjectByPresetName(const char *preset)
{
	_asm {
		push ebp
		mov ebp, esp
		push preset
		call Proc_CreateObjectByPresetName
		mov esp, ebp
		pop ebp
		retn
	}
}
HookJumpClass cobpnh("Object create hook [Name] [replace]", 0x6C5CC0, &Hook_CreateObjectByPresetName);

void __stdcall Proc_ObjReInit(nc_ScriptableGameObj *obj)
{
	gzEventObjectInit evt;
	evt.m_obj = obj;
	evt.SetEventType(EVT_OBJECT_INIT);
	EventManager::ProcessEvent(evt);
}
__declspec(naked) void Hook_ObjReInit()
{
	_asm
	{
		push ecx
		call Proc_ObjReInit
		pop ebx
		pop ecx
		retn 4
	}
}
HookJumpClass ori("Object re-init hook", 0x6B5E75, &Hook_ObjReInit);

bool __stdcall Proc_ServerDamage(nc_DamageableGameObj *Object, const nc_OffenseObjectClass *Offense, float DamageMultipler, int Warhead)
{
	if (Object->Defense.Health.Get() == 0.0f)
		return false;

	gzEventObjectDamage evt;
	evt.m_attacker = (Offense->Damager.Reference) ? Offense->Damager.Reference->obj->As_DamageableGameObj() : NULL;
	evt.m_defender = Object;
	evt.m_damage = Offense->Damage;
	evt.m_warhead = Offense->Warhead;
	evt.SetEventType(EVT_OBJECT_DAMAGE);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] Damage refused by class \"%s\".\n", __FUNCTION__, retObj->GetName());
#endif
		return false;
	}
	return true;
}
__declspec(naked) void Hook_ServerDamage()
{
	_asm {
		push ebp
		mov ebp, esp
		push ecx
		push [ebp+0x10]
		push [ebp+0xC]
		push [ebp+8]
		push ecx
		call Proc_ServerDamage
		test al, al
		pop ecx
		mov esp, ebp
		pop ebp
		jz no_damage
		sub esp, 8
		push ebx
		push ebp
		push esi
		mov ebx, 0x6D8DC6
		jmp ebx
no_damage:
		retn 0xC
	}
}
HookJumpClass sdh("Server damage hook", 0x6D8DC0, &Hook_ServerDamage);

void __stdcall Proc_OnObjectKill(nc_OffenseObjectClass *Offense, nc_DamageableGameObj *victim)
{
	nc_ScriptableGameObj *killer = NULL;
	if (Offense->Damager.Reference)
		killer = Offense->Damager.Reference->obj;

	gzEventObjectKill evt;
	evt.m_attacker = (killer ? killer->As_DamageableGameObj() : NULL);
	evt.m_defender = victim->As_DamageableGameObj();
	evt.SetEventType(EVT_OBJECT_KILL);
	EventManager::ProcessEvent(evt);
}
__declspec(naked) void Hook_OnObjectKill()
{
	_asm {
		push esi
		push ebp
		call Proc_OnObjectKill
		mov eax, [esi+0x6F8]
		mov edi, 0x6D8E9C
		jmp edi
	}
}
HookJumpClass ook("Object kill hook", 0x6D8E96, &Hook_OnObjectKill);

void __stdcall Proc_OnObjectKillExpired(nc_DamageableGameObj *victim)
{
	gzEventObjectKill evt;
	evt.m_attacker = NULL;
	evt.m_defender = victim;
	evt.SetEventType(EVT_OBJECT_KILL);
	EventManager::ProcessEvent(evt);
}
__declspec(naked) void Hook_OnObjectKillExpired()
{
	_asm {
		push ebx
		call Proc_OnObjectKillExpired
		mov eax, [edi-74h]
		xor esi, esi
		mov ecx, 0x67C0D0
		jmp ecx
	}
}
HookJumpClass oke("Object kill (expired) hook", 0x67C0CB, &Hook_OnObjectKillExpired);

void __stdcall Hook_SoldierTransition(nc_SoldierGameObj *soldier) {
	nc_TransitionInstanceClass *Instance;
	_asm {
		mov Instance, ecx
	}

	gzEventObjectTransition evt;
	evt.m_soldier = soldier;
	evt.m_instance = Instance;
	evt.SetEventType(EVT_OBJECT_TRANSITION);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#ifdef DEVMSG
		stConsole::Out("[%s] Transition request from %ls to %s refused by class \"%s\".\n",
			__FUNCTION__,
			soldier->Player->PlayerName.m_Buffer,
			Instance->Target.Reference->obj->definition->Get_Name(),
			retObj->GetName()
		);
#endif
		return;
	}
	Instance->Start(soldier);
}
HookCallClass st("Soldier transition hook", 0x6D4517, &Hook_SoldierTransition);

void __stdcall Proc_JumpStart(nc_SoldierGameObj *soldier)
{
	if (soldier->Player) {
#if ISDEV()
		stConsole::Out("Start jump: %ls - Height: %f\n",
			soldier->Player->PlayerName.m_Buffer,
			soldier->Get_Transform()->PosZ
		);
#endif

		gzEventObjectJump evt;
		evt.m_soldier = soldier;
		evt.SetEventType(EVT_OBJECT_JUMP_START);
		EventManager::ProcessEvent(evt);
	}
}
__declspec(naked) void Hook_JumpStart()
{
	_asm {
		push ecx
		mov eax, [ecx+0x2C]
		mov eax, [eax+0x4C]
		add eax, 0xFFFFF894
		push eax
		call Proc_JumpStart
		pop ecx
		sub esp, 0x48
		push esi
		mov esi, ecx
		mov ecx, 0x69BDF6
		jmp ecx
	}
}
HookJumpClass js("Jump start hook", 0x69BDF0, &Hook_JumpStart);

void __stdcall Proc_JumpComplete(nc_SoldierGameObj *soldier)
{
	if (soldier && soldier->Player)
	{
		gzEventObjectJump evt;
		evt.m_soldier = soldier;
		evt.SetEventType(EVT_OBJECT_JUMP_COMPLETE);
		EventManager::ProcessEvent(evt);
#if ISDEV()
		stConsole::Out("Complete jump: %ls - From height: %f - Current height: %f - Distance: %f\n",
			soldier->Player->PlayerName.m_Buffer,
			soldier->Human.JumpStart.PosZ,
			soldier->Get_Transform()->PosZ,
			soldier->Human.JumpStart.PosZ - soldier->Get_Transform()->PosZ
		);
#endif
	}
}
__declspec(naked) void Hook_JumpComplete()
{
	_asm {
		push ecx
		mov eax, [ecx+0x2C]
		mov eax, [eax+0x4C]
		add eax, 0xFFFFF894
		push eax
		call Proc_JumpComplete
		pop ecx
		sub esp, 0x88
		mov eax, 0x69BF76
		jmp eax
	}
}
HookJumpClass jc("Jump complete hook", 0x69BF70, &Hook_JumpComplete);

void __stdcall Hook_SoldierSquished(const nc_OffenseObjectClass &offense, float damage, nc_Vector3 &speed, int unk) {
	nc_SoldierGameObj *killed;
	_asm mov killed, ecx

	gzEventObjectKill evt;
	evt.m_attacker = offense.Damager.Reference->obj->As_DamageableGameObj();
	evt.m_defender = killed->As_DamageableGameObj();
	evt.SetEventType(EVT_OBJECT_SQUISH);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] Squish damage refused by class \"%s\".\n",
			__FUNCTION__,
			retObj->GetName()
		);
#endif
		return;
	}
	killed->Apply_Damage_Extended(offense, damage, speed, unk);
}
HookCallClass ss("Soldier squish hook", 0x6C7A98, &Hook_SoldierSquished);
PatchByteClass ssp("Soldier squish NOP", 0x6C7A9D, 0x90, 1);

bool __stdcall Proc_C4Detonate(nc_C4GameObj *C4, nc_SmartGameObj *Enemy)
{
	if (C4->AmmoDef->AmmoType.Get() != 3)
		Enemy = NULL;

	gzEventObjectC4Detonation evt;
	evt.m_C4 = C4;
	evt.m_explodeBy = Enemy;
	evt.SetEventType(EVT_OBJECT_C4_DETONATE);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] C4 detonation refused by class \"%s\".\n",
			__FUNCTION__,
			retObj->GetName()
		);
#endif
		return false;
	}
	return true;
}
__declspec(naked) void Hook_C4Detonate()
{
	_asm {
		push ecx
		push ebx
		push ecx
		call Proc_C4Detonate
		pop ecx
		test al, al
		jz no_detonate
		sub esp, 0xC
		push esi
		mov esi, ecx
		mov eax, 0x70BEA3
		jmp eax
no_detonate:
		retn
	}
}
HookJumpClass cd("C4 detonate hook", 0x70BE90, &Hook_C4Detonate);
PatchByteClass cmcl("Maintain C4 limit \"retn\" patch", 0x70CFD0, 0xC3, 1);

void __stdcall Hook_PowerupGrant(nc_SoldierGameObj *Soldier) {
	nc_PowerUpGameObj *Powerup;
	_asm {
		mov Powerup, ecx
	}
	if (!stricmp(Soldier->Physics->PhysRender->Get_Name(),"null") || Soldier->Defense.Health.Get() == 0.0f)
		return;
	if (Soldier->Player)
	{
		gzEventObjectPowerupGrant evt;
		evt.m_powerup = Powerup;
		evt.m_soldier = Soldier;
		evt.SetEventType(EVT_OBJECT_POWERUP_GRANT);
		EventHandler *retObj = EventManager::ProcessEvent(evt);
		if (!evt.CanContinue())
		{
#if ISDEV()
			stConsole::Out("[%s] Powerup(%s) grant for %ls refused by class \"%s\".\n",
				__FUNCTION__,
				Powerup->definition->Get_Name(),
				Soldier->Player->PlayerName.m_Buffer,
				retObj->GetName()
			);
#endif
			return;
		}
	}
	Powerup->Grant(Soldier);
}
HookCallClass pg("Powerup grant hook", 0x6F12AC, &Hook_PowerupGrant);

void __stdcall Hook_FireBullet(bool isPrimary) {
	nc_WeaponClass *Weapon;
	_asm
	{
		mov Weapon, ecx
	}
	if (Weapon->Owner.Reference)
	{
		gzEventObjectFire evt;
		evt.m_primaryFire = isPrimary;
		evt.m_shooter = Weapon->Owner.Reference->obj->As_SoldierGameObj();
		evt.m_weapon = Weapon;
		evt.SetEventType(EVT_OBJECT_FIRE);
		EventManager::ProcessEvent(evt);
	}
	_asm {
		mov eax, 0x6FDA60
		mov ecx, Weapon
		push dword ptr isPrimary
		call eax
	}
}
HookCallClass fbh("Weapon Fire hook", 0x700DC3, &Hook_FireBullet);

int __cdecl Hook_C4Creation(int Team)
{
	nc_C4GameObj *c4;
	_asm
	{
		mov c4, ecx
	}
	gzEventObjectC4Creation evt;
	evt.m_C4 = c4;
	evt.SetEventType(EVT_OBJECT_C4_CREATE);
	EventManager::ProcessEvent(evt);
	return Team;
}
HookCallClass c4ch("C4 creation hook", 0x70AD79, &Hook_C4Creation);

void __stdcall Hook_BuildingInit()
{
	nc_BuildingGameObj *Building;
	_asm {
		mov Building, ecx
	}
	
	gzEventObjectCreate evt;
	evt.m_obj = Building;
	evt.SetEventType(EVT_OBJECT_CREATE);
	EventManager::ProcessEvent(evt);

	_asm {
		mov ecx, Building
		mov eax, 0x683C80
		call eax
	}
}
HookCallClass bih("Building creation hook", 0x67FB99, &Hook_BuildingInit);

bool __stdcall Proc_VehicleFlipKill(nc_VehicleGameObj *vehicle)
{
	gzEventObjectFlipKill evt;
	evt.m_vehicle = vehicle;
	evt.m_allowFlipKill = true;
	for (unsigned int i = 0; i < EventManager::m_list.Count(); i++)
	{
		EventManager::m_list[i]->Object_FlipKill(evt);
		if (!evt.m_allowFlipKill)
			return false;
		if (!evt.CanContinue())
			break;
	}
	return true;
}
__declspec(naked) void Hook_VehicleFlipKill()
{
	_asm {
		push ecx
		lea ebx, [ecx-0x76C]
		push ebx
		call Proc_VehicleFlipKill
		pop ecx
		mov ebx, 0x67C065
		jmp ebx
	}
}
HookJumpClass vfkh("Vehicle flip kill hook", 0x67C060, &Hook_VehicleFlipKill);


/***********************/
/* Network event hooks */
/***********************/
bool __stdcall Proc_WOLPage(const char *sender, const char *message)
{
	gzEventNetWolPage evt;
	evt.m_sender = sender;
	evt.m_message = message;
	evt.SetEventType(EVT_NET_WOLPAGE);
	EventHandler *retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] WOL Page from %s refused by class \"%s\".\n",
			__FUNCTION__,
			sender,
			retObj->GetName()
		);
#endif
		return false;
	}
	return true;
}
__declspec(naked) void Hook_WOLPage()
{
	_asm {
		mov eax, [esp+0xC]
		add eax, 0x24
		push [esp+0x10]
		push eax
		call Proc_WOLPage
		test al, al
		jz block_page
		sub esp, 0xC
		push esi
		mov esi, [esp+0x14]
		mov eax, 0x4C1E54
		jmp eax
block_page:
		xor eax, eax
		retn 0x10
	}
}
HookJumpClass wph("WOL Page hook", 0x4C1E50, &Hook_WOLPage);

void __stdcall Proc_CDKeyAuthCallback(int ID, int Type, const char *msg)
{
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(ID);
	if (pData && pData->IsActive)
	{
		gzEventNetSerialAuth evt;
		evt.SetPlayer(pData);
		evt.SetInt(Type);
		evt.m_message = msg;
		evt.SetEventType(EVT_NET_SERIAL_AUTH);
		EventManager::ProcessEvent(evt);
	}
}
__declspec(naked) void Hook_CDKeyAuthCallback()
{
	_asm {
		push ebp
		mov ebp, esp
		push [ebp+10h] // Message
		push [ebp+0Ch] // Type
		push [ebp+8] // Player ID
		call Proc_CDKeyAuthCallback
		mov esp, ebp
		pop ebp
		retn
	}
}
HookJumpClass ckac("CD key auth call back hook", 0x4E2180, &Hook_CDKeyAuthCallback);


/**************/
/* Misc hooks */
/**************/
void __stdcall Proc_BeaconUpdateState(nc_BeaconGameObj *Beacon)
{
	Beacon->ArmTime -= nc_TimeManager::FrameSeconds;
	nc_BeaconGameObjDef *BeaconDef = (nc_BeaconGameObjDef *)Beacon->definition;
	if (Beacon->PreDetonateCinematicActive)
	{
		if ((Beacon->DetonateTime -= nc_TimeManager::FrameSeconds) <= 0.0f)
			Beacon->Set_State(4);
		if (Beacon->PreDetonateCinematicDelay > 0.0f)
		{
			Beacon->PreDetonateCinematicDelay -= nc_TimeManager::FrameSeconds;
			if (Beacon->PreDetonateCinematicDelay <= 0.0f)
			{
				Beacon->PreDetonateCinematicDelay = 0.0f;
				nc_Vector3 animPos;
				animPos.X = Beacon->Physics->PhysRender->Transform.PosX;
				animPos.Y = Beacon->Physics->PhysRender->Transform.PosY;
				animPos.Z = Beacon->Physics->PhysRender->Transform.PosZ;
				nc_CinematicGameObj *animObj = (nc_CinematicGameObj *)nc_ObjectLibraryManager::Create_Object(nc_DefinitionMgrClass::Find_Definition(BeaconDef->PreDetonateCinematicObj, true)->Get_Name());
				if (animObj != NULL)
				{
					animObj->Set_Position(animPos);
					animObj->Start_Observers();
					Beacon->CinematicObj.__as(animObj);
				}
			}
		}
	}
	switch (Beacon->State)
	{
		case 1: // Begin arming
			if ((Beacon->ArmTime / BeaconDef->ArmTime) <= 0.0f)
				Beacon->Set_State(2);
			break;
		case 2: // Deployed
			break;
		case 4: // Detonated, cleanups
			if (Beacon->ArmTime != 0.0f)
			{
				if (nc_CombatManager::BeaconPlacementEndsGame == true)
				{
					nc_BaseControllerClass *enemyBase = nc_BaseControllerClass::Find_Base(Beacon->Get_Player_Type() ^ 1);
					if (enemyBase != NULL)
					{
						nc_Vector3 beaconPos;
						Beacon->Get_Position(&beaconPos);
						if (nc_CollisionMath::Overlap_Test(enemyBase->BeaconZone, beaconPos) != 1)
						{
							if (IsBeaconPedApproved(enemyBase->team ^ 1))
							{
								enemyBase->Set_Beacon_Destroyed_Base(true);
								enemyBase->Destroy_Base();
							}
						}
					}
				}
				if (BeaconDef->IsNuke == true)
					nc_BackgroundMgrClass::Restore_Sky_Tint(5.0f);
				else
				{
					nc_BackgroundMgrClass::Restore_Clouds(5.0f);
					nc_BackgroundMgrClass::Restore_Lightning(5.0f);
				}
				nc_WeatherMgrClass::Restore_Wind(5.0f);
				Beacon->Set_Delete_Pending();
			}
			break;
	}
}
__declspec(naked) void Hook_BeaconUpdateState()
{
	_asm {
		push ecx
		call Proc_BeaconUpdateState
		retn
	}
}
HookJumpClass bus("Beacon update state hook", 0x709780, &Hook_BeaconUpdateState);

bool __stdcall Hook_EnemySeenVehicle(nc_DamageableGameObj *obj)
{
	nc_SmartGameObj *smart;
	_asm {
		mov smart, ecx
	}
	if (!smart->Is_Enemy(obj))
		return false;
	if (smart->As_VehicleGameObj())
		return smart->As_VehicleGameObj()->VehicleCanEnemySeen;
	return true;
}
HookCallClass esvh("Enemy seen vehicle hook", 0x69EB89, &Hook_EnemySeenVehicle);

#if VERC(1, 1, 0)
void __stdcall Hook_ProcessConnectionRequest(nc_cPacket &packet)
{
	wchar_t nick[256], pass[256];
	unsigned int exeKey = 0;
	int bandwidth = 0;
	packet.Get_Wide_Terminated_String(nick, 255, true);
	packet.Get_Wide_Terminated_String(pass, 255, true);
	packet.Internal_Get<unsigned int>(exeKey, -1);
	packet.Internal_Get<int>(bandwidth, -1);

	// Invalid files checksum
	if (exeKey != cGame->ExeKey)
	{
		nc_cNetwork::PServerConnection->Send_Refuse_Sc(&packet.Sender,VERSION_MISMATCH);
		return;
	}

	// Process player connect event
	gzEventPlayerConnect evt;
	evt.m_addr.Address(inet_ntoa(packet.Sender.sin_addr));
	evt.m_addr.Port(ntohs(packet.Sender.sin_port));
	evt.m_nick = nick;
	evt.m_password = pass;
	evt.m_exeKey = exeKey;
	evt.m_bandwidth = bandwidth;
	evt.SetEventType(EVT_PLAYER_CONNECT);
	evt.SetInt(REFUSE1);
	EventHandler* retObj = EventManager::ProcessEvent(evt);
	if (!evt.CanContinue())
	{
#if ISDEV()
		stConsole::Out("[%s] Connection request from %s:%u refused by class \"%s\".\n",
			__FUNCTION__,
			evt.m_addr.Address().GetString(),
			evt.m_addr.Port(),
			retObj->GetName()
		);
#endif
		if (evt.GetInt() >= 1)
		{
			if (evt.GetInt() > 5)
				evt.SetInt(REFUSE1);
			nc_cNetwork::PServerConnection->Send_Refuse_Sc(&packet.Sender, (nc_REFUSAL_CODE)evt.GetInt());
		}
		return;
	}

	// Reset packet read position
	packet.BitReadPosition = 0;

	nc_cNetwork::PServerConnection->Process_Connection_Request(packet);
}
HookCallClass pcr("Process Connection Request hook", 0x6185E8, &Hook_ProcessConnectionRequest);
#endif

/***********/
/* Patches */
/***********/
__declspec(naked) void Patch_cRemoteHostSize()
{
	_asm {
		mov eax, 127 // Max connections, nc_cRemoteHost array size
#if ISDEV()
		// Force to bind the IP to 0.0.0.0
		mov edx, 0
		mov [esp+0x10], edx
#endif
		sub esp, 0x10
		mov edx, 0x617ED7
		jmp edx
	}
}
HookJumpClass crsp("Remote host size patch", 0x617ED0, &Patch_cRemoteHostSize);

PatchByteClass suowl("FDS auto start on windows user log on patch", 0x434CDE, 0x90, 7);
PatchByteClass gof("GameOver result screen fix", 0x4BAD6C, 0x90, 9);

void __stdcall Patch_WolLadderMapFix(char *data, char *string)
{
	_asm push ecx
	char Map[128];
	memset(Map,0,sizeof(Map));
	if ((cGame->MapNumber -1) == -1)
	{
		for (int i = 0; i < 100; i++)
		{
			if (!*cGame->MapList[i].m_Buffer)
				break;
			strncpy(Map,cGame->MapList[i].m_Buffer,127);
		}
	}
	else if (!*cGame->MapList[cGame->MapNumber].m_Buffer)
		strncpy(Map,cGame->MapList[0].m_Buffer,127);
	else
		strncpy(Map,cGame->MapList[cGame->MapNumber -1].m_Buffer,127);
	_asm {
		pop ecx
		mov eax, 0x4BB590
		lea edx, Map
		push edx
		push data
		call eax
	}
}
HookCallClass wlsf("WOL ladder map fix",0x43F8EF,&Patch_WolLadderMapFix);

void __stdcall Patch_WolLadderNicknameLengthExtend(char *data, char *string)
{
	nc_cPlayer *p;
	_asm {
		push ecx
		mov p, esi
	}
	char Name[65];
	memset(Name,0,sizeof(Name));
	wcstombs(Name,p->PlayerName.m_Buffer,sizeof(Name) -1);
	_asm {
		pop ecx
		mov eax, 0x4BB590
		lea edx, Name
		push edx
		push data
		call eax
	}
}
HookCallClass wlnle("WOL ladder nickname length extension", 0x43FCC8, &Patch_WolLadderNicknameLengthExtend);

void __stdcall Proc_PurchaseTerminalFix(nc_cPlayer *pData)
{
	pData->Set_Is_Active(true);
	nc_cNetwork::Send_Object_Update(pData, pData->PlayerId);
}
__declspec(naked) void Patch_PurchaseTerminalFix()
{
	__asm {
		mov [esp+4], ecx
		jmp Proc_PurchaseTerminalFix
	}
}
HookCallClass tbf("Purchase Terminal fix", 0x406707, &Patch_PurchaseTerminalFix);

float __stdcall GetDamageMultiplier(nc_DefenseObjectClass &defense, nc_OffenseObjectClass &offense)
{
	return nc_ArmorWarheadManager::Get_Damage_Multiplier(defense.ShieldType.Get(),offense.Warhead);
}
__declspec(naked) void Patch_DamageScoreFix()
{
	_asm {
		push ebp
		mov ebp, esp
		push [ebp+4+0x4C] // OffenseObject
		push ebx // DefenseObject
		call GetDamageMultiplier
		fmul [ebp+4+0x14] // ShieldDamage
		fadd [ebp+4+0x10] // HealthDamage
		mov esp, ebp
		pop ebp
		mov ecx, 0x68B2A6
		jmp ecx
	}
}
HookJumpClass pf("Points fix", 0x68B29E, &Patch_DamageScoreFix);

int __cdecl Patch_UnlimitedInventoryAmmoFix()
{
	nc_WeaponClass *Weapon;
	_asm {
		mov Weapon, ecx
	}
	int Return = 0;
	if (Weapon)
	{
		if (Weapon->InventoryBullets.Get() == -1)
		{
			if (Weapon->LoadedBullets.Get() == 0)
				Return = -1;
			else
				Return = Weapon->LoadedBullets.Get();
		}
		else
			Return = Weapon->LoadedBullets.Get() + Weapon->InventoryBullets.Get();
	}
	return Return;
}
HookCallClass uiafh("Infinity Inventory startup fix", 0x6F38A2, &Patch_UnlimitedInventoryAmmoFix);

bool __stdcall Proc_DoorCheck(nc_DoorPhysClass *Door, nc_SmartGameObj *Object)
{
	if (Object->As_SoldierGameObj() && Object->As_SoldierGameObj()->Vehicle && !((nc_DoorPhysDefClass *)Door->Definition)->DoorOpensForVehicles)
		return false;
	return (Object->ControlOwner >= 0);
}
__declspec(naked) void Hook_DoorCheck()
{
	_asm {
		push ecx
		push [esp+0x10]
		call Proc_DoorCheck
		mov ecx, 0x6997E6
		jmp ecx
	}
}
HookJumpClass dch("Door check hook", 0x6997E1, &Hook_DoorCheck);

/********************/
/* Event-less hooks */
/********************/
void __stdcall Hook_TiberiumDamage(const nc_OffenseObjectClass &offense, float damage, nc_Vector3 &velocity, int unk) {
	nc_SoldierGameObj *victim;
	_asm mov victim, ecx
	gzCommands->Send_Custom_Event(NULL, victim, 306494, 1, 0);
	victim->Apply_Damage_Extended(offense,damage,velocity,unk);
}
HookCallClass tdh("Tiberium damage hook", 0x6A951C, &Hook_TiberiumDamage);
PatchByteClass tdhb("Tiberium damage hook byte", 0x6A9521, 0x90, 1);

nc_ScriptableGameObj *__cdecl Hook_PlayerSpawnPreset(const char *preset)
{
	int pId = -1;
	_asm
	{
		mov ecx, [esp+0x138-4]
		mov pId, ecx
	}
	nc_cPlayer *pData = nc_cPlayerManager::Find_Player(pId);
	if (pData)
		preset = gzGameMgr->m_settings->m_SpawnChar[pData->PlayerType.Get()].GetString();
	return nc_ObjectLibraryManager::Create_Object(preset);
}
HookCallClass psp("Player spawn preset hook", 0x4068A0, &Hook_PlayerSpawnPreset);

float RefineryLastPoolDistribute[2] = { 0.0f, 0.0f };
void __stdcall Proc_RefineryThink(nc_RefineryGameObj *Refinery)
{
	if (Refinery->Destroyed == false)
	{
		if (Refinery->Harvester == NULL)
		{
			if (Refinery->Defense.Health.Get() > 0.0f)
			{
				if (cGame->Is_Gameplay_Permitted())
				{
					if (Refinery->IsHarvesterDocked)
					{
						Refinery->IsHarvesterDocked = false;
						Refinery->Set_Object_Dirty_Bit(nc_DB_RARE, true);
					}
					Refinery->Base->Request_Harvester(Refinery->Get_Definition()->Harvester);
				}
			}
		}
		else
		{
			if (Refinery->IsHarvesterDocked)
			{
				Refinery->UnloadTimeCounter -= nc_TimeManager::FrameSeconds;
				if ((RefineryLastPoolDistribute[Refinery->Base->team] - Refinery->UnloadTimeCounter) >= 0.1f)
				{
					float DistributeAmount = (Refinery->Get_Definition()->FundsGathered / Refinery->Get_Definition()->UnloadTime) * (RefineryLastPoolDistribute[Refinery->Base->team] - Refinery->UnloadTimeCounter);
					for (nc_GenericSLNode<nc_SmartGameObj> *Smart = nc_GameObjManager::SmartGameObjList->HeadNode; Smart != NULL; Smart = Smart->NodeNext)
					{
						if (Smart->NodeData->As_SoldierGameObj() && Smart->NodeData->Player && Smart->NodeData->Player->PlayerType.Get() == Refinery->Base->team)
						{
							Smart->NodeData->Player->Money += DistributeAmount;
							Smart->NodeData->Player->Set_Object_Dirty_Bit(nc_DB_OCCASIONAL, true);
						}
					}
					RefineryLastPoolDistribute[Refinery->Base->team] = Refinery->UnloadTimeCounter;
				}
				if (Refinery->UnloadTimeCounter <= 0.0f)
				{
#if ISDEV()
					stConsole::In("msg %s is going to harvester Tiberium.",Refinery->Harvester->Vehicle->definition->Get_Name());
#endif
					Refinery->IsHarvesterDocked = false;
					Refinery->PoolMoney = 0.0f;
					Refinery->Harvester->Go_Harvest();
					Refinery->Set_Object_Dirty_Bit(nc_DB_RARE, true);
					nc_PhysClass *UnloadAnimPhys = nc_PhysicsSceneClass::TheScene->Find_Static_Object(Refinery->UnloadAnimId);
					if (UnloadAnimPhys)
					{
						nc_StaticAnimPhysClass *UnloadStaticAnim = UnloadAnimPhys->As_StaticAnimPhysClass();
						if (UnloadStaticAnim)
						{
							UnloadStaticAnim->AnimCollisionMgr.AnimationMode = 1;
							UnloadStaticAnim->AnimCollisionMgr.TargetFrame = 0.0f;
						}
					}
				}
			}
		}
		if (cGame->Is_Gameplay_Permitted())
		{
			if (Refinery->IsHarvesterDocked == false)
			{
				RefineryLastPoolDistribute[Refinery->Base->team] = Refinery->Get_Definition()->UnloadTime;
				Refinery->TickCounter -= nc_TimeManager::FrameSeconds;
				if (Refinery->TickCounter <= 0.0f)
				{
					for (nc_GenericSLNode<nc_SmartGameObj> *Smart = nc_GameObjManager::SmartGameObjList->HeadNode; Smart != NULL; Smart = Smart->NodeNext)
					{
						if (Smart->NodeData->As_SoldierGameObj() && Smart->NodeData->Player && Smart->NodeData->Player->PlayerType.Get() == Refinery->Base->team)
						{
							Smart->NodeData->Player->Money += floor(Refinery->Get_Definition()->FundsDistribedPerSec / Refinery->Base->OperationTimeFactor);
							Smart->NodeData->Player->Set_Object_Dirty_Bit(nc_DB_OCCASIONAL, true);
						}
					}
					Refinery->TickCounter = 1.0f;
				}
			}
		}
		if (Refinery->Harvester)
			Refinery->Harvester->Think();
	}
	else
	{
		if (Refinery->Harvester)
			Refinery->Harvester->Vehicle->Set_Delete_Pending();
	}
	Refinery->nc_ScriptableGameObj::Think();
}
__declspec(naked) void Hook_RefineryThink()
{
	_asm {
		push ecx
		call Proc_RefineryThink
		retn
	}
}
HookJumpClass rth("RefineryGameObj::Think hook", 0x742D40, Hook_RefineryThink);

void __stdcall Proc_RefineryHarvesterDestroyed(nc_RefineryGameObj *Refinery, nc_VehicleGameObj *Vehicle)
{
	if (Refinery->Harvester)
	{
		Refinery->Base->On_Vehicle_Destroyed(Vehicle);
		nc_PhysClass *UnloadAnimPhys = nc_PhysicsSceneClass::TheScene->Find_Static_Object(Refinery->UnloadAnimId);
		if (UnloadAnimPhys)
		{
			nc_StaticAnimPhysClass *UnloadStaticAnim = UnloadAnimPhys->As_StaticAnimPhysClass();
			if (UnloadStaticAnim)
			{
				UnloadStaticAnim->AnimCollisionMgr.AnimationMode = 1;
				UnloadStaticAnim->AnimCollisionMgr.TargetFrame = 0.0f;
			}
		}
	}
}
__declspec(naked) void Hook_RefineryHarvesterDestroyed()
{
	_asm {
		push [esp+4]
		push ecx
		call Proc_RefineryHarvesterDestroyed
		retn 4
	}
}
HookJumpClass rhdh("Harvester destroy hook for Refinery", 0x743380, &Hook_RefineryHarvesterDestroyed);

void __stdcall Proc_VehicleGenerationComplete(nc_BaseControllerClass *base, nc_VehicleGameObj *vehicle)
{
	if (base->BuildingList.Count() > 0)
	{
		nc_BuildingGameObj *Refinery = base->Find_Building(nc_REFINERY);
		if (Refinery && !Refinery->As_RefineryGameObj()->Harvester)
		{
			if (((nc_RefineryGameObjDef *)Refinery->As_RefineryGameObj()->definition)->Harvester == vehicle->definition->Get_ID())
			{
				Refinery->As_RefineryGameObj()->Set_Harvester_Vehicle(vehicle);
				vehicle->VehicleCanEnemySeen = false;
#if ISDEV()
				stConsole::In("msg %s has been assigned to %s",vehicle->definition->Get_Name(),Refinery->definition->Get_Name());
#endif
			}
		}
	}
}
__declspec(naked) void Hook_VehicleGenerationComplete(nc_VehicleGameObj *vehicle)
{
	_asm {
		push ebp
		mov ebp, esp
		push vehicle
		push ecx
		call Proc_VehicleGenerationComplete
		mov esp, ebp
		pop ebp
		retn 4
	}
}
HookJumpClass vgch("Vehicle generation complete hook", 0x6EE4C0, &Hook_VehicleGenerationComplete);

void __stdcall Hook_NewTeamRebalance()
{
	if (nc_cPlayerManager::Count() > 0)
	{
		nc_cPlayerManager::Sort_Players(true);
		int rnd = stRandom.GetInt(2, (INT_MAX - 1024)), activeCount = 0;
		for (int i = 0; i >= 0; i++)
		{
			nc_cPlayer *pData = nc_cPlayerManager::Player_Array[i];

			if (pData == NULL)
				break;

			if (pData->IsActive)
			{
				if ((activeCount % 2) == 1)
					rnd++;
				int team = (rnd % 2);
				pData->PlayerType = team;
				pData->Set_Object_Dirty_Bit(nc_DB_RARE, true);
#ifdef DEVMSG
				stConsole::Out("[Remix] %ls has been assigned to team %d (Previous score: %f)\n", pData->PlayerName.m_Buffer, team, pData->Score.Get());
#endif
				activeCount++;
			}
		}
		nc_cPlayerManager::Reset_Players();
		nc_WideStringClass msg;

		msg.Convert_From("Teams have been remixed.");
		Send_Text_Msg(msg, 0, false, -1, -1);

		msg.Convert_From("Teams have been rebalanced.");
		Send_Text_Msg(msg, 0, false, -1, -1);

		msg.Free_String();
	}
}
HookCallClass ntr("New Team Rebalancer", 0x475AB3, &Hook_NewTeamRebalance);
PatchByteClass ntrhb1("New Team Rebalancer hook byte 1", 0x471997, 0x90, 5);
PatchByteClass ntrb2("New Team Rebalancer hook byte 2", 0x475A70, 0x90, 5);

PatchByteClass gqrb("GameSpy Query and Response blocker", 0x774BD0, 0xC3, 1);


/*************/
/* Dev hooks */
/*************/
#if ISDEV()
float Speed_Sync_Speed;
nc_SoldierGameObj *Speed_Sync_Soldier;
void Proc_SoldierSetSpeed(nc_SoldierGameObj *soldier, float Speed) {
	if (soldier->ControlOwner != -1) {
		char msg[64];
		sprintf(msg, "NC\nSS\n%f", Speed_Sync_Speed);
		nc_WideStringClass wstr;
		wstr.Get_String(0, false);
		wstr.Convert_From(msg);
		Send_Text_Msg(wstr, 2 , false, -2, soldier->ControlOwner);
		wstr.Free_String();
	}
}
__declspec(naked) void Hook_SoldierSetSpeed() {
	_asm {
		mov Speed_Sync_Soldier, ecx
		mov eax, [esp+4]
		mov Speed_Sync_Speed, eax
	}
	Proc_SoldierSetSpeed(Speed_Sync_Soldier,Speed_Sync_Speed);
	_asm {
		mov ecx, Speed_Sync_Soldier
		mov ecx, [ecx+0x774]
		mov eax, 0x6C8F26
		jmp eax
	}
}
HookJumpClass sssh("Soldier set speed hook", 0x6C8F20, &Hook_SoldierSetSpeed);

void __stdcall Proc_RefineryOnHarvesterDock(nc_RefineryGameObj *Refinery)
{
	Refinery->IsHarvesterDocked = true;
	Refinery->UnloadTimeCounter = Refinery->Get_Definition()->UnloadTime;
	Refinery->HarvDockDistributePerSec = Refinery->Get_Definition()->FundsGathered / Refinery->UnloadTimeCounter;
	Refinery->Set_Object_Dirty_Bit(nc_DB_RARE, true);
	stConsole::In("msg %s is unloading Tiberium.", Refinery->Harvester->Vehicle->definition->Get_Name());
}
__declspec(naked) void Hook_RefineryOnHarvesterDock()
{
	_asm {
		push ecx
		call Proc_RefineryOnHarvesterDock
		retn
	}
}
HookJumpClass rohdh("Harvester dock hook", 0x7433A0, &Hook_RefineryOnHarvesterDock);

//void __stdcall Proc_CollisionOccurred(nc_PhysObserverClass* obj, nc_CollisionEventClass& collide)
//{
//	nc_DamageableGameObj* collider = collide.AttachedObjPhys->PhysObserver->As_DamageableGameObj();
//	stConsole::Out("Collide;%s\n", collider->definition->Get_Name());
//}
//__declspec(naked) void Hook_CollisionOccurred()
//{
//	_asm
//	{
//		push [esp+4]
//		push ecx
//		call Proc_CollisionOccurred
//		xor eax, eax
//		retn 4
//	}
//}
//HookJumpClass co("Collision Occurred hook", 0x67CD10, &Hook_CollisionOccurred);

/*
void __stdcall Proc_cGodCreateCommando(int pId, int type)
{
	nc_SpawnerClass *spawner = NULL;
	aString spawnPreset = "Commando";
	
	if (cGameType::GameType == 1)
	{
		// SpawnManager::Get_Primary_Spawner()
		for (int i = 0; i < nc_SpawnManager::Total_Spwaner; i++)
		{
			if (nc_SpawnManager::SpawnerList[i]->Definition->IsPrimary)
			{
				if (nc_SpawnManager::SpawnerList[i]->Definition->IsSoldierStartup)
				{
					spawner = nc_SpawnManager::SpawnerList[i];
					break;
				}
			}
		}
	}
	else
	{
		if (cGame->Is_Cnc() || cGame->Is_Skirmish())
		{
			if (type == 1)
				spawnPreset = "CnC_GDI_Minigunner_0";
			else
				spawnPreset = "CnC_Nod_Minigunner_0";
		}
	}

	nc_SoldierGameObj *soldier = nc_ObjectLibraryManager::Create_Object(spawnPreset.GetString())->As_SoldierGameObj();

	// SpawnManager::Get_Multiplayer_Spawn_Location(int, SoldierGameObj *)
	aVector<nc_SpawnerClass *> spawnerList;
	for (int i = 0; i < nc_SpawnManager::Total_Spwaner; i++)
	{
		if (nc_SpawnManager::SpawnerList[i]->Definition->IsPrimary && nc_SpawnManager::SpawnerList[i]->Definition->IsSoldierStartup)
		{
			if (nc_SpawnManager::SpawnerList[i]->Definition->PlayerType == type)
				spawnerList.Add(nc_SpawnManager::SpawnerList[i]);
		}
	}
	int spawnerIndex = stRandom.Get_Int(0, spawnerList.Count() - 1);
	while (!soldier->Physics->As_MoveablePhysClass()->Can_Teleport(spawnerList[spawnerIndex]->TM, true, NULL))
		spawnerIndex = stRandom.Get_Int(0, spawnerList.Count() - 1);
	soldier->Set_Transform(&spawnerList[spawnerIndex]->TM);
}
__declspec(naked) void Hook_cGodCreateCommando()
{
	_asm {
		push [esp+8]
		push [esp+8]
		call Proc_cGodCreateCommando
		retn
	}
}
HookJumpClass cgcc("cGod::Create_Commando replacement", 0x4067D0, &Hook_cGodCreateCommando);
*/

/*
void __stdcall Proc_cPlayerSetMoney(nc_cPlayer *pData, float amount)
{
	int pTeam = pData->PlayerType.Get();
	pData->Money = amount;
	for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList != NULL; pList = pList->NodeNext)
	{
		if (pList->NodeData->IsActive)
			pData->Set_Object_Dirty_Bit(pList->NodeData->PlayerId, nc_DB_OCCASIONAL, (pTeam == pList->NodeData->PlayerType.Get()) ? true : false);
	}
}
__declspec(naked) void Hook_cPlayerSetMoney()
{
	_asm {
		push [esp+4]
		push ecx
		call Proc_cPlayerSetMoney
		retn 4
	}
}
HookJumpClass cpsm("cPlayer::Set_Money hook", 0x40FA20, &Hook_cPlayerSetMoney);

void __stdcall Proc_cPlayerIncrementMoney(nc_cPlayer *pData, float amount)
{
	int pTeam = pData->PlayerType.Get();
	pData->Money += amount;
	for (nc_GenericSLNode<nc_cPlayer> *pList = nc_cPlayerManager::PlayerList->HeadNode; pList != NULL; pList = pList->NodeNext)
	{
		if (pList->NodeData->IsActive)
			pData->Set_Object_Dirty_Bit(pList->NodeData->PlayerId, nc_DB_OCCASIONAL, (pTeam == pList->NodeData->PlayerType.Get()) ? true : false);
	}
}
__declspec(naked) void Hook_cPlayerIncrementMoney()
{
	_asm {
		push [esp+4]
		push ecx
		call Proc_cPlayerIncrementMoney
		retn 4
	}
}
HookJumpClass cpim("cPlayer::Increment_Money hook", 0x40FC60, &Hook_cPlayerIncrementMoney);
*/
#endif
