
#include "StdAfx.h"

#include "Client.h"
#include "ClientCommands.h"
#include "ClientEvents.h"
#include "World.h"

// Database access
#include "Database.h"
#include "CharacterDatabase.h"

// Network helpers
#include "BinaryWriter.h"
#include "ChatMsgs.h"
#include "ObjectMsgs.h"

// Objects
#include "PhysicsObj.h"
#include "Item.h"
#include "Monster.h"
#include "Player.h"
#include "Door.h"
#include "ChatMsgs.h"

CClientEvents::CClientEvents(CClient *parent)
{
	m_pClient = parent;

	m_pPlayer = NULL;
	m_fLogout = 0;
}

CClientEvents::~CClientEvents()
{
	if (m_pPlayer)
	{
		g_pWorld->RemoveEntity(m_pPlayer);
	}
}

void CClientEvents::DetachPlayer()
{
	m_pPlayer = NULL;
}

DWORD CClientEvents::GetPlayerID()
{
	if (!m_pPlayer)
		return NULL;

	return m_pPlayer->m_dwGUID;
}

CBasePlayer* CClientEvents::GetPlayer()
{
	return m_pPlayer;
}

void CClientEvents::ExitWorld()
{
	if (m_pPlayer)
	{
		g_pWorld->RemoveEntity(m_pPlayer);
		DetachPlayer();
	}

	m_pClient->ExitWorld();
	m_fLogout = 0;
}

void CClientEvents::Think()
{
	if (m_fLogout && ((m_fLogout + 5.00f) < g_pGlobals->Time()))
	{
		ExitWorld();
	}
}

void CClientEvents::BeginLogout()
{
	if (m_pPlayer && !m_fLogout)
	{
		m_pPlayer->Animation_ClipMotions(0);
		m_pPlayer->Animation_PlayPrimary(286, 1.0f, 5.0f);

		m_fLogout = g_pGlobals->Time();
	}
}

void CClientEvents::LoginError(int iError)
{
	DWORD ErrorPackage[2];

	ErrorPackage[0] = 0xF659;
	ErrorPackage[1] = iError;

	m_pClient->SendNetMessage(ErrorPackage, sizeof(ErrorPackage), PRIVATE_MSG);
}

void CClientEvents::LoginCharacter(DWORD dwGUID, const char *szAccount)
{
	_CHARDESC desc;

	if (m_pPlayer || g_pWorld->FindPlayer(dwGUID))
	{
		// LOG(Temp, Normal, "Character already logged in!\n");
		LoginError(13);
		LOG(Client, Warning, "Login request, but character already logged in!\n");
		return;
	}

	if (!g_pDB->CharDB()->GetCharacterDesc(dwGUID, &desc))
		return;

	if (stricmp(szAccount, desc.szAccount))
	{
		LoginError(15);
		LOG(Client, Warning, "Bad account for login: \"%s\" \"%s\"\n", szAccount, desc.szAccount);
		return;
	}

	m_pPlayer = new CBasePlayer(m_pClient, dwGUID);
	g_pWorld->CreateEntity(m_pPlayer);
	m_pPlayer->LoginCharacter();

	return;
}

void CClientEvents::SendText(const char *szText, long lColor)
{
	m_pClient->SendNetMessage(ServerText(szText, lColor), PRIVATE_MSG, FALSE, TRUE);
}

void CClientEvents::UpdateBurdenUI()
{
}

void CClientEvents::Attack(DWORD dwTarget, DWORD dwHeight, float flPower)
{
	m_pPlayer->Attack(dwTarget, dwHeight, flPower);
}

void CClientEvents::SendTellByGUID(const char* szText, DWORD dwGUID)
{
	if (strlen(szText) > 300)
		return;

	//should really check for invalid characters and such ;]

	while (szText[0] == ' ') //Skip leading spaces.
		szText++;
	if (szText[0] == '\0') //Make sure the text isn't blank
		return;

	if (dwGUID == m_pPlayer->m_dwGUID)
	{
		m_pPlayer->SendNetMessage(ServerText("You really need some new friends..", 1), PRIVATE_MSG, FALSE);
		return;
	}

	CBasePlayer *pTarget;

	if (!(pTarget = g_pWorld->FindPlayer(dwGUID)))
		return;

	char szResponse[300];
	_snprintf(szResponse, 300, "You tell %s, \"%s\"", pTarget->GetName(), szText);
	m_pPlayer->SendNetMessage(ServerText(szResponse, 4), PRIVATE_MSG, FALSE);

	pTarget->SendNetMessage(DirectChat(szText, m_pPlayer->GetName(), m_pPlayer->m_dwGUID, dwGUID, 3), PRIVATE_MSG, TRUE);
}

void CClientEvents::SendTellByName(const char* szText, const char* szName)
{
	if (strlen(szName) > 300)
		return;
	if (strlen(szText) > 300)
		return;

	//should really check for invalid characters and such ;]

	while (szText[0] == ' ') //Skip leading spaces.
		szText++;
	if (szText[0] == '\0') //Make sure the text isn't blank
		return;

	CBasePlayer *pTarget;

	if (!(pTarget = g_pWorld->FindPlayer(szName)))
		return;

	if (pTarget->m_dwGUID == m_pPlayer->m_dwGUID)
	{
		m_pPlayer->SendNetMessage(ServerText("You really need some new friends..", 1), PRIVATE_MSG, FALSE);
		return;
	}

	char szResponse[300];
	_snprintf(szResponse, 300, "You tell %s, \"%s\"", pTarget->GetName(), szText);
	m_pPlayer->SendNetMessage(ServerText(szResponse, 4), PRIVATE_MSG, FALSE, TRUE);

	pTarget->SendNetMessage(DirectChat(szText, m_pPlayer->GetName(), m_pPlayer->m_dwGUID, pTarget->m_dwGUID, 3), PRIVATE_MSG, TRUE);
}

void CClientEvents::ClientText(char* szText)
{
	if (strlen(szText) > 1000)
		return;

	//should really check for invalid characters and such ;]

	while (szText[0] == ' ') //Skip leading spaces.
		szText++;
	if (szText[0] == '\0') //Make sure the text isn't blank
		return;

	if (szText[0] == '!' || szText[0] == '@' || szText[0] == '/')
	{
		CommandBase::Execute(++szText, m_pPlayer, m_pClient->GetAccessLevel());
	}
	else
		m_pPlayer->SpeakLocal(szText);
}

void CClientEvents::EmoteText(char* szText)
{
	if (strlen(szText) > 300)
		return;

	//TODO: Check for invalid characters and such ;)

	while (szText[0] == ' ') //Skip leading spaces.
		szText++;
	if (szText[0] == '\0') //Make sure the text isn't blank
		return;

	m_pPlayer->EmoteLocal(szText);
}

void CClientEvents::ActionText(char* szText)
{
	if (strlen(szText) > 300)
		return;

	//TODO: Check for invalid characters and such ;)

	while (szText[0] == ' ') //Skip leading spaces.
		szText++;
	if (szText[0] == '\0') //Make sure the text isn't blank
		return;

	m_pPlayer->ActionLocal(szText);
}

void CClientEvents::ChannelText(DWORD dwChannel, const char* szText)
{
	if (strlen(szText) > 300)
		return;

	//TODO: Check for invalid characters and such ;)
	while (szText[0] == ' ')
		szText++;
	if (szText[0] == '\0')
		return;

	// 0x00000400 = Urgent Assistance "help channel"
	// 0x00000800 = Fellowship
	switch (dwChannel)
	{
	case 0x400:
	case 0x800:
		//For now we'll just spam them to everyone!
		// g_pWorld->BroadcastGlobal(ChannelChat(dwChannel, m_pPlayer->GetName(), szText), PRIVATE_MSG, m_pPlayer->m_dwGUID, TRUE);
		g_pWorld->BroadcastGlobal(ServerText(csprintf("%s says to your fellow testers, \"%s\"", m_pPlayer->GetName(), szText), 3), PRIVATE_MSG, m_pPlayer->m_dwGUID, TRUE);
	}

	//Give a special copy to this player?
	switch (dwChannel)
	{
	case 0x400:
	case 0x800:
		m_pClient->SendNetMessage(ServerText(csprintf("You say to your fellow testers, \"%s\"", szText), 3), PRIVATE_MSG, TRUE);
		break;
	default:
		SendText("The server does not support this channel.", 1);
		break;
	}

	if (dwChannel == 0x800)
	{
		LOG(Client, Normal, "[%s] %s says, \"%s\"\n", timestamp(), m_pPlayer->GetName(), szText);
	}
}

void CClientEvents::RequestHealthUpdate(DWORD dwGUID)
{
	CPhysicsObj *pEntity = g_pWorld->FindWithinPVS(m_pPlayer, dwGUID);

	if (pEntity) {
		if (pEntity->IsMonster())
		{
			m_pClient->SendNetMessage(HealthUpdate((CBaseMonster *)pEntity), PRIVATE_MSG, TRUE, TRUE);
		}
	}
}

void CClientEvents::EquipItem(DWORD dwItemID, DWORD dwCoverage)
{

	// Scenarios to consider:
	// 1. Item being equipped from the GROUND!
	// 2. Item being equipped from different equip slot.
	// 3. Item being equipped from the player's inventory.

	CPhysicsObj *		pEntity;
	CBaseItem *			pItem;

	//Does the player have an inventory?
	if (!(m_pPlayer->Container_HasContents()))
		return;

	//Find the item.
	if (!(pEntity = g_pWorld->FindWithinPVS(m_pPlayer, dwItemID)))
		return;
	if (!(pEntity->IsItem()))
		return;
	pItem = (CBaseItem *)pEntity;

	//Can this item be equipped?
	if (!(pItem->CanEquip()))
		return;
	if (!(m_pPlayer->Container_CanEquip(pItem, dwCoverage)))
		return;

	//Equip it
	DWORD dwCell = m_pPlayer->GetLandcell();

	//Take it out of whatever slot it may be in.
	if (pItem->HasOwner())
		m_pPlayer->Container_ReleaseItem(pItem, FALSE);

	if (!(pItem->m_ItemType & (TYPE_ARMOR | TYPE_CLOTHING)))
	{
		pItem->SetWorldWielder(dwCell, m_pPlayer);
	}

	pItem->SetWorldContainer(dwCell, NULL);
	pItem->SetWorldCoverage(dwCell, dwCoverage);

	//The container will auto-correct this slot into a valid range.
	m_pPlayer->Container_EquipItem(dwCell, pItem, dwCoverage);

	m_pPlayer->EmitSound(0x77, 1.0f);
	m_pClient->SendNetMessage(InventoryEquip(dwItemID, dwCoverage), PRIVATE_MSG, TRUE);

	UpdateBurdenUI();

	if (pItem->m_ItemType & (TYPE_ARMOR|TYPE_CLOTHING))
	{
		m_pPlayer->UpdateModel();
	}
}

void CClientEvents::StoreItem(DWORD dwItemID, DWORD dwContainer, char cSlot)
{
	if (cSlot < 0)
		return;

	// Scenarios to consider:
	// 1. Item being stored is equipped.
	// 2. Item being stored is on the GROUND!
	// 3. Item being stored is already in the inventory.

	CPhysicsObj *		pEntity;
	CPhysicsObj *		pContainer;
	CBaseItem *			pItem;

	//Find the container.
	if (!(m_pPlayer->Container_HasContents()))
		return;
	if (!(pContainer = m_pPlayer->Container_FindContainer(dwContainer)))
		return;

	//Find the item.
	if (!(pEntity = g_pWorld->FindWithinPVS(m_pPlayer, dwItemID)))
		return;
	if (!(pEntity->IsItem()))
		return;

	pItem = (CBaseItem *)pEntity;
	if (!(pItem->CanPickup()))
		return;
	if (!(pContainer->Container_CanStore(pItem)))
		return;
	if (pItem->GetSlotType()) //Only store/move ordinary items for now.
		return;

	DWORD dwCell = m_pPlayer->GetLandcell();

	//Take it out of whatever slot it's in.
	pItem->SetWorldContainer(dwCell, pContainer);
	if (pItem->IsWielded())
		pItem->SetWorldWielder(dwCell, NULL);

	m_pPlayer->Container_ReleaseItem(pItem, FALSE);

	//The container will auto-correct this slot into a valid range.
	cSlot = pContainer->Container_InsertInventoryItem(dwCell, pItem, cSlot);

	m_pPlayer->EmitSound(0x78, 1.0f);
	m_pClient->SendNetMessage(InventoryMove(dwItemID, dwContainer, cSlot, 0), PRIVATE_MSG, TRUE);

	UpdateBurdenUI();

	if (pItem->m_ItemType & (TYPE_ARMOR | TYPE_CLOTHING))
		m_pPlayer->UpdateModel();
}

void CClientEvents::DropItem(DWORD dwItemID)
{
	// Scenarios to consider:
	// 1. Item being stored is equipped.
	// 3. Item being stored is in the inventory.

	CBaseItem *			pItem;

	//Find the container.
	if (!(m_pPlayer->Container_HasContents()))
		return;

	//Find the item.
	if (!(pItem = m_pPlayer->Container_FindItem(dwItemID)))
		return;

	m_pPlayer->Animation_PlayPrimary(24, 1.0f, 2.0f);
	DWORD dwCell = m_pPlayer->GetLandcell();

	//Take it out of whatever slot it's in.
	m_pPlayer->Container_ReleaseItem(pItem, FALSE);

	pItem->SetWorldContainer(dwCell, NULL);
	//if ( pItem->IsWielded() )
	pItem->SetWorldWielder(dwCell, NULL);

	m_pPlayer->EmitSound(0x7B, 1.0f);
	m_pClient->SendNetMessage(InventoryDrop(dwItemID), PRIVATE_MSG, TRUE);

	g_pWorld->InsertEntity(pItem);

	pItem->Movement_Teleport(m_pPlayer->m_Origin, m_pPlayer->m_Angles);

	UpdateBurdenUI();

	if (pItem->m_ItemType & (TYPE_ARMOR | TYPE_CLOTHING))
		m_pPlayer->UpdateModel();
}

void CClientEvents::ChangeCombatStance(DWORD dwStance)
{
	m_pPlayer->Animation_SetCombatMode((WORD)dwStance);

	ActionComplete();
}

void CClientEvents::ExitPortal()
{
	m_pPlayer->ExitPortal();
}

void CClientEvents::Ping()
{
	// Pong!
	DWORD Pong = 0x1EA;
	m_pClient->SendNetMessage(&Pong, sizeof(Pong), PRIVATE_MSG, TRUE);
}

void CClientEvents::UseItemEx(DWORD dwSourceID, DWORD dwDestID)
{
	CPhysicsObj *pSource = m_pPlayer->FindChild(dwSourceID);
	CPhysicsObj *pDest = m_pPlayer->FindChild(dwDestID);

	if (!pSource)
		pSource = g_pWorld->FindWithinPVS(m_pPlayer, dwSourceID);

	if (!pDest)
		pDest = g_pWorld->FindWithinPVS(m_pPlayer, dwDestID);

	if (pSource && pDest)
		pDest->UseEx(m_pPlayer, pSource);
	else
		ActionComplete();
}

void CClientEvents::UseObject(DWORD dwEID)
{
	CPhysicsObj *pTarget = m_pPlayer->FindChild(dwEID);

	if (!pTarget)
		pTarget = g_pWorld->FindWithinPVS(m_pPlayer, dwEID);

	if (pTarget)
		pTarget->Use(m_pPlayer);
	else
		ActionComplete();
}

void CClientEvents::ActionComplete()
{
	DWORD ActionComplete[2];
	ActionComplete[0] = 0x1C7;
	ActionComplete[1] = 0;

	m_pClient->SendNetMessage(ActionComplete, sizeof(ActionComplete), PRIVATE_MSG, TRUE);
}

void CClientEvents::Identify(DWORD dwObjectID)
{
	SendText("Identify will be incomplete -- it's not done!", 9);

	CPhysicsObj *pTarget = m_pPlayer->FindChild(dwObjectID);

	if (!pTarget)
		pTarget = g_pWorld->FindWithinPVS(m_pPlayer, dwObjectID);

	if (pTarget)
	{
		pTarget->Identify(m_pPlayer);
		m_pPlayer->SetLastAssessed(pTarget->m_dwGUID);
	}

	ActionComplete();
}

void CClientEvents::SpendAttributeXP(DWORD dwAttribute, DWORD dwXP)
{
	if (dwAttribute < 1 || dwAttribute > 6)
		return;

	DWORD dwUnassignedXP = m_pPlayer->GetObjectStat(eUnassignedXP);
	if (dwUnassignedXP < dwXP)
		return;

	m_pPlayer->GiveAttributeXP((eAttribute)dwAttribute, dwXP);
	m_pPlayer->SetObjectStat(eUnassignedXP, dwUnassignedXP - dwXP);
}

void CClientEvents::SpendVitalXP(DWORD dwVital, DWORD dwXP)
{
	if (dwVital != 1 && dwVital != 3 && dwVital != 5)
		return;

	DWORD dwUnassignedXP = m_pPlayer->GetObjectStat(eUnassignedXP);
	if (dwUnassignedXP < dwXP)
		return;

	m_pPlayer->GiveVitalXP((eVital)dwVital, dwXP);
	m_pPlayer->SetObjectStat(eUnassignedXP, dwUnassignedXP - dwXP);
}

void CClientEvents::SpendSkillXP(DWORD dwSkill, DWORD dwXP)
{
	if (dwSkill < 1 || dwSkill > 0x27)
		return;

	DWORD dwUnassignedXP = m_pPlayer->GetObjectStat(eUnassignedXP);
	if (dwUnassignedXP < dwXP)
		return;

	m_pPlayer->GiveSkillXP((eSkill)dwSkill, dwXP);
	m_pPlayer->SetObjectStat(eUnassignedXP, dwUnassignedXP - dwXP);
}

void CClientEvents::LifestoneRecall()
{
	//150 or 163?

	if (m_pPlayer->m_bLifestoneBound)
	{
		m_pPlayer->Animation_PlaySimpleAnimation(0x163, 1.0f, 18.0f, ANIM_LSRECALL);
	}
	else
	{
		m_pClient->SendNetMessage(ServerText("You are not bound to a Lifestone!", 7), PRIVATE_MSG);
	}
}

void CClientEvents::MarketplaceRecall()
{
	//150 or 163?
	m_pPlayer->Animation_PlaySimpleAnimation(0x163, 1.0f, 18.0f, ANIM_MPRECALL);
}

// This is it!
void CClientEvents::ProcessEvent(BinaryReader *in)
{
	if (!m_pPlayer)	return;

	DWORD dwSequence = in->ReadDWORD();
	DWORD dwEvent = in->ReadDWORD();
	if (in->GetLastError()) return;

#ifdef _DEBUG
	LOG(Client, Verbose, "Processing event: 0x%X\n", dwEvent);
#endif

	switch (dwEvent)
	{
		case 0x0008: //Attack
		{
			DWORD dwTarget = in->ReadDWORD();
			DWORD dwHeight = in->ReadDWORD();
			float flPower = in->ReadFloat();
			if (in->GetLastError()) break;

			Attack(dwTarget, dwHeight, flPower);
			break;
		}
		case 0x0015: //Client Text
		{
			char *szText = in->ReadString();
			if (in->GetLastError()) break;

			ClientText(szText);
			break;
		}
		case 0x0019: //Store Item
		{
			DWORD dwItemID = in->ReadDWORD();
			DWORD dwContainer = in->ReadDWORD();
			DWORD dwSlot = in->ReadDWORD();
			if (in->GetLastError()) break;

			StoreItem(dwItemID, dwContainer, (char)dwSlot);
			break;
		}
		case 0x001A: //Equip Item
		{
			DWORD dwItemID = in->ReadDWORD();
			DWORD dwCoverage = in->ReadDWORD();
			if (in->GetLastError()) break;

			EquipItem(dwItemID, dwCoverage);
			break;
		}
		case 0x001B: //Drop Item
		{
			DWORD dwItemID = in->ReadDWORD();
			if (in->GetLastError()) break;

			DropItem(dwItemID);
			break;
		}
		case 0x001F: // TOD 2017
		{
			DWORD dwUnk = in->ReadDWORD(); // value was "1"
			if (in->GetLastError()) break;

			break;
		}
		case 0x0032: //Send Tell by GUID
		{
			char* szText = in->ReadString();
			DWORD dwGUID = in->ReadDWORD();
			if (in->GetLastError()) break;

			SendTellByGUID(szText, dwGUID);
			break;
		}
		case 0x0035: //Use Item Ex
		{
			//SendText("Use extended not implemented yet.", 9);
			DWORD dwSourceID = in->ReadDWORD();
			DWORD dwDestID = in->ReadDWORD();
			if (in->GetLastError()) break;

			UseItemEx(dwSourceID, dwDestID);
			break;
		}
		case 0x0036: //Use Object
		{
			//SendText("Use not implemented yet.", 9);
			DWORD dwEID = in->ReadDWORD();
			if (in->GetLastError()) break;

			UseObject(dwEID);
			break;
		}
		case 0x0044: //Spend Vital XP
		{
			DWORD dwVital = in->ReadDWORD();
			DWORD dwXP = in->ReadDWORD();
			if (in->GetLastError()) break;

			SpendVitalXP(dwVital, dwXP);
			break;
		}
		case 0x0045: //Spend Attribute XP
		{
			DWORD dwAttribute = in->ReadDWORD();
			DWORD dwXP = in->ReadDWORD();
			if (in->GetLastError()) break;

			SpendAttributeXP(dwAttribute, dwXP);
			break;
		}
		case 0x0046: //Spend Skill XP
		{
			DWORD dwSkill = in->ReadDWORD();
			DWORD dwXP = in->ReadDWORD();
			if (in->GetLastError()) break;

			SpendSkillXP(dwSkill, dwXP);
			break;
		}
		case 0x0053: //Change Combat Mode
		{
			DWORD dwStance = in->ReadDWORD();
			if (in->GetLastError()) break;

			ChangeCombatStance(dwStance);
			break;
		}
		case 0x005D: //Send Tell by Name
		{
			char* szText = in->ReadString();
			char* szName = in->ReadString();
			if (in->GetLastError()) break;

			SendTellByName(szText, szName);
			break;
		}
		case 0x0063: //Lifestone Recall
		{
			LifestoneRecall();
			break;
		}
		case 0x00A1: //  "Login Complete"
		{
			ExitPortal();
			break;
		}
		case 0x00C8: //Identify
		{
			DWORD dwObjectID = in->ReadDWORD();
			if (in->GetLastError()) break;

			Identify(dwObjectID);
			break;
		}
		case 0x00D6: // Advocate teleport (triggered by having an admin flag set, clicking the mini-map)
		{
			// Starts with string (was empty when I tested)
			in->ReadString();

			// Then position (target)
			loc_t loc = in->Read<loc_t>();
			heading_t angles = in->Read<heading_t>();
			m_pPlayer->Movement_Teleport(loc, angles);
			break;
		}
		case 0x0147: //Channel Text
		{
			DWORD dwChannel = in->ReadDWORD();
			char* szText = in->ReadString();
			if (in->GetLastError()) break;

			ChannelText(dwChannel, szText);
		}
		break;
		case 0x01BF: //Request health update
		{
			DWORD dwGUID = in->ReadDWORD();
			if (in->GetLastError()) break;

			RequestHealthUpdate(dwGUID);
			break;
		}
		case 0x01DF: //Indirect Text (@me)
		{
			char *szText = in->ReadString();
			if (in->GetLastError()) break;

			EmoteText(szText);
			break;
		}
		case 0x01E1: //Emote Text (*laugh* sends 'laughs')
		{
			char *szText = in->ReadString();
			if (in->GetLastError()) break;

			ActionText(szText);
			break;
		}
		case 0x01E9: //Ping
		{
			Ping();
			break;
		}
		case 0x028D: //Marketplace Recall
		{
			MarketplaceRecall();
			break;
		}
		case 0xF61B: //Jump Movement
		{
			float flPower = in->ReadFloat();
			float flDir1 = in->ReadFloat();
			float flDir2 = in->ReadFloat();
			float flHeight = in->ReadFloat();

			loc_t *location = (loc_t *)in->ReadArray(sizeof(loc_t));
			heading_t *angles = (heading_t *)in->ReadArray(sizeof(heading_t));
			if (in->GetLastError()) break;

			memcpy(&m_pPlayer->m_Origin, location, sizeof(loc_t));
			memcpy(&m_pPlayer->m_Angles, angles, sizeof(heading_t));

			m_pPlayer->Animation_Jump(flPower, flDir1, flDir2, flHeight);
			m_pPlayer->m_bAnimUpdate = TRUE;
			break;
		}
		case 0xF61C: //Update Vector Movement
		{
			float fSpeedMod = m_pPlayer->m_fSpeedMod;
			float flForwardMod = 0;
			float flStrafMod = 0;
			float flTurnMod = 0;
			WORD wForwardAnim = 0;
			WORD wStrafAnim = 0;
			WORD wTurnAnim = 0;
			WORD wStance = 0x3D;
			BOOL bRun = FALSE;

			DWORD dwFlags = in->ReadDWORD();

			if (dwFlags & 1)
			{
				DWORD dwRunUnk = in->ReadDWORD();
	#ifdef _DEBUG
				if (dwRunUnk != 2)
				{
					LOG(Temp, Normal, "RunUnk is %08X??\n", dwRunUnk);
				}
				else
	#endif
					bRun = TRUE;
			}

			if (!bRun)
				fSpeedMod = 1.0f;

			if (dwFlags & 2)
			{
				wStance = in->ReadWORD();
				in->ReadWORD(); //0x8000?? sequence maybe
			}

			if (dwFlags & 4)
			{
				DWORD dwForwardAnim = in->ReadDWORD();
				flForwardMod = fSpeedMod;

				switch (dwForwardAnim)
				{
				case 0x45000006: //backwards
					dwForwardAnim--;
					flForwardMod *= -backwards_factor;
					break;
				case 0x45000005: //forwards

					if (bRun)
						dwForwardAnim += 2;

					break;
				default:
					//0x45 = run/walk forward/back
					//0x44 = run/walk change
					//0x43 = emotes?
					//0x41 = change position? (kneel, sleep..)
					//0x40 = attack animations?
					//0x13 = drink something

					if ((dwForwardAnim >> 24) != 0x45)
						LOG(Temp, Normal, "Forward: %08X\n", dwForwardAnim);

					flForwardMod = 1.0000f;
					break;
				}
				wForwardAnim = (WORD)dwForwardAnim;
			}

			if (dwFlags & 8) {
				DWORD dwAutorunUnk = in->ReadDWORD();

				if (dwAutorunUnk != 2)
				{
					SendText("You cannot use right-click movement at this time!", 1);
					LOG(Temp, Normal, "Autorun is %08X?\n", dwAutorunUnk);
				}
				else
				{
					if (!bRun)
						wForwardAnim = 0x0005;
					else
						wForwardAnim = 0x0007;
					flForwardMod = fSpeedMod;
				}
			}

			if (dwFlags & 0x20) {
				DWORD dwStrafAnim = in->ReadDWORD();
				flStrafMod = fSpeedMod;

				switch (dwStrafAnim)
				{
				case 0x65000010: //left?
					dwStrafAnim--;
					flStrafMod *= -1.0f;
				case 0x6500000F: //right?
					flStrafMod *= (walk_anim_speed / sidestep_anim_speed) * sidestep_factor;

					if (flStrafMod > max_sidestep_anim_rate)
						flStrafMod = max_sidestep_anim_rate;
					else if (flStrafMod < -max_sidestep_anim_rate)
						flStrafMod = -max_sidestep_anim_rate;

					break;
				default:
					if ((dwStrafAnim >> 24) != 0x65)
					{
						// DebugMe();
						LOG(Animation, Warning, "Strafe anim == 0x%02X", dwStrafAnim >> 24);
					}

					flStrafMod = 1.0000f;
					break;
				}
				wStrafAnim = (WORD)dwStrafAnim;
			}

			if (dwFlags & 0x100) {
				DWORD dwTurnAnim = in->ReadDWORD();

				flTurnMod = 1.0f;
				switch (dwTurnAnim)
				{
				case 0x6500000E: //turn left

					dwTurnAnim--;
					flTurnMod *= -1.0f;

				case 0x6500000D: //turn right
					if (bRun)
						flTurnMod *= run_turn_factor;
					break;
				default:
					if ((dwTurnAnim >> 24) != 0x65)
					{
						LOG(Animation, Warning, "Turn anim == 0x%02X\n", dwTurnAnim >> 24);
					}

					break;
				}

				wTurnAnim = (WORD)dwTurnAnim;
			}

			DWORD dwEmotes = (dwFlags >> 11) & 7;
			animation_list lEmotes;

			for (unsigned int i = 0; i < dwEmotes; i++)
			{
				animation_t emote;

				emote.wIndex = in->ReadWORD();	//animation
				emote.wSequence = in->ReadWORD();	//counter
				emote.fSpeed = in->ReadFloat();	//play speed

				lEmotes.push_back(emote);
			}

			loc_t *location = (loc_t *)in->ReadArray(sizeof(loc_t));
			heading_t *angles = (heading_t *)in->ReadArray(sizeof(heading_t));

			if (in->GetLastError())
			{
				LOG(Animation, Verbose, "Bad animation message:\n");
				LOG_BYTES(Animation, Verbose, in->GetDataStart(), in->GetDataLen());
				break;
			}

			memcpy(&m_pPlayer->m_Origin, location, sizeof(loc_t));
			memcpy(&m_pPlayer->m_Angles, angles, sizeof(heading_t));

			m_pPlayer->m_fForwardSpeed = flForwardMod;
			m_pPlayer->m_fStrafSpeed = flStrafMod;
			m_pPlayer->m_fTurnSpeed = flTurnMod;

			m_pPlayer->m_wForwardAnim = wForwardAnim;
			m_pPlayer->m_wStrafAnim = wStrafAnim;
			m_pPlayer->m_wTurnAnim = wTurnAnim;
			m_pPlayer->m_wStance = wStance;

			DWORD dwAnimQueued = m_pPlayer->Animation_EmoteQueueCount();
			DWORD dwAnimNeeded = lEmotes.size();
			DWORD dwLastSequence = 0;

			for (unsigned int i = 0; i < dwAnimNeeded; i++)
			{
				if (dwAnimQueued >= MAX_EMOTE_QUEUE)
					break;
				dwAnimQueued++;

				for (animation_list::iterator it = lEmotes.begin(); it != lEmotes.end(); it++)
				{
					//find 0x800x
					if ((it->wSequence & 0xFF) == i)
					{
						m_pPlayer->Animation_PlayEmote(it->wIndex, it->fSpeed);
						break;
					}
				}
			}

			m_pPlayer->m_bAnimUpdate = TRUE;
			m_pPlayer->Movement_UpdatePos();
			break;
		}
		case 0xF753: //Update Exact Position
		{
			loc_t*		location = reinterpret_cast<loc_t*>	(in->ReadArray(sizeof(loc_t)));
			heading_t*	angles = reinterpret_cast<heading_t*>(in->ReadArray(sizeof(heading_t)));
			WORD		instance = in->ReadWORD();

			if (in->GetLastError()) break;
			if (instance != m_pPlayer->m_wInstance)
			{
				LOG(Temp, Normal, "Bad instance!!!!!!!!!!!!\n");
				break;
			}

			memcpy(&m_pPlayer->m_Origin, location, sizeof(loc_t));
			memcpy(&m_pPlayer->m_Angles, angles, sizeof(heading_t));

			m_pPlayer->Movement_UpdatePos();
			break;
		}
		default:
		{
			//Unknown Event
			// LOG(Temp, Normal, "Unhandled client event 0x%X:\n", dwEvent );
			// LOG_BYTES(Temp, Verbose, in->GetDataPtr(), in->GetDataEnd() - in->GetDataPtr() );
		}
	}
}








