engine/ai/aiConnection.cc

2976 lines
97 KiB
C++
Raw Normal View History

2024-01-07 04:36:33 +00:00
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "core/realComp.h"
#include "math/mMatrix.h"
#include "console/console.h"
#include "game/gameBase.h"
#include "ai/aiConnection.h"
#include "ai/aiStep.h"
#include "ai/aiNavStep.h"
#include "ai/aiTask.h"
#include "ai/graphMath.h"
#include "terrain/waterBlock.h"
#include "scenegraph/sceneGraph.h"
#include "game/vehicle.h"
#include "platform/profiler.h"
IMPLEMENT_CONOBJECT(AIConnection);
static S32 gAIDetectionOffset = 0;
AIConnection::AIConnection()
{
mAIControlled = true;
mMoveMode = mMoveModePending = ModeStop;
mMoveLocation.set(0, 0, 0);
mNodeLocation.set(0, 0, 0);
mAimLocation.set(0, 0, 0);
mMoveSpeed = 0.0f;
mMoveTolerance = 0.25f;
mStep = NULL;
mCurrentTask = NULL;
mCurrentTaskTime = 0;
mPathDest.set(0, 0, 0);
mNewPath = false;
//clear the triggers
for (int i = 0; i < MaxTriggerKeys; i++)
mTriggers[i] = false;
mPrevNodeLocation.set(0, 0, 0);
mInitialLocation.set(0, 0, 0);
mEnergyReserve = 0.0f;
mEnergyFloat = 0.10f;
mEnergyRecharge = false;
//init engage vars
mEngageState = ChooseWeapon;
mProjectileName = StringTable->insert("");
mProjectile = NULL;
mEngageMinDistance = 20;
mEngageMaxDistance = 75;
mDistToTarg2D = -1.0f;
mLookAtTargetTimeMS = 0;
mFiring = false;
mTriggerCounter = 0;
mScriptTriggerCounter = -1;
mVictimTime = 0;
mSkillLevel = 0.5;
mChangeWeaponCounter = 0;
mTurretMountedId = 0;
mMountedImage = NULL;
//piloting vars
mPilotDestination.set(0, 0, 0);
mPilotAimLocation.set(0, 0, 0);
mPilotSpeed = 1.0f;
mPitchUpMax = -0.25;
mPitchDownMax = 0.1f;
mPitchIncMax = 0.05f;
mCurrentPitch = 0;
mPreviousPitch = 0;
mDesiredPitch = 0;
mPitchIncrement = 0;
mTargStillTimeMS = 0;
mTargPrevTimeMS = 0;
mTargPrevLocation[0].set(0, 0, 0);
mTargPrevLocation[1].set(0, 0, 0);
mTargPrevLocation[2].set(0, 0, 0);
mTargPrevLocation[3].set(0, 0, 0);
mPlayerDetectionIndex = 0;
mPlayerDetectionCounter = gAIDetectionOffset;
gAIDetectionOffset++;
if (gAIDetectionOffset >= 3)
gAIDetectionOffset = 0;
mDetectHiddenPeriod = 6000;
mBlindedTimer = 0;
//init target object vars
mTargetObject = NULL;
mObjectMode = DestroyObject;
mDistToObject2D = -1.0f;
//these are used for both mEngageTarget and mTargetObject
mRangeToTarget = 30;
mCheckTargetLOSCounter = 0;
mTargetInRange = false;
mTargetInSight = false;
mWeaponEnergy = 0.0f;
mWeaponErrorFactor = 1.0f;
//init evading vars
mEnemyProjectile = NULL;
mEvadingCounter = 0;
mIsEvading = false;
mAvoidingObject = NULL;
mStuckInitialized = false;
// LH. Just for the heck of it (and as way to familiarize with code) - added in all
// these that weren't being constructed.
mMoveDestination.set(0, 0, 0);
mEvadeLocation.set(0, 0, 0);
mImpactLocation.set(0, 0, 0);
mCorpseLocation.set(0, 0, 0);
mStateCounter = 0;
mDelayCounter = 0;
mPackCheckCounter = 0;
mAimAtLazedTarget = false;
mProjectileCounter = 0;
mPath.setTeam(getSensorGroup());
mStuckDestination.set(0,0,0);
mTargetPlayer = NULL;
mLocation.set(0,0,0);
mVelocity = mVelocity2D = mRotation = mHeadRotation =
mMuzzlePosition = mEyePosition = mTargLocation =
mTargVelocity = mTargVelocity2D = mTargRotation = mLocation;
mTargEnergy = 1.0;
mTargDamage = 0;
}
AIConnection::~AIConnection()
{
}
void AIConnection::setSkillLevel(F32 level)
{
mSkillLevel = getMax(0.0f, getMin(1.0f, level));
}
void AIConnection::setMoveSpeed(F32 speed)
{
if (speed <= 0.0f)
mMoveSpeed = 0.0f;
else
mMoveSpeed = getMin(1.0f, speed);
}
void AIConnection::setMoveMode(S32 mode, bool abortStuckCode)
{
if (mode < 0 || mode >= ModeCount)
mode = 0;
//if we're setting mode::express and we're currently stuck, let the stuck code
//finish "unsticking" the bot... calling setMoveDestination will abort the stuck code
if (mMoveMode == ModeStuck && mode != ModeStop && !abortStuckCode)
return;
//make sure we're not moving if in the middle of a jet...
mMoveModePending = mode;
if (!mNavUsingJet)
mMoveMode = mode;
}
void AIConnection::setMoveTolerance(F32 tolerance)
{
mMoveTolerance = getMax(0.1f, tolerance);
}
void AIConnection::setMoveDestination(const Point3F &location)
{
if (mMoveDestination != location && mMoveMode == ModeStuck)
{
setMoveMode(ModeExpress, true);
mStuckLocation.set(0, 0, 0);
}
mMoveDestination = location;
}
void AIConnection::setMoveLocation(const Point3F &location)
{
mMoveLocation = location;
}
void AIConnection::setAimLocation(const Point3F &location)
{
mAimLocation = location;
}
bool AIConnection::setScriptAimLocation(const Point3F &location, S32 duration)
{
//can't set through scripts if the bots are aiming at either an object, or an engage player
if (mTargetPlayer || bool(mTargetObject))
return false;
setAimLocation(location);
mLookAtTargetTimeMS = getMax(mLookAtTargetTimeMS, (S32)Sim::getCurrentTime() + duration);
return true;
}
void AIConnection::setPilotPitchRange(F32 pitchUpMax, F32 pitchDownMax, F32 pitchIncMax)
{
mPitchUpMax = pitchUpMax;
mPitchDownMax = pitchDownMax;
mPitchIncMax = pitchIncMax;
}
void AIConnection::setPilotDestination(const Point3F &dest, F32 maxSpeed)
{
//must always aim where you're flying to
mPilotDestination = dest;
mPilotAimLocation = dest;
mPilotSpeed = getMax(0.0f, getMin(1.0f, maxSpeed));
}
void AIConnection::setPilotAimLocation(const Point3F &aimLocation)
{
//if you're aiming, you can't move
mPilotAimLocation = aimLocation;
mPilotSpeed = 0;
}
void AIConnection::setWeaponInfo(const char *projectile, S32 minDist, S32 maxDist, S32 triggerCount, F32 energyRequired, F32 errorFactor)
{
//set the non-pointer params
mEngageMinDistance = minDist;
mEngageMaxDistance = maxDist;
mTriggerCounter = triggerCount;
mScriptTriggerCounter = -1;
mWeaponEnergy = energyRequired;
mWeaponErrorFactor = errorFactor;
if ((! projectile) || (! projectile[0]) || (! dStricmp(projectile, "NoAmmo")))
mProjectile = NULL;
else
{
if (! Sim::findObject(projectile, mProjectile))
{
Con::printf("setWeaponInfo() failed - unable to find datablock: %s", projectile);
mProjectile = NULL;
}
}
if (mProjectile)
mProjectileName = StringTable->insert(projectile);
else
mProjectileName = StringTable->insert("");
}
void AIConnection::setEnergyLevels(F32 eReserve, F32 eFloat)
{
mEnergyReserve = eReserve;
mEnergyFloat = eFloat;
}
void AIConnection::setEngageTarget(GameConnection *target)
{
//reset the bools if we're aiming at someone new...
//note, these extraneous retarded checks are because mTargetObject is a SimObjectPtr...
//won't compile just comparing target != mEngageTarget...
if (target != NULL && ((! bool(mEngageTarget)) || (target->getId() != mEngageTarget->getId())))
{
//reset some engagement vars
mTargetInRange = false;
mTargetInSight = false;
mEngageState = ChooseWeapon;
mFiring = false;
mTriggerCounter = 0;
mScriptTriggerCounter = -1;
}
mEngageTarget = target;
mTargetPlayer = NULL;
if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject()))
mEngageTarget = NULL;
else
mTargetPlayer = dynamic_cast<Player*>(mEngageTarget->getControlObject());
//make sure we actually got a target
if (! mTargetPlayer)
mEngageTarget = NULL;
}
S32 AIConnection::getEngageTarget()
{
//see if have someone to shoot at
Player *targetPlayer = NULL;
if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject()))
{
mTargetPlayer = NULL;
mEngageTarget = NULL;
return -1;
}
else
targetPlayer = dynamic_cast<Player*>(mEngageTarget->getControlObject());
//see if the target is dead
if (! targetPlayer || ! dStricmp(targetPlayer->getStateName(), "dead"))
{
mTargetPlayer = NULL;
mEngageTarget = NULL;
return -1;
}
//return the id of the target
return mEngageTarget->getId();
}
void AIConnection::setVictim(GameConnection *victim, Player *corpse)
{
mVictim = victim;
mCorpse = corpse;
mVictimTime = Sim::getCurrentTime();
}
S32 AIConnection::getVictimCorpse()
{
if (bool(mCorpse))
return mCorpse->getId();
else
return -1;
}
S32 AIConnection::getVictimTime()
{
return mVictimTime;
}
void AIConnection::setTargetObject(ShapeBase *targetObject, F32 range, S32 objectMode)
{
//reset the bools if we're not aiming at a player, and we have a new target object
//note, these extraneous retarded checks are because mTargetObject is a SimObjectPtr...
//won't compile just comparing targetObject != mTargetObject...
if (! mTargetPlayer && (! targetObject || ! bool(mTargetObject) || targetObject->getId() != mTargetObject->getId()))
{
mTargetInRange = false;
mTargetInSight = false;
mEngageState = ChooseWeapon;
mFiring = false;
mTriggerCounter = 0;
mScriptTriggerCounter = -1;
}
mTargetObject = targetObject;
mRangeToTarget = range;
mObjectMode = objectMode;
}
S32 AIConnection::getTargetObject()
{
if (bool(mTargetObject))
return mTargetObject->getId();
else
return -1;
}
void AIConnection::setPathDest(const Point3F * dest)
{
if( dest )
mPathDest = * dest;
mNewPath = true;
}
const Point3F * AIConnection::getPathDest()
{
if( mNewPath ){
mNewPath = false;
return & mPathDest;
}
return NULL;
}
// Pass down jetting abilities to the path machinery-
void AIConnection::setPathCapabilities(Player * )
{
PROFILE_START(AI_setPathCapabilities);
JetManager::Ability ability;
// Tribes player jetting was remove from the player class.
#if 0
player->getJetAbility(ability.acc, ability.dur, ability.v0);
#else
ability.acc = 0;
ability.dur = 0;
ability.v0 = 0;
#endif
mPath.setJetAbility(ability);
PROFILE_END();
}
F32 AIConnection::getPathDistance(const Point3F &destination, const Point3F &source)
{
//find our current location
Point3F sourceLocation = source;
if (sourceLocation == Point3F(-1, -1, -1))
{
//make sure we have a valid player control object
Player *myPlayer = NULL;
if (! getControlObject())
return -1;
myPlayer = dynamic_cast<Player*>(getControlObject());
if (! myPlayer)
return -1;
setPathCapabilities(myPlayer);
MatrixF const& tempTransform = myPlayer->getTransform();
tempTransform.getColumn(3, &sourceLocation);
//make sure the client can actually get to the destination
if (! mPath.canReachLoc(destination))
return -1;
}
//this is a weird change - want the bots to avoid vertical movement, so the distance function will
//exaggerate the z component...
Point3F distVec = sourceLocation - destination;
return mSqrt(distVec.x * distVec.x + distVec.y * distVec.y + 9 * distVec.z * distVec.z);
//this is just a euclidean distance anyways... if it becomes an actual path distance again, use it...
//return NavigationGraph::fastDistance(sourceLocation, destination);
}
F32 AIConnection::getPathDistRemaining(F32 maxDist)
{
//if the path is current, use the path distRemaining function
if (mPath.isPathCurrent())
return mPath.distRemaining(maxDist);
//otherwise, return the euclidean dist
else
return getMin(maxDist, (mLocation - mMoveDestination).len());
}
Point3F AIConnection::getLOSLocation(const Point3F &targetPoint, F32 minDistance, F32 maxDistance, const Point3F &nearPoint)
{
//find our current location
Point3F sourceLocation;
Player *myPlayer = NULL;
if (! getControlObject())
return targetPoint;
myPlayer = dynamic_cast<Player*>(getControlObject());
if (! myPlayer)
return targetPoint;
MatrixF const& tempTransform = myPlayer->getTransform();
tempTransform.getColumn(3, &sourceLocation);
Point3F nearLocation = nearPoint;
if (nearLocation == Point3F(-1, -1, -1))
nearLocation = sourceLocation;
Point3F graphPoint;
if (minDistance < 0)
graphPoint = NavigationGraph::findLOSLocation(sourceLocation, targetPoint, 0, SphereF(nearLocation, 1e6), maxDistance);
else
graphPoint = NavigationGraph::findLOSLocation(sourceLocation, targetPoint, minDistance, SphereF(nearLocation, minDistance / 2.0f), maxDistance);
return graphPoint;
}
Point3F AIConnection::getHideLocation(const Point3F &targetPoint, F32 range, const Point3F &nearPoint, F32 hideLength)
{
//find our current location
Point3F sourceLocation = nearPoint;
if (sourceLocation == Point3F(-1, -1, -1))
{
Player *myPlayer = NULL;
if (! getControlObject())
return targetPoint;
myPlayer = dynamic_cast<Player*>(getControlObject());
if (! myPlayer)
return targetPoint;
MatrixF const& tempTransform = myPlayer->getTransform();
tempTransform.getColumn(3, &sourceLocation);
}
if (hideLength <= 0)
return NavigationGraph::hideOnSlope(sourceLocation, targetPoint, range, 20);
else
return NavigationGraph::hideOnDistance(sourceLocation, targetPoint, range, hideLength);
}
void AIConnection::process(ShapeBase *ctrlObject)
{
if (!ctrlObject)
return;
PROFILE_START(AI_process);
//update the task queue
AITask *highestWeightTask = NULL;
S32 i;
for (i = 0; i < mTaskList.size(); i++)
{
AITask *task = mTaskList[i];
task->calcWeight(this);
//see if it's the highest so far
if ((! highestWeightTask) || (task->getWeight() > highestWeightTask->getWeight()))
highestWeightTask = task;
}
// Path needs team for avoiding threats-
mPath.setTeam(getSensorGroup());
//now see if we have a new task
if (highestWeightTask && mCurrentTask != highestWeightTask)
{
if (bool(mCurrentTask))
mCurrentTask->retire(this);
highestWeightTask->assume(this);
mCurrentTask = highestWeightTask;
mCurrentTaskTime = Sim::getCurrentTime();
}
//monitor the current task
if (mCurrentTask) {
PROFILE_START(AI_MonitorTask);
mCurrentTask->monitor(this);
PROFILE_END();
}
//see if our control object is a player
Player *myPlayer = NULL;
myPlayer = dynamic_cast<Player*>(ctrlObject);
if (myPlayer)
{
//initialize the process vars
initProcessVars(myPlayer);
//next, process the current step
if (bool(mStep))
mStep->process(this, myPlayer);
//process the engagement
if (mTurretMountedId <= 0) {
PROFILE_START(AI_EngagementOuter);
processEngagement(myPlayer);
PROFILE_END();
}
//finally, process the movement itself
if (!myPlayer->isMounted())
processMovement(myPlayer);
else
processVehicleMovement(myPlayer);
}
//else see if we're trying to pilot a vehicle
else
{
Vehicle *myVehicle;
myVehicle = dynamic_cast<Vehicle*>(ctrlObject);
if (myVehicle)
{
processPilotVehicle(myVehicle);
}
}
//debug
aiDebugStuff();
PROFILE_END();
}
// LH- changed this to not normalize when there's a zero length. Dividing by zero
// results in interupts. Instead do normalization here checking lengths first.
F32 AIConnection::get2DDot(const Point3F &vec1, const Point3F &vec2)
{
//create the 2D vectors and check for zero length.
Point3F vec1_2D(vec1.x, vec1.y, 0);
F32 len1 = vec1_2D.len();
if (len1 < __EQUAL_CONST_F)
return 1;
Point3F vec2_2D(vec2.x, vec2.y, 0);
F32 len2 = vec2_2D.len();
if (len2 < __EQUAL_CONST_F)
return 1;
// Normalize and return the dot-
return mDot(vec1_2D /= len1, vec2_2D /= len2);
}
F32 AIConnection::get2DAngle(const Point3F &endPt, const Point3F &basePt)
{
Point3F direction2D = endPt - basePt;
F32 angularDirection;
direction2D.z = 0;
if (! isZero(direction2D.y))
angularDirection = mAtan(direction2D.x, direction2D.y);
else if (direction2D.x < 0)
angularDirection = -M_PI / 2.0f;
else
angularDirection = M_PI / 2.0f;
if (angularDirection < 0)
angularDirection += M_2PI;
else if (angularDirection >= M_2PI)
angularDirection -= M_2PI;
//note: return value is always between 0 and (2 * Pi)
return angularDirection;
}
Point3F AIConnection::dopeAimLocation(const Point3F &startLocation, const Point3F &aimLocation)
{
//find the "horizontal" orthogonal vector
Point3F horzOrth;
if (! findCrossVector(startLocation - aimLocation, Point3F(0, 0, 1), &horzOrth))
return aimLocation;
//now find the "vertical" orthogonal vector
Point3F vertOrth;
if (! findCrossVector(startLocation - aimLocation, horzOrth, &vertOrth))
return aimLocation;
//now we have the horizonal and vertical components of the plane which is perpendicular to
//the direction we are firing...
//determine the radius factor
F32 radiusFactor;
if (mSkillLevel == 1.0f)
radiusFactor = 0.0f;
else
radiusFactor = 0.04 + (1.0f - mSkillLevel) * 0.2;
//calculate the radius error
F32 radiusError = (startLocation - aimLocation).len() * radiusFactor * mWeaponErrorFactor;
if (! mProjectile->isBallistic && mTargStillTimeMS > 0 && radiusError > 0)
{
//begin honing in after 3 seconds, and over the next 3 seconds
S32 elapsedTime = Sim::getCurrentTime() - mTargStillTimeMS - 3000;
if (elapsedTime > 0)
radiusError = radiusError * (F32(3000 - getMin(elapsedTime, 3000)) / 3000.0f);
}
F32 horzError = gRandGen.randF() * radiusError * (gRandGen.randF() < 0.5f ? -1.0f : 1.0f);
F32 vertError = gRandGen.randF() * radiusError * (gRandGen.randF() < 0.5f ? -1.0f : 1.0f);
Point3F dopedAimLocation = aimLocation + (horzOrth * horzError) + (vertOrth * vertError);
return dopedAimLocation;
}
F32 AIConnection::getOutdoorRadius(const Point3F &location)
{
F32 freedomRadius;
if (mPath.locationIsOutdoors(location, &freedomRadius))
return freedomRadius;
else
return -1;
}
void AIConnection::initProcessVars(Player *player)
{
PROFILE_START(AI_initProcessVars);
//find my current location (global coords), velocity, energy, damage, etc...
MatrixF const& tempTransform = player->getTransform();
tempTransform.getColumn(3, &mLocation);
mVelocity = player->getVelocity();
mVelocity2D = mVelocity;
mVelocity2D.z = 0;
mRotation = player->getRotation();
mHeadRotation = player->getHeadRotation();
mEnergy = player->getEnergyValue();
mDamage = player->getDamageValue();
if (mEnergy < mEnergyReserve)
mEnergyRecharge = true;
else if (mEnergy > mEnergyReserve + mEnergyFloat)
mEnergyRecharge = false;
if (((mEnergy > mEnergyReserve + mEnergyFloat) || (mEnergy > mEnergyReserve && !mEnergyRecharge)) &&
(mEnergy > mWeaponEnergy - mEnergyFloat))
mEnergyAvailable = true;
else
mEnergyAvailable = false;
//find the muzzle point
//player->getMuzzlePoint(0, &mMuzzlePosition);
player->getMuzzlePointAI(0, &mMuzzlePosition);
//find the eye transform
MatrixF eyeTransform;
player->getEyeTransform(&eyeTransform);
eyeTransform.getColumn(3, &mEyePosition);
//find out far we have to go, and if we're heading in the right direction
mDistToNode2D = (Point3F(mNodeLocation.x, mNodeLocation.y, 0) -
Point3F(mLocation.x, mLocation.y, 0)).len();
//find out how far off course our velocity is taking us
mDotOffCourse = get2DDot(mNodeLocation - mLocation, mVelocity2D);
mDotOffCourse = mClampF(mDotOffCourse, -1.0f, 1.0f);
//find out the difference between them
F32 dummy;
bool outdoors = mPath.locationIsOutdoors(mLocation, &dummy);
mHeadingDownhill = false;
if (mVelocity2D.len() >= player->getMaxForwardVelocity() * 0.85f && outdoors && !mInWater)
{
//make sure we're heading in approximately the right direction (+- 30 deg or so)
if (mDotOffCourse > 0.85)
{
Point3F myDirection2D = mVelocity2D;
if (myDirection2D.len() < 0.001f)
myDirection2D.set(0, 1, 0);
myDirection2D.normalize();
U32 mask = TerrainObjectType | InteriorObjectType | WaterObjectType;
RayInfo ray1Info, ray2Info;
Point3F startPt = mLocation;
Point3F endPt = mLocation;
endPt.z -= 5.0f;
//if we're not within 1.0 m of the ground, we can't ski...
player->disableCollision();
if (! gServerContainer.castRay(startPt, endPt, mask, &ray1Info))
mHeadingDownhill = true;
else
{
//run another LOS to see if we are heading downhill
startPt += myDirection2D;
endPt += myDirection2D;
//if we don't hit anything, we're (on the edge of a cliff?) heading downhill
if (! gServerContainer.castRay(startPt, endPt, mask, &ray2Info))
mHeadingDownhill = true;
else if (ray1Info.point.z > ray2Info.point.z)
mHeadingDownhill = true;
}
player->enableCollision();
}
}
//see if we have a target object
if (bool(mTargetObject))
{
MatrixF const& objTransform = mTargetObject->getTransform();
objTransform.getColumn(3, &mObjectLocation);
mDistToObject2D = (Point3F(mLocation.x, mLocation.y, 0.0f) -
Point3F(mObjectLocation.x, mObjectLocation.y, 0.0f)).len();
}
//see if have someone to shoot at
mTargetPlayer = NULL;
if ((! bool(mEngageTarget)) || (! mEngageTarget->getControlObject()))
mEngageTarget = NULL;
else
mTargetPlayer = dynamic_cast<Player*>(mEngageTarget->getControlObject());
if (mTargetPlayer)
{
//find the target location (global coords), velocity, energy, damage, etc...
MatrixF const& targTransform = mTargetPlayer->getTransform();
targTransform.getColumn(3, &mTargLocation);
mTargVelocity = mTargetPlayer->getVelocity();
mTargVelocity2D = mTargVelocity;
mTargVelocity2D.z = 0;
mTargRotation = mTargetPlayer->getRotation();
mTargEnergy = mTargetPlayer->getEnergyValue();
mTargDamage = mTargetPlayer->getDamageValue();
//see if the target is standing still
if (mTargVelocity.len() > 4.0f)
mTargStillTimeMS = 0;
else if (mTargStillTimeMS == 0)
mTargStillTimeMS = Sim::getCurrentTime();
//keep an array of prev locations to simulate a slower response time... (up to 600 ms)
if (Sim::getCurrentTime() - mTargPrevTimeMS > 300)
{
mTargPrevTimeMS = Sim::getCurrentTime();
mTargPrevLocation[3] = mTargPrevLocation[2];
mTargPrevLocation[2] = mTargPrevLocation[1];
mTargPrevLocation[1] = mTargPrevLocation[0];
mTargetPlayer->getWorldBox().getCenter(&mTargPrevLocation[0]);
}
}
else
mEngageTarget = NULL;
if (mTargetPlayer)
{
//see if the target is dead
if (! dStricmp(mTargetPlayer->getStateName(), "dead"))
{
mTargetPlayer = NULL;
mEngageTarget = NULL;
}
else
{
//find the 2D distance between you and the target
Point3F dist2DVector = mTargLocation - mLocation;
dist2DVector.z = 0;
mDistToTarg2D = dist2DVector.len();
}
}
//see if we're outdoors
mOutdoors = (getOutdoorRadius(mLocation) > 0);
//see if either we or the targ is in water
mInWater = false;
mTargInWater = false;
mObjectInWater = false;
SimpleQueryList sql;
gServerSceneGraph->getWaterObjectList(sql);
// gServerContainer.findObjects(WaterObjectType, SimpleQueryList::insertionCallback, S32(&sql));
for (U32 i = 0; i < sql.mList.size(); i++)
{
WaterBlock* pBlock = dynamic_cast<WaterBlock*>(sql.mList[i]);
if (pBlock)
{
if (pBlock->isPointSubmergedSimple(mLocation) || pBlock->isPointSubmergedSimple(mMuzzlePosition))
mInWater = true;
if (mTargetPlayer)
{
if (pBlock->isPointSubmergedSimple(mTargLocation))
mTargInWater = true;
}
if (bool(mTargetObject))
{
if (pBlock->isPointSubmergedSimple(mObjectLocation))
mObjectInWater = true;
}
}
}
//now see if we're within range of either the engageTarget, or the targetObject
if (--mCheckTargetLOSCounter <= 0)
{
mCheckTargetLOSCounter = 10;
mTargetInSight = false;
if (mTargetPlayer || bool(mTargetObject))
{
F32 rangeDist;
Point3F rangeLocation;
if (mTargetPlayer)
{
rangeDist = mDistToTarg2D;
mTargetPlayer->getWorldBox().getCenter(&rangeLocation);
}
else
{
rangeDist = mDistToObject2D;
mTargetObject->getWorldBox().getCenter(&rangeLocation);
}
if (rangeDist <= mRangeToTarget)
{
//see if we have line of site
RayInfo rayInfo;
Point3F startPt = mMuzzlePosition;
Point3F endPt = rangeLocation;
U32 mask = TerrainObjectType | InteriorObjectType;
if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo))
mTargetInSight = true;
}
}
}
//reset the weaponEnergy level if req'd
if (! bool(mTargetPlayer))
mWeaponEnergy = 0.0f;
//set the corpse vars
if (bool(mCorpse))
{
MatrixF const& corpseTransform = mCorpse->getTransform();
corpseTransform.getColumn(3, &mCorpseLocation);
}
PROFILE_END();
}
void AIConnection::updateDetectionTable(Player *player)
{
//if the player has been blinded, no updates to the table can be made
if (Sim::getCurrentTime() < mBlindedTimer)
return;
//time slice the detection LOS calls...
mPlayerDetectionCounter--;
if (mPlayerDetectionCounter <= 0)
{
SimGroup *clientGroup = Sim::getClientGroup();
AssertFatal(clientGroup, "Unable to get the client group");
//make sure we have more than one client
if (clientGroup->size() <= 1)
return;
//find the next client index
mPlayerDetectionIndex++;
if (mPlayerDetectionIndex >= clientGroup->size())
mPlayerDetectionIndex = 0;
//get the client from the group
GameConnection *targClient = static_cast<GameConnection*>((*clientGroup)[mPlayerDetectionIndex]);
//make sure it's not me...
S32 targClientId = targClient->getId();
if (getId() == targClientId)
{
mPlayerDetectionIndex++;
if (mPlayerDetectionIndex >= clientGroup->size())
mPlayerDetectionIndex = 0;
targClient = static_cast<GameConnection*>((*clientGroup)[mPlayerDetectionIndex]);
targClientId = targClient->getId();
}
//now find this target in the table
PlayerDetectionEntry *targEntry = NULL;
for (S32 i = 0; i < mPlayerDetectionTable.size(); i++)
{
if (mPlayerDetectionTable[i].playerId == targClientId)
targEntry = &(mPlayerDetectionTable[i]);
}
//if the entry wasn't found, create one... (and yes, this table will never shrink)
if (!targEntry)
{
PlayerDetectionEntry tempEntry;
tempEntry.playerId = targClientId;
tempEntry.playerLOS = false;
tempEntry.playerLOSTime = 0;
tempEntry.playerLastPosition.set(0, 0, 0);
mPlayerDetectionTable.push_front(tempEntry);
targEntry = &(mPlayerDetectionTable.first());
}
//make sure the client has a player
Player *targPlayer = NULL;
if (! targClient->getControlObject())
{
if (targEntry->playerLOS)
{
targEntry->playerLOS = false;
targEntry->playerLOSTime = Sim::getCurrentTime();
}
return;
}
targPlayer = dynamic_cast<Player*>(targClient->getControlObject());
if (! targPlayer)
{
if (targEntry->playerLOS)
{
targEntry->playerLOS = false;
targEntry->playerLOSTime = Sim::getCurrentTime();
}
return;
}
//now cast line of sight, see if we can see the player
MatrixF myEyeTransform, targEyeTransform;
Point3F myEyePosition, targEyePosition;
player->getEyeTransform(&myEyeTransform);
myEyeTransform.getColumn(3, &myEyePosition);
targPlayer->getEyeTransform(&targEyeTransform);
targEyeTransform.getColumn(3, &targEyePosition);
//if the target player is cloaked, they can't be detected
bool targPlayerIsCloaked = targPlayer->getCloakedState();
Point3F losVector = targEyePosition - myEyePosition;
F32 distToTarg = losVector.len();
bool clearLOSToTarg;
if (mSkillLevel >= 1.0f)
clearLOSToTarg = true;
else if (distToTarg < 0.5f)
clearLOSToTarg = true;
else if (targPlayerIsCloaked)
clearLOSToTarg = false;
else if (distToTarg > 300.0f)
clearLOSToTarg = false;
else
{
//first, see if we're facing the right way...
Point3F facingVector = mAimLocation - mLocation;
F32 visibleRange = 0.4 - (0.4 * mSkillLevel);
if (facingVector.len() < 0.5f)
clearLOSToTarg = false;
else if (get2DDot(facingVector, losVector) < visibleRange && distToTarg > 1.5f)
clearLOSToTarg = false;
else
{
//see if we have line of site
if (losVector.len() < 0.001f)
losVector.set(0, 1, 0);
losVector.normalize();
RayInfo rayInfo;
Point3F startPt = myEyePosition;
Point3F endPt = myEyePosition + (getMin(300.0f, distToTarg) * losVector);
U32 mask = TerrainObjectType | InteriorObjectType;
if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo))
clearLOSToTarg = true;
else
clearLOSToTarg = false;
}
}
//set the time if the status changed
if (clearLOSToTarg != targEntry->playerLOS)
targEntry->playerLOSTime = Sim::getCurrentTime();
//set the bool
targEntry->playerLOS = clearLOSToTarg;
//update the position if required
if (clearLOSToTarg)
targPlayer->getWorldBox().getCenter(&targEntry->playerLastPosition);
//don't forget to set the timeslice counter
mPlayerDetectionCounter = 30 / (clientGroup->size() - 1);
if (mPlayerDetectionCounter < 3)
mPlayerDetectionCounter = 3;
}
}
void AIConnection::setBlinded(S32 duration)
{
//can't blind the Kidney Bot!!!
if (mSkillLevel >= 1.0f)
return;
//first, set the blinded timer
mBlindedTimer = Sim::getCurrentTime() + duration;
//now loop through the table, and anyone who he has LOS to, he now doesn't
PlayerDetectionEntry *targEntry = NULL;
for (S32 i = 0; i < mPlayerDetectionTable.size(); i++)
{
targEntry = &(mPlayerDetectionTable[i]);
if (targEntry->playerLOS)
{
targEntry->playerLOS = false;
targEntry->playerLOSTime = Sim::getCurrentTime();
}
}
//next, set the evade location to make us react...
Point3F dangerLocation = mLocation;
dangerLocation.x += -2.0f + (gRandGen.randF() * 4.0f);
dangerLocation.y += -2.0f + (gRandGen.randF() * 4.0f);
setEvadeLocation(dangerLocation);
mEvadingCounter = 30;
}
void AIConnection::clientDetected(S32 targId)
{
//first, make sure we're not detecting ourself
if (getId() == targId)
return;
SimGroup *clientGroup = Sim::getClientGroup();
AssertFatal(clientGroup, "Unable to get the client group");
GameConnection *targClient = NULL;
for (SimGroup::iterator itr = clientGroup->begin(); itr != clientGroup->end(); itr++)
{
GameConnection *client = static_cast<GameConnection*>(*itr);
if (client->getId() == targId)
{
targClient = client;
break;
}
}
if (! targClient)
return;
//now find this target in the table
PlayerDetectionEntry *targEntry = NULL;
for (S32 i = 0; i < mPlayerDetectionTable.size(); i++)
{
if (mPlayerDetectionTable[i].playerId == targId)
targEntry = &(mPlayerDetectionTable[i]);
}
//if the entry wasn't found, create one... (and yes, this table will never shrink)
if (!targEntry)
{
PlayerDetectionEntry tempEntry;
tempEntry.playerId = targId;
tempEntry.playerLOS = false;
tempEntry.playerLOSTime = 0;
tempEntry.playerLastPosition.set(0, 0, 0);
mPlayerDetectionTable.push_front(tempEntry);
targEntry = &(mPlayerDetectionTable.first());
}
//make sure the client has a player
Player *targPlayer = NULL;
if (! targClient->getControlObject())
return;
targPlayer = dynamic_cast<Player*>(targClient->getControlObject());
if (! targPlayer)
return;
//set the time if the status changed
if (!targEntry->playerLOS)
targEntry->playerLOSTime = Sim::getCurrentTime();
//set the bool
targEntry->playerLOS = true;
//update the position
targPlayer->getWorldBox().getCenter(&targEntry->playerLastPosition);
}
bool AIConnection::hasLOSToClient(S32 clientId, S32 &losTime, Point3F &lastLocation)
{
//now find this target in the table
PlayerDetectionEntry *targEntry = NULL;
for (S32 i = 0; i < mPlayerDetectionTable.size(); i++)
{
if (mPlayerDetectionTable[i].playerId == clientId)
targEntry = &(mPlayerDetectionTable[i]);
}
if (! targEntry)
{
losTime = Sim::getCurrentTime();
lastLocation.set(0, 0, 0);
return false;
}
else
{
losTime = Sim::getCurrentTime() - targEntry->playerLOSTime;
lastLocation = targEntry->playerLastPosition;
return targEntry->playerLOS;
}
}
// LH- put script calls into separate methods so profiler can measure them.
// Then added slicing since it was weighing in on profiles.
void AIConnection::scriptProcessEngagement()
{
if (!gScriptEngageSlicer.ready(mPackCheckCounter, 6))
return;
char idStr[32], targetIdStr[32], targetTypeStr[32], projectileStr[64];
dSprintf(idStr, sizeof(idStr), "%d", getId());
if (mTargetPlayer)
{
dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "player");
dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mEngageTarget->getId());
}
else if (bool(mTargetObject) && mObjectMode == AIConnection::DestroyObject)
{
dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "object");
dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mTargetObject->getId());
}
else
{
dSprintf(targetTypeStr, sizeof(targetTypeStr), "%s", "none");
dSprintf(targetIdStr, sizeof(targetIdStr), "%d", -1);
}
if (bool(mEnemyProjectile) && mEvadingCounter > 0 && mEvadingCounter < 45)
dSprintf(projectileStr, sizeof(projectileStr), "%d", mEnemyProjectile->getId());
else
dSprintf(projectileStr, sizeof(projectileStr), "%d", -1);
Con::executef(5, "AIProcessEngagement", idStr, targetIdStr, targetTypeStr, projectileStr);
}
void AIConnection::scriptChooseEngageWeapon(F32 distToTarg)
{
char idStr[32], targetIdStr[32], distTotargStr[32];
dSprintf(idStr, sizeof(idStr), "%d", getId());
dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mEngageTarget->getId());
dSprintf(distTotargStr, sizeof(distTotargStr), "%f", distToTarg);
const char* canUseEnergyStr = mNavUsingJet ? "false" : "true";
const char* environmentStr = (mInWater || mTargInWater ? "water" : (mOutdoors ? "outdoors" : "indoors"));
Con::executef(6, "AIChooseEngageWeapon", idStr, targetIdStr, distTotargStr, canUseEnergyStr, environmentStr);
}
void AIConnection::scriptChooseObjectWeapon(F32 distToTarg)
{
char idStr[32], targetIdStr[32], distTotargStr[32];
dSprintf(idStr, sizeof(idStr), "%d", getId());
dSprintf(targetIdStr, sizeof(targetIdStr), "%d", mTargetObject->getId());
dSprintf(distTotargStr, sizeof(distTotargStr), "%f", distToTarg);
const char* canUseEnergyStr = mNavUsingJet ? "false" : "true";
const char* environmentStr = (mInWater || mTargInWater ? "water" : (mOutdoors ? "outdoors" : "indoors"));
Con::executef(7, "AIChooseObjectWeapon", idStr, targetIdStr, distTotargStr, gTargetObjectMode[mObjectMode], canUseEnergyStr, environmentStr);
}
void AIConnection::setEvadeLocation(const Point3F &dangerLocation, S32 durationTicks)
{
//find a new location orthogonal to the danger location to head to
Point3F dangerVector = dangerLocation - mLocation;
Point3F myVector = mMoveLocation - mLocation;
if (dangerVector.len() < 0.001f)
dangerVector.set(0, 1, 0);
dangerVector.normalize();
if (myVector.len() < 0.001f)
myVector.set(0, 1, 0);
myVector.normalize();
Point3F orthDanger;
mCross(dangerVector, Point3F(0, 0, 1), &orthDanger);
if (orthDanger.len() < 0.001f)
orthDanger.set(0, 1, 0);
orthDanger.normalize();
//if we have a NAN vector, the danger location is us!
if (orthDanger.x != orthDanger.x || orthDanger.y != orthDanger.y)
{
dangerVector = mTargLocation - mLocation;
if (dangerVector.len() < 0.001f)
dangerVector.set(0, 1, 0);
dangerVector.normalize();
mCross(dangerVector, Point3F(0, 0, 1), &orthDanger);
orthDanger.normalize();
//if we have *STILL* have a NAN vector, head north!
if (orthDanger.x != orthDanger.x || orthDanger.y != orthDanger.y)
orthDanger.set(0, 1, 0);
}
//choose the point closest to the direction we're already moving...
F32 myAngle = get2DAngle(mLocation + mVelocity2D, mLocation);
F32 tempAngle1 = get2DAngle(mLocation, mTargLocation + orthDanger);
F32 tempAngle2 = get2DAngle(mLocation, mTargLocation - orthDanger);
F32 angleDiff1, angleDiff2;
if (myAngle < tempAngle1)
angleDiff1 = getMin(tempAngle1 - myAngle, 256 + myAngle - tempAngle1);
else
angleDiff1 = getMin(myAngle - tempAngle1, 256 + tempAngle1 - myAngle);
if (myAngle < tempAngle2)
angleDiff2 = getMin(tempAngle2 - myAngle, 256 + myAngle - tempAngle2);
else
tempAngle2 = getMin(myAngle - tempAngle2, 256 + tempAngle2 - myAngle);
if (angleDiff1 < angleDiff2)
mEvadeLocation = mLocation + (orthDanger * 30.0f);
else
mEvadeLocation = mLocation - (orthDanger * 30.0f);
if (durationTicks > 0)
mEvadingCounter = durationTicks;
}
void AIConnection::processEngagement(Player *player)
{
//updatate the detection table
PROFILE_START(AI_updateDetectionTable);
updateDetectionTable(player);
PROFILE_END();
//call the script function once each frame to handle packs, grenades, health kits, etc...
scriptProcessEngagement();
//see if we need to reset the script trigger counter (if we changed weapons...)
ShapeBaseImageData* tempImage = player->getMountedImage(0);
if (tempImage != mMountedImage)
mScriptTriggerCounter = -1;
mMountedImage = tempImage;
//if the script wants us to fire (using the repair pack to self repair mainly)...
if (mScriptTriggerCounter-- >= 0)
pressFire(true);
//make sure we have someone to shoot at
if (! mTargetPlayer && ! bool(mTargetObject))
return;
//if we have something or someone to shoot at, let the engage code determine whether to fire
pressFire(false);
//reset the counter
mLookAtTargetTimeMS = Sim::getCurrentTime() + 1500;
//see if we're shooting a player, or a static object
bool engagingPlayer;
Point3F targetLocation;
F32 distToTarg;
if (mTargetPlayer)
{
engagingPlayer = true;
//if the player has been blinded, use the last known location only
if (Sim::getCurrentTime() < mBlindedTimer)
{
S32 dummyTime;
hasLOSToClient(mEngageTarget->getId(), dummyTime, targetLocation);
}
else
{
//see if we have LOS to the client
S32 losTime;
Point3F losLocation;
bool hasLOS = hasLOSToClient(mEngageTarget->getId(), losTime, losLocation);
//if we have just lost LOS to the target, assume he went around a corner, and fire at the last known location
if (!hasLOS && losTime < 2500)
targetLocation = losLocation;
//this simulates slower response - the bot doesn't quite keep up to the target's current location
else if (mSkillLevel >= 0.9f)
targetLocation = mTargPrevLocation[0];
else if (mSkillLevel >= 0.7f)
targetLocation = mTargPrevLocation[1];
else if (mSkillLevel >= 0.3f)
targetLocation = mTargPrevLocation[2];
else
targetLocation = mTargPrevLocation[3];
}
//now calculate the 2D distance
distToTarg = (Point3F(targetLocation.x, targetLocation.y, 0) - Point3F(mLocation.x, mLocation.y, 0)).len();
}
else
{
engagingPlayer = false;
//if a repair node has been added to the object, use that, otherwise, use the worldBoxCenter
targetLocation = mTargetObject->getAIRepairPoint();
if (targetLocation == Point3F(0, 0, 0))
mTargetObject->getWorldBox().getCenter(&targetLocation);
distToTarg = mDistToObject2D;
}
//detect the projectiles from the target
mProjectileCounter--;
if (engagingPlayer)
{
const char *incoming = mEngageTarget->getDataField(StringTable->insert("projectile"), NULL);
if (incoming && incoming[0])
{
Projectile *projectile;
if (Sim::findObject(incoming, projectile))
{
//see if it's a new threat, or time to re-evaluate the current one
if (projectile != (Projectile*)mEnemyProjectile || mProjectileCounter <= 0)
{
//reset the projectile counter
mProjectileCounter = 15;
F32 timeToImpact;
ProjectileData* projData = projectile->getDataBlock() != NULL ?
dynamic_cast<ProjectileData*>(projectile->getDataBlock()) :
NULL;
if (projData && projectile->calculateImpact(4.0f, mImpactLocation, timeToImpact)) {
//see if the impact location is within range
Point3F predictMyLocation = mLocation + mVelocity * timeToImpact;
F32 distToDanger = (predictMyLocation - mImpactLocation).len();
if (distToDanger < getMax(projData->damageRadius, 2.0f))
{
setEvadeLocation(mImpactLocation);
mEnemyProjectile = projectile;
//set the evade counter - any value above 45 is simulated response time
S32 responseTime = S32(30 * (1.0f - mSkillLevel));
mEvadingCounter = 45 + responseTime;
}
}
}
}
}
}
//if we're in the middle of a NavGraph Jet, we can't shoot...
Point3F dummyPoint;
if (mNavUsingJet && mJetting.shouldAimAt(dummyPoint))
return;
//here we only fire at someone if we've seen them recently enough
if (bool(mEngageTarget))
{
S32 detectLOSTime;
Point3F lastLOSLocation;
bool hasLOSDetect = hasLOSToClient(mEngageTarget->getId(), detectLOSTime, lastLOSLocation);
if (!hasLOSDetect && (detectLOSTime > mDetectHiddenPeriod))
return;
}
//process the engagement state machine
switch (mEngageState)
{
case ChooseWeapon:
{
//set the aim location
if (! mTargetInRange)
setAimLocation(targetLocation);
else
setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z));
//set the bool
mFiring = false;
//limit changing the weapon too often for low skill
if (--mChangeWeaponCounter > 0)
{
mEngageState = ReloadWeapon;
mStateCounter = 45;
//set the delay counter
mDelayCounter = S32(20.0f * (1.0f - mSkillLevel));
}
//else reset the counter
else
mChangeWeaponCounter = S32((1.0f - mSkillLevel) / 0.10f);
//keep track of the previous weapon
StringTableEntry prevWeapon = mProjectileName;
//call the script function to set the weapon vars
if (engagingPlayer)
scriptChooseEngageWeapon(distToTarg);
else
scriptChooseObjectWeapon(distToTarg);
if (distToTarg > mEngageMaxDistance)
{
mEngageState = OutOfRange;
mStateCounter = 30;
}
else
{
mEngageState = ReloadWeapon;
mStateCounter = 60;
//set the delay counter
mDelayCounter = S32(20.0f * (1.0f - mSkillLevel));
//see if we've switched weapons
if (dStricmp(mProjectileName, prevWeapon))
mDelayCounter *= 3;
}
break;
}
case OutOfRange:
{
//set the aim location
if (! mTargetInRange)
setAimLocation(targetLocation);
else
setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z));
if (distToTarg <= mEngageMaxDistance || (--mStateCounter <= 0))
mEngageState = ChooseWeapon;
break;
}
case ReloadWeapon:
{
//set the aim location
if (! mTargetInRange)
setAimLocation(targetLocation);
else
setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z));
//make sure we're not stuck in this state
if (--mStateCounter <= 0)
{
mEngageState = ChooseWeapon;
break;
}
//make sure we're not trying to use an energy weapon while jetting
if (mWeaponEnergy > 0 && mNavUsingJet)
{
mEngageState = ChooseWeapon;
break;
}
//if the weapon is ready, time to aim
bool weaponReady = player->isImageReady(0);
bool hasEnergy = (mEnergy >= mWeaponEnergy);
//add in factor so they're not unloading on you at lower skill settings...
F32 skillVelocityFactor = 6.0f + (mSkillLevel * mSkillLevel * mSkillLevel * 300.0f);
bool mustSlowDown = (mVelocity.len() > skillVelocityFactor);
if (weaponReady && hasEnergy && !mustSlowDown)
{
mStateCounter = 15;
if (--mDelayCounter <= 0)
mEngageState = FindTargetPoint;
}
//if firing is sustained, keep firing
if (mFiring && mTriggerCounter > 0)
{
mTriggerCounter--;
pressFire();
}
break;
}
case FindTargetPoint:
{
//set the aim location
if (! mTargetInRange)
setAimLocation(targetLocation);
else
setAimLocation(Point3F(targetLocation.x, targetLocation.y, mAimLocation.z));
//make sure we're not stuck in this state
if (--mStateCounter <= 0)
{
mEngageState = ChooseWeapon;
break;
}
//make sure we're not trying to use an energy weapon while jetting
if (mWeaponEnergy > 0 && mNavUsingJet)
{
mEngageState = ChooseWeapon;
break;
}
//should we go for center mass, or splash damage, or are we shooting at a lazed target
Point3F aimAtTargetPoint;
mAimAtLazedTarget = false;
//only shoot at lazed targets if we're using a ballistic weapon
if (mProjectile && mProjectile->isBallistic)
{
//see if we have a lazed target point...
SimSet *targetSet = Sim::getServerTargetSet();
if (targetSet)
{
SimSet::iterator i;
for (i = targetSet->begin(); i != targetSet->end(); i++)
{
GameBase *target = static_cast<GameBase*>(*i);
Point3F targetPoint;
U32 dummyTeam; //when the sensor network is finished, then the target's
//visibility can be properly determined.
target->getTarget(&targetPoint, &dummyTeam);
//see if the target is within 20m of what we're trying to shoot at...
if ((targetPoint - targetLocation).len() < 10.0f)
{
aimAtTargetPoint = targetPoint;
mAimAtLazedTarget = true;
}
}
}
}
if (! mAimAtLazedTarget)
{
//see if we should aim for the target's feet
if (engagingPlayer && mProjectile && mProjectile->hasDamageRadius && mProjectile->damageRadius > 5.0f)
aimAtTargetPoint = mTargLocation;
//else aim for the target's center mass
else
aimAtTargetPoint = targetLocation;
}
//find the aim vector
Point3F aimVectorMin, aimVectorMax;
F32 timeMin, timeMax;
if (mProjectile)
{
bool canShoot;
if (engagingPlayer)
canShoot = mProjectile->calculateAim(aimAtTargetPoint, mTargVelocity, mMuzzlePosition, mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax);
else
{
canShoot = false;
if (mObjectMode != MissileVehicle || player->getLockedTargetId() == mTargetObject->getId())
canShoot = mProjectile->calculateAim(aimAtTargetPoint, Point3F(0, 0, 0), mMuzzlePosition, mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax);
}
if (canShoot)
{
//find the aimlocation from the normalized aim vector
Point3F aimLocation = mMuzzlePosition + aimVectorMin * (aimAtTargetPoint - mMuzzlePosition).len();
//now add the tone down the accuracy for moving targets
bool needToDope = mVelocity.len() > 4.0f || engagingPlayer || (mProjectile->isBallistic && ! mAimAtLazedTarget);
if (needToDope)
aimLocation = dopeAimLocation(mMuzzlePosition, aimLocation);
//set the aim location
setAimLocation(aimLocation);
mEngageState = AimAtTarget;
mStateCounter = 15;
//call processEngagement again immediately
PROFILE_START(AI_EngagementInner);
processEngagement(player);
PROFILE_END();
}
else
{
//reset the bool
mTargetInRange = false;
//if firing is sustained, keep firing
if (mFiring && mTriggerCounter > 0)
{
mTriggerCounter--;
pressFire();
}
}
}
//if no projectile, we need to find one...
else
{
mEngageState = ChooseWeapon;
mTargetInRange = false;
}
break;
}
case AimAtTarget:
{
//make sure we're not trying to use an energy weapon while jetting
if (mWeaponEnergy > 0 && mNavUsingJet)
{
mEngageState = ChooseWeapon;
break;
}
//the process order is: fire, then move + aim, we need to aim as if we're
//shooting next frame
if (engagingPlayer)
{
Point3F nextFrameAimPoint = mAimLocation - mVelocity / 30.0f;
setAimLocation(nextFrameAimPoint);
}
//see if we have line of site
bool clearLOSToTarget = false;
RayInfo rayInfo;
Point3F startPt = mMuzzlePosition + mVelocity / 30.0f; //as if it's next frame already
Point3F endPt = mAimLocation;
U32 mask = TerrainObjectType | InteriorObjectType | PlayerObjectType | ForceFieldObjectType;
//if the player is mounted on a vehicle, mask in vehicle types as well...
if (player->isMounted())
mask |= VehicleObjectType;
player->disableCollision();
if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo))
clearLOSToTarget = true;
else if (engagingPlayer && bool(rayInfo.object) && bool(mTargetPlayer))
{
if (rayInfo.object->getId() == mTargetPlayer->getId())
clearLOSToTarget = true;
}
else if (!engagingPlayer && bool(rayInfo.object) && bool(mTargetObject))
{
if (rayInfo.object->getId() == mTargetObject->getId())
clearLOSToTarget = true;
}
player->enableCollision();
bool readyToFire = false;
//if we have a clear LOS, or if we're firing a ballistic weapon and have enough skill to shoot from behind hills
if (clearLOSToTarget || (mProjectile->isBallistic && mSkillLevel >= 0.7f))
readyToFire = true;
//else if we're firing a linear weapon with splash damage
else if (! clearLOSToTarget && ! mProjectile->isBallistic)
{
//see if the impact point is within the damage radius of the projectile
if (mProjectile->hasDamageRadius && (rayInfo.point - mAimLocation).len() < getMax(10.0f, mProjectile->damageRadius))
{
//need to see if the impact point has LOS to the target, otherwise, bot will try to shoot through walls...
//readyToFire = true;
readyToFire = false;
}
}
if (readyToFire)
{
mTargetInRange = true;
mEngageState = FireWeapon;
}
else
{
mTargetInRange = false;
mEngageState = FindTargetPoint;
}
//if firing is sustained, keep firing
if (mFiring && mTriggerCounter > 0)
{
mTriggerCounter--;
pressFire();
}
break;
}
case FireWeapon:
{
//make sure we're not trying to use an energy weapon while jetting
if (mWeaponEnergy > 0 && mNavUsingJet)
{
mEngageState = ChooseWeapon;
break;
}
//if we're firing, we must be in range
mTargetInRange = true;
//press fire and move to reload
pressFire();
mFiring = true;
mTriggerCounter--;
//see if firing is to be sustained (chaingun, elf gun, etc...)
if (mTriggerCounter > 0)
{
mEngageState = FindTargetPoint;
mStateCounter = 15;
}
else
{
mEngageState = ChooseWeapon;
mStateCounter = 60;
}
break;
}
}
}
F32 AIConnection::angleDifference(F32 angle1, F32 angle2)
{
F32 tempAngle1 = getMin(angle1, angle2);
F32 tempAngle2 = getMax(angle1, angle2);
F32 difference = getMin(F32(tempAngle2 - tempAngle1),
F32(tempAngle1 + M_2PI - tempAngle2));
return difference;
}
Point3F AIConnection::correctHeading()
{
Point3F newLocation = mNodeLocation;
//make sure we're heading in approximately the right direction (+- 30 deg or so)
//no need to overcorrect if we're not moving very fast
if (mVelocity2D.len() > 4)
{
//if we're heading more than 90 deg in the wrong direction, or we're heading *way* too fast
//to hit the node, choose the exact opposite direction of the velocity
if (mDotOffCourse <= 0)
{
//find out our current heading (sub 90 deg since our axis is rotated)
F32 heading = mAtan(mVelocity2D.x, mVelocity2D.y) - (M_PI / 2.0f);
F32 newHeading = heading + M_PI;
newLocation.x = mLocation.x + mDistToNode2D * mCos(newHeading);
newLocation.y = mLocation.y - mDistToNode2D * mSin(newHeading);
}
//else mirror the angle through the desired heading
else
{
//find out our current heading (sub 90 deg since our axis is rotated)
F32 heading = mAtan(mVelocity2D.x, mVelocity2D.y) - (M_PI / 2.0f);
//find out what angle we're currently off by
F32 headingDiff = mAcos(mDotOffCourse);
//determine the "overcompensate" factor
F32 overCompFactor;
if (mDotOffCourse > 0.85)
overCompFactor = 3.0f;
else
overCompFactor = 2.0f;
//now see find out which direction
F32 angle1 = heading + headingDiff;
F32 angle2 = heading - headingDiff;
Point3F newVec1(mCos(angle1), -mSin(angle1), 0);
Point3F newVec2(mCos(angle2), -mSin(angle2), 0);
//the dot with one of these angles should have a dot near 1 -
//create the "correcting" angle to overcompensate the headingDiff
F32 newAngle;
F32 angle1Dot = get2DDot(mNodeLocation - mLocation, newVec1);
F32 angle2Dot = get2DDot(mNodeLocation - mLocation, newVec2);
if (angle1Dot > angle2Dot)
newAngle = heading + overCompFactor * headingDiff;
else
newAngle = heading - overCompFactor * headingDiff;
//find the new point
newLocation.x = mLocation.x + mDistToNode2D * mCos(newAngle);
newLocation.y = mLocation.y - mDistToNode2D * mSin(newAngle);
}
}
return newLocation;
}
Point3F AIConnection::avoidPlayers(Player *player, const Point3F &desiredDestination, bool destIsFinal)
{
F32 avoidObjectAngle = -1;
//see if we're near another player
Box3F queryBox;
queryBox.min = mLocation;
queryBox.max = mLocation;
queryBox.min -= Point3F(2.0f, 2.0f, 0.5f);
queryBox.max += Point3F(2.0f, 2.0f, 2.5f);
ShapeBase *closestObject = NULL;
F32 closestDist = 32767;
Point3F closestLocation;
SimpleQueryList result;
//U32 mask = PlayerObjectType;
U32 mask = ShapeBaseObjectType | StaticTSObjectType;
gServerContainer.findObjects(queryBox, mask, SimpleQueryList::insertionCallback, S32(&result));
if (result.mList.size() > 1)
{
//find out if the closest person is to the left or right...
for (S32 i = 0; i < result.mList.size(); i++)
{
ShapeBase *neighborObject = static_cast<ShapeBase*>(result.mList[i]);
//can't bump into yourself
if (neighborObject->getId() == player->getId())
continue;
//if it's not a static TS object, see if it's a shape base with the aiAvoidThis flag set...
if (!(neighborObject->getType() & StaticTSObjectType))
{
ShapeBaseData *db = static_cast<ShapeBaseData*>(neighborObject->getDataBlock());
if (!db || ! db->aiAvoidThis)
continue;
}
//make sure it's not a corpse!
if (neighborObject->getType() & CorpseObjectType)
continue;
//now we see if it's actually in our way, or if it's just the bounding box...
Point3F tempVector = neighborObject->getBoxCenter() - player->getBoxCenter();
if (tempVector.len() > 1.0f)
{
tempVector.normalize();
player->disableCollision();
RayInfo rayInfo;
Point3F startPt = player->getBoxCenter();
Point3F endPt = startPt + (tempVector * 2.0f);
bool losResult = gServerContainer.castRay(startPt, endPt, neighborObject->getType(), &rayInfo);
player->enableCollision();
//we disregard this object if we didn't intersect with it
if (!losResult || (rayInfo.object->getId() != neighborObject->getId()))
continue;
}
MatrixF const& tempTransform = neighborObject->getTransform();
Point3F tempLocation;
tempTransform.getColumn(3, &tempLocation);
F32 tempDist = (tempLocation - mLocation).len();
if (tempDist < closestDist)
{
closestDist = tempDist;
closestObject = neighborObject;
closestLocation = tempLocation;
}
}
}
//if we didn't find anyone, return the desired dest
if (! closestObject)
{
mAvoidingObject = NULL;
return desiredDestination;
}
//otherwise, we've got a neighbor...
else
{
//update the detection table for this bot
Player *closestPlayer = dynamic_cast<Player*>(closestObject);
if (bool(closestPlayer))
{
GameConnection *closestClient = closestPlayer->getControllingClient();
if (closestClient)
clientDetected(closestClient->getId());
}
//find out how close we are to our destination
Point3F newLocation;
F32 newHeading;
F32 distToDest2D = (Point3F(desiredDestination.x, desiredDestination.y, 0.0f) -
Point3F(mLocation.x, mLocation.y, 0.0f)).len();
//create and normalize the vectors
Point3F directionVector = desiredDestination - mLocation;
Point3F neighborVector = closestLocation - mLocation;
directionVector.z = 0;
//normalize the vector
if (directionVector.len() < 0.001f)
directionVector.set(0, 1, 0);
directionVector.normalize();
//make sure we didn't get any -NAN values...
if (directionVector.x != directionVector.x || directionVector.y != directionVector.y)
directionVector.set(0, 1, 0);
neighborVector.z = 0;
//normalize the vector
if (neighborVector.len() < 0.001f)
neighborVector.set(0, 1, 0);
neighborVector.normalize();
//make sure we didn't get any -NAN values...
if (neighborVector.x != neighborVector.x || neighborVector.y != neighborVector.y)
neighborVector.set(0, 1, 0);
//dot the direction vector with the neighbor vector - if directly in our path, move at 90 deg...
F32 neighborDot = get2DDot(neighborVector, directionVector);
//if the neighbor is behind us, not at all in our way, and we're still moving
if (! destIsFinal && neighborDot < 0.5)
{
mAvoidingObject = NULL;
return desiredDestination;
}
//we've stopped - move at 45 away from target
else if (destIsFinal)
{
F32 neighborHeading = mAtan(neighborVector.x, neighborVector.y) - (M_PI / 2.0f);
F32 angle1 = neighborHeading + ((M_PI / 2.0f) + (M_PI / 4.0f));
F32 angle2 = neighborHeading - ((M_PI / 2.0f) + (M_PI / 4.0f));
Point3F newVec1(mCos(angle1), -mSin(angle1), 0);
Point3F newVec2(mCos(angle2), -mSin(angle2), 0);
//find out which newVec will take us furthest away from the neighbor (smallest dot product)
F32 angle1Dot = get2DDot(neighborVector, newVec1);
F32 angle2Dot = get2DDot(neighborVector, newVec2);
if (angle1Dot < angle2Dot)
newHeading = angle1;
else
newHeading = angle2;
//now that we've chosen the new heading, calculate the new destination location
newLocation.x = mLocation.x + getMax(6.0f, distToDest2D) * mCos(newHeading);
newLocation.y = mLocation.y - getMax(6.0f, distToDest2D) * mSin(newHeading);
newLocation.z = desiredDestination.z;
//note that since we're already at our final destination, all we need to do is
//get bumped out of the way, so no need to save the avoidance vars...
mAvoidingObject = NULL;
}
else if ((GameBase*)mAvoidingObject != closestObject || desiredDestination != mAvoidDestinationPoint)
{
F32 neighborHeading = mAtan(neighborVector.x, neighborVector.y) - (M_PI / 2.0f);
F32 angle1 = neighborHeading + (M_PI / 2.0f);
F32 angle2 = neighborHeading - (M_PI / 2.0f);
Point3F newVec1(mCos(angle1), -mSin(angle1), 0);
Point3F newVec2(mCos(angle2), -mSin(angle2), 0);
F32 angle1Dot = get2DDot(directionVector, newVec1);
F32 angle2Dot = get2DDot(directionVector, newVec2);
if (angle1Dot > angle2Dot)
newHeading = angle1;
else
newHeading = angle2;
//now that we've chosen the new heading, calculate the new destination location
newLocation.x = mLocation.x + getMax(6.0f, distToDest2D) * mCos(newHeading);
newLocation.y = mLocation.y - getMax(6.0f, distToDest2D) * mSin(newHeading);
newLocation.z = desiredDestination.z;
//now save off the avoidance vars
mAvoidingObject = closestObject;
mAvoidSourcePoint = mLocation;
mAvoidDestinationPoint = desiredDestination;
mAvoidMovePoint = newLocation;
mAvoidForcedPath = false;
}
//otherwise we hit this object the last time we were trying to move to our dest
else
{
if ((mAvoidMovePoint - mLocation).len() < 1.5f)
{
if (! mAvoidForcedPath)
{
mPath.forceSearch();
mAvoidForcedPath = true;
}
newLocation = desiredDestination;
}
else
newLocation = mAvoidMovePoint;
}
return newLocation;
}
}
void AIConnection::processVehicleMovement(Player *player)
{
//first, kill the nav jetting state machine...
if (mNavUsingJet)
{
mNavUsingJet = false;
mJetting.reset();
mPath.forceSearch();
}
//now set the aim location if the bot is a passenger.
//note - if the passenger is in a fixed mounted position, this will be ignored anyways...
if (player->isMounted() && bool(player->getObjectMount()))
{
Point3F vec;
MatrixF vehicleMat;
player->getObjectMount()->getMountTransform(player->getMountNode(), &vehicleMat);
vehicleMat.getColumn(1,&vec);
F32 vehicleRot = mAtan(vec.x,vec.y) - (M_PI / 2.0f);
//choose a point so the bot will face the same direction as the vehicle...
Point3F aimVector(mCos(vehicleRot), -mSin(vehicleRot), 0);
Point3F aimLocation = mLocation + 30.0f * aimVector;
aimLocation.z += 2.0f;
setScriptAimLocation(aimLocation, 1000);
}
//finally, the script callback (mainly to handle vehicle firing, etc...)
char idStr[32];
dSprintf(idStr, sizeof(idStr), "%d", getId());
Con::executef(2, "AIProcessVehicle", idStr);
}
void AIConnection::processPilotVehicle(Vehicle *myVehicle)
{
//eventually, I may move some of the pilot code from ::getMoveList() to here...
myVehicle;
//kill the nav jetting state machine...
if (mNavUsingJet)
{
mNavUsingJet = false;
mJetting.reset();
mPath.forceSearch();
}
//finally, the script callback (mainly to handle vehicle firing, etc...)
char idStr[32];
dSprintf(idStr, sizeof(idStr), "%d", getId());
Con::executef(2, "AIPilotVehicle", idStr);
}
void AIConnection::processMovement(Player *player)
{
player->updateWorkingCollisionSet();
// Inform path machinery about our jetting ability.
setPathCapabilities(player);
//if we're avoiding danger, override the current destination and move mode...
//note, if mEvadingCounter > 45, we use this to slow down reaction time...
mEvadingCounter--;
if (mEvadingCounter > 0 && mEvadingCounter < 45)
{
//make sure we don't get stuckin in the nav jet state machine
mJetting.reset();
mNavUsingJet = false;
mIsEvading = true;
setMoveLocation(Point3F(mEvadeLocation.x, mEvadeLocation.y, 0));
setMoveSpeed(1.0f);
//jump if we can - should encourage skiing
if (player->canJump())
pressJump();
else
pressJet();
}
//regular navigation...
else
{
// For debugging movement code of single bot (lh)-
S32 focusOnBot = Con::getIntVariable("$AIFocusOnBot");
if (focusOnBot)
{
if (focusOnBot != getId())
return;
else
{
F32 E = mPath.jetWillNeedEnergy(12.0);
if (E > 0.0)
Con::printf("%d wants %f energy", focusOnBot, E);
}
}
//reset the nav jet stuff if we've just finished evading
if (mIsEvading)
{
mIsEvading = false;
mNavUsingJet = false;
mPath.forceSearch();
}
mPath.setDestMounted(false);
if (!mNavUsingJet)
mMoveMode = mMoveModePending;
switch (mMoveMode)
{
case ModeStop:
{
//update the path anyways - mainly to correct aiming probs...
mPath.updateLocations(mLocation, mMoveDestination);
mNodeLocation = mPath.getSeekLoc(mVelocity);
mNavUsingJet = false;
//even stopped, we need to move out of the way of teammates
Point3F moveLocation = avoidPlayers(player, mLocation, true);
if (moveLocation != mLocation)
{
setMoveSpeed(0.6f);
setMoveLocation(moveLocation);
}
else
{
setMoveSpeed(0.0f);
}
break;
}
case ModeGainHeight:
{
mNavUsingJet = false;
//jump if we can - should encourage skiing
if (player->canJump())
pressJump();
else
pressJet();
setMoveLocation(mLocation);
break;
}
case ModeMountVehicle:
//set the path var
mPath.setDestMounted(true);
//if we're already mounted, no need to keep moving...
if (player->isMounted())
{
setMoveSpeed(0.0f);
break;
}
//fall through to mode express...
case ModeWalk:
case ModeExpress:
{
//see if we have a corpse to walk to first
mPath.updateLocations(mLocation, mMoveDestination);
mNodeLocation = mPath.getSeekLoc(mVelocity);
//see if we have a brand new node location...
if (mNodeLocation != mPrevNodeLocation)
{
mPrevNodeLocation = mNodeLocation;
mInitialLocation = mLocation;
}
//else see if we've overshot the node and have to recalc the path
else
{
if (!mNavUsingJet)
{
Point3F vec1 = mInitialLocation - mNodeLocation;
Point3F vec2 = mLocation - mNodeLocation;
// Moved 2d check here - problems picking up packs with 3d dist check.
// Also, vectors don't need to be normalized if dot compare is with 0.
vec2.z = vec1.z = 0;
if (vec1.lenSquared() > 1.0f && vec2.lenSquared() > 1.0f)
if (mDot(vec1, vec2) < 0.0f)
{
mPrevNodeLocation.set(0, 0, 0);
mPath.forceSearch();
}
}
}
if (mPath.userMustJet() && !mInWater)
{
mNavUsingJet = true;
if (mJetting.status() == AIJetWorking)
{
mJetting.process(this, player);
if (mJetting.status() != AIJetWorking)
{
if (mJetting.status() == AIJetSuccess)
mPath.informJetDone();
else
{
if (mMoveMode == ModeMountVehicle)
pressJump();
mPath.forceSearch();
}
mJetting.reset();
}
else if (mJetting.badTimeToSearch())
mPath.informJetBusy();
}
else
mJetting.init(mNodeLocation, mPath.intoMount(), mPath.getJetInfo());
}
else
{
if (mNavUsingJet)
{
//force a path search after jetting is done...
mPath.forceSearch();
mNavUsingJet = false;
}
//jet if we're falling too quickly
if (mVelocity.z < -20.0f)
pressJet();
//see if we're getting near the end of the path
//F32 distToEnd = getMax(mPath.distRemaining(2 * mMoveTolerance), (mLocation - mMoveDestination).len());
bool pathIsCurrent = mPath.isPathCurrent();
F32 distToEnd = (pathIsCurrent ? mPath.distRemaining(2 * mMoveTolerance) : (2 * mMoveTolerance));
F32 tolerance = distToEnd > mMoveTolerance ? 0.25f : mMoveTolerance;
F32 jetEnergy = mPath.jetWillNeedEnergy(30);
//find out where we're really heading...
Point3F moveLocation = mNodeLocation;
if (mDistToNode2D > 0.25f)
moveLocation = correctHeading();
//calculate the distance to where we're moving
F32 distToMove2D = (Point3F(mLocation.x, mLocation.y, 0) -
Point3F(moveLocation.x, moveLocation.y, 0)).len();
F32 distToEnd2D = (Point3F(mLocation.x, mLocation.y, 0) -
Point3F(mMoveDestination.x, mMoveDestination.y, 0)).len();
distToEnd = getMax(distToEnd, distToEnd2D);
//now adjust the heading if we're bumping into other players
if (mMoveMode != ModeMountVehicle || distToEnd > 3.0f)
moveLocation = avoidPlayers(player, moveLocation, false);
//now move in the calculated direction
setMoveLocation(moveLocation);
//see if we're stuck...
if (distToEnd > tolerance && distToEnd > 1.0f)
{
Point3F checkStuck2D = mLocation - mStuckLocation;
checkStuck2D.z = 0.0f;
if ((mNodeLocation - mStuckDestination).len() > 1.0f || (checkStuck2D).len() > 1.0f)
{
mStuckLocation = mLocation;
mStuckDestination = mNodeLocation;
mStuckTimer = Sim::getCurrentTime();
}
else if (Sim::getCurrentTime() - mStuckTimer > 2000)
{
setMoveMode(ModeStuck);
mStuckInitialized = false;
mStuckTryJump = true;
mStuckJumpInitialized = false;
return;
}
else
{
// Know we want to be moving- inform Path of how well we're doing-
mPath.informProgress(mVelocity);
}
}
//look ahead in the path, and see if the next point is collinear with our current destination
Point3F nextNodeLocation;
bool isCollinearPath = false;
if (mPath.getPathNodeLoc(1, nextNodeLocation))
{
F32 tempDot = get2DDot(moveLocation - mLocation, nextNodeLocation - moveLocation);
if (tempDot > 0.9f)
isCollinearPath = true;
}
//add jumping and jetting to speed the player up...
if (distToMove2D > 30.0f)
{
//full speed with skiing
setMoveSpeed(1.0f);
//jump if we can - should encourage skiing
if (mHeadingDownhill)
{
if (player->canJump() && mSkillLevel >= 0.25f)
pressJump();
}
else
{
if (mEnergyAvailable && mEnergy > jetEnergy && mMoveMode != ModeWalk)
{
if (player->canJump())
pressJump();
else
pressJet();
}
}
}
else if (distToEnd > 10.0f && isCollinearPath)
{
//full speed - no skiing
setMoveSpeed(1.0f);
if (mEnergyAvailable && mEnergy > jetEnergy && mMoveMode != ModeWalk)
pressJet();
}
else if (distToMove2D > getMax(4.0f, tolerance))
{
//full speed - no skiing
setMoveSpeed(1.0f);
}
else if (distToMove2D > tolerance)
{
//slower speed with no skiing
setMoveSpeed(0.4f);
}
else if (distToEnd < tolerance)
{
//stopped - we've arrived
setMoveMode(ModeStop);
}
}
}
break;
case ModeStuck:
{
if (mStuckTryJump)
{
if (! mStuckJumpInitialized)
{
mStuckJumpInitialized = true;
mStuckJumpTimer = Sim::getCurrentTime();
setMoveSpeed(0.0f);
setMoveLocation(mLocation);
}
else if (Sim::getCurrentTime() - mStuckJumpTimer < 500)
{
if (mEnergy < 0.25f)
mStuckJumpTimer = Sim::getCurrentTime();
}
else if (Sim::getCurrentTime() - mStuckJumpTimer < 1000)
{
pressJump();
pressJet();
}
else if (Sim::getCurrentTime() - mStuckJumpTimer < 2000)
{
setMoveLocation(mStuckDestination);
setMoveSpeed(1.0f);
pressJet();
}
else
{
//set the bool so it'll try to back up if we're still stuck...
mStuckTryJump = false;
//see if that unstuck us...
if ((mLocation - mStuckLocation).len() > 1.0f)
{
mPath.forceSearch();
setMoveSpeed(1.0f);
setMoveMode(ModeExpress, true);
mStuckLocation.set(0, 0, 0);
return;
}
}
}
//else try backing up, and researching the path
else
{
//I guess we're still stuck... try backing up
if (! mStuckInitialized)
{
mStuckInitialized = true;
//find a new direction to move
Point3F newLocation;
F32 foundLength = 32767;
Point3F curHeading = mNodeLocation - mLocation;
F32 curAngle = mAtan(curHeading.x, curHeading.y) - (M_PI / 2.0f);
for (F32 i = 0.0f; i < 5.0f; i += 1.0f)
{
//run an LOS to see if the way is clear...
F32 testAngle = curAngle + ((i + 2.0) * M_PI / 4.0f);
Point3F newVec(mCos(testAngle), -mSin(testAngle), 0.0f);
U32 mask = TerrainObjectType | InteriorObjectType;
RayInfo rayInfo;
Point3F startPt = mLocation;
Point3F endPt = mLocation + (4.0f * newVec);
startPt.z += 0.3f;
endPt.z += 0.3f;
if (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo))
{
newLocation = endPt;
break;
}
else if ((rayInfo.point - startPt).len() < foundLength)
{
foundLength = (rayInfo.point - startPt).len();
newLocation = endPt;
}
}
//set the move location, and set the time stamp
mStuckTimer = Sim::getCurrentTime();
setMoveLocation(newLocation);
setMoveSpeed(1.0f);
}
//see if our time has run out, or if we're close to our dest
if ((mLocation - mMoveLocation).len() < 1.0f || Sim::getCurrentTime() - mStuckTimer > 2000)
{
mStuckLocation.set(0, 0, 0);
mPath.informStuck(mStuckLocation, mStuckDestination);
setMoveMode(ModeExpress, true);
}
}
}
break;
}
}
//if we're in the middle of a nav jet, aim at the landing place...
Point3F jetAimLocation;
if (mNavUsingJet && mJetting.shouldAimAt(jetAimLocation))
{
setAimLocation(jetAimLocation);
}
//else set the aiming if we don't have a target to shoot at
else if (mMoveMode != ModeStop && mPath.isPathCurrent() && !bool(mTargetPlayer) && !bool(mTargetObject) && (Sim::getCurrentTime() > mLookAtTargetTimeMS))
{
//set the player to always look 10m ahead on the path
Point3F directionVector;
Point3F aimPoint;
F32 distToEnd = mPath.distRemaining(10);
if (distToEnd >= 10)
aimPoint = mPath.getLocOnPath(10);
else
aimPoint = mNodeLocation;
//now extend that point by 30m so we don't look down
directionVector = aimPoint - mLocation;
directionVector.z = 0;
F32 directionLen = directionVector.len();
// LH pulled apart the normalize() to avoid (interupt causing) divides by zero
if (directionLen < 0.001)
{
aimPoint.x = mLocation.x + 30.0f * mCos(mRotation.z);
aimPoint.y = mLocation.y - 30.0f * mSin(mRotation.z);
aimPoint.z = mMuzzlePosition.z;
}
else
{ // LH- here's the normalize:
aimPoint.x = mLocation.x + 30.0f * (directionVector.x / directionLen);
aimPoint.y = mLocation.y + 30.0f * (directionVector.y / directionLen);
aimPoint.z = mMuzzlePosition.z;
}
setAimLocation(aimPoint);
}
}
void AIConnection::scriptSustainFire(S32 count)
{
mScriptTriggerCounter = count;
}
static Point3F extractRotation(const MatrixF & matrix)
{
const F32 * mat = (const F32*)matrix;
Point3F r;
r.x = mAsin(mat[MatrixF::idx(2,1)]);
if(mCos(r.x) != 0.f)
{
r.y = mAtan(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]);
r.z = mAtan(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]);
}
else
{
r.y = 0.f;
r.z = mAtan(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]);
}
return(r);
}
void AIConnection::getMoveList(Move** movePtr,U32* numMoves)
{
//initialize the move structure and return pointers
mMove = NullMove;
*movePtr = &mMove;
*numMoves = 1;
//if the system is not enabled, return having set the movePtr to a NullMove
if (!gAISystemEnabled)
return;
//movement is done in the object's rotation - create a matrix
MatrixF moveMatrix;
moveMatrix.set(EulerF(0, 0, 0));
moveMatrix.setColumn(3, Point3F(0, 0, 0));
moveMatrix.transpose();
//aiming variables
Point3F curLocation;
F32 curYaw, curPitch;
F32 newYaw, newPitch;
Point3F rotation;
F32 xDiff, yDiff, zDiff;
//make sure we have a valid control object
ShapeBase *ctrlObject = getControlObject();
if (! ctrlObject)
return;
//get the control object
Player *myPlayer = NULL;
myPlayer = dynamic_cast<Player*>(ctrlObject);
//make sure we're either controlling a player or a vehicle
if (myPlayer)
{
//if we're dead, no need to call any of the script functions...
if (! dStricmp(myPlayer->getStateName(), "dead"))
return;
//process the ai tasks and steps
process(myPlayer);
F32 vehicleRot = 0;
// Find the rotation around the Z axis from the mounted vehicle
if (myPlayer->isMounted() && bool(myPlayer->getObjectMount()))
{
Point3F vec, pos;
MatrixF vehicleMat;
myPlayer->getObjectMount()->getMountTransform(myPlayer->getMountNode(), &vehicleMat);
vehicleMat.getColumn(1,&vec);
vehicleMat.getColumn(3,&pos);
vehicleRot = -mAtan(-vec.x,vec.y);
}
//get the current location (global coords)
MatrixF const& myTransform = myPlayer->getTransform();
myTransform.getColumn(3, &curLocation);
//initialize the aiming variables
rotation = myPlayer->getRotation();
Point3F headRotation = myPlayer->getHeadRotation();
curYaw = rotation.z + vehicleRot;
curPitch = headRotation.x;
xDiff = mAimLocation.x - curLocation.x;
yDiff = mAimLocation.y - curLocation.y;
//first do Yaw
if (! isZero(xDiff) || ! isZero(yDiff))
{
//use the cur yaw between -Pi and Pi
while (curYaw > M_2PI)
curYaw -= M_2PI;
while (curYaw < -M_2PI)
curYaw += M_2PI;
//find the new yaw
newYaw = mAtan(xDiff, yDiff);
//find the yaw diff
F32 yawDiff = newYaw - curYaw;
//make it between 0 and 2PI
if (yawDiff < 0.0f)
yawDiff += M_2PI;
else if (yawDiff >= M_2PI)
yawDiff -= M_2PI;
//now make sure we take the short way around the circle
if (yawDiff > M_PI)
yawDiff -= M_2PI;
else if (yawDiff < -M_PI)
yawDiff += M_2PI;
mMove.yaw = yawDiff;
//set up the movement matrix
moveMatrix.set(EulerF(0, 0, newYaw));
}
else
moveMatrix.set(EulerF(0, 0, curYaw));
//next do pitch
// F32 horzDist = Point2F(yDiff, xDiff).len();
// F32 horzDist = Point2F(mAimLocation.x - mMuzzlePosition.x, mAimLocation.y - mMuzzlePosition.y).len();
F32 horzDist = Point2F(mAimLocation.x - mEyePosition.x, mAimLocation.y - mEyePosition.y).len();
if (! isZero(horzDist))
{
//we shoot from the gun, not the eye...
// F32 vertDist = mAimLocation.z - mMuzzlePosition.z;
F32 vertDist = mAimLocation.z - mEyePosition.z;
newPitch = mAtan(horzDist, vertDist) - (M_PI / 2.0f);
F32 pitchDiff = newPitch - curPitch;
mMove.pitch = pitchDiff;
}
//finally, move towards mMoveLocation
xDiff = mMoveLocation.x - curLocation.x;
yDiff = mMoveLocation.y - curLocation.y;
if (((mFabs(xDiff) > 0) || (mFabs(yDiff) > 0)) && (! isZero(mMoveSpeed)))
{
if (isZero(xDiff))
mMove.y = (curLocation.y > mMoveLocation.y ? -mMoveSpeed : mMoveSpeed);
else if (isZero(yDiff))
mMove.x = (curLocation.x > mMoveLocation.x ? -mMoveSpeed : mMoveSpeed);
else if (mFabs(xDiff) > mFabs(yDiff))
{
F32 value = mFabs(yDiff / xDiff) * mMoveSpeed;
mMove.y = (curLocation.y > mMoveLocation.y ? -value : value);
mMove.x = (curLocation.x > mMoveLocation.x ? -mMoveSpeed : mMoveSpeed);
}
else
{
F32 value = mFabs(xDiff / yDiff) * mMoveSpeed;
mMove.x = (curLocation.x > mMoveLocation.x ? -value : value);
mMove.y = (curLocation.y > mMoveLocation.y ? -mMoveSpeed : mMoveSpeed);
}
//now multiply the move vector by the transpose of the object rotation matrix
moveMatrix.transpose();
Point3F newMove;
moveMatrix.mulP(Point3F(mMove.x, mMove.y, 0), &newMove);
//and sub the result back in the move structure
mMove.x = newMove.x;
mMove.y = newMove.y;
}
}
else
{
Vehicle *myVehicle;
myVehicle = dynamic_cast<Vehicle*>(ctrlObject);
if (myVehicle)
{
//process the ai tasks and steps (well, not steps get ignored for piloting vehicles...)
process(myVehicle);
//get the current location (global coords)
MatrixF const& myTransform = myVehicle->getTransform();
myTransform.getColumn(3, &curLocation);
//initialize the aiming variables
rotation = extractRotation(myTransform);
xDiff = mPilotAimLocation.x - curLocation.x;
yDiff = mPilotAimLocation.y - curLocation.y;
zDiff = mPilotAimLocation.z - curLocation.z;
F32 dist2D = Point2F(yDiff, xDiff).len();
//first do Yaw
curYaw = rotation.z;
F32 yawDiff = 0;
Point3F velocity = myVehicle->getVelocity();
Point3F velocity2D(velocity.x, velocity.y, 0.0f);
F32 velocityDot = get2DDot(velocity2D, (mPilotDestination - curLocation));
//if ((mFabs(xDiff) > 5.0f || mFabs(yDiff) > 5.0f) && (mPilotSpeed == 0.0f || (velocityDot < 0.8f && velocity2D.len() > 5.0f)))
if (mFabs(xDiff) > 5.0f || mFabs(yDiff) > 5.0f)
{
//use the cur yaw between -Pi and Pi
while (curYaw > M_2PI)
curYaw -= M_2PI;
while (curYaw < -M_2PI)
curYaw += M_2PI;
//find the new yaw
newYaw = mAtan(xDiff, yDiff);
//find the yaw diff
yawDiff = newYaw - curYaw;
//make it between 0 and 2PI
if (yawDiff < 0.0f)
yawDiff += M_2PI;
else if (yawDiff >= M_2PI)
yawDiff -= M_2PI;
//now make sure we take the short way around the circle
if (yawDiff > M_PI)
yawDiff -= M_2PI;
else if (yawDiff < -M_PI)
yawDiff += M_2PI;
//set up the movement matrix
mMove.yaw = yawDiff;
moveMatrix.set(EulerF(0, 0, newYaw));
}
else
moveMatrix.set(EulerF(0, 0, curYaw));
//both pitch and destination should use the pilotDestination, not the aim location
xDiff = mPilotDestination.x - curLocation.x;
yDiff = mPilotDestination.y - curLocation.y;
zDiff = mPilotDestination.z - curLocation.z;
dist2D = Point2F(yDiff, xDiff).len();
//get the yAxis - the vertical slope of which is our current pitch
Point3F yAxis;
myTransform.mulV(Point3F(0, 1, 0), &yAxis);
yAxis.normalize();
curPitch = mAtan(1.0f, yAxis.z) - (M_PI / 2.0f);
//if we're below our destination, and don't have enough vertical velocity, pitch up...
newPitch = mAtan(dist2D, zDiff) - (M_PI / 2.0f);
newPitch = getMax(mPitchUpMax, getMin(mPitchDownMax, newPitch));
//use a pitch delta to prevent from pitching too fast...
S32 pitchDir = 0;
F32 pitchDelta = curPitch - mPreviousPitch;
mPreviousPitch = curPitch;
//make sure we've got some horizontal speed
if (mPilotSpeed > 0.0f && velocity2D.len() > 5.0f)
{
//at our current rate of ascent/descent,
F32 timeToDest = dist2D / velocity2D.len();
F32 vertDist = velocity.z * timeToDest;
//if we're below our dest and our vertical lift is too low, pitch up
if (zDiff > 5.0f && (vertDist < zDiff - 2.0f || curPitch > 0.0f))
pitchDir = 1;
//else if we're higher than our dest, and our vertical drop is too low, pitch down
else if (zDiff < -5.0f && (vertDist > zDiff + 2.0f || curPitch < 0.0f))
pitchDir = -1;
//make sure we're within our pitch limits, and that we're not pitching too fast
if (pitchDir > 0 && (curPitch < mPitchUpMax || pitchDelta < -mPitchIncMax))
pitchDir = -1;
else if (pitchDir < 0 && (curPitch > mPitchDownMax || pitchDelta > mPitchIncMax))
pitchDir = 1;
//if we didn't meet the above conditions, our velocity is on track, and we should level out
if (pitchDir == 0)
{
//if we're pitched up, and our change in pitch is also in the up direction
if (curPitch < 0.0f && pitchDelta < 0)
pitchDir = -1;
else if (curPitch > 0.0f && pitchDelta > 0)
pitchDir = 1;
}
//now put the pitch into the pitch move struct
if (pitchDir > 0)
mMove.pitch = mPitchUpMax;
else if (pitchDir < 0)
mMove.pitch = mPitchDownMax;
}
//now calculate the actual movement towards mPilotDestination
//first calculate speed - scale by whether we're already heading in the right direction...
F32 speed = 0.0f;
if ((mFabs(xDiff) > 5.0f) || (mFabs(yDiff) > 5.0f))
{
//if we're heading within a 45 deg cone, add some speed
if (mFabs(yawDiff < 0.8f))
speed = 1.0f - (mFabs(yawDiff) / M_PI);
}
//slow our movespeed down according to how far we are from our target...
if (speed > 0.0f && dist2D >= 5.0f && dist2D <= 20.0f)
speed *= 0.3f * getMin(0.7f, dist2D / 20.0f);
//see if we need to reverse thrust...
if (dist2D < 6.0f && velocity2D.len() > 5.0f && mFabs(velocityDot) >= 0.78f)
speed = getMax(-1.0f, -2.0f * speed);
//now factor in our pilot speed
speed *= mPilotSpeed;
//see if we should add some jetting
if (velocity2D.len() < 8.0f && curPitch <= 0.05f && zDiff > 5.0f)
mTriggers[JetTrigger] = true;
//set some debugging vars
mVehicleLocation = curLocation;
mCurrentPitch = curPitch;
mDesiredPitch = newPitch;
mPitchIncrement = mMove.pitch;
mPilotDistToDest2D = dist2D;
mPilotCurVelocity = myVehicle->getVelocity().len();
mPilotCurThrust = speed;
mCurrentYaw = curYaw;
//fill in the move struction
if (((mFabs(xDiff) > 5.0f) || (mFabs(yDiff) > 5.0f)) && (! isZero(speed)))
{
if (mFabs(xDiff) <= 5.0f)
mMove.y = (curLocation.y > mPilotDestination.y ? -speed : speed);
else if (mFabs(yDiff) <= 5.0f)
mMove.x = (curLocation.x > mPilotDestination.x ? -speed : speed);
else if (mFabs(xDiff) > mFabs(yDiff))
{
F32 value = mFabs(yDiff / xDiff) * speed;
mMove.y = (curLocation.y > mPilotDestination.y ? -value : value);
mMove.x = (curLocation.x > mPilotDestination.x ? -speed : speed);
}
else
{
F32 value = mFabs(xDiff / yDiff) * speed;
mMove.x = (curLocation.x > mPilotDestination.x ? -value : value);
mMove.y = (curLocation.y > mPilotDestination.y ? -speed : speed);
}
//now multiply the move vector by the transpose of the object rotation matrix
moveMatrix.transpose();
Point3F newMove;
moveMatrix.mulP(Point3F(mMove.x, mMove.y, 0), &newMove);
//and sub the result back in the move structure
mMove.x = newMove.x;
mMove.y = newMove.y;
}
}
}
//copy the triggers into the move
for (int i = 0; i < MaxTriggerKeys; i++)
{
mMove.trigger[i] = mTriggers[i];
mTriggers[i] = false;
}
}
//-----------------------------------------------------------------------------
//STEP AND TASK FUNCTIONS
void AIConnection::clearStep()
{
if (mStep)
mStep->deleteObject();
mStep = NULL;
}
void AIConnection::setStep(AIStep *step)
{
clearStep();
mStep = step;
}
const char *AIConnection::getStepStatus()
{
if (! mStep)
return "Finished";
else
{
int status = mStep->getStatus();
switch (status)
{
case AIStep::InProgress:
return "InProgress";
case AIStep::Failed:
return "Failed";
case AIStep::Finished:
default:
return "Finished";
}
}
}
const char *AIConnection::getStepName()
{
if (! mStep)
return "NONE";
else
{
const char *stepName = mStep->getName();
if (!stepName || !stepName[0])
return "NONE";
else
return stepName;
}
}
void AIConnection::clearTasks()
{
while (mTaskList.size())
{
AITask *tempPtr = mTaskList[0];
mTaskList.pop_front();
tempPtr->deleteObject();
}
mCurrentTask = NULL;
}
void AIConnection::addTask(AITask *task)
{
if (! task)
return;
mTaskList.push_back(task);
}
void AIConnection::removeTask(S32 id)
{
for (S32 i = 0; i < mTaskList.size(); i++)
{
if (mTaskList[i]->getId() == id)
{
if (mCurrentTask == mTaskList[i])
{
mCurrentTask->retire(this);
mCurrentTask = NULL;
}
mTaskList[i]->deleteObject();
mTaskList.erase(i);
break;
}
}
}
void AIConnection::listTasks()
{
for (S32 i = 0; i < mTaskList.size(); i++)
{
Con::printf("%d: %s", mTaskList[i]->getId(), mTaskList[i]->getName());
}
}
void AIConnection::missionCycleCleanup()
{
setMoveMode(ModeStop);
clearTasks();
clearStep();
mPath.forceSearch();
mPath.missionCycleCleanup();
}