#include "common.h"

#include "main.h"

#include "Camera.h"
#include "Coronas.h"
#include "Darkel.h"
#include "Entity.h"
#include "Explosion.h"
#include "Font.h"
#include "Garages.h"
#include "General.h"
#include "ModelIndices.h"
#include "Object.h"
#include "Pad.h"
#include "Pickups.h"
#include "PlayerPed.h"
#include "Wanted.h"
#include "DMAudio.h"
#include "Fire.h"
#include "PointLights.h"
#include "Pools.h"
#ifdef FIX_BUGS
#include "Replay.h"
#endif
#include "Script.h"
#include "Shadows.h"
#include "SpecialFX.h"
#include "Sprite.h"
#include "Timer.h"
#include "WaterLevel.h"
#include "World.h"
#include "Hud.h"
#include "Messages.h"
#include "Streaming.h"

CPickup CPickups::aPickUps[NUMPICKUPS];
int16 CPickups::NumMessages;
int32 CPickups::aPickUpsCollected[NUMCOLLECTEDPICKUPS];
int16 CPickups::CollectedPickUpIndex;

int32 CPickups::PlayerOnWeaponPickup;
int32 CollectPickupBuffer;

// unused
bool CPickups::bPickUpcamActivated;
CVehicle *CPickups::pPlayerVehicle;
CVector CPickups::StaticCamCoors;
uint32 CPickups::StaticCamStartTime;

tPickupMessage CPickups::aMessages[NUMPICKUPMESSAGES];

uint16 AmmoForWeapon[WEAPONTYPE_TOTALWEAPONS + 1] = { 0,  1,  1,  1,   1,   1,   1,   1,   1,   1,  1,  1, 8,   8,   8,    8, 8,   68, 24,
													 32, 28, 20, 200, 120, 120, 120, 120, 120, 40, 28, 8, 300, 200, 1000, 1, 400, 36, 0 };

uint16 AmmoForWeapon_OnStreet[WEAPONTYPE_TOTALWEAPONS + 1] = { 0,  1,  1,  1,   1,  1,  1,  1,  1,  1,  1,  1, 4,   4,   4,   4, 4,   34, 12,
															  16, 14, 10, 100, 60, 60, 60, 60, 60, 20, 14, 4, 150, 100, 500, 1, 400, 36, 0 };

uint16 CostOfWeapon[WEAPONTYPE_TOTALWEAPONS + 3] = { 0,     10,    10,   10,   10,   10,    10,   10,    10,  10,  10,  10,   1000, 1000,
													1000,  500,   8000, 250,  400,  1200,  1250, 1250,  800, 800, 650, 1200, 5000, 400,
													10000, 10000, 8000, 8000, 8000, 10000, 1000, 11000, 500, 20,  10,  0 };

struct
{
	uint8 r,g,b;
	float unk;
} aPickupColors[] = {
	{ 128, 128, 128, 1.0f },
	{ 128, 128, 128, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 97, 194, 247, 1.0f },
	{ 27, 89, 130, 1.0f },
	{ 27, 89, 130, 1.0f },
	{ 27, 89, 130, 1.0f },
	{ 27, 89, 130, 1.0f },
	{ 27, 89, 130, 1.0f },
	{ 149, 194, 24, 1.0f },
	{ 149, 194, 24, 1.0f },
	{ 45, 155, 90, 1.0f },
	{ 45, 155, 90, 1.0f },
	{ 45, 155, 90, 1.0f },
	{ 255, 227, 79, 1.0f },
	{ 255, 227, 79, 1.0f },
	{ 255, 227, 79, 1.0f },
	{ 255, 227, 79, 1.0f },
	{ 254, 137, 0, 1.0f },
	{ 254, 137, 0, 1.0f },
	{ 249, 131, 215, 1.0f },
	{ 249, 131, 215, 1.0f },
	{ 164, 40, 178, 1.0f },
	{ 164, 40, 178, 1.0f },
	{ 164, 40, 178, 1.0f },
	{ 164, 40, 178, 1.0f },
	{ 69, 69, 69, 1.0f },
	{ 69, 69, 69, 1.0f },
	{ 69, 69, 69, 1.0f },
	{ 255, 100, 100, 1.0f },
	{ 128, 255, 128, 1.0f },
	{ 100, 100, 255, 1.0f },
	{ 255, 255, 100, 1.0f },
	{ 255, 100, 100, 1.0f },
	{ 100, 255, 100, 1.0f },
	{ 255, 255, 255, 1.0f }
};


void
ModifyStringLabelForControlSetting(char *str)
{
	int len = (int)strlen(str);
	if (len <= 2)
		return;

	if (str[len - 2] != '_')
		return;

	switch (CPad::GetPad(0)->Mode) {
		case 0:
		case 1:
			str[len - 1] = 'L';
			break;
		case 2:
			str[len - 1] = 'T';
			break;
		case 3:
			str[len - 1] = 'C';
			break;
		default:
			return;
	}
}

void
CPickup::ExtractAmmoFromPickup(CPlayerPed *player)
{
	eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex());
	
	if (m_eType == PICKUP_IN_SHOP || !CWeaponInfo::IsWeaponSlotAmmoMergeable(CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot))
		return;

	uint32 ammo = m_nQuantity;
	if (ammo == 0) {
		if (!m_bWasAmmoCollected)
			ammo = AmmoForWeapon_OnStreet[weaponType];
		else
			goto removeAmmo;
	}
	player->GrantAmmo(weaponType, ammo);
	DMAudio.PlayOneShot(player->m_audioEntityId, SOUND_WEAPON_RELOAD, weaponType); // BUG? weapon type as volume, wtf?
removeAmmo:
	m_nQuantity = 0;
	m_bWasAmmoCollected = true;
}

void
CPickup::Remove()
{
	GetRidOfObjects();
	m_bRemoved = true;
	m_eType = PICKUP_NONE;
}

CObject *
CPickup::GiveUsAPickUpObject(CObject **ppObject, CObject **ppExtraObject, int32 handle, int32 extraHandle)
{
	CObject *&object = *ppObject;
	CObject *&extraObject = *ppExtraObject;

	object = extraObject = nil;

	int32 modelId = -1;
	if (CModelInfo::GetModelInfo(m_eModelIndex)->GetModelType() == MITYPE_WEAPON) {
		CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(((CWeaponModelInfo*)CModelInfo::GetModelInfo(m_eModelIndex))->GetWeaponInfo());
		modelId = weaponInfo->m_nModelId;
		if (modelId == m_eModelIndex)
			modelId = weaponInfo->m_nModel2Id;
	}

	if (handle >= 0) {
		CPools::MakeSureSlotInObjectPoolIsEmpty(handle);
		if (extraHandle >= 0)
			CPools::MakeSureSlotInObjectPoolIsEmpty(extraHandle);
		if (object == nil)
			object = new(handle) CObject(m_eModelIndex, false);

		if (extraHandle >= 0 && modelId != -1 && extraObject == nil)
			extraObject = new(extraHandle) CObject(modelId, false);
	} else {
		object = new CObject(m_eModelIndex, false);
		if (modelId != -1)
			extraObject = new CObject(modelId, false);
	}

	if (object == nil) return nil;
	object->ObjectCreatedBy = MISSION_OBJECT;
	object->SetPosition(m_vecPos);
	object->SetOrientation(0.0f, 0.0f, -HALFPI);
	object->GetMatrix().UpdateRW();
	object->UpdateRwFrame();

	object->bAffectedByGravity = false;
	object->bExplosionProof = true;
	object->bUsesCollision = false;
	object->bIsPickup = true;
	object->bAmmoCollected = m_bWasAmmoCollected;
	object->bHasPreRenderEffects = true;

	if (extraObject) {
		extraObject->ObjectCreatedBy = MISSION_OBJECT;
		extraObject->SetPosition(m_vecPos);
		extraObject->SetOrientation(0.0f, 0.0f, -HALFPI);
		extraObject->GetMatrix().UpdateRW();
		extraObject->UpdateRwFrame();

		extraObject->bAffectedByGravity = false;
		extraObject->bExplosionProof = true;
		extraObject->bUsesCollision = false;
		extraObject->bIsPickup = true;
		extraObject->bAmmoCollected = true;
		extraObject->bHasPreRenderEffects = true;
		extraObject->m_nBonusValue = 0;
		extraObject->bPickupObjWithMessage = false;
		extraObject->bOutOfStock = false;
	}

	object->m_nBonusValue = (m_eModelIndex == MI_PICKUP_BONUS || m_eModelIndex == MI_PICKUP_CLOTHES) ? m_nQuantity : 0;

	switch (m_eType)
	{
	case PICKUP_IN_SHOP:
		object->bPickupObjWithMessage = true;
		object->bOutOfStock = false;
		if (m_eModelIndex == MI_PICKUP_HEALTH || m_eModelIndex == MI_PICKUP_ADRENALINE)
			object->m_nCostValue = 0;
		else
			object->m_nCostValue = CostOfWeapon[CPickups::WeaponForModel(m_eModelIndex)];
		break;
	case PICKUP_ON_STREET:
	case PICKUP_ONCE:
	case PICKUP_ONCE_TIMEOUT:
	case PICKUP_COLLECTABLE1:
	case PICKUP_MONEY:
	case PICKUP_MINE_INACTIVE:
	case PICKUP_MINE_ARMED:
	case PICKUP_NAUTICAL_MINE_INACTIVE:
	case PICKUP_NAUTICAL_MINE_ARMED:
	case PICKUP_FLOATINGPACKAGE:
	case PICKUP_ON_STREET_SLOW:
		object->bPickupObjWithMessage = false;
		object->bOutOfStock = false;
		break;
	case PICKUP_IN_SHOP_OUT_OF_STOCK:
		object->bPickupObjWithMessage = false;
		object->bOutOfStock = true;
		object->bRenderScorched = true;
		break;
	case PICKUP_FLOATINGPACKAGE_FLOATING:
	default:
		break;
	}
	return object;
}

bool
CPickup::CanBePickedUp(CPlayerPed *player, int playerId)
{
	assert(m_pObject != nil);
	bool cannotBePickedUp =
		(m_pObject->GetModelIndex() == MI_PICKUP_BODYARMOUR && player->m_fArmour > CWorld::Players[playerId].m_nMaxArmour - 0.5f)
		|| (m_pObject->GetModelIndex() == MI_PICKUP_HEALTH && player->m_fHealth > CWorld::Players[playerId].m_nMaxHealth - 0.5f)
		|| (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE && player->m_pWanted->GetWantedLevel() == 0)
		|| (m_pObject->GetModelIndex() == MI_PICKUP_KILLFRENZY && (CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame));
	return !cannotBePickedUp;
}

bool
CPickup::Update(CPlayerPed *player, CVehicle *vehicle, int playerId)
{
	bool result = false;
	float waterLevel;

	if (m_pObject) {
		m_pObject->GetMatrix().GetPosition() = m_vecPos;
		if (m_pExtraObject)
			m_pExtraObject->GetMatrix().GetPosition() = m_vecPos;
	}
	if (m_eType == PICKUP_ASSET_REVENUE) {
		uint32 timePassed = CTimer::GetTimeInMilliseconds() - m_nTimer;
		m_nTimer = CTimer::GetTimeInMilliseconds();

		if (Distance(FindPlayerCoors(), m_vecPos) > 10.0f)
			m_fRevenue += float(timePassed * m_nMoneySpeed) / SQR(1200.0f);

		m_fRevenue = Min(m_fRevenue, m_nQuantity);

		m_pObject->m_nCostValue = m_fRevenue < 10 ? 0 : m_fRevenue;
	}

	if (m_bRemoved) {
		if (CTimer::GetTimeInMilliseconds() > m_nTimer) {
			// respawn pickup if we're far enough
			float dist = (FindPlayerCoors().x - m_vecPos.x) * (FindPlayerCoors().x - m_vecPos.x) + (FindPlayerCoors().y - m_vecPos.y) * (FindPlayerCoors().y - m_vecPos.y);
			if (dist > 100.0f || m_eType == PICKUP_IN_SHOP && dist > 2.4f) {
				m_pObject = GiveUsAPickUpObject(&m_pObject, &m_pExtraObject, -1, -1);
				if (m_pObject) {
					CWorld::Add(m_pObject);
					m_bRemoved = false;
				}
			}
		}
		return false;
	}

	if (!m_pObject) {
		GiveUsAPickUpObject(&m_pObject, &m_pExtraObject, -1, -1);
		if (m_pObject)
			CWorld::Add(m_pObject);
		if (m_pExtraObject)
			CWorld::Add(m_pExtraObject);
	}

	if (!m_pObject) return false;

	if (!IsMine()) {
		// let's check if we touched the pickup
		bool isPickupTouched = false;
		if (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE) {
			if (vehicle != nil) {
				if (vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f))
					isPickupTouched = true;
			}
			else {
				if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) {
					if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) +
						(player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f)
						isPickupTouched = true;
				}
			}
		} else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA) {
			if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) {
				isPickupTouched = true;
			}
		} else if (vehicle == nil) {
			if (Abs(player->GetPosition().z - m_pObject->GetPosition().z) < 2.0f) {
				if ((player->GetPosition().x - m_pObject->GetPosition().x) * (player->GetPosition().x - m_pObject->GetPosition().x) +
					(player->GetPosition().y - m_pObject->GetPosition().y) * (player->GetPosition().y - m_pObject->GetPosition().y) < 1.8f)
					isPickupTouched = true;
			}
		}

		if (isPickupTouched) {
			eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex());
			if (weaponType < WEAPONTYPE_TOTALWEAPONS && CDarkel::FrenzyOnGoing()) {
				isPickupTouched = false;
				m_bWasControlMessageShown = false;
			} else if (weaponType < WEAPONTYPE_TOTALWEAPONS && weaponType != WEAPONTYPE_UNARMED) {
				uint32 slot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot;
				eWeaponType plrWeaponSlot = FindPlayerPed()->GetWeapon(slot).m_eWeaponType;
				if (plrWeaponSlot != weaponType) {
					if (CStreaming::ms_aInfoForModel[m_pObject->GetModelIndex()].m_loadState == STREAMSTATE_LOADED) {
						if (plrWeaponSlot == WEAPONTYPE_UNARMED || (FindPlayerPed()->GetWeapon(slot).m_nAmmoTotal == 0 && !CWeaponInfo::IsWeaponSlotAmmoMergeable(slot))) {
							if (CTimer::GetTimeInMilliseconds() - FindPlayerPed()->m_nPadDownPressedInMilliseconds < 1500) {
								CPickups::PlayerOnWeaponPickup = 6;
								isPickupTouched = false;
							}
						} else {
							CPickups::PlayerOnWeaponPickup = 6;
							if (CWeaponInfo::IsWeaponSlotAmmoMergeable(slot)) {
								if (m_eType == PICKUP_ONCE_TIMEOUT || m_eType == PICKUP_ONCE || m_eType == PICKUP_ON_STREET) {
									ExtractAmmoFromPickup(player);
									FindPlayerPed()->GetWeapon(slot).Reload();
								}
							}
							if (!m_bWasControlMessageShown) {
								switch (CPad::GetPad(0)->Mode)
								{
								case 0:
								case 1:
									CHud::SetHelpMessage(TheText.Get("PU_CF1"), false);
									break;
								case 2:
									CHud::SetHelpMessage(TheText.Get("PU_CF3"), false);
									break;
								case 3:
									CHud::SetHelpMessage(TheText.Get("PU_CF4"), false);
									break;
								default:
									break;
								}
								m_bWasControlMessageShown = true;
							}
							if (CollectPickupBuffer == 0)
								isPickupTouched = false;
							if (CTimer::GetTimeInMilliseconds() - FindPlayerPed()->m_nPadDownPressedInMilliseconds < 1500)
								isPickupTouched = false;
						}
					} else
						isPickupTouched = false;
				}
			}
		} else
			m_bWasControlMessageShown = false;

		// if we didn't then we've got nothing to do
		if (isPickupTouched && CanBePickedUp(player, playerId)) {
			if (m_pObject->GetModelIndex() != MI_PICKUP_PROPERTY && m_pObject->GetModelIndex() != MI_PICKUP_PROPERTY_FORSALE)
				CPad::GetPad(0)->StartShake(120, 100);

			eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex());
			switch (m_eType)
			{
			case PICKUP_IN_SHOP:
				if (CWorld::Players[playerId].m_nMoney < CostOfWeapon[weaponType])
					CGarages::TriggerMessage("PU_MONY", -1, 6000, -1);
				else {
					CWorld::Players[playerId].m_nMoney -= CostOfWeapon[weaponType];
					if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) {
						if (!player->DoesPlayerWantNewWeapon(weaponType, false))
							break; 
						player->GiveWeapon(weaponType, AmmoForWeapon[weaponType]);
						player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType);
						DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON_BOUGHT, m_pObject->GetModelIndex() - MI_GRENADE);
					}
					result = true;
					Remove();
				}
				break;
			case PICKUP_ON_STREET:
			case PICKUP_ON_STREET_SLOW:
				if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) {
					if (!player->DoesPlayerWantNewWeapon(weaponType, false))
						break;
					if (weaponType != WEAPONTYPE_UNARMED) {
						player->GiveWeapon(weaponType, m_nQuantity != 0 ? m_nQuantity : (m_bWasAmmoCollected ? 0 : AmmoForWeapon_OnStreet[weaponType]), true);

						if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) 
							player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType);

						DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE);
					} else if (m_pObject->GetModelIndex() == MI_PICKUP_CAMERA && vehicle != nil) {
						DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0);
						CPickups::bPickUpcamActivated = true;
						CPickups::pPlayerVehicle = FindPlayerVehicle();
						CPickups::StaticCamCoors = m_pObject->GetPosition();
						CPickups::StaticCamStartTime = CTimer::GetTimeInMilliseconds();
					}
				}
				if (m_eType == PICKUP_ON_STREET)
					m_nTimer = CTimer::GetTimeInMilliseconds() + 30000;
				else if (m_eType == PICKUP_ON_STREET_SLOW) {
					if (MI_PICKUP_BRIBE == m_pObject->GetModelIndex())
						m_nTimer = CTimer::GetTimeInMilliseconds() + 300000;
					else
						m_nTimer = CTimer::GetTimeInMilliseconds() + 720000;
				}

				result = true;
				GetRidOfObjects();
				m_bRemoved = true;
				break;
			case PICKUP_ONCE:
			case PICKUP_ONCE_TIMEOUT:
			case PICKUP_ONCE_TIMEOUT_SLOW:
				if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) {
					if (!player->DoesPlayerWantNewWeapon(weaponType, false)) {
						ExtractAmmoFromPickup(player);
						break;
					}

					if (weaponType != WEAPONTYPE_UNARMED) {
						player->GiveWeapon(weaponType, m_nQuantity != 0 ? m_nQuantity : (m_bWasAmmoCollected ? 0 : AmmoForWeapon[weaponType]), true);
						if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED))
							player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType);
					}
					if (MI_PICKUP_SAVEGAME != m_pObject->GetModelIndex())
						DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON, m_pObject->GetModelIndex() - MI_GRENADE);
				}
				result = true;
				Remove();
				break;
			case PICKUP_COLLECTABLE1:
				CWorld::Players[playerId].m_nCollectedPackages++;
				CWorld::Players[playerId].m_nMoney += 100;

				if (CWorld::Players[playerId].m_nCollectedPackages == CWorld::Players[playerId].m_nTotalPackages) {
					printf("All collectables have been picked up\n");
					CGarages::TriggerMessage("CO_ALL", -1, 5000, -1);
					CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 100000;
				} else
					CGarages::TriggerMessage("CO_ONE", CWorld::Players[CWorld::PlayerInFocus].m_nCollectedPackages, 5000, CWorld::Players[CWorld::PlayerInFocus].m_nTotalPackages);

				result = true;
				Remove();
				DMAudio.PlayFrontEndSound(SOUND_PICKUP_HIDDEN_PACKAGE, 0);
				break;
			case PICKUP_MONEY:
				CWorld::Players[playerId].m_nMoney += m_nQuantity;
				sprintf(gString, "$%d", m_nQuantity);
#ifdef MONEY_MESSAGES
				CMoneyMessages::RegisterOne(m_vecPos + CVector(0.0f, 0.0f, 1.0f), gString, 0, 255, 0, 0.5f, 0.5f);
#endif
				result = true;
				Remove();
				DMAudio.PlayFrontEndSound(SOUND_PICKUP_MONEY, 0);
				player->Say(SOUND_PED_MUGGING);
				break;
			case PICKUP_ASSET_REVENUE:
				CWorld::Players[CWorld::PlayerInFocus].m_nMoney += m_fRevenue;
				m_fRevenue = 0.0f;
				DMAudio.PlayFrontEndSound(SOUND_PICKUP_MONEY, 0);
				break;
			case PICKUP_PROPERTY_LOCKED:
				if (!m_bWasControlMessageShown) {
					m_bWasControlMessageShown = true;
					CHud::SetHelpMessage(TheText.Get(m_sTextKey), false);
				}
				break;
			case PICKUP_PROPERTY_FORSALE:
				ModifyStringLabelForControlSetting(m_sTextKey);
				CMessages::InsertNumberInString(TheText.Get(m_sTextKey), m_nQuantity,
					0, 0, 0, 0, 0, gUString);
				if (!CHud::IsHelpMessageBeingDisplayed())
					CHud::SetHelpMessage(gUString, false);
				if (CollectPickupBuffer == 0)
					break;
				if (CTheScripts::IsPlayerOnAMission())
					CHud::SetHelpMessage(TheText.Get("PROP_2"), true);
				else {
					if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= m_nQuantity) {
						CWorld::Players[CWorld::PlayerInFocus].m_nMoney -= m_nQuantity;
						CHud::SetHelpMessage(nil, true);
						result = true;
						Remove();
						break;
					}
					CHud::SetHelpMessage(TheText.Get("PROP_1"), true);
				}
				break;
			default:
				break;
			}
		}
	} else {
		switch (m_eType)
		{
		case PICKUP_MINE_INACTIVE:
			if (vehicle != nil && !vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) {
				m_eType = PICKUP_MINE_ARMED;
				m_nTimer = CTimer::GetTimeInMilliseconds() + 10000;
			}
			break;
		case PICKUP_NAUTICAL_MINE_INACTIVE:
		{
			if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false))
				m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f;

			m_pObject->GetMatrix().UpdateRW();
			m_pObject->UpdateRwFrame();

			bool touched = false;
			for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) {
				CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i);
				if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) {
					touched = true;
#ifdef FIX_BUGS
					break; // added break here
#endif
				}
			}

			if (!touched) {
				m_eType = PICKUP_NAUTICAL_MINE_ARMED;
				m_nTimer = CTimer::GetTimeInMilliseconds() + 10000;
			}
			break;
		}
		case PICKUP_NAUTICAL_MINE_ARMED:
			if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false))
				m_pObject->GetMatrix().GetPosition().z = waterLevel + 0.6f;

			m_pObject->GetMatrix().UpdateRW();
			m_pObject->UpdateRwFrame();
			// no break here
		case PICKUP_MINE_ARMED:
		{
			bool explode = false;
			if (CTimer::GetTimeInMilliseconds() > m_nTimer)
				explode = true;
#ifdef FIX_BUGS
			else// added else here since vehicle lookup is useless
#endif
			{
				for (int32 i = CPools::GetVehiclePool()->GetSize()-1; i >= 0; i--) {
					CVehicle *vehicle = CPools::GetVehiclePool()->GetSlot(i);
					if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 1.5f)) {
						explode = true;
#ifdef FIX_BUGS
						break; // added break here
#endif
					}
				}
			}
			if (explode) {
				CExplosion::AddExplosion(nil, nil, EXPLOSION_MINE, m_pObject->GetPosition(), 0);
				Remove();
			}
			break;
		}
		case PICKUP_FLOATINGPACKAGE:
			m_pObject->m_vecMoveSpeed.z -= 0.01f * CTimer::GetTimeStep();
			m_pObject->GetMatrix().GetPosition() += m_pObject->GetMoveSpeed() * CTimer::GetTimeStep();

			m_pObject->GetMatrix().UpdateRW();
			m_pObject->UpdateRwFrame();
			if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false) && waterLevel >= m_pObject->GetPosition().z)
				m_eType = PICKUP_FLOATINGPACKAGE_FLOATING;
			break;
		case PICKUP_FLOATINGPACKAGE_FLOATING:
			if (CWaterLevel::GetWaterLevel(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z + 5.0f, &waterLevel, false))
				m_pObject->GetMatrix().GetPosition().z = waterLevel;

			m_pObject->GetMatrix().UpdateRW();
			m_pObject->UpdateRwFrame();
			if (vehicle != nil && vehicle->IsSphereTouchingVehicle(m_pObject->GetPosition().x, m_pObject->GetPosition().y, m_pObject->GetPosition().z, 2.0f)) {
				Remove();
				result = true;
				DMAudio.PlayFrontEndSound(SOUND_PICKUP_FLOAT_PACKAGE, 0);
			}
			break;
		default: break;
		}
	}

	if (!m_bRemoved && (m_eType == PICKUP_ONCE_TIMEOUT || m_eType == PICKUP_ONCE_TIMEOUT_SLOW || m_eType == PICKUP_MONEY) && CTimer::GetTimeInMilliseconds() > m_nTimer)
		Remove();
	
	return result;
}

void
CPickup::ProcessGunShot(CVector *vec1, CVector *vec2)
{
	CColLine line(*vec1, *vec2);
	if (m_pObject) {
		CColSphere sphere;
		sphere.radius = 4.0f;
		sphere.center = m_pObject->GetPosition();
		if (CCollision::TestLineSphere(line, sphere)) {
			CExplosion::AddExplosion(nil, nil, EXPLOSION_MINE, m_pObject->GetPosition(), 0);
			CWorld::Remove(m_pObject);
			delete m_pObject;
			m_pObject = nil;
			m_bRemoved = true;
			m_eType = PICKUP_NONE;
		}
	}
}

void
CPickup::GetRidOfObjects()
{
	if (m_pObject) {
		CWorld::Remove(m_pObject);
		delete m_pObject;
		m_pObject = nil;
	}
	if (m_pExtraObject) {
		CWorld::Remove(m_pExtraObject);
		delete m_pExtraObject;
		m_pExtraObject = nil;
	}
}

void
CPickups::Init(void)
{
	NumMessages = 0;
	for (int i = 0; i < NUMPICKUPS; i++) {
		aPickUps[i].m_eType = PICKUP_NONE;
		aPickUps[i].m_nIndex = 1;
		aPickUps[i].m_pObject = nil;
		aPickUps[i].m_pExtraObject = nil;
	}

	for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++)
		aPickUpsCollected[i] = 0;

	CollectedPickUpIndex = 0;
}

bool
CPickups::TestForPickupsInBubble(CVector pos, float range)
{
	for (int i = 0; i < NUMPICKUPS; i++) {
		if ((aPickUps[i].m_vecPos - pos).Magnitude() < range)
			return true;
	}
	return false;
}

bool
CPickups::TryToMerge_WeaponType(CVector pos, eWeaponType weapon, uint8 type, uint32 quantity, bool unused) {
	for (int i = 0; i < NUMPICKUPS; i++) {
		if (aPickUps[i].m_eType == type && aPickUps[i].m_eModelIndex == ModelForWeapon(weapon))
			if ((aPickUps[i].m_vecPos - pos).Magnitude() < 7.5f) {
				aPickUps[i].m_nQuantity += quantity;
				return true;
			}
	}
	return false;
}

bool
CPickups::IsPickUpPickedUp(int32 pickupId)
{
	for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) {
		if (pickupId == aPickUpsCollected[i]) {
			aPickUpsCollected[i] = 0;
			return true;
		}
	}
	return false;
}

void
CPickups::PassTime(uint32 time)
{
	for (int i = 0; i < NUMPICKUPS; i++) {
		if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_eType != PICKUP_ASSET_REVENUE) {
			if (aPickUps[i].m_nTimer <= time)
				aPickUps[i].m_nTimer = 0;
			else
				aPickUps[i].m_nTimer -= time;
		}
	}
}

int32
CPickups::GetActualPickupIndex(int32 index)
{
	if (index == -1) return -1;

	// doesn't look nice
	if ((uint16)((index & 0xFFFF0000) >> 16) != aPickUps[(uint16)index].m_nIndex) return -1;
	return (uint16)index;
}

bool
CPickups::GivePlayerGoodiesWithPickUpMI(int16 modelIndex, int playerIndex)
{
	CPlayerPed *player;

	if (playerIndex <= 0) player = CWorld::Players[CWorld::PlayerInFocus].m_pPed;
	else player = CWorld::Players[playerIndex].m_pPed;

	if (modelIndex == MI_PICKUP_ADRENALINE) {
		player->m_bAdrenalineActive = true;
		player->m_nAdrenalineTime = CTimer::GetTimeInMilliseconds() + 20000;
		player->m_fCurrentStamina = player->m_fMaxStamina;
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_ADRENALINE, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_BODYARMOUR) {
		player->m_fArmour = CWorld::Players[playerIndex].m_nMaxArmour;
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_ARMOUR, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_INFO) {
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_HEALTH) {
		player->m_fHealth = CWorld::Players[playerIndex].m_nMaxHealth;
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_HEALTH, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_BONUS) {
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_BRIBE) {
		int32 level = Max(FindPlayerPed()->m_pWanted->GetWantedLevel() - 1, 0);
		player->SetWantedLevel(level);
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0);
		return true;
	} else if (modelIndex == MI_PICKUP_KILLFRENZY) {
		DMAudio.PlayFrontEndSound(SOUND_PICKUP_BONUS, 0);
		return true;
	}
	return false;
}

void
CPickups::RemovePickUp(int32 pickupIndex)
{
	int32 index = GetActualPickupIndex(pickupIndex);
	if (index == -1) return;

	if (aPickUps[index].m_pObject) {
		CWorld::Remove(aPickUps[index].m_pObject);
		delete aPickUps[index].m_pObject;
		aPickUps[index].m_pObject = nil;
	}
	if (aPickUps[index].m_pExtraObject) {
		CWorld::Remove(aPickUps[index].m_pExtraObject);
		delete aPickUps[index].m_pExtraObject;
		aPickUps[index].m_pExtraObject = nil;
	}
	aPickUps[index].m_eType = PICKUP_NONE;
	aPickUps[index].m_bRemoved = true;
}

int32
CPickups::GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity, uint32 rate, bool highPriority, char* pText)
{
	bool bFreeFound = false;
	int32 slot = 0;

	if (type == PICKUP_FLOATINGPACKAGE || type == PICKUP_NAUTICAL_MINE_INACTIVE || highPriority) {
		for (slot = NUMPICKUPS-1; slot >= 0; slot--) {
			if (aPickUps[slot].m_eType == PICKUP_NONE) {
				bFreeFound = true;
				break;
			}
		}
	}
	if (!bFreeFound) {
		for (slot = 0; slot < NUMGENERALPICKUPS; slot++) {
			if (aPickUps[slot].m_eType == PICKUP_NONE) {
				bFreeFound = true;
				break;
			}
		}
	}

	if (!bFreeFound) {
		for (slot = 0; slot < NUMGENERALPICKUPS; slot++) {
			if (aPickUps[slot].m_eType == PICKUP_MONEY) break;
		}

		if (slot >= NUMGENERALPICKUPS) {
			for (slot = 0; slot < NUMGENERALPICKUPS; slot++) {
				if (aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT_SLOW) break;
			}

			if (slot >= NUMGENERALPICKUPS) return -1;
			aPickUps[slot].GetRidOfObjects();
		}
	}

	if (slot >= NUMPICKUPS) return -1;

	aPickUps[slot].m_eType = type;
	aPickUps[slot].m_bRemoved = false;
	aPickUps[slot].m_nQuantity = quantity;
	aPickUps[slot].m_nMoneySpeed = rate;
	aPickUps[slot].m_fRevenue = 0.0f;
	aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds();
	aPickUps[slot].m_bWasAmmoCollected = highPriority;
	aPickUps[slot].m_bWasControlMessageShown = false;
	if (type == PICKUP_ONCE_TIMEOUT)
		aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 20000;
	else if (type == PICKUP_ONCE_TIMEOUT_SLOW)
		aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 120000;
	else if (type == PICKUP_MONEY)
		aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 30000;
	else if (type == PICKUP_MINE_INACTIVE || type == PICKUP_MINE_ARMED) {
		aPickUps[slot].m_eType = PICKUP_MINE_INACTIVE;
		aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500;
	} else if (type == PICKUP_NAUTICAL_MINE_INACTIVE || type == PICKUP_NAUTICAL_MINE_ARMED) {
		aPickUps[slot].m_eType = PICKUP_NAUTICAL_MINE_INACTIVE;
		aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 1500;
	}
	aPickUps[slot].m_eModelIndex = modelIndex;
	if (pText)
		strncpy(aPickUps[slot].m_sTextKey, pText, 8);
	else
		aPickUps[slot].m_sTextKey[0] = '\0';

	aPickUps[slot].m_vecPos = pos;
	aPickUps[slot].m_pObject = aPickUps[slot].GiveUsAPickUpObject(&aPickUps[slot].m_pObject, &aPickUps[slot].m_pExtraObject, -1, -1);
	if (aPickUps[slot].m_pObject)
		CWorld::Add(aPickUps[slot].m_pObject);
	if (aPickUps[slot].m_pExtraObject)
		CWorld::Add(aPickUps[slot].m_pExtraObject);
	return GetNewUniquePickupIndex(slot);
}

int32
CPickups::GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity)
{
	return GenerateNewOne(pos, ModelForWeapon(weaponType), type, quantity);
}

int32
CPickups::GetNewUniquePickupIndex(int32 slot)
{
	if (aPickUps[slot].m_nIndex >= 0xFFFE)
		aPickUps[slot].m_nIndex = 1;
	else
		aPickUps[slot].m_nIndex++;
	return slot | (aPickUps[slot].m_nIndex << 16);
}

int32
CPickups::ModelForWeapon(eWeaponType weaponType)
{
	return CWeaponInfo::GetWeaponInfo(weaponType)->m_nModelId;
}

eWeaponType
CPickups::WeaponForModel(int32 model)
{
	if (model == MI_PICKUP_BODYARMOUR) return WEAPONTYPE_ARMOUR;
	if (model == MI_PICKUP_HEALTH) return WEAPONTYPE_HEALTH;
	if (model == MI_PICKUP_ADRENALINE) return WEAPONTYPE_ARMOUR;
	if (model == -1) return WEAPONTYPE_UNARMED;
	return ((CWeaponModelInfo*)CModelInfo::GetModelInfo(model))->GetWeaponInfo();
}

void
CPickups::AddToCollectedPickupsArray(int32 index)
{
	aPickUpsCollected[CollectedPickUpIndex++] = index | (aPickUps[index].m_nIndex << 16);
	if (CollectedPickUpIndex >= NUMCOLLECTEDPICKUPS)
		CollectedPickUpIndex = 0;
}

void
CPickups::Update()
{
#ifdef FIX_BUGS // RIP speedrunning (solution from SA)
	if (CReplay::IsPlayingBack())
		return;
#endif
#ifdef CAMERA_PICKUP
	if ( bPickUpcamActivated ) // taken from PS2
	{
		float dist = Distance2D(StaticCamCoors, FindPlayerCoors());
		float mult;
		if ( dist < 10.0f )
			mult = 1.0f - (dist / 10.0f );
		else
			mult = 0.0f;

		CVector pos = StaticCamCoors;
		pos.z += (pPlayerVehicle->GetColModel()->boundingBox.GetSize().z + 2.0f) * mult;

		if ( (CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 750 )
		{
			TheCamera.SetCamPositionForFixedMode(pos, CVector(0.0f, 0.0f, 0.0f));
			TheCamera.TakeControl(FindPlayerVehicle(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_SCRIPT);
		}

		if ( FindPlayerVehicle() != pPlayerVehicle || Distance(StaticCamCoors, FindPlayerCoors()) > 40.0f
			|| ((CTimer::GetTimeInMilliseconds() - StaticCamStartTime) > 60000) )
		{
			TheCamera.RestoreWithJumpCut();
			bPickUpcamActivated = false;
		}
	}
#endif
	if (CPad::GetPad(0)->CollectPickupJustDown())
		CollectPickupBuffer = 6;
	else
		CollectPickupBuffer = Max(0, CollectPickupBuffer - 1);

	if (PlayerOnWeaponPickup)
		PlayerOnWeaponPickup = Max(0, PlayerOnWeaponPickup - 1);

#define PICKUPS_FRAME_SPAN (6)
#ifdef FIX_BUGS
	for (uint32 i = NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN) / PICKUPS_FRAME_SPAN; i < NUMGENERALPICKUPS * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1) / PICKUPS_FRAME_SPAN; i++) {
#else // BUG: this code can only reach 318 out of 320 pickups
	for (uint32 i = NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN); i < NUMGENERALPICKUPS / PICKUPS_FRAME_SPAN * (CTimer::GetFrameCounter() % PICKUPS_FRAME_SPAN + 1); i++) {
#endif
		if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus))
			AddToCollectedPickupsArray(i);
	}
#undef PICKUPS_FRAME_SPAN
	for (uint32 i = NUMGENERALPICKUPS; i < NUMPICKUPS; i++) {
		if (aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].Update(FindPlayerPed(), FindPlayerVehicle(), CWorld::PlayerInFocus))
			AddToCollectedPickupsArray(i);
	}
}

CPickup*
CPickups::FindPickUpForThisObject(CEntity *object)
{
	for (uint32 i = 0; i < NUMPICKUPS; i++) {
		if (aPickUps[i].m_eType != PICKUP_NONE && (aPickUps[i].m_pObject == object || aPickUps[i].m_pExtraObject == object)) {
			return &aPickUps[i];
		}
	}
	return &aPickUps[0];
}

void
CPickups::DoPickUpEffects(CEntity *entity)
{
	CPickup *pickup = FindPickUpForThisObject(entity);

	if (entity->GetModelIndex() == MI_PICKUP_KILLFRENZY)
		entity->bDoNotRender = CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame;

	if (!entity->bDoNotRender) {
		float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800));
		float modifiedSin = 0.3f * (s + 1.0f);

#ifdef FIX_BUGS
		int16 colorId = 0;
#else
		int16 colorId;
#endif
		bool doInnerGlow = false;
		bool doOuterGlow = true;

		if (entity->GetModelIndex() == MI_PICKUP_ADRENALINE || entity->GetModelIndex() == MI_PICKUP_CAMERA) {
			colorId = WEAPONTYPE_TOTALWEAPONS;
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_BODYARMOUR) {
			colorId = WEAPONTYPE_ARMOUR;
		} else if (entity->GetModelIndex() == MI_PICKUP_BRIBE) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_INFO || entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_HEALTH || entity->GetModelIndex() == MI_PICKUP_BONUS) {
			colorId = WEAPONTYPE_HEALTH;
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_PROPERTY) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_PROPERTY_FORSALE) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_REVENUE) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_SAVEGAME) {
			doInnerGlow = true;
			doOuterGlow = false;
		} else if (entity->GetModelIndex() == MI_PICKUP_CLOTHES) {
			colorId = WEAPONTYPE_TOTALWEAPONS;
			doOuterGlow = false;
			doInnerGlow = true;
		} else
			colorId = WeaponForModel(entity->GetModelIndex());

		const CVector& pos = pickup->m_vecPos;
		if (doOuterGlow) {
			bool corona1 = false;
			bool corona2 = false;
			int timerVal = (CTimer::GetTimeInMilliseconds() >> 9) & 7;

			if (timerVal < 3)
				corona1 = false;
			else if (timerVal == 3)
				corona1 = (CGeneral::GetRandomNumber() & 3) != 0;
			else
				corona1 = true;

			timerVal = (timerVal - 1) & 7;
			if (timerVal < 3)
				corona2 = false;
			else if (timerVal == 3)
				corona2 = (CGeneral::GetRandomNumber() & 3) != 0;
			else
				corona2 = true;

			if (((CObject*)entity)->bAmmoCollected) {
				corona2 = false;
				corona1 = false;
			}

			if (corona1) {
				CCoronas::RegisterCorona((uintptr)entity,
					aPickupColors[colorId].r * 0.45f, aPickupColors[colorId].g * 0.45f, aPickupColors[colorId].b * 0.45f,
					255, pos, 0.76f, 65.0f,
					CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF,
					0.0f, false, -0.4f);
				CShadows::StoreStaticShadow((uintptr)entity,
					SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0,
					aPickupColors[colorId].r * 0.3f, aPickupColors[colorId].g * 0.3f, aPickupColors[colorId].b * 0.3f,
					4.0f, 1.0f, 40.0f, false, 0.0f);
				float radius = (CGeneral::GetRandomNumber() & 0xF) * 0.1f + 3.0f;
				CPointLights::AddLight(CPointLights::LIGHT_POINT, pos, CVector(0.0f, 0.0f, 0.0f), radius, aPickupColors[colorId].r / 256.0f, aPickupColors[colorId].g / 256.0f, aPickupColors[colorId].b / 256.0f, CPointLights::FOG_NONE, true);
			} else
				CCoronas::RegisterCorona((uintptr)entity, 0, 0, 0, 255, pos, 0.57f, 65.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);

			if (corona2) {
				CCoronas::RegisterCorona(
					(uintptr)entity + 1,
					aPickupColors[colorId].r * 0.55f, aPickupColors[colorId].g * 0.55f, aPickupColors[colorId].b * 0.55f,
					255,
					pos,
					0.6f,
					65.0f,
					CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF,
					0.0f, false, -0.4f);
				if (!corona1)
					CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0,
						aPickupColors[colorId].r * 0.25f, aPickupColors[colorId].g * 0.25f, aPickupColors[colorId].b * 0.25f,
						4.0f, 1.0f, 40.0f, false, 0.0f);
			} else
				CCoronas::RegisterCorona((uintptr)entity + 1, 0, 0, 0, 255, pos, 0.45f, 65.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
		}

		CObject *object = (CObject*)entity;
		if (object->bPickupObjWithMessage || object->bOutOfStock || object->m_nBonusValue || object->m_nCostValue) {
			
			float dist = Distance2D(pos, TheCamera.GetPosition());
			const float MAXDIST = 14.0f;

			if (dist < MAXDIST && NumMessages < NUMPICKUPMESSAGES) {
				RwV3d vecOut;
				float fDistX, fDistY;
				if (CSprite::CalcScreenCoors(entity->GetPosition() + CVector(0.0f, 0.0f, 0.7f), &vecOut, &fDistX, &fDistY, true)) {
					aMessages[NumMessages].m_pos.x = vecOut.x;
					aMessages[NumMessages].m_pos.y = vecOut.y;
					aMessages[NumMessages].m_dist.x = fDistX;
					aMessages[NumMessages].m_dist.y = fDistY;
					aMessages[NumMessages].m_weaponType = WeaponForModel(entity->GetModelIndex());
					aMessages[NumMessages].m_color.red = aPickupColors[colorId].r;
					aMessages[NumMessages].m_color.green = aPickupColors[colorId].g;
					aMessages[NumMessages].m_color.blue = aPickupColors[colorId].b;
					aMessages[NumMessages].m_color.alpha = (1.0f - dist / MAXDIST) * 128.0f;
					aMessages[NumMessages].m_bOutOfStock = object->bOutOfStock;
					aMessages[NumMessages].m_quantity = object->m_nBonusValue;
					aMessages[NumMessages].money = object->m_nCostValue;
					NumMessages++;
				}
			}
		}

		uint32 model = entity->GetModelIndex();
		CColModel *colModel = entity->GetColModel();
		CVector colLength = colModel->boundingBox.max - colModel->boundingBox.min;
		float maxDimension = Max(colLength.x, Max(colLength.y, colLength.z));

		float scale = (Max(1.f, 1.2f / maxDimension) - 1.0f) * 0.6f + 1.0f;
		if (model == MI_MINIGUN || model == MI_MINIGUN2)
			scale = 1.2f;

		entity->GetMatrix().SetRotateZOnlyScaled((float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800), scale);

		if (entity->GetModelIndex() == MI_MINIGUN2) {
			CMatrix matrix1;
			CMatrix matrix2; // unused
			entity->SetPosition(pickup->m_vecPos);
			matrix1.SetRotateX(0.0f);
			matrix1.Rotate(DEGTORAD(4.477f), DEGTORAD(-29.731f), DEGTORAD(-1.064f));
			matrix1.Translate(CVector(0.829f, -0.001f, 0.226f));
			entity->GetMatrix() *= matrix1;
		}

		if (doOuterGlow) {
			CVector scale(0.0f, 0.0f, 0.0f);
			if (colLength.x == maxDimension)
				scale.x = colLength.x;
			else if (colLength.y == maxDimension)
				scale.y = colLength.y;
			else
				scale.z = colLength.z;

			for (int i = 0; i < 4; i++) {
				CVector pos = entity->GetMatrix() * (scale * ((float)i / 3.0f));
				CCoronas::RegisterCorona(
					(uintptr)entity + 8 + i,
					aPickupColors[colorId].r * 0.15f,
					aPickupColors[colorId].g * 0.15f,
					aPickupColors[colorId].b * 0.15f,
					255,
					pos,
					1.0f,
					65.0f,
					CCoronas::TYPE_STAR, CCoronas::FLARE_NONE,
					CCoronas::REFLECTION_OFF,
					CCoronas::LOSCHECK_OFF,
					CCoronas::STREAK_OFF,
					0.0f,
					false,
					-0.5f);
			}
		}

		if (doInnerGlow)
			CCoronas::RegisterCorona(
#ifdef FIX_BUGS
				(uintptr)entity + 8 + 4,
#else
				(uintptr)entity + 9,
#endif
				126, 69, 121, 255, entity->GetPosition(), 1.2f, 50.0f,
				CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
	}
}

void
CPickups::DoMineEffects(CEntity *entity)
{
	const CVector &pos = entity->GetPosition();
	float dist = Distance(pos, TheCamera.GetPosition());
	const float MAXDIST = 20.0f;

	if (dist < MAXDIST) {
		float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x1FF) * DEGTORAD(360.0f / 0x200));

		int32 red = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 64.0f;
		CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, red, 0, 0, 4.0f, 1.0f, 40.0f,
		                            false, 0.0f);
		CCoronas::RegisterCorona((uintptr)entity, red, 0, 0, 255, pos, 0.6f, 60.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
	}

	entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x3FF) * DEGTORAD(360.0f / 0x400));
}

void
CPickups::DoMoneyEffects(CEntity *entity)
{
	const CVector &pos = entity->GetPosition();
	float dist = Distance(pos, TheCamera.GetPosition());
	const float MAXDIST = 20.0f;

	if (dist < MAXDIST) {
		float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x3FF) * DEGTORAD(360.0f / 0x400));

		int32 green = (MAXDIST - dist) * (0.2f * s + 0.3f) / MAXDIST * 64.0f;
		CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, 0, green, 0, 4.0f, 1.0f,
		                            40.0f, false, 0.0f);
		CCoronas::RegisterCorona((uintptr)entity, 0, green, 0, 255, pos, 0.4f, 40.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
	}

	entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800));
}

void
CPickups::DoCollectableEffects(CEntity *entity)
{
	const CVector &pos = entity->GetPosition();
	float dist = Distance(pos, TheCamera.GetPosition());
	const float MAXDIST = 14.0f;

	if (dist < MAXDIST) {
		float s = Sin((float)((CTimer::GetTimeInMilliseconds() + (uintptr)entity) & 0x7FF) * DEGTORAD(360.0f / 0x800));

		int32 color = (MAXDIST - dist) * (0.5f * s + 0.5f) / MAXDIST * 255.0f;
		CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, color, color, color, 4.0f,
		                            1.0f, 40.0f, false, 0.0f);
		CCoronas::RegisterCorona((uintptr)entity, color, color, color, 255, pos, 0.6f, 40.0f, CCoronas::TYPE_HEX, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
	}

	entity->GetMatrix().SetRotateZOnly((float)(CTimer::GetTimeInMilliseconds() & 0xFFF) * DEGTORAD(360.0f / 0x1000));
}

void
CPickups::RenderPickUpText()
{
	wchar *strToPrint;
	for (int32 i = 0; i < NumMessages; i++) {

		if (aMessages[i].money != 0) {
			sprintf(gString, "$%d", aMessages[i].money);
			AsciiToUnicode(gString, gUString);
			strToPrint = gUString;
		} else {
			switch (aMessages[i].m_quantity) // could use some enum maybe
			{
			case 0:
				if (aMessages[i].m_weaponType == WEAPONTYPE_HEALTH || aMessages[i].m_weaponType == WEAPONTYPE_ARMOUR) {
					strToPrint = nil;
				} else {
					if (aMessages[i].m_bOutOfStock)
						strToPrint = TheText.Get("STOCK");
					else {
						sprintf(gString, "$%d", CostOfWeapon[aMessages[i].m_weaponType]);
						AsciiToUnicode(gString, gUString);
						strToPrint = gUString;
					}
				}
				break;
			case 1:
				strToPrint = TheText.Get("OUTFT1");
				break;
			case 2:
				strToPrint = TheText.Get("OUTFT2");
				break;
			case 3:
				strToPrint = TheText.Get("OUTFT3");
				break;
			case 4:
				strToPrint = TheText.Get("OUTFT4");
				break;
			case 5:
				strToPrint = TheText.Get("OUTFT5");
				break;
			case 6:
				strToPrint = TheText.Get("OUTFT6");
				break;
			case 7:
				strToPrint = TheText.Get("OUTFT7");
				break;
			case 8:
				strToPrint = TheText.Get("OUTFT8");
				break;
			case 9:
				strToPrint = TheText.Get("OUTFT9");
				break;
			case 10:
				strToPrint = TheText.Get("OUTFT10");
				break;
			case 11:
				strToPrint = TheText.Get("OUTFT11");
				break;
			case 12:
				strToPrint = TheText.Get("OUTFT12");
				break;
			case 13:
				strToPrint = TheText.Get("OUTFT13");
				break;
			default:
				break;
			}
		}
		if (strToPrint == nil)
			continue;
		CFont::SetPropOn();
		CFont::SetBackgroundOff();

#ifdef FIX_BUGS
		const float MAX_SCALE = SCREEN_WIDTH / DEFAULT_SCREEN_WIDTH;
#else
		float MAX_SCALE = RsGlobal.width / DEFAULT_SCREEN_WIDTH;
#endif

		float fScaleY = aMessages[i].m_dist.y / 30.0f;
		if (fScaleY > MAX_SCALE) fScaleY = MAX_SCALE;

		float fScaleX = aMessages[i].m_dist.x / 30.0f;
		if (fScaleX > MAX_SCALE) fScaleX = MAX_SCALE;

		CFont::SetScale(fScaleX, fScaleY); // this shouldn't be scaled
		CFont::SetCentreOn();
		CFont::SetCentreSize(SCREEN_WIDTH);
		CFont::SetJustifyOff();

		CFont::SetColor(CRGBA(aMessages[i].m_color.red, aMessages[i].m_color.green, aMessages[i].m_color.blue, aMessages[i].m_color.alpha));
		CFont::SetBackGroundOnlyTextOff();
		CFont::SetFontStyle(FONT_STANDARD);
		CFont::PrintString(aMessages[i].m_pos.x, aMessages[i].m_pos.y, strToPrint);
	}
	NumMessages = 0;
}

void
CPickups::CreateSomeMoney(CVector pos, int money)
{
	bool found;

	int pickupCount = Min(money / 20 + 1, 7);
	int moneyPerPickup = money / pickupCount;

	for (int i = 0; i < pickupCount; i++) {
		// (CGeneral::GetRandomNumber() % 256) * PI / 128 gives a float up to something TWOPI-ish.
		pos.x += 1.5f * Sin((CGeneral::GetRandomNumber() % 256) * PI / 128);
		pos.y += 1.5f * Cos((CGeneral::GetRandomNumber() % 256) * PI / 128);
		pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &found) + 0.5f;
		if (found) {
			CPickups::GenerateNewOne(CVector(pos.x, pos.y, pos.z), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 3));
		}
	}
}

void
CPickups::RemoveAllPickupsOfACertainWeaponGroupWithNoAmmo(eWeaponType weaponType)
{
	uint32 weaponSlot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot;
	if (CWeaponInfo::IsWeaponSlotAmmoMergeable(weaponSlot)) {
		for (int slot = 0; slot < NUMPICKUPS; slot++) {
			if (aPickUps[slot].m_eType == PICKUP_ONCE || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT_SLOW) {
				if (aPickUps[slot].m_pObject) {
					if (CWeaponInfo::GetWeaponInfo(WeaponForModel(aPickUps[slot].m_pObject->GetModelIndex()))->m_nWeaponSlot == weaponSlot &&
						aPickUps[slot].m_nQuantity == 0) {
						CWorld::Remove(aPickUps[slot].m_pObject);
						delete aPickUps[slot].m_pObject;
						aPickUps[slot].m_bRemoved = true;
						aPickUps[slot].m_pObject = nil;
						aPickUps[slot].m_eType = PICKUP_NONE;
					}
				}
			}
		}
	}
}

void
CPickups::DetonateMinesHitByGunShot(CVector *vec1, CVector *vec2)
{
	for (int i = 0; i < NUMGENERALPICKUPS; i++) {
		if (aPickUps[i].m_eType == PICKUP_NAUTICAL_MINE_ARMED)
			aPickUps[i].ProcessGunShot(vec1, vec2);
	}
}

void
CPickups::RemoveUnnecessaryPickups(const CVector& center, float radius)
{
	for (int i = 0; i < NUMPICKUPS; i++) {
		if (aPickUps[i].m_eType == PICKUP_ONCE_TIMEOUT || aPickUps[i].m_eType == PICKUP_MONEY) {
			if (Distance(center, aPickUps[i].m_vecPos) < radius) {
				aPickUps[i].GetRidOfObjects();
				aPickUps[i].m_bRemoved = true;
				aPickUps[i].m_eType = PICKUP_NONE;
			}
		}
	}
}

void
CPickups::Load(uint8 *buf, uint32 size)
{
INITSAVEBUF

	for (int32 i = 0; i < NUMPICKUPS; i++) {
		aPickUps[i] = ReadSaveBuf<CPickup>(buf);

		if (aPickUps[i].m_eType != PICKUP_NONE) {
			if (aPickUps[i].m_pObject != nil)
				aPickUps[i].m_pObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pObject - 1);
			if (aPickUps[i].m_pExtraObject != nil)
				aPickUps[i].m_pExtraObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pExtraObject - 1);
		}
			
	}

	CollectedPickUpIndex = ReadSaveBuf<uint16>(buf);
	ReadSaveBuf<uint16>(buf);
	NumMessages = 0;

	for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++)
		aPickUpsCollected[i] = ReadSaveBuf<int32>(buf);

VALIDATESAVEBUF(size)
}

void
CPickups::Save(uint8 *buf, uint32 *size)
{
	*size = sizeof(aPickUps);
	*size += sizeof(uint16) + sizeof(uint16) + sizeof(aPickUpsCollected);

INITSAVEBUF

	for (int32 i = 0; i < NUMPICKUPS; i++) {
		CPickup *buf_pickup = WriteSaveBuf(buf, aPickUps[i]);
		if (buf_pickup->m_eType != PICKUP_NONE) {
			if (buf_pickup->m_pObject != nil)
				buf_pickup->m_pObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pObject) + 1);
			if (buf_pickup->m_pExtraObject != nil)
				buf_pickup->m_pExtraObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pExtraObject) + 1);
		}
	}

	WriteSaveBuf(buf, CollectedPickUpIndex);
	WriteSaveBuf(buf, (uint16)0); // possibly was NumMessages

	for (uint16 i = 0; i < NUMCOLLECTEDPICKUPS; i++)
		WriteSaveBuf(buf, aPickUpsCollected[i]);

VALIDATESAVEBUF(*size)
}

void
CPacManPickup::Update()
{
}

int32 CollectGameState;
int16 ThingsToCollect;

CPacManPickup CPacManPickups::aPMPickUps[NUMPACMANPICKUPS];
CVector CPacManPickups::LastPickUpCoors;
int32 CPacManPickups::PillsEatenInRace;
bool CPacManPickups::bPMActive;

void
CPacManPickups::Init()
{
}

void
CPacManPickups::Update()
{
}

void
CPacManPickups::GeneratePMPickUps(CVector pos, float scrambleMult, int16 count, uint8 type)
{
}

// diablo porn mission pickups
static const CVector aRacePoints1[] = {
	CVector(913.62219f, -155.13692f, 4.9699469f),
	CVector(913.92401f, -124.12943f, 4.9692569f),
	CVector(913.27899f, -93.524231f, 7.4325991f),
	CVector(912.60852f, -63.15905f, 7.4533591f),
	CVector(934.22144f, -42.049122f, 7.4511471f),
	CVector(0.0f, 0.0f, 0.0f),
};

void
CPacManPickups::GeneratePMPickUpsForRace(int32 race)
{
}

void
CPacManPickups::GenerateOnePMPickUp(CVector pos)
{
}

void
CPacManPickups::Render()
{
}

void
CPacManPickups::ClearPMPickUps()
{
}

void
CPacManPickups::StartPacManRace(int32 race)
{
}

void
CPacManPickups::StartPacManRecord()
{
}

uint32
CPacManPickups::QueryPowerPillsEatenInRace()
{
	return 0;
}

void
CPacManPickups::ResetPowerPillsEatenInRace()
{
}

void
CPacManPickups::CleanUpPacManStuff()
{
}

void
CPacManPickups::StartPacManScramble(CVector pos, float scrambleMult, int16 count)
{
}

uint32
CPacManPickups::QueryPowerPillsCarriedByPlayer()
{
	return 0;
}

void
CPacManPickups::ResetPowerPillsCarriedByPlayer()
{
}

void
CPed::CreateDeadPedMoney(void)
{
	if (!CGame::nastyGame)
		return;

	int mi = GetModelIndex();

	if ((mi >= MI_COP && mi <= MI_FIREMAN) || (CharCreatedBy == MISSION_CHAR && !bMoneyHasBeenGivenByScript) || bInVehicle)
		return;

	int money = m_nPedMoney;
	if (money < 10)
		return;

	CVector pickupPos = GetPosition();
	CPickups::CreateSomeMoney(pickupPos, money);
	m_nPedMoney = 0;
}

void
CPed::CreateDeadPedWeaponPickups(void)
{
	CVector pickupPos;

	if (bInVehicle)
		return;

	for(int i = 0; i < TOTAL_WEAPON_SLOTS; i++) {

		eWeaponType weapon = GetWeapon(i).m_eWeaponType;
		int weaponAmmo = GetWeapon(i).m_nAmmoTotal;
		if (weapon == WEAPONTYPE_UNARMED || weapon == WEAPONTYPE_DETONATOR || (weaponAmmo == 0 && !GetWeapon(i).IsTypeMelee()))
			continue;

		int quantity = Min(weaponAmmo, AmmoForWeapon_OnStreet[weapon] / 2);
		CreateDeadPedPickupCoors(&pickupPos.x, &pickupPos.y, &pickupPos.z);
		pickupPos.z += 0.3f;
		if (!CPickups::TryToMerge_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, quantity, false)) {
			CPickups::GenerateNewOne_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, Min(weaponAmmo, quantity));
		}
	}
	ClearWeapons();
}

void
CPed::CreateDeadPedPickupCoors(float *x, float *y, float *z)
{
	bool found = false;
	CVector pickupPos;

#define NUMBER_OF_ATTEMPTS 32
	for (int i = 0; i < NUMBER_OF_ATTEMPTS; i++) {

		pickupPos = GetPosition();
		pickupPos.x = 1.5f * Sin((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().x;
		pickupPos.y = 1.5f * Cos((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().y;
		pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f;

		if (!found)
			continue;

		CVector pedPos = GetPosition();
		pedPos.z += 0.3f;

		CVector pedToPickup = pickupPos - pedPos;
		float distance = pedToPickup.Magnitude();

		// outer edge of pickup
		distance = (distance + 0.4f) / distance;
		CVector pickupPos2 = pedPos;
		pickupPos2 += distance * pedToPickup;

		if ((pickupPos - FindPlayerCoors()).Magnitude2D() > 2.0f || i > NUMBER_OF_ATTEMPTS / 2) {

			if (i > NUMBER_OF_ATTEMPTS / 2 || !CPickups::TestForPickupsInBubble(pickupPos, 1.3f)) {

				if (CWorld::GetIsLineOfSightClear(pickupPos2, pedPos,
					true, i < NUMBER_OF_ATTEMPTS / 2, false, i < NUMBER_OF_ATTEMPTS / 2, false, false, false)) {

					if (i > NUMBER_OF_ATTEMPTS / 2 || !CWorld::TestSphereAgainstWorld(pickupPos, 1.2f, nil, false, true, false, false, false, false)) {
						*x = pickupPos.x;
						*y = pickupPos.y;
						*z = pickupPos.z;
						return;
					}
				}
			}
		}
	}
	*x = GetPosition().x;
	*y = GetPosition().y;
	*z = GetPosition().z + 0.4f;
#undef NUMBER_OF_ATTEMPTS
}