1765 lines
58 KiB
C++
1765 lines
58 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// rvAAS_tactical.cpp
|
|
//
|
|
// AAST Tactical Search Parameters Documentation
|
|
// ---------------------------------------------
|
|
// There are 10 tests at your disposal when creating search functions, 3
|
|
// sets of 3 and one special case test. Each of these 10 tests can have
|
|
// hard min and max limits, as well as a "soft" weight which will determine how
|
|
// highly the feature is ranked against other features. Let's examine a
|
|
// diagram of what these 10 tests are:
|
|
//
|
|
// [F] ######### Key:
|
|
// | ######### [F] = Focus Test Set (enemy, or forward projection)
|
|
// / ######### [O] = Owner Test Set (actor who is doing the search)
|
|
// [P] ######### [P] = Path Test Set (line between owner and focus)
|
|
// | ######### <-> = Advance Test (In front / behind owner)
|
|
// / x ##### x = Feature (feature being tested)
|
|
// <---[O]---> ##### ### = Walls
|
|
// ####################
|
|
// ####################
|
|
// ####################
|
|
//
|
|
// Each Test Set Has The Following:
|
|
// - Distance RANGE=[ 0, 1] WEIGHT=(-1=Close, 1=Far)
|
|
// Distance is computed from the origin of the subject to the origin of the
|
|
// feature being tested (x in the above diagram). Distance will be a common
|
|
// test to use on all three sets, with all mannor of clamped ranges and
|
|
// sort values. Most common though will be to sort close to the owner so
|
|
// as to minimize how long it will take for the AI to get to the spot.
|
|
//
|
|
// - FacingDot RANGE=[-1, 1] WEIGHT=(-1=Behind, 1=In Front)
|
|
// FacingDot is computed with the dotproduct of the subject's facing and
|
|
// the feature's normal. This too will be a common test to compare with
|
|
// all three subjects. With it you can prefer points in front or behind
|
|
// things.
|
|
//
|
|
// - DirectionDot RANGE=[-1, 1] WEIGHT=(-1=Toward, 1=Away)
|
|
// DirectionDot is computed by subtracing the origins of the feature and
|
|
// the subject, normalizing and taking the dotproduct of the result with
|
|
// the feature's normal. Usually, you will want a negative weight on this
|
|
// test to comare how close the feature is pointing at the subject (usually
|
|
// tested against Focus)
|
|
//
|
|
// Lastly, there is a special "additional" test for "advance":
|
|
// - Advance RANGE=[-1, 1] WEIGHT=(-1=Backward, 1=Forward)
|
|
// Advance is computed using the owner direction dot with the path facing.
|
|
// This test will tell you how much the feature is between the owner and
|
|
// the focus, reguardless of what direction either is facing at the time.
|
|
// As a result, it approximates "advancing".
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Includes
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
#include "../Game_local.h"
|
|
#include "AI.h"
|
|
#include "AI_Manager.h"
|
|
#include "AI_Util.h"
|
|
#include "AAS_local.h"
|
|
#include "AAS_tactical.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Global::CONTANER SIZES AND OTHER LIMITS
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
const int MAX_FEATURE_LIST = 20;
|
|
const int MAX_AREAS_TOUCHED = 450;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// Global::FEATURE TESTING DISTANCES
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
const float MAX_DISTANCE = 650.0f;
|
|
const float MIN_DISTANCE = 72.0f;
|
|
const float MIN_NEAR_DISTANCE = 112.0f;
|
|
const float FROM_ENEMY_PROJECT = 350.0f;
|
|
const float TEST_TEAMMATE_DIST = 150.0f;
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// aasFeature_s::Normal
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
idVec3& aasFeature_s::Normal()
|
|
{
|
|
static idVec3 n(0,0,0);
|
|
n[0] = ((float)(normalx) / 127.0f) - 1.0f;
|
|
n[1] = ((float)(normaly) / 127.0f) - 1.0f;
|
|
return n;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// aasFeature_s::Origin()
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
idVec3& aasFeature_s::Origin()
|
|
{
|
|
static idVec3 o;
|
|
o.Set((float)x, (float)y, (float)z);
|
|
return o;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// aasFeature_s::GetLookPos()
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
int aasFeature_s::GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance )
|
|
{
|
|
static idVec3 up(0.0f,0.0f,1.0f);
|
|
static idVec3 direction;
|
|
static idVec3 right;
|
|
static float rightDot;
|
|
static float distance;
|
|
|
|
lookPos = Origin();
|
|
lookPos[2] += height - leanDistance;
|
|
direction = aimAtOrigin - lookPos;
|
|
distance = direction.NormalizeFast();
|
|
right = Normal().Cross(up);
|
|
rightDot = right * direction;
|
|
|
|
|
|
// Check For Optimal Conditions
|
|
//------------------------------
|
|
if (flags&FEATURE_LOOK_OVER && fabsf(rightDot)<0.2f)
|
|
{
|
|
lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad
|
|
return FEATURE_LOOK_OVER;
|
|
}
|
|
|
|
if (flags&FEATURE_LOOK_RIGHT && rightDot>0.0f)
|
|
{
|
|
lookPos += right * leanDistance;
|
|
return FEATURE_LOOK_RIGHT;
|
|
}
|
|
|
|
if (flags&FEATURE_LOOK_LEFT && rightDot<0.0f)
|
|
{
|
|
lookPos -= right * leanDistance;
|
|
return FEATURE_LOOK_LEFT;
|
|
}
|
|
|
|
|
|
// So Nothing Matches Perfectly, Let's Try Fallback Cases In This Order
|
|
//----------------------------------------------------------------------
|
|
if (flags&FEATURE_LOOK_OVER)
|
|
{
|
|
lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad
|
|
return FEATURE_LOOK_OVER;
|
|
}
|
|
|
|
if (flags&FEATURE_LOOK_RIGHT)
|
|
{
|
|
lookPos += right * leanDistance;
|
|
return FEATURE_LOOK_RIGHT;
|
|
}
|
|
|
|
if (flags&FEATURE_LOOK_LEFT)
|
|
{
|
|
lookPos -= right * leanDistance;
|
|
return FEATURE_LOOK_LEFT;
|
|
}
|
|
|
|
// This Is Odd, There Must Be No Look Flags On This Feature At All
|
|
//-----------------------------------------------------------------
|
|
return 0;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvSortReach
|
|
//
|
|
// This structure is used by the search heap below to sort areas by actual
|
|
// distance from the start point to do a distance based BFS instead of a
|
|
// least links based BFS.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
struct rvSortReach
|
|
{
|
|
int mAreaNum;
|
|
idReachability* mReach;
|
|
float mDistance;
|
|
|
|
bool operator<(const rvSortReach& r) const
|
|
{
|
|
return (mDistance>r.mDistance);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvTest
|
|
//
|
|
// A test is a mechanism to weight values and to provide min and max bounds.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
struct rvTest
|
|
{
|
|
float mMin;
|
|
float mMax;
|
|
float mWeight;
|
|
float mValue;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Save
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Save(idSaveGame *savefile)
|
|
{
|
|
savefile->WriteFloat(mMin);
|
|
savefile->WriteFloat(mMax);
|
|
savefile->WriteFloat(mWeight);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Restore
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Restore(idRestoreGame *savefile)
|
|
{
|
|
savefile->ReadFloat(mMin);
|
|
savefile->ReadFloat(mMax);
|
|
savefile->ReadFloat(mWeight);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Reset - Sets the values to defaults
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Reset()
|
|
{
|
|
mMax = 1.0f;
|
|
mMin = -1.0f;
|
|
mWeight = 0.0f;
|
|
mValue = 0.0f;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Weight - Simple compute weight
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float Weight()
|
|
{
|
|
return mValue * mWeight;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Test - Records the value out of the range and returns true if succeeded
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool Test(float Value, float MaxScale=1.0f)
|
|
{
|
|
if (mMin>-1.0f || mMax<1.0f || mWeight!=0.0f)
|
|
{
|
|
mValue = Value;
|
|
if (MaxScale!=1.0f)
|
|
{
|
|
mValue /= MaxScale;
|
|
}
|
|
mValue = (mValue-mMin) / (mMax-mMin); // Now Scale It [0.0, 1.0] Of Min And Max
|
|
return (mValue>=0.0f && mValue<=1.0f); // If Not In [0.0, 1.0], Test Fails
|
|
}
|
|
return true; // Test Is Not Active
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// WeightRange - Returns the abs of the weight, and adds to negatives
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float WeightRange(float& negatives)
|
|
{
|
|
if (mWeight<0.0f)
|
|
{
|
|
negatives += mWeight;
|
|
}
|
|
return fabsf(mWeight);
|
|
}
|
|
|
|
void DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction);
|
|
void DrawDebugInfo(const idVec4 color, const idVec3& origin);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvTestSet
|
|
//
|
|
// Test sets are designed to be used by the rvAASTacticalSensorLocal class to compute
|
|
// various vectors against a given feature.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
struct rvTestSet
|
|
{
|
|
// Parameters
|
|
//------------
|
|
bool mProjectOrigin; // If True, Origin Is Cast Out Along mFacing Vector
|
|
idVec3 mOrigin; // Position Of The Test Subject
|
|
idVec3 mFacing; // Orientation Of The Test Subject
|
|
|
|
// Computed During Test()
|
|
//------------------------
|
|
rvTest mDistance; // Resulting Distance To Feature
|
|
rvTest mFacingDot; // Resulting Facing Dot Product To Feature
|
|
rvTest mDirectionDot; // Resulting Direction Dot Product To Feature
|
|
idVec3 mDirection; // Resulting Direction To Feature
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Constructor
|
|
///////////////////////////////////////////////////////////////////////////
|
|
rvTestSet( void )
|
|
: mOrigin(0,0,0), mFacing(0,0,0), mProjectOrigin(false)
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Save
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Save(idSaveGame *savefile)
|
|
{
|
|
savefile->WriteBool(mProjectOrigin);
|
|
savefile->WriteVec3(mOrigin);
|
|
savefile->WriteVec3(mFacing);
|
|
mDistance.Save(savefile);
|
|
mFacingDot.Save(savefile);
|
|
mDirectionDot.Save(savefile);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Restore
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Restore(idRestoreGame *savefile)
|
|
{
|
|
savefile->ReadBool(mProjectOrigin);
|
|
savefile->ReadVec3(mOrigin);
|
|
savefile->ReadVec3(mFacing);
|
|
mDistance.Restore(savefile);
|
|
mFacingDot.Restore(savefile);
|
|
mDirectionDot.Restore(savefile);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Reset - Restets all sub tests
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void Reset()
|
|
{
|
|
mProjectOrigin = false;
|
|
mDirectionDot.Reset();
|
|
mFacingDot.Reset();
|
|
mDistance.Reset();
|
|
// bdube: Had to comment this out becaue it would break the test function which checks for non defaults
|
|
// mDistance.mMin = 0.0f; // special case (default would be -1.0f because all others are dot products)
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Weight - Adds up the weights of the sub tests
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float Weight()
|
|
{
|
|
return (mDistance.Weight() + mFacingDot.Weight() + mDirectionDot.Weight());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// WeightRange - Computes weight range of all sub tests
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float WeightRange(float& negatives)
|
|
{
|
|
return (mDistance.WeightRange(negatives) + mFacingDot.WeightRange(negatives) + mDirectionDot.WeightRange(negatives));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Test - This is the actuall feature test
|
|
///////////////////////////////////////////////////////////////////////////
|
|
bool Test(aasFeature_t* f, float distance=0.0f)
|
|
{
|
|
// First Compute The Direction
|
|
//-----------------------------
|
|
mDirection = f->Origin() - mOrigin;
|
|
|
|
// If Project Origin Is True, Then Move Origin Along Facing Vector
|
|
//-----------------------------------------------------------------
|
|
if (mProjectOrigin)
|
|
{
|
|
mDirection.ProjectOntoVector(mFacing);
|
|
mOrigin += mDirection;
|
|
mDirection = f->Origin() - mOrigin;
|
|
}
|
|
|
|
// If No Override On Distance, Compute It By Normalizing The Direction
|
|
//---------------------------------------------------------------------
|
|
if (!distance)
|
|
{
|
|
distance = mDirection.Normalize();
|
|
}
|
|
else
|
|
{
|
|
mDirection.Normalize();
|
|
}
|
|
|
|
|
|
// THE DISTANCE TEST
|
|
//-------------------
|
|
if (!mDistance.Test(distance, MAX_DISTANCE))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// THE FACING TEST
|
|
//-----------------
|
|
if (!mFacingDot.Test(f->Normal()*mFacing))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// THE DIRECTION TEST
|
|
//--------------------
|
|
if (!mDirectionDot.Test(f->Normal()*mDirection))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SetupOriginAndFacing - Called For Each Test Set
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void SetupOriginAndFacing(const idEntity* ent, const idVec3* originOverride=0, const idVec3* facingOverride=0)
|
|
{
|
|
if (!ent)
|
|
{
|
|
mOrigin = (originOverride)?(*originOverride):(vec3_origin);
|
|
mFacing = (facingOverride)?(*facingOverride):(vec3_origin);
|
|
}
|
|
else
|
|
{
|
|
const idActor* entActor = dynamic_cast<const idActor*>(ent); // bleh. Base entity class should properly return origin, angles, and forward vector
|
|
|
|
mOrigin = (originOverride)?(*originOverride):(ent->GetPhysics()->GetOrigin());
|
|
mFacing = (facingOverride)?(*facingOverride):(entActor?entActor->viewAxis[0]:ent->GetPhysics()->GetAxis(0)[0]);
|
|
}
|
|
|
|
mFacing[2] = 0;
|
|
mFacing.Normalize();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// ProjectOriginForward - Used By Several Setup Options To Project Origin
|
|
// Along Facing Direction Some
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void ProjectOriginForward(float distance, float xyRange=0.0f, bool randomFacing=0.0f)
|
|
{
|
|
mOrigin += (mFacing * distance);
|
|
if (xyRange)
|
|
{
|
|
mOrigin[0] += rvRandom::flrand(-xyRange, xyRange);
|
|
mOrigin[1] += rvRandom::flrand(-xyRange, xyRange);
|
|
}
|
|
if (randomFacing)
|
|
{
|
|
mFacing[0] = rvRandom::flrand(-1.0f, 1.0f);
|
|
mFacing[1] = rvRandom::flrand(-1.0f, 1.0f);
|
|
}
|
|
mFacing[2] = 0.0f;
|
|
mFacing.Normalize();
|
|
}
|
|
|
|
void DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin);
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal
|
|
//
|
|
// This is the local implimentation of the rvAASTacticalSensor interface. It
|
|
// contains all the various local data and functions needed to execute a
|
|
// search of the tactical data in AAS.
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
struct rvAASTacticalSensorLocal : rvAASTacticalSensor
|
|
{
|
|
// Owner
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
idActor* mOwner; // Owner Of The Sensor
|
|
idAI* mOwnerAI; // Owner As Already Cast To AI Type
|
|
|
|
// Search Parameters
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
idStr mSearchName; // Current Search Name
|
|
int mFlagsMatchAny; // Features must match AT LEAST ONE of these flags
|
|
int mFlagsMatchAll; // Features must match ALL of these flags
|
|
int mFlagsMatchNone; // Features must match NONE of these flags
|
|
int mFeaturesSearchMax; // Maximum number of features to extract from the grid
|
|
int mFeaturesFinalMax; // After sorting, prune list down to this size
|
|
rvTestSet mFromOwner; // Test Set For Owner Relation
|
|
rvTestSet mFromEnemy; // Test Set For Focus Relation
|
|
rvTestSet mFromTether; // Test Set for Tether Relation
|
|
rvTestSet mFromPath; // Test Set For Path Relation
|
|
rvTest mAdvance; // Single Test For Advance / Retreat
|
|
rvTest mAssignment; // Single Test For Assignment Direction Dot Product
|
|
rvTest mLeanNormal; // Single Test For Lean Normal Biasing
|
|
idVec3 mAssignmentDirection; // Used By Assignment Test
|
|
bool mAssignmentValid; // Turns On And Off The Assignment Test
|
|
idEntityPtr<idEntity> mEnemyOverride; // Overrides Enemy Pointer To Any Entity
|
|
|
|
// Search & Update Results
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
idList<aasFeature_t*> mFeatures; // The list of all features found in the most recent search
|
|
idVec3 mReservedOrigin; // Origin of feature that is currently reserved
|
|
aasFeature_t* mReserved; // Which feature is currently reserved
|
|
aasFeature_t* mNear; // Which feature is closest
|
|
aasFeature_t* mLook; // Which feature to look down
|
|
int mLookStartTime;
|
|
float mLookStopDist;
|
|
|
|
|
|
|
|
|
|
|
|
// Local API
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
rvAASTacticalSensorLocal();
|
|
~rvAASTacticalSensorLocal();
|
|
void Update();
|
|
void Save(idSaveGame *savefile);
|
|
void Restore(idRestoreGame *savefile);
|
|
void Clear();
|
|
void DrawDebugInfo();
|
|
|
|
// Search
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void Search();
|
|
void SearchReset(idEntity* enemyOverride=0, float ownerRangeMin=0.0f, float ownerRangeMax=1.0f);
|
|
void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f);
|
|
void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f);
|
|
void SearchHide(idEntity* from=0);
|
|
void SearchFlank();
|
|
void SearchAdvance();
|
|
void SearchRetreat();
|
|
void SearchAmbush();
|
|
void SearchDebug();
|
|
float SearchComputeWeightRange(float& rangeNegative);
|
|
float SearchComputeWeight();
|
|
|
|
// Feature Testing
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void TestSetupCurrentValues();
|
|
bool TestValid(aasFeature_t* f, float walkDistanceToFeature);
|
|
bool TestValidWithCurrentState(aasFeature_t* f=0);
|
|
|
|
// Feature Reservation
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void Reserve(aasFeature_t* f);
|
|
|
|
// Access To Results
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
int FeatureCount() {return mFeatures.Num();}
|
|
aasFeature_t* Feature(int i) {return mFeatures[i];}
|
|
aasFeature_t* Near() const {return mNear;}
|
|
aasFeature_t* Look() const {return mLook;}
|
|
aasFeature_t* Reserved() const {return mReserved;}
|
|
const idVec3& ReservedOrigin() const {return mReservedOrigin;}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Global::Objects
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
rvAASTacticalSensorLocal* mSensor;
|
|
float mDebugRadius;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Global::Typedefines
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
typedef idEntityPtr<idEntity> TEntPtr;
|
|
typedef aasFeature_t* TFeaturePtr;
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensor::CREATE_SENSOR
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
rvAASTacticalSensor* rvAASTacticalSensor::CREATE_SENSOR(idActor* owner)
|
|
{
|
|
rvAASTacticalSensorLocal* nSensor = new rvAASTacticalSensorLocal();
|
|
nSensor->mOwner = owner;
|
|
nSensor->mOwnerAI = dynamic_cast<idAI*>(owner);
|
|
return nSensor;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
rvAASTacticalSensorLocal::rvAASTacticalSensorLocal()
|
|
{
|
|
mOwner = 0;
|
|
mOwnerAI = 0;
|
|
Clear();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Destructor
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
rvAASTacticalSensorLocal::~rvAASTacticalSensorLocal()
|
|
{
|
|
Reserve(0);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Clear
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Clear()
|
|
{
|
|
mReserved = 0;
|
|
mNear = 0;
|
|
mLook = 0;
|
|
mSearchName = "";
|
|
mFeatures.Clear();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Save
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Save(idSaveGame *savefile)
|
|
{
|
|
savefile->WriteString(mSearchName);
|
|
savefile->WriteInt(mFlagsMatchAny);
|
|
savefile->WriteInt(mFlagsMatchAll);
|
|
savefile->WriteInt(mFlagsMatchNone);
|
|
savefile->WriteInt(mFeaturesSearchMax);
|
|
savefile->WriteInt(mFeaturesFinalMax);
|
|
mFromOwner.Save(savefile);
|
|
mFromEnemy.Save(savefile);
|
|
mFromTether.Save(savefile);
|
|
mFromPath.Save(savefile);
|
|
mAdvance.Save(savefile);
|
|
mAssignment.Save(savefile);
|
|
mLeanNormal.Save(savefile);
|
|
savefile->WriteVec3(mAssignmentDirection);
|
|
savefile->WriteBool(mAssignmentValid);
|
|
mEnemyOverride.Save(savefile);
|
|
|
|
// cnicholson: NOTE: The following 4 vars are set to 0 / cleared in the restore, so don't save them.
|
|
// NOSAVE: idList<aasFeature_t*> mFeatures;
|
|
// NOSAVE: savefile->WriteVec3(mFeatures);
|
|
// NOSAVE: aasFeature_t* mReserved;
|
|
// NOSAVE: aasFeature_t* mNear;
|
|
// NOSAVE: aasFeature_t* mLook;
|
|
savefile->WriteInt(mLookStartTime); // cnicholson: Added unsaved var
|
|
savefile->WriteFloat(mLookStopDist);// cnicholson: Added unsaved var
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Restore
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Restore(idRestoreGame *savefile)
|
|
{
|
|
// Clear Old Data
|
|
//----------------
|
|
mFeatures.Clear();
|
|
mNear = 0;
|
|
mLook = 0;
|
|
mReserved = 0;
|
|
|
|
// Read The Save File Search Parameters
|
|
//--------------------------------------
|
|
savefile->ReadString(mSearchName);
|
|
savefile->ReadInt(mFlagsMatchAny);
|
|
savefile->ReadInt(mFlagsMatchAll);
|
|
savefile->ReadInt(mFlagsMatchNone);
|
|
savefile->ReadInt(mFeaturesSearchMax);
|
|
savefile->ReadInt(mFeaturesFinalMax);
|
|
mFromOwner.Restore(savefile);
|
|
mFromEnemy.Restore(savefile);
|
|
mFromTether.Restore(savefile);
|
|
mFromPath.Restore(savefile);
|
|
mAdvance.Restore(savefile);
|
|
mAssignment.Restore(savefile);
|
|
mLeanNormal.Restore(savefile);
|
|
savefile->ReadVec3(mAssignmentDirection);
|
|
savefile->ReadBool(mAssignmentValid);
|
|
mEnemyOverride.Restore(savefile);
|
|
// Search();
|
|
|
|
savefile->ReadInt(mLookStartTime); // cnicholson: Added unrestored var
|
|
savefile->ReadFloat(mLookStopDist);// cnicholson: Added unrestored var
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Update
|
|
//
|
|
// If called regularly, this function will handle drawing debug information
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Update()
|
|
{
|
|
idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0));
|
|
if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden())
|
|
{
|
|
return;
|
|
}
|
|
|
|
idAASFile* file = aas->GetFile();
|
|
|
|
idVec3 velocityFwd = mOwner->GetPhysics()->GetLinearVelocity();
|
|
const idVec3& ownerOrigin = mOwner->GetPhysics()->GetOrigin();
|
|
int ownerAreaNum = mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) );
|
|
aasFeature_t* feature = 0;
|
|
aasArea_t& area = file->GetArea(ownerAreaNum);
|
|
idActor* teammate = NULL;
|
|
float featureDistance = 0.0f;
|
|
float featureDotLeft = 0.0f;
|
|
float featureDotDirection = 0.0f;
|
|
float teammateDistance = 0.0f;
|
|
float closestDistance = 0.0f;
|
|
idVec3 velocityLeft;
|
|
idVec3 velocityDown;
|
|
idVec3 featureDirection;
|
|
idVec3 teammateDirection;
|
|
|
|
|
|
|
|
// Update And Possibly Clear The Near Feature
|
|
//--------------------------------------------
|
|
if (mNear)
|
|
{
|
|
closestDistance = mNear->Origin().Dist(ownerOrigin);
|
|
if (closestDistance>MIN_NEAR_DISTANCE)
|
|
{
|
|
mNear = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Search For Features In This Area That Are Close To Owner Origin
|
|
//-----------------------------------------------------------------
|
|
if (area.numFeatures)
|
|
{
|
|
for (int areaFeatureNum=0; areaFeatureNum<area.numFeatures; areaFeatureNum++)
|
|
{
|
|
feature = & (file->GetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum)));
|
|
if (feature!=mNear)
|
|
{
|
|
featureDirection = feature->Origin();
|
|
featureDirection -= ownerOrigin;
|
|
featureDistance = featureDirection.NormalizeFast();
|
|
|
|
if (featureDistance<MIN_NEAR_DISTANCE && (!mNear || featureDistance<closestDistance))
|
|
{
|
|
mNear = feature;
|
|
closestDistance = featureDistance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (velocityFwd.LengthSqr()>1.0f)
|
|
{
|
|
// Compute Velocity Vectors
|
|
//--------------------------
|
|
velocityFwd.NormalizeFast();
|
|
velocityFwd.NormalVectors(velocityLeft, velocityDown);
|
|
|
|
|
|
// Check If We Should Clear The Look Feature
|
|
//-------------------------------------------
|
|
if (mLook)
|
|
{
|
|
const idVec3& featureOrigin = mLook->Origin();
|
|
const idVec3& featureNormal = mLook->Normal();
|
|
|
|
// Too Far?
|
|
//----------
|
|
if (featureOrigin.Dist(ownerOrigin)>mLookStopDist)
|
|
{
|
|
mLook = 0;
|
|
}
|
|
|
|
// Check All Team Mates To See If This Look Points At Them
|
|
//---------------------------------------------------------
|
|
for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next())
|
|
{
|
|
if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin();
|
|
teammateDistance = teammateDirection.NormalizeFast();
|
|
if (teammateDistance<TEST_TEAMMATE_DIST && teammateDirection*featureNormal>0.85f)
|
|
{
|
|
mLook = 0;
|
|
break;
|
|
}
|
|
}
|
|
teammate = NULL;
|
|
}
|
|
|
|
// And, If We Are Moving, Check The Near Feature To See If It Qualifies As A Look Feature
|
|
//----------------------------------------------------------------------------------------
|
|
if (mNear && mNear!=mLook && (gameLocal.GetTime() - mLookStartTime)>3000)
|
|
{
|
|
const idVec3& featureOrigin = mNear->Origin();
|
|
const idVec3& featureNormal = mNear->Normal();
|
|
|
|
// Compute Feature Direction
|
|
//---------------------------
|
|
featureDirection = featureOrigin;
|
|
featureDirection -= ownerOrigin;
|
|
featureDistance = featureDirection.NormalizeFast();
|
|
|
|
// Must Be Behind Me (I've Alreay Walked Past It)
|
|
//------------------------------------------------
|
|
if (featureDistance>16.0f && featureDirection*velocityFwd<0.0f)
|
|
{
|
|
// Must Be Facing Away From Me
|
|
//-----------------------------
|
|
featureDotDirection = featureNormal*featureDirection;
|
|
if (featureDotDirection>-0.8f)
|
|
{
|
|
// Must Be Roughly Perpendicular To My Velocity (Within 45 Degrees)
|
|
//------------------------------------------------------------------
|
|
featureDotLeft = featureNormal*velocityLeft;
|
|
if (fabsf(featureDotLeft)>0.5f)
|
|
{
|
|
// If On Left Of Me, Must Have A Right Lean, And Converse
|
|
//--------------------------------------------------------
|
|
if ((featureDotLeft>0.0f && mNear->flags&FEATURE_LOOK_RIGHT) ||
|
|
(featureDotLeft<0.0f && mNear->flags&FEATURE_LOOK_LEFT))
|
|
{
|
|
// Check All Team Mates To See If This Look Points At Them
|
|
//---------------------------------------------------------
|
|
for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next())
|
|
{
|
|
if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin();
|
|
teammateDistance = teammateDirection.NormalizeFast();
|
|
if (teammateDistance<TEST_TEAMMATE_DIST && teammateDirection*featureNormal>0.85f)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!teammate)
|
|
{
|
|
mLook = mNear;
|
|
mLookStartTime = gameLocal.GetTime();
|
|
mLookStopDist = rvRandom::flrand(48.0f, 80.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!mOwnerAI || !mOwnerAI->move.fl.moving)
|
|
{
|
|
mLook = 0;
|
|
}
|
|
|
|
// Draw Any Debug Info
|
|
//---------------------
|
|
DrawDebugInfo();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Reserve
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Reserve(aasFeature_t* f)
|
|
{
|
|
if (f!=mReserved && mOwner)
|
|
{
|
|
mReserved = f;
|
|
|
|
if ( f )
|
|
{
|
|
mReservedOrigin = f->Origin ( );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FEATURE TESTING
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// TestSetupCurrentValuesFor
|
|
//
|
|
// This function sets up the test sets to have new
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::TestSetupCurrentValues()
|
|
{
|
|
// Owner Test Set
|
|
//----------------
|
|
mFromOwner.SetupOriginAndFacing(mOwner);
|
|
|
|
|
|
// Enemy Test Set
|
|
//----------------
|
|
//NOTE!!! This does NOT clear any old info about your enemy, so if any tests
|
|
// use this info ASSUMING you have an enemy, your test will be totally
|
|
// wrong!!!
|
|
if (mOwnerAI && mEnemyOverride)
|
|
{
|
|
mFromEnemy.SetupOriginAndFacing(mEnemyOverride, &mOwnerAI->LastKnownPosition(mEnemyOverride), 0);
|
|
}
|
|
else if (mOwnerAI && mOwnerAI->GetEnemy())
|
|
{
|
|
mFromEnemy.SetupOriginAndFacing(mOwnerAI->GetEnemy(), &mOwnerAI->LastKnownPosition(mOwnerAI->GetEnemy()), 0);
|
|
}
|
|
|
|
// Tether Test Set
|
|
//----------------
|
|
if (mOwnerAI && mOwnerAI->IsTethered ( ) )
|
|
{
|
|
mFromTether.SetupOriginAndFacing(mOwnerAI->GetTether ( ));
|
|
}
|
|
|
|
// Path Test Set
|
|
//----------------
|
|
mFromPath.mProjectOrigin = true;
|
|
mFromPath.mOrigin = mFromOwner.mOrigin;
|
|
mFromPath.mFacing = mFromEnemy.mOrigin - mFromOwner.mOrigin;
|
|
mFromPath.mFacing[2] = 0;
|
|
mFromPath.mFacing.Normalize();
|
|
|
|
// Advance Test Set
|
|
//------------------
|
|
// NOTHING TO DO HERE FOR NOW...
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::TestValidReserved
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
bool rvAASTacticalSensorLocal::TestValidWithCurrentState(aasFeature_t* f)
|
|
{
|
|
// If Trying To Hide From An Enemy That No Longer Exists, Any Feature is Invalid
|
|
//-------------------------------------------------------------------------------
|
|
if (mEnemyOverride.GetSpawnId() && !mEnemyOverride.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Reset The Test Parameters With Current Origins (Cuz Things May Have Moved)
|
|
//----------------------------------------------------------------------------
|
|
TestSetupCurrentValues();
|
|
|
|
// And Run The Test That The Original Search Ran
|
|
//-----------------------------------------------
|
|
if (!f)
|
|
{
|
|
return TestValid(mReserved, 0.0f);
|
|
}
|
|
return TestValid(f, 0.0f);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::TestValid
|
|
//
|
|
// This is THE function that tests if a given feature matches the current
|
|
// search parameters. An optional walk distance may be passed into the function
|
|
// to replace the from owner straight line distance.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
bool rvAASTacticalSensorLocal::TestValid(aasFeature_t* f, float walkDistanceToFeature)
|
|
{
|
|
static idVec3 LeanNormal;
|
|
static idVec3 Up(0.0f,0.0f,1.0f);
|
|
static float LeanNormalDot;
|
|
|
|
|
|
// Is There A Feature At All
|
|
//---------------------------
|
|
if (!f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Does It Match The Flags?
|
|
//--------------------------
|
|
if (!(f->flags&mFlagsMatchAny) ||
|
|
((f->flags&mFlagsMatchAll)!=mFlagsMatchAll) ||
|
|
(f->flags&mFlagsMatchNone))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Does It Pass The Tests?
|
|
//-------------------------
|
|
if (!mFromOwner.Test(f, walkDistanceToFeature) ||
|
|
!mFromEnemy.Test(f) ||
|
|
!mFromTether.Test(f) ||
|
|
!mFromPath.Test(f) ||
|
|
!mAdvance.Test(mFromOwner.mDirection*mFromPath.mFacing) ||
|
|
!mAssignment.Test(mFromOwner.mDirection*mAssignmentDirection))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Make sure this cover point is vaild for the current tether
|
|
//------------------------------------------------------------
|
|
if ( mOwnerAI && mOwnerAI->IsTethered() && !mOwnerAI->GetTether()->ValidateDestination ( mOwnerAI, f->Origin() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Does It Pass The Lean Normal Test?
|
|
//------------------------------------
|
|
if (mOwnerAI && (mEnemyOverride||mOwnerAI->GetEnemy()))
|
|
{//we have an enemy position to test against
|
|
mLeanNormal.mValue = 0.0f;
|
|
if (!(f->flags&FEATURE_LOOK_OVER) && ((f->flags&FEATURE_LOOK_RIGHT) || (f->flags&FEATURE_LOOK_LEFT)))
|
|
{
|
|
LeanNormal = f->Normal().Cross(Up); // Start With Left
|
|
LeanNormalDot = LeanNormal * mFromEnemy.mDirection;
|
|
|
|
if (!(f->flags&FEATURE_LOOK_LEFT) || ((f->flags&FEATURE_LOOK_RIGHT) && LeanNormalDot<0.0f))
|
|
{
|
|
LeanNormalDot *= -1.0f; // Use The Right Normal
|
|
}
|
|
|
|
if (!mLeanNormal.Test(LeanNormalDot))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Is Anyone Else Going There?
|
|
//-----------------------------
|
|
if (mOwnerAI && !aiManager.ValidateDestination(mOwnerAI, f->Origin()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Everything Passed, This Feature Is Good To Go
|
|
//-----------------------------------------------
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SEARCH PARAMETERS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SearchReset
|
|
//
|
|
// Initialize Default Flags, Feature Counts, And Population Points. This
|
|
// function must be called before first in any search function, because
|
|
// the search functions rely on this standard set of parameters and then
|
|
// build upon them.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchReset(idEntity* enemyOverride, float ownerRangeMin, float ownerRangeMax)
|
|
{
|
|
if (!mOwner)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Setup Base Search Parameters
|
|
//------------------------------
|
|
mFlagsMatchAll = 0; // Must Have All Of These
|
|
mFlagsMatchAny = (FEATURE_LOOK_LEFT|FEATURE_LOOK_RIGHT|FEATURE_LOOK_OVER); // Must Have At Least One Of These
|
|
mFlagsMatchNone = (FEATURE_PINCH|FEATURE_VANTAGE); // Don't Want Any Of These
|
|
mFeaturesSearchMax = 100;
|
|
mFeaturesFinalMax = 20;
|
|
mAssignmentValid = false; // TODO: Turn This Back On
|
|
mAssignmentDirection = vec3_zero;
|
|
mEnemyOverride = enemyOverride;
|
|
|
|
|
|
// Lean Normal Test
|
|
//------------------
|
|
mLeanNormal.Reset();
|
|
mLeanNormal.mMin =-0.2f; // Must Lean Toward Enemy
|
|
|
|
|
|
// Owner Test Set Default Values
|
|
//-------------------------------
|
|
mFromOwner.Reset();
|
|
mFromOwner.mDistance.mMin = ownerRangeMin;
|
|
mFromOwner.mDistance.mMax = ownerRangeMax;
|
|
mFromOwner.mDistance.mWeight =-1.0f; // Prefer Close To Owner
|
|
|
|
// Enemy Test Set Default Values
|
|
//-------------------------------
|
|
//NOTE!!! This does NOT clear any old info about your enemy, so if any tests
|
|
// use this info ASSUMING you have an enemy, your test will be totally
|
|
// wrong!!!
|
|
mFromEnemy.Reset();
|
|
if ( mOwnerAI && mOwnerAI->enemy.ent )
|
|
{
|
|
mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; // must be at least 100 units from enemy
|
|
mFromEnemy.mDistance.mMax = 2.0f; // don't care how far the distance is to the enemy, let it go over max (up to 1600)
|
|
mFromEnemy.mDirectionDot.mMax =-0.7f; // Must Face Within 45 Degrees Of enemy
|
|
mFromEnemy.mDirectionDot.mWeight =-0.3f; // Prefer To Face Toward Enemy
|
|
if (mOwnerAI && mOwnerAI->enemy.ent )
|
|
{
|
|
// Cap Min And Max Distances To Attack Range, and Aware Range
|
|
//------------------------------------------------------------
|
|
mFromEnemy.mDistance.mMax = mOwnerAI->combat.attackRange[1] / MAX_DISTANCE;
|
|
mFromEnemy.mDistance.mMin = mOwnerAI->combat.attackRange[0] / MAX_DISTANCE;
|
|
|
|
if (mFromEnemy.mDistance.mMin < (mOwnerAI->combat.awareRange / MAX_DISTANCE))
|
|
{
|
|
mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE;
|
|
}
|
|
|
|
// If haven't seen enemy in a while, allow you to go right to his last known spot
|
|
//--------------------------------------------------------------------------------
|
|
if ( mOwnerAI->enemy.lastVisibleTime && (gameLocal.GetTime() - mOwnerAI->enemy.lastVisibleTime)>mOwnerAI->combat.maxLostVisTime/2.0f)
|
|
{
|
|
mFromEnemy.mDistance.mMin = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Tether test set
|
|
//-------------------------------
|
|
mFromTether.Reset();
|
|
if (mOwnerAI && mOwnerAI->IsTethered ( ) )
|
|
{
|
|
mFromTether.mFacingDot.mMin = 0.5f;
|
|
mFromTether.mFacingDot.mWeight = 0.3f;
|
|
|
|
// Disable the owner distance test
|
|
mFromOwner.Reset();
|
|
}
|
|
|
|
// Other Test Sets
|
|
//-----------------
|
|
mFromPath.Reset();
|
|
mAdvance.Reset();
|
|
mAssignment.Reset();
|
|
|
|
|
|
// Default Test Values
|
|
//---------------------
|
|
TestSetupCurrentValues();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchDebug
|
|
//
|
|
// TO RUN THIS FUNCTION, TYPE "extract_tactical" ON THE CONSOLE.
|
|
//
|
|
// Feel free to modify this function to test whatever search or other
|
|
// operation you need. The owner will be the player.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchDebug()
|
|
{
|
|
SearchCover();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchRadius
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchRadius(const idVec3& origin, float rangeMin, float rangeMax)
|
|
{
|
|
SearchReset(0, rangeMin, rangeMax);
|
|
mSearchName = "Radius";
|
|
mFromEnemy.Reset(); // Don't Care About Enemy At All
|
|
if ( origin != vec3_origin )
|
|
{
|
|
mFromOwner.mOrigin = origin; // Override the owner origin
|
|
}
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchCover
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchCover(float rangeMin, float rangeMax)
|
|
{
|
|
SearchReset(0, rangeMin, rangeMax);
|
|
mSearchName = "Cover";
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchHide
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchHide(idEntity* from)
|
|
{
|
|
SearchReset(from);
|
|
mSearchName = "Hide";
|
|
mFlagsMatchNone |= FEATURE_LOOK_OVER; // Want Full Height Walls Here
|
|
mFromEnemy.mDirectionDot.mMax = -0.8f; // Must Almost Exactly At The Enemy
|
|
mFromOwner.mDistance.mMax = 2.0f; // Go as far as you need to - ignore tethering for hide
|
|
mFromOwner.mDistance.mMin = 0.4f; // Get A Good Distance Away
|
|
mFromOwner.mDirectionDot.Reset(); // Ignore any direction dot with the leader
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchFlank
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchFlank()
|
|
{
|
|
SearchReset();
|
|
mSearchName = "Flank";
|
|
mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are
|
|
mFromEnemy.mFacingDot.mMin = -0.2f; // Must Be Behind Enemy
|
|
mAdvance.mMin = -0.5f; // In Front Of Owner
|
|
mAdvance.mMax = 0.8f; // But Not Directly Along Path
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchAdvance
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchAdvance()
|
|
{
|
|
SearchReset();
|
|
mSearchName = "Advance";
|
|
mFromOwner.mDistance.mMin = 0.15f; // Make Sure To Move Some
|
|
mAdvance.mMin = 0.3f; // Forward!
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchRetreat
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchRetreat()
|
|
{
|
|
SearchReset();
|
|
mSearchName = "Retreat";
|
|
mFromOwner.mDistance.mMin = 0.1f; // Make Sure To Move Some
|
|
mAdvance.mMax = -0.3f; // Backward!
|
|
Search();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::SearchAmbush
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::SearchAmbush()
|
|
{
|
|
SearchReset();
|
|
mSearchName = "Ambush";
|
|
mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are
|
|
mFromEnemy.mFacingDot.mMax = 0.2f; // Must Be In Front Of Enemy
|
|
mAdvance.mMin = -0.5f; // In Front Of Owner
|
|
mAdvance.mMax = 0.8f; // But Not Directly Along Path
|
|
Search();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// THE SEARCH
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SearchComputeWeightRange
|
|
//
|
|
// We compute the weight range so that we can make a better scale factor
|
|
// between 0.0 and 1.0 later on. It's not critical that all the weights
|
|
// factor exactly 0.0 to 1.0, but it is nice to see how "good" a feature
|
|
// matches the given search parameters
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float rvAASTacticalSensorLocal::SearchComputeWeightRange(float& rangeNegative)
|
|
{
|
|
return (mFromOwner.WeightRange(rangeNegative) +
|
|
mFromEnemy.WeightRange(rangeNegative) +
|
|
mFromTether.WeightRange(rangeNegative) +
|
|
mFromPath.WeightRange(rangeNegative) +
|
|
mAdvance.WeightRange(rangeNegative) +
|
|
mLeanNormal.WeightRange(rangeNegative) +
|
|
mAssignment.WeightRange(rangeNegative));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Weight
|
|
//
|
|
// Add up the computed weight of all tests.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float rvAASTacticalSensorLocal::SearchComputeWeight()
|
|
{
|
|
return (mFromOwner.Weight() +
|
|
mFromEnemy.Weight() +
|
|
mFromTether.Weight() +
|
|
mFromPath.Weight() +
|
|
mAdvance.Weight() +
|
|
mLeanNormal.Weight() +
|
|
mAssignment.Weight());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// SortFeature function (used by Search() below)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
ID_INLINE int rvSortFeature( const TFeaturePtr *a, const TFeaturePtr *b )
|
|
{
|
|
if ((*a)->weight > (*b)->weight)
|
|
{
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::Search
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::Search()
|
|
{
|
|
idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0));
|
|
if (!mOwner || !aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures())
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
static idAASFile* file;
|
|
static int areaNum;
|
|
static int areaNumOwner;
|
|
static rvBits<32000> areaVisit;
|
|
static int areaVisitCount;
|
|
static int travelTime;
|
|
static int areaFeatureNum;
|
|
static int featureNum;
|
|
static aasFeature_t* featurePtr;
|
|
static idVec3 featureOrigin;
|
|
static idReachability* reach;
|
|
static idList<rvSortReach> searchHeap;
|
|
static rvSortReach sortReach;
|
|
static float walkDistanceToFeature;
|
|
static float weight;
|
|
static float weightRangeNegative;
|
|
static float weightRangeTotal;
|
|
static idVec3 from;
|
|
static idVec3 endPos;
|
|
static int endAreaNum;
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// SETUP
|
|
//-----------------------------------------------------------------------------
|
|
file = aas->GetFile();
|
|
weightRangeNegative = 0.0f;
|
|
weightRangeTotal = SearchComputeWeightRange(weightRangeNegative);
|
|
|
|
mFeatures.Clear();
|
|
searchHeap.Clear();
|
|
areaVisit.clear();
|
|
if (!mAssignmentValid)
|
|
{
|
|
mAssignment.Reset(); // Never Worry About Squad Assignments If No Leader Is Active
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// PHASE I - POPULATE AREA QUEUE
|
|
//-----------------------------------------------------------------------------
|
|
if ( mOwnerAI )
|
|
{
|
|
areaNumOwner = mOwnerAI->PointReachableAreaNum ( mFromOwner.mOrigin );
|
|
}
|
|
else
|
|
{
|
|
areaNumOwner = aas->PointReachableAreaNum(mFromOwner.mOrigin, mOwner->GetPhysics()->GetBounds(), AREA_REACHABLE_WALK);
|
|
}
|
|
|
|
from = mFromOwner.mOrigin;
|
|
areaNum = areaNumOwner;
|
|
sortReach.mAreaNum = areaNumOwner;
|
|
sortReach.mDistance = 0.0f;
|
|
sortReach.mReach = 0;
|
|
searchHeap.Append(sortReach);
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// PHASE II - BREADTH FIRST SEARCH NEIGHBORING AREAS FOR FEATURES THAT TEST OK
|
|
//-----------------------------------------------------------------------------
|
|
areaVisitCount = 0;
|
|
while (searchHeap.Num() && areaVisitCount<MAX_AREAS_TOUCHED && mFeatures.Num()<mFeaturesSearchMax)
|
|
{
|
|
rvSortReach sr = searchHeap[0];
|
|
searchHeap.HeapPop();
|
|
if (areaVisit[sr.mAreaNum])
|
|
{
|
|
continue;
|
|
}
|
|
areaVisit.set(sr.mAreaNum);
|
|
areaVisitCount ++;
|
|
|
|
|
|
// Check For Features Here
|
|
//-------------------------
|
|
const aasArea_t& area = file->GetArea(sr.mAreaNum);
|
|
if (area.numFeatures)
|
|
{
|
|
for (areaFeatureNum=0; areaFeatureNum<area.numFeatures; areaFeatureNum++)
|
|
{
|
|
featurePtr = & (file->GetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum)));
|
|
featureOrigin = featurePtr->Origin();
|
|
|
|
// If Walk Path Is Valid, Allow From Owner Test To Use Computed Straight Line Distance
|
|
//-------------------------------------------------------------------------------------
|
|
if (aas->WalkPathValid(areaNumOwner, mFromOwner.mOrigin, sr.mAreaNum, featureOrigin, TFL_WALK, endPos, endAreaNum))
|
|
{
|
|
walkDistanceToFeature = 0.0f; // Allows Test to use straight line distance computed
|
|
}
|
|
|
|
// If It Is Not Possible To Straight Line Walk To A Feature, Use The Enter Point And Distance Of The Area (Which Is A Rough Appx)
|
|
//--------------------------------------------------------------------------------------------------------------------------------
|
|
else
|
|
{
|
|
walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(featureOrigin)):(mFromOwner.mOrigin.Dist(featureOrigin)));
|
|
}
|
|
|
|
// Test The Feature To See If It's Valid
|
|
//---------------------------------------
|
|
if (!TestValid(featurePtr, walkDistanceToFeature))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Compute The Weight
|
|
//--------------------
|
|
if (weightRangeTotal>0.0f)
|
|
{
|
|
weight = SearchComputeWeight(); // Compute Weight Sum
|
|
weight -= weightRangeNegative; // Bring it into a positive range
|
|
weight /= weightRangeTotal; // Scale down to 0.0 - 1.0
|
|
|
|
assert(weight>0.0f && weight<255.0f);
|
|
featurePtr->weight = (char)(weight*255);
|
|
}
|
|
else
|
|
{
|
|
featurePtr->weight = (unsigned char)(128); // No Sorting
|
|
}
|
|
|
|
|
|
// Append The Feature To The List
|
|
//--------------------------------
|
|
mFeatures.Append(featurePtr);
|
|
}
|
|
}
|
|
|
|
|
|
// Add Neighboring Areas To Search
|
|
//---------------------------------
|
|
for (reach=area.reach; reach; reach=reach->next)
|
|
{
|
|
if ((reach->travelType&TFL_WALK))
|
|
{
|
|
walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(reach->end)):(mFromOwner.mOrigin.Dist(reach->end)));
|
|
|
|
if (walkDistanceToFeature<MAX_DISTANCE)
|
|
{
|
|
sortReach.mDistance = walkDistanceToFeature;
|
|
sortReach.mAreaNum = reach->toAreaNum;
|
|
sortReach.mReach = reach;
|
|
searchHeap.HeapAdd(sortReach);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// PHASE III - SORT AND CLIP THE FEATURE LIST
|
|
//-----------------------------------------------------------------------------
|
|
mFeatures.Sort(rvSortFeature);
|
|
|
|
// Now, Clip The Sorted List To The Max Size
|
|
//-------------------------------------------
|
|
if (mFeatures.Num()>MAX_FEATURE_LIST)
|
|
{
|
|
mFeatures.Resize(MAX_FEATURE_LIST);
|
|
}
|
|
|
|
// Now Reset Any Parameters Which Were Only "Temporary" During The Search, and Do Not Invalidate The Point Later
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
mFromOwner.mDistance.mMin = 0.0f; // Allow Getting Close Again
|
|
|
|
|
|
// Print Search Results
|
|
//----------------------
|
|
if (ai_showTacticalFeatures.GetInteger()==3)
|
|
{
|
|
common->Printf( "[%10d] Search%s Found %d Features For %s\n", gameLocal.GetTime(), mSearchName.c_str(), mFeatures.Num(), mOwner->GetName() );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// DEBUG GRAPHICS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// aasFeature_t::DrawDebugInfo
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void aasFeature_t::DrawDebugInfo( int index )
|
|
{
|
|
static idVec3 Height;
|
|
static idVec3 Orig;
|
|
static idVec3 Norm;
|
|
static idVec3 Text;
|
|
static idVec4 color;
|
|
static idVec3 Left;
|
|
|
|
int lifetime = 0;
|
|
|
|
color = colorWhite;
|
|
|
|
if (flags & FEATURE_COVER)
|
|
{
|
|
color = colorGreen;
|
|
}
|
|
|
|
Orig = Origin();
|
|
Orig[2] += 1.0f;
|
|
|
|
Height = Orig;
|
|
Height[2] += height;
|
|
|
|
Norm = Orig;
|
|
Norm += Normal() * mDebugRadius;
|
|
Left = Normal().Cross(idVec3(0,0,-1)) * mDebugRadius;
|
|
|
|
gameRenderWorld->DebugLine( color, Orig, Height, lifetime );
|
|
gameRenderWorld->DebugLine( color, Orig, Norm, lifetime );
|
|
|
|
if (index>=0)
|
|
{
|
|
Text = (Origin() + Height) * 0.5f;
|
|
gameRenderWorld->DrawText( va( "%d", index ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime );
|
|
|
|
Text[2] += 15.0f;
|
|
gameRenderWorld->DrawText( va( "%d", (int)weight ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime );
|
|
}
|
|
|
|
// Left Corner Is On The Ground
|
|
//------------------------------
|
|
if (flags & FEATURE_CORNER_LEFT)
|
|
{
|
|
gameRenderWorld->DebugLine( color, Orig, Orig + Left, lifetime );
|
|
}
|
|
|
|
// Otherwise Windows Are At The Given Height
|
|
//-------------------------------------------
|
|
else if (flags & FEATURE_LOOK_LEFT)
|
|
{
|
|
gameRenderWorld->DebugLine( color, Height, Height + Left, lifetime );
|
|
}
|
|
|
|
if (flags & FEATURE_CORNER_RIGHT)
|
|
{
|
|
gameRenderWorld->DebugLine( color, Orig, Orig - Left, lifetime );
|
|
}
|
|
else if (flags & FEATURE_LOOK_RIGHT)
|
|
{
|
|
gameRenderWorld->DebugLine( color, Height, Height - Left, lifetime );
|
|
}
|
|
|
|
if (flags & FEATURE_LOOK_OVER)
|
|
{
|
|
gameRenderWorld->DebugLine( color, Height, Height + (Normal()*mDebugRadius), lifetime );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// rvTest::DrawDebugInfo
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction)
|
|
{
|
|
gameRenderWorld->DebugFOV(color, origin, direction, mMax, 20.0f, mMin, 10.0f, 20.0f);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// rvTest::DrawDebugInfo
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin)
|
|
{
|
|
static idVec3 up(0.0f,0.0f,-1.0f);
|
|
if (mMax<1.0f)
|
|
{
|
|
gameRenderWorld->DebugCircle(color, origin, up, (mMax * MAX_DISTANCE), 25);
|
|
}
|
|
if (mMin>0.0f)
|
|
{
|
|
gameRenderWorld->DebugCircle(color, origin, up, (mMin * MAX_DISTANCE), 25);
|
|
}
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// rvTestSet::DrawDebugInfo
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void rvTestSet::DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin)
|
|
{
|
|
static int lifetime = 0;
|
|
static idVec3 origin;
|
|
|
|
origin = mOrigin;
|
|
if (mProjectOrigin)
|
|
{
|
|
origin = nonProjectedOrigin;
|
|
}
|
|
|
|
gameRenderWorld->DebugArrow( color, origin, origin+mFacing * 25.0f, 8, lifetime );
|
|
mFacingDot.DrawDebugInfo(color, origin, mFacing);
|
|
mDistance.DrawDebugInfo(color, origin);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// rvAASTacticalSensorLocal::DrawDebugInfo
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void rvAASTacticalSensorLocal::DrawDebugInfo()
|
|
{
|
|
idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0));
|
|
if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden() || (ai_showTacticalFeatures.GetInteger()<2 && !mOwner->DebugFilter(ai_showTacticalFeatures) && !mOwner->DebugFilter(ai_debugTactical)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
static idVec3 pos;
|
|
bool reservedDrawn = false;
|
|
bool nearDrawn = false;
|
|
bool lookDrawn = false;
|
|
|
|
mDebugRadius = aas->GetSettings()->boundingBoxes[0][1][0];
|
|
|
|
|
|
// Draw Parameters
|
|
//-----------------
|
|
if (ai_showTacticalFeatures.GetInteger()==1)
|
|
{
|
|
if (!mSearchName.IsEmpty())
|
|
{
|
|
pos = mFromOwner.mOrigin + mFromEnemy.mOrigin;
|
|
pos *= 0.5f;
|
|
pos[2] += 25.0f;
|
|
gameRenderWorld->DrawText(mSearchName.c_str(), pos, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, 0 );
|
|
pos[2] -= 25.0f;
|
|
|
|
// Draw Tests
|
|
//------------
|
|
mFromEnemy.DrawDebugInfo(colorYellow, pos);
|
|
mFromTether.DrawDebugInfo(colorOrange, pos);
|
|
mFromOwner.DrawDebugInfo(colorMagenta, pos);
|
|
mFromPath.DrawDebugInfo(colorCyan, pos);
|
|
mAssignment.DrawDebugInfo(colorPink, pos);
|
|
mAdvance.DrawDebugInfo(colorPurple, mFromOwner.mOrigin, mFromPath.mFacing);
|
|
mLeanNormal.DrawDebugInfo(colorPurple, pos);
|
|
}
|
|
|
|
|
|
// Draw Features
|
|
//---------------
|
|
for (int i=0; i<mFeatures.Num(); i++)
|
|
{
|
|
mFeatures[i]->DrawDebugInfo(i);
|
|
if (mFeatures[i]==mReserved)
|
|
{
|
|
reservedDrawn = true;
|
|
}
|
|
if (mFeatures[i]==mNear)
|
|
{
|
|
nearDrawn = true;
|
|
}
|
|
if (mFeatures[i]==mLook)
|
|
{
|
|
lookDrawn = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw All Neighboring Features If Player & CVar==2
|
|
//---------------------------------------------------
|
|
if (mOwner==gameLocal.GetLocalPlayer() && ai_showTacticalFeatures.GetInteger()>=2)
|
|
{
|
|
idAASFile* file = aas->GetFile();
|
|
const idVec3& playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin();
|
|
for (int i=0; i<file->GetNumFeatures(); i++)
|
|
{
|
|
if (file->GetFeature(i).Origin().Dist(playerOrigin)<600.0f)
|
|
{
|
|
file->GetFeature(i).DrawDebugInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Always Draw Reserved
|
|
//----------------------
|
|
if (mReserved)
|
|
{
|
|
if (!reservedDrawn)
|
|
{
|
|
mReserved->DrawDebugInfo();
|
|
}
|
|
gameRenderWorld->DebugArrow(colorBlue, mOwner->GetPhysics()->GetOrigin(), mReserved->Origin(), 8);
|
|
}
|
|
|
|
// If Near Is Valid, Draw It
|
|
//---------------------------
|
|
if (mNear && (!mReserved || mNear!=mReserved))
|
|
{
|
|
if (!nearDrawn)
|
|
{
|
|
mNear->DrawDebugInfo();
|
|
}
|
|
gameRenderWorld->DebugArrow(colorOrange, mOwner->GetPhysics()->GetOrigin(), mNear->Origin(), 8);
|
|
}
|
|
|
|
// If Look Is True, Then Draw That
|
|
//---------------------------------
|
|
if (mLook && (!mOwnerAI || mOwnerAI->InLookAtCoverMode()))
|
|
{
|
|
idVec3 n = mLook->Normal();
|
|
n *= 64.0f;
|
|
gameRenderWorld->DebugArrow(colorYellow, mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32), mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32) + n, 8);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// RAVENEND - CDR
|
|
|
|
|
|
|
|
|
|
|