#include "common.h"

#include "World.h"
#include "PlayerPed.h"
#include "CopPed.h"
#include "Wanted.h"
#include "DMAudio.h"
#include "ModelIndices.h"
#include "Vehicle.h"
#include "RpAnimBlend.h"
#include "AnimBlendAssociation.h"
#include "General.h"
#include "ZoneCull.h"
#include "PathFind.h"
#include "RoadBlocks.h"
#include "CarCtrl.h"
#include "Renderer.h"
#include "Camera.h"
#include "PedPlacement.h"
#include "Ropes.h"
#include "Stinger.h"

CCopPed::CCopPed(eCopType copType, int32 modifier) : CPed(PEDTYPE_COP)
{
	m_nCopType = copType;
	switch (copType) {
	case COP_STREET:
		SetModelIndex(MI_COP);
		GiveWeapon(WEAPONTYPE_NIGHTSTICK, 1000, true);
		GiveDelayedWeapon(WEAPONTYPE_COLT45, 1000);
		m_currentWeapon = WEAPONTYPE_UNARMED;
		m_fArmour = 0.0f;
		m_wepSkills = 208; /* TODO: what is this? seems unused */
		m_wepAccuracy = 60;
		break;
	case COP_FBI:
		SetModelIndex(MI_FBI);
		GiveDelayedWeapon(WEAPONTYPE_MP5, 1000);
		SetCurrentWeapon(WEAPONTYPE_MP5);
		m_fArmour = 100.0f;
		m_wepSkills = 176; /* TODO: what is this? seems unused */
		m_wepAccuracy = 76;
		break;
	case COP_SWAT:
	case COP_HELI_SWAT:
		SetModelIndex(MI_SWAT);
		GiveDelayedWeapon(WEAPONTYPE_UZI, 1000);
		SetCurrentWeapon(WEAPONTYPE_UZI);
		m_fArmour = 50.0f;
		m_wepSkills = 32; /* TODO: what is this? seems unused */
		m_wepAccuracy = 68;
		break;
	case COP_ARMY:
		SetModelIndex(MI_ARMY);
		GiveDelayedWeapon(WEAPONTYPE_MP5, 1000);
		SetCurrentWeapon(WEAPONTYPE_MP5);
		m_fArmour = 100.0f;
		m_wepSkills = 32; /* TODO: what is this? seems unused */
		m_wepAccuracy = 84;
		break;
	case COP_MIAMIVICE:
		switch (modifier) {
		case 0: SetModelIndex(MI_VICE1); break;
		case 1: SetModelIndex(MI_VICE2); break;
		case 2: SetModelIndex(MI_VICE3); break;
		case 3: SetModelIndex(MI_VICE4); break;
		case 4: SetModelIndex(MI_VICE5); break;
		case 5: SetModelIndex(MI_VICE6); break;
		case 6: SetModelIndex(MI_VICE7); break;
		case 7: SetModelIndex(MI_VICE8); break;
		default: assert(0); break;
		}
		GiveDelayedWeapon(WEAPONTYPE_UZI, 1000);
		SetCurrentWeapon(WEAPONTYPE_UZI);
		m_fArmour = 100.0f;
		m_wepSkills = 176;
		m_wepAccuracy = 76;
		break;
	}
	m_bIsInPursuit = false;
	field_5FE = 1;
	m_bIsDisabledCop = false;
	m_attackTimer = 0;
	m_bBeatingSuspect = false;
	m_bStopAndShootDisabledZone = false;
	m_bDragsPlayerFromCar = false;
	m_bZoneDisabled = false;
	field_628 = -1;
	m_nRoadblockVeh = nil;
	m_bThrowsSpikeTrap = false;
	m_pRopeEntity = nil;
	m_fAbseilPos = 0.0f;
	m_nHassleTimer = 0;
	field_61C = 0;
	field_624 = 0;
	m_pStinger = new CStinger;
	SetWeaponLockOnTarget(nil);
}

CCopPed::~CCopPed()
{
	ClearPursuit();
	m_pStinger->Remove();
	delete m_pStinger;
}

// Parameter should always be CPlayerPed, but it seems they considered making civilians arrestable at some point
void
CCopPed::SetArrestPlayer(CPed *player)
{
	if (!IsPedInControl() || !player)
		return;

	player->Say(SOUND_PED_PLAYER_REACTTOCOP);
	Say(SOUND_PED_ARREST_COP);

	if (player->EnteringCar()) {
		if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer)
			return;

		// why?
		player->bGonnaKillTheCarJacker = true;

		// Genius
		FindPlayerPed()->m_bCanBeDamaged = false;
		((CPlayerPed*)player)->m_pArrestingCop = this;
		this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop);

	} else if (player->m_nPedState != PED_DIE && player->m_nPedState != PED_DEAD && player->m_nPedState != PED_ARRESTED) {
		player->m_nLastPedState = player->m_nPedState;
		player->SetPedState(PED_ARRESTED);

		FindPlayerPed()->m_bCanBeDamaged = false;
		((CPlayerPed*)player)->m_pArrestingCop = this;
		this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop);
	}

	SetPedState(PED_ARREST_PLAYER);
	SetObjective(OBJECTIVE_NONE);
	m_prevObjective = OBJECTIVE_NONE;
	bIsPointingGunAt = false;
	m_pSeekTarget = player;
	m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget);
	SetCurrentWeapon(WEAPONTYPE_COLT45);
	if (player->InVehicle()) {
		player->m_pMyVehicle->m_nNumGettingIn = 0;
		player->m_pMyVehicle->m_nGettingInFlags = 0;
		player->m_pMyVehicle->bIsHandbrakeOn = true;
		player->m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED);
	}
	if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED || GetWeapon()->m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE)
		SetCurrentWeapon(WEAPONTYPE_COLT45);
}

void
CCopPed::ClearPursuit(void)
{
	CPlayerPed *player = FindPlayerPed();
	if (!player)
		return;

	CWanted *wanted = player->m_pWanted;
	int ourCopId = 0;
	bool foundMyself = false;
	int biggestCopId = 0;
	if (!m_bIsInPursuit)
		return;

	m_bIsInPursuit = false;
	for (int i = 0; i < Max(wanted->m_MaxCops, wanted->m_CurrentCops); ++i)  {
		if (!foundMyself && wanted->m_pCops[i] == this) {
			wanted->m_pCops[i] = nil;
			--wanted->m_CurrentCops;
			foundMyself = true;
			ourCopId = i;
			biggestCopId = i;
		} else {
			if (wanted->m_pCops[i])
				biggestCopId = i;
		}
	}
	if (foundMyself && biggestCopId > ourCopId) {
		wanted->m_pCops[ourCopId] = wanted->m_pCops[biggestCopId];
		wanted->m_pCops[biggestCopId] = nil;
	}
	m_objective = OBJECTIVE_NONE;
	m_prevObjective = OBJECTIVE_NONE;
	m_nLastPedState = PED_NONE;
	bIsRunning = false;
	bNotAllowedToDuck = false;
	bKindaStayInSamePlace = false;
	m_bStopAndShootDisabledZone = false;
	m_bDragsPlayerFromCar = false;
	m_bZoneDisabled = false;
	ClearObjective();
	if (IsPedInControl()) {
		if (!m_pMyVehicle || wanted->GetWantedLevel() != 0)  {
			if (m_pMyVehicle && (m_pMyVehicle->GetPosition() - GetPosition()).MagnitudeSqr() < sq(5.0f)) {
				m_nLastPedState = PED_IDLE;
				SetSeek((CEntity*)m_pMyVehicle, 2.5f);
			} else {
				m_nLastPedState = PED_WANDER_PATH;
				SetFindPathAndFlee(FindPlayerPed()->GetPosition(), 10000, true);
			}
		} else {
			SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
		}
	}
}

// TODO: I don't know why they needed that parameter.
void
CCopPed::SetPursuit(bool ignoreCopLimit)
{
	if (CTimer::GetTimeInMilliseconds() < field_61C)
		return;

	CWanted *wanted = FindPlayerPed()->m_pWanted;
	if (m_bIsInPursuit || !IsPedInControl())
		return;

	if (wanted->m_CurrentCops < wanted->m_MaxCops || ignoreCopLimit) {
		for (int i = 0; i < wanted->m_MaxCops; ++i) {
			if (!wanted->m_pCops[i]) {
				m_bIsInPursuit = true;
				++wanted->m_CurrentCops;
				wanted->m_pCops[i] = this;
				break;
			}
		}
		if (m_bIsInPursuit) {
			ClearObjective();
			m_prevObjective = OBJECTIVE_NONE;
			SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, FindPlayerPed());
			SetObjectiveTimer(0);
			bNotAllowedToDuck = true;
			bIsRunning = true;
			m_bStopAndShootDisabledZone = false;
		}
	}
}

void
CCopPed::ArrestPlayer(void)
{
	m_pVehicleAnim = nil;
	CPed *suspect = (CPed*)m_pSeekTarget;
	if (suspect) {
		if (suspect->CanSetPedState())
			suspect->SetPedState(PED_ARRESTED);

		if (suspect->bInVehicle && m_pMyVehicle && suspect->m_pMyVehicle == m_pMyVehicle) {

			// BUG? I will never understand why they used LINE_UP_TO_CAR_2...
			LineUpPedWithCar(LINE_UP_TO_CAR_2);
		}

		if (suspect && (suspect->m_nPedState == PED_ARRESTED || suspect->DyingOrDead() || suspect->EnteringCar())) {

			CAnimBlendAssociation *arrestAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ARREST);
			if (!arrestAssoc || arrestAssoc->blendDelta < 0.0f)
				CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ARREST, 4.0f);

			CVector suspMidPos;
			suspect->m_pedIK.GetComponentPosition(suspMidPos, PED_MID);
			m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(suspMidPos.x, suspMidPos.y,
				GetPosition().x, GetPosition().y);

			m_fRotationCur = m_fRotationDest;
			SetOrientation(0.0f, 0.0f, m_fRotationCur);
		} else {
			ClearPursuit();
		}
	} else {
		ClearPursuit();
	}
}

void
CCopPed::ScanForCrimes(void)
{
	CVehicle *playerVeh = FindPlayerVehicle();

	// Look for car alarms
	if (playerVeh && playerVeh->IsCar()) {
		if (playerVeh->IsAlarmOn()) {
			if ((playerVeh->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f))
				FindPlayerPed()->SetWantedLevelNoDrop(1);
		}
	}

	// Look for stolen cop cars
	if (!m_bIsInPursuit) {
		CPlayerPed *player = FindPlayerPed();
		if ((m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER)
			&& player->m_pWanted->GetWantedLevel() == 0) {

			if (player->m_pMyVehicle
#ifdef FIX_BUGS
				&& m_pMyVehicle == player->m_pMyVehicle
#endif
				&& player->m_pMyVehicle->bIsLawEnforcer)
				player->SetWantedLevelNoDrop(1);
		}
	}
}

void
CCopPed::CopAI(void)
{
	CWanted *wanted = FindPlayerPed()->m_pWanted;
	int wantedLevel = wanted->GetWantedLevel();
	CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed();

	if (wanted->m_bIgnoredByEveryone || wanted->m_bIgnoredByCops) {
		if (m_nPedState != PED_ARREST_PLAYER)
			ClearPursuit();

		return;
	}
	if (CCullZones::NoPolice() && m_bIsInPursuit && !m_bIsDisabledCop) {
		if (bHitSomethingLastFrame) {
			m_bZoneDisabled = true;
			m_bIsDisabledCop = true;
			bKindaStayInSamePlace = true;
			bIsRunning = false;
			bNotAllowedToDuck = false;
			bCrouchWhenShooting = false;
			SetIdle();
			ClearObjective();
			ClearPursuit();
			m_prevObjective = OBJECTIVE_NONE;
			m_nLastPedState = PED_NONE;
			SetAttackTimer(0);

			// Safe distance for disabled zone? Or to just make game easier?
			if (m_fDistanceToTarget > 15.0f)
				m_bStopAndShootDisabledZone = true;
		}
	} else if (m_bZoneDisabled && !CCullZones::NoPolice()) {
		m_bZoneDisabled = false;
		m_bIsDisabledCop = false;
		m_bStopAndShootDisabledZone = false;
		bKindaStayInSamePlace = false;
		bCrouchWhenShooting = false;
		bDuckAndCover = false;
		ClearPursuit();
	}
	if (wantedLevel > 0) {
		if (!m_bIsDisabledCop) {
			// Turn and shoot the player's vehicle, if possible
			if (!m_bIsInPursuit && !GetWeapon()->IsTypeMelee() && FindPlayerVehicle() && m_fDistanceToTarget < CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange) {
				if (FindPlayerVehicle()->m_vecMoveSpeed.Magnitude2D() > 0.1f) {
					CVector2D distToVeh = GetPosition() - FindPlayerVehicle()->GetPosition();
					distToVeh.Normalise();
					CVector2D vehSpeed = FindPlayerVehicle()->m_vecMoveSpeed;
					vehSpeed.Normalise();

					if (DotProduct2D(distToVeh, vehSpeed) > 0.8f) {
						SetLookFlag(playerOrHisVeh, true);
						SetMoveState(PEDMOVE_STILL);
						if (TurnBody()) {
							SetAttack(FindPlayerVehicle());
							SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 1000.0f));
							SetAttackTimer(CGeneral::GetRandomNumberInRange(200.0f, 300.0f));
						}
					} else if (m_nPedState == PED_ATTACK)
						RestorePreviousState();
				}
			}

			if (!m_bIsInPursuit || wanted->m_CurrentCops > wanted->m_MaxCops) {
				CCopPed *copFarthestToTarget = nil;
				float copFarthestToTargetDist = m_fDistanceToTarget;

				int oldCopNum = wanted->m_CurrentCops;
				int maxCops = wanted->m_MaxCops;
				
				for (int i = 0; i < Max(maxCops, oldCopNum); i++) {
					CCopPed *cop = wanted->m_pCops[i];
					if (cop && cop->m_fDistanceToTarget > copFarthestToTargetDist) {
						copFarthestToTargetDist = cop->m_fDistanceToTarget;
						copFarthestToTarget = wanted->m_pCops[i];
					}
				}

				if (m_bIsInPursuit) {
					if (copFarthestToTarget && oldCopNum > maxCops) {
						if (copFarthestToTarget == this && m_fDistanceToTarget > 10.0f) {
							ClearPursuit();
						} else if(copFarthestToTargetDist > 10.0f)
							copFarthestToTarget->ClearPursuit();
					}
				} else {
					if (oldCopNum < maxCops) {
						SetPursuit(true);
					} else {
						if (m_fDistanceToTarget <= 10.0f || copFarthestToTarget && m_fDistanceToTarget < copFarthestToTargetDist) {
							if (copFarthestToTarget && copFarthestToTargetDist > 10.0f)
								copFarthestToTarget->ClearPursuit();

							SetPursuit(true);
						}
					}
				}
			} else
				SetPursuit(false);

			if (!m_bIsInPursuit)
				return;

			if (wantedLevel > 1 && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED)
				SetCurrentWeapon(WEAPONTYPE_COLT45);
			else if (wantedLevel == 1 && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED && !FindPlayerPed()->m_pCurrentPhysSurface) {
				// i.e. if player is on top of car, cop will still use colt45.
				SetCurrentWeapon(GetWeaponSlot(WEAPONTYPE_NIGHTSTICK) >= 0 ? WEAPONTYPE_NIGHTSTICK : WEAPONTYPE_UNARMED);
			}

			if (m_bBeatingSuspect && GetWeapon()->m_eWeaponType == WEAPONTYPE_NIGHTSTICK)
				Say(SOUND_PED_PULLOUTWEAPON);

			if (FindPlayerVehicle()) {
				if (m_bBeatingSuspect) {
					--wanted->m_CopsBeatingSuspect;
					m_bBeatingSuspect = false;
				}
				if (m_fDistanceToTarget * FindPlayerSpeed().Magnitude() > 4.0f)
					ClearPursuit();
			}
			return;
		}
		SetCurrentWeapon(WEAPONTYPE_COLT45);
		CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType);
		float weaponRange = weaponInfo->m_fRange;
		SetLookFlag(playerOrHisVeh, true);
		TurnBody();
		if (!bIsDucking || bCrouchWhenShooting && GetCrouchFireAnim(weaponInfo)) {
			if (m_attackTimer >= CTimer::GetTimeInMilliseconds()) {
				if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT && !m_bZoneDisabled) {
					CVector targetDist = playerOrHisVeh->GetPosition() - GetPosition();
					if (m_fDistanceToTarget > 30.0f) {
						if (bIsDucking)
							ClearDuck();

						// Target is coming onto us
						if (DotProduct(playerOrHisVeh->m_vecMoveSpeed, targetDist) > 0.0f) {
							m_bIsDisabledCop = false;
							bKindaStayInSamePlace = false;
							bNotAllowedToDuck = false;
							bDuckAndCover = false;
							SetPursuit(false);
							SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, FindPlayerPed());
						}
					} else if (m_fDistanceToTarget < 5.0f
							&& (!FindPlayerVehicle() || FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr() < sq(1.f/200.f))) {
						m_bIsDisabledCop = false;
						bKindaStayInSamePlace = false;
						bNotAllowedToDuck = false;
						bDuckAndCover = false;
					} else {
						float dotProd;
						if (m_nRoadblockVeh) {
							dotProd = DotProduct2D(playerOrHisVeh->GetPosition() - m_nRoadblockVeh->GetPosition(), GetPosition() - m_nRoadblockVeh->GetPosition());
						} else
							dotProd = -1.0f;

						if(dotProd < 0.0f) {
							if (bIsDucking)
								ClearDuck();
							m_bIsDisabledCop = false;
							bKindaStayInSamePlace = false;
							bNotAllowedToDuck = false;
							bCrouchWhenShooting = false;
							bDuckAndCover = false;
							SetPursuit(false);
						} else {
							bIsPointingGunAt = true;
						}
					}
				}
			} else {
				if (m_fDistanceToTarget < weaponRange) {
					CVector gunPos = weaponInfo->m_vecFireOffset;
					TransformToNode(gunPos, PED_HANDR);

					CColPoint foundCol;
					CEntity *foundEnt;
					if (!CWorld::ProcessLineOfSight(gunPos, playerOrHisVeh->GetPosition(), foundCol, foundEnt,
						false, true, false, false, true, false, false)
						|| foundEnt && foundEnt == playerOrHisVeh) {

						SetWeaponLockOnTarget(playerOrHisVeh);
						SetAttack(playerOrHisVeh);
						SetShootTimer(CGeneral::GetRandomNumberInRange(500, 1000));
					}
					SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 300));
				}
				SetMoveState(PEDMOVE_STILL);
			}
		}
	} else {
		if (!m_bIsDisabledCop || m_bZoneDisabled) {
			if (m_nPedState != PED_AIM_GUN) {
				if (m_bIsInPursuit)
					ClearPursuit();

				if (IsPedInControl()) {
					// Entering the vehicle
					if (m_pMyVehicle && !bInVehicle) {
						if (m_pMyVehicle->IsLawEnforcementVehicle()) {
							if (m_pMyVehicle->pDriver) {
								if (m_pMyVehicle->pDriver->m_nPedType == PEDTYPE_COP) {
									if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER)
										SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle);
								} else if (m_pMyVehicle->pDriver->IsPlayer()) {
									FindPlayerPed()->SetWantedLevelNoDrop(1);
								}
							} else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) {
								SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
							}
						} else {
							m_pMyVehicle = nil;
							ClearObjective();
							SetWanderPath(CGeneral::GetRandomNumber() & 7);
						}
					} else {
						if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_HASSLE_CHAR && CharCreatedBy == RANDOM_CHAR) {
							for (int i = 0; i < m_numNearPeds; i++) {
								CPed *nearPed = m_nearPeds[i];
								if (nearPed->CharCreatedBy == RANDOM_CHAR) {
									if ((nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->IsGangMember())
										&& nearPed->IsPedInControl()) {

										bool anotherCopChasesHim = false;
										if (nearPed->m_nPedState == PED_FLEE_ENTITY) {
											if (nearPed->m_fleeFrom && nearPed->m_fleeFrom->IsPed() &&
												((CPed*)nearPed->m_fleeFrom)->m_nPedType == PEDTYPE_COP) {
												anotherCopChasesHim = true;
											}
										}
										if (!anotherCopChasesHim) {
											SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, nearPed);
											nearPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, this);
											nearPed->bBeingChasedByPolice = true;
											return;
										}
									} else {
										if (nearPed->m_nPedType != PEDTYPE_COP && !nearPed->IsPlayer()
											&& nearPed->IsPedInControl() && m_nHassleTimer < CTimer::GetTimeInMilliseconds()) {

											if (nearPed->m_objective == OBJECTIVE_NONE && nearPed->m_nPedState == PED_WANDER_PATH
												&& !nearPed->m_pLookTarget && nearPed->m_lookTimer < CTimer::GetTimeInMilliseconds()) {

												if ((GetPosition() - nearPed->GetPosition()).MagnitudeSqr() < sq(5.0f)) {

													if (CWorld::GetIsLineOfSightClear(GetPosition(), nearPed->GetPosition(),
														true, false, false, false, false, false, false)) {
														Say(SOUND_PED_COP_REACTION);
														SetObjective(OBJECTIVE_HASSLE_CHAR, nearPed);
														nearPed->SetObjective(OBJECTIVE_WAIT_ON_FOOT_FOR_COP, this);
														m_nHassleTimer = CTimer::GetTimeInMilliseconds() + 100000;
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		} else {
			if (m_bIsInPursuit && m_nPedState != PED_AIM_GUN)
				ClearPursuit();

			m_bIsDisabledCop = false;
			bKindaStayInSamePlace = false;
			bNotAllowedToDuck = false;
			bCrouchWhenShooting = false;
			bDuckAndCover = false;
			if (bIsDucking)
				ClearDuck();
			if (m_pMyVehicle)
				SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
		}
	}
}

void
CCopPed::ProcessControl(void)
{
	if (m_nCopType == COP_HELI_SWAT)
		ProcessHeliSwat();

	CPed::ProcessControl();

	if (m_bThrowsSpikeTrap) {
		if (CGame::currArea != AREA_MALL)
			ProcessStingerCop();
		return;
	}

	if (m_pStinger && m_pStinger->bIsDeployed && m_pStinger->m_nSpikeState == STINGERSTATE_DEPLOYED && CGame::currArea != AREA_MALL)
		m_pStinger->Process();

	if (bWasPostponed)
		return;

	if (m_nPedState == PED_DEAD) {
		ClearPursuit();
		m_objective = OBJECTIVE_NONE;
		return;
	}
	if (m_nPedState == PED_DIE)
		return;

	if (m_nPedState == PED_ARREST_PLAYER) {
		ArrestPlayer();
		return;
	}
	GetWeapon()->Update(m_audioEntityId, nil);
	if (m_moved.Magnitude() > 0.0f)
		Avoid();

	CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed();
	CPlayerPed *player = FindPlayerPed();

	m_fDistanceToTarget = (playerOrHisVeh->GetPosition() - GetPosition()).Magnitude();
	if (player->m_nPedState == PED_ARRESTED || player->DyingOrDead()) {
		if (m_fDistanceToTarget < 5.0f) {
			SetArrestPlayer(player);
			return;
		}
		if (IsPedInControl())
			SetIdle();
	}

	if (m_bIsInPursuit) {
		if (player->m_nPedState != PED_ARRESTED && !player->DyingOrDead()) {
			if (player->m_pWanted->m_CurrentCops == 1) {
				Say(SOUND_PED_COP_ALONE);
			} else {
				int numCopsNear = 0;
				for (int i = 0; i < player->m_numNearPeds; ++i) {
					CPed *nearPed = player->m_nearPeds[i];
					if (nearPed->m_nPedType == PEDTYPE_COP && nearPed->m_nPedState != PED_DEAD)
						++numCopsNear;
				}
				if (numCopsNear <= 3) {
					Say(SOUND_PED_COP_LITTLECOPSAROUND);
					if (!player->bInVehicle) {
						CVector distToPlayer = player->GetPosition() - GetPosition();
						if (distToPlayer.MagnitudeSqr() < sq(20.0f)) {
							player->Say(SOUND_PED_PLAYER_FARFROMCOPS);
							if (player->m_nPedState != PED_ATTACK && player->m_nPedState != PED_AIM_GUN) {
								player->SetLookFlag(this, false);
								player->SetLookTimer(1000);
							}
						}
					}
				} else if ((CGeneral::GetRandomNumber() % 16) == 1) {
					Say(SOUND_PED_COP_MANYCOPSAROUND);
				}
			}
		}
	}

	if (IsPedInControl()) {
		CopAI();
		/* switch (m_nCopType)
		{
			case COP_FBI:
				CopAI();
				break;
			case COP_SWAT:
				CopAI();
				break;
			case COP_ARMY:
				CopAI();
				break;
			default:
				CopAI();
				break;
		} */
	} else if (InVehicle()) {
		if (m_pMyVehicle->pDriver == this && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE &&
			CanPedDriveOff() && m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE) {

			CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle);
			m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE;
			m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS;
			m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 17;
		}
	}
	if (IsPedInControl() || m_nPedState == PED_DRIVING)
		ScanForCrimes();

	// They may have used goto to jump here in case of PED_ATTACK.
	if (m_nPedState == PED_IDLE || m_nPedState == PED_ATTACK) {
		if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT &&
			player && player->EnteringCar() && m_fDistanceToTarget < 1.3f) {
			SetArrestPlayer(player);
		}
	} else {
		if (m_nPedState == PED_SEEK_POS) {
			if (player->m_nPedState == PED_ARRESTED) {
				SetIdle();
				SetLookFlag(player, false);
				SetLookTimer(1000);
				RestorePreviousObjective();
			} else {
				if (player->m_pMyVehicle && player->m_pMyVehicle->m_nNumGettingIn != 0) {
					m_distanceToCountSeekDone = 1.3f;
				}

				if (!bDuckAndCover && Seek()) {
					CVehicle *playerVeh = FindPlayerVehicle();
					if (!playerVeh && player && player->EnteringCar()) {
						SetArrestPlayer(player);
					} else if (1.5f + GetPosition().z <= m_vecSeekPos.z || GetPosition().z - 0.3f >= m_vecSeekPos.z) {
						SetMoveState(PEDMOVE_STILL);
					} else if (playerVeh && playerVeh->CanPedEnterCar() && playerVeh->m_nNumGettingIn == 0) {
						SetCarJack(playerVeh);
					}
				}
			}
		} else if (m_nPedState == PED_SEEK_ENTITY) {
			if (!m_pSeekTarget) {
				RestorePreviousState();
			} else {
				m_vecSeekPos = m_pSeekTarget->GetPosition();
				if (Seek()) {
					if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_fDistanceToTarget < 2.5f && player) {
						if (player->m_nPedState == PED_ARRESTED || player->m_nPedState == PED_ENTER_CAR ||
							(player->m_nPedState == PED_CARJACK && m_fDistanceToTarget < 1.3f)) {
							SetArrestPlayer(player);
						} else
							RestorePreviousState();
					} else {
						RestorePreviousState();
					}

				}
			}
		}
	}

	if (m_pPointGunAt)
		Say(SOUND_PED_COP_UNK_129);

	if (m_bStopAndShootDisabledZone) {
		bool dontShoot = false;
		if (GetIsOnScreen()) {
			if (((CTimer::GetFrameCounter() + m_randomSeed) & 0x1F) == 17) {
				CEntity* foundBuilding = nil;
				CColPoint foundCol;
				CVector lookPos = GetPosition() + CVector(0.0f, 0.0f, 0.7f);
				CVector camPos = TheCamera.GetGameCamPosition();
				CWorld::ProcessLineOfSight(camPos, lookPos, foundCol, foundBuilding,
					true, false, false, false, false, false, false);

				// He's at least 15.0 far, in disabled zone, collided into somewhere (that's why m_bStopAndShootDisabledZone set),
				// and now has building on front of him. He's stupid, we don't need him.
				if (foundBuilding) {
					FlagToDestroyWhenNextProcessed();
					dontShoot = true;
				}
			}
		} else {
			FlagToDestroyWhenNextProcessed();
			dontShoot = true;
		}

		if (!dontShoot) {
			bStopAndShoot = true;
			bKindaStayInSamePlace = true;
			bIsPointingGunAt = true;
			SetAttack(m_pedInObjective);
		}
	}

	if (field_624 >= 2 && m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) {
		CVector centre = GetPosition() + CVector(0.f, 0.f, 0.65f);
		if (CWorld::TestSphereAgainstWorld(centre, 0.35f, this, true, false, false, false, false, false)) {
			field_624 = 0;
			m_bStopAndShootDisabledZone = true;
			ClearPursuit();
			SetObjective(OBJECTIVE_NONE);
			SetWanderPath(CGeneral::GetRandomNumberInRange(0,8));
			field_61C = CTimer::GetTimeInMilliseconds() + 30000;
		} else {
			field_624 = 0;
			if (GetWeapon()->IsTypeMelee()) {
				// TODO(Miami): enum
				for (int i = 3; i < 7; i++) {
					if (HasWeaponSlot(i)) {
						SetCurrentWeapon(i);
						break;
					}
				}
				SetMoveState(PEDMOVE_STILL);
				bStopAndShoot = true;
			}
		}
	} else if (CTimer::GetTimeStep() / 100.f <= m_fDistanceTravelled)
		field_624 = 0;
}

void
CCopPed::ProcessHeliSwat(void)
{
	CVector bestPos = GetPosition();
	SetPedState(PED_ABSEIL);
	CPedPlacement::FindZCoorForPed(&bestPos);
	if (GetPosition().z - 2.0f >= bestPos.z && m_pRopeEntity) {
		m_fAbseilPos += 0.003f * CTimer::GetTimeStep();
		m_vecMoveSpeed.z = -0.03f;
		m_vecTurnSpeed = CVector(0.f, 0.f, (m_randomSeed % 32) * 0.003f - 0.05f);
		CPhysical::ApplyTurnSpeed();
		GetMatrix().Reorthogonalise();
		CVector posOnRope;

		if (CRopes::FindCoorsAlongRope(m_nRopeID, m_fAbseilPos, &posOnRope)) {
			SetPosition(posOnRope);
		} else {
			bUsesCollision = true;
			m_vecMoveSpeed = CVector(0.f, 0.f, 0.f);
			SetPedState(PED_IDLE);
			m_nCopType = COP_SWAT;
			SetInTheAir();
			bKnockedUpIntoAir = true;
		}
		Say(SOUND_PED_COP_HELIPILOTPHRASE);
	} else {
		bUsesCollision = true;
		m_vecMoveSpeed = CVector(0.f, 0.f, 0.f);
		SetPedState(PED_IDLE);
		m_nCopType = COP_SWAT;
		SetInTheAir();
		bKnockedUpIntoAir = true;
	}
}

void
CCopPed::ProcessStingerCop(void)
{
	if (m_pStinger->bIsDeployed || FindPlayerVehicle() && (FindPlayerVehicle()->IsCar() || FindPlayerVehicle()->IsBike())) {
		if (m_pStinger->bIsDeployed) {
			m_pStinger->Process();
		} else {
			CVector2D vehDist = GetPosition() - FindPlayerVehicle()->GetPosition();
			CVector2D dirVehGoing = FindPlayerVehicle()->m_vecMoveSpeed;
			if (vehDist.MagnitudeSqr() < sq(30.0f)) {
				if (dirVehGoing.MagnitudeSqr() > 0.0f) {
					vehDist.Normalise();
					dirVehGoing.Normalise();
					if (DotProduct2D(vehDist, dirVehGoing) > 0.8f) {
						float angle = (CrossProduct2D(vehDist, dirVehGoing - vehDist) < 0.0f ?
							FindPlayerVehicle()->GetForward().Heading() - HALFPI :
							HALFPI + FindPlayerVehicle()->GetForward().Heading());

						SetHeading(angle);
						m_fRotationCur = angle;
						m_fRotationDest = angle;
						m_pStinger->Deploy(this);
					}
				}
			}
		}
	} else {
		ClearPursuit();
	}
}
