Files
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2886 lines
98 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// FILE : Restart.cpp
// PURPOSE : Handles restarting the player after he has been arrested or killed
// AUTHOR : Graeme
// CREATED : 02-08-00
//
/////////////////////////////////////////////////////////////////////////////////
#include "ai/navmesh/navmeshstore.h"
#include "ai/navmesh/pathserverbase.h"
#include "pathserver/PathServer.h"
#include "vehicleAi/pathFind.h"
#include "control/restart.h"
#include "core/game.h"
#include "Stats/StatsMgr.h"
#include "game/zones.h"
#include "network/players/NetworkPlayerMgr.h"
#include "peds/ped.h"
#include "peds/PedIntelligence.h"
#include "math/angmath.h"
#include "scene/world/gameWorld.h"
#include "scene/streamer/streamvolume.h"
#include "vector/vector2.h"
#include "Stats/StatsInterface.h"
#include "camera/CamInterface.h"
#include "camera/debug/DebugDirector.h"
#include "camera/helpers/Frame.h"
#include "physics/physics.h"
#include "fwscene/stores/boxstreamer.h"
AI_OPTIMISATIONS()
RestartPoint CRestart::HospitalRestarts[MAX_HOSPITAL_RESTARTS];
s32 CRestart::NumberOfHospitalRestarts;
RestartPoint CRestart::PoliceRestarts[MAX_POLICE_RESTARTS];
s32 CRestart::NumberOfPoliceRestarts;
SaveHouseStruct CRestart::SaveHouses[MAX_SAVE_HOUSES];
s32 CRestart::NumberOfSaveHouses;
bool CRestart::ms_bOverrideSavehouse = false;
Vector3 CRestart::ms_vCoordsOfSavehouseOverride;
float CRestart::ms_fHeadingOfSavehouseOverride = 0.0f;
Vector3 CRestart::NetworkRestartPoints[MAX_NETWORK_RESTARTS];
float CRestart::NetworkRestartHeadings[MAX_NETWORK_RESTARTS];
u32 CRestart::NetworkRestartTimes[MAX_NETWORK_RESTARTS];
s32 CRestart::NetworkRestartTeams[MAX_NETWORK_RESTARTS];
s32 CRestart::NumberOfNetworkRestarts;
bool CRestart::bOverrideRestart;
Vector3 CRestart::OverridePosition;
float CRestart::OverrideHeading;
bool CRestart::bPausePlayerRespawnAfterDeathArrest = false;
bool CRestart::bSuppressFadeOutAfterDeath = false;
bool CRestart::bSuppressFadeOutAfterArrest = false;
bool CRestart::bSuppressFadeInAfterDeathArrest = false;
Vector3 CRestart::ExtraHospitalRestartCoors;
float CRestart::ExtraHospitalRestartRadius;
float CRestart::ExtraHospitalRestartHeading;
Vector3 CRestart::ExtraPoliceStationRestartCoors;
float CRestart::ExtraPoliceStationRestartRadius;
float CRestart::ExtraPoliceStationRestartHeading;
bool CRestart::bOverrideRespawnBasePointForMission;
Vector3 CRestart::OverrideRespawnBasePointForMission;
bool CRestart::ms_bIgnoreNextRestart = false;
void CRestart::ResetData(void)
{
u16 loop;
for (loop = 0; loop < MAX_HOSPITAL_RESTARTS; loop++)
{
HospitalRestarts[loop].m_Coords = Vector3(0.0f, 0.0f, 0.0f);
HospitalRestarts[loop].m_Heading = 0.0f;
HospitalRestarts[loop].m_bEnabled = false;
}
for (loop = 0; loop < MAX_POLICE_RESTARTS; loop++)
{
PoliceRestarts[loop].m_Coords = Vector3(0.0f, 0.0f, 0.0f);
PoliceRestarts[loop].m_Heading = 0.0f;
PoliceRestarts[loop].m_bEnabled = false;
}
for (loop = 0; loop < MAX_SAVE_HOUSES; loop++)
{
SaveHouses[loop].Coords = Vector3(0.0f, 0.0f, 0.0f);
SaveHouses[loop].Heading = 0.0f;
SaveHouses[loop].RoomName[0] = '\0';
SaveHouses[loop].WhenToUse = 0;
SaveHouses[loop].m_PlayerModelNameHash = 0;
SaveHouses[loop].m_bEnabled = false;
SaveHouses[loop].m_bAvailableForAutosaves = false;
}
NumberOfHospitalRestarts = 0;
NumberOfPoliceRestarts = 0;
NumberOfSaveHouses = 0;
ms_bOverrideSavehouse = false;
ms_vCoordsOfSavehouseOverride.Zero();
ms_fHeadingOfSavehouseOverride = 0.0f;
NumberOfNetworkRestarts = 0;
bOverrideRestart = FALSE;
OverridePosition = Vector3(0.0f, 0.0f, 0.0f);
OverrideHeading = 0.0f;
bPausePlayerRespawnAfterDeathArrest = false;
bSuppressFadeOutAfterDeath = false;
bSuppressFadeOutAfterArrest = false;
bSuppressFadeInAfterDeathArrest = false;
ExtraHospitalRestartRadius = 0.0f;
ExtraPoliceStationRestartRadius = 0.0f;
bOverrideRespawnBasePointForMission = false;
ms_bIgnoreNextRestart = false;
}
void CRestart::Init(unsigned initMode)
{
if(initMode == INIT_SESSION)
{
ResetData();
}
}
void CRestart::Shutdown(unsigned /*shutdownMode*/)
{
ResetData();
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::AddHospitalRestartPoint
// PURPOSE : Adds a new point to the array of Hospital Restart points
// INPUTS : Vector describing the position of the point, player heading when restarted
/////////////////////////////////////////////////////////////////////////////////
s32 CRestart::AddHospitalRestartPoint(const Vector3 &NewPoint, const float NewHeading, s32 WhenToUse)
{
if (Verifyf(NumberOfHospitalRestarts < (MAX_HOSPITAL_RESTARTS - 1), "Too many hospital restarts"))
{
HospitalRestarts[NumberOfHospitalRestarts].m_Coords = NewPoint;
HospitalRestarts[NumberOfHospitalRestarts].m_Heading = NewHeading;
HospitalRestarts[NumberOfHospitalRestarts].m_WhenToUse = WhenToUse;
HospitalRestarts[NumberOfHospitalRestarts].m_bEnabled = true;
NumberOfHospitalRestarts++;
return (NumberOfHospitalRestarts-1);
}
return -1;
}
void CRestart::DisableHospitalRestartPoint(const s32 index, const bool bDisable)
{
if (Verifyf(index >= 0 && index < NumberOfHospitalRestarts, "Hospital restart Index is not valid"))
{
HospitalRestarts[index].m_bEnabled = !bDisable;
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::AddPoliceRestartPoint
// PURPOSE : Adds a new point to the array of Police Restart points
// INPUTS : Vector describing the position of the point, player heading when restarted
/////////////////////////////////////////////////////////////////////////////////
s32 CRestart::AddPoliceRestartPoint(const Vector3 &NewPoint, const float NewHeading, s32 WhenToUse)
{
if (Verifyf(NumberOfPoliceRestarts < (MAX_POLICE_RESTARTS - 1), "Too many police restarts"))
{
PoliceRestarts[NumberOfPoliceRestarts].m_Coords = NewPoint;
PoliceRestarts[NumberOfPoliceRestarts].m_Heading = NewHeading;
PoliceRestarts[NumberOfPoliceRestarts].m_WhenToUse = WhenToUse;
PoliceRestarts[NumberOfPoliceRestarts].m_bEnabled = true;
NumberOfPoliceRestarts++;
return (NumberOfPoliceRestarts-1);
}
return -1;
}
void CRestart::DisablePoliceRestartPoint(const s32 index, const bool bDisable)
{
if (Verifyf(index >= 0 && index < NumberOfPoliceRestarts, "Police restart Index is not valid"))
{
PoliceRestarts[index].m_bEnabled = !bDisable;
}
}
int CRestart::AddSaveHouse(const Vector3 &NewPoint, const float NewHeading, const char *pRoomName, s32 WhenToUse, u32 playerModelNameHash)
{
if (Verifyf(NumberOfSaveHouses < MAX_SAVE_HOUSES, "CRestart::AddSaveHouse - Too many Save Houses"))
{
SaveHouses[NumberOfSaveHouses].Coords = NewPoint;
SaveHouses[NumberOfSaveHouses].Heading = NewHeading;
strncpy(SaveHouses[NumberOfSaveHouses].RoomName, pRoomName, MAX_LENGTH_OF_ROOM_NAME);
SaveHouses[NumberOfSaveHouses].WhenToUse = WhenToUse;
SaveHouses[NumberOfSaveHouses].m_PlayerModelNameHash = playerModelNameHash;
SaveHouses[NumberOfSaveHouses].m_bEnabled = true;
SaveHouses[NumberOfSaveHouses].m_bAvailableForAutosaves = true;
NumberOfSaveHouses++;
return (NumberOfSaveHouses-1);
}
return -1;
}
void CRestart::SetSaveHouseEnabled(int SaveHouseIndex, bool bNewEnabledFlag, bool bAvailableForAutosaves)
{
if ( (SaveHouseIndex >= 0) && (SaveHouseIndex < NumberOfSaveHouses) )
{
SaveHouses[SaveHouseIndex].m_bEnabled = bNewEnabledFlag;
SaveHouses[SaveHouseIndex].m_bAvailableForAutosaves = bAvailableForAutosaves;
}
else
{
Assertf(0, "CRestart::SetSaveHouseEnabled - SaveHouseIndex is out of range");
}
}
void CRestart::SetSavehouseOverride(bool bOverride, const Vector3 &vecCoords, float fHeading)
{
ms_bOverrideSavehouse = bOverride;
ms_vCoordsOfSavehouseOverride = vecCoords;
ms_fHeadingOfSavehouseOverride = fHeading;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : AddNetworkRestartPoint
// PURPOSE : Adds a new point to the array of Network Restart points
// INPUTS : Vector describing the position of the point, player heading when restarted
/////////////////////////////////////////////////////////////////////////////////
void CRestart::AddNetworkRestartPoint(const Vector3 &NewPoint, const float NewHeading, const int Team)
{
Assertf(NumberOfNetworkRestarts < (MAX_NETWORK_RESTARTS - 1), "Too many network restarts");
NetworkRestartPoints[NumberOfNetworkRestarts] = NewPoint;
NetworkRestartHeadings[NumberOfNetworkRestarts] = NewHeading;
NetworkRestartTimes[NumberOfNetworkRestarts] = 0;
NetworkRestartTeams[NumberOfNetworkRestarts] = Team;
NumberOfNetworkRestarts++;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : RemoveAllNetworkRestartPoints
// PURPOSE : Remove all of the network restart points that have been set up so far
// INPUTS :
/////////////////////////////////////////////////////////////////////////////////
void CRestart::RemoveAllNetworkRestartPoints()
{
NumberOfNetworkRestarts=0;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindNetworkRestartPoint
// PURPOSE : Goes through all of the restart points and identifies the one that is
// furthest away from any of the players.
// INPUTS :
/////////////////////////////////////////////////////////////////////////////////
#define COOL_DOWN_TIME (20000) // Only consider a restart point this long after it has been used.
bool CRestart::FindNetworkRestartPoint(Vector3 *pResultCoors, float *pResultHeading, int Team)
{
float BiggestDistToClosestPlayer = 0.0f;
s32 BestRestartPoint = -1;
float BiggestDistToClosestPlayer_HotPoint = 0.0f;
s32 BestRestartPoint_HotPoint = -1;
for (s32 i = 0; i < NumberOfNetworkRestarts; i++)
{
float ClosestDist = 9999999.9f;
// First we make sure that the restart point belongs to the right team.
if (Team < 0 || NetworkRestartTeams[i] < 0 || Team == NetworkRestartTeams[i])
{
unsigned numRemotePhysicalPlayers = netInterface::GetNumRemotePhysicalPlayers();
const netPlayer * const *remotePhysicalPlayers = netInterface::GetRemotePhysicalPlayers();
for(unsigned index = 0; index < numRemotePhysicalPlayers; index++)
{
const CNetGamePlayer *pPlayer = SafeCast(const CNetGamePlayer, remotePhysicalPlayers[index]);
if(pPlayer->GetPlayerPed())
{
float Dist = (VEC3V_TO_VECTOR3(pPlayer->GetPlayerPed()->GetTransform().GetPosition()) - NetworkRestartPoints[i]).XYMag();
ClosestDist = rage::Min(ClosestDist, Dist);
}
}
if (fwTimer::GetTimeInMilliseconds() > NetworkRestartTimes[i] + COOL_DOWN_TIME)
{
if (ClosestDist > BiggestDistToClosestPlayer)
{
BiggestDistToClosestPlayer = ClosestDist;
BestRestartPoint = i;
}
}
else
{
if (ClosestDist > BiggestDistToClosestPlayer_HotPoint)
{
BiggestDistToClosestPlayer_HotPoint = ClosestDist;
BestRestartPoint_HotPoint = i;
}
}
}
}
// First we try to pick a point that hasn't been used recently.
if (BestRestartPoint >= 0)
{
*pResultCoors = NetworkRestartPoints[BestRestartPoint];
*pResultHeading = NetworkRestartHeadings[BestRestartPoint];
NetworkRestartTimes[BestRestartPoint] = fwTimer::GetTimeInMilliseconds();
return true;
}
// Now also consider points that have been used in the last 20 seconds or so.
if (BestRestartPoint_HotPoint >= 0)
{
*pResultCoors = NetworkRestartPoints[BestRestartPoint_HotPoint];
*pResultHeading = NetworkRestartHeadings[BestRestartPoint_HotPoint];
NetworkRestartTimes[BestRestartPoint_HotPoint] = fwTimer::GetTimeInMilliseconds();
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : FindNetworkRestartPointForScript
// PURPOSE : Goes through all of the restart points and identifies the one that is
// furthest away from any of the players.
// INPUTS :
/////////////////////////////////////////////////////////////////////////////////
void CRestart::FindNetworkRestartPointForScript(Vector3 *pResultCoors, float *pResultHeading, int Team)
{
#if __ASSERT
bool bResult =
#endif
FindNetworkRestartPoint(pResultCoors, pResultHeading, Team);
Assert(bResult);
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::FindClosestHospitalRestartPoint
// PURPOSE : Finds the nearest Hospital Restart point to the players current
// position (and returns a suitable ped creation point nearby)
// OUTPUTS : Point to restart the player at, player's heading when restarted
// RETURNS : False when spawn point returned uses the one passed in
/////////////////////////////////////////////////////////////////////////////////
bool CRestart::FindClosestHospitalRestartPoint(const Vector3& TestPoint, float fPlayerCurrentHeading, Vector3 *ClosestPoint, float *pHeading)
{
float ClosestMag, DiffDist;
s16 ClosestIndex = -1;
u16 loop;
Vector3 Diff;
Vector3 TestPoint2 = TestPoint;
if (ms_bIgnoreNextRestart)
{
*ClosestPoint = TestPoint;
*pHeading = fPlayerCurrentHeading;
bOverrideRestart = false;
ms_bIgnoreNextRestart = false;
return false;
}
if (bOverrideRestart)
{ // If the restart point has been overridden then use the override
// point instead of choosing a hospital one
*ClosestPoint = OverridePosition;
*pHeading = OverrideHeading;
bOverrideRestart = FALSE;
return true;
}
else
{
// This can be used by the level designers to make the player spawn near the point he took the mission
if (bOverrideRespawnBasePointForMission)
{
TestPoint2 = OverrideRespawnBasePointForMission;
bOverrideRespawnBasePointForMission = false;
}
if (ExtraHospitalRestartRadius > 0.0f &&
Vector2(TestPoint2.x - ExtraHospitalRestartCoors.x, TestPoint2.y - ExtraHospitalRestartCoors.y).Mag() < ExtraHospitalRestartRadius)
{
*ClosestPoint = ExtraHospitalRestartCoors;
*pHeading = ExtraHospitalRestartHeading;
return true;
}
// int MapAreaForTestPoint = CMapAreas::GetAreaIndexFromPosition(&TestPoint2); rage conv
// Choose the closest hospital restart point to the player
ClosestMag = 9999999.9f;
// Try to find the closest hospital restart point in the same zone
for (loop = 0; loop < NumberOfHospitalRestarts; loop++)
{
if( !HospitalRestarts[loop].m_bEnabled )
continue;
if (HospitalRestarts[loop].m_WhenToUse > StatsInterface::GetIntStat(STAT_CITIES_PASSED))
continue;
Diff = TestPoint2 - HospitalRestarts[loop].m_Coords;
DiffDist = Diff.Mag();
// The following bit of code favours respawn points that are in the same area as the player currently is.
// if(MapAreaForTestPoint != LEVEL_GENERIC && rage conv
// MapAreaForTestPoint != CMapAreas::GetAreaIndexFromPosition(&HospitalRestartPoints[loop]))
// DiffDist *= 6.0f;
if (DiffDist < ClosestMag)
{
ClosestMag = DiffDist;
ClosestIndex = loop;
}
}
if (ClosestIndex < 0)
{ // We didn't find a restart point. Presumably not set by script.
// Just return a point near the current coordinates
Assertf(0, "Couldn't find a hospital restart zone near the player");
// don't want to return an uninitialised vec, so set to current position in the absence of path nodes
*ClosestPoint = TestPoint;
*pHeading = 0.0f;
return false;
}
else
{
*ClosestPoint = HospitalRestarts[ClosestIndex].m_Coords;
*pHeading = HospitalRestarts[ClosestIndex].m_Heading;
}
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::FindClosestPoliceRestartPoint
// PURPOSE : Finds the nearest Police Restart point to the players current
// position (and returns a suitable ped creation point nearby)
// OUTPUTS : Point to restart the player at, player's heading when restarted
/////////////////////////////////////////////////////////////////////////////////
void CRestart::FindClosestPoliceRestartPoint(const Vector3& TestPoint, float fPlayerCurrentHeading, Vector3 *ClosestPoint, float *pHeading)
{
float ClosestMag, DiffDist;
s16 ClosestIndex = -1;
u16 loop;
Vector3 TestPoint2 = TestPoint;
Vector3 Diff;
if (ms_bIgnoreNextRestart)
{
*ClosestPoint = TestPoint;
*pHeading = fPlayerCurrentHeading;
bOverrideRestart = false;
ms_bIgnoreNextRestart = false;
return;
}
if (bOverrideRestart)
{ // If the restart point has been overridden then use the override
// point instead of choosing a police one
*ClosestPoint = OverridePosition;
*pHeading = OverrideHeading;
bOverrideRestart = FALSE;
return;
}
else
{
// This can be used by the level designers to make the player spawn near the point he took the mission
if (bOverrideRespawnBasePointForMission)
{
TestPoint2 = OverrideRespawnBasePointForMission;
bOverrideRespawnBasePointForMission = false;
}
if (ExtraPoliceStationRestartRadius > 0.0f &&
Vector2(TestPoint2.x - ExtraPoliceStationRestartCoors.x, TestPoint2.y - ExtraPoliceStationRestartCoors.y).Mag() < ExtraPoliceStationRestartRadius)
{
*ClosestPoint = ExtraPoliceStationRestartCoors;
*pHeading = ExtraPoliceStationRestartHeading;
return;
}
// eMapArea MapAreaForTestPoint = CMapAreas::GetAreaIndexFromPosition(&TestPoint2); rage conv
// Choose the closest police restart point to the player
ClosestMag = 99999.9f;
// Try to find the closest police restart point in the same zone
for (loop = 0; loop < NumberOfPoliceRestarts; loop++)
{
if( !PoliceRestarts[loop].m_bEnabled )
continue;
if (PoliceRestarts[loop].m_WhenToUse > StatsInterface::GetIntStat(STAT_CITIES_PASSED))
continue;
Diff = TestPoint2 - PoliceRestarts[loop].m_Coords;
DiffDist = Diff.Mag();
// The following bit of code favours respawn points that are in the same area as the player currently is.
// if(MapAreaForTestPoint != LEVEL_GENERIC && rage conv
// MapAreaForTestPoint != CMapAreas::GetAreaIndexFromPosition(&PoliceRestartPoints[loop]))
// DiffDist *= 6.0f;
if (DiffDist < ClosestMag)
{
ClosestMag = DiffDist;
ClosestIndex = loop;
}
}
if (ClosestIndex < 0)
{ // We didn't find a restart point. Presumably not set by script.
Assertf(0, "Couldn't find a police restart zone near the player");
// don't want to return an uninitialised vec, so set to current position in the absence of path nodes
*ClosestPoint = TestPoint;
*pHeading = 0.0f;
}
else
{
*ClosestPoint = PoliceRestarts[ClosestIndex].m_Coords;
*pHeading = PoliceRestarts[ClosestIndex].m_Heading;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::OverrideNextRestart
// PURPOSE : Sets a new point to be used next time the player is restarted
// The closest Hospital or Police Restart point is ignored
// INPUT : Restart point to use next time the player is killed or arrested
// Player's heading when restarted
/////////////////////////////////////////////////////////////////////////////////
void CRestart::OverrideNextRestart(const Vector3 &OverridePoint, const float OverrideHdng)
{
OverridePosition = OverridePoint;
OverrideHeading = OverrideHdng;
bOverrideRestart = TRUE;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CRestart::CancelOverrideRestart
// PURPOSE : If an override restart point has previously been set then it will
// be ignored and when the player dies or is arrested, the closest
// Hospital or Police Restart point will be used
/////////////////////////////////////////////////////////////////////////////////
void CRestart::CancelOverrideRestart(void)
{
bOverrideRestart = FALSE;
}
void CRestart::SetIgnoreNextRestart(bool bIgnore)
{
ms_bIgnoreNextRestart = bIgnore;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : SetRespawnBasePointForDurationOfMission
// PURPOSE : Sometimes a mission takes the player somewhere else. This command allows
// level designers to fix a respawn point for the duration of 1 mission.
// OUTPUTS :
/////////////////////////////////////////////////////////////////////////////////
void CRestart::SetRespawnPointForDurationOfMission(const Vector3& Coors)
{
bOverrideRespawnBasePointForMission = true;
OverrideRespawnBasePointForMission = Coors;
}
void CRestart::ClearRespawnPointForDurationOfMission()
{
bOverrideRespawnBasePointForMission = false;
}
const float CNetRespawnMgr::ms_fSearchMinDistDefault = 0.0f;
const float CNetRespawnMgr::ms_fSearchMaxDistDefault = 50.0f;
const float CNetRespawnMgr::ms_fSearchMaxDistCutoff = 100.0f;
const float CNetRespawnMgr::ms_fSearchHeadingNone = -1000.0f;
const Vector3 CNetRespawnMgr::ms_vSearchTargetPosNone(10000.0f, 10000.0f, 10000.0f);
float CNetRespawnMgr::ms_fMaxDistanceForBunchingPlayers = 60.0f;
float CNetRespawnMgr::ms_fMinDistanceForScoringEnemyPlayersFarther = 0.0f;
float CNetRespawnMgr::ms_fMaxDistanceForScoringEnemyPlayersFarther = 100.0f;
float CNetRespawnMgr::ms_fMinDistanceForScoringFriendlyPlayersCloser = 0.0f;
float CNetRespawnMgr::ms_fIdealDistanceForScoringFriendlyPlayersCloser = 20.0f;
float CNetRespawnMgr::ms_fMaxDistanceForScoringFriendlyPlayersCloser = 150.0f;
float CNetRespawnMgr::ms_fMinDistanceForScoringEnemyAiFarther = 0.0f;
float CNetRespawnMgr::ms_fMaxDistanceForScoringEnemyAiFarther = 100.0f;
float CNetRespawnMgr::ms_fMinDistanceForScoringFriendlyAiCloser = 0.0f;
float CNetRespawnMgr::ms_fIdealDistanceForScoringFriendlyAiCloser = 20.0f;
float CNetRespawnMgr::ms_fMaxDistanceForScoringFriendlyAiCloser = 150.0f;
float CNetRespawnMgr::ms_fMinScoreFOV = 0.0f;
float CNetRespawnMgr::ms_fMinScoreProximityToOrigin = 0.0f;
float CNetRespawnMgr::ms_fMinScoreTeamBunchingDirection = -1.0f;
float CNetRespawnMgr::ms_fMinScoreTeamBunchingPosition = 0.0f;
float CNetRespawnMgr::ms_fMinScoreEnemyPlayersFarther = -5.0f;
float CNetRespawnMgr::ms_fMinScoreFriendlyPlayersCloser = 0.0f;
float CNetRespawnMgr::ms_fMinScoreFriendlyPlayersTooClose = -2.0f;
float CNetRespawnMgr::ms_fMinScoreEnemyAiFarther = -5.0f;
float CNetRespawnMgr::ms_fMinScoreFriendlyAiCloser = 0.0f;
float CNetRespawnMgr::ms_fMinScoreFriendlyAiTooClose = -2.0f;
float CNetRespawnMgr::ms_fMinScoreRandomness = 0.0f;
float CNetRespawnMgr::ms_fMaxScoreFOV = 1.0f;
float CNetRespawnMgr::ms_fMaxScoreProximityToOrigin = 1.0f;
float CNetRespawnMgr::ms_fMaxScoreTeamBunchingDirection = 1.0f;
float CNetRespawnMgr::ms_fMaxScoreTeamBunchingPosition = 1.5f;
float CNetRespawnMgr::ms_fMaxScoreEnemyPlayersFarther = 0.0f;
float CNetRespawnMgr::ms_fMaxScoreFriendlyPlayersCloser = 1.5f;
float CNetRespawnMgr::ms_fMaxScoreFriendlyPlayersTooClose = 0.0f;
float CNetRespawnMgr::ms_fMaxScoreEnemyAiFarther = 0.0f;
float CNetRespawnMgr::ms_fMaxScoreFriendlyAiCloser = 1.5f;
float CNetRespawnMgr::ms_fMaxScoreFriendlyAiTooClose = 0.0f;
float CNetRespawnMgr::ms_fMaxScoreRandomness = 1.0f;
#if __BANK
bool CNetRespawnMgr::ms_bDebugUseAngledArea = false;
bool CNetRespawnMgr::ms_bAdvancedUsePlayerAsPed = true;
bool CNetRespawnMgr::ms_bAdvancedUsePlayerAsOrigin = true;
bool CNetRespawnMgr::ms_bAdvancedUseCameraAsOrigin = false;
bool CNetRespawnMgr::ms_bAdvancedUsePlayerAsTarget = false;
bool CNetRespawnMgr::ms_bAdvancedUseCameraAsTarget = false;
float CNetRespawnMgr::ms_fAdvancedMinSearchDistance = 0.0f;
float CNetRespawnMgr::ms_fAdvancedMaxSearchDistance = 50.0f;
bool CNetRespawnMgr::ms_bAdvancedFlagScriptCommand = false;
bool CNetRespawnMgr::ms_bAdvancedFlagIgnoreTargetHeading = false;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferCloseToSpawnOrigin = false;
bool CNetRespawnMgr::ms_bAdvancedFlagMaySpawnInInterior = true;
bool CNetRespawnMgr::ms_bAdvancedFlagMaySpawnInExterior = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferWideFOV = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferTeamBunching = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferEnemyPlayersFarther = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferFriendlyPlayersCloser = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferEnemyAiFarther = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferFriendlyAiCloser = true;
bool CNetRespawnMgr::ms_bAdvancedFlagPreferRandomness = true;
bool CNetRespawnMgr::ms_bDrawResultForPedPositionProjectionTesting = false;
float CNetRespawnMgr::ms_fTimeForPedPositionProjectionTesting = 3.0f;
Vector3 CNetRespawnMgr::ms_vResultForPedPositionProjectionTesting = VEC3_ZERO;
CNetRespawnMgr::OverridePositions CNetRespawnMgr::ms_aEnemyPlayerPositionsOverride;
CNetRespawnMgr::OverridePositions CNetRespawnMgr::ms_aFriendlyPlayerPositionsOverride;
CNetRespawnMgr::OverridePositions CNetRespawnMgr::ms_aEnemyAiPositionsOverride;
CNetRespawnMgr::OverridePositions CNetRespawnMgr::ms_aFriendlyAiPositionsOverride;
CNetRespawnMgr::TSpawnRecord CNetRespawnMgr::ms_SpawnRecord;
#endif
RegdConstPed CNetRespawnMgr::m_pPed;
Vector3 CNetRespawnMgr::m_vSearchOrigin = VEC3_ZERO;
Vector3 CNetRespawnMgr::m_vSearchTargetPos = VEC3_ZERO;
float CNetRespawnMgr::m_fSearchOptimalHeading = CNetRespawnMgr::ms_fSearchHeadingNone;
float CNetRespawnMgr::m_fSearchMinDist = 0.0f;
float CNetRespawnMgr::m_fSearchMaxDist = 0.0f;
int CNetRespawnMgr::m_iTeam = -1;
CScriptAngledArea CNetRespawnMgr::m_SearchAngledArea;
Vector3 CNetRespawnMgr::m_vSearchMins;
Vector3 CNetRespawnMgr::m_vSearchMaxs;
s32 CNetRespawnMgr::m_iSearchVolumeType;
fwFlags16 CNetRespawnMgr::m_uFlags = 0;
u32 CNetRespawnMgr::m_iSearchStartTimeMs = 0;
u32 CNetRespawnMgr::m_iSeachNumFrames = 0;
Vector3 CNetRespawnMgr::m_vLargestBunchOfEnemyPlayersPosition = VEC3_ZERO;
Vector3 CNetRespawnMgr::m_vLargestBunchOfFriendlyPlayersPosition = VEC3_ZERO;
bool CNetRespawnMgr::m_bSearchActive = false;
bool CNetRespawnMgr::m_bSearchComplete = false;
bool CNetRespawnMgr::m_bSearchSuccessful = false;
atRangeArray<TNavMeshIndex, CNetRespawnMgr::ms_iMaxStoredNavmeshes> CNetRespawnMgr::m_Timeslice_StoredNavmeshes;
s32 CNetRespawnMgr::m_Timeslice_iCurrentNavmesh;
s32 CNetRespawnMgr::m_Timeslice_iCurrentPoly;
s32 CNetRespawnMgr::m_iNumBestSpawnPoints = 0;
atRangeArray<CNetRespawnMgr::TSpawnPoint, CNetRespawnMgr::ms_iMaxSpawnPoints + 1> CNetRespawnMgr::m_BestSpawnPoints;
float CNetRespawnMgr::m_fCachedNavmeshLoadDistance = -1.0f;
const float CNetRespawnMgr::ms_fZCutoff = 500.0f;
CNetRespawnMgr::CachedPositions CNetRespawnMgr::m_PlayerPositionsFromNetwork;
CNetRespawnMgr::CachedPositions CNetRespawnMgr::m_AiPositionsFromWorld;
#if __BANK
bool CNetRespawnMgr::m_bDebugSearch = false;
bool CNetRespawnMgr::m_bDebugScores = false;
bool CNetRespawnMgr::ms_bUsePlayerOverrides = false;
bool CNetRespawnMgr::ms_bUseAiOverrides = false;
#endif
#define RESPAWNMGR_NUM_HEADINGS 8
#define SCRIPTERS_FORGOT_TO_CANCEL_SEARCH_MS 10000
CNetRespawnMgr::CNetRespawnMgr()
{
}
CNetRespawnMgr::~CNetRespawnMgr()
{
}
// NAME : Init
// PURPOSE : Initialises the system
void CNetRespawnMgr::Init(unsigned int UNUSED_PARAM(initMode))
{
}
// NAME : Shutdown
// PURPOSE : Shuts down the system
void CNetRespawnMgr::Shutdown(unsigned int UNUSED_PARAM(shutdownMode))
{
}
// NAME : Update
// PURPOSE : Updates down the system
void CNetRespawnMgr::Update()
{
if(m_bSearchActive)
{
if(StreamAroundSearchOrigin())
{
UpdateSearch();
}
}
else
{
u32 iTimeDelta = fwTimer::GetTimeInMilliseconds() - m_iSearchStartTimeMs;
if(iTimeDelta > SCRIPTERS_FORGOT_TO_CANCEL_SEARCH_MS && CPathServer::GetIsNavMeshRegionRequired(NMR_NetworkRespawnMgr))
{
Assertf(false, "Script forgot to cancel network respawn manager; automatically unloading requested navmesh regions.");
CPathServer::RemoveNavMeshRegion(NMR_NetworkRespawnMgr, THREAD_INVALID);
}
}
// Display the best candidates so far
#if __BANK && DEBUG_DRAW
if(m_bDebugSearch)
{
//Check if we are using player overrides.
if(ms_bUsePlayerOverrides)
{
//Draw the enemy player positions.
for(int i = 0; i < ms_aEnemyPlayerPositionsOverride.GetCount(); ++i)
{
grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(ms_aEnemyPlayerPositionsOverride[i].m_vPosition + ZAXIS), VECTOR3_TO_VEC3V(ms_aEnemyPlayerPositionsOverride[i].m_vPosition - ZAXIS), 0.25f, Color_red, false);
}
//Draw the friendly player positions.
for(int i = 0; i < ms_aFriendlyPlayerPositionsOverride.GetCount(); ++i)
{
grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(ms_aFriendlyPlayerPositionsOverride[i].m_vPosition + ZAXIS), VECTOR3_TO_VEC3V(ms_aFriendlyPlayerPositionsOverride[i].m_vPosition - ZAXIS), 0.25f, Color_green, false);
}
}
//Check if we are using AI overrides.
if(ms_bUseAiOverrides)
{
//Draw the enemy AI positions.
for(int i = 0; i < ms_aEnemyAiPositionsOverride.GetCount(); ++i)
{
grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(ms_aEnemyAiPositionsOverride[i].m_vPosition + ZAXIS), VECTOR3_TO_VEC3V(ms_aEnemyAiPositionsOverride[i].m_vPosition - ZAXIS), 0.125f, Color_red, false);
}
//Draw the friendly AI positions.
for(int i = 0; i < ms_aFriendlyAiPositionsOverride.GetCount(); ++i)
{
grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(ms_aFriendlyAiPositionsOverride[i].m_vPosition + ZAXIS), VECTOR3_TO_VEC3V(ms_aFriendlyAiPositionsOverride[i].m_vPosition - ZAXIS), 0.125f, Color_green, false);
}
}
//Draw the best bunch of enemy players position.
if(m_vLargestBunchOfEnemyPlayersPosition.IsNonZero())
{
grcDebugDraw::Sphere(m_vLargestBunchOfEnemyPlayersPosition, 1.0f, Color_red, false);
}
//Draw the best bunch of friendly players position.
if(m_vLargestBunchOfFriendlyPlayersPosition.IsNonZero())
{
grcDebugDraw::Sphere(m_vLargestBunchOfFriendlyPlayersPosition, 1.0f, Color_green, false);
}
//Draw the spawn points.
for(int s=0; s<m_iNumBestSpawnPoints; s++)
{
TSpawnPoint sp = m_BestSpawnPoints[s];
Vector3 vHdg(-rage::Sinf(sp.m_fHeading), rage::Cosf(sp.m_fHeading), 0.0f);
//Calculate the color.
//We want the color to range from green (best) to red (worst).
Color32 color(0, 0, 0, 255);
//Calculate the lerp value.
//This will range from 0 (best) to 1 (worst).
float fLerp = (float)s / (float)m_iNumBestSpawnPoints;
//Calculate the red component.
color.SetRed(Lerp(fLerp, 0, 255));
//Calculate the green component.
color.SetGreen(Lerp(fLerp, 255, 0));
grcDebugDraw::Line(sp.m_vPos, sp.m_vPos+(ZAXIS*3.0f), color, color, 1);
grcDebugDraw::Arrow(
VECTOR3_TO_VEC3V(sp.m_vPos+(ZAXIS*3.0f)),
VECTOR3_TO_VEC3V(sp.m_vPos+(ZAXIS*3.0f)+(vHdg*2.0f)),
0.75f,
color,
1);
//Check if we should debug scores.
if(m_bDebugScores)
{
//Create storage for the strings.
char scoresText[64];
//Generate the coordinate.
Vector3 vCoords = sp.m_vPos + (ZAXIS * 4.0f);
//Generate the color.
Color32 color = CRGBA(255, 128, 128, 255);
//Keep track of the line.
int iLine = 0;
formatf(scoresText, "Total: %.2f", sp.m_Score.m_fTotal);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
//Change the color.
color = CRGBA(255, 192, 128, 255);
formatf(scoresText, "FOV: %.2f", sp.m_Score.m_fFOV);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Proximity To Origin: %.2f", sp.m_Score.m_fProximityToOrigin);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Team Bunching: %.2f", sp.m_Score.m_fTeamBunching);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Enemy Players Farther: %.2f", sp.m_Score.m_fEnemyPlayersFarther);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Friendly Players Closer: %.2f", sp.m_Score.m_fFriendlyPlayersCloser);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Enemy AI Farther: %.2f", sp.m_Score.m_fEnemyAiFarther);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Friendly AI Closer: %.2f", sp.m_Score.m_fFriendlyAiCloser);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
formatf(scoresText, "Randomness: %.2f", sp.m_Score.m_fRandomness);
grcDebugDraw::Text(vCoords, color, 0, iLine * grcDebugDraw::GetScreenSpaceTextHeight(), scoresText);
++iLine;
}
}
}
if(ms_bDrawResultForPedPositionProjectionTesting)
{
grcDebugDraw::Sphere(ms_vResultForPedPositionProjectionTesting, 1.0f, Color_purple);
}
#endif
}
// NAME : StartSearch
// PURPOSE : Start a search with the given parameters (spherical area)
void CNetRespawnMgr::StartSearch(const Vector3 & vOrigin, const Vector3 & vTargetPos, const float fMinDist, const float fMaxDist, const fwFlags16 uFlags)
{
//Create the input.
StartSearchInput input(vOrigin);
input.m_vTargetPos = vTargetPos;
input.m_fMinDist = fMinDist;
input.m_fMaxDist = fMaxDist;
input.m_uFlags = uFlags;
//Start the search.
StartSearch(input);
}
// NAME : StartSearch
// PURPOSE : Start a search with the given parameters (angled area)
void CNetRespawnMgr::StartSearch(const Vector3 & vAngledAreaPt1, const Vector3 & vAngledAreaPt2, const float fAngledAreaWidth, const Vector3 & vTargetPos, const fwFlags16 uFlags)
{
//Create the input.
StartSearchInput input(vAngledAreaPt1, vAngledAreaPt2, fAngledAreaWidth);
input.m_vTargetPos = vTargetPos;
input.m_uFlags = uFlags;
//Start the search.
StartSearch(input);
}
// NAME : StartSearch
// PURPOSE : Start a search with the given parameters
void CNetRespawnMgr::StartSearch(const StartSearchInput& rInput)
{
Assert(!m_bSearchActive);
m_iSearchStartTimeMs = fwTimer::GetTimeInMilliseconds();
m_iSeachNumFrames = 2;
m_pPed = rInput.m_pPed;
switch(rInput.m_iSearchVolumeType)
{
case SEARCH_VOLUME_SPHERE:
{
m_iSearchVolumeType = SEARCH_VOLUME_SPHERE;
m_vSearchOrigin = rInput.m_vOrigin;
m_fSearchMinDist = Min(rInput.m_fMinDist, ms_fSearchMaxDistCutoff);
m_fSearchMaxDist = Min(rInput.m_fMaxDist, ms_fSearchMaxDistCutoff);
m_vSearchMins = m_vSearchOrigin - Vector3(m_fSearchMaxDist, m_fSearchMaxDist, m_fSearchMaxDist);
m_vSearchMaxs = m_vSearchOrigin + Vector3(m_fSearchMaxDist, m_fSearchMaxDist, m_fSearchMaxDist);
break;
}
case SEARCH_VOLUME_ANGLED_AREA:
{
m_iSearchVolumeType = SEARCH_VOLUME_ANGLED_AREA;
Vector3 vSearchExtents[2];
m_SearchAngledArea.Set(rInput.m_vAngledAreaPt1, rInput.m_vAngledAreaPt2, rInput.m_fAngledAreaWidth, vSearchExtents);
// Expand minmax so we have chance of visiting floor polygons if designers have placed points on ground
vSearchExtents[0].z = Min(rInput.m_vAngledAreaPt1.z, rInput.m_vAngledAreaPt2.z) - 1.0f;
vSearchExtents[1].z = Max(rInput.m_vAngledAreaPt1.z, rInput.m_vAngledAreaPt2.z) + 0.5f;
m_vSearchMins = vSearchExtents[0];
m_vSearchMaxs = vSearchExtents[1];
// Derive the search origin & max distance from the min/max of the angled area
m_vSearchOrigin = (m_vSearchMins + m_vSearchMaxs) * 0.5f;
m_fSearchMaxDist = (vSearchExtents[0] - m_vSearchOrigin).Mag();
break;
}
default:
{
FastAssert(false);
break;
}
}
m_vSearchTargetPos = rInput.m_uFlags.IsFlagSet(FLAG_IGNORE_TARGET_HEADING) ? ms_vSearchTargetPosNone : rInput.m_vTargetPos;
m_iTeam = rInput.m_iTeam;
m_uFlags = rInput.m_uFlags;
if (m_uFlags.IsFlagSet(FLAG_IGNORE_Z))
{
m_vSearchMins.z = -MINMAX_MAX_FLOAT_VAL;
m_vSearchMaxs.z = MINMAX_MAX_FLOAT_VAL;
}
else
{
m_vSearchMins.z = m_vSearchOrigin.z - ms_fZCutoff;
m_vSearchMaxs.z = m_vSearchOrigin.z + ms_fZCutoff;
}
//If a ped is specified, but no team, assign the ped's team.
if(m_iTeam < 0 && m_pPed)
{
m_iTeam = GetNetTeamForPed(*m_pPed);
}
m_bSearchActive = true;
m_bSearchComplete = false;
m_bSearchSuccessful = false;
m_iNumBestSpawnPoints = 0;
for(int n=0; n<m_Timeslice_StoredNavmeshes.GetMaxCount(); n++)
m_Timeslice_StoredNavmeshes[n] = NAVMESH_NAVMESH_INDEX_NONE;
m_Timeslice_iCurrentNavmesh = -1;
m_Timeslice_iCurrentPoly = -1;
m_fCachedNavmeshLoadDistance = CPathServer::GetNavMeshLoadDistance();
CPathServer::SetNavMeshLoadDistance(m_fCachedNavmeshLoadDistance/2.0f);
CPathServer::AddNavMeshRegion( NMR_NetworkRespawnMgr, THREAD_INVALID, m_vSearchOrigin.x, m_vSearchOrigin.y, m_fSearchMaxDist );
}
// NAME : CancelSearch
// PURPOSE : Abort current search
void CNetRespawnMgr::CancelSearch()
{
m_bSearchActive = false;
m_bSearchComplete = false;
m_bSearchSuccessful = false;
if(m_fCachedNavmeshLoadDistance >= 0.0f)
CPathServer::SetNavMeshLoadDistance(m_fCachedNavmeshLoadDistance);
if( CPathServer::GetIsNavMeshRegionRequired( NMR_NetworkRespawnMgr) )
{
CPathServer::RemoveNavMeshRegion( NMR_NetworkRespawnMgr, THREAD_INVALID );
}
m_fCachedNavmeshLoadDistance = -1.0f;
}
// NAME : FindLargestBunchOfPlayers
// PURPOSE : Finds the largest bunch of players
bool CNetRespawnMgr::FindLargestBunchOfPlayers(bool bFriendly, Vector3& vPosition)
{
//Find the enemy and friendly player positions.
Positions enemyPlayerPositions;
Positions friendlyPlayerPositions;
FindEnemyAndFriendlyPlayerPositions(enemyPlayerPositions, friendlyPlayerPositions);
//Choose the positions.
Positions& rPositions = bFriendly ? friendlyPlayerPositions : enemyPlayerPositions;
//Keep track of the largest bunch.
struct Bunch
{
Bunch()
: m_iSize(0)
, m_vPosition(VEC3_ZERO)
{
}
int m_iSize;
Vector3 m_vPosition;
};
Bunch largestBunch;
//Iterate over the positions.
for(int i = 0; i < rPositions.GetCount(); ++i)
{
//Grab the start position.
Vector3 vStartPosition = rPositions[i].m_vPosition;
//Start a bunch.
int iBunchSize = 1;
Vector3 vTotalBunchPosition = vStartPosition;
//Iterate over the positions.
for(int j = 0; j < rPositions.GetCount(); ++j)
{
//Do not count the same position twice.
if(i == j)
{
continue;
}
//Grab the other position.
Vector3 vOtherPosition = rPositions[j].m_vPosition;
//Ensure the distance is within the threshold.
float fDistSq = vStartPosition.Dist2(vOtherPosition);
float fMaxDistSq = square(ms_fMaxDistanceForBunchingPlayers);
if(fDistSq > fMaxDistSq)
{
continue;
}
//Add to the bunch.
iBunchSize++;
vTotalBunchPosition += vOtherPosition;
}
//Ensure the bunch is the larger.
if(iBunchSize <= largestBunch.m_iSize)
{
continue;
}
//Assign the largest bunch.
largestBunch.m_iSize = iBunchSize;
largestBunch.m_vPosition = vTotalBunchPosition / (float)iBunchSize;
}
//Ensure the largest bunch is valid.
if(largestBunch.m_iSize <= 0)
{
return false;
}
//Assign the position.
vPosition = largestBunch.m_vPosition;
return true;
}
// NAME : IsSearchActive
// PURPOSE : Returns whether a search is currently active
bool CNetRespawnMgr::IsSearchActive()
{
return m_bSearchActive;
}
// NAME : IsSearchComplete
// PURPOSE : Returns whether the search has completed processing
bool CNetRespawnMgr::IsSearchComplete()
{
return m_bSearchComplete;
}
// NAME : HasSearchFailed
// PURPOSE : Returns whether the search has completed unsuccessfully
bool CNetRespawnMgr::WasSearchSuccessful()
{
Assertf(!m_bSearchActive, "script called WasSearchSuccessful() when search was still active.");
Assertf(m_bSearchComplete, "script called WasSearchSuccessful() when search was not yet complete.");
return m_bSearchSuccessful;
}
// NAME : GetNumSearchResults
// PURPOSE : Returns the number of search results found
int CNetRespawnMgr::GetNumSearchResults()
{
Assertf(!m_bSearchActive, "script called GetSearchResults() when search was still active.");
Assertf(m_bSearchComplete, "script called GetSearchResults() when search was not yet complete.");
Assertf(m_bSearchSuccessful, "script called GetSearchResults() when search was not successful.");
return m_iNumBestSpawnPoints;
}
// NAME : GetSearchResults
// PURPOSE : Retrieves the search results
void CNetRespawnMgr::GetSearchResults(const int iIndex, Vector3 & vSpawnPosOut, float & fSpawnHeadingOut)
{
Assertf(!m_bSearchActive, "script called GetSearchResults() when search was still active.");
Assertf(m_bSearchComplete, "script called GetSearchResults() when search was not yet complete.");
Assertf(m_bSearchSuccessful, "script called GetSearchResults() when search was not successful.");
Assert(iIndex >= 0 && iIndex < m_iNumBestSpawnPoints);
vSpawnPosOut = m_BestSpawnPoints[iIndex].m_vPos;
fSpawnHeadingOut = m_BestSpawnPoints[iIndex].m_fHeading;
}
// NAME : GetSearchResultFlags
// PURPOSE : Retrieves the flags associated with a spawn point result
s32 CNetRespawnMgr::GetSearchResultFlags(const int iIndex)
{
Assertf(!m_bSearchActive, "script called GetSearchResultFlags() when search was still active.");
Assertf(m_bSearchComplete, "script called GetSearchResultFlags() when search was not yet complete.");
Assertf(m_bSearchSuccessful, "script called GetSearchResultFlags() when search was not successful.");
Assert(iIndex >= 0 && iIndex < m_iNumBestSpawnPoints);
return m_BestSpawnPoints[iIndex].m_iFlags;
}
// NAME : StreamAroundSearchOrigin
// PURPOSE : Requests/streams collision & navmesh around the search origin
// Returns true once all the required data has been streamed into memory
bool CNetRespawnMgr::StreamAroundSearchOrigin()
{
static bool bStreamNavmesh = true;
bool bNavmeshComplete = !bStreamNavmesh;
if(bStreamNavmesh)
{
bNavmeshComplete = CPathServer::AreAllNavMeshRegionsLoaded();
}
return (bNavmeshComplete);
}
// NAME : UpdateSearch
// PURPOSE : Internally updates the search process
bool CNetRespawnMgr::UpdateSearch()
{
Assert(m_bSearchActive);
Assert(!m_bSearchComplete);
// Sanity-check the search duration; if designers forget to cancel a respawn search it'll stuff up the game beyond belief..
u32 iTimeDelta = fwTimer::GetTimeInMilliseconds() - m_iSearchStartTimeMs;
if(m_uFlags.IsFlagSet(FLAG_SCRIPT_COMMAND) && iTimeDelta > SCRIPTERS_FORGOT_TO_CANCEL_SEARCH_MS)
{
Assertf(false, "CNetRespawnMgr : Script respawn search has been going on for over 10 secs. Did you forget to cancel it with \"NETWORK_CANCEL_RESPAWN_SEARCH\"? Quitting search now..");
CancelSearch();
return false;
}
//Update the bunches.
UpdateLargestBunches();
LOCK_NAVMESH_DATA
// Countdown at start
if(m_iSeachNumFrames > 0)
{
m_iSeachNumFrames--;
return true;
}
if(m_Timeslice_iCurrentNavmesh == -1)
{
s32 iNumMeshes = 0;
aiNavMeshStore * pNavMeshStore = fwPathServer::GetNavMeshStore(kNavDomainRegular);
for(s32 n=0; n<pNavMeshStore->GetMaxMeshIndex(); n++)
{
CNavMesh * pNavMesh = pNavMeshStore->GetMeshByIndex(n);
if(pNavMesh && geomBoxes::TestAABBtoAABB(m_vSearchMins, m_vSearchMaxs, pNavMesh->GetQuadTree()->m_Mins, pNavMesh->GetQuadTree()->m_Maxs) )
{
m_Timeslice_StoredNavmeshes[iNumMeshes++] = n;
if(iNumMeshes >= m_Timeslice_StoredNavmeshes.GetMaxCount())
{
break;
}
}
}
m_Timeslice_iCurrentNavmesh = 0;
m_Timeslice_iCurrentPoly = 0;
}
static dev_s32 iNumPolysPerIteration_Default = 250;
static dev_s32 iNumPolysPerIteration_Faded = 2500;
dev_s32 iNumPolysPerIteration = (camInterface::GetFadeLevel() > 0.9f) ? iNumPolysPerIteration_Faded : iNumPolysPerIteration_Default;
if(m_Timeslice_iCurrentNavmesh < ms_iMaxStoredNavmeshes &&
m_Timeslice_StoredNavmeshes[m_Timeslice_iCurrentNavmesh] != NAVMESH_NAVMESH_INDEX_NONE)
{
TShortMinMax searchMinMax;
searchMinMax.SetFloat(
Max(m_vSearchMins.x, -MINMAX_MAX_FLOAT_VAL),
Max(m_vSearchMins.y, -MINMAX_MAX_FLOAT_VAL),
Max(m_vSearchMins.z, -MINMAX_MAX_FLOAT_VAL),
Max(m_vSearchMaxs.x, MINMAX_MAX_FLOAT_VAL),
Max(m_vSearchMaxs.y, MINMAX_MAX_FLOAT_VAL),
Max(m_vSearchMaxs.z, MINMAX_MAX_FLOAT_VAL));
CNavMesh * pNavMesh = fwPathServer::GetNavMeshFromIndex(m_Timeslice_StoredNavmeshes[m_Timeslice_iCurrentNavmesh], kNavDomainRegular);
if(!pNavMesh)
{
m_Timeslice_iCurrentNavmesh++;
m_Timeslice_iCurrentPoly = 0;
}
else
{
s32 iNumIters = iNumPolysPerIteration;
while(m_Timeslice_iCurrentPoly < pNavMesh->GetNumPolys() && iNumIters > 0)
{
TNavMeshPoly * pPoly = pNavMesh->GetPoly(m_Timeslice_iCurrentPoly);
if(pPoly->GetIsNetworkSpawnCandidate() && searchMinMax.Intersects(pPoly->m_MinMax))
{
if( (!m_uFlags.IsFlagSet(FLAG_MAY_SPAWN_IN_INTERIOR) && pPoly->GetIsInterior()) ||
(!m_uFlags.IsFlagSet(FLAG_MAY_SPAWN_IN_EXTERIOR) && !pPoly->GetIsInterior()))
{
m_Timeslice_iCurrentPoly++;
continue;
}
Vector3 vCentroid;
pNavMesh->GetPolyCentroid(m_Timeslice_iCurrentPoly, vCentroid);
// Test candidate point for containment within our spawn volume
bool bWithinVolume = false;
switch(m_iSearchVolumeType)
{
case SEARCH_VOLUME_SPHERE:
{
float fDistSqr = (vCentroid - m_vSearchOrigin).XYMag2();
if(fDistSqr > m_fSearchMinDist*m_fSearchMinDist && fDistSqr < m_fSearchMaxDist*m_fSearchMaxDist)
{
bWithinVolume = true;
}
break;
}
case SEARCH_VOLUME_ANGLED_AREA:
{
bWithinVolume = m_SearchAngledArea.TestPoint(vCentroid);
break;
}
}
// Ensure valid, and evaluate spawn point
if(bWithinVolume)
{
// Only decrement num iterations when we actually do some work (below)
iNumIters--;
bool bCanUsePoly = false;
// hack fix until artists stop putting terrain up in the fucking air, underneath the cutoff height we've all agreed upon
if(Abs(vCentroid.z - m_vSearchOrigin.z) < ms_fZCutoff || m_uFlags.IsFlagSet(FLAG_IGNORE_Z))
{
//Evaluate the spawn point.
TSpawnPoint spawnPoint;
spawnPoint.m_vPos = vCentroid;
if(pPoly->GetIsPavement())
spawnPoint.m_iFlags |= TSpawnPoint::FLAG_PAVEMENT;
if(pPoly->GetIsInterior())
spawnPoint.m_iFlags |= TSpawnPoint::FLAG_INTERIOR;
if(EvaluateSpawnPoint(pPoly, spawnPoint))
{
bCanUsePoly = true;
//Insert the spawn point.
InsertSpawnPoint(spawnPoint);
}
}
if(bCanUsePoly)
{
#if __BANK && DEBUG_DRAW
if(m_bDebugSearch)
{
grcDebugDraw::Line(vCentroid, vCentroid+ZAXIS, Color_green, Color_green, 10);
}
#endif
}
else
{
#if __BANK && DEBUG_DRAW
if(m_bDebugSearch)
grcDebugDraw::Line(vCentroid, vCentroid+ZAXIS, Color_red, Color_red, 10);
#endif
}
}
}
m_Timeslice_iCurrentPoly++;
}
if(m_Timeslice_iCurrentPoly >= pNavMesh->GetNumPolys())
{
m_Timeslice_iCurrentNavmesh++;
m_Timeslice_iCurrentPoly = 0;
}
}
}
else
{
m_bSearchActive = false;
m_bSearchComplete = true;
m_bSearchSuccessful = (m_iNumBestSpawnPoints > 0);
m_Timeslice_iCurrentNavmesh = -1;
m_Timeslice_iCurrentPoly = -1;
CPathServer::SetNavMeshLoadDistance(m_fCachedNavmeshLoadDistance);
if( CPathServer::GetIsNavMeshRegionRequired( NMR_NetworkRespawnMgr) )
{
CPathServer::RemoveNavMeshRegion( NMR_NetworkRespawnMgr, THREAD_INVALID );
}
#if __BANK
if(m_bSearchSuccessful)
{
AddSpawnRecord();
}
#endif
}
UNLOCK_NAVMESH_DATA
return !m_bSearchActive;
}
bool CNetRespawnMgr::InsertSpawnPoint(const TSpawnPoint& rSpawnPoint)
{
s32 s,s2;
for(s=0; s<m_iNumBestSpawnPoints; s++)
{
if(rSpawnPoint.m_Score.m_fTotal > m_BestSpawnPoints[s].m_Score.m_fTotal)
{
for(s2=m_iNumBestSpawnPoints; s2>s; s2--)
{
m_BestSpawnPoints[s2] = m_BestSpawnPoints[s2-1];
}
m_BestSpawnPoints[s] = rSpawnPoint;
if(m_iNumBestSpawnPoints < ms_iMaxSpawnPoints)
m_iNumBestSpawnPoints++;
return true;
}
}
if(m_iNumBestSpawnPoints < ms_iMaxSpawnPoints)
{
m_BestSpawnPoints[m_iNumBestSpawnPoints] = rSpawnPoint;
m_iNumBestSpawnPoints++;
return true;
}
return false;
}
float CNetRespawnMgr::FindFieldOfViewAtPosition(const Vector3 & vPos, TNavMeshPoly * pPoly, const float fBaseHeading)
{
const float fRequiredClearDist = Min(15.0f, SHORT_LINE_OF_SIGHT_MAXDIST-1.0f);
const float fMaxTestAngle = PI;
const float fTestAngleInc = fMaxTestAngle / ((float)RESPAWNMGR_NUM_HEADINGS);
float fAngleMod = 0.0f;
while(fAngleMod < fMaxTestAngle)
{
// Left test
float fAngle = fwAngle::LimitRadianAngle(fBaseHeading+fAngleMod);
Vector2 vTestDir = Vector2(-rage::Sinf(fAngle), rage::Cosf(fAngle)) * fRequiredClearDist;
TNavMeshPoly * pEndPoly = CPathServer::TestShortLineOfSightImmediate(vPos, vTestDir, pPoly, NULL, NULL, NULL, kNavDomainRegular);
if(!pEndPoly)
break;
// Right test
if(fAngleMod != 0.0f)
{
fAngle = fwAngle::LimitRadianAngle(fBaseHeading-fAngleMod);
vTestDir = Vector2(-rage::Sinf(fAngle), rage::Cosf(fAngle)) * fRequiredClearDist;
pEndPoly = CPathServer::TestShortLineOfSightImmediate(vPos, vTestDir, pPoly, NULL, NULL, NULL, kNavDomainRegular);
if(!pEndPoly)
break;
}
// Step
fAngleMod += fTestAngleInc;
}
return fAngleMod;
}
// NAME : EvaluateSpawnPoint
// PURPOSE : Evalulates the spawn point.
bool CNetRespawnMgr::EvaluateSpawnPoint(TNavMeshPoly* pPoly, TSpawnPoint& rSpawnPoint)
{
//Evaluate the FOV and heading.
if(!EvaluateFOVAndHeading(pPoly, rSpawnPoint))
{
return false;
}
//Evaluate the proximity to the origin.
EvaluateProximityToOrigin(rSpawnPoint);
//Evaluate the team bunching.
EvaluateTeamBunching(rSpawnPoint);
//Evaluate the player positions.
EvaluatePlayerPositions(rSpawnPoint);
//Evaluate the AI positions.
EvaluateAiPositions(rSpawnPoint);
//Evaluate the randomness.
EvaluateRandomness(rSpawnPoint);
return true;
}
void CNetRespawnMgr::AddPosition(Positions& rPositions, const CPed& rPed, bool bUseProjectedPosition)
{
//Assert that the positions are not full.
aiAssert(!rPositions.IsFull());
//Add the position.
Position& rPosition = rPositions.Append();
rPosition.Reset();
//Check if we should not use the projected position.
if(!bUseProjectedPosition)
{
//Set the position.
rPosition.m_vPosition = VEC3V_TO_VECTOR3(rPed.GetTransform().GetPosition());
}
else
{
//Calculate the projected position.
float fTime = 1.0f;
float fDistanceFromStart = 0.0f;
rPosition.m_vPosition = VEC3V_TO_VECTOR3(CalculateProjectedPosition(rPed, fTime, fDistanceFromStart));
}
//Set the flags.
if(rPed.GetIsInVehicle() && rPed.GetVehiclePedInside()->GetIsAircraft())
{
rPosition.m_uFlags.SetFlag(Position::IsInAircraft);
}
}
Vec3V_Out CNetRespawnMgr::CalculateProjectedPosition(const CPed& rPed, float fTime, float& fDistanceFromStart)
{
//Note: This code was ported straight from MP3.
// No changes have been made except where API's differed.
Vector3 projectedposition(Vector3::ZeroType);
fDistanceFromStart = 0.f;
//Get the position.
Vector3 pos = VEC3V_TO_VECTOR3(rPed.GetTransform().GetPosition());
//Set the projectedposition to the current position as a baseline
projectedposition = pos;
//Get the velocity.
Vector3 velocity = rPed.GetVelocity();
const float velocityProcessingThreshold = 0.1f;
float velocityMag = velocity.Mag();
if (velocityMag > velocityProcessingThreshold)
{
const float partspersecond = 5.f;
int parts = (int) (fTime * partspersecond);
bool bProceed = true;
Vector3 vDeltaPart = (velocity * (1.f/partspersecond));
float fDeltaZ = rPed.IsProne() ? 0.2f : rPed.GetIsCrouching() ? 0.6f : 1.f;
Vector3 vPrevStepPos = pos;
Vector3 vNextStepPos = pos;
Vector3 hitpos(Vector3::ZeroType);
while (bProceed && parts > 0)
{
vPrevStepPos = vNextStepPos;
vNextStepPos += vDeltaPart;
{
const int numIsects = 8; //reduced from 16, as we get the closest within the first 8
phIntersection testIntersections[numIsects];
u32 nFlags = ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE;
const float fCylinderRadius = 0.2f;
int nNumResults = CPhysics::GetLevel()->TestCapsule(vPrevStepPos, vNextStepPos, fCylinderRadius, testIntersections, NULL, nFlags, TYPE_FLAGS_ALL, phLevelBase::STATE_FLAGS_ALL, true, numIsects);
int nUseIntersection = -1;
for(int i=0; i<nNumResults; i++)
{
if(testIntersections[i].IsAHit())
{
// check we've got the first intersection along the probe
if(nUseIntersection < 0 || testIntersections[i].GetT() < testIntersections[nUseIntersection].GetT())
{
nUseIntersection = i;
}
}
else
break;
}
if(nUseIntersection > -1)
{
//Need to position a little back from the point of intersection to allow place for body to be at
hitpos = VEC3V_TO_VECTOR3(testIntersections[nUseIntersection].GetPosition());
Vector3 backdir = vPrevStepPos - hitpos;
backdir.NormalizeFast();
const float fEstimatedPedRadius = 0.5f;
vNextStepPos = hitpos + (backdir * fEstimatedPedRadius);
projectedposition = vNextStepPos;
bProceed = false;
}
}
if (bProceed)
{
{
const int numIsects = 8; //reduced from 16, as we get the closest within the first 8
phIntersection testIntersections[numIsects];
phSegment testSegment;
Vector3 vNextStepPosDown = vNextStepPos;
vNextStepPosDown.z -= 10.f;
testSegment.Set(vNextStepPos, vNextStepPosDown);
u32 nFlags = ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_OBJECT_TYPE;
int nNumResults = CPhysics::GetLevel()->TestProbe(testSegment, testIntersections, rPed.GetCurrentPhysicsInst(), nFlags, TYPE_FLAGS_ALL, phLevelBase::STATE_FLAGS_ALL, numIsects);
int nUseIntersection = -1;
for(int i=0; i<nNumResults; i++)
{
if(testIntersections[i].IsAHit())
{
// check we've got the first intersection along the probe
if(nUseIntersection < 0 || testIntersections[i].GetT() < testIntersections[nUseIntersection].GetT())
{
nUseIntersection = i;
}
}
else
break;
}
if(nUseIntersection > -1)
{
hitpos = VEC3V_TO_VECTOR3(testIntersections[nUseIntersection].GetPosition());
vNextStepPos = hitpos;
vNextStepPos.z += fDeltaZ;
}
}
}
parts--;
}
if (bProceed)
projectedposition = vNextStepPos;
fDistanceFromStart = (projectedposition - pos).Mag();
}
return VECTOR3_TO_VEC3V(projectedposition);
}
void CNetRespawnMgr::EvaluateAiPositions(TSpawnPoint& rSpawnPoint)
{
//Ensure we care about the AI.
bool bCareAboutEnemies = m_uFlags.IsFlagSet(FLAG_PREFER_ENEMY_AI_FARTHER);
bool bCareAboutFriendlies = m_uFlags.IsFlagSet(FLAG_PREFER_FRIENDLY_AI_CLOSER);
if(!bCareAboutEnemies && !bCareAboutFriendlies)
{
return;
}
//Create the input.
EvaluatePositionsInput input;
//Assign the care flags.
input.m_bCareAboutEnemies = bCareAboutEnemies;
input.m_bCareAboutFriendlies = bCareAboutFriendlies;
//Find the closest enemy and friendly AI.
FindClosestEnemyAndFriendlyAi(rSpawnPoint.m_vPos, input.m_fClosestEnemyDistSq, input.m_fClosestFriendlyDistSq);
//Assign the distances.
input.m_fMinDistanceForScoringEnemiesFarther = ms_fMinDistanceForScoringEnemyAiFarther;
input.m_fMaxDistanceForScoringEnemiesFarther = ms_fMaxDistanceForScoringEnemyAiFarther;
input.m_fMinDistanceForScoringFriendliesCloser = ms_fMinDistanceForScoringFriendlyAiCloser;
input.m_fIdealDistanceForScoringFriendliesCloser = ms_fIdealDistanceForScoringFriendlyAiCloser;
input.m_fMaxDistanceForScoringFriendliesCloser = ms_fMaxDistanceForScoringFriendlyAiCloser;
//Assign the scores.
input.m_fMinScoreEnemiesFarther = ms_fMinScoreEnemyAiFarther;
input.m_fMaxScoreEnemiesFarther = ms_fMaxScoreEnemyAiFarther;
input.m_fMinScoreFriendliesCloser = ms_fMinScoreFriendlyAiCloser;
input.m_fMaxScoreFriendliesCloser = ms_fMaxScoreFriendlyAiCloser;
input.m_fMinScoreFriendliesTooClose = ms_fMinScoreFriendlyAiTooClose;
input.m_fMaxScoreFriendliesTooClose = ms_fMaxScoreFriendlyAiTooClose;
//Create the output.
EvaluatePositionsOutput output;
//Evaluate the positions.
EvaluatePositions(input, output);
#if __BANK
rSpawnPoint.m_Score.m_fEnemyAiFarther = output.m_fScoreEnemiesFarther;
rSpawnPoint.m_Score.m_fFriendlyAiCloser = output.m_fScoreFriendliesCloser;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += output.m_fScoreEnemiesFarther;
rSpawnPoint.m_Score.m_fTotal += output.m_fScoreFriendliesCloser;
}
bool CNetRespawnMgr::EvaluateFOVAndHeading(TNavMeshPoly* pPoly, TSpawnPoint& rSpawnPoint)
{
float fChosenHeading = ms_fSearchHeadingNone;
float fFOV = 0.0f;
//-------------------------------------------------------------------------
// If we have a target position, then evaluate the FOV towards this target
if(! m_vSearchTargetPos.IsClose(ms_vSearchTargetPosNone, 0.1f) )
{
const Vector3 vToTarget = m_vSearchTargetPos - rSpawnPoint.m_vPos;
fChosenHeading = fwAngle::LimitRadianAngleSafe( fwAngle::GetRadianAngleBetweenPoints( vToTarget.x, vToTarget.y, 0.0f, 0.0f ) );
fFOV = FindFieldOfViewAtPosition(rSpawnPoint.m_vPos, pPoly, fChosenHeading);
}
//----------------------------------------------------------------------------
// If we have a target heading, then evaluate the FOV in that given direction
else if( m_fSearchOptimalHeading != ms_fSearchHeadingNone )
{
fChosenHeading = m_fSearchOptimalHeading;
fFOV = FindFieldOfViewAtPosition(rSpawnPoint.m_vPos, pPoly, fChosenHeading);
}
//-------------------------------------------------------------------------------------
// Otherwise evaluate N different directions, and choose the one with the widest FOV
else
{
int iBestHeading = -1;
float fBestHeadingFOV = 0.0f;
float fHeadingFOV[RESPAWNMGR_NUM_HEADINGS];
for(int h=0; h<RESPAWNMGR_NUM_HEADINGS; h++)
fHeadingFOV[h] = 0.0f;
int h;
for(h=0; h<RESPAWNMGR_NUM_HEADINGS; h++)
{
float fBaseHeading = (h > 0) ? (TWO_PI / ((float)RESPAWNMGR_NUM_HEADINGS)) * ((float)h) : 0.0f;
fHeadingFOV[h] = FindFieldOfViewAtPosition(rSpawnPoint.m_vPos, pPoly, fBaseHeading);
if(fHeadingFOV[h] > fBestHeadingFOV)
{
fBestHeadingFOV = fHeadingFOV[h];
iBestHeading = h;
}
}
// If no headings passed the test then return false
if(iBestHeading == -1)
{
return false;
}
// pick randomly from the angles with the widest FOV
int iBestHeadings[RESPAWNMGR_NUM_HEADINGS];
int iNumBestHeadings = 0;
for(h=0; h<RESPAWNMGR_NUM_HEADINGS; h++)
{
if(fHeadingFOV[h] == fBestHeadingFOV)
iBestHeadings[iNumBestHeadings++] = h;
}
Assert(iNumBestHeadings > 0);
int iRandomHeading = iBestHeadings[ fwRandom::GetRandomNumberInRange(0, iNumBestHeadings) ];
fChosenHeading = (iRandomHeading > 0) ? (TWO_PI / ((float)RESPAWNMGR_NUM_HEADINGS)) * ((float)iRandomHeading) : 0.0f;
fFOV = fBestHeadingFOV;
}
//Assign the heading.
rSpawnPoint.m_fHeading = fChosenHeading;
//Ensure we care about a wide FOV.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_WIDE_FOV))
{
return true;
}
//At this point, the FOV should range from 0 ... PI.
//Normalize the FOV.
static float sPiInverted = 1.0f / PI;
float fNormal = fFOV * sPiInverted;
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
float fScore = Lerp(fNormal, ms_fMinScoreFOV, ms_fMaxScoreFOV);
#if __BANK
rSpawnPoint.m_Score.m_fFOV = fScore;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += fScore;
return true;
}
void CNetRespawnMgr::EvaluatePlayerPositions(TSpawnPoint& rSpawnPoint)
{
//Ensure we care about the players.
bool bCareAboutEnemies = m_uFlags.IsFlagSet(FLAG_PREFER_ENEMY_PLAYERS_FARTHER);
bool bCareAboutFriendlies = m_uFlags.IsFlagSet(FLAG_PREFER_FRIENDLY_PLAYERS_CLOSER);
if(!bCareAboutEnemies && !bCareAboutFriendlies)
{
return;
}
//Create the input.
EvaluatePositionsInput input;
//Assign the care flags.
input.m_bCareAboutEnemies = bCareAboutEnemies;
input.m_bCareAboutFriendlies = bCareAboutFriendlies;
//Find the closest enemy and friendly players.
FindClosestEnemyAndFriendlyPlayers(rSpawnPoint.m_vPos, input.m_fClosestEnemyDistSq, input.m_fClosestFriendlyDistSq);
//Assign the distances.
input.m_fMinDistanceForScoringEnemiesFarther = ms_fMinDistanceForScoringEnemyPlayersFarther;
input.m_fMaxDistanceForScoringEnemiesFarther = ms_fMaxDistanceForScoringEnemyPlayersFarther;
input.m_fMinDistanceForScoringFriendliesCloser = ms_fMinDistanceForScoringFriendlyPlayersCloser;
input.m_fIdealDistanceForScoringFriendliesCloser = ms_fIdealDistanceForScoringFriendlyPlayersCloser;
input.m_fMaxDistanceForScoringFriendliesCloser = ms_fMaxDistanceForScoringFriendlyPlayersCloser;
//Assign the scores.
input.m_fMinScoreEnemiesFarther = ms_fMinScoreEnemyPlayersFarther;
input.m_fMaxScoreEnemiesFarther = ms_fMaxScoreEnemyPlayersFarther;
input.m_fMinScoreFriendliesCloser = ms_fMinScoreFriendlyPlayersCloser;
input.m_fMaxScoreFriendliesCloser = ms_fMaxScoreFriendlyPlayersCloser;
input.m_fMinScoreFriendliesTooClose = ms_fMinScoreFriendlyPlayersTooClose;
input.m_fMaxScoreFriendliesTooClose = ms_fMaxScoreFriendlyPlayersTooClose;
//Create the output.
EvaluatePositionsOutput output;
//Evaluate the positions.
EvaluatePositions(input, output);
#if __BANK
rSpawnPoint.m_Score.m_fEnemyPlayersFarther = output.m_fScoreEnemiesFarther;
rSpawnPoint.m_Score.m_fFriendlyPlayersCloser = output.m_fScoreFriendliesCloser;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += output.m_fScoreEnemiesFarther;
rSpawnPoint.m_Score.m_fTotal += output.m_fScoreFriendliesCloser;
}
void CNetRespawnMgr::EvaluatePositions(const EvaluatePositionsInput& rInput, EvaluatePositionsOutput& rOutput)
{
//Check if we care about enemies being farther away.
rOutput.m_fScoreEnemiesFarther = 0.0f;
if(rInput.m_bCareAboutEnemies)
{
//Check if the closest enemy is valid.
if(rInput.m_fClosestEnemyDistSq > 0.0f)
{
//Clamp the distance to values we care about.
float fMinDistSq = square(rInput.m_fMinDistanceForScoringEnemiesFarther);
float fMaxDistSq = square(rInput.m_fMaxDistanceForScoringEnemiesFarther);
float fDistSq = Clamp(rInput.m_fClosestEnemyDistSq, fMinDistSq, fMaxDistSq);
//Normalize the value.
float fNormal = (fDistSq - fMinDistSq) / (fMaxDistSq - fMinDistSq);
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
rOutput.m_fScoreEnemiesFarther = Lerp(fNormal, rInput.m_fMinScoreEnemiesFarther, rInput.m_fMaxScoreEnemiesFarther);
}
}
//Check if we care about friendlies being closer.
rOutput.m_fScoreFriendliesCloser = 0.0f;
if(rInput.m_bCareAboutFriendlies)
{
//Check if the closest friendly is valid.
if(rInput.m_fClosestFriendlyDistSq > 0.0f)
{
//Check if the distance has exceeded the ideal distance.
float fDistSq = rInput.m_fClosestFriendlyDistSq;
float fIdealDistSq = square(rInput.m_fIdealDistanceForScoringFriendliesCloser);
if(fDistSq >= fIdealDistSq)
{
//Clamp the distance to values we care about.
float fMaxDistSq = square(rInput.m_fMaxDistanceForScoringFriendliesCloser);
fDistSq = Clamp(fDistSq, fIdealDistSq, fMaxDistSq);
//Normalize the value.
float fNormal = (fDistSq - fIdealDistSq) / (fMaxDistSq - fIdealDistSq);
//Flip the value, since values closer to the ideal are better and should therefore be scored higher.
fNormal = 1.0f - fNormal;
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
rOutput.m_fScoreFriendliesCloser = Lerp(fNormal, rInput.m_fMinScoreFriendliesCloser, rInput.m_fMaxScoreFriendliesCloser);
}
else
{
//Clamp the distance to values we care about.
float fMinDistSq = square(rInput.m_fMinDistanceForScoringFriendliesCloser);
fDistSq = Clamp(fDistSq, fMinDistSq, fIdealDistSq);
//Normalize the value.
float fNormal = (fDistSq - fMinDistSq) / (fIdealDistSq - fMinDistSq);
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
rOutput.m_fScoreFriendliesCloser = Lerp(fNormal, rInput.m_fMinScoreFriendliesTooClose, rInput.m_fMaxScoreFriendliesTooClose);
}
}
}
}
void CNetRespawnMgr::EvaluateProximityToOrigin(TSpawnPoint& rSpawnPoint)
{
//Ensure we care about being close to the origin.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_CLOSE_TO_SPAWN_ORIGIN))
{
return;
}
//Calculate the distance from the origin.
float fDistSq = rSpawnPoint.m_vPos.Dist2(m_vSearchOrigin);
//Clamp the distance to values we care about.
float fMinDistSq = square(m_fSearchMinDist);
float fMaxDistSq = square(m_fSearchMaxDist);
fDistSq = Clamp(fDistSq, fMinDistSq, fMaxDistSq);
//Normalize the value from 0 ... 1.
float fNormal = (fDistSq - fMinDistSq) / (fMaxDistSq - fMinDistSq);
//Flip the value, since values closer to the minimum are better and should therefore be scored higher.
fNormal = 1.0f - fNormal;
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
float fScore = Lerp(fNormal, ms_fMinScoreProximityToOrigin, ms_fMaxScoreProximityToOrigin);
#if __BANK
rSpawnPoint.m_Score.m_fProximityToOrigin = fScore;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += fScore;
}
void CNetRespawnMgr::EvaluateRandomness(TSpawnPoint& rSpawnPoint)
{
//Ensure we care about randomness.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_RANDOMNESS))
{
return;
}
//Choose a random value.
float fNormal = fwRandom::GetRandomNumberInRange(0.0f, 1.0f);
//Calculate the score.
fNormal = Clamp(fNormal, 0.0f, 1.0f);
float fScore = Lerp(fNormal, ms_fMinScoreRandomness, ms_fMaxScoreRandomness);
#if __BANK
rSpawnPoint.m_Score.m_fRandomness = fScore;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += fScore;
}
void CNetRespawnMgr::EvaluateTeamBunching(TSpawnPoint& rSpawnPoint)
{
//Ensure we care about team bunching.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_TEAM_BUNCHING))
{
return;
}
//Ensure the largest bunch of enemy player position is valid.
if(!m_vLargestBunchOfEnemyPlayersPosition.IsNonZero())
{
return;
}
//Ensure the largest bunch of friendly player position is valid.
if(!m_vLargestBunchOfFriendlyPlayersPosition.IsNonZero())
{
return;
}
//Calculate the direction from the friendly players to the position.
Vector3 vFriendlyPlayersToPosition = rSpawnPoint.m_vPos - m_vLargestBunchOfFriendlyPlayersPosition;
Vector3 vFriendlyPlayersToPositionDirection = vFriendlyPlayersToPosition;
vFriendlyPlayersToPositionDirection.NormalizeSafe(VEC3_ZERO);
//Calculate the direction from the enemy players to the friendly players.
Vector3 vEnemyPlayersToFriendlyPlayers = m_vLargestBunchOfFriendlyPlayersPosition - m_vLargestBunchOfEnemyPlayersPosition;
Vector3 vEnemyPlayersToFriendlyPlayersDirection = vEnemyPlayersToFriendlyPlayers;
vEnemyPlayersToFriendlyPlayersDirection.NormalizeSafe(VEC3_ZERO);
//Calculate the normal for the direction.
float fNormalDirection = vFriendlyPlayersToPositionDirection.Dot(vEnemyPlayersToFriendlyPlayersDirection);
fNormalDirection += 1.0f;
fNormalDirection *= 0.5f;
//Calculate the score for the direction.
float fScoreDirection = Lerp(fNormalDirection, ms_fMinScoreTeamBunchingDirection, ms_fMaxScoreTeamBunchingDirection);
//Calculate the distance from the friendlies.
float fDistSq = vFriendlyPlayersToPosition.Mag2();
//Clamp the distance to values we care about.
float fIdealDistSq = square(ms_fIdealDistanceForScoringFriendlyPlayersCloser);
fDistSq = Clamp(fDistSq, 0.0f, fIdealDistSq);
//Calculate the normal for the position.
float fNormalPosition = fDistSq / fIdealDistSq;
//Calculate the score for the position.
float fScorePosition = Lerp(fNormalPosition, ms_fMinScoreTeamBunchingPosition, ms_fMaxScoreTeamBunchingPosition);
//Calculate the score.
float fScore = fScoreDirection + fScorePosition;
#if __BANK
rSpawnPoint.m_Score.m_fTeamBunching = fScore;
#endif
//Add the score.
rSpawnPoint.m_Score.m_fTotal += fScore;
}
void CNetRespawnMgr::FindClosestEnemyAndFriendlyAi(const Vector3& vPos, float& fClosestEnemyDistSq, float& fClosestFriendlyDistSq)
{
//Initialize the distances.
fClosestEnemyDistSq = FLT_MAX;
fClosestFriendlyDistSq = FLT_MAX;
//Find the enemy and friendly AI positions.
Positions enemyAiPositions;
Positions friendlyAiPositions;
FindEnemyAndFriendlyAiPositions(enemyAiPositions, friendlyAiPositions);
//Find the closest enemy and friendly positions.
FindClosestEnemyAndFriendlyPositions(vPos, enemyAiPositions, friendlyAiPositions, fClosestEnemyDistSq, fClosestFriendlyDistSq);
}
void CNetRespawnMgr::FindClosestEnemyAndFriendlyPlayers(const Vector3& vPos, float& fClosestEnemyDistSq, float& fClosestFriendlyDistSq)
{
//Initialize the distances.
fClosestEnemyDistSq = FLT_MAX;
fClosestFriendlyDistSq = FLT_MAX;
//Find the enemy and friendly player positions.
Positions enemyPlayerPositions;
Positions friendlyPlayerPositions;
FindEnemyAndFriendlyPlayerPositions(enemyPlayerPositions, friendlyPlayerPositions);
//Find the closest enemy and friendly positions.
FindClosestEnemyAndFriendlyPositions(vPos, enemyPlayerPositions, friendlyPlayerPositions, fClosestEnemyDistSq, fClosestFriendlyDistSq);
}
void CNetRespawnMgr::FindClosestEnemyAndFriendlyPositions(const Vector3& vPos, const Positions& rEnemyPositions, const Positions& rFriendlyPositions, float& fClosestEnemyDistSq, float& fClosestFriendlyDistSq)
{
//Find the closest enemy.
for(int i = 0; i < rEnemyPositions.GetCount(); ++i)
{
//Get the position.
const Position& rPosition = rEnemyPositions[i];
//Calculate the distance.
float fDistSq = !rPosition.UseFlatDistance() ? (vPos - rPosition.m_vPosition).Mag2() :
(vPos - rPosition.m_vPosition).XYMag2();
if(fDistSq >= fClosestEnemyDistSq)
{
continue;
}
//Assign the distance.
fClosestEnemyDistSq = fDistSq;
}
//Find the closest friendly.
for(int i = 0; i < rFriendlyPositions.GetCount(); ++i)
{
//Get the position.
const Position& rPosition = rFriendlyPositions[i];
//Calculate the distance.
float fDistSq = !rPosition.UseFlatDistance() ? (vPos - rPosition.m_vPosition).Mag2() :
(vPos - rPosition.m_vPosition).XYMag2();
if(fDistSq >= fClosestFriendlyDistSq)
{
continue;
}
//Assign the distance.
fClosestFriendlyDistSq = fDistSq;
}
}
void CNetRespawnMgr::FindEnemyAndFriendlyAiPositions(Positions& rEnemyAiPositions, Positions& rFriendlyAiPositions)
{
#if __BANK
//Check if we are using AI overrides.
if(ms_bUseAiOverrides)
{
//Find the enemy and friendly AI positions from the overrides.
FindEnemyAndFriendlyAiPositionsFromOverrides(rEnemyAiPositions, rFriendlyAiPositions);
}
else
#endif
{
//Find the enemy and friendly AI positions from the world.
FindEnemyAndFriendlyAiPositionsFromWorld(rEnemyAiPositions, rFriendlyAiPositions);
}
}
void CNetRespawnMgr::FindEnemyAndFriendlyAiPositionsFromWorld(Positions& rEnemyAiPositions, Positions& rFriendlyAiPositions)
{
//Check if the cache is valid.
if(m_AiPositionsFromWorld.IsValid())
{
//Set the positions.
rEnemyAiPositions = m_AiPositionsFromWorld.m_EnemyPositions;
rFriendlyAiPositions = m_AiPositionsFromWorld.m_FriendlyPositions;
//Nothing else to do.
return;
}
//Ensure the ped is valid.
if(!m_pPed)
{
return;
}
//Ensure we care about the AI.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_ENEMY_AI_FARTHER) && !m_uFlags.IsFlagSet(FLAG_PREFER_FRIENDLY_AI_CLOSER))
{
return;
}
//Calculate the range.
float fRange = m_fSearchMaxDist;
//Calculate the expansion.
float fExpansion = 0.0f;
fExpansion = Max(fExpansion, ms_fMaxDistanceForScoringEnemyPlayersFarther);
fExpansion = Max(fExpansion, ms_fMaxDistanceForScoringFriendlyPlayersCloser);
fExpansion = Max(fExpansion, ms_fMaxDistanceForScoringEnemyAiFarther);
fExpansion = Max(fExpansion, ms_fMaxDistanceForScoringFriendlyAiCloser);
//Expand the range.
fRange += fExpansion;
//Find all of the peds within range of the search origin.
s32 iCount = 0;
static const int s_iMaxResults = 32;
CEntity* pResults[s_iMaxResults];
CGameWorld::FindObjectsInRange(m_vSearchOrigin, fRange, true, &iCount, s_iMaxResults, pResults, false, false, true, false, false);
//Iterate over the peds.
for(int i = 0; i < iCount; ++i)
{
//Ensure the entity is valid.
const CEntity* pEntity = pResults[i];
if(!pEntity)
{
continue;
}
//Ensure the entity is a ped.
if(!aiVerifyf(pEntity->GetIsTypePed(), "Entity is not a ped."))
{
continue;
}
//Grab the ped.
const CPed* pPed = static_cast<const CPed *>(pEntity);
//Ensure the ped is not a player.
if(pPed->IsPlayer())
{
continue;
}
//Generate the friendly state.
bool bFriendly;
//Check if the ped is friendly.
if(pPed->GetPedIntelligence()->IsFriendlyWith(*m_pPed))
{
bFriendly = true;
}
//Check if the ped is an enemy.
else if(pPed->GetPedIntelligence()->IsThreatenedBy(*m_pPed))
{
bFriendly = false;
}
//The ped is neutral.
else
{
continue;
}
//Choose the positions.
Positions& rPositions = bFriendly ? rFriendlyAiPositions : rEnemyAiPositions;
//Ensure the positions are not full.
if(rPositions.IsFull())
{
continue;
}
//Add the position.
AddPosition(rPositions, *pPed, false);
}
//Cache the data.
m_AiPositionsFromWorld.m_EnemyPositions = rEnemyAiPositions;
m_AiPositionsFromWorld.m_FriendlyPositions = rFriendlyAiPositions;
m_AiPositionsFromWorld.m_uTimeLastUpdated = fwTimer::GetTimeInMilliseconds();
}
void CNetRespawnMgr::FindEnemyAndFriendlyPlayerPositions(Positions& rEnemyPlayerPositions, Positions& rFriendlyPlayerPositions)
{
#if __BANK
//Check if we are using player overrides.
if(ms_bUsePlayerOverrides)
{
//Find the enemy and friendly player positions from the overrides.
FindEnemyAndFriendlyPlayerPositionsFromOverrides(rEnemyPlayerPositions, rFriendlyPlayerPositions);
}
else
#endif
{
//Find the enemy and friendly player positions from the network.
FindEnemyAndFriendlyPlayerPositionsFromNetwork(rEnemyPlayerPositions, rFriendlyPlayerPositions);
}
}
void CNetRespawnMgr::FindEnemyAndFriendlyPlayerPositionsFromNetwork(Positions& rEnemyPlayerPositions, Positions& rFriendlyPlayerPositions)
{
//Check if the cache is valid.
if(m_PlayerPositionsFromNetwork.IsValid())
{
//Set the positions.
rEnemyPlayerPositions = m_PlayerPositionsFromNetwork.m_EnemyPositions;
rFriendlyPlayerPositions = m_PlayerPositionsFromNetwork.m_FriendlyPositions;
//Nothing else to do.
return;
}
//The team must be valid for anyone to be considered friendly.
//This will happen if no team or ped is specified.
//This can also happen in games like deathmatch, where no one is assigned a team.
bool bCanBeFriendly = (m_iTeam >= 0);
//Get the net player for the ped.
CNetGamePlayer* pNetPlayer = m_pPed ? GetNetPlayerForPed(*m_pPed) : NULL;
//Iterate over the players.
unsigned numPhysicalPlayers = netInterface::GetNumPhysicalPlayers();
const netPlayer * const *allPhysicalPlayers = netInterface::GetAllPhysicalPlayers();
for(unsigned index = 0; index < numPhysicalPlayers; index++)
{
const CNetGamePlayer *pOtherPlayer = SafeCast(const CNetGamePlayer, allPhysicalPlayers[index]);
//Ensure the ped is valid.
const CPed* pOtherPed = pOtherPlayer->GetPlayerPed();
if(!pOtherPed)
{
continue;
}
//Ensure the other ped is not the ped.
if(pOtherPed == m_pPed)
{
continue;
}
//Ensure the other ped is not injured.
if(pOtherPed->IsInjured())
{
continue;
}
//Ensure the other ped is visible.
if(!pOtherPed->GetIsVisible())
{
continue;
}
//Ensure the players are not in different sessions.
if(pNetPlayer && NetworkInterface::ArePlayersInDifferentTutorialSessions(*pNetPlayer, *pOtherPlayer))
{
continue;
}
//Grab the team.
int iOtherTeam = pOtherPlayer->GetTeam();
//Check if the other ped is friendly.
bool bFriendly = bCanBeFriendly && (m_iTeam == iOtherTeam);
//Choose the positions.
Positions& rPositions = bFriendly ? rFriendlyPlayerPositions : rEnemyPlayerPositions;
//Ensure the positions are not full.
if(rPositions.IsFull())
{
continue;
}
//Add the position.
AddPosition(rPositions, *pOtherPed, true);
}
//Cache the data.
m_PlayerPositionsFromNetwork.m_EnemyPositions = rEnemyPlayerPositions;
m_PlayerPositionsFromNetwork.m_FriendlyPositions = rFriendlyPlayerPositions;
m_PlayerPositionsFromNetwork.m_uTimeLastUpdated = fwTimer::GetTimeInMilliseconds();
}
CNetGamePlayer* CNetRespawnMgr::GetNetPlayerForPed(const CPed& rPed)
{
//Ensure the net object is valid.
netObject* pNetObject = rPed.GetNetworkObject();
if(!aiVerifyf(pNetObject, "The ped must have a valid net object to get the net player."))
{
return NULL;
}
//Ensure the network object type is a player or ped.
NetworkObjectType nNetworkObjectType = pNetObject->GetObjectType();
if(!aiVerifyf(nNetworkObjectType == NET_OBJ_TYPE_PED || nNetworkObjectType == NET_OBJ_TYPE_PLAYER, "The ped has an unexpected network object type: %d.", nNetworkObjectType))
{
return NULL;
}
//Grab the ped network object.
CNetObjPed* pNetObjPed = static_cast<CNetObjPed *>(pNetObject);
return pNetObjPed->GetPlayerOwner();
}
int CNetRespawnMgr::GetNetTeamForPed(const CPed& rPed)
{
//Ensure the net player is valid.
CNetGamePlayer* pNetPlayer = GetNetPlayerForPed(rPed);
if(!aiVerifyf(pNetPlayer, "The ped must have a valid net player to get the team."))
{
return -1;
}
return pNetPlayer->GetTeam();
}
void CNetRespawnMgr::UpdateLargestBunchOfEnemyPlayers()
{
//Clear the largest bunch of enemy players.
m_vLargestBunchOfEnemyPlayersPosition.Zero();
//Ensure we care about team bunching.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_TEAM_BUNCHING))
{
return;
}
//Find the best bunch of enemy players.
FindLargestBunchOfPlayers(false, m_vLargestBunchOfEnemyPlayersPosition);
}
void CNetRespawnMgr::UpdateLargestBunchOfFriendlyPlayers()
{
//Clear the largest bunch of friendly players.
m_vLargestBunchOfFriendlyPlayersPosition.Zero();
//Ensure we care about team bunching.
if(!m_uFlags.IsFlagSet(FLAG_PREFER_TEAM_BUNCHING))
{
return;
}
//Find the best bunch of friendly players.
FindLargestBunchOfPlayers(true, m_vLargestBunchOfFriendlyPlayersPosition);
}
void CNetRespawnMgr::UpdateLargestBunches()
{
//Update the largest bunch of enemy players.
UpdateLargestBunchOfEnemyPlayers();
//Update the largest bunch of friendly players.
UpdateLargestBunchOfFriendlyPlayers();
}
#if __BANK
void CNetRespawnMgr::AddSpawnRecord()
{
ms_SpawnRecord.m_vOrigin = m_vSearchOrigin;
ms_SpawnRecord.m_vTarget = m_vSearchTargetPos;
ms_SpawnRecord.m_fMinDistance = m_fSearchMinDist;
ms_SpawnRecord.m_fMaxDistance = m_fSearchMaxDist;
ms_SpawnRecord.m_uFlags = m_uFlags;
ms_SpawnRecord.m_pPed = m_pPed;
ms_SpawnRecord.m_iTeam = m_iTeam;
ms_SpawnRecord.m_BestSpawnPoint = m_BestSpawnPoints[0];
ms_SpawnRecord.m_aEnemyPlayerPositions.Reset();
ms_SpawnRecord.m_aFriendlyPlayerPositions.Reset();
FindEnemyAndFriendlyPlayerPositions(ms_SpawnRecord.m_aEnemyPlayerPositions, ms_SpawnRecord.m_aFriendlyPlayerPositions);
ms_SpawnRecord.m_aEnemyAiPositions.Reset();
ms_SpawnRecord.m_aFriendlyAiPositions.Reset();
FindEnemyAndFriendlyAiPositions(ms_SpawnRecord.m_aEnemyAiPositions, ms_SpawnRecord.m_aFriendlyAiPositions);
}
void CNetRespawnMgr::CopyEnemyAndFriendlyPositionsFromOverrides(const OverridePositions& rEnemyOverridePositions, const OverridePositions& rFriendlyOverridePositions, Positions& rEnemyPositions, Positions& rFriendlyPositions)
{
//Iterate over the enemy override positions.
for(int i = 0; i < rEnemyOverridePositions.GetCount(); ++i)
{
//Ensure the enemy positions are not full.
if(rEnemyPositions.IsFull())
{
break;
}
//Add the position.
rEnemyPositions.Append() = rEnemyOverridePositions[i];
}
//Iterate over the friendly override positions.
for(int i = 0; i < rFriendlyOverridePositions.GetCount(); ++i)
{
//Ensure the friendly positions are not full.
if(rFriendlyPositions.IsFull())
{
break;
}
//Add the position.
rFriendlyPositions.Append() = rFriendlyOverridePositions[i];
}
}
void CNetRespawnMgr::FindEnemyAndFriendlyAiPositionsFromOverrides(Positions& rEnemyAiPositions, Positions& rFriendlyAiPositions)
{
//Copy the enemy and friendly AI positions from the overrides.
CopyEnemyAndFriendlyPositionsFromOverrides(ms_aEnemyAiPositionsOverride, ms_aFriendlyAiPositionsOverride, rEnemyAiPositions, rFriendlyAiPositions);
}
void CNetRespawnMgr::FindEnemyAndFriendlyPlayerPositionsFromOverrides(Positions& rEnemyPlayerPositions, Positions& rFriendlyPlayerPositions)
{
//Copy the enemy and friendly player positions from the overrides.
CopyEnemyAndFriendlyPositionsFromOverrides(ms_aEnemyPlayerPositionsOverride, ms_aFriendlyPlayerPositionsOverride, rEnemyPlayerPositions, rFriendlyPlayerPositions);
}
#endif
#if __BANK
void DumpLastSpawnRecord()
{
//Ensure the search is not active.
if(CNetRespawnMgr::IsSearchActive())
{
Displayf("Search is active.");
return;
}
//Ensure the search was successful.
if(!CNetRespawnMgr::WasSearchSuccessful())
{
Displayf("Search was unsuccessful.");
return;
}
Displayf("Origin: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_vOrigin.x, CNetRespawnMgr::ms_SpawnRecord.m_vOrigin.y, CNetRespawnMgr::ms_SpawnRecord.m_vOrigin.z);
Displayf("Target: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_vTarget.x, CNetRespawnMgr::ms_SpawnRecord.m_vTarget.y, CNetRespawnMgr::ms_SpawnRecord.m_vTarget.z);
Displayf("Min Distance: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_fMinDistance);
Displayf("Max Distance: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_fMaxDistance);
Displayf("Flags:");
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_SCRIPT_COMMAND) { Displayf(" Script Command"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_IGNORE_TARGET_HEADING) { Displayf(" Ignore Target Heading"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_CLOSE_TO_SPAWN_ORIGIN) { Displayf(" Prefer Close To Spawn Origin"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_MAY_SPAWN_IN_INTERIOR) { Displayf(" May Spawn In Interior"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_MAY_SPAWN_IN_EXTERIOR) { Displayf(" May Spawn In Exterior"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_WIDE_FOV) { Displayf(" Prefer Wide FOV"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_TEAM_BUNCHING) { Displayf(" Prefer Team Bunching"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_ENEMY_PLAYERS_FARTHER) { Displayf(" Prefer Enemy Players Farther"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_FRIENDLY_PLAYERS_CLOSER) { Displayf(" Prefer Friendly Players Closer"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_ENEMY_AI_FARTHER) { Displayf(" Prefer Enemy AI Farther"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_FRIENDLY_AI_CLOSER) { Displayf(" Prefer Friendly AI Closer"); }
if(CNetRespawnMgr::ms_SpawnRecord.m_uFlags & CNetRespawnMgr::FLAG_PREFER_RANDOMNESS) { Displayf(" Prefer Randomness"); }
Displayf("Ped: %s", CNetRespawnMgr::ms_SpawnRecord.m_pPed ? CNetRespawnMgr::ms_SpawnRecord.m_pPed->GetModelName() : "None");
Displayf("Team: %d", CNetRespawnMgr::ms_SpawnRecord.m_iTeam);
Displayf("Best Spawn Point:");
Displayf(" Position: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_vPos.x, CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_vPos.y, CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_vPos.z);
Displayf(" Heading: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_fHeading);
Displayf(" Score:");
Displayf(" Total: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fTotal);
#if __BANK
Displayf(" FOV: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fFOV);
Displayf(" Proximity To Origin: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fProximityToOrigin);
Displayf(" Team Bunching: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fTeamBunching);
Displayf(" Enemy Players Farther: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fEnemyPlayersFarther);
Displayf(" Friendly Players Closer: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fFriendlyPlayersCloser);
Displayf(" Enemy AI Farther: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fEnemyAiFarther);
Displayf(" Friendly AI Closer: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fFriendlyAiCloser);
Displayf(" Randomness: %.2f", CNetRespawnMgr::ms_SpawnRecord.m_BestSpawnPoint.m_Score.m_fRandomness);
#endif
Displayf("Enemy Players:");
for(int i = 0; i < CNetRespawnMgr::ms_SpawnRecord.m_aEnemyPlayerPositions.GetCount(); ++i)
{
Displayf(" Position: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_aEnemyPlayerPositions[i].m_vPosition.x, CNetRespawnMgr::ms_SpawnRecord.m_aEnemyPlayerPositions[i].m_vPosition.y, CNetRespawnMgr::ms_SpawnRecord.m_aEnemyPlayerPositions[i].m_vPosition.z);
}
Displayf("Friendly Players:");
for(int i = 0; i < CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyPlayerPositions.GetCount(); ++i)
{
Displayf(" Position: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyPlayerPositions[i].m_vPosition.x, CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyPlayerPositions[i].m_vPosition.y, CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyPlayerPositions[i].m_vPosition.z);
}
Displayf("Enemy AI:");
for(int i = 0; i < CNetRespawnMgr::ms_SpawnRecord.m_aEnemyAiPositions.GetCount(); ++i)
{
Displayf(" Position: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_aEnemyAiPositions[i].m_vPosition.x, CNetRespawnMgr::ms_SpawnRecord.m_aEnemyAiPositions[i].m_vPosition.y, CNetRespawnMgr::ms_SpawnRecord.m_aEnemyAiPositions[i].m_vPosition.z);
}
Displayf("Friendly AI:");
for(int i = 0; i < CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyAiPositions.GetCount(); ++i)
{
Displayf(" Position: %.2f, %.2f, %.2f", CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyAiPositions[i].m_vPosition.x, CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyAiPositions[i].m_vPosition.y, CNetRespawnMgr::ms_SpawnRecord.m_aFriendlyAiPositions[i].m_vPosition.z);
}
}
void PlaceEnemyAiPositionAtCameraPosition()
{
//Ensure the enemy AI positions are not full.
if(CNetRespawnMgr::ms_aEnemyAiPositionsOverride.IsFull())
{
//Remove the first element.
CNetRespawnMgr::ms_aEnemyAiPositionsOverride.Pop();
}
//Add an enemy AI position.
CNetRespawnMgr::Position& rPosition = CNetRespawnMgr::ms_aEnemyAiPositionsOverride.Append();
rPosition.Reset();
rPosition.m_vPosition = camInterface::GetPos();
}
void PlaceEnemyPlayerPositionAtCameraPosition()
{
//Ensure the enemy player positions are not full.
if(CNetRespawnMgr::ms_aEnemyPlayerPositionsOverride.IsFull())
{
//Remove the first element.
CNetRespawnMgr::ms_aEnemyPlayerPositionsOverride.Pop();
}
//Add an enemy player position.
CNetRespawnMgr::Position& rPosition = CNetRespawnMgr::ms_aEnemyPlayerPositionsOverride.Append();
rPosition.Reset();
rPosition.m_vPosition = camInterface::GetPos();
}
void PlaceFriendlyAiPositionAtCameraPosition()
{
//Ensure the friendly AI positions are not full.
if(CNetRespawnMgr::ms_aFriendlyAiPositionsOverride.IsFull())
{
//Remove the first element.
CNetRespawnMgr::ms_aFriendlyAiPositionsOverride.Pop();
}
//Add a friendly AI position.
CNetRespawnMgr::Position& rPosition = CNetRespawnMgr::ms_aFriendlyAiPositionsOverride.Append();
rPosition.Reset();
rPosition.m_vPosition = camInterface::GetPos();
}
void PlaceFriendlyPlayerPositionAtCameraPosition()
{
//Ensure the friendly player positions are not full.
if(CNetRespawnMgr::ms_aFriendlyPlayerPositionsOverride.IsFull())
{
//Remove the first element.
CNetRespawnMgr::ms_aFriendlyPlayerPositionsOverride.Pop();
}
//Add a friendly player position.
CNetRespawnMgr::Position& rPosition = CNetRespawnMgr::ms_aFriendlyPlayerPositionsOverride.Append();
rPosition.Reset();
rPosition.m_vPosition = camInterface::GetPos();
}
void ResetEnemyAiPositions()
{
CNetRespawnMgr::ms_aEnemyAiPositionsOverride.Reset();
}
void ResetEnemyPlayerPositions()
{
CNetRespawnMgr::ms_aEnemyPlayerPositionsOverride.Reset();
}
void ResetFriendlyAiPositions()
{
CNetRespawnMgr::ms_aFriendlyAiPositionsOverride.Reset();
}
void ResetFriendlyPlayerPositions()
{
CNetRespawnMgr::ms_aFriendlyPlayerPositionsOverride.Reset();
}
void TestPedPositionProjection()
{
//Calculate the projected position.
float fTime = CNetRespawnMgr::ms_fTimeForPedPositionProjectionTesting;
CNetRespawnMgr::ms_vResultForPedPositionProjectionTesting = VEC3V_TO_VECTOR3(CNetRespawnMgr::CalculateProjectedPositionForPlayer(fTime));
//Draw the result.
CNetRespawnMgr::ms_bDrawResultForPedPositionProjectionTesting = true;
}
void TestRespawn()
{
//Create the input parameters.
CNetRespawnMgr::StartSearchInput input(VEC3_ZERO);
//Assign the ped.
if(CNetRespawnMgr::ms_bAdvancedUsePlayerAsPed)
{
input.m_pPed = CGameWorld::FindLocalPlayer();
}
//Calculate the origin.
if(CNetRespawnMgr::ms_bAdvancedUsePlayerAsOrigin)
{
input.m_vOrigin = CPlayerInfo::ms_cachedMainPlayerPos;
}
else if(CNetRespawnMgr::ms_bAdvancedUseCameraAsOrigin)
{
input.m_vOrigin = camInterface::GetPos();
}
else
{
input.m_vOrigin = VEC3_ZERO;
}
//Calculate the target.
if(CNetRespawnMgr::ms_bAdvancedUsePlayerAsTarget)
{
input.m_vTargetPos = CPlayerInfo::ms_cachedMainPlayerPos;
}
else if(CNetRespawnMgr::ms_bAdvancedUseCameraAsTarget)
{
input.m_vTargetPos = camInterface::GetPos();
}
else
{
input.m_vTargetPos = CNetRespawnMgr::ms_vSearchTargetPosNone;
}
//Calculate the min search distance.
input.m_fMinDist = CNetRespawnMgr::ms_fAdvancedMinSearchDistance;
//Calculate the max search distance.
input.m_fMaxDist = CNetRespawnMgr::ms_fAdvancedMaxSearchDistance;
//Calculate the flags.
if(CNetRespawnMgr::ms_bAdvancedFlagScriptCommand)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_SCRIPT_COMMAND);
}
if(CNetRespawnMgr::ms_bAdvancedFlagIgnoreTargetHeading)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_IGNORE_TARGET_HEADING);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferCloseToSpawnOrigin)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_CLOSE_TO_SPAWN_ORIGIN);
}
if(CNetRespawnMgr::ms_bAdvancedFlagMaySpawnInInterior)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_MAY_SPAWN_IN_INTERIOR);
}
if(CNetRespawnMgr::ms_bAdvancedFlagMaySpawnInExterior)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_MAY_SPAWN_IN_EXTERIOR);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferWideFOV)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_WIDE_FOV);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferTeamBunching)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_TEAM_BUNCHING);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferEnemyPlayersFarther)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_ENEMY_PLAYERS_FARTHER);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferFriendlyPlayersCloser)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_FRIENDLY_PLAYERS_CLOSER);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferEnemyAiFarther)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_ENEMY_AI_FARTHER);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferFriendlyAiCloser)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_FRIENDLY_AI_CLOSER);
}
if(CNetRespawnMgr::ms_bAdvancedFlagPreferRandomness)
{
input.m_uFlags.SetFlag(CNetRespawnMgr::FLAG_PREFER_RANDOMNESS);
}
// Toggle use of angled area volume?
if(CNetRespawnMgr::ms_bDebugUseAngledArea)
{
input.m_iSearchVolumeType = CNetRespawnMgr::SEARCH_VOLUME_ANGLED_AREA;
input.m_vAngledAreaPt1 = CPhysics::g_vClickedPos[0];
input.m_vAngledAreaPt2 = CPhysics::g_vClickedPos[1];
input.m_fAngledAreaWidth = CNetRespawnMgr::ms_fAdvancedMinSearchDistance;
}
// Otherwise its a spherical volume
else
{
input.m_iSearchVolumeType = CNetRespawnMgr::SEARCH_VOLUME_SPHERE;
}
//Start the search.
CNetRespawnMgr::StartSearch(input);
}
void StopTestingRespawn()
{
CNetRespawnMgr::CancelSearch();
}
Vec3V_Out CNetRespawnMgr::CalculateProjectedPositionForPlayer(float fTime)
{
//Ensure the player is valid.
const CPed* pPed = CGameWorld::FindLocalPlayer();
if(!pPed)
{
return Vec3V(V_ZERO);
}
//Calculate the projected position.
float fDistanceFromStart = 0.0f;
return CalculateProjectedPosition(*pPed, fTime, fDistanceFromStart);
}
void CNetRespawnMgr::InitWidgets()
{
bkBank * bank = BANKMGR.FindBank("Game Logic");
Assertf(bank, "Couldn't find \'Game Logic\' bank; this needs to be initialised before CNetRespawnMgr");
bank->PushGroup("Net Respawn Mgr");
bank->PushGroup("Options");
bank->AddToggle("Use Player As Ped", &ms_bAdvancedUsePlayerAsPed);
bank->AddToggle("Use Player As Origin", &ms_bAdvancedUsePlayerAsOrigin);
bank->AddToggle("Use Camera As Origin", &ms_bAdvancedUseCameraAsOrigin);
bank->AddToggle("Use Player As Target", &ms_bAdvancedUsePlayerAsTarget);
bank->AddToggle("Use Camera As Target", &ms_bAdvancedUseCameraAsTarget);
bank->AddSlider("Min Search Distance", &ms_fAdvancedMinSearchDistance, 0.0f, 100.0f, 1.0f);
bank->AddSlider("Max Search Distance", &ms_fAdvancedMaxSearchDistance, 0.0f, 100.0f, 1.0f);
bank->AddToggle("Use Angled Area (defined by measuring tool and min seach dist)", &ms_bDebugUseAngledArea);
bank->PopGroup();
bank->PushGroup("Flags");
bank->AddToggle("Script Command", &ms_bAdvancedFlagScriptCommand);
bank->AddToggle("Ignore Target Heading", &ms_bAdvancedFlagIgnoreTargetHeading);
bank->AddToggle("Prefer Close To Spawn Origin", &ms_bAdvancedFlagPreferCloseToSpawnOrigin);
bank->AddToggle("May Spawn In Interior", &ms_bAdvancedFlagMaySpawnInInterior);
bank->AddToggle("May Spawn In Exterior", &ms_bAdvancedFlagMaySpawnInExterior);
bank->AddToggle("Prefer Wide FOV", &ms_bAdvancedFlagPreferWideFOV);
bank->AddToggle("Prefer Team Bunching", &ms_bAdvancedFlagPreferTeamBunching);
bank->AddToggle("Prefer Enemy Players Farther", &ms_bAdvancedFlagPreferEnemyPlayersFarther);
bank->AddToggle("Prefer Friendly Players Closer", &ms_bAdvancedFlagPreferFriendlyPlayersCloser);
bank->AddToggle("Prefer Enemy AI Farther", &ms_bAdvancedFlagPreferEnemyAiFarther);
bank->AddToggle("Prefer Friendly AI Closer", &ms_bAdvancedFlagPreferFriendlyAiCloser);
bank->AddToggle("Prefer Randomness", &ms_bAdvancedFlagPreferRandomness);
bank->PopGroup();
bank->PushGroup("Scoring Tuning");
bank->AddSlider("Max Distance For Bunching Players", &ms_fMaxDistanceForBunchingPlayers, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Min Distance For Enemy Players Farther", &ms_fMinDistanceForScoringEnemyPlayersFarther, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Max Distance For Enemy Players Farther", &ms_fMaxDistanceForScoringEnemyPlayersFarther, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Min Distance For Friendly Players Closer", &ms_fMinDistanceForScoringFriendlyPlayersCloser, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Ideal Distance For Friendly Players Closer", &ms_fIdealDistanceForScoringFriendlyPlayersCloser, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Max Distance For Friendly Players Closer", &ms_fMaxDistanceForScoringFriendlyPlayersCloser, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Min Distance For Enemy AI Farther", &ms_fMinDistanceForScoringEnemyAiFarther, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Max Distance For Enemy AI Farther", &ms_fMaxDistanceForScoringEnemyAiFarther, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Min Distance For Friendly AI Closer", &ms_fMinDistanceForScoringFriendlyAiCloser, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Ideal Distance For Friendly AI Closer", &ms_fIdealDistanceForScoringFriendlyAiCloser, 0.0f, 300.0f, 1.0f);
bank->AddSlider("Max Distance For Friendly AI Closer", &ms_fMaxDistanceForScoringFriendlyAiCloser, 0.0f, 300.0f, 1.0f);
bank->PopGroup();
bank->PushGroup("Scoring Weights");
bank->AddSlider("Min Score FOV", &ms_fMinScoreFOV, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score FOV", &ms_fMaxScoreFOV, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Proximity To Origin", &ms_fMinScoreProximityToOrigin, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Proximity To Origin", &ms_fMaxScoreProximityToOrigin, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Team Bunching Direction", &ms_fMinScoreTeamBunchingDirection, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Team Bunching Direction", &ms_fMaxScoreTeamBunchingDirection, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Team Bunching Position", &ms_fMinScoreTeamBunchingPosition, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Team Bunching Position", &ms_fMaxScoreTeamBunchingPosition, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Enemy Players Farther", &ms_fMinScoreEnemyPlayersFarther, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Enemy Players Farther", &ms_fMaxScoreEnemyPlayersFarther, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Friendly Players Closer", &ms_fMinScoreFriendlyPlayersCloser, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Friendly Players Closer", &ms_fMaxScoreFriendlyPlayersCloser, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Friendly Players Too Close", &ms_fMinScoreFriendlyPlayersTooClose, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Friendly Players Too Close", &ms_fMaxScoreFriendlyPlayersTooClose, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Enemy AI Farther", &ms_fMinScoreEnemyAiFarther, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Enemy AI Farther", &ms_fMaxScoreEnemyAiFarther, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Friendly AI Closer", &ms_fMinScoreFriendlyAiCloser, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Friendly AI Closer", &ms_fMaxScoreFriendlyAiCloser, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Friendly AI Too Close", &ms_fMinScoreFriendlyAiTooClose, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Friendly AI Too Close", &ms_fMaxScoreFriendlyAiTooClose, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Min Score Randomness", &ms_fMinScoreRandomness, -10.0f, 10.0f, 0.1f);
bank->AddSlider("Max Score Randomness", &ms_fMaxScoreRandomness, -10.0f, 10.0f, 0.1f);
bank->PopGroup();
bank->PushGroup("Player Overrides");
bank->AddToggle("Use Player Overrides", &ms_bUsePlayerOverrides);
bank->AddButton("Place Enemy Player Position At Camera Position", PlaceEnemyPlayerPositionAtCameraPosition);
bank->AddButton("Reset Enemy Player Positions", ResetEnemyPlayerPositions);
bank->AddButton("Place Friendly Player Position At Camera Position", PlaceFriendlyPlayerPositionAtCameraPosition);
bank->AddButton("Reset Friendly Player Positions", ResetFriendlyPlayerPositions);
bank->PopGroup();
bank->PushGroup("AI Overrides");
bank->AddToggle("Use AI Overrides", &ms_bUseAiOverrides);
bank->AddButton("Place Enemy AI Position At Camera Position", PlaceEnemyAiPositionAtCameraPosition);
bank->AddButton("Reset Enemy AI Positions", ResetEnemyAiPositions);
bank->AddButton("Place Friendly AI Position At Camera Position", PlaceFriendlyAiPositionAtCameraPosition);
bank->AddButton("Reset Friendly AI Positions", ResetFriendlyAiPositions);
bank->PopGroup();
bank->PushGroup("Ped Position Projection Testing");
bank->AddSlider("Time", &ms_fTimeForPedPositionProjectionTesting, 0.0f, 10.0f, 0.1f);
bank->AddToggle("Draw Result", &ms_bDrawResultForPedPositionProjectionTesting);
bank->AddButton("Test Ped Position Projection", TestPedPositionProjection);
bank->PopGroup();
bank->AddButton("Test Respawn", TestRespawn);
bank->AddButton("Stop Test", StopTestingRespawn);
bank->AddButton("Dump Last Spawn Record", DumpLastSpawnRecord);
bank->AddToggle("Debug Search", &m_bDebugSearch);
bank->AddToggle("Debug Scores", &m_bDebugScores);
bank->PopGroup();
}
#endif