#include "common.h"

#include "GameLogic.h"
#include "Clock.h"
#include "Stats.h"
#include "Pickups.h"
#include "Timer.h"
#include "Streaming.h"
#include "CutsceneMgr.h"
#include "World.h"
#include "PlayerPed.h"
#include "Wanted.h"
#include "Camera.h"
#include "Messages.h"
#include "CarCtrl.h"
#include "Restart.h"
#include "Pad.h"
#include "References.h"
#include "Fire.h"
#include "Script.h"
#include "Garages.h"
#include "Population.h"
#include "General.h"
#include "DMAudio.h"
#include "Radar.h"
#include "Pools.h"
#include "Hud.h"
#include "Particle.h"
#include "ColStore.h"
#include "Automobile.h"
#include "MBlur.h"
#include "screendroplets.h"
#include "SaveBuf.h"

uint8 CGameLogic::ActivePlayers;
uint8 CGameLogic::ShortCutState;
CAutomobile* CGameLogic::pShortCutTaxi;
uint32 CGameLogic::NumAfterDeathStartPoints;
CVector CGameLogic::ShortCutStart;
float CGameLogic::ShortCutStartOrientation;
CVector CGameLogic::ShortCutDestination;
float CGameLogic::ShortCutDestinationOrientation;
uint32 CGameLogic::ShortCutTimer;
CVector CGameLogic::AfterDeathStartPoints[NUM_SHORTCUT_START_POINTS];
float CGameLogic::AfterDeathStartPointOrientation[NUM_SHORTCUT_START_POINTS];
CVector CGameLogic::ShortCutDropOffForMission;
float CGameLogic::ShortCutDropOffOrientationForMission;
bool CGameLogic::MissionDropOffReadyToBeUsed;

#define SHORTCUT_TAXI_COST (9)
#define TOTAL_BUSTED_AUDIO (28)

void
CGameLogic::InitAtStartOfGame()
{
	ActivePlayers = 1;
	ShortCutState = SHORTCUT_NONE;
	pShortCutTaxi = nil;
	NumAfterDeathStartPoints = 0;
}

void
CGameLogic::PassTime(uint32 time)
{
	int32 minutes, hours, days;

	minutes = time + CClock::GetMinutes();
	hours = CClock::GetHours();

	for (; minutes >= 60; minutes -= 60)
		hours++;

	if (hours > 23) {
		days = CStats::DaysPassed;
		for (; hours >= 24; hours -= 24)
			days++;
		CStats::DaysPassed = days;
	}

	CClock::SetGameClock(hours, minutes);
	CPickups::PassTime(time * 1000);
}

void 
CGameLogic::SortOutStreamingAndMemory(const CVector &pos)
{
	CTimer::Stop();
	CStreaming::FlushRequestList();
	CStreaming::DeleteRwObjectsAfterDeath(pos);
	CStreaming::RemoveUnusedModelsInLoadedList();
	CGame::DrasticTidyUpMemory(true);
	CWorld::Players[CWorld::PlayerInFocus].m_pPed->Undress("player");
	CStreaming::LoadSceneCollision(pos);
	CStreaming::LoadScene(pos);
	CWorld::Players[CWorld::PlayerInFocus].m_pPed->Dress();
	CTimer::Update();
}

void
CGameLogic::Update()
{
	CVector vecRestartPos;
	float fRestartFloat;

#ifdef MISSION_REPLAY
	// what a place to check!
	if (gbTryingPorn4Again) {
		CRunningScript* pScript = CTheScripts::pActiveScripts;
		if (pScript && !CGeneral::faststricmp(pScript->m_abScriptName, "porno4"))
			gbTryingPorn4Again = false;
	}
#endif

	if (CCutsceneMgr::IsCutsceneProcessing()) return;

	UpdateShortCut();
	CPlayerInfo &pPlayerInfo = CWorld::Players[CWorld::PlayerInFocus];

	switch (pPlayerInfo.m_WBState) {
	case WBSTATE_PLAYING:
		if (pPlayerInfo.m_pPed->m_nPedState == PED_DEAD) {
			pPlayerInfo.m_pPed->ClearAdrenaline();
			pPlayerInfo.KillPlayer();
		}
		if (pPlayerInfo.m_pPed->m_nPedState == PED_ARRESTED) {
			pPlayerInfo.m_pPed->ClearAdrenaline();
			pPlayerInfo.ArrestPlayer();
		}
		break;
	case WBSTATE_WASTED:
#ifdef MISSION_REPLAY
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) {
#else
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) {
#endif
			TheCamera.SetFadeColour(200, 200, 200);
			TheCamera.Fade(2.0f, FADE_OUT);
		}

#ifdef MISSION_REPLAY
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) {
#else
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) {
#endif
			pPlayerInfo.m_WBState = WBSTATE_PLAYING;
			if (pPlayerInfo.m_bGetOutOfHospitalFree) {
				pPlayerInfo.m_bGetOutOfHospitalFree = false;
			} else {
				pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - 100);
				pPlayerInfo.m_pPed->ClearWeapons();
			}

			if (pPlayerInfo.m_pPed->bInVehicle) {
				CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle;
				if (pVehicle != nil) {
					if (pVehicle->pDriver == pPlayerInfo.m_pPed) {
						pVehicle->pDriver = nil;
						if (pVehicle->GetStatus() != STATUS_WRECKED)
							pVehicle->SetStatus(STATUS_ABANDONED);
					} else
						pVehicle->RemovePassenger(pPlayerInfo.m_pPed);
				}
			}
			CEventList::Initialise();
#ifdef SCREEN_DROPLETS
			ScreenDroplets::Initialise();
#endif
			CMessages::ClearMessages();
			CCarCtrl::ClearInterestingVehicleList();
			CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, true);
			CRestart::FindClosestHospitalRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat);
			CRestart::OverrideHospitalLevel = LEVEL_GENERIC;
			CRestart::OverridePoliceStationLevel = LEVEL_GENERIC;
			PassTime(720);
			RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat);
			AfterDeathArrestSetUpShortCutTaxi();
			SortOutStreamingAndMemory(pPlayerInfo.GetPos());
			TheCamera.m_fCamShakeForce = 0.0f;
			TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE);
			CPad::GetPad(0)->StopShaking(0);
			CReferences::RemoveReferencesToPlayer();
			CPopulation::m_CountDownToPedsAtStart = 10;
			CCarCtrl::CountDownToCarsAtStart = 10;
			CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED;
			if (CRestart::bFadeInAfterNextDeath) { 
				TheCamera.SetFadeColour(200, 200, 200);
				TheCamera.Fade(4.0f, FADE_IN);
			} else
				CRestart::bFadeInAfterNextDeath = true;
		}
		break;
	case WBSTATE_BUSTED:
#ifdef MISSION_REPLAY
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) {
#else
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) {
#endif
			TheCamera.SetFadeColour(0, 0, 0);
			TheCamera.Fade(2.0f, FADE_OUT);
		}


		if (!CTheScripts::IsPlayerOnAMission() && pPlayerInfo.m_nBustedAudioStatus == BUSTEDAUDIO_NONE) {
			if (CGeneral::GetRandomNumberInRange(0, 4) == 0)
				pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_DONE;
			else {
				pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_LOADING;
				char name[12];
				sprintf(name, pPlayerInfo.m_nCurrentBustedAudio >= 10 ? "bust_%d" : "bust_0%d", pPlayerInfo.m_nCurrentBustedAudio);
				DMAudio.ClearMissionAudio(0);
				DMAudio.PreloadMissionAudio(0, name);
				pPlayerInfo.m_nCurrentBustedAudio = pPlayerInfo.m_nCurrentBustedAudio % TOTAL_BUSTED_AUDIO + 1;
			}
		}
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 4000 &&
			pPlayerInfo.m_nBustedAudioStatus == BUSTEDAUDIO_LOADING &&
			DMAudio.GetMissionAudioLoadingStatus(0) == 1) {
			DMAudio.PlayLoadedMissionAudio(0);
			pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_DONE;
		}
		
#ifdef MISSION_REPLAY
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) {
#else
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) {
#endif
#ifdef FIX_BUGS
			pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_NONE;
#endif
			pPlayerInfo.m_WBState = WBSTATE_PLAYING;
			int takeMoney;

			switch (pPlayerInfo.m_pPed->m_pWanted->GetWantedLevel()) {
			case 0:
			case 1:
				takeMoney = 100;
				break;
			case 2:
				takeMoney = 200;
				break;
			case 3:
				takeMoney = 400;
				break;
			case 4:
				takeMoney = 600;
				break;
			case 5:
				takeMoney = 900;
				break;
			case 6:
				takeMoney = 1500;
				break;
			}
			if (pPlayerInfo.m_bGetOutOfJailFree) {
				pPlayerInfo.m_bGetOutOfJailFree = false;
			} else {
				pPlayerInfo.m_nMoney = Max(0, pPlayerInfo.m_nMoney - takeMoney);
				pPlayerInfo.m_pPed->ClearWeapons();
			}

			if (pPlayerInfo.m_pPed->bInVehicle) {
				CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle;
				if (pVehicle != nil) {
					if (pVehicle->pDriver == pPlayerInfo.m_pPed) {
						pVehicle->pDriver = nil;
						if (pVehicle->GetStatus() != STATUS_WRECKED)
							pVehicle->SetStatus(STATUS_ABANDONED);
					}
					else
						pVehicle->RemovePassenger(pPlayerInfo.m_pPed);
				}
			}
			CEventList::Initialise();
#ifdef SCREEN_DROPLETS
			ScreenDroplets::Initialise();
#endif
			CMessages::ClearMessages();
			CCarCtrl::ClearInterestingVehicleList();
			CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, true);
			CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat);
			CRestart::OverrideHospitalLevel = LEVEL_GENERIC;
			CRestart::OverridePoliceStationLevel = LEVEL_GENERIC;
			PassTime(720);
			RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat);
			AfterDeathArrestSetUpShortCutTaxi();
			pPlayerInfo.m_pPed->ClearWeapons();
			SortOutStreamingAndMemory(pPlayerInfo.GetPos());
			TheCamera.m_fCamShakeForce = 0.0f;
			TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE);
			CPad::GetPad(0)->StopShaking(0);
			CReferences::RemoveReferencesToPlayer();
			CPopulation::m_CountDownToPedsAtStart = 10;
			CCarCtrl::CountDownToCarsAtStart = 10;
			CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED;
			if (CRestart::bFadeInAfterNextArrest) {
				TheCamera.SetFadeColour(0, 0, 0);
				TheCamera.Fade(4.0f, FADE_IN);
			} else
				CRestart::bFadeInAfterNextArrest = true;
		}
		break;
	case WBSTATE_FAILED_CRITICAL_MISSION:
#ifdef MISSION_REPLAY
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > AddExtraDeathDelay() + 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= AddExtraDeathDelay() + 0x800)) {
#else
		if ((CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 0x800) && (CTimer::GetPreviousTimeInMilliseconds() - pPlayerInfo.m_nWBTime <= 0x800)) {
#endif
			TheCamera.SetFadeColour(0, 0, 0);
			TheCamera.Fade(2.0f, FADE_OUT);
		}
#ifdef MISSION_REPLAY
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) {
#else
		if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) {
#endif
			pPlayerInfo.m_WBState = WBSTATE_PLAYING;
			if (pPlayerInfo.m_pPed->bInVehicle) {
				CVehicle *pVehicle = pPlayerInfo.m_pPed->m_pMyVehicle;
				if (pVehicle != nil) {
					if (pVehicle->pDriver == pPlayerInfo.m_pPed) {
						pVehicle->pDriver = nil;
						if (pVehicle->GetStatus() != STATUS_WRECKED)
							pVehicle->SetStatus(STATUS_ABANDONED);
					} else
						pVehicle->RemovePassenger(pPlayerInfo.m_pPed);
				}
			}
			CEventList::Initialise();
#ifdef SCREEN_DROPLETS
			ScreenDroplets::Initialise();
#endif
			CMessages::ClearMessages();
			CCarCtrl::ClearInterestingVehicleList();
			CWorld::ClearExcitingStuffFromArea(pPlayerInfo.GetPos(), 4000.0f, true);
			CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat);
			CRestart::OverridePoliceStationLevel = LEVEL_GENERIC;
			CRestart::OverrideHospitalLevel = LEVEL_GENERIC;
			RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat);
			SortOutStreamingAndMemory(pPlayerInfo.GetPos());
			TheCamera.m_fCamShakeForce = 0.0f;
			TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE);
			CPad::GetPad(0)->StopShaking(0);
			CReferences::RemoveReferencesToPlayer();
			CPopulation::m_CountDownToPedsAtStart = 10;
			CCarCtrl::CountDownToCarsAtStart = 10;
			CPad::GetPad(CWorld::PlayerInFocus)->DisablePlayerControls = PLAYERCONTROL_ENABLED;
			TheCamera.SetFadeColour(0, 0, 0);
			TheCamera.Fade(4.0f, FADE_IN);
		}
		break;
	case 4:
		return;
	}
}

void
CGameLogic::RestorePlayerStuffDuringResurrection(CPlayerPed *pPlayerPed, CVector pos, float angle)
{
	ClearShortCut();
	CPlayerInfo* pPlayerInfo = pPlayerPed->GetPlayerInfoForThisPlayerPed();
	pPlayerPed->m_fHealth = pPlayerInfo->m_nMaxHealth;
	pPlayerPed->m_fArmour = 0.0f;
	pPlayerPed->bIsVisible = true;
	pPlayerPed->m_bloodyFootprintCountOrDeathTime = 0;
	pPlayerPed->bDoBloodyFootprints = false;
	pPlayerPed->m_nDrunkenness = 0;
	pPlayerPed->m_nFadeDrunkenness = 0;
	CMBlur::ClearDrunkBlur();
	pPlayerPed->m_nDrunkCountdown = 0;
	pPlayerPed->ClearAdrenaline();
	pPlayerPed->m_fCurrentStamina = pPlayerPed->m_fMaxStamina;
	if (pPlayerPed->m_pFire)
		pPlayerPed->m_pFire->Extinguish();
	pPlayerPed->bInVehicle = false;
	pPlayerPed->m_pMyVehicle = nil;
	pPlayerPed->m_pVehicleAnim = nil;
	pPlayerPed->m_pWanted->Reset();
	pPlayerPed->bCancelEnteringCar = false;
	pPlayerPed->RestartNonPartialAnims();
	pPlayerInfo->MakePlayerSafe(false);
	pPlayerPed->bRemoveFromWorld = false;
	pPlayerPed->ClearWeaponTarget();
	pPlayerPed->SetInitialState();
	CCarCtrl::ClearInterestingVehicleList();
	pPlayerPed->Teleport(pos + CVector(0.0f, 0.0f, 1.0f));
	pPlayerPed->SetMoveSpeed(0.0f, 0.0f, 0.0f);
	pPlayerPed->m_fRotationCur = DEGTORAD(angle);
	pPlayerPed->m_fRotationDest = pPlayerPed->m_fRotationCur;
	pPlayerPed->SetHeading(pPlayerPed->m_fRotationCur);
	CTheScripts::ClearSpaceForMissionEntity(pos, pPlayerPed);
	CWorld::ClearExcitingStuffFromArea(pos, 4000.0f, true);
	pPlayerPed->RestoreHeadingRate();
	CGame::currArea = AREA_MAIN_MAP;
	CStreaming::RemoveBuildingsNotInArea(AREA_MAIN_MAP);
	TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString();
	TheCamera.Restore();
	CReferences::RemoveReferencesToPlayer();
	CGarages::PlayerArrestedOrDied();
	CStats::CheckPointReachedUnsuccessfully();
	CWorld::Remove(pPlayerPed);
	CWorld::Add(pPlayerPed);
	CHud::ResetWastedText();
	CStreaming::StreamZoneModels(pos);
	clearWaterDrop = true;
}

void
CGameLogic::ClearShortCut()
{
	if (pShortCutTaxi) {
		if (pShortCutTaxi->VehicleCreatedBy == MISSION_VEHICLE) {
			pShortCutTaxi->VehicleCreatedBy = RANDOM_VEHICLE;
			--CCarCtrl::NumMissionCars;
			++CCarCtrl::NumRandomCars;
		}
		CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi));
		pShortCutTaxi = nil;
	}
	CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI);
}

void
CGameLogic::SetUpShortCut(CVector vStartPos, float fStartAngle, CVector vEndPos, float fEndAngle)
{
	ClearShortCut();
	ShortCutState = SHORTCUT_INIT;
	ShortCutStart = vStartPos;
	ShortCutStartOrientation = fStartAngle;
	ShortCutDestination = vEndPos;
	ShortCutDestinationOrientation = fEndAngle;
	CStreaming::RequestModel(MI_KAUFMAN, 0);
}

void
CGameLogic::AbandonShortCutIfTaxiHasBeenMessedWith()
{
	if (!pShortCutTaxi)
		return;
	if (pShortCutTaxi->pDriver == nil ||
		pShortCutTaxi->pDriver->DyingOrDead() ||
		pShortCutTaxi->pDriver->GetPedState() == PED_DRAG_FROM_CAR ||
		pShortCutTaxi->pDriver->GetPedState() == PED_ON_FIRE ||
		pShortCutTaxi->pDriver->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE ||
		pShortCutTaxi->m_fHealth < 250.0f ||
		pShortCutTaxi->bRenderScorched)
		ClearShortCut();
}

void
CGameLogic::AbandonShortCutIfPlayerMilesAway()
{
	if (!pShortCutTaxi)
		return;
	if ((FindPlayerCoors() - pShortCutTaxi->GetPosition()).Magnitude() > 120.0f)
		ClearShortCut();
}

void
CGameLogic::UpdateShortCut()
{
	switch (ShortCutState) {
	case SHORTCUT_INIT:
		if (!CStreaming::HasModelLoaded(MI_KAUFMAN)) {
			CStreaming::RequestModel(MI_KAUFMAN, 0);
			return;
		}
		pShortCutTaxi = new CAutomobile(MI_KAUFMAN, RANDOM_VEHICLE);
		if (!pShortCutTaxi)
			return;
		pShortCutTaxi->SetPosition(ShortCutStart);
		pShortCutTaxi->SetHeading(DEGTORAD(ShortCutStartOrientation));
		pShortCutTaxi->PlaceOnRoadProperly();
		pShortCutTaxi->SetStatus(STATUS_PHYSICS);
		pShortCutTaxi->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER;
		pShortCutTaxi->AutoPilot.m_nCruiseSpeed = 0;
		pShortCutTaxi->SetUpDriver();
		pShortCutTaxi->VehicleCreatedBy = MISSION_VEHICLE;
		++CCarCtrl::NumMissionCars;
		--CCarCtrl::NumRandomCars;
		CTheScripts::ClearSpaceForMissionEntity(ShortCutStart, pShortCutTaxi);
		CWorld::Add(pShortCutTaxi);
		CRadar::SetEntityBlip(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi), 0, BLIP_DISPLAY_MARKER_ONLY);
		ShortCutState = SHORTCUT_IDLE;
		break;
	case SHORTCUT_IDLE:
		if (FindPlayerPed()->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && FindPlayerPed()->m_carInObjective == pShortCutTaxi) {
			CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI);
			FindPlayerPed()->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, pShortCutTaxi);
			ShortCutState = SHORTCUT_GETTING_IN;
		}
		AbandonShortCutIfTaxiHasBeenMessedWith();
		AbandonShortCutIfPlayerMilesAway();
		break;
	case SHORTCUT_GETTING_IN:
		if (pShortCutTaxi->pPassengers[0] == FindPlayerPed() ||
			pShortCutTaxi->pPassengers[1] == FindPlayerPed() ||
			pShortCutTaxi->pPassengers[2] == FindPlayerPed()) {
			pShortCutTaxi->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD;
			pShortCutTaxi->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2500;
			TheCamera.SetFadeColour(0, 0, 0);
			TheCamera.Fade(2.5f, FADE_OUT);
			ShortCutState = SHORTCUT_TRANSITION;
			ShortCutTimer = CTimer::GetTimeInMilliseconds() + 3000;
			CMessages::AddBigMessage(TheText.Get("TAXI"), 4500, 1);
		}
		AbandonShortCutIfTaxiHasBeenMessedWith();
		break;
	case SHORTCUT_TRANSITION:
		if (CTimer::GetTimeInMilliseconds() > ShortCutTimer) {
			CTimer::Suspend();
			CColStore::RequestCollision(ShortCutDestination);
			CStreaming::LoadSceneCollision(ShortCutDestination);
			CStreaming::LoadScene(ShortCutDestination);
			CTheScripts::ClearSpaceForMissionEntity(ShortCutDestination, pShortCutTaxi);
			pShortCutTaxi->Teleport(ShortCutDestination);
			pShortCutTaxi->SetHeading(DEGTORAD(ShortCutDestinationOrientation));
			pShortCutTaxi->PlaceOnRoadProperly();
			pShortCutTaxi->SetMoveSpeed(pShortCutTaxi->GetForward() * 0.4f);
			ShortCutTimer = CTimer::GetTimeInMilliseconds() + 1500;
			TheCamera.SetFadeColour(0, 0, 0);
			TheCamera.Fade(1.0f, FADE_IN);
			ShortCutState = SHORTCUT_ARRIVING;
			CTimer::Resume();
		}
		break;
	case SHORTCUT_ARRIVING:
		if (CTimer::GetTimeInMilliseconds() > ShortCutTimer) {
			CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - SHORTCUT_TAXI_COST);
			FindPlayerPed()->SetObjective(OBJECTIVE_LEAVE_CAR, pShortCutTaxi);
			FindPlayerPed()->m_carInObjective = pShortCutTaxi;
			ShortCutState = SHORTCUT_GETTING_OUT;
		}
		AbandonShortCutIfTaxiHasBeenMessedWith();
		break;
	case SHORTCUT_GETTING_OUT:
		if (pShortCutTaxi->pPassengers[0] != FindPlayerPed() &&
			pShortCutTaxi->pPassengers[1] != FindPlayerPed() &&
			pShortCutTaxi->pPassengers[2] != FindPlayerPed()) {
			CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI);
			pShortCutTaxi->AutoPilot.m_nCarMission = MISSION_CRUISE;
			pShortCutTaxi->AutoPilot.m_nCruiseSpeed = 18;
			CCarCtrl::JoinCarWithRoadSystem(pShortCutTaxi);
			pShortCutTaxi->VehicleCreatedBy = RANDOM_VEHICLE;
			++CCarCtrl::NumRandomCars;
			--CCarCtrl::NumMissionCars;
			CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi));
			ShortCutState = SHORTCUT_NONE;
			pShortCutTaxi = nil;
		}
		AbandonShortCutIfTaxiHasBeenMessedWith();
		break;
	}
}

void
CGameLogic::AddShortCutPointAfterDeath(CVector point, float angle)
{
	if (NumAfterDeathStartPoints >= NUM_SHORTCUT_START_POINTS)
		return;
	AfterDeathStartPoints[NumAfterDeathStartPoints] = point;
	AfterDeathStartPointOrientation[NumAfterDeathStartPoints] = angle;
	NumAfterDeathStartPoints++;
}

void
CGameLogic::AddShortCutDropOffPointForMission(CVector point, float angle)
{
	ShortCutDropOffForMission = point;
	ShortCutDropOffOrientationForMission = angle;
	MissionDropOffReadyToBeUsed = true;
}

void
CGameLogic::RemoveShortCutDropOffPointForMission()
{
	MissionDropOffReadyToBeUsed = false;
}

void
CGameLogic::AfterDeathArrestSetUpShortCutTaxi()
{
	if (!MissionDropOffReadyToBeUsed)
		return;
	int nClosestPoint = -1;
	float fDistanceToPoint = 999999.9f;
	for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) {
		float dist = (AfterDeathStartPoints[i] - FindPlayerCoors()).Magnitude();
		if (dist < fDistanceToPoint) {
			fDistanceToPoint = dist;
			nClosestPoint = i;
		}
	}
	if (fDistanceToPoint < 100.0f)
		SetUpShortCut(AfterDeathStartPoints[nClosestPoint],
			AfterDeathStartPointOrientation[nClosestPoint],
			ShortCutDropOffForMission,
			ShortCutDropOffOrientationForMission);
	MissionDropOffReadyToBeUsed = false;
}

void
CGameLogic::Save(uint8* buf, uint32* size)
{
INITSAVEBUF
	WriteSaveBuf(buf, NumAfterDeathStartPoints);
	*size += sizeof(NumAfterDeathStartPoints);
	for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) {
		WriteSaveBuf(buf, AfterDeathStartPoints[i].x);
		*size += sizeof(AfterDeathStartPoints[i].x);
		WriteSaveBuf(buf, AfterDeathStartPoints[i].y);
		*size += sizeof(AfterDeathStartPoints[i].y);
		WriteSaveBuf(buf, AfterDeathStartPoints[i].z);
		*size += sizeof(AfterDeathStartPoints[i].z);
		WriteSaveBuf(buf, AfterDeathStartPointOrientation[i]);
		*size += sizeof(AfterDeathStartPointOrientation[i]);
	}
VALIDATESAVEBUF(*size)
}

void
CGameLogic::Load(uint8* buf, uint32 size)
{
INITSAVEBUF
	ReadSaveBuf(&NumAfterDeathStartPoints, buf);
	for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) {
		ReadSaveBuf(&AfterDeathStartPoints[i].x, buf);
		ReadSaveBuf(&AfterDeathStartPoints[i].y, buf);
		ReadSaveBuf(&AfterDeathStartPoints[i].z, buf);
		ReadSaveBuf(&AfterDeathStartPointOrientation[i], buf);
	}
VALIDATESAVEBUF(size)
}
