#include "GameMessages.h"
#include "User.h"
#include "Entity.h"
#include "PacketUtils.h"
#include "BitStream.h"
#include "Game.h"
#include "dMessageIdentifiers.h"
#include "SlashCommandHandler.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "dLogger.h"
#include "GeneralUtils.h"
#include "Character.h"
#include "EntityManager.h"
#include "Database.h"
#include "dServer.h"
#include "../dWorldServer/ObjectIDManager.h"
#include "CppScripts.h"
#include "UserManager.h"
#include "ZoneInstanceManager.h"
#include "WorldPackets.h"
#include "Item.h"
#include "ZCompression.h"
#include "Player.h"
#include "dConfig.h"
#include "TeamManager.h"
#include "ChatPackets.h"
#include "GameConfig.h"
#include "RocketLaunchLupComponent.h"
#include "eUnequippableActiveType.h"

#include <sstream>
#include <future>
#include <chrono>
#include "RakString.h"

#include "../thirdparty/cpp-httplib/httplib.h" //sorry not sorry.

//CDB includes:
#include "CDClientManager.h"
#include "CDEmoteTable.h"

//Component includes:
#include "ControllablePhysicsComponent.h"
#include "CharacterComponent.h"
#include "MissionOfferComponent.h"
#include "MissionComponent.h"
#include "DestroyableComponent.h"
#include "ScriptComponent.h"
#include "RebuildComponent.h"
#include "VendorComponent.h"
#include "RocketLaunchpadControlComponent.h"
#include "PropertyEntranceComponent.h"
#include "MovingPlatformComponent.h"
#include "PetComponent.h"
#include "ModuleAssemblyComponent.h"
#include "VehiclePhysicsComponent.h"
#include "PossessableComponent.h"
#include "PossessorComponent.h"
#include "RacingControlComponent.h"
#include "RailActivatorComponent.h"
#include "LevelProgressionComponent.h"

// Message includes:
#include "dZoneManager.h"
#include "PropertyDataMessage.h"
#include "PropertyManagementComponent.h"
#include "PropertyVendorComponent.h"
#include "PropertySelectQueryProperty.h"
#include "TradingManager.h"

void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_FIRE_EVENT_CLIENT_SIDE);

	//bitStream.Write(args);
	uint32_t argSize = args.size();
	bitStream.Write(argSize);
	for (uint32_t k = 0; k < argSize; k++) {
		bitStream.Write(static_cast<uint16_t>(args[k]));
	}
	bitStream.Write(object);
	bitStream.Write0();
	//bitStream.Write(param1);
	bitStream.Write0();
	//bitStream.Write(param2);
	bitStream.Write(sender);

	SEND_PACKET;
}

void GameMessages::SendTeleport(const LWOOBJID& objectID, const NiPoint3& pos, const NiQuaternion& rot, const SystemAddress& sysAddr, bool bSetRotation, bool noGravTeleport) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_TELEPORT);

	bool bIgnoreY = (pos.y == 0.0f);
	bool bUseNavmesh = false;
	bool bSkipAllChecks = false;
	//float w = 1.0f;
	//float x = 0.0f;
	//float y = 0.0f;
	//float z = 0.0f;

	bitStream.Write(bIgnoreY);
	bitStream.Write(bSetRotation);
	bitStream.Write(bSkipAllChecks);
	bitStream.Write(pos.x);
	bitStream.Write(pos.y);
	bitStream.Write(pos.z);
	bitStream.Write(bUseNavmesh);
	bitStream.Write(noGravTeleport);

	bitStream.Write(rot.w != 1.0f);
	if (rot.w != 1.0f) bitStream.Write(rot.w);

	bitStream.Write(rot.x);
	bitStream.Write(rot.y);
	bitStream.Write(rot.z);

	SEND_PACKET;
}

void GameMessages::SendPlayAnimation(Entity* entity, const std::u16string& animationName, float fPriority, float fScale) {
	if (!entity) {
		Game::logger->Log("SendPlayAnimation", "Trying to play animation, but entity var is nullptr!");
		return;
	}

	//Stolen from the old DLU codebase as the new one's autogenerated code doesn't work properly for animationIDs longer than 6 characters.
	CBITSTREAM;
	CMSGHEADER;
	uint16_t gameMsgID = GAME_MSG_PLAY_ANIMATION;
	std::string sAnimationID = GeneralUtils::UTF16ToWTF8(animationName);

	uint32_t animationIDLength = sAnimationID.size();
	bool bExpectAnimToExist = true;
	bool bPlayImmediate = false;
	bool bTriggerOnCompleteMsg = false;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(gameMsgID);

	bitStream.Write(animationIDLength);
	PacketUtils::WriteWString(bitStream, animationName, animationIDLength);

	bitStream.Write(bExpectAnimToExist);

	bitStream.Write(bPlayImmediate);

	bitStream.Write(bTriggerOnCompleteMsg);

	bitStream.Write(fPriority != 0.0f);
	if (fPriority != 0.0f) bitStream.Write(fPriority);

	bitStream.Write(fScale != 1.0f);
	if (fScale != 1.0f) bitStream.Write(fScale);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_PLAYER_READY);
	SEND_PACKET;
}

void GameMessages::SendPlayerAllowedRespawn(LWOOBJID entityID, bool doNotPromptRespawn, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entityID);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_PLAYER_ALLOWED_RESPAWN);
	bitStream.Write(doNotPromptRespawn);

	SEND_PACKET;
}

void GameMessages::SendInvalidZoneTransferList(Entity* entity, const SystemAddress& sysAddr, const std::u16string& feedbackURL, const std::u16string& invalidMapTransferList, bool feedbackOnExit, bool feedbackOnInvalidTransfer) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_INVALID_ZONE_TRANSFER_LIST);

	uint32_t CustomerFeedbackURLLength = feedbackURL.size();
	bitStream.Write(CustomerFeedbackURLLength);
	for (uint32_t k = 0; k < CustomerFeedbackURLLength; k++) {
		bitStream.Write(static_cast<uint16_t>(feedbackURL[k]));
	}

	uint32_t InvalidMapTransferListLength = invalidMapTransferList.size();
	bitStream.Write(InvalidMapTransferListLength);
	for (uint32_t k = 0; k < InvalidMapTransferListLength; k++) {
		bitStream.Write(static_cast<uint16_t>(invalidMapTransferList[k]));
	}

	bitStream.Write(feedbackOnExit);
	bitStream.Write(feedbackOnInvalidTransfer);

	SEND_PACKET;
}

void GameMessages::SendKnockback(const LWOOBJID& objectID, const LWOOBJID& caster, const LWOOBJID& originator, int knockBackTimeMS, const NiPoint3& vector) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG_KNOCKBACK);

	bool casterFlag = caster != LWOOBJID_EMPTY;
	bool originatorFlag = originator != LWOOBJID_EMPTY;
	bool knockBackTimeMSFlag = knockBackTimeMS != 0;

	bitStream.Write(casterFlag);
	if (casterFlag) bitStream.Write(caster);
	bitStream.Write(originatorFlag);
	if (originatorFlag) bitStream.Write(originator);
	bitStream.Write(knockBackTimeMSFlag);
	if (knockBackTimeMSFlag) bitStream.Write(knockBackTimeMS);
	bitStream.Write(vector);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendStartArrangingWithItem(
	Entity* entity,
	const SystemAddress& sysAddr,
	bool bFirstTime,
	const LWOOBJID& buildAreaID,
	NiPoint3 buildStartPOS,
	int sourceBAG,
	const LWOOBJID& sourceID,
	LOT sourceLOT,
	int sourceTYPE,
	const LWOOBJID& targetID,
	LOT targetLOT,
	NiPoint3 targetPOS,
	int targetTYPE
) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_START_ARRANGING_WITH_ITEM);

	bitStream.Write(bFirstTime);
	bitStream.Write(buildAreaID != LWOOBJID_EMPTY);
	if (buildAreaID != LWOOBJID_EMPTY) bitStream.Write(buildAreaID);
	bitStream.Write(buildStartPOS);
	bitStream.Write(sourceBAG);
	bitStream.Write(sourceID);
	bitStream.Write(sourceLOT);
	bitStream.Write(sourceTYPE);
	bitStream.Write(targetID);
	bitStream.Write(targetLOT);
	bitStream.Write(targetPOS);
	bitStream.Write(targetTYPE);

	SEND_PACKET;
}

void GameMessages::SendPlayerSetCameraCyclingMode(const LWOOBJID& objectID, const SystemAddress& sysAddr,
	bool bAllowCyclingWhileDeadOnly, eCyclingMode cyclingMode) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG_PLAYER_SET_CAMERA_CYCLING_MODE);

	bitStream.Write(bAllowCyclingWhileDeadOnly);

	bitStream.Write(cyclingMode != ALLOW_CYCLE_TEAMMATES);
	if (cyclingMode != ALLOW_CYCLE_TEAMMATES) {
		bitStream.Write<uint32_t>(cyclingMode);
	}

	SEND_PACKET;
}

void GameMessages::SendPlayNDAudioEmitter(Entity* entity, const SystemAddress& sysAddr, std::string audioGUID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLAY_ND_AUDIO_EMITTER);
	bitStream.Write0();
	bitStream.Write0();

	uint32_t length = audioGUID.size();
	bitStream.Write(length);
	for (uint32_t k = 0; k < length; k++) {
		bitStream.Write(static_cast<char>(audioGUID[k]));
	}

	//PacketUtils::WriteString(bitStream, audioGUID, audioGUID.size());

	//bitStream.Write(uint32_t(audioGUID.size()));
	//for (char character : audioGUID) {
	//	bitStream.Write(character);
	//}

	bitStream.Write(uint32_t(0));
	bitStream.Write0();
	bitStream.Write0();

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendStartPathing(Entity* entity) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write<uint16_t>(GAME_MSG_START_PATHING);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAddr, bool bStopAtDesiredWaypoint,
	int iIndex, int iDesiredWaypointIndex, int nextIndex,
	MovementPlatformState movementState) {
	CBITSTREAM;
	CMSGHEADER;

	const auto lot = entity->GetLOT();

	if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449) {
		iDesiredWaypointIndex = 0;
		iIndex = 0;
		nextIndex = 0;
		bStopAtDesiredWaypoint = true;
		movementState = MovementPlatformState::Stationary;
	}

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLATFORM_RESYNC);

	bool bReverse = false;
	int eCommand = 0;
	int eUnexpectedCommand = 0;
	float fIdleTimeElapsed = 0.0f;
	float fMoveTimeElapsed = 0.0f;
	float fPercentBetweenPoints = 0.0f;
	NiPoint3 ptUnexpectedLocation = NiPoint3::ZERO;
	NiQuaternion qUnexpectedRotation = NiQuaternion::IDENTITY;

	bitStream.Write(bReverse);
	bitStream.Write(bStopAtDesiredWaypoint);
	bitStream.Write(eCommand);
	bitStream.Write<int32_t>(static_cast<int32_t>(movementState));
	bitStream.Write(eUnexpectedCommand);
	bitStream.Write(fIdleTimeElapsed);
	bitStream.Write(fMoveTimeElapsed);
	bitStream.Write(fPercentBetweenPoints);
	bitStream.Write(iDesiredWaypointIndex);
	bitStream.Write(iIndex);
	bitStream.Write(nextIndex);
	bitStream.Write(ptUnexpectedLocation.x);
	bitStream.Write(ptUnexpectedLocation.y);
	bitStream.Write(ptUnexpectedLocation.z);

	bitStream.Write(qUnexpectedRotation != NiQuaternion::IDENTITY);
	if (qUnexpectedRotation != NiQuaternion::IDENTITY) {
		bitStream.Write(qUnexpectedRotation.x);
		bitStream.Write(qUnexpectedRotation.y);
		bitStream.Write(qUnexpectedRotation.z);
		bitStream.Write(qUnexpectedRotation.w);
	}

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_RESTORE_TO_POST_LOAD_STATS);
	SEND_PACKET;
}

void GameMessages::SendServerDoneLoadingAllObjects(Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_DONE_LOADING_ALL_OBJECTS);
	SEND_PACKET;
}

void GameMessages::SendChatModeUpdate(const LWOOBJID& objectID, uint8_t level) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG_UPDATE_CHAT_MODE);
	bitStream.Write(level);
	SEND_PACKET_BROADCAST;
}

void GameMessages::SendGMLevelBroadcast(const LWOOBJID& objectID, uint8_t level) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG_SET_GM_LEVEL);
	bitStream.Write1();
	bitStream.Write(level);
	SEND_PACKET_BROADCAST;
}

void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey, eLootSourceType lootSourceType) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(static_cast<uint16_t>(GAME_MSG_ADD_ITEM_TO_INVENTORY_CLIENT_SYNC));
	bitStream.Write(item->GetBound());
	bitStream.Write(item->GetInfo().isBOE);
	bitStream.Write(item->GetInfo().isBOP);

	bitStream.Write(lootSourceType != eLootSourceType::LOOT_SOURCE_NONE); // Loot source
	if (lootSourceType != eLootSourceType::LOOT_SOURCE_NONE) bitStream.Write(lootSourceType);
	LWONameValue extraInfo;

	auto config = item->GetConfig();

	for (auto* data : config) {
		extraInfo.name += GeneralUtils::ASCIIToUTF16(data->GetString()) + u",";
	}

	if (extraInfo.name.length() > 0) extraInfo.name.pop_back(); // remove the last comma

	bitStream.Write<uint32_t>(extraInfo.name.size());
	if (extraInfo.name.size() > 0) {
		for (uint32_t i = 0; i < extraInfo.name.size(); ++i) {
			bitStream.Write(static_cast<uint16_t>(extraInfo.name[i]));
		}
		bitStream.Write(static_cast<uint16_t>(0x00));
	}

	bitStream.Write(item->GetLot());

	bitStream.Write(subKey != LWOOBJID_EMPTY);
	if (subKey != LWOOBJID_EMPTY) bitStream.Write(subKey);

	auto* inventory = item->GetInventory();
	const auto inventoryType = inventory->GetType();

	bitStream.Write(inventoryType != eInventoryType::ITEMS);
	if (inventoryType != eInventoryType::ITEMS) bitStream.Write(inventoryType);

	bitStream.Write(itemCount != 1);
	if (itemCount != 1) bitStream.Write(itemCount);

	const auto count = item->GetCount();

	bitStream.Write(count != 0); //items total
	if (count != 0) bitStream.Write(count);

	bitStream.Write(objectID);
	bitStream.Write(0.0f);
	bitStream.Write(0.0f);
	bitStream.Write(0.0f);
	bitStream.Write(showFlyingLoot);
	bitStream.Write(item->GetSlot());

	SEND_PACKET;
}

void GameMessages::SendNotifyClientFlagChange(const LWOOBJID& objectID, int iFlagID, bool bFlag, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG_NOTIFY_CLIENT_FLAG_CHANGE);
	bitStream.Write(bFlag);
	bitStream.Write(iFlagID);

	SEND_PACKET;
}

void GameMessages::SendChangeObjectWorldState(const LWOOBJID& objectID, int state, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG_CHANGE_OBJECT_WORLD_STATE);
	bitStream.Write(state);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
		SEND_PACKET;
}

void GameMessages::SendOfferMission(const LWOOBJID& entity, const SystemAddress& sysAddr, int32_t missionID, const LWOOBJID& offererID) {
	//You might be wondering.
	//"Why are we sending it twice, once to a non-player object?
	//Well, the first one (sent to the offerer) makes the client zoom into the object.
	//The second, actually makes the UI pop up so you can be offered the mission.
	//Why is it like this? Because LU isn't just a clown, it's the entire circus.

	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(offererID);
	bitStream.Write(uint16_t(GAME_MSG_OFFER_MISSION));
	bitStream.Write(missionID);
	bitStream.Write(offererID);

	SEND_PACKET;

	{
		CBITSTREAM;
		CMSGHEADER;

		bitStream.Write(entity);
		bitStream.Write(uint16_t(GAME_MSG_OFFER_MISSION));
		bitStream.Write(missionID);
		bitStream.Write(offererID);

		SEND_PACKET;
	}
}

void GameMessages::SendNotifyMission(Entity* entity, const SystemAddress& sysAddr, int missionID, int missionState, bool sendingRewards) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_NOTIFY_MISSION));
	bitStream.Write(missionID);
	bitStream.Write(missionState);
	bitStream.Write(sendingRewards);

	SEND_PACKET;
}

void GameMessages::SendNotifyMissionTask(Entity* entity, const SystemAddress& sysAddr, int missionID, int taskMask, std::vector<float> updates) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_NOTIFY_MISSION_TASK);

	bitStream.Write(missionID);
	bitStream.Write(taskMask);
	bitStream.Write((unsigned char)updates.size());

	for (uint32_t i = 0; i < updates.size(); ++i) {
		bitStream.Write(updates[i]);
	}

	SEND_PACKET;
}

void GameMessages::SendModifyLEGOScore(Entity* entity, const SystemAddress& sysAddr, int64_t score, eLootSourceType sourceType) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_MODIFY_LEGO_SCORE);
	bitStream.Write(score);

	bitStream.Write(sourceType != eLootSourceType::LOOT_SOURCE_NONE);
	if (sourceType != eLootSourceType::LOOT_SOURCE_NONE) bitStream.Write(sourceType);

	SEND_PACKET;
}

void GameMessages::SendUIMessageServerToSingleClient(Entity* entity, const SystemAddress& sysAddr, const std::string& message, NDGFxValue args) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_UI_MESSAGE_SERVER_TO_SINGLE_CLIENT);

	bitStream.Write(args);
	uint32_t strMessageNameLength = message.size();
	bitStream.Write(strMessageNameLength);

	for (uint32_t k = 0; k < strMessageNameLength; k++) {
		bitStream.Write(static_cast<char>(message[k]));
	}

	SEND_PACKET;
}

void GameMessages::SendUIMessageServerToAllClients(const std::string& message, NDGFxValue args) {
	CBITSTREAM;
	CMSGHEADER;

	LWOOBJID empty = 0;
	bitStream.Write(empty);
	bitStream.Write((uint16_t)GAME_MSG_UI_MESSAGE_SERVER_TO_ALL_CLIENTS);

	bitStream.Write(args);
	uint32_t strMessageNameLength = message.size();
	bitStream.Write(strMessageNameLength);

	for (uint32_t k = 0; k < strMessageNameLength; k++) {
		bitStream.Write(static_cast<char>(message[k]));
	}

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(Entity* entity, std::u16string effectName, const LWOOBJID& fromObjectID, float radius) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLAY_EMBEDDED_EFFECT_ON_ALL_CLIENTS_NEAR_OBJECT);

	bitStream.Write(static_cast<uint32_t>(effectName.length()));
	for (uint32_t k = 0; k < effectName.length(); k++) {
		bitStream.Write(static_cast<uint16_t>(effectName[k]));
	}
	bitStream.Write(fromObjectID);
	bitStream.Write(radius);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendPlayFXEffect(Entity* entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority, float scale, bool serialize) {
	SendPlayFXEffect(entity->GetObjectID(), effectID, effectType, name, secondary, priority, scale, serialize);
}

void GameMessages::SendPlayFXEffect(const LWOOBJID& entity, int32_t effectID, const std::u16string& effectType, const std::string& name, LWOOBJID secondary, float priority, float scale, bool serialize) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity);
	bitStream.Write((uint16_t)GAME_MSG::GAME_MSG_PLAY_FX_EFFECT);

	bitStream.Write(effectID != -1);
	if (effectID != -1) bitStream.Write(effectID);

	bitStream.Write<uint32_t>(effectType.size());
	for (const auto& k : effectType) {
		bitStream.Write<uint16_t>(k);
	}

	bitStream.Write(scale != 1.0f);
	if (scale != 1.0f) bitStream.Write(scale);

	bitStream.Write<uint32_t>(name.size());
	for (const auto& k : name) {
		bitStream.Write(k);
	}

	bitStream.Write(priority != 1.0);
	if (priority != 1.0) bitStream.Write(priority);

	bitStream.Write(secondary != LWOOBJID_EMPTY);
	if (secondary != LWOOBJID_EMPTY) bitStream.Write(secondary);

	bitStream.Write(serialize);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendStopFXEffect(Entity* entity, bool killImmediate, std::string name) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_STOP_FX_EFFECT);

	bitStream.Write(killImmediate);
	bitStream.Write<uint32_t>(name.size());
	bitStream.Write(name.c_str(), name.size());

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendBroadcastTextToChatbox(Entity* entity, const SystemAddress& sysAddr, const std::u16string& attrs, const std::u16string& wsText) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG::GAME_MSG_BROADCAST_TEXT_TO_CHATBOX);

	LWONameValue attribs;
	attribs.name = attrs;
	attribs.length = attrs.size();

	bitStream.Write(static_cast<uint32_t>(attribs.length));
	for (uint32_t i = 0; i < attribs.length; ++i) {
		bitStream.Write(static_cast<uint16_t>(attribs.name[i]));
	}
	bitStream.Write(static_cast<uint16_t>(0x00)); // Null Terminator

	uint32_t wsTextLength = wsText.size();
	bitStream.Write(wsTextLength);
	for (uint32_t k = 0; k < wsTextLength; k++) {
		bitStream.Write(static_cast<uint16_t>(wsText[k]));
	}

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendSetCurrency(Entity* entity, int64_t currency, int lootType, const LWOOBJID& sourceID, const LOT& sourceLOT, int sourceTradeID, bool overrideCurrent, eLootSourceType sourceType) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_SET_CURRENCY));

	bitStream.Write(currency);

	bitStream.Write(lootType != LOOTTYPE_NONE);
	if (lootType != LOOTTYPE_NONE) bitStream.Write(lootType);

	bitStream.Write(NiPoint3::ZERO);

	bitStream.Write(sourceLOT != LOT_NULL);
	if (sourceLOT != LOT_NULL) bitStream.Write(sourceLOT);

	bitStream.Write(sourceID != LWOOBJID_EMPTY);
	if (sourceID != LWOOBJID_EMPTY) bitStream.Write(sourceID);

	bitStream.Write(sourceTradeID != LWOOBJID_EMPTY);
	if (sourceTradeID != LWOOBJID_EMPTY) bitStream.Write(sourceTradeID);

	bitStream.Write(sourceType != LOOTTYPE_NONE);
	if (sourceType != LOOTTYPE_NONE) bitStream.Write(sourceType);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendRebuildNotifyState(Entity* entity, int prevState, int state, const LWOOBJID& playerID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_REBUILD_NOTIFY_STATE);

	bitStream.Write(prevState);
	bitStream.Write(state);
	bitStream.Write(playerID);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendEnableRebuild(Entity* entity, bool enable, bool fail, bool success, int failReason, float duration, const LWOOBJID& playerID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_ENABLE_REBUILD);

	bitStream.Write(enable);
	bitStream.Write(fail);
	bitStream.Write(success);

	bitStream.Write(failReason != eFailReason::REASON_NOT_GIVEN);
	if (failReason != eFailReason::REASON_NOT_GIVEN) bitStream.Write(failReason);

	bitStream.Write(duration);
	bitStream.Write(playerID);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendTerminateInteraction(const LWOOBJID& objectID, eTerminateType type, const LWOOBJID& terminator) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG_TERMINATE_INTERACTION);

	bitStream.Write(terminator);
	bitStream.Write(type);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_DIE));
	bitStream.Write(bClientDeath);
	bitStream.Write(bSpawnLoot);
	bitStream.Write(deathType);
	bitStream.Write(directionRelative_AngleXZ);
	bitStream.Write(directionRelative_AngleY);
	bitStream.Write(directionRelative_Force);

	bitStream.Write(killType != eKillType::VIOLENT);
	if (killType != eKillType::VIOLENT) bitStream.Write(killType);

	bitStream.Write(killerID);
	bitStream.Write(lootOwnerID);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendDie(Entity* entity, const LWOOBJID& killerID, const LWOOBJID& lootOwnerID, bool bDieAccepted, eKillType killType, std::u16string deathType, float directionRelative_AngleY, float directionRelative_AngleXZ, float directionRelative_Force, bool bClientDeath, bool bSpawnLoot, float coinSpawnTime) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());

	bitStream.Write((uint16_t)GAME_MSG_DIE);

	bitStream.Write(bClientDeath);
	bitStream.Write(bSpawnLoot);

	//bitStream.Write(coinSpawnTime != -1.0f);
	//if (coinSpawnTime != -1.0f) bitStream.Write(coinSpawnTime);

	uint32_t deathTypeLength = deathType.size();
	bitStream.Write(deathTypeLength);
	for (uint32_t k = 0; k < deathTypeLength; k++) {
		bitStream.Write(static_cast<uint16_t>(deathType[k]));
	}

	bitStream.Write(directionRelative_AngleXZ);
	bitStream.Write(directionRelative_AngleY);
	bitStream.Write(directionRelative_Force);

	bitStream.Write(killType != VIOLENT);
	if (killType != VIOLENT) bitStream.Write(killType);

	bitStream.Write(killerID);

	bitStream.Write(lootOwnerID != LWOOBJID_EMPTY);
	if (lootOwnerID != LWOOBJID_EMPTY) {
		bitStream.Write(lootOwnerID);
	}

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendSetInventorySize(Entity* entity, int invType, int size) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_SET_INVENTORY_SIZE));
	bitStream.Write(invType);
	bitStream.Write(size);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendSetEmoteLockState(Entity* entity, bool bLock, int emoteID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_SET_EMOTE_LOCK_STATE));
	bitStream.Write(bLock);
	bitStream.Write(emoteID);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendSetJetPackMode(Entity* entity, bool use, bool bypassChecks, bool doHover, int effectID, float airspeed, float maxAirspeed, float verticalVelocity, int warningEffectID) {
	/* historical jamesster jetpack values
	if (bIsJamessterPhysics) {
		fAirspeed = 75;
		fMaxAirspeed = 75;
		fVertVel = 15;
	}
	*/

	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_SET_JET_PACK_MODE));

	bitStream.Write(bypassChecks);
	bitStream.Write(doHover);
	bitStream.Write(use);

	bitStream.Write(effectID != -1);
	if (effectID != -1) bitStream.Write(effectID);

	bitStream.Write(airspeed != 10);
	if (airspeed != 10) bitStream.Write(airspeed);

	bitStream.Write(maxAirspeed != 15);
	if (maxAirspeed != 15) bitStream.Write(maxAirspeed);

	bitStream.Write(verticalVelocity != 1);
	if (verticalVelocity != 1) bitStream.Write(verticalVelocity);

	bitStream.Write(warningEffectID != -1);
	if (warningEffectID != -1) bitStream.Write(warningEffectID);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendResurrect(Entity* entity) {
	DestroyableComponent* dest = static_cast<DestroyableComponent*>(entity->GetComponent(COMPONENT_TYPE_DESTROYABLE));

	if (dest != nullptr && entity->GetLOT() == 1) {
		auto* levelComponent = entity->GetComponent<LevelProgressionComponent>();
		if (levelComponent) {
			dest->SetHealth(levelComponent->GetLevel() >= 45 ? 8 : 4);
			dest->SetImagination(levelComponent->GetLevel() >= 45 ? 20 : 6);
		}
	}

	auto cont = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
	if (cont && entity->GetLOT() == 1) {
		cont->SetPosition(entity->GetRespawnPosition());
		cont->SetRotation(entity->GetRespawnRotation());
	}

	CBITSTREAM;
	CMSGHEADER;

	bool bRezImmediately = false;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_RESURRECT));
	bitStream.Write(bRezImmediately);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLAY2_DAMBIENT_SOUND);

	uint32_t audioGUIDSize = audioGUID.size();

	bitStream.Write(force);
	bitStream.Write(audioGUIDSize);

	for (uint32_t k = 0; k < audioGUIDSize; k++) {
		bitStream.Write(static_cast<char>(audioGUID[k]));
	}

	bitStream.Write(result);

	SystemAddress sysAddr = entity->GetSystemAddress();

	SEND_PACKET;
}

void GameMessages::SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLAY2_DAMBIENT_SOUND);

	uint32_t audioGUIDSize = audioGUID.size();

	bitStream.Write(audioGUIDSize);
	for (uint32_t k = 0; k < audioGUIDSize; k++) {
		bitStream.Write(static_cast<char>(audioGUID[k]));
	}
	bitStream.Write(result);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_SET_NETWORK_SCRIPT_VAR);

	// FIXME: this is a bad place to need to do a conversion because we have no clue whether data is utf8 or plain ascii
	// an this has performance implications
	const auto u16Data = GeneralUtils::ASCIIToUTF16(data);
	uint32_t dataSize = static_cast<uint32_t>(u16Data.size());

	bitStream.Write(dataSize);
	for (auto value : u16Data) {
		bitStream.Write(uint16_t(value));
	}
	if (dataSize > 0) bitStream.Write(uint16_t(0));

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos, int count) {
	if (GameConfig::GetValue<int32_t>("no_drops") == 1) {
		return;
	}

	bool bUsePosition = false;
	NiPoint3 finalPosition;
	LWOOBJID lootID = LWOOBJID_EMPTY;
	LWOOBJID owner = entity->GetObjectID();

	if (item != LOT_NULL && item != 0) {
		lootID = ObjectIDManager::Instance()->GenerateObjectID();

		Loot::Info info;
		info.id = lootID;
		info.count = count;
		info.lot = item;
		entity->AddLootItem(info);
	}

	if (item == LOT_NULL && currency != 0) {
		entity->RegisterCoinDrop(currency);
	}

	if (spawnPos != NiPoint3::ZERO) {
		bUsePosition = true;

		//Calculate where the loot will go:
		uint16_t degree = GeneralUtils::GenerateRandomNumber<uint16_t>(0, 360);

		double rad = degree * 3.14 / 180;
		double sin_v = sin(rad) * 4.2;
		double cos_v = cos(rad) * 4.2;

		finalPosition = NiPoint3(static_cast<float>(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast<float>(spawnPos.GetZ() + cos_v));
	}

	//Write data to packet & send:
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_DROP_CLIENT_LOOT));

	bitStream.Write(bUsePosition);

	bitStream.Write(finalPosition != NiPoint3::ZERO);
	if (finalPosition != NiPoint3::ZERO) bitStream.Write(finalPosition);

	bitStream.Write(currency);
	bitStream.Write(item);
	bitStream.Write(lootID);
	bitStream.Write(owner);
	bitStream.Write(sourceID);

	bitStream.Write(spawnPos != NiPoint3::ZERO);
	if (spawnPos != NiPoint3::ZERO) bitStream.Write(spawnPos);

	auto* team = TeamManager::Instance()->GetTeam(owner);

	// Currency and powerups should not sync
	if (team != nullptr && currency == 0) {
		CDObjectsTable* objectsTable = CDClientManager::Instance()->GetTable<CDObjectsTable>("Objects");

		const CDObjects& object = objectsTable->GetByID(item);

		if (object.type != "Powerup") {
			for (const auto memberId : team->members) {
				auto* member = EntityManager::Instance()->GetEntity(memberId);

				if (member == nullptr) continue;

				SystemAddress sysAddr = member->GetSystemAddress();
				SEND_PACKET;
			}

			return;
		}
	}

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendSetPlayerControlScheme(Entity* entity, eControlSceme controlScheme) {
	CBITSTREAM;
	CMSGHEADER;

	bool bDelayCamSwitchIfInCinematic = true;
	bool bSwitchCam = true;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(uint16_t(GAME_MSG_SET_PLAYER_CONTROL_SCHEME));

	bitStream.Write(bDelayCamSwitchIfInCinematic);
	bitStream.Write(bSwitchCam);

	bitStream.Write(controlScheme != SCHEME_A);
	if (controlScheme != SCHEME_A) bitStream.Write(controlScheme);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_PLAYER_REACHED_RESPAWN_CHECKPOINT);

	bitStream.Write(position.x);
	bitStream.Write(position.y);
	bitStream.Write(position.z);

	auto con = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(COMPONENT_TYPE_CONTROLLABLE_PHYSICS));
	if (con) {
		auto rot = con->GetRotation();
		bitStream.Write(rot.x);
		bitStream.Write(rot.y);
		bitStream.Write(rot.z);
		bitStream.Write(rot.w);
	}

	//bitStream.Write(position);
	//bitStream.Write(rotation);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendAddSkill(Entity* entity, TSkillID skillID, int slotID) {
	int AICombatWeight = 0;
	bool bFromSkillSet = false;
	int castType = 0;
	float fTimeSecs = -1.0f;
	int iTimesCanCast = -1;
	bool temporary = true;

	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write((uint16_t)GAME_MSG_ADD_SKILL);

	bitStream.Write(AICombatWeight != 0);
	if (AICombatWeight != 0) bitStream.Write(AICombatWeight);

	bitStream.Write(bFromSkillSet);

	bitStream.Write(castType != 0);
	if (castType != 0) bitStream.Write(castType);

	bitStream.Write(fTimeSecs != -1.0f);
	if (fTimeSecs != -1.0f) bitStream.Write(fTimeSecs);

	bitStream.Write(iTimesCanCast != -1);
	if (iTimesCanCast != -1) bitStream.Write(iTimesCanCast);

	bitStream.Write(skillID);

	bitStream.Write(slotID != -1);
	if (slotID != -1) bitStream.Write(slotID);

	bitStream.Write(temporary);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendRemoveSkill(Entity* entity, TSkillID skillID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write<uint16_t>(GAME_MSG_REMOVE_SKILL);
	bitStream.Write(false);
	bitStream.Write(skillID);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendFinishArrangingWithItem(Entity* entity, const LWOOBJID& buildArea) {
	CBITSTREAM;
	CMSGHEADER;

	bool bFirstTime = true;
	const LWOOBJID& buildAreaID = buildArea;
	int newSourceBAG = 0;
	const LWOOBJID& newSourceID = LWOOBJID_EMPTY;
	LOT newSourceLOT = LOT_NULL;
	int newSourceTYPE = 0;
	const LWOOBJID& newTargetID = LWOOBJID_EMPTY;
	LOT newTargetLOT = LOT_NULL;
	int newTargetTYPE = 0;
	NiPoint3 newTargetPOS = NiPoint3();
	int oldItemBAG = 0;
	const LWOOBJID& oldItemID = LWOOBJID_EMPTY;
	LOT oldItemLOT = LOT_NULL;
	int oldItemTYPE = 0;


	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_FINISH_ARRANGING_WITH_ITEM);

	bitStream.Write(buildAreaID != LWOOBJID_EMPTY);
	if (buildAreaID != LWOOBJID_EMPTY) bitStream.Write(buildAreaID);
	bitStream.Write(newSourceBAG);
	bitStream.Write(newSourceID);
	bitStream.Write(newSourceLOT);
	bitStream.Write(newSourceTYPE);
	bitStream.Write(newTargetID);
	bitStream.Write(newTargetLOT);
	bitStream.Write(newTargetTYPE);
	bitStream.Write(newTargetPOS);
	bitStream.Write(oldItemBAG);
	bitStream.Write(oldItemID);
	bitStream.Write(oldItemLOT);
	bitStream.Write(oldItemTYPE);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendModularBuildEnd(Entity* entity) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_MODULAR_BUILD_END);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_OPEN_WINDOW);

	SEND_PACKET;
}

void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) {
	CBITSTREAM;
	CMSGHEADER;

	VendorComponent* vendor = static_cast<VendorComponent*>(entity->GetComponent(COMPONENT_TYPE_VENDOR));
	if (!vendor) return;

	std::map<LOT, int> vendorItems = vendor->GetInventory();

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE);

	bitStream.Write(bUpdateOnly);
	bitStream.Write(static_cast<uint32_t>(vendorItems.size()));

	for (std::pair<LOT, int> item : vendorItems) {
		bitStream.Write(static_cast<int>(item.first));
		bitStream.Write(static_cast<int>(item.second));
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
		SEND_PACKET;
}

void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	int iResult = 0x02; // success, seems to be the only relevant one

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_TRANSACTION_RESULT);
	bitStream.Write(iResult);

	SEND_PACKET;
}

void GameMessages::SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID objectID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining) {
	CBITSTREAM;
	CMSGHEADER;
	// this is used for a lot more than just inventory trashing (trades, vendors, etc.) but for now since it's just used for that, that's all im going to implement
	bool bConfirmed = true;
	bool bDeleteItem = true;
	bool bOutSuccess = false;
	int eInvType = inventoryType;
	int eLootTypeSource = LOOTTYPE_NONE;
	bool forceDeletion = true;
	LWOOBJID iLootTypeSource = LWOOBJID_EMPTY;
	LWOOBJID iObjID = objectID;
	LOT iObjTemplate = templateID;
	LWOOBJID iRequestingObjID = LWOOBJID_EMPTY;
	uint32_t iStackCount = stackCount;
	uint32_t iStackRemaining = stackRemaining;
	LWOOBJID iSubkey = LWOOBJID_EMPTY;
	LWOOBJID iTradeID = LWOOBJID_EMPTY;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_REMOVE_ITEM_FROM_INVENTORY);
	bitStream.Write(bConfirmed);
	bitStream.Write(bDeleteItem);
	bitStream.Write(bOutSuccess);
	bitStream.Write1();
	bitStream.Write(eInvType);
	bitStream.Write1();
	bitStream.Write(eLootTypeSource);
	bitStream.Write(static_cast<uint32_t>(0)); //extra info
	//bitStream.Write(static_cast<uint16_t>(0)); //extra info
	bitStream.Write(forceDeletion);
	bitStream.Write0();
	bitStream.Write1();
	bitStream.Write(iObjID);
	bitStream.Write1();
	bitStream.Write(iObjTemplate);
	bitStream.Write0();
	bitStream.Write1();
	bitStream.Write(iStackCount);
	bitStream.Write1();
	bitStream.Write(iStackRemaining);
	bitStream.Write0();
	bitStream.Write0();

	SEND_PACKET;
}

void GameMessages::SendConsumeClientItem(Entity* entity, bool bSuccess, LWOOBJID item) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG_CONSUME_CLIENT_ITEM);
	bitStream.Write(bSuccess);
	bitStream.Write(item);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendUseItemResult(Entity* entity, LOT templateID, bool useItemResult) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG_USE_ITEM_RESULT);
	bitStream.Write(templateID);
	bitStream.Write(useItemResult);

	SystemAddress sysAddr = entity->GetSystemAddress();
	SEND_PACKET;
}

void GameMessages::SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_USE_ITEM_REQUIREMENTS_RESPONSE);

	bitStream.Write(itemResponse);

	SEND_PACKET;
}

void GameMessages::SendMoveInventoryBatch(Entity* entity, uint32_t stackCount, int srcInv, int dstInv, const LWOOBJID& iObjID) {
	CBITSTREAM;
	CMSGHEADER;

	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	Item* itemStack = inv->FindItemById(iObjID);
	if (!itemStack) return;

	bool bAllowPartial = false;
	bool bOutSuccess = false;
	uint32_t count = stackCount;
	int dstBag = dstInv;
	LOT moveLOT = itemStack->GetLot();
	//LWOOBJID moveSubkey = LWOOBJID_EMPTY;
	bool showFlyingLoot = false;
	int srcBag = srcInv;
	LWOOBJID startObjectID = iObjID;

	bitStream.Write(bAllowPartial);
	bitStream.Write(bOutSuccess);
	if (count == 1) {
		bitStream.Write0();
	} else {
		bitStream.Write1();
		bitStream.Write(count);
	}
	if (dstBag == 0) {
		bitStream.Write0();
	} else {
		bitStream.Write1();
		bitStream.Write(dstBag);
	}
	bitStream.Write0();
	bitStream.Write(showFlyingLoot);
	if (srcBag == 0) {
		bitStream.Write0();
	} else {
		bitStream.Write1();
		bitStream.Write(srcBag);
	}
	bitStream.Write1();
	bitStream.Write(startObjectID);

	auto sysAddr = entity->GetSystemAddress();

	SEND_PACKET;
}

void GameMessages::SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_MATCH_RESPONSE);
	bitStream.Write(response);

	SEND_PACKET;
}

void GameMessages::SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, int type) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_MATCH_UPDATE);
	bitStream.Write(uint32_t(data.size()));
	for (char character : data) {
		bitStream.Write(uint16_t(character));
	}
	if (data.size() > 0) bitStream.Write(uint16_t(0));
	bitStream.Write(type);

	SEND_PACKET;
}

void GameMessages::SendRequestActivitySummaryLeaderboardData(const LWOOBJID& objectID, const LWOOBJID& targetID,
	const SystemAddress& sysAddr, const int32_t& gameID,
	const int32_t& queryType, const int32_t& resultsEnd,
	const int32_t& resultsStart, bool weekly) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_REQUEST_ACTIVITY_SUMMARY_LEADERBOARD_DATA);

	bitStream.Write(gameID != 0);
	if (gameID != 0) {
		bitStream.Write<int32_t>(gameID);
	}

	bitStream.Write(queryType != 1);
	if (queryType != 1) {
		bitStream.Write<int32_t>(queryType);
	}

	bitStream.Write(resultsEnd != 10);
	if (resultsEnd != 10) {
		bitStream.Write<int32_t>(resultsEnd);
	}

	bitStream.Write(resultsStart != 0);
	if (resultsStart != 0) {
		bitStream.Write<int32_t>(resultsStart);
	}

	bitStream.Write<LWOOBJID>(targetID);
	bitStream.Write(weekly);

	SEND_PACKET;
}

void GameMessages::SendActivityPause(LWOOBJID objectId, bool pause, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ACTIVITY_PAUSE);
	bitStream.Write(pause);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_START_ACTIVITY_TIME);
	bitStream.Write<float_t>(startTime);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_REQUEST_ACTIVITY_ENTER);
	bitStream.Write<bool>(bStart);
	bitStream.Write<LWOOBJID>(userID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::NotifyLevelRewards(LWOOBJID objectID, const SystemAddress& sysAddr, int level, bool sending_rewards) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write((uint16_t)GAME_MSG::GAME_MSG_NOTIFY_LEVEL_REWARDS);

	bitStream.Write(level);
	bitStream.Write(sending_rewards);

	SEND_PACKET;
}

void GameMessages::SendSetShootingGalleryParams(LWOOBJID objectId, const SystemAddress& sysAddr,
	float cameraFOV,
	float cooldown,
	float minDistance,
	NiPoint3 muzzlePosOffset,
	NiPoint3 playerPosOffset,
	float projectileVelocity,
	float timeLimit,
	bool bUseLeaderboards) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_SHOOTING_GALLERY_PARAMS);
	/*
	bitStream.Write<float>(cameraFOV);
	bitStream.Write<float>(cooldown);
	bitStream.Write<float>(minDistance);
	bitStream.Write<NiPoint3>(muzzlePosOffset);
	bitStream.Write<NiPoint3>(playerPosOffset);
	bitStream.Write<float>(projectileVelocity);
	bitStream.Write<float>(timeLimit);
	bitStream.Write<bool>(bUseLeaderboards);
	*/
	// No clue about the order here
	bitStream.Write<NiPoint3>(playerPosOffset);
	bitStream.Write<float>(projectileVelocity);
	bitStream.Write<float>(cooldown);
	bitStream.Write<NiPoint3>(muzzlePosOffset);
	bitStream.Write<float>(minDistance);
	bitStream.Write<float>(cameraFOV);
	bitStream.Write<bool>(bUseLeaderboards);
	bitStream.Write<float>(timeLimit);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendNotifyClientShootingGalleryScore(LWOOBJID objectId, const SystemAddress& sysAddr,
	float addTime,
	int32_t score,
	LWOOBJID target,
	NiPoint3 targetPos) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE);
	bitStream.Write<float>(addTime);
	bitStream.Write<int32_t>(score);
	bitStream.Write<LWOOBJID>(target);
	bitStream.Write<NiPoint3>(targetPos);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::HandleUpdateShootingGalleryRotation(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	float angle = 0.0f;
	NiPoint3 facing = NiPoint3::ZERO;
	NiPoint3 muzzlePos = NiPoint3::ZERO;
	inStream->Read(angle);
	inStream->Read(facing);
	inStream->Read(muzzlePos);
}


void GameMessages::HandleActivitySummaryLeaderboardData(RakNet::BitStream* instream, Entity* entity,
	const SystemAddress& sysAddr) {
	Game::logger->Log("AGS", "We got mail!");
}

void GameMessages::SendActivitySummaryLeaderboardData(const LWOOBJID& objectID, const Leaderboard* leaderboard, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_SEND_ACTIVITY_SUMMARY_LEADERBOARD_DATA);

	bitStream.Write(leaderboard->GetGameID());
	bitStream.Write(leaderboard->GetInfoType());

	// Leaderboard is written back as LDF string
	const auto leaderboardString = leaderboard->ToString();
	bitStream.Write<uint32_t>(leaderboardString.size());
	for (const auto c : leaderboardString) {
		bitStream.Write<uint16_t>(c);
	}
	if (!leaderboardString.empty()) bitStream.Write(uint16_t(0));

	bitStream.Write0();
	bitStream.Write0();

	SEND_PACKET;
}

void GameMessages::HandleRequestActivitySummaryLeaderboardData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	int32_t gameID = 0;
	if (inStream->ReadBit()) inStream->Read(gameID);

	int32_t queryType = 1;
	if (inStream->ReadBit()) inStream->Read(queryType);

	int32_t resultsEnd = 10;
	if (inStream->ReadBit()) inStream->Read(resultsEnd);

	int32_t resultsStart = 0;
	if (inStream->ReadBit()) inStream->Read(resultsStart);

	LWOOBJID target{};
	inStream->Read(target);

	bool weekly = inStream->ReadBit();

	const auto* leaderboard = LeaderboardManager::GetLeaderboard(gameID, (InfoType)queryType, weekly, entity->GetObjectID());
	SendActivitySummaryLeaderboardData(entity->GetObjectID(), leaderboard, sysAddr);
	delete leaderboard;
}

void GameMessages::HandleActivityStateChangeRequest(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID objectID;
	inStream->Read<LWOOBJID>(objectID);

	int32_t value1;
	inStream->Read<int32_t>(value1);

	int32_t value2;
	inStream->Read<int32_t>(value2);

	uint32_t stringValueLength;
	inStream->Read<uint32_t>(stringValueLength);

	std::u16string stringValue;
	for (uint32_t i = 0; i < stringValueLength; ++i) {
		uint16_t character;
		inStream->Read(character);
		stringValue.push_back(character);
	}

	auto* assosiate = EntityManager::Instance()->GetEntity(objectID);

	Game::logger->Log("Activity State Change", "%s [%i, %i] from %i to %i", GeneralUtils::UTF16ToWTF8(stringValue).c_str(), value1, value2, entity->GetLOT(), assosiate != nullptr ? assosiate->GetLOT() : 0);

	std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SHOOTING_GALLERY);
	for (Entity* scriptEntity : scriptedActs) {
		scriptEntity->OnActivityStateChangeRequest(objectID, value1, value2, stringValue);
	}

	entity->OnActivityStateChangeRequest(objectID, value1, value2, stringValue);
}

void GameMessages::SendStartCelebrationEffect(Entity* entity, const SystemAddress& sysAddr, int celebrationID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_START_CELEBRATION_EFFECT);

	bitStream.Write<uint32_t>(0); //animation
	bitStream.Write0(); //No custom bg obj
	bitStream.Write0(); //custom path
	bitStream.Write0(); //leadin
	bitStream.Write0(); //leadout
	bitStream.Write1();
	bitStream.Write(celebrationID);
	bitStream.Write<float>(0.0f); //duration
	bitStream.Write<uint32_t>(0); //icon
	bitStream.Write<uint32_t>(0); //text
	bitStream.Write<uint32_t>(0); //mixer
	bitStream.Write<uint32_t>(0); //cue
	bitStream.Write<uint32_t>(0); //pathnodename
	bitStream.Write<uint32_t>(0); //soundguid
	bitStream.Write<uint32_t>(0); //subtext

	SEND_PACKET;

	//PacketUtils::SavePacket("StartCelebrationEffect.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());
}


void GameMessages::SendSetRailMovement(const LWOOBJID& objectID, bool pathGoForward, std::u16string pathName,
	uint32_t pathStart, const SystemAddress& sysAddr, int32_t railActivatorComponentID,
	LWOOBJID railActivatorObjectID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_RAIL_MOVEMENT);

	bitStream.Write(pathGoForward);

	bitStream.Write(uint32_t(pathName.size()));
	for (auto character : pathName) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write<uint32_t>(pathStart);

	const auto componentIDIsDefault = railActivatorComponentID == -1;
	bitStream.Write(!componentIDIsDefault);
	if (!componentIDIsDefault)
		bitStream.Write<int32_t>(railActivatorComponentID);

	const auto activatorObjectIDIsDefault = railActivatorObjectID == LWOOBJID_EMPTY;
	bitStream.Write(!activatorObjectIDIsDefault);
	if (!activatorObjectIDIsDefault)
		bitStream.Write<LWOOBJID>(railActivatorObjectID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendStartRailMovement(const LWOOBJID& objectID, std::u16string pathName, std::u16string startSound,
	std::u16string loopSound, std::u16string stopSound, const SystemAddress& sysAddr,
	uint32_t pathStart, bool goForward, bool damageImmune, bool noAggro, bool notifyActor,
	bool showNameBillboard, bool cameraLocked, bool collisionEnabled, bool useDB,
	int32_t railComponentID, LWOOBJID railActivatorObjectID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_START_RAIL_MOVEMENT);

	bitStream.Write(damageImmune);
	bitStream.Write(noAggro);
	bitStream.Write(notifyActor);
	bitStream.Write(showNameBillboard);
	bitStream.Write(cameraLocked);
	bitStream.Write(collisionEnabled);

	bitStream.Write(uint32_t(loopSound.size()));
	for (auto character : loopSound) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write(goForward);

	bitStream.Write(uint32_t(pathName.size()));
	for (auto character : pathName) {
		bitStream.Write<uint16_t>(character);
	}

	const auto pathStartIsDefault = pathStart == 0;
	bitStream.Write(!pathStartIsDefault);
	if (!pathStartIsDefault) {
		bitStream.Write<uint32_t>(pathStart);
	}

	const auto railComponentIDIsDefault = railComponentID == -1;
	bitStream.Write(!railComponentIDIsDefault);
	if (!railComponentIDIsDefault) {
		bitStream.Write<int32_t>(railComponentID);
	}

	const auto railObjectIDIsDefault = railActivatorObjectID == LWOOBJID_EMPTY;
	bitStream.Write(!railObjectIDIsDefault);
	if (!railObjectIDIsDefault) {
		bitStream.Write<LWOOBJID>(railActivatorObjectID);
	}

	bitStream.Write(uint32_t(startSound.size()));
	for (auto character : startSound) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write(uint32_t(stopSound.size()));
	for (auto character : stopSound) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write(useDB);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyClientObject(const LWOOBJID& objectID, std::u16string name, int param1, int param2, const LWOOBJID& paramObj, std::string paramStr, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_CLIENT_OBJECT);

	bitStream.Write(uint32_t(name.size()));
	for (auto character : name) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write(param1);

	bitStream.Write(param2);

	bitStream.Write(paramObj);

	bitStream.Write(uint32_t(paramStr.size()));
	for (auto character : paramStr) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyClientZoneObject(const LWOOBJID& objectID, const std::u16string& name, int param1,
	int param2, const LWOOBJID& paramObj, const std::string& paramStr,
	const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_CLIENT_ZONE_OBJECT);

	bitStream.Write(uint32_t(name.size()));
	for (const auto& character : name) {
		bitStream.Write<uint16_t>(character);
	}

	bitStream.Write(param1);
	bitStream.Write(param2);
	bitStream.Write(paramObj);

	bitStream.Write(uint32_t(paramStr.size()));
	for (const auto& character : paramStr) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyClientFailedPrecondition(LWOOBJID objectId, const SystemAddress& sysAddr,
	const std::u16string& failedReason, int preconditionID) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_CLIENT_FAILED_PRECONDITION);

	bitStream.Write(uint32_t(failedReason.size()));
	for (uint16_t character : failedReason) {
		bitStream.Write(uint16_t(character));
	}

	bitStream.Write(preconditionID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendToggleGMInvis(LWOOBJID objectId, bool enabled, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_TOGGLE_GM_INVIS);
	bitStream.Write(enabled); // does not matter?

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendSetName(LWOOBJID objectID, std::u16string name, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write<uint16_t>(GAME_MSG::GAME_MSG_SET_NAME);

	bitStream.Write<uint32_t>(name.size());

	for (size_t i = 0; i < name.size(); ++i)
		bitStream.Write(name[i]);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendBBBSaveResponse(const LWOOBJID& objectId, const LWOOBJID& localID, unsigned char* buffer, uint32_t bufferSize, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write<uint16_t>(GAME_MSG::GAME_MSG_BBB_SAVE_RESPONSE);

	bitStream.Write(localID);

	//Write a fake sd0 header:
	bitStream.Write<unsigned char>(0x73); //s
	bitStream.Write<unsigned char>(0x64); //d
	bitStream.Write<unsigned char>(0x30); //0
	bitStream.Write<unsigned char>(0x01); //1
	bitStream.Write<unsigned char>(0xFF); //end magic

	bitStream.Write(bufferSize);

	for (size_t i = 0; i < bufferSize; ++i)
		bitStream.Write(buffer[i]);

	SEND_PACKET;
	PacketUtils::SavePacket("GAME_MSG_BBB_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());
}

// Property

void GameMessages::SendOpenPropertyVendor(const LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_OPEN_PROPERTY_VENDOR);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendOpenPropertyManagment(const LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(PropertyManagementComponent::Instance()->GetParent()->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_OPEN_PROPERTY_MANAGEMENT);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendDownloadPropertyData(const LWOOBJID objectId, const PropertyDataMessage& data, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_DOWNLOAD_PROPERTY_DATA);

	data.Serialize(bitStream);

	Game::logger->Log("SendDownloadPropertyData", "(%llu) sending property data (%d)", objectId, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPropertyRentalResponse(const LWOOBJID objectId, const LWOCLONEID cloneId, const uint32_t code, const LWOOBJID propertyId, const int64_t rentDue, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PROPERTY_RENTAL_RESPONSE);

	bitStream.Write(cloneId);
	bitStream.Write(code);
	bitStream.Write(propertyId);
	bitStream.Write(rentDue);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendLockNodeRotation(Entity* entity, std::string nodeName) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_LOCK_NODE_ROTATION);

	bitStream.Write(uint32_t(nodeName.size()));
	for (char character : nodeName) {
		bitStream.Write(character);
	}

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendSetBuildModeConfirmed(LWOOBJID objectId, const SystemAddress& sysAddr, bool start, bool warnVisitors, bool modePaused, int32_t modeValue, LWOOBJID playerId, NiPoint3 startPos) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_BUILD_MODE_CONFIRMED);

	bitStream.Write(start);
	bitStream.Write(warnVisitors);
	bitStream.Write(modePaused);
	bitStream.Write1();
	bitStream.Write(modeValue);
	bitStream.Write(playerId);
	bitStream.Write1();
	bitStream.Write(startPos);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendGetModelsOnProperty(LWOOBJID objectId, std::map<LWOOBJID, LWOOBJID> models, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_GET_MODELS_ON_PROPERTY);

	bitStream.Write(static_cast<uint32_t>(models.size()));

	for (const auto& pair : models) {
		bitStream.Write(pair.first);
		bitStream.Write(pair.second);
	}

	Game::logger->Log("SendGetModelsOnProperty", "Sending property models to (%llu) (%d)", objectId, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendZonePropertyModelEquipped(LWOOBJID objectId, LWOOBJID playerId, LWOOBJID propertyId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ZONE_PROPERTY_MODEL_EQUIPPED);

	bitStream.Write(playerId);
	bitStream.Write(propertyId);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPlaceModelResponse(LWOOBJID objectId, const SystemAddress& sysAddr, NiPoint3 position,
	LWOOBJID plaque, int32_t response, NiQuaternion rotation) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PLACE_MODEL_RESPONSE);

	bitStream.Write(position != NiPoint3::ZERO);
	if (position != NiPoint3::ZERO) {
		bitStream.Write(position);
	}

	bitStream.Write(plaque != LWOOBJID_EMPTY);
	if (plaque != LWOOBJID_EMPTY) {
		bitStream.Write(plaque);
	}

	bitStream.Write(response != 0);
	if (response != 0) {
		bitStream.Write(response);
	}

	bitStream.Write(rotation != NiQuaternion::IDENTITY);
	if (rotation != NiQuaternion::IDENTITY) {
		bitStream.Write(response);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;

}

void GameMessages::SendUGCEquipPreCreateBasedOnEditMode(LWOOBJID objectId, const SystemAddress& sysAddr, int modelCount, LWOOBJID model) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_HANDLE_UGC_EQUIP_PRE_CREATE_BASED_ON_EDIT_MODE);

	bitStream.Write(modelCount);
	bitStream.Write(model);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendUGCEquipPostDeleteBasedOnEditMode(LWOOBJID objectId, const SystemAddress& sysAddr, LWOOBJID inventoryItem, int itemTotal) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_HANDLE_UGC_EQUIP_POST_DELETE_BASED_ON_EDIT_MODE);

	bitStream.Write(inventoryItem);

	bitStream.Write(itemTotal != 0);
	if (itemTotal != 0) {
		bitStream.Write(itemTotal);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::HandleSetPropertyAccess(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint8_t accessType{};
	int32_t renew{};

	bool accessTypeIsDefault{};
	inStream->Read(accessTypeIsDefault);
	if (accessTypeIsDefault != 0) inStream->Read(accessType);

	bool renewIsDefault{};
	inStream->Read(renewIsDefault);
	if (renewIsDefault != 0) inStream->Read(renew);

	Game::logger->Log("GameMessages", "Set privacy option to: %i", accessType);

	if (PropertyManagementComponent::Instance() == nullptr) return;

	PropertyManagementComponent::Instance()->SetPrivacyOption(static_cast<PropertyPrivacyOption>(accessType));
}

void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool isProperty{};
	LWOOBJID objectId{};
	LWOOBJID playerId{};
	LWOOBJID worldId{};
	uint32_t nameLength{};
	std::u16string name{};
	uint32_t descriptionLength{};
	std::u16string description{};

	inStream->Read(isProperty);
	inStream->Read(objectId);
	inStream->Read(playerId);
	inStream->Read(worldId);

	inStream->Read(descriptionLength);
	for (uint32_t i = 0; i < descriptionLength; ++i) {
		uint16_t character;
		inStream->Read(character);
		description.push_back(character);
	}

	inStream->Read(nameLength);
	for (uint32_t i = 0; i < nameLength; ++i) {
		uint16_t character;
		inStream->Read(character);
		name.push_back(character);
	}

	PropertyManagementComponent::Instance()->UpdatePropertyDetails(GeneralUtils::UTF16ToWTF8(name), GeneralUtils::UTF16ToWTF8(description));
}

void GameMessages::HandleQueryPropertyData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	Game::logger->Log("HandleQueryPropertyData", "Entity (%i) requesting data", entity->GetLOT());

	/*
	auto entites = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_PROPERTY_VENDOR);

	entity = entites[0];
	*/

	auto* propertyVendorComponent = static_cast<PropertyVendorComponent*>(entity->GetComponent(COMPONENT_TYPE_PROPERTY_VENDOR));

	if (propertyVendorComponent != nullptr) {
		propertyVendorComponent->OnQueryPropertyData(entity, sysAddr);
	}

	/*
	entites = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_PROPERTY_MANAGEMENT);

	entity = entites[0];
	*/

	auto* propertyManagerComponent = static_cast<PropertyManagementComponent*>(entity->GetComponent(COMPONENT_TYPE_PROPERTY_MANAGEMENT));

	if (propertyManagerComponent != nullptr) {
		propertyManagerComponent->OnQueryPropertyData(entity, sysAddr);
	}
}

void GameMessages::HandleSetBuildMode(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool start{};
	int32_t distanceType = -1;
	bool modePaused{};
	int modeValue = 1;
	LWOOBJID playerId{};
	NiPoint3 startPosition = NiPoint3::ZERO;

	inStream->Read(start);

	if (inStream->ReadBit())
		inStream->Read(distanceType);

	inStream->Read(modePaused);

	if (inStream->ReadBit())
		inStream->Read(modeValue);

	inStream->Read(playerId);

	if (inStream->ReadBit())
		inStream->Read(startPosition);

	auto* player = EntityManager::Instance()->GetEntity(playerId);

	if (startPosition == NiPoint3::ZERO) {
		startPosition = player->GetPosition();
	}

	player->GetCharacter()->SetBuildMode(start);

	Game::logger->Log("GameMessages", "Sending build mode confirm (%i): (%d) (%i) (%d) (%i) (%llu)", entity->GetLOT(), start, distanceType, modePaused, modeValue, playerId);

	SendSetBuildModeConfirmed(entity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS, start, false, modePaused, modeValue, playerId, startPosition);
}

void GameMessages::HandleStartBuildingWithItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	if (!entity->HasComponent(COMPONENT_TYPE_PROPERTY_MANAGEMENT)) {
		return;
	}

	bool firstTime{};
	bool success{};
	int32_t sourceBag{};
	LWOOBJID sourceId{};
	LOT sourceLot{};
	int32_t sourceType{};
	LWOOBJID targetId{};
	LOT targetLot{};
	NiPoint3 targetPosition{};
	int32_t targetType{};

	inStream->Read(firstTime);
	inStream->Read(success);
	inStream->Read(sourceBag);
	inStream->Read(sourceId);
	inStream->Read(sourceLot);
	inStream->Read(sourceType);
	inStream->Read(targetId);
	inStream->Read(targetLot);
	inStream->Read(targetPosition);
	inStream->Read(targetType);

	if (sourceType == 1) {
		sourceType = 4;
	}

	Game::logger->Log("GameMessages", "Handling start building with item (%i): (%d) (%d) (%i) (%llu) (%i) (%i) (%llu) (%i) (%i)", entity->GetLOT(), firstTime, success, sourceBag, sourceId, sourceLot, sourceType, targetId, targetLot, targetType);

	auto* user = UserManager::Instance()->GetUser(sysAddr);

	auto* player = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());

	SendStartArrangingWithItem(
		player,
		sysAddr,
		firstTime,
		entity->GetObjectID(),
		player->GetPosition(),
		sourceBag,
		sourceId,
		sourceLot,
		sourceType,
		targetId,
		targetLot,
		targetPosition,
		targetType
	);
}

void GameMessages::HandlePropertyEditorBegin(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	PropertyManagementComponent::Instance()->OnStartBuilding();

	dZoneManager::Instance()->GetZoneControlObject()->OnZonePropertyEditBegin();
}

void GameMessages::HandlePropertyEditorEnd(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	PropertyManagementComponent::Instance()->OnFinishBuilding();

	dZoneManager::Instance()->GetZoneControlObject()->OnZonePropertyEditEnd();
}

void GameMessages::HandlePropertyContentsFromClient(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	User* user = UserManager::Instance()->GetUser(sysAddr);

	Entity* player = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());

	SendGetModelsOnProperty(player->GetObjectID(), PropertyManagementComponent::Instance()->GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
}

void GameMessages::HandlePropertyModelEquipped(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	dZoneManager::Instance()->GetZoneControlObject()->OnZonePropertyModelEquipped();
}

void GameMessages::HandlePlacePropertyModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID model;

	inStream->Read(model);

	PropertyManagementComponent::Instance()->UpdateModelPosition(model, NiPoint3::ZERO, NiQuaternion::IDENTITY);
}

void GameMessages::HandleUpdatePropertyModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID model;
	NiPoint3 position;
	NiQuaternion rotation = NiQuaternion::IDENTITY;

	inStream->Read(model);
	inStream->Read(position);

	if (inStream->ReadBit()) {
		inStream->Read(rotation);
	}

	PropertyManagementComponent::Instance()->UpdateModelPosition(model, position, rotation);
}

void GameMessages::HandleDeletePropertyModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID model = LWOOBJID_EMPTY;
	int deleteReason = 0;

	if (inStream->ReadBit()) {
		inStream->Read(model);
	}

	if (inStream->ReadBit()) {
		inStream->Read(deleteReason);

	}

	PropertyManagementComponent::Instance()->DeleteModel(model, deleteReason);
}

void GameMessages::HandleBBBLoadItemRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID itemID = LWOOBJID_EMPTY;
	inStream->Read(itemID);

	Game::logger->Log("BBB", "Load item request for: %lld", itemID);

	CBITSTREAM;
	PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_LOAD_RESPONSE_ITEMID);
	bitStream.Write(static_cast<uint8_t>(1));
	bitStream.Write<LWOOBJID>(itemID);
	bitStream.Write<LWOOBJID>(itemID);
	SEND_PACKET;
}

void GameMessages::SendSmash(Entity* entity, float force, float ghostOpacity, LWOOBJID killerID, bool ignoreObjectVisibility) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_SMASH);

	bitStream.Write(ignoreObjectVisibility);
	bitStream.Write(force);
	bitStream.Write(ghostOpacity);
	bitStream.Write(killerID);

	SEND_PACKET_BROADCAST;
}

void GameMessages::SendUnSmash(Entity* entity, LWOOBJID builderID, float duration) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_UNSMASH);

	bitStream.Write(builderID != LWOOBJID_EMPTY);
	if (builderID != LWOOBJID_EMPTY) bitStream.Write(builderID);

	bitStream.Write(duration != 3.0f);
	if (duration != 3.0f) bitStream.Write(duration);

	SEND_PACKET_BROADCAST;
}

void GameMessages::HandleControlBehaviors(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	// TODO
	Game::logger->Log("GameMessages", "Recieved Control Behavior GameMessage, but property behaviors are unimplemented.");
}

void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	/*
								  ___            ___
		  /\  /\___ _ __ ___     / __\ ___      /   \_ __ __ _  __ _  ___  _ __  ___
		 / /_/ / _ \ '__/ _ \   /__\/// _ \    / /\ / '__/ _` |/ _` |/ _ \| '_ \/ __|
		/ __  /  __/ | |  __/  / \/  \  __/   / /_//| | | (_| | (_| | (_) | | | \__ \
		\/ /_/ \___|_|  \___|  \_____/\___|  /___,' |_|  \__,_|\__, |\___/|_| |_|___/
															   |___/
		   ___                               _
		  / __\ _____      ____ _ _ __ ___  / \
		 /__\/// _ \ \ /\ / / _` | '__/ _ \/  /
		/ \/  \  __/\ V  V / (_| | | |  __/\_/
		\_____/\___| \_/\_/ \__,_|_|  \___\/

						<>=======()
							   (/\___   /|\\          ()==========<>_
									 \_/ | \\        //|\   ______/ \)
									   \_|  \\      // | \_/
										 \|\/|\_   //  /\/
										  (oo)\ \_//  /
										 //_/\_\/ /  |
										@@/  |=\  \  |
											 \_=\_ \ |
											   \==\ \|\_ snd
											__(\===\(  )\
										   (((~) __(_/   |
												(((~) \  /
												______/ /
												'------'
	*/

	//First, we have Wincent's clean methods of reading in the data received from the client.
	LWOOBJID localId;
	uint32_t timeTaken;

	inStream->Read(localId);

	uint32_t ld0Size;
	inStream->Read(ld0Size);
	for (auto i = 0; i < 5; ++i) {
		uint8_t c;
		inStream->Read(c);
	}

	uint32_t lxfmlSize;
	inStream->Read(lxfmlSize);
	uint8_t* inData = static_cast<uint8_t*>(std::malloc(lxfmlSize));

	if (inData == nullptr) {
		return;
	}

	for (uint32_t i = 0; i < lxfmlSize; ++i) {
		uint8_t c;
		inStream->Read(c);
		inData[i] = c;
	}

	inStream->Read(timeTaken);

	/*
		Disabled this, as it's kinda silly to do this roundabout way of storing plaintext lxfml, then recompressing
		it to send it back to the client.

		On DLU we had agreed that bricks wouldn't be taken anyway, but if your server decides otherwise, feel free to
		comment this back out and add the needed code to get the bricks used from lxfml and take them from the inventory.

		Note, in the live client it'll still display the bricks going out as they're being used, but on relog/world change,
		they reappear as we didn't take them.
	*/

	////Decompress the SD0 from the client so we can process the lxfml properly
	//uint8_t* outData = new uint8_t[327680];
	//int32_t error;
	//int32_t size = ZCompression::Decompress(inData, lxfmlSize, outData, 327680, error);

	//if (size == -1) {
	//	Game::logger->Log("GameMessages", "Failed to decompress LXFML: (%i)", error);
	//	return;
	//}
	//
	//std::string lxfml(reinterpret_cast<char*>(outData), size); //std::string version of the decompressed data!

	//Now, the cave of dragons:

	//We runs this in async because the http library here is blocking, meaning it'll halt the thread.
	//But we don't want the server to go unresponsive, because then the client would disconnect.
	std::async(std::launch::async, [&]() {

		//We need to get a new ID for our model first:
		ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t newID) {
			LWOOBJID newIDL = newID;
			newIDL = GeneralUtils::SetBit(newIDL, OBJECT_BIT_CHARACTER);
			newIDL = GeneralUtils::SetBit(newIDL, OBJECT_BIT_PERSISTENT);

			ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t blueprintIDSmall) {
				blueprintIDSmall = ObjectIDManager::Instance()->GenerateRandomObjectID();
				LWOOBJID blueprintID = blueprintIDSmall;
				blueprintID = GeneralUtils::SetBit(blueprintID, OBJECT_BIT_CHARACTER);
				blueprintID = GeneralUtils::SetBit(blueprintID, OBJECT_BIT_PERSISTENT);

				//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
				const auto& worldId = dZoneManager::Instance()->GetZone()->GetZoneID();

				const auto zoneId = worldId.GetMapID();
				const auto cloneId = worldId.GetCloneID();

				auto query = CDClientDatabase::CreatePreppedStmt(
					"SELECT id FROM PropertyTemplate WHERE mapID = ?;");
				query.bind(1, (int)zoneId);

				auto result = query.execQuery();

				if (result.eof() || result.fieldIsNull(0)) {
					return;
				}

				int templateId = result.getIntField(0);

				result.finalize();

				auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;");

				propertyLookup->setInt(1, templateId);
				propertyLookup->setInt64(2, cloneId);

				auto* propertyEntry = propertyLookup->executeQuery();
				uint64_t propertyId = 0;

				if (propertyEntry->next()) {
					propertyId = propertyEntry->getUInt64(1);
				}

				delete propertyLookup;

				//Insert into ugc:
				auto ugcs = Database::CreatePreppedStmt("INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)");
				ugcs->setUInt(1, blueprintIDSmall);
				ugcs->setInt(2, entity->GetParentUser()->GetAccountID());
				ugcs->setInt(3, entity->GetCharacter()->GetID());
				ugcs->setInt(4, 0);

				//whacky stream biz
				std::string s((char*)inData, lxfmlSize);
				std::istringstream iss(s);
				std::istream& stream = iss;

				ugcs->setBlob(5, &iss);
				ugcs->setBoolean(6, false);
				ugcs->setString(7, "weedeater.lxfml");
				ugcs->execute();
				delete ugcs;

				//Insert into the db as a BBB model:
				auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents`(`id`, `property_id`, `ugc_id`, `lot`, `x`, `y`, `z`, `rx`, `ry`, `rz`, `rw`) VALUES (?,?,?,?,?,?,?,?,?,?,?)");
				stmt->setUInt64(1, newIDL);
				stmt->setUInt64(2, propertyId);
				stmt->setUInt(3, blueprintIDSmall);
				stmt->setUInt(4, 14); //14 is the lot the BBB models use
				stmt->setDouble(5, 0.0f); //x
				stmt->setDouble(6, 0.0f); //y
				stmt->setDouble(7, 0.0f); //z
				stmt->setDouble(8, 0.0f);
				stmt->setDouble(9, 0.0f);
				stmt->setDouble(10, 0.0f);
				stmt->setDouble(11, 0.0f);
				stmt->execute();
				delete stmt;

				/*
					Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
					(or you uncomment the lxfml decomp stuff above)
				*/

				////Send off to UGC for processing, if enabled:
				//if (Game::config->GetValue("ugc_remote") == "1") {
				//	std::string ugcIP = Game::config->GetValue("ugc_ip");
				//	int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));

				//	httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^

				//	//Send out a request:
				//	std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
				//	cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");

				//	//When the "put" above returns, it means that the UGC HTTP server is done processing our model &
				//	//the nif, hkx and checksum files are ready to be downloaded from cache.
				//}

				//Tell the client their model is saved: (this causes us to actually pop out of our current state):
				CBITSTREAM;
				PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE);
				bitStream.Write(localId);
				bitStream.Write<unsigned int>(0);
				bitStream.Write<unsigned int>(1);
				bitStream.Write(blueprintID);

				bitStream.Write(lxfmlSize + 9);

				//Write a fake sd0 header:
				bitStream.Write<unsigned char>(0x73); //s
				bitStream.Write<unsigned char>(0x64); //d
				bitStream.Write<unsigned char>(0x30); //0
				bitStream.Write<unsigned char>(0x01); //1
				bitStream.Write<unsigned char>(0xFF); //end magic

				bitStream.Write(lxfmlSize);

				for (size_t i = 0; i < lxfmlSize; ++i)
					bitStream.Write(inData[i]);

				SEND_PACKET;
				PacketUtils::SavePacket("MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());

				//Now we have to construct this object:
				/*
				* This needs to be sent as config data, but I don't know how to right now.
					'blueprintid': (9, 1152921508346399522),
					'componentWhitelist': (1, 1),
					'modelType': (1, 2),
					'propertyObjectID': (7, True),
					'userModelID': (9, 1152921510759098799)
				*/

				EntityInfo info;
				info.lot = 14;
				info.pos = {};
				info.rot = {};
				info.spawner = nullptr;
				info.spawnerID = entity->GetObjectID();
				info.spawnerNodeID = 0;

				LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", blueprintID);
				LDFBaseData* componentWhitelist = new LDFData<int>(u"componentWhitelist", 1);
				LDFBaseData* modelType = new LDFData<int>(u"modelType", 2);
				LDFBaseData* propertyObjectID = new LDFData<bool>(u"propertyObjectID", true);
				LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", newIDL);

				info.settings.push_back(ldfBlueprintID);
				info.settings.push_back(componentWhitelist);
				info.settings.push_back(modelType);
				info.settings.push_back(propertyObjectID);
				info.settings.push_back(userModelID);

				Entity* newEntity = EntityManager::Instance()->CreateEntity(info, nullptr);
				if (newEntity) {
					EntityManager::Instance()->ConstructEntity(newEntity);

					//Make sure the propMgmt doesn't delete our model after the server dies
					//Trying to do this after the entity is constructed. Shouldn't really change anything but
					//there was an issue with builds not appearing since it was placed above ConstructEntity.
					PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
				}
				});
			});
		});
}

void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool includeNullAddress{};
	bool includeNullDescription{};
	bool playerOwn{};
	bool updateUi{};
	int32_t numResults{};
	int32_t reputation{};
	int32_t sortMethod{};
	int32_t startIndex{};
	uint32_t filterTextLength{};
	std::string filterText{};

	inStream->Read(includeNullAddress);
	inStream->Read(includeNullDescription);
	inStream->Read(playerOwn);
	inStream->Read(updateUi);
	inStream->Read(numResults);
	inStream->Read(reputation);
	inStream->Read(sortMethod);
	inStream->Read(startIndex);
	inStream->Read(filterTextLength);

	for (auto i = 0u; i < filterTextLength; i++) {
		char c;
		inStream->Read(c);
		filterText.push_back(c);
	}

	auto* player = Player::GetPlayer(sysAddr);

	auto* entranceComponent = entity->GetComponent<PropertyEntranceComponent>();

	if (entranceComponent == nullptr) return;

	entranceComponent->OnPropertyEntranceSync(player,
		includeNullAddress,
		includeNullDescription,
		playerOwn,
		updateUi,
		numResults,
		reputation,
		sortMethod,
		startIndex,
		filterText,
		sysAddr
	);
}

void GameMessages::HandleEnterProperty(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint32_t index{};
	bool returnToZone{};

	inStream->Read(index);
	inStream->Read(returnToZone);

	auto* player = Player::GetPlayer(sysAddr);

	auto* entranceComponent = entity->GetComponent<PropertyEntranceComponent>();
	if (entranceComponent != nullptr) {
		entranceComponent->OnEnterProperty(player, index, returnToZone, sysAddr);
		return;
	}

	auto rocketLaunchLupComponent = entity->GetComponent<RocketLaunchLupComponent>();
	if (rocketLaunchLupComponent != nullptr) {
		rocketLaunchLupComponent->OnSelectWorld(player, index);
	}
}

void GameMessages::HandleSetConsumableItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LOT lot;

	inStream->Read(lot);

	auto* inventory = entity->GetComponent<InventoryComponent>();

	if (inventory == nullptr) return;

	inventory->SetConsumable(lot);
}

void GameMessages::SendPlayCinematic(LWOOBJID objectId, std::u16string pathName, const SystemAddress& sysAddr,
	bool allowGhostUpdates, bool bCloseMultiInteract, bool bSendServerNotify, bool bUseControlledObjectForAudioListener,
	int endBehavior, bool hidePlayerDuringCine, float leadIn, bool leavePlayerLockedWhenFinished,
	bool lockPlayer, bool result, bool skipIfSamePath, float startTimeAdvance) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PLAY_CINEMATIC);

	bitStream.Write(allowGhostUpdates);
	bitStream.Write(bCloseMultiInteract);
	bitStream.Write(bSendServerNotify);
	bitStream.Write(bUseControlledObjectForAudioListener);

	bitStream.Write(endBehavior != 0);
	if (endBehavior != 0) bitStream.Write(endBehavior);

	bitStream.Write(hidePlayerDuringCine);

	bitStream.Write(leadIn != -1);
	if (leadIn != -1) bitStream.Write(leadIn);

	bitStream.Write(leavePlayerLockedWhenFinished);
	bitStream.Write(lockPlayer);

	bitStream.Write<uint32_t>(pathName.size());
	for (char16_t c : pathName) {
		bitStream.Write(c);
	}

	bitStream.Write(result);

	bitStream.Write(skipIfSamePath);

	bitStream.Write(startTimeAdvance);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendEndCinematic(LWOOBJID objectId, std::u16string pathName, const SystemAddress& sysAddr,
	float leadOut, bool leavePlayerLocked) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_END_CINEMATIC);

	bitStream.Write(leadOut != -1);
	if (leadOut != -1) bitStream.Write(leadOut);

	bitStream.Write(leavePlayerLocked);

	bitStream.Write<uint32_t>(pathName.size());
	for (uint32_t i = 0; i < pathName.size(); i++) {
		bitStream.Write(pathName[i]);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::HandleCinematicUpdate(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	eCinematicEvent event;
	if (!inStream->ReadBit()) {
		event = STARTED;
	} else {
		inStream->Read<eCinematicEvent>(event);
	}

	float_t overallTime;
	if (!inStream->ReadBit()) {
		overallTime = -1.0f;
	} else {
		inStream->Read<float_t>(overallTime);
	}

	uint32_t pathNameLength;
	inStream->Read(pathNameLength);

	std::u16string pathName;
	for (size_t i = 0; i < pathNameLength; i++) {
		char16_t character;
		inStream->Read(character);
		pathName.push_back(character);
	}

	float_t pathTime;
	if (!inStream->ReadBit()) {
		pathTime = -1.0f;
	} else {
		inStream->Read<float_t>(pathTime);
	}

	int32_t waypoint;
	if (!inStream->ReadBit()) {
		waypoint = -1;
	} else {
		inStream->Read<int32_t>(waypoint);
	}

	std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPT);
	for (Entity* scriptEntity : scriptedActs) {
		scriptEntity->OnCinematicUpdate(scriptEntity, entity, event, pathName, pathTime, overallTime, waypoint);
	}
}

void GameMessages::SendSetStunned(LWOOBJID objectId, eStunState stateChangeType, const SystemAddress& sysAddr,
	LWOOBJID originator, bool bCantAttack, bool bCantEquip,
	bool bCantInteract, bool bCantJump, bool bCantMove, bool bCantTurn,
	bool bCantUseItem, bool bDontTerminateInteract, bool bIgnoreImmunity,
	bool bCantAttackOutChangeWasApplied, bool bCantEquipOutChangeWasApplied,
	bool bCantInteractOutChangeWasApplied, bool bCantJumpOutChangeWasApplied,
	bool bCantMoveOutChangeWasApplied, bool bCantTurnOutChangeWasApplied,
	bool bCantUseItemOutChangeWasApplied) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_STUNNED);

	bitStream.Write(originator != LWOOBJID_EMPTY);
	if (originator != LWOOBJID_EMPTY) bitStream.Write(originator);

	bitStream.Write(stateChangeType);

	bitStream.Write(bCantAttack);
	bitStream.Write(bCantAttackOutChangeWasApplied);

	bitStream.Write(bCantEquip);
	bitStream.Write(bCantEquipOutChangeWasApplied);

	bitStream.Write(bCantInteract);
	bitStream.Write(bCantInteractOutChangeWasApplied);

	bitStream.Write(bCantJump);
	bitStream.Write(bCantJumpOutChangeWasApplied);

	bitStream.Write(bCantMove);
	bitStream.Write(bCantMoveOutChangeWasApplied);

	bitStream.Write(bCantTurn);
	bitStream.Write(bCantTurnOutChangeWasApplied);

	bitStream.Write(bCantUseItem);
	bitStream.Write(bCantUseItemOutChangeWasApplied);

	bitStream.Write(bDontTerminateInteract);

	bitStream.Write(bIgnoreImmunity);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendOrientToAngle(LWOOBJID objectId, bool bRelativeToCurrent, float fAngle, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ORIENT_TO_ANGLE);

	bitStream.Write(bRelativeToCurrent);
	bitStream.Write(fAngle);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendAddRunSpeedModifier(LWOOBJID objectId, LWOOBJID caster, uint32_t modifier, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ADD_RUN_SPEED_MODIFIER);

	bitStream.Write(caster != LWOOBJID_EMPTY);
	if (caster != LWOOBJID_EMPTY) bitStream.Write(caster);

	bitStream.Write(modifier != 500);
	if (modifier != 500) bitStream.Write(modifier);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendRemoveRunSpeedModifier(LWOOBJID objectId, uint32_t modifier, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_REMOVE_RUN_SPEED_MODIFIER);

	bitStream.Write(modifier != 500);
	if (modifier != 500) bitStream.Write(modifier);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPropertyEntranceBegin(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PROPERTY_ENTRANCE_BEGIN);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPropertySelectQuery(LWOOBJID objectId, int32_t navOffset, bool thereAreMore, int32_t cloneId, bool hasFeaturedProperty, bool wasFriends, const std::vector<PropertySelectQueryProperty>& entries, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PROPERTY_SELECT_QUERY);

	bitStream.Write(navOffset);
	bitStream.Write(thereAreMore);
	bitStream.Write(cloneId);
	bitStream.Write(hasFeaturedProperty);
	bitStream.Write(wasFriends);

	bitStream.Write<uint32_t>(entries.size());

	for (auto& entry : entries) {
		entry.Serialize(bitStream);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyObject(LWOOBJID objectId, LWOOBJID objIDSender, std::u16string name, const SystemAddress& sysAddr, int param1, int param2) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_OBJECT);

	bitStream.Write(objIDSender);
	bitStream.Write(static_cast<uint32_t>(name.size()));
	for (const auto character : name) {
		bitStream.Write(character);
	}

	bitStream.Write(param1);
	bitStream.Write(param2);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::HandleVerifyAck(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bDifferent;
	std::string sBitStream;
	uint32_t uiHandle = 0;

	bDifferent = inStream->ReadBit();

	uint32_t sBitStreamLength = 0;
	inStream->Read(sBitStreamLength);
	for (uint64_t k = 0; k < sBitStreamLength; k++) {
		uint8_t character;
		inStream->Read(character);
		sBitStream.push_back(character);
	}

	if (inStream->ReadBit()) {
		inStream->Read(uiHandle);
	}
}

void GameMessages::SendTeamPickupItem(LWOOBJID objectId, LWOOBJID lootID, LWOOBJID lootOwnerID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_TEAM_PICKUP_ITEM);

	bitStream.Write(lootID);
	bitStream.Write(lootOwnerID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

//Trading:

void GameMessages::SendServerTradeInvite(LWOOBJID objectId, bool bNeedInvitePopUp, LWOOBJID i64Requestor, std::u16string wsName, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_INVITE);

	bitStream.Write(bNeedInvitePopUp);
	bitStream.Write(i64Requestor);
	bitStream.Write(static_cast<uint32_t>(wsName.size()));
	for (const auto character : wsName) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendServerTradeInitialReply(LWOOBJID objectId, LWOOBJID i64Invitee, int32_t resultType, std::u16string wsName, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_INITIAL_REPLY);

	bitStream.Write(i64Invitee);
	bitStream.Write(resultType);
	bitStream.Write(static_cast<uint32_t>(wsName.size()));
	for (const auto character : wsName) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendServerTradeFinalReply(LWOOBJID objectId, bool bResult, LWOOBJID i64Invitee, std::u16string wsName, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_FINAL_REPLY);

	bitStream.Write(bResult);
	bitStream.Write(i64Invitee);
	bitStream.Write(static_cast<uint32_t>(wsName.size()));
	for (const auto character : wsName) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendServerTradeAccept(LWOOBJID objectId, bool bFirst, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_ACCEPT);

	bitStream.Write(bFirst);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendServerTradeCancel(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_CANCEL);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, const std::vector<TradeItem>& items, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SERVER_TRADE_UPDATE);

	bitStream.Write(false);
	bitStream.Write(coins);
	bitStream.Write(static_cast<uint32_t>(items.size()));

	for (const auto& item : items) {
		bitStream.Write(item.itemId);
		bitStream.Write(item.itemId);

		bitStream.Write(item.itemLot);
		bitStream.Write0();
		bitStream.Write1();
		bitStream.Write(item.itemCount);
		bitStream.Write0();
		bitStream.Write0();
		bitStream.Write0();
		bitStream.Write0();
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::HandleClientTradeRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	// Check if the player has restricted trade access
	auto* character = entity->GetCharacter();

	if (character->HasPermission(PermissionMap::RestrictedTradeAccess)) {
		// Send a message to the player
		ChatPackets::SendSystemMessage(
			sysAddr,
			u"This character has restricted trade access."
		);

		return;
	}

	bool bNeedInvitePopUp = inStream->ReadBit();
	LWOOBJID i64Invitee;

	inStream->Read(i64Invitee);

	auto* invitee = EntityManager::Instance()->GetEntity(i64Invitee);

	if (invitee != nullptr && invitee->IsPlayer()) {
		character = invitee->GetCharacter();

		if (character->HasPermission(PermissionMap::RestrictedTradeAccess)) {
			// Send a message to the player
			ChatPackets::SendSystemMessage(
				sysAddr,
				u"The character you are trying to trade with has restricted trade access."
			);

			return;
		}

		Game::logger->Log("GameMessages", "Trade request to (%llu)", i64Invitee);

		auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());

		if (trade != nullptr) {
			if (!trade->IsParticipant(i64Invitee)) {
				TradingManager::Instance()->CancelTrade(trade->GetTradeId());

				TradingManager::Instance()->NewTrade(entity->GetObjectID(), i64Invitee);
			}
		} else {
			TradingManager::Instance()->NewTrade(entity->GetObjectID(), i64Invitee);
		}

		SendServerTradeInvite(
			i64Invitee,
			bNeedInvitePopUp,
			entity->GetObjectID(),
			GeneralUtils::UTF8ToUTF16(entity->GetCharacter()->GetName()),
			invitee->GetSystemAddress()
		);
	}
}

void GameMessages::HandleClientTradeCancel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());

	if (trade == nullptr) return;

	Game::logger->Log("GameMessages", "Trade canceled from (%llu)", entity->GetObjectID());

	TradingManager::Instance()->CancelTrade(trade->GetTradeId());
}

void GameMessages::HandleClientTradeAccept(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bFirst = inStream->ReadBit();

	Game::logger->Log("GameMessages", "Trade accepted from (%llu) -> (%d)", entity->GetObjectID(), bFirst);

	auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());

	if (trade == nullptr) return;

	trade->SetAccepted(entity->GetObjectID(), bFirst);
}

void GameMessages::HandleClientTradeUpdate(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint64_t currency;
	uint32_t itemCount;

	inStream->Read(currency);
	inStream->Read(itemCount);

	Game::logger->Log("GameMessages", "Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount);

	std::vector<TradeItem> items{};

	for (size_t i = 0; i < itemCount; i++) {
		LWOOBJID itemId;
		LWOOBJID itemId2;

		inStream->Read(itemId);
		inStream->Read(itemId2);

		LOT lot = 0;
		LWOOBJID unknown1 = 0;
		uint32_t unknown2 = 0;
		uint16_t slot = 0;
		uint32_t unknown3 = 0;
		uint32_t ldfSize = 0;
		bool unknown4;

		inStream->Read(lot);
		if (inStream->ReadBit()) {
			inStream->Read(unknown1);
		}
		if (inStream->ReadBit()) {
			inStream->Read(unknown2);
		}
		if (inStream->ReadBit()) {
			inStream->Read(slot);
		}
		if (inStream->ReadBit()) {
			inStream->Read(unknown3);
		}
		if (inStream->ReadBit()) // No
		{
			inStream->Read(ldfSize);
			bool compressed = inStream->ReadBit();
			if (compressed) {
				uint32_t ldfCompressedSize = 0;
				inStream->Read(ldfCompressedSize);
				inStream->IgnoreBytes(ldfCompressedSize);
			} else {
				inStream->IgnoreBytes(ldfSize);
			}
		}
		unknown4 = inStream->ReadBit();

		items.push_back({ itemId, lot, unknown2 });

		Game::logger->Log("GameMessages", "Trade item from (%llu) -> (%llu)/(%llu), (%i), (%llu), (%i), (%i)", entity->GetObjectID(), itemId, itemId2, lot, unknown1, unknown2, unknown3);
	}

	auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());

	if (trade == nullptr) return;

	trade->SetCoins(entity->GetObjectID(), currency);
	trade->SetItems(entity->GetObjectID(), items);
	trade->SendUpdateToOther(entity->GetObjectID());
}

//Pets:

void GameMessages::SendNotifyPetTamingMinigame(LWOOBJID objectId, LWOOBJID petId, LWOOBJID playerTamingId, bool bForceTeleport, uint32_t notifyType, NiPoint3 petsDestPos, NiPoint3 telePos, NiQuaternion teleRot, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_PET_TAMING_MINIGAME);

	bitStream.Write(petId);
	bitStream.Write(playerTamingId);
	bitStream.Write(bForceTeleport);
	bitStream.Write(notifyType);
	bitStream.Write(petsDestPos);
	bitStream.Write(telePos);

	const bool hasDefault = teleRot != NiQuaternion::IDENTITY;
	bitStream.Write(hasDefault);
	if (hasDefault) bitStream.Write(teleRot);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyTamingModelLoadedOnServer(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_TAMING_MODEL_LOADED_ON_SERVER);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendNotifyPetTamingPuzzleSelected(LWOOBJID objectId, std::vector<Brick>& bricks, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_PET_TAMING_PUZZLE_SELECTED);

	bitStream.Write(static_cast<uint32_t>(bricks.size()));
	for (const auto& brick : bricks) {
		bitStream.Write(brick.designerID);
		bitStream.Write(brick.materialID);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPetTamingTryBuildResult(LWOOBJID objectId, bool bSuccess, int32_t iNumCorrect, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PET_TAMING_TRY_BUILD_RESULT);

	bitStream.Write(bSuccess);
	bitStream.Write(iNumCorrect != 0);
	if (iNumCorrect != 0) bitStream.Write(iNumCorrect);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPetResponse(LWOOBJID objectId, LWOOBJID objIDPet, int32_t iPetCommandType, int32_t iResponse, int32_t iTypeID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PET_RESPONSE);

	bitStream.Write(objIDPet);
	bitStream.Write(iPetCommandType);
	bitStream.Write(iResponse);
	bitStream.Write(iTypeID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendAddPetToPlayer(LWOOBJID objectId, int32_t iElementalType, std::u16string name, LWOOBJID petDBID, LOT petLOT, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ADD_PET_TO_PLAYER);

	bitStream.Write(iElementalType);
	bitStream.Write(static_cast<uint32_t>(name.size()));
	for (const auto character : name) {
		bitStream.Write(character);
	}

	bitStream.Write(petDBID);
	bitStream.Write(petLOT);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendRegisterPetID(LWOOBJID objectId, LWOOBJID objID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_REGISTER_PET_ID);

	bitStream.Write(objID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendRegisterPetDBID(LWOOBJID objectId, LWOOBJID petDBID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_REGISTER_PET_DBID);

	bitStream.Write(petDBID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_MARK_INVENTORY_ITEM_AS_ACTIVE);

	bitStream.Write(bActive);

	bitStream.Write(iType != eUnequippableActiveType::INVALID);
	if (iType != eUnequippableActiveType::INVALID) bitStream.Write(iType);

	bitStream.Write(itemID != LWOOBJID_EMPTY);
	if (itemID != LWOOBJID_EMPTY) bitStream.Write(itemID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendClientExitTamingMinigame(LWOOBJID objectId, bool bVoluntaryExit, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_CLIENT_EXIT_TAMING_MINIGAME);

	bitStream.Write(bVoluntaryExit);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendShowPetActionButton(LWOOBJID objectId, int32_t buttonLabel, bool bShow, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SHOW_PET_ACTION_BUTTON);

	bitStream.Write(buttonLabel);
	bitStream.Write(bShow);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID target, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PLAY_EMOTE);

	bitStream.Write(emoteID);
	bitStream.Write(target);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_BOUNCER_ACTIVE_STATUS);

	bitStream.Write(bActive);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendSetPetName(LWOOBJID objectId, std::u16string name, LWOOBJID petDBID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_PET_NAME);

	bitStream.Write(static_cast<uint32_t>(name.size()));
	for (const auto character : name) {
		bitStream.Write(character);
	}

	bitStream.Write(petDBID != LWOOBJID_EMPTY);
	if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendSetPetNameModerated(LWOOBJID objectId, LWOOBJID petDBID, int32_t nModerationStatus, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SET_PET_NAME_MODERATED);

	bitStream.Write(petDBID != LWOOBJID_EMPTY);
	if (petDBID != LWOOBJID_EMPTY) bitStream.Write(petDBID);

	bitStream.Write(nModerationStatus);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendPetNameChanged(LWOOBJID objectId, int32_t moderationStatus, std::u16string name, std::u16string ownerName, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_PET_NAME_CHANGED);

	bitStream.Write(moderationStatus);

	bitStream.Write(static_cast<uint32_t>(name.size()));
	for (const auto character : name) {
		bitStream.Write(character);
	}

	bitStream.Write(static_cast<uint32_t>(ownerName.size()));
	for (const auto character : ownerName) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::HandleClientExitTamingMinigame(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bVoluntaryExit = inStream->ReadBit();

	auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID());

	if (petComponent == nullptr) {
		return;
	}

	petComponent->ClientExitTamingMinigame(bVoluntaryExit);
}

void GameMessages::HandleStartServerPetMinigameTimer(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID());

	if (petComponent == nullptr) {
		return;
	}

	petComponent->StartTimer();
}

void GameMessages::HandlePetTamingTryBuild(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint32_t brickCount;
	std::vector<Brick> bricks;
	bool clientFailed;

	inStream->Read(brickCount);

	bricks.reserve(brickCount);

	for (uint32_t i = 0; i < brickCount; i++) {
		Brick brick;

		inStream->Read(brick);

		bricks.push_back(brick);
	}

	clientFailed = inStream->ReadBit();

	auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID());

	if (petComponent == nullptr) {
		return;
	}

	petComponent->TryBuild(bricks.size(), clientFailed);
}

void GameMessages::HandleNotifyTamingBuildSuccess(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	NiPoint3 position;

	inStream->Read(position);

	auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID());

	if (petComponent == nullptr) {
		return;
	}

	petComponent->NotifyTamingBuildSuccess(position);
}

void GameMessages::HandleRequestSetPetName(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint32_t nameLength;
	std::u16string name;

	inStream->Read(nameLength);

	for (size_t i = 0; i < nameLength; i++) {
		char16_t character;
		inStream->Read(character);
		name.push_back(character);
	}

	auto* petComponent = PetComponent::GetTamingPet(entity->GetObjectID());

	if (petComponent == nullptr) {
		petComponent = PetComponent::GetActivePet(entity->GetObjectID());

		if (petComponent == nullptr) {
			return;
		}
	}

	petComponent->RequestSetPetName(name);
}

void GameMessages::HandleCommandPet(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	NiPoint3 genericPosInfo;
	LWOOBJID objIdSource;
	int32_t iPetCommandType;
	int32_t iTypeID;
	bool overrideObey;

	inStream->Read(genericPosInfo);
	inStream->Read(objIdSource);
	inStream->Read(iPetCommandType);
	inStream->Read(iTypeID);
	overrideObey = inStream->ReadBit();

	auto* petComponent = entity->GetComponent<PetComponent>();

	if (petComponent == nullptr) {
		return;
	}

	petComponent->Command(genericPosInfo, objIdSource, iPetCommandType, iTypeID, overrideObey);
}

void GameMessages::HandleDespawnPet(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bDeletePet;

	bDeletePet = inStream->ReadBit();

	auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID());

	if (petComponent == nullptr) {
		return;
	}

	if (bDeletePet) {
		petComponent->Release();
	} else {
		petComponent->Deactivate();
	}
}

void GameMessages::HandleMessageBoxResponse(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	int32_t iButton;
	uint32_t identifierLength;
	std::u16string identifier;
	uint32_t userDataLength;
	std::u16string userData;

	inStream->Read(iButton);

	inStream->Read(identifierLength);
	for (size_t i = 0; i < identifierLength; i++) {
		char16_t character;
		inStream->Read(character);
		identifier.push_back(character);
	}

	inStream->Read(userDataLength);
	for (size_t i = 0; i < userDataLength; i++) {
		char16_t character;
		inStream->Read(character);
		userData.push_back(character);
	}

	Game::logger->Log("HandleMessageBoxResponse", "Button: %d; LOT: %u identifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(identifier).c_str(), GeneralUtils::UTF16ToWTF8(userData).c_str());

	auto* user = UserManager::Instance()->GetUser(sysAddr);

	if (user == nullptr) {
		return;
	}

	auto* userEntity = user->GetLastUsedChar()->GetEntity();

	if (userEntity == nullptr) {
		return;
	}

	entity->OnMessageBoxResponse(userEntity, iButton, identifier, userData);

	auto* scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>();

	if (scriptedActivityComponent != nullptr) {
		scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
	}

	auto* racingControlComponent = entity->GetComponent<RacingControlComponent>();

	if (racingControlComponent != nullptr) {
		racingControlComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier));
	}

	for (auto* shootingGallery : EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SHOOTING_GALLERY)) {
		shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData);
	}
}

void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	int32_t iButton;
	uint32_t buttonIdentifierLength;
	std::u16string buttonIdentifier;
	uint32_t identifierLength;
	std::u16string identifier;

	inStream->Read(buttonIdentifierLength);
	for (size_t i = 0; i < buttonIdentifierLength; i++) {
		char16_t character;
		inStream->Read(character);
		buttonIdentifier.push_back(character);
	}

	inStream->Read(iButton);

	inStream->Read(identifierLength);
	for (size_t i = 0; i < identifierLength; i++) {
		char16_t character;
		inStream->Read(character);
		identifier.push_back(character);
	}

	Game::logger->Log("HandleChoiceBoxRespond", "Button: %d; LOT: %u buttonIdentifier: %s; userData: %s", iButton, entity->GetLOT(), GeneralUtils::UTF16ToWTF8(buttonIdentifier).c_str(), GeneralUtils::UTF16ToWTF8(identifier).c_str());

	auto* user = UserManager::Instance()->GetUser(sysAddr);

	if (user == nullptr) {
		return;
	}

	auto* userEntity = user->GetLastUsedChar()->GetEntity();

	if (userEntity == nullptr) {
		return;
	}

	entity->OnChoiceBoxResponse(userEntity, iButton, buttonIdentifier, identifier);
}

void GameMessages::SendDisplayZoneSummary(LWOOBJID objectId, const SystemAddress& sysAddr, bool isPropertyMap, bool isZoneStart, LWOOBJID sender) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_DISPLAY_ZONE_SUMMARY);

	bitStream.Write(isPropertyMap);
	bitStream.Write(isZoneStart);
	bitStream.Write(sender != LWOOBJID_EMPTY);
	if (sender != LWOOBJID_EMPTY) bitStream.Write(sender);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

//UI

void GameMessages::SendNotifyNotEnoughInvSpace(LWOOBJID objectId, uint32_t freeSlotsNeeded, eInventoryType inventoryType, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_NOTIFY_FINISHED_RACE);

	bitStream.Write(freeSlotsNeeded);
	bitStream.Write(inventoryType != 0);
	if (inventoryType != 0) bitStream.Write(inventoryType);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendDisplayMessageBox(LWOOBJID objectId, bool bShow, LWOOBJID callbackClient, const std::u16string& identifier, int32_t imageID, const std::u16string& text, const std::u16string& userData, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_DISPLAY_MESSAGE_BOX);

	bitStream.Write(bShow);
	bitStream.Write(callbackClient);

	bitStream.Write(static_cast<uint32_t>(identifier.size()));
	for (const auto character : identifier) {
		bitStream.Write(character);
	}

	bitStream.Write(imageID);

	bitStream.Write(static_cast<uint32_t>(text.size()));
	for (const auto character : text) {
		bitStream.Write(character);
	}

	bitStream.Write(static_cast<uint32_t>(userData.size()));
	for (const auto character : userData) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendDisplayChatBubble(LWOOBJID objectId, const std::u16string& text, const SystemAddress& sysAddr) {
	// GAME_MSG_DISPLAY_CHAT_BUBBLE
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_DISPLAY_CHAT_BUBBLE);

	bitStream.Write(static_cast<uint32_t>(text.size()));
	for (const auto character : text) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

// Mounts

void GameMessages::SendSetMountInventoryID(Entity* entity, const LWOOBJID& objectID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;
	bitStream.Write(entity->GetObjectID());
	bitStream.Write(GAME_MSG::GAME_MSG_SET_MOUNT_INVENTORY_ID);
	bitStream.Write(objectID);

	SEND_PACKET_BROADCAST;
}


void GameMessages::HandleDismountComplete(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	// Get the objectID from the bitstream
	LWOOBJID objectId{};
	inStream->Read(objectId);

	// If we aren't possessing somethings, the don't do anything
	if (objectId != LWOOBJID_EMPTY) {
		auto* possessorComponent = entity->GetComponent<PossessorComponent>();
		auto* mount = EntityManager::Instance()->GetEntity(objectId);
		// make sure we have the things we need and they aren't null
		if (possessorComponent && mount) {
			if (!possessorComponent->GetIsDismounting()) return;
			possessorComponent->SetIsDismounting(false);
			possessorComponent->SetPossessable(LWOOBJID_EMPTY);
			possessorComponent->SetPossessableType(ePossessionType::NO_POSSESSION);

			// character related things
			auto* character = entity->GetComponent<CharacterComponent>();
			if (character) {
				// If we had an active item turn it off
				if (possessorComponent->GetMountItemID() != LWOOBJID_EMPTY) GameMessages::SendMarkInventoryItemAsActive(entity->GetObjectID(), false, eUnequippableActiveType::MOUNT, possessorComponent->GetMountItemID(), entity->GetSystemAddress());
				possessorComponent->SetMountItemID(LWOOBJID_EMPTY);
			}

			// Set that the controllabel phsyics comp is teleporting
			auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
			if (controllablePhysicsComponent) controllablePhysicsComponent->SetIsTeleporting(true);

			// Call dismoint on the possessable comp to let it handle killing the possessable
			auto* possessableComponent = mount->GetComponent<PossessableComponent>();
			if (possessableComponent) possessableComponent->Dismount();

			// Update the entity that was possessing
			EntityManager::Instance()->SerializeEntity(entity);

			// We aren't mounted so remove the stun
			GameMessages::SendSetStunned(entity->GetObjectID(), eStunState::POP, UNASSIGNED_SYSTEM_ADDRESS, LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
		}
	}
}


void GameMessages::HandleAcknowledgePossession(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	EntityManager::Instance()->SerializeEntity(entity);
	LWOOBJID objectId{};
	inStream->Read(objectId);
	auto* mount = EntityManager::Instance()->GetEntity(objectId);
	if (mount) EntityManager::Instance()->SerializeEntity(mount);
}

//Racing

void GameMessages::HandleModuleAssemblyQueryData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	auto* moduleAssemblyComponent = entity->GetComponent<ModuleAssemblyComponent>();

	Game::logger->Log("HandleModuleAssemblyQueryData", "Got Query from %i", entity->GetLOT());

	if (moduleAssemblyComponent != nullptr) {
		Game::logger->Log("HandleModuleAssemblyQueryData", "Returning assembly %s", GeneralUtils::UTF16ToWTF8(moduleAssemblyComponent->GetAssemblyPartsLOTs()).c_str());

		SendModuleAssemblyDBDataForClient(entity->GetObjectID(), moduleAssemblyComponent->GetSubKey(), moduleAssemblyComponent->GetAssemblyPartsLOTs(), UNASSIGNED_SYSTEM_ADDRESS);
	}
}


void GameMessages::HandleModularAssemblyNIFCompleted(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID objectID;

	inStream->Read(objectID);
}


void GameMessages::HandleVehicleSetWheelLockState(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bExtraFriction = inStream->ReadBit();
	bool bLocked = inStream->ReadBit();
}


void GameMessages::HandleRacingClientReady(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID playerID;

	inStream->Read(playerID);

	auto* player = EntityManager::Instance()->GetEntity(playerID);

	if (player == nullptr) {
		return;
	}

	auto* racingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent<RacingControlComponent>();

	if (racingControlComponent == nullptr) {
		return;
	}

	racingControlComponent->OnRacingClientReady(player);
}


void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bClientDeath;
	bool bSpawnLoot;
	std::u16string deathType;
	float directionRelativeAngleXZ;
	float directionRelativeAngleY;
	float directionRelativeForce;
	int32_t killType = VIOLENT;
	LWOOBJID killerID;
	LWOOBJID lootOwnerID = LWOOBJID_EMPTY;

	bClientDeath = inStream->ReadBit();
	bSpawnLoot = inStream->ReadBit();

	uint32_t deathTypeLength = 0;
	inStream->Read(deathTypeLength);

	for (size_t i = 0; i < deathTypeLength; i++) {
		char16_t character;
		inStream->Read(character);

		deathType.push_back(character);
	}

	inStream->Read(directionRelativeAngleXZ);
	inStream->Read(directionRelativeAngleY);
	inStream->Read(directionRelativeForce);

	if (inStream->ReadBit()) {
		inStream->Read(killType);
	}

	inStream->Read(killerID);

	if (inStream->ReadBit()) {
		inStream->Read(lootOwnerID);
	}

	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();

	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>();

	Game::logger->Log("HandleRequestDie", "Got die request: %i", entity->GetLOT());

	if (racingControlComponent != nullptr) {
		auto* possessableComponent = entity->GetComponent<PossessableComponent>();

		if (possessableComponent != nullptr) {
			entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor());

			if (entity == nullptr) {
				return;
			}
		}

		racingControlComponent->OnRequestDie(entity);
	}
}


void GameMessages::HandleVehicleNotifyServerAddPassiveBoostAction(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	//SendVehicleAddPassiveBoostAction(entity->GetObjectID(), sysAddr);
}


void GameMessages::HandleVehicleNotifyServerRemovePassiveBoostAction(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	//SendVehicleRemovePassiveBoostAction(entity->GetObjectID(), sysAddr);
}


void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID playerID;

	inStream->Read(playerID);

	auto* player = EntityManager::Instance()->GetEntity(playerID);

	if (player == nullptr) {
		return;
	}

	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject();

	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>();

	Game::logger->Log("HandleRacingPlayerInfoResetFinished", "Got finished: %i", entity->GetLOT());

	if (racingControlComponent != nullptr) {
		racingControlComponent->OnRacingPlayerInfoResetFinished(player);
	}
}

void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_UPDATE_REPUTATION);

	bitStream.Write(reputation);

	SEND_PACKET;
}

void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	float performanceCost = 0.0f;

	if (inStream->ReadBit()) inStream->Read(performanceCost);

	if (performanceCost == 0.0f) return;

	auto zone = dZoneManager::Instance()->GetZone();
	const auto& worldId = zone->GetZoneID();
	const auto cloneId = worldId.GetCloneID();
	const auto zoneId = worldId.GetMapID();

	auto updatePerformanceCostQuery = Database::CreatePreppedStmt("UPDATE properties SET performance_cost = ? WHERE clone_id = ? AND zone_id = ?");

	updatePerformanceCostQuery->setDouble(1, performanceCost);
	updatePerformanceCostQuery->setInt(2, cloneId);
	updatePerformanceCostQuery->setInt(3, zoneId);

	updatePerformanceCostQuery->executeUpdate();

	delete updatePerformanceCostQuery;
	updatePerformanceCostQuery = nullptr;
}

void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID pickupObjID = LWOOBJID_EMPTY;
	LWOOBJID pickupSpawnerID = LWOOBJID_EMPTY;
	int32_t pickupSpawnerIndex = -1;
	NiPoint3 vehiclePosition = NiPoint3::ZERO;

	if (inStream->ReadBit()) inStream->Read(pickupObjID);
	if (inStream->ReadBit()) inStream->Read(pickupSpawnerID);
	if (inStream->ReadBit()) inStream->Read(pickupSpawnerIndex);
	if (inStream->ReadBit()) inStream->Read(vehiclePosition);

	auto* pickup = EntityManager::Instance()->GetEntity(pickupObjID);

	if (pickup == nullptr) {
		return;
	}

	auto* possessableComponent = entity->GetComponent<PossessableComponent>();

	if (possessableComponent != nullptr) {
		entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor());

		if (entity == nullptr) {
			return;
		}
	}

	auto* characterComponent = entity->GetComponent<CharacterComponent>();
	if (characterComponent != nullptr) {
		characterComponent->UpdatePlayerStatistic(RacingImaginationPowerUpsCollected);
	}

	pickup->OnFireEventServerSide(entity, "powerup");

	pickup->Kill(entity);
}


void GameMessages::SendModuleAssemblyDBDataForClient(LWOOBJID objectId, LWOOBJID assemblyID, const std::u16string& data, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_MODULE_ASSEMBLY_DB_DATA_FOR_CLIENT);

	bitStream.Write(assemblyID);

	bitStream.Write(static_cast<uint32_t>(data.size()));
	for (auto character : data) {
		bitStream.Write(character);
	}

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendNotifyVehicleOfRacingObject(LWOOBJID objectId, LWOOBJID racingObjectID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_VEHICLE_OF_RACING_OBJECT);

	bitStream.Write(racingObjectID != LWOOBJID_EMPTY);
	if (racingObjectID != LWOOBJID_EMPTY) bitStream.Write(racingObjectID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendRacingPlayerLoaded(LWOOBJID objectId, LWOOBJID playerID, LWOOBJID vehicleID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_RACING_PLAYER_LOADED);

	bitStream.Write(playerID);
	bitStream.Write(vehicleID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendVehicleUnlockInput(LWOOBJID objectId, bool bLockWheels, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_UNLOCK_INPUT);

	bitStream.Write(bLockWheels);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendVehicleSetWheelLockState(LWOOBJID objectId, bool bExtraFriction, bool bLocked, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_SET_WHEEL_LOCK_STATE);

	bitStream.Write(bExtraFriction);
	bitStream.Write(bLocked);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendRacingSetPlayerResetInfo(LWOOBJID objectId, int32_t currentLap, uint32_t furthestResetPlane, LWOOBJID playerID, NiPoint3 respawnPos, uint32_t upcomingPlane, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_RACING_SET_PLAYER_RESET_INFO);

	bitStream.Write(currentLap);
	bitStream.Write(furthestResetPlane);
	bitStream.Write(playerID);
	bitStream.Write(respawnPos);
	bitStream.Write(upcomingPlane);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendRacingResetPlayerToLastReset(LWOOBJID objectId, LWOOBJID playerID, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_RACING_RESET_PLAYER_TO_LAST_RESET);

	bitStream.Write(playerID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendNotifyRacingClient(LWOOBJID objectId, int32_t eventType, int32_t param1, LWOOBJID paramObj, std::u16string paramStr, LWOOBJID singleClient, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_NOTIFY_RACING_CLIENT);

	bitStream.Write(eventType != 0);
	if (eventType != 0) bitStream.Write(eventType);

	bitStream.Write(param1);

	bitStream.Write(paramObj);

	bitStream.Write(static_cast<uint32_t>(paramStr.size()));
	for (auto character : paramStr) {
		bitStream.Write(character);
	}

	bitStream.Write(singleClient);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ACTIVITY_ENTER);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendActivityStart(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ACTIVITY_START);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendActivityExit(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ACTIVITY_EXIT);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendActivityStop(LWOOBJID objectId, bool bExit, bool bUserCancel, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_ACTIVITY_STOP);

	bitStream.Write(bExit);
	bitStream.Write(bUserCancel);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendVehicleAddPassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_ADD_PASSIVE_BOOST_ACTION);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendVehicleRemovePassiveBoostAction(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_REMOVE_PASSIVE_BOOST_ACTION);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


void GameMessages::SendVehicleNotifyFinishedRace(LWOOBJID objectId, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_VEHICLE_NOTIFY_FINISHED_RACE);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}

void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uint32_t buffID, uint32_t msDuration,
	bool addImmunity, bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout,
	bool cancelOnRemoveBuff, bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone,
	const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectID);
	bitStream.Write(GAME_MSG::GAME_MSG_ADD_BUFF);

	bitStream.Write(false); // Added by teammate
	bitStream.Write(false); // Apply on teammates
	bitStream.Write(false); // Cancel on damage absorb ran out
	bitStream.Write(cancelOnDamaged);
	bitStream.Write(cancelOnDeath);
	bitStream.Write(cancelOnLogout);
	bitStream.Write(false); // Cancel on move
	bitStream.Write(cancelOnRemoveBuff);

	bitStream.Write(cancelOnUi);
	bitStream.Write(cancelOnUnequip);
	bitStream.Write(cancelOnZone);
	bitStream.Write(false); // Ignore immunities
	bitStream.Write(addImmunity);
	bitStream.Write(false); // Use ref count

	bitStream.Write(buffID);
	bitStream.Write(msDuration);

	bitStream.Write(casterID);
	bitStream.Write(casterID);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


// NT

void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bAllowPartial;
	int32_t destSlot = -1;
	int32_t iStackCount = 1;
	eInventoryType invTypeDst = ITEMS;
	eInventoryType invTypeSrc = ITEMS;
	LWOOBJID itemID = LWOOBJID_EMPTY;
	bool showFlyingLoot;
	LWOOBJID subkey = LWOOBJID_EMPTY;
	LOT itemLOT = 0;

	bAllowPartial = inStream->ReadBit();
	if (inStream->ReadBit()) inStream->Read(destSlot);
	if (inStream->ReadBit()) inStream->Read(iStackCount);
	if (inStream->ReadBit()) inStream->Read(invTypeDst);
	if (inStream->ReadBit()) inStream->Read(invTypeSrc);
	if (inStream->ReadBit()) inStream->Read(itemID);
	showFlyingLoot = inStream->ReadBit();
	if (inStream->ReadBit()) inStream->Read(subkey);
	if (inStream->ReadBit()) inStream->Read(itemLOT);

	if (invTypeDst == invTypeSrc) {
		return;
	}

	auto* inventoryComponent = entity->GetComponent<InventoryComponent>();

	if (inventoryComponent != nullptr) {
		if (itemID != LWOOBJID_EMPTY) {
			auto* item = inventoryComponent->FindItemById(itemID);

			if (item == nullptr) {
				return;
			}

			if (inventoryComponent->IsPet(item->GetSubKey()) || !item->GetConfig().empty()) {
				return;
			}

			inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot);
		}
	}
}


void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;

	bitStream.Write(objectId);
	bitStream.Write(GAME_MSG::GAME_MSG_SHOW_ACTIVITY_COUNTDOWN);

	bitStream.Write(bPlayAdditionalSound);

	bitStream.Write(bPlayCountdownSound);

	bitStream.Write(static_cast<uint32_t>(sndName.size()));
	for (auto character : sndName) {
		bitStream.Write(character);
	}

	bitStream.Write(stateToPlaySoundOn);

	if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
	SEND_PACKET;
}


//-----------------------------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------- Handlers ------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------------------------

void GameMessages::HandleToggleGhostReferenceOverride(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bOverride = false;

	inStream->Read(bOverride);

	auto* player = Player::GetPlayer(sysAddr);

	if (player != nullptr) {
		player->SetGhostOverride(bOverride);

		EntityManager::Instance()->UpdateGhosting(player);
	}
}


void GameMessages::HandleSetGhostReferencePosition(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	NiPoint3 position;

	inStream->Read(position);

	auto* player = Player::GetPlayer(sysAddr);

	if (player != nullptr) {
		player->SetGhostOverridePoint(position);

		EntityManager::Instance()->UpdateGhosting(player);
	}
}


void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bConfirmed{}; // this doesnt even do anything, thanks ND!
	bool countIsDefault{};
	int count = 1;
	LOT item;

	inStream->Read(bConfirmed);
	inStream->Read(countIsDefault);
	if (countIsDefault) inStream->Read(count);
	inStream->Read(item);

	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* player = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!player) return;

	auto* propertyVendorComponent = static_cast<PropertyVendorComponent*>(entity->GetComponent(COMPONENT_TYPE_PROPERTY_VENDOR));

	if (propertyVendorComponent != nullptr) {
		propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count);

		return;
	}

	const auto isCommendationVendor = entity->GetLOT() == 13806;

	VendorComponent* vend = static_cast<VendorComponent*>(entity->GetComponent(COMPONENT_TYPE_VENDOR));
	if (!vend && !isCommendationVendor) return;

	InventoryComponent* inv = static_cast<InventoryComponent*>(player->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
	CDItemComponentTable* itemComponentTable = CDClientManager::Instance()->GetTable<CDItemComponentTable>("ItemComponent");

	int itemCompID = compRegistryTable->GetByIDAndType(item, COMPONENT_TYPE_ITEM);
	CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);

	Character* character = player->GetCharacter();
	if (!character) return;

	// Extra currency that needs to be deducted in case of crafting
	auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp);
	for (const auto& craftingCurrency : craftingCurrencies) {
		inv->RemoveItem(craftingCurrency.first, craftingCurrency.second * count);
	}

	if (isCommendationVendor) {
		if (itemComp.commendationLOT != 13763) {
			return;
		}

		auto* missionComponent = player->GetComponent<MissionComponent>();

		if (missionComponent == nullptr) {
			return;
		}

		LOT tokenId = -1;

		if (missionComponent->GetMissionState(545) == MissionState::MISSION_STATE_COMPLETE) // "Join Assembly!"
		{
			tokenId = 8318; // "Assembly Token"
		}

		if (missionComponent->GetMissionState(556) == MissionState::MISSION_STATE_COMPLETE) // "Join Venture League!"
		{
			tokenId = 8321; // "Venture League Token"
		}

		if (missionComponent->GetMissionState(567) == MissionState::MISSION_STATE_COMPLETE) // "Join The Sentinels!"
		{
			tokenId = 8319; // "Sentinels Token"
		}

		if (missionComponent->GetMissionState(578) == MissionState::MISSION_STATE_COMPLETE) // "Join Paradox!"
		{
			tokenId = 8320; // "Paradox Token"
		}

		const uint32_t altCurrencyCost = itemComp.commendationCost * count;

		if (inv->GetLotCount(tokenId) < altCurrencyCost) {
			return;
		}

		inv->RemoveItem(tokenId, altCurrencyCost);

		inv->AddItem(item, count, eLootSourceType::LOOT_SOURCE_VENDOR);
	} else {
		float buyScalar = vend->GetBuyScalar();

		const auto coinCost = static_cast<uint32_t>(std::floor((itemComp.baseValue * buyScalar) * count));

		if (character->GetCoins() < coinCost) {
			return;
		}

		if (Inventory::IsValidItem(itemComp.currencyLOT)) {
			const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count;

			if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
				return;
			}

			inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
		}

		character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::LOOT_SOURCE_VENDOR);
		inv->AddItem(item, count, eLootSourceType::LOOT_SOURCE_VENDOR);
	}

	GameMessages::SendVendorTransactionResult(entity, sysAddr);
}

void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool countIsDefault{};
	int count = 1;
	LWOOBJID iObjID;

	inStream->Read(countIsDefault);
	if (countIsDefault) inStream->Read(count);
	inStream->Read(iObjID);

	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* player = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!player) return;
	Character* character = player->GetCharacter();
	if (!character) return;
	InventoryComponent* inv = static_cast<InventoryComponent*>(player->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	VendorComponent* vend = static_cast<VendorComponent*>(entity->GetComponent(COMPONENT_TYPE_VENDOR));
	if (!vend) return;

	Item* item = inv->FindItemById(iObjID);
	if (!item) return;

	CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
	CDItemComponentTable* itemComponentTable = CDClientManager::Instance()->GetTable<CDItemComponentTable>("ItemComponent");

	int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), COMPONENT_TYPE_ITEM);
	CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);

	// Items with a base value of 0 or max int are special items that should not be sold if they're not sub items
	if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) return;

	float sellScalar = vend->GetSellScalar();
	if (Inventory::IsValidItem(itemComp.currencyLOT)) {
		const auto altCurrency = (itemComp.altCurrencyCost * sellScalar) * count;
		inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::LOOT_SOURCE_VENDOR); // Return alt currencies like faction tokens.
	}

	//inv->RemoveItem(count, -1, iObjID);
	inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
	character->SetCoins(std::floor(character->GetCoins() + ((itemComp.baseValue * sellScalar) * count)), eLootSourceType::LOOT_SOURCE_VENDOR);
	//EntityManager::Instance()->SerializeEntity(player); // so inventory updates
	GameMessages::SendVendorTransactionResult(entity, sysAddr);
}

void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool confirmed = false;
	bool countIsDefault{};
	int count = 1;
	LWOOBJID iObjID;

	inStream->Read(confirmed);
	inStream->Read(countIsDefault);
	if (countIsDefault) inStream->Read(count);
	inStream->Read(iObjID);

	//if (!confirmed) return; they always built in this confirmed garbage... but never used it?

	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* player = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!player) return;
	Character* character = player->GetCharacter();
	if (!character) return;
	InventoryComponent* inv = static_cast<InventoryComponent*>(player->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	VendorComponent* vend = static_cast<VendorComponent*>(entity->GetComponent(COMPONENT_TYPE_VENDOR));
	if (!vend) return;

	Item* item = inv->FindItemById(iObjID);
	if (!item) return;

	CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
	CDItemComponentTable* itemComponentTable = CDClientManager::Instance()->GetTable<CDItemComponentTable>("ItemComponent");

	int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), COMPONENT_TYPE_ITEM);
	CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);

	float sellScalar = vend->GetSellScalar();

	const auto cost = static_cast<uint32_t>(std::floor(((itemComp.baseValue * sellScalar) * count)));

	if (character->GetCoins() < cost) {
		return;
	}

	if (Inventory::IsValidItem(itemComp.currencyLOT)) {
		const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * sellScalar) * count;

		if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
			return;
		}

		inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
	}

	//inv->RemoveItem(count, -1, iObjID);
	inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false);
	character->SetCoins(character->GetCoins() - cost, eLootSourceType::LOOT_SOURCE_VENDOR);
	//EntityManager::Instance()->SerializeEntity(player); // so inventory updates
	GameMessages::SendVendorTransactionResult(entity, sysAddr);
}

void GameMessages::HandleParseChatMessage(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	std::u16string wsString;
	int iClientState;
	inStream->Read(iClientState);

	uint32_t wsStringLength;
	inStream->Read(wsStringLength);
	for (uint32_t i = 0; i < wsStringLength; ++i) {
		uint16_t character;
		inStream->Read(character);
		wsString.push_back(character);
	}

	if (wsString[0] == L'/') {
		SlashCommandHandler::HandleChatCommand(wsString, entity, sysAddr);
	}
}

void GameMessages::HandleFireEventServerSide(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	uint32_t argsLength{};
	std::u16string args{};
	bool param1IsDefault{};
	int param1 = -1;
	bool param2IsDefault{};
	int param2 = -1;
	bool param3IsDefault{};
	int param3 = -1;
	LWOOBJID senderID{};

	inStream->Read(argsLength);
	for (uint32_t i = 0; i < argsLength; ++i) {
		uint16_t character;
		inStream->Read(character);
		args.push_back(character);
	}
	inStream->Read(param1IsDefault);
	if (param1IsDefault) inStream->Read(param1);
	inStream->Read(param2IsDefault);
	if (param2IsDefault) inStream->Read(param2);
	inStream->Read(param3IsDefault);
	if (param3IsDefault) inStream->Read(param3);
	inStream->Read(senderID);

	auto* sender = EntityManager::Instance()->GetEntity(senderID);
	auto* player = Player::GetPlayer(sysAddr);

	if (!player) {
		return;
	}

	// This should probably get it's own "ServerEvents" system or something at some point
	if (args == u"ZonePlayer") {
		// Should probably check to make sure they're using a launcher at some point before someone makes a hack that lets you testmap

		LWOCLONEID cloneId = 0;
		LWOMAPID mapId = 0;

		auto* rocketPad = entity->GetComponent<RocketLaunchpadControlComponent>();

		if (rocketPad == nullptr) return;

		cloneId = rocketPad->GetSelectedCloneId(player->GetObjectID());

		if (param2) {
			mapId = rocketPad->GetDefaultZone();
		} else {
			mapId = param3;
		}

		if (mapId == 0) {
			mapId = rocketPad->GetSelectedMapId(player->GetObjectID());
		}

		if (mapId == 0) {
			mapId = dZoneManager::Instance()->GetZoneID().GetMapID(); // Fallback to sending the player back to the same zone.
		}

		Game::logger->Log("FireEventServerSide", "Player %llu has requested zone transfer to (%i, %i).", sender->GetObjectID(), (int)mapId, (int)cloneId);

		auto* character = player->GetCharacter();

		if (mapId <= 0) {
			return;
		}

		ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, mapId, cloneId, false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
			Game::logger->Log("UserManager", "Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);

			if (character) {
				character->SetZoneID(zoneID);
				character->SetZoneInstance(zoneInstance);
				character->SetZoneClone(zoneClone);
			}

			WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift);
			return;
			});
	}

	entity->OnFireEventServerSide(sender, GeneralUtils::UTF16ToWTF8(args), param1, param2, param3);
}

void GameMessages::HandleRequestPlatformResync(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	if (entity->GetLOT() == 6267 || entity->GetLOT() == 16141) return;
	GameMessages::SendPlatformResync(entity, sysAddr);
}

void GameMessages::HandleRebuildCancel(RakNet::BitStream* inStream, Entity* entity) {
	bool bEarlyRelease;
	LWOOBJID userID;

	inStream->Read(bEarlyRelease);
	inStream->Read(userID);

	RebuildComponent* rebComp = static_cast<RebuildComponent*>(entity->GetComponent(COMPONENT_TYPE_REBUILD));
	if (!rebComp) return;

	rebComp->CancelRebuild(EntityManager::Instance()->GetEntity(userID), eFailReason::REASON_CANCELED_EARLY);
}

void GameMessages::HandleRequestUse(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	bool bIsMultiInteractUse = false;
	unsigned int multiInteractID;
	int multiInteractType;
	bool secondary;
	LWOOBJID objectID;

	inStream->Read(bIsMultiInteractUse);
	inStream->Read(multiInteractID);
	inStream->Read(multiInteractType);
	inStream->Read(objectID);
	inStream->Read(secondary);

	Entity* interactedObject = EntityManager::Instance()->GetEntity(objectID);

	if (interactedObject == nullptr) {
		Game::logger->Log("GameMessages", "Object %llu tried to interact, but doesn't exist!", objectID);

		return;
	}

	if (interactedObject->GetLOT() == 9524) {
		entity->GetCharacter()->SetBuildMode(true);
	}

	if (bIsMultiInteractUse) {
		if (multiInteractType == 0) {
			auto* missionOfferComponent = static_cast<MissionOfferComponent*>(interactedObject->GetComponent(COMPONENT_TYPE_MISSION_OFFER));

			if (missionOfferComponent != nullptr) {
				missionOfferComponent->OfferMissions(entity, multiInteractID);
			}
		} else {
			interactedObject->OnUse(entity);
		}
	} else {
		interactedObject->OnUse(entity);
	}

	//Perform use task if possible:
	auto missionComponent = static_cast<MissionComponent*>(entity->GetComponent(COMPONENT_TYPE_MISSION));

	if (missionComponent == nullptr) return;

	missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_MISSION_INTERACTION, interactedObject->GetLOT(), interactedObject->GetObjectID());
	missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_NON_MISSION_INTERACTION, interactedObject->GetLOT(), interactedObject->GetObjectID());
}

void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) {
	int emoteID;
	LWOOBJID targetID;

	inStream->Read(emoteID);
	inStream->Read(targetID);

	Game::logger->Log("GameMessages", "Emote (%i) (%llu)", emoteID, targetID);

	//TODO: If targetID != 0, and we have one of the "perform emote" missions, complete them.

	if (emoteID == 0) return;
	std::string sAnimationName = "deaded"; //Default name in case we fail to get the emote

	MissionComponent* mission = static_cast<MissionComponent*>(entity->GetComponent(COMPONENT_TYPE_MISSION));
	if (mission) {
		mission->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, targetID);
	}

	if (targetID != LWOOBJID_EMPTY) {
		auto* targetEntity = EntityManager::Instance()->GetEntity(targetID);

		Game::logger->Log("GameMessages", "Emote target found (%d)", targetEntity != nullptr);

		if (targetEntity != nullptr) {
			targetEntity->OnEmoteReceived(emoteID, entity);
		}
	} else {
		const auto scriptedEntities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPT);

		const auto& referencePoint = entity->GetPosition();

		for (auto* scripted : scriptedEntities) {
			if (Vector3::DistanceSquared(scripted->GetPosition(), referencePoint) > 5.0f * 5.0f) continue;

			scripted->OnEmoteReceived(emoteID, entity);
		}
	}

	CDEmoteTableTable* emotes = CDClientManager::Instance()->GetTable<CDEmoteTableTable>("EmoteTable");
	if (emotes) {
		CDEmoteTable* emote = emotes->GetEmote(emoteID);
		if (emote) sAnimationName = emote->animationName;
	}

	GameMessages::SendPlayAnimation(entity, GeneralUtils::ASCIIToUTF16(sAnimationName));
}

void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	LWOOBJID modelID;

	inStream->Read(modelID);

	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* character = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!character) return;
	InventoryComponent* inv = static_cast<InventoryComponent*>(character->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	auto* item = inv->FindItemById(modelID);

	if (item == nullptr) {
		return;
	}

	item->Disassemble(TEMP_MODELS);

	item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::LOOT_SOURCE_QUICKBUILD);
}

void GameMessages::HandleSetFlag(RakNet::BitStream* inStream, Entity* entity) {
	bool bFlag{};
	int iFlagID{};

	inStream->Read(bFlag);
	inStream->Read(iFlagID);

	auto user = entity->GetParentUser();
	if (user) {
		auto character = user->GetLastUsedChar();
		if (!character) return;

		character->SetPlayerFlag(iFlagID, bFlag);
	}
}

void GameMessages::HandleRespondToMission(RakNet::BitStream* inStream, Entity* entity) {
	int missionID{};
	LWOOBJID playerID{};
	LWOOBJID receiverID{};
	bool isDefaultReward{};
	LOT reward = LOT_NULL;

	inStream->Read(missionID);
	inStream->Read(playerID);
	inStream->Read(receiverID);
	inStream->Read(isDefaultReward);
	if (isDefaultReward) inStream->Read(reward);

	MissionComponent* missionComponent = static_cast<MissionComponent*>(entity->GetComponent(COMPONENT_TYPE_MISSION));
	if (!missionComponent) {
		Game::logger->Log("GameMessages", "Unable to get mission component for entity %llu to handle RespondToMission", playerID);
		return;
	}

	Mission* mission = missionComponent->GetMission(missionID);
	if (mission) {
		mission->SetReward(reward);
	} else {
		Game::logger->Log("GameMessages", "Unable to get mission %i for entity %llu to update reward in RespondToMission", missionID, playerID);
	}

	Entity* offerer = EntityManager::Instance()->GetEntity(receiverID);

	if (offerer == nullptr) {
		Game::logger->Log("GameMessages", "Unable to get receiver entity %llu for RespondToMission", receiverID);
		return;
	}

	for (CppScripts::Script* script : CppScripts::GetEntityScripts(offerer)) {
		script->OnRespondToMission(offerer, missionID, EntityManager::Instance()->GetEntity(playerID), reward);
	}
}

void GameMessages::HandleMissionDialogOK(RakNet::BitStream* inStream, Entity* entity) {
	bool bIsComplete{};
	MissionState iMissionState{};
	int missionID{};
	LWOOBJID responder{};
	Entity* player = nullptr;

	inStream->Read(bIsComplete);
	inStream->Read(iMissionState);
	inStream->Read(missionID);
	inStream->Read(responder);
	player = EntityManager::Instance()->GetEntity(responder);

	for (CppScripts::Script* script : CppScripts::GetEntityScripts(entity)) {
		script->OnMissionDialogueOK(entity, player, missionID, iMissionState);
	}

	// Get the player's mission component
	MissionComponent* missionComponent = static_cast<MissionComponent*>(player->GetComponent(COMPONENT_TYPE_MISSION));
	if (!missionComponent) {
		Game::logger->Log("GameMessages", "Unable to get mission component for entity %llu to handle MissionDialogueOK", player->GetObjectID());
		return;
	}

	if (iMissionState == MissionState::MISSION_STATE_AVAILABLE || iMissionState == MissionState::MISSION_STATE_COMPLETE_AVAILABLE) {
		missionComponent->AcceptMission(missionID);
	} else if (iMissionState == MissionState::MISSION_STATE_READY_TO_COMPLETE || iMissionState == MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE) {
		missionComponent->CompleteMission(missionID);
	}
}

void GameMessages::HandleRequestLinkedMission(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID playerId{};
	int missionId{};
	bool bMissionOffered{};

	inStream->Read(playerId);
	inStream->Read(missionId);
	inStream->Read(bMissionOffered);

	auto* player = EntityManager::Instance()->GetEntity(playerId);

	auto* missionOfferComponent = static_cast<MissionOfferComponent*>(entity->GetComponent(COMPONENT_TYPE_MISSION_OFFER));

	if (missionOfferComponent != nullptr) {
		missionOfferComponent->OfferMissions(player, 0);
	}
}

void GameMessages::HandleHasBeenCollected(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID playerID;
	inStream->Read(playerID);

	Entity* player = EntityManager::Instance()->GetEntity(playerID);
	if (!player || !entity || entity->GetCollectibleID() == 0) return;

	MissionComponent* missionComponent = static_cast<MissionComponent*>(player->GetComponent(COMPONENT_TYPE_MISSION));
	if (missionComponent) {
		missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ENVIRONMENT, entity->GetLOT(), entity->GetObjectID());
	}
}

void GameMessages::HandleNotifyServerLevelProcessingComplete(RakNet::BitStream* inStream, Entity* entity) {
	auto* levelComp = entity->GetComponent<LevelProgressionComponent>();
	if (!levelComp) return;
	auto* character = entity->GetComponent<CharacterComponent>();
	if (!character) return;

	//Update our character's level in memory:
	levelComp->SetLevel(levelComp->GetLevel() + 1);

	levelComp->HandleLevelUp();

	auto* inventoryComponent = entity->GetComponent<InventoryComponent>();

	if (inventoryComponent != nullptr) {
		auto* inventory = inventoryComponent->GetInventory(ITEMS);

		if (inventory != nullptr && Game::config->GetValue("disable_extra_backpack") != "1") {
			inventory->SetSize(inventory->GetSize() + 2);
		}
	}

	//Play the level up effect:
	GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true);

	//Send a notification in chat:
	std::stringstream wss;
	wss << "level=1:";
	wss << levelComp->GetLevel();
	wss << "\n";
	wss << "name=0:";
	wss << character->GetName();

	// FIXME: only really need utf8 conversion for the name, so move that up?
	std::u16string attrs = GeneralUtils::UTF8ToUTF16(wss.str());
	std::u16string wsText = u"UI_LEVEL_PROGRESSION_LEVELUP_MESSAGE";

	GameMessages::SendBroadcastTextToChatbox(entity, UNASSIGNED_SYSTEM_ADDRESS, attrs, wsText);
}

void GameMessages::HandlePickupCurrency(RakNet::BitStream* inStream, Entity* entity) {
	unsigned int currency;
	inStream->Read(currency);

	if (currency == 0) return;

	auto* ch = entity->GetCharacter();
	if (entity->CanPickupCoins(currency)) {
		ch->SetCoins(ch->GetCoins() + currency, eLootSourceType::LOOT_SOURCE_PICKUP);
	}
}

void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID killerID;
	LWOOBJID lootOwnerID;
	bool bDieAccepted = false;
	eKillType killType;
	std::u16string deathType;
	float directionRelative_AngleY;
	float directionRelative_AngleXZ;
	float directionRelative_Force;
	bool bClientDeath = false;
	bool bSpawnLoot = true;
	float coinSpawnTime = -1.0f;

	inStream->Read(bClientDeath);
	inStream->Read(bDieAccepted);
	inStream->Read(bSpawnLoot);

	bool coinSpawnTimeIsDefault{};
	inStream->Read(coinSpawnTimeIsDefault);
	if (coinSpawnTimeIsDefault != 0) inStream->Read(coinSpawnTime);

	/*uint32_t deathTypeLength = deathType.size();
	inStream->Read(deathTypeLength);
	for (uint32_t k = 0; k < deathTypeLength; k++) {
		inStream->Read(static_cast<uint16_t>(deathType[k]));
	}*/

	inStream->Read(directionRelative_AngleXZ);
	inStream->Read(directionRelative_AngleY);
	inStream->Read(directionRelative_Force);

	bool killTypeIsDefault{};
	inStream->Read(killTypeIsDefault);
	if (killTypeIsDefault != 0) inStream->Read(killType);

	inStream->Read(lootOwnerID);
	inStream->Read(killerID);
}

void GameMessages::HandleEquipItem(RakNet::BitStream* inStream, Entity* entity) {
	bool immediate;
	LWOOBJID objectID;
	inStream->Read(immediate);
	inStream->Read(immediate); //twice?
	inStream->Read(objectID);

	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	Item* item = inv->FindItemById(objectID);
	if (!item) return;

	item->Equip();

	EntityManager::Instance()->SerializeEntity(entity);
}

void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity) {
	bool immediate;
	LWOOBJID objectID;
	inStream->Read(immediate);
	inStream->Read(immediate);
	inStream->Read(immediate);
	inStream->Read(objectID);

	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	auto* item = inv->FindItemById(objectID);

	if (!item) return;

	item->UnEquip();

	EntityManager::Instance()->SerializeEntity(entity);
}

void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	// this is used for a lot more than just inventory trashing (trades, vendors, etc.) but for now since it's just used for that, that's all im going to implement
	bool bConfirmed = false;
	bool bDeleteItem = true;
	bool bOutSuccess = false;
	bool eInvTypeIsDefault = false;
	int eInvType = INVENTORY_MAX;
	bool eLootTypeSourceIsDefault = false;
	int eLootTypeSource = LOOTTYPE_NONE;
	LWONameValue extraInfo;
	bool forceDeletion = true;
	bool iLootTypeSourceIsDefault = false;
	LWOOBJID iLootTypeSource = LWOOBJID_EMPTY;
	bool iObjIDIsDefault = false;
	LWOOBJID iObjID;
	bool iObjTemplateIsDefault = false;
	LOT iObjTemplate = LOT_NULL;
	bool iRequestingObjIDIsDefault = false;
	LWOOBJID iRequestingObjID = LWOOBJID_EMPTY;
	bool iStackCountIsDefault = false;
	uint32_t iStackCount = 1;
	bool iStackRemainingIsDefault = false;
	uint32_t iStackRemaining = 0;
	bool iSubkeyIsDefault = false;
	LWOOBJID iSubkey = LWOOBJID_EMPTY;
	bool iTradeIDIsDefault = false;
	LWOOBJID iTradeID = LWOOBJID_EMPTY;

	inStream->Read(bConfirmed);
	inStream->Read(bDeleteItem);
	inStream->Read(bOutSuccess);
	inStream->Read(eInvTypeIsDefault);
	if (eInvTypeIsDefault) inStream->Read(eInvType);
	inStream->Read(eLootTypeSourceIsDefault);
	if (eLootTypeSourceIsDefault) inStream->Read(eLootTypeSource);
	inStream->Read(extraInfo.length);
	if (extraInfo.length > 0) {
		for (uint32_t i = 0; i < extraInfo.length; ++i) {
			uint16_t character;
			inStream->Read(character);
			extraInfo.name.push_back(character);
		}
		uint16_t nullTerm;
		inStream->Read(nullTerm);
	}
	inStream->Read(forceDeletion);
	inStream->Read(iLootTypeSourceIsDefault);
	if (iLootTypeSourceIsDefault) inStream->Read(iLootTypeSource);
	inStream->Read(iObjIDIsDefault);
	if (iObjIDIsDefault) inStream->Read(iObjID);
	inStream->Read(iObjTemplateIsDefault);
	if (iObjTemplateIsDefault) inStream->Read(iObjTemplate);
	inStream->Read(iRequestingObjIDIsDefault);
	if (iRequestingObjIDIsDefault) inStream->Read(iRequestingObjID);
	inStream->Read(iStackCountIsDefault);
	if (iStackCountIsDefault) inStream->Read(iStackCount);
	inStream->Read(iStackRemainingIsDefault);
	if (iStackRemainingIsDefault) inStream->Read(iStackRemaining);
	inStream->Read(iSubkeyIsDefault);
	if (iSubkeyIsDefault) inStream->Read(iSubkey);
	inStream->Read(iTradeIDIsDefault);
	if (iTradeIDIsDefault) inStream->Read(iTradeID);

	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	auto* item = inv->FindItemById(iObjID);

	if (item == nullptr) {
		return;
	}

	iStackCount = std::min<uint32_t>(item->GetCount(), iStackCount);

	if (bConfirmed) {
		for (auto i = 0; i < iStackCount; ++i) {
			if (eInvType == eInventoryType::MODELS) {
				item->DisassembleModel();
			}
		}

		item->SetCount(item->GetCount() - iStackCount, true);
		EntityManager::Instance()->SerializeEntity(entity);

		auto* missionComponent = entity->GetComponent<MissionComponent>();

		if (missionComponent != nullptr) {
			missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, item->GetLot(), LWOOBJID_EMPTY, "", -iStackCount);
		}
	}
}

void GameMessages::HandleMoveItemInInventory(RakNet::BitStream* inStream, Entity* entity) {
	bool destInvTypeIsDefault = false;
	int32_t destInvType = eInventoryType::INVALID;
	LWOOBJID iObjID;
	int inventoryType;
	int responseCode;
	int slot;
	inStream->Read(destInvTypeIsDefault);
	if (destInvTypeIsDefault) { inStream->Read(destInvType); }
	inStream->Read(iObjID);
	inStream->Read(inventoryType);
	inStream->Read(responseCode);
	inStream->Read(slot);

	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	auto* item = inv->FindItemById(iObjID);

	if (!item) {
		return;
	}

	inv->MoveStack(item, static_cast<eInventoryType>(destInvType), slot);
	EntityManager::Instance()->SerializeEntity(entity);
}

void GameMessages::HandleMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	eInventoryType inventoryTypeA;
	eInventoryType inventoryTypeB;
	LWOOBJID objectID;
	bool showFlyingLoot = true;
	bool stackCountIsDefault = false;
	uint32_t stackCount = 1;
	bool templateIDIsDefault = false;
	LOT templateID = LOT_NULL;

	inStream->Read(inventoryTypeA);
	inStream->Read(inventoryTypeB);
	inStream->Read(objectID);
	inStream->Read(showFlyingLoot);
	inStream->Read(stackCountIsDefault);
	if (stackCountIsDefault) inStream->Read(stackCount);
	inStream->Read(templateIDIsDefault);
	if (templateIDIsDefault) inStream->Read(templateID);

	auto inv = entity->GetComponent<InventoryComponent>();
	if (!inv) return;

	auto* item = inv->FindItemById(objectID);

	if (!item) {
		// Attempt to find the item by lot in inventory A since A is the source inventory.
		item = inv->FindItemByLot(templateID, static_cast<eInventoryType>(inventoryTypeA));
		if (!item) {
			// As a final resort, try to find the item in its default inventory based on type.
			item = inv->FindItemByLot(templateID);
			if (!item) {
				return;
			}
		}
	}

	if (entity->GetCharacter()) {
		if (entity->GetCharacter()->GetBuildMode()) {
			showFlyingLoot = false;
		}
	}

	inv->MoveItemToInventory(item, inventoryTypeB, stackCount, showFlyingLoot);
	EntityManager::Instance()->SerializeEntity(entity);
}

void GameMessages::HandleBuildModeSet(RakNet::BitStream* inStream, Entity* entity) {
	bool bStart = false;

	inStream->Read(bStart);
	// there's more here but we don't need it (for now?)

	Game::logger->Log("GameMessages", "Set build mode to (%d) for (%llu)", bStart, entity->GetObjectID());

	if (entity->GetCharacter()) {
		entity->GetCharacter()->SetBuildMode(bStart);
	}
}

void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* character = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!character) return;
	InventoryComponent* inv = static_cast<InventoryComponent*>(character->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	Game::logger->Log("GameMessages", "Build finished");

	GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build
	GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it

	//inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap
	//EntityManager::Instance()->SerializeEntity(entity);

	uint8_t count; // 3 for rockets, 7 for cars

	inStream->Read(count);

	auto* temp = inv->GetInventory(TEMP_MODELS);
	std::vector<LOT> modList;

	if (count >= 3) {
		std::u16string modules;

		for (uint32_t k = 0; k < count; k++) {
			uint32_t mod;
			inStream->Read(mod);
			modList.push_back(mod);
			modules += u"1:" + (GeneralUtils::to_u16string(mod));
			if (k + 1 != count) modules += u"+";

			if (temp->GetLotCount(mod) > 0) {
				inv->RemoveItem(mod, 1, TEMP_MODELS);
			} else {
				inv->RemoveItem(mod, 1);
			}
		}

		const auto moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", modules);

		std::vector<LDFBaseData*> config;
		config.push_back(moduleAssembly);

		if (count == 3) {
			inv->AddItem(6416, 1, eLootSourceType::LOOT_SOURCE_QUICKBUILD, eInventoryType::MODELS, config);
		} else if (count == 7) {
			inv->AddItem(8092, 1, eLootSourceType::LOOT_SOURCE_QUICKBUILD, eInventoryType::MODELS, config);
		}

		auto* missionComponent = character->GetComponent<MissionComponent>();

		if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) {
			if (missionComponent != nullptr) {
				missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_SCRIPT, entity->GetLOT(), entity->GetObjectID());
			}
		}
	}

	ScriptComponent* script = static_cast<ScriptComponent*>(entity->GetComponent(COMPONENT_TYPE_SCRIPT));

	for (CppScripts::Script* script : CppScripts::GetEntityScripts(entity)) {
		script->OnModularBuildExit(entity, character, count >= 3, modList);
	}

	// Move remaining temp models back to models
	std::vector<Item*> items;

	for (const auto& pair : temp->GetItems()) {
		items.push_back(pair.second);
	}

	for (auto* item : items) {
		inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false);
	}
}

void GameMessages::HandleDoneArrangingWithItem(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* character = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!character) return;
	InventoryComponent* inv = static_cast<InventoryComponent*>(character->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	/**
		[int] - newSourceBAG
		[LWOOBJID] - newSourceID
		[LOT] - newSourceLOT
		[int] - newSourceTYPE
		[LWOOBJID] - newTargetID
		[LOT] - newTargetLOT
		[int] - newTargetTYPE
		[NiPoint3] - newTtargetPOS
		[int] - oldItemBAG
		[LWOOBJID] - oldItemID
		[LOT] - oldItemLOT
		[int] - oldItemTYPE
	 */
	int newSourceBAG = 0;
	LWOOBJID newSourceID = 0;
	LOT newSourceLOT = 0;
	int newSourceTYPE = 0;
	LWOOBJID newTargetID = 0;
	LOT newTargetLOT = 0;
	int newTargetTYPE = 0;
	NiPoint3 newTargetPOS;
	int oldItemBAG = 0;
	LWOOBJID oldItemID = 0;
	LOT oldItemLOT = 0;
	int oldItemTYPE = 0;

	inStream->Read(newSourceBAG);
	inStream->Read(newSourceID);
	inStream->Read(newSourceLOT);
	inStream->Read(newSourceTYPE);
	inStream->Read(newTargetID);
	inStream->Read(newTargetLOT);
	inStream->Read(newTargetTYPE);
	inStream->Read(newTargetPOS);
	inStream->Read(oldItemBAG);
	inStream->Read(oldItemID);
	inStream->Read(oldItemLOT);
	inStream->Read(oldItemTYPE);

	/*
	Game::logger->Log("GameMessages",
		"\nnewSourceBAG: %d\nnewSourceID: %llu\nnewSourceLOT: %d\nnewSourceTYPE: %d\nnewTargetID: %llu\nnewTargetLOT: %d\nnewTargetTYPE: %d\nnewTargetPOS: %f, %f, %f\noldItemBAG: %d\noldItemID: %llu\noldItemLOT: %d\noldItemTYPE: %d",
		newSourceBAG, newSourceID, newSourceLOT, newSourceTYPE, newTargetID, newTargetLOT, newTargetTYPE, newTargetPOS.x, newTargetPOS.y, newTargetPOS.z, oldItemBAG, oldItemID, oldItemLOT, oldItemTYPE
	);
	*/

	if (PropertyManagementComponent::Instance() != nullptr) {
		const auto& buildAreas = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_BUILD_BORDER);

		const auto& entities = EntityManager::Instance()->GetEntitiesInGroup("PropertyPlaque");

		Entity* buildArea;

		if (!buildAreas.empty()) {
			buildArea = buildAreas[0];
		} else if (!entities.empty()) {
			buildArea = entities[0];

			Game::logger->Log("BuildBorderComponent", "Using PropertyPlaque");
		} else {
			Game::logger->Log("BuildBorderComponent", "No build area found");

			return;
		}

		Game::logger->Log("GameMessages", "Build area found: %llu", buildArea->GetObjectID());

		GameMessages::SendStartArrangingWithItem(
			character,
			character->GetSystemAddress(),
			false,
			buildArea->GetObjectID(),
			character->GetPosition(),
			newSourceBAG,
			newSourceID,
			newSourceLOT,
			newSourceTYPE,
			newTargetID,
			newTargetLOT,
			newTargetPOS,
			newTargetTYPE
		);
	}

	Game::logger->Log("GameMessages", "Done Arranging");

	//GenericInventory* models = inv->GetGenericInventory(MODELS);
	//GenericInventory* tempModels = inv->GetGenericInventory(TEMP_MODELS);

	auto* inventory = inv->GetInventory(TEMP_MODELS);

	std::vector<Item*> items;

	for (const auto& pair : inventory->GetItems()) {
		items.push_back(pair.second);
	}

	for (auto* item : items) {
		inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false, false);
	}
}

void GameMessages::HandleModularBuildMoveAndEquip(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	User* user = UserManager::Instance()->GetUser(sysAddr);
	if (!user) return;
	Entity* character = EntityManager::Instance()->GetEntity(user->GetLoggedInChar());
	if (!character) return;

	Game::logger->Log("GameMessages", "Build and move");

	LOT templateID;

	inStream->Read(templateID);

	InventoryComponent* inv = static_cast<InventoryComponent*>(character->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;

	auto* item = inv->FindItemByLot(templateID, TEMP_MODELS);

	if (item == nullptr) {
		return;
	}

	inv->MoveItemToInventory(item, eInventoryType::MODELS, 1, false, true);
}

void GameMessages::HandlePickupItem(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID lootObjectID;
	LWOOBJID playerID;
	inStream->Read(lootObjectID);
	inStream->Read(playerID);

	entity->PickupItem(lootObjectID);

	auto* team = TeamManager::Instance()->GetTeam(entity->GetObjectID());

	if (team != nullptr) {
		for (const auto memberId : team->members) {
			auto* member = EntityManager::Instance()->GetEntity(memberId);

			if (member == nullptr || memberId == playerID) continue;

			SendTeamPickupItem(lootObjectID, lootObjectID, playerID, member->GetSystemAddress());
		}
	}
}

void GameMessages::HandleResurrect(RakNet::BitStream* inStream, Entity* entity) {
	bool immediate = inStream->ReadBit();

	Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
	for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
		script->OnPlayerResurrected(zoneControl, entity);
	}

	std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY);
	for (Entity* scriptEntity : scriptedActs) {
		if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
			for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
				script->OnPlayerResurrected(scriptEntity, entity);
			}
		}
	}
}

void GameMessages::HandlePushEquippedItemsState(RakNet::BitStream* inStream, Entity* entity) {
	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;
	inv->PushEquippedItems();
}

void GameMessages::HandlePopEquippedItemsState(RakNet::BitStream* inStream, Entity* entity) {
	InventoryComponent* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
	if (!inv) return;
	inv->PopEquippedItems();
	EntityManager::Instance()->SerializeEntity(entity); // so it updates on client side
}


void GameMessages::HandleClientItemConsumed(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID itemConsumed;

	inStream->Read(itemConsumed);

	auto* inventory = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));

	if (inventory == nullptr) {
		return;
	}

	auto* item = inventory->FindItemById(itemConsumed);

	if (item == nullptr) {
		return;
	}

	item->Consume();

	auto* missions = static_cast<MissionComponent*>(entity->GetComponent(COMPONENT_TYPE_MISSION));
	if (missions != nullptr) {
		missions->Progress(MissionTaskType::MISSION_TASK_TYPE_FOOD, item->GetLot());
	}
}


void GameMessages::HandleUseNonEquipmentItem(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID itemConsumed;

	inStream->Read(itemConsumed);

	auto* inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));

	if (!inv) return;

	auto* item = inv->FindItemById(itemConsumed);

	if (item == nullptr) {
		return;
	}

	item->UseNonEquip();
}

void GameMessages::HandleMatchRequest(RakNet::BitStream* inStream, Entity* entity) {
	LWOOBJID activator;
	//std::map<LWOOBJID, LWONameValue> additionalPlayers;
	uint32_t playerChoicesLen;
	std::string playerChoices;
	int type;
	int value;

	inStream->Read(activator);
	inStream->Read(playerChoicesLen);
	for (uint32_t i = 0; i < playerChoicesLen; ++i) {
		uint16_t character;
		inStream->Read(character);
		playerChoices.push_back(character);
	}
	if (playerChoicesLen > 0) {
		uint16_t nullTerm;
		inStream->Read(nullTerm);
	}
	inStream->Read(type);
	inStream->Read(value);

	std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY);
	if (type == 0) { // join
		if (value != 0) {
			for (Entity* scriptedAct : scriptedActs) {
				ScriptedActivityComponent* comp = static_cast<ScriptedActivityComponent*>(scriptedAct->GetComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY));
				if (!comp) continue;
				if (comp->GetActivityID() == value) {
					comp->PlayerJoin(entity);
				}
			}
		} else {

		}
	} else if (type == 1) { // ready/unready
		for (Entity* scriptedAct : scriptedActs) {
			ScriptedActivityComponent* comp = static_cast<ScriptedActivityComponent*>(scriptedAct->GetComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY));
			if (!comp) continue;
			if (comp->PlayerIsInQueue(entity)) {
				comp->PlayerReady(entity, value);
			}
		}
	}
}

void GameMessages::HandleGetHotPropertyData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	SendGetHotPropertyData(inStream, entity, sysAddr);
}

void GameMessages::SendGetHotPropertyData(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	CBITSTREAM;
	CMSGHEADER;
	/**
	 * [u32] - Number of properties
	 *     [objid] - property id
	 *     [objid] - property owner id
	 *     [wstring] - property owner name
	 *     [u64] - total reputation
	 *     [i32] - property template id
	 *     [wstring] - property name
	 *     [wstring] - property description
	 *     [float] - performance cost
	 *     [timestamp] - time last published
	 *     [cloneid] - clone id
	 *
	 */
	 // TODO This needs to be implemented when reputation is implemented for getting hot properties.
	 /**
	 bitStream.Write(entity->GetObjectID());
	 bitStream.Write(GAME_MSG::GAME_MSG_SEND_HOT_PROPERTY_DATA);
	 std::vector<int32_t> t = {25166, 25188, 25191, 25194};
	 bitStream.Write<uint32_t>(4);
	 for (uint8_t i = 0; i < 4; i++) {
		 bitStream.Write<LWOOBJID>(entity->GetObjectID());
		 bitStream.Write<LWOOBJID>(entity->GetObjectID());
		 bitStream.Write<uint32_t>(1);
		 bitStream.Write<uint16_t>('c');
		 bitStream.Write<uint64_t>(42069);
		 bitStream.Write<int32_t>(t[i]);
		 bitStream.Write<uint32_t>(1);
		 bitStream.Write<uint16_t>('c');
		 bitStream.Write<uint32_t>(1);
		 bitStream.Write<uint16_t>('c');
		 bitStream.Write<float>(420.69f);
		 bitStream.Write<uint64_t>(1658376385);
		 bitStream.Write<int32_t>(25166);
	 }
	 SEND_PACKET*/
}

void GameMessages::HandleReportBug(RakNet::BitStream* inStream, Entity* entity) {
	//Definitely not stolen from autogenerated code, no sir:
	std::u16string body;
	std::string clientVersion;
	std::string nOtherPlayerID;
	std::string selection;
	uint32_t messageLength;
	int32_t reporterID = 0;

	//Reading:
	inStream->Read(messageLength);

	for (uint32_t i = 0; i < (messageLength); ++i) {
		uint16_t character;
		inStream->Read(character);
		body.push_back(character);
	}

	auto character = entity->GetCharacter();
	if (character) reporterID = character->GetID();

	uint32_t clientVersionLength;
	inStream->Read(clientVersionLength);
	for (unsigned int k = 0; k < clientVersionLength; k++) {
		unsigned char character;
		inStream->Read(character);
		clientVersion.push_back(character);
	}

	uint32_t nOtherPlayerIDLength;
	inStream->Read(nOtherPlayerIDLength);
	for (unsigned int k = 0; k < nOtherPlayerIDLength; k++) {
		unsigned char character;
		inStream->Read(character);
		nOtherPlayerID.push_back(character);
	}
	// Convert other player id from LWOOBJID to the database id.
	uint32_t otherPlayer = LWOOBJID_EMPTY;
	if (nOtherPlayerID != "") otherPlayer = std::atoi(nOtherPlayerID.c_str());

	uint32_t selectionLength;
	inStream->Read(selectionLength);
	for (unsigned int k = 0; k < selectionLength; k++) {
		unsigned char character;
		inStream->Read(character);
		selection.push_back(character);
	}

	try {
		sql::PreparedStatement* insertBug = Database::CreatePreppedStmt("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)");
		insertBug->setString(1, GeneralUtils::UTF16ToWTF8(body));
		insertBug->setString(2, clientVersion);
		insertBug->setString(3, std::to_string(otherPlayer));
		insertBug->setString(4, selection);
		insertBug->setInt(5, reporterID);
		insertBug->execute();
		delete insertBug;
	} catch (sql::SQLException& e) {
		Game::logger->Log("HandleReportBug", "Couldn't save bug report! (%s)", e.what());
	}
}

void
GameMessages::HandleClientRailMovementReady(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	const auto possibleRails = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_RAIL_ACTIVATOR);
	for (const auto* possibleRail : possibleRails) {
		const auto* rail = possibleRail->GetComponent<RailActivatorComponent>();
		if (rail != nullptr) {
			rail->OnRailMovementReady(entity);
		}
	}
}

void GameMessages::HandleCancelRailMovement(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
	const auto immediate = inStream->ReadBit();

	const auto possibleRails = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_RAIL_ACTIVATOR);
	for (const auto* possibleRail : possibleRails) {
		auto* rail = possibleRail->GetComponent<RailActivatorComponent>();
		if (rail != nullptr) {
			rail->OnCancelRailMovement(entity);
		}
	}
}

void GameMessages::HandlePlayerRailArrivedNotification(RakNet::BitStream* inStream, Entity* entity,
	const SystemAddress& sysAddr) {
	uint32_t pathNameLength;
	inStream->Read(pathNameLength);

	std::u16string pathName;
	for (auto k = 0; k < pathNameLength; k++) {
		uint16_t c;
		inStream->Read(c);
		pathName.push_back(c);
	}

	int32_t waypointNumber;
	inStream->Read(waypointNumber);

	const auto possibleRails = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_RAIL_ACTIVATOR);
	for (auto* possibleRail : possibleRails) {
		for (CppScripts::Script* script : CppScripts::GetEntityScripts(possibleRail)) {
			script->OnPlayerRailArrived(possibleRail, entity, pathName, waypointNumber);
		}
	}
}

void GameMessages::HandleModifyPlayerZoneStatistic(RakNet::BitStream* inStream, Entity* entity) {
	const auto set = inStream->ReadBit();
	const auto statisticsName = GeneralUtils::ReadWString(inStream);

	int32_t value;
	if (inStream->ReadBit()) {
		inStream->Read<int32_t>(value);
	} else {
		value = 0;
	}

	LWOMAPID zone;
	if (inStream->ReadBit()) {
		inStream->Read<LWOMAPID>(zone);
	} else {
		zone = LWOMAPID_INVALID;
	}

	// Notify the character component that something's changed
	auto* characterComponent = entity->GetComponent<CharacterComponent>();
	if (characterComponent != nullptr) {
		characterComponent->HandleZoneStatisticsUpdate(zone, statisticsName, value);
	}
}

void GameMessages::HandleUpdatePlayerStatistic(RakNet::BitStream* inStream, Entity* entity) {
	int32_t updateID;
	inStream->Read<int32_t>(updateID);

	int64_t updateValue;
	if (inStream->ReadBit()) {
		inStream->Read<int64_t>(updateValue);
	} else {
		updateValue = 1;
	}

	auto* characterComponent = entity->GetComponent<CharacterComponent>();
	if (characterComponent != nullptr) {
		characterComponent->UpdatePlayerStatistic((StatisticID)updateID, (uint64_t)std::max(updateValue, int64_t(0)));
	}
}
