mirror of
https://github.com/tribes2/engine.git
synced 2026-01-20 03:34:48 +00:00
806 lines
24 KiB
C++
806 lines
24 KiB
C++
//-----------------------------------------------------------------------------
|
|
// V12 Engine
|
|
//
|
|
// Copyright (c) 2001 GarageGames.Com
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "core/realComp.h"
|
|
#include "console/simBase.h"
|
|
#include "ai/aiConnection.h"
|
|
#include "game/player.h"
|
|
#include "game/projectile.h"
|
|
#include "math/mRandom.h"
|
|
#include "ai/aiStep.h"
|
|
|
|
AIStep::AIStep()
|
|
{
|
|
mStatus = InProgress;
|
|
}
|
|
|
|
void AIStep::process(AIConnection *client, Player *player)
|
|
{
|
|
//only process if we're still "in progress"
|
|
if (mStatus != InProgress)
|
|
return;
|
|
|
|
//make sure we have a client and a player
|
|
if (!client || !player)
|
|
mStatus = Failed;
|
|
|
|
//make sure we're not dead
|
|
if (! dStricmp(player->getStateName(), "dead"))
|
|
mStatus = Failed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//ESCORT step
|
|
AIStepEscort::AIStepEscort(GameConnection *clientToEscort)
|
|
{
|
|
mInitialized = false;
|
|
mClientToEscort = clientToEscort;
|
|
mResetDestinationCounter = 0;
|
|
}
|
|
|
|
void AIStepEscort::process(AIConnection *client, Player *player)
|
|
{
|
|
Parent::process(client, player);
|
|
if (mStatus != InProgress)
|
|
return;
|
|
|
|
//make sure we have a valid target to escort
|
|
Player *targetPlayer = NULL;
|
|
if ((! bool(mClientToEscort)) || (! mClientToEscort->getControlObject()))
|
|
mStatus = Failed;
|
|
else
|
|
targetPlayer = dynamic_cast<Player*>(mClientToEscort->getControlObject());
|
|
if (! targetPlayer)
|
|
{
|
|
mStatus = Failed;
|
|
return;
|
|
}
|
|
|
|
//set the energy levels
|
|
if (! mInitialized)
|
|
{
|
|
mInitialized = true;
|
|
if (targetPlayer->isMounted())
|
|
client->setMoveMode(AIConnection::ModeMountVehicle);
|
|
else
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
client->setMoveTolerance(4.0f);
|
|
client->setEnergyLevels(0.05f, 0.15f);
|
|
}
|
|
|
|
//get the target's current location (global coords)
|
|
MatrixF const& targetTransform = targetPlayer->getTransform();
|
|
Point3F targetLocation;
|
|
targetTransform.getColumn(3, &targetLocation);
|
|
|
|
//go back to using the euclidean distance
|
|
F32 distToTarget = getMax(client->getPathDistRemaining(20), (client->mLocation - targetLocation).len());
|
|
|
|
//cut down on the number of path searches
|
|
if (--mResetDestinationCounter <= 0)
|
|
{
|
|
mResetDestinationCounter = 5;
|
|
client->setMoveDestination(targetLocation);
|
|
}
|
|
|
|
//get the current time
|
|
S32 curTime = Sim::getCurrentTime();
|
|
|
|
//see if we're close enough to the target yet
|
|
if (distToTarget < 10.0f)
|
|
{
|
|
mProximityBuffer = true;
|
|
if (client->getMoveMode() != AIConnection::ModeStop)
|
|
{
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
mStoppedTime = curTime;
|
|
mIdleStarted = false;
|
|
}
|
|
}
|
|
else if (distToTarget < 16.0f)
|
|
{
|
|
if (mProximityBuffer)
|
|
{
|
|
if (client->getMoveMode() != AIConnection::ModeStop)
|
|
{
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
mStoppedTime = curTime;
|
|
mIdleStarted = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//make sure we aim at the player momentarily...
|
|
if (client->getMoveMode() == AIConnection::ModeStop)
|
|
client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500);
|
|
|
|
if (targetPlayer->isMounted())
|
|
client->setMoveMode(AIConnection::ModeMountVehicle);
|
|
else
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//make sure we aim at the player momentarily...
|
|
if (client->getMoveMode() == AIConnection::ModeStop)
|
|
client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500);
|
|
mProximityBuffer = false;
|
|
if (targetPlayer->isMounted())
|
|
client->setMoveMode(AIConnection::ModeMountVehicle);
|
|
else
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
|
|
//now see if it's time to "idle"
|
|
if (client->getMoveMode() == AIConnection::ModeStop && curTime - mStoppedTime > 5000)
|
|
{
|
|
//init the idle vars
|
|
if (!mIdleStarted)
|
|
{
|
|
mIdleStarted = true;
|
|
mChokePoints.clear();
|
|
NavigationGraph::getChokePoints(client->mLocation, mChokePoints, 10, 65);
|
|
mIdleNextTime = 0;
|
|
}
|
|
|
|
//see if it's time to look around some more
|
|
if (curTime > mIdleNextTime)
|
|
{
|
|
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
|
|
|
|
//see if we should look or play an animation
|
|
if (gRandGen.randF() < 0.06f)
|
|
{
|
|
player->setActionThread("pda", false, true, false);
|
|
}
|
|
else
|
|
{
|
|
Point3F lookLocation;
|
|
if (mChokePoints.size() == 0)
|
|
{
|
|
if (gRandGen.randF() < 0.3f)
|
|
lookLocation = targetLocation;
|
|
else
|
|
lookLocation = client->mLocation;
|
|
}
|
|
else
|
|
{
|
|
S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9));
|
|
if (index == mChokePoints.size())
|
|
lookLocation = targetLocation;
|
|
else
|
|
lookLocation = mChokePoints[index];
|
|
}
|
|
|
|
//add some noise to the lookLocation
|
|
Point3F tempVector = lookLocation - client->mLocation;
|
|
tempVector.z = 0;
|
|
if (tempVector.len() < 0.1f)
|
|
tempVector.set(1, 0, 0);
|
|
tempVector.normalize();
|
|
tempVector *= 10.0f;
|
|
F32 noise = 2.0f;
|
|
tempVector.x += -noise + gRandGen.randF() * 2 * noise;
|
|
tempVector.y += -noise + gRandGen.randF() * 2 * noise;
|
|
tempVector.z = 1.5f + gRandGen.randF() * 2 * noise;
|
|
Point3F newAimLocation = client->mLocation + tempVector;
|
|
|
|
//now aim at that point
|
|
if (!client->scriptIsAiming())
|
|
client->setScriptAimLocation(newAimLocation, 3000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//ENGAGE step
|
|
AIStepEngage::AIStepEngage(GameConnection *target)
|
|
{
|
|
mInitialized = false;
|
|
mTarget = target;
|
|
mStraifeCounter = 0;
|
|
mPauseCounter = 0;
|
|
mPausing = false;
|
|
mCheckLOSCounter = 0;
|
|
mClearLOSToTarget = false;
|
|
|
|
mSearching = false;
|
|
mSearchInitialized = false;
|
|
|
|
mUsingEnergyWeapon = false;
|
|
mEnergyWeaponRecharge = 0.0f;
|
|
}
|
|
|
|
Point3F AIStepEngage::findStraifeLocation(AIConnection *client)
|
|
{
|
|
Point3F newLocation;
|
|
Point3F tempV = client->mLocation - client->mTargLocation;
|
|
Point3F newLocationVector;
|
|
if (tempV.len() < 0.001f)
|
|
tempV.set(0, 1, 0);
|
|
tempV.normalize();
|
|
mCross(tempV, Point3F(0, 0, 1), &newLocationVector);
|
|
if (newLocationVector.len() < 0.001f)
|
|
newLocationVector.set(0, 1, 0);
|
|
newLocationVector.normalize();
|
|
|
|
F32 newLocationLength = newLocationVector.len();
|
|
if (newLocationLength > 0.9f && newLocationLength < 1.1f)
|
|
{
|
|
//if we're not moving, choose randomly whether we straife right or left
|
|
if (client->mVelocity2D.len() < 1.0f)
|
|
newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance);
|
|
else
|
|
{
|
|
//otherwise, choose the point closest to the direction we're already moving...
|
|
F32 myAngle = AIConnection::get2DAngle(client->mLocation + client->mVelocity2D, client->mLocation);
|
|
F32 tempAngle1 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation + newLocationVector);
|
|
F32 tempAngle2 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation - newLocationVector);
|
|
|
|
if (mFabs(tempAngle1 - myAngle) < mFabs(tempAngle2 - myAngle))
|
|
newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance);
|
|
else
|
|
newLocation = client->mTargLocation - (newLocationVector * client->mEngageMinDistance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con::printf("DEBUG AIStepEngage::findStraifeLocation() - player and target are on top of each other...");
|
|
newLocation = client->mTargLocation;
|
|
}
|
|
|
|
//return the result
|
|
return newLocation;
|
|
}
|
|
|
|
void AIStepEngage::process(AIConnection *client, Player *player)
|
|
{
|
|
Parent::process(client, player);
|
|
if (mStatus != InProgress)
|
|
return;
|
|
|
|
//make sure the target is set
|
|
if (! mInitialized)
|
|
{
|
|
mInitialized = true;
|
|
|
|
//set the target and the energy levels
|
|
client->setMoveTolerance(2.0f);
|
|
client->setEngageTarget(mTarget);
|
|
client->setEnergyLevels(0.3f, 0.2f);
|
|
return;
|
|
}
|
|
|
|
//make sure we still have a target
|
|
if (! client->mTargetPlayer)
|
|
{
|
|
mStatus = Finished;
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
return;
|
|
}
|
|
|
|
//set the outdoor bools
|
|
bool targIsOutdoors = (client->getOutdoorRadius(client->mTargLocation) > 0);
|
|
bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0);
|
|
|
|
//set the LOS variables
|
|
S32 losTime;
|
|
Point3F losLocation;
|
|
bool hasLOS = client->hasLOSToClient(client->getEngageTarget(), losTime, losLocation);
|
|
|
|
//only gain height or straife if the target is outdoors
|
|
if (targIsOutdoors)
|
|
{
|
|
//decriment the straife counter
|
|
bool timeToStraife = false;
|
|
mStraifeCounter--;
|
|
|
|
//see if we should try to gain height
|
|
S32 mode = client->getMoveMode();
|
|
if (mode == AIConnection::ModeGainHeight)
|
|
{
|
|
//see if we need to cancel the gaining of height
|
|
if (client->mEnergy < 0.2f || client->mLocation.z - client->mTargLocation.z > 15.0f ||
|
|
client->mEnergy < client->mWeaponEnergy - client->mEnergyReserve)
|
|
{
|
|
timeToStraife = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//see if we should try to gain height - only if we're outdoors
|
|
if (playerIsOutdoors && mPauseCounter <= 0 && (client->mEnergy > 0.5f || client->mEnergy - client->mTargEnergy > 0.3f) &&
|
|
client->mEnergy > client->mWeaponEnergy + client->mEnergyFloat &&
|
|
client->mDistToTarg2D < 45.0f && client->mVelocity2D.len() > 5.0f &&
|
|
client->mLocation.z - client->mTargLocation.z < 15.0f && client->mSkillLevel >= 0.3f)
|
|
client->setMoveMode(AIConnection::ModeGainHeight);
|
|
|
|
//see if we need to straife
|
|
else
|
|
{
|
|
//see if we're nearish our destination
|
|
Point3F straifeVector = client->mLocation - mStraifeLocation;
|
|
straifeVector.z = 0;
|
|
if (straifeVector.len() < getMin(8, client->mEngageMinDistance))
|
|
{
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
if (!mPausing)
|
|
{
|
|
//if we have had LOS to the client within the last 5 seconds at this moment, then pause
|
|
//no need to pause if we're behind a hill
|
|
if (hasLOS || losTime < 5000)
|
|
{
|
|
F32 skillAdjust = (1.0f - client->mSkillLevel) *
|
|
(1.0f - client->mSkillLevel) *
|
|
(1.0f - client->mSkillLevel);
|
|
mPauseCounter = S32(gRandGen.randF() * 400.0f * skillAdjust);
|
|
mPausing = true;
|
|
}
|
|
}
|
|
mPauseCounter--;
|
|
}
|
|
|
|
if (mStraifeCounter <= 0 || mPauseCounter <= 0)
|
|
timeToStraife = true;
|
|
}
|
|
}
|
|
|
|
//see if we need to straife
|
|
if (timeToStraife)
|
|
{
|
|
F32 skillAdjust = (1.0f - client->mSkillLevel) *
|
|
(1.0f - client->mSkillLevel);
|
|
mStraifeCounter = 70 + S32(400.0f * skillAdjust);
|
|
mPauseCounter = 32767;
|
|
mPausing = false;
|
|
mStraifeLocation = findStraifeLocation(client);
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
client->setMoveDestination(mStraifeLocation);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if we've never even detected the client, technically, this is a script error -
|
|
//bots shouldn't be fighting anyone they've never seen. However...
|
|
if (losLocation == Point3F(0, 0, 0))
|
|
losLocation = client->mTargLocation;
|
|
|
|
//if the targ is Indoors, and we're outdoors, move to the client's position so that
|
|
//it's not so rediculously easy to lose the bots just by running into a doorway..
|
|
if (playerIsOutdoors)
|
|
losLocation = client->mTargLocation;
|
|
|
|
//see if we have LOS to the target (needed since we use a slightly different point than the LOS detection table
|
|
if (--mCheckLOSCounter <= 0)
|
|
{
|
|
player->disableCollision();
|
|
mCheckLOSCounter = 10;
|
|
mClearLOSToTarget = false;
|
|
RayInfo rayInfo;
|
|
Point3F startPt = player->getBoxCenter();
|
|
Point3F endPt = client->mTargLocation;
|
|
U32 mask = TerrainObjectType | InteriorObjectType | WaterObjectType | ForceFieldObjectType;
|
|
mClearLOSToTarget = (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo));
|
|
player->enableCollision();
|
|
}
|
|
|
|
//see if we're already at our losLocation, is it time to start idling (searching)?
|
|
if (!mClearLOSToTarget && !mSearching && ((client->mLocation - losLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop))
|
|
{
|
|
mSearching = true;
|
|
mSearchInitialized = false;
|
|
}
|
|
else if (mSearching)
|
|
{
|
|
//see if we should abort the search (we found our target)
|
|
if (mClearLOSToTarget)
|
|
{
|
|
mSearching = false;
|
|
}
|
|
|
|
//else initialize the search
|
|
else if (! mSearchInitialized)
|
|
{
|
|
mSearchInitialized = true;
|
|
mChokeLocation = losLocation;
|
|
mChokeIndex = -1;
|
|
NavigationGraph::getChokePoints(losLocation, mChokePoints, 10, 65);
|
|
mSearchTimer = Sim::getCurrentTime() + 3000;
|
|
}
|
|
|
|
else
|
|
{
|
|
//see if we're at the choke location, and we've been there for a few seconds...
|
|
if (((client->mLocation - mChokeLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop) && (Sim::getCurrentTime() > mSearchTimer))
|
|
{
|
|
//remove the current choke index from the array
|
|
if (mChokeIndex >= 0)
|
|
mChokePoints.erase(mChokeIndex);
|
|
|
|
//if we've run out of choke points to check, we've lost our target...
|
|
if (mChokePoints.size() <= 0)
|
|
{
|
|
mStatus = Failed;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
|
|
mChokeLocation = mChokePoints[mChokeIndex];
|
|
mSearchTimer = Sim::getCurrentTime() + (gRandGen.randF() * 1000) + 2000;
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
}
|
|
|
|
//move to the choke point
|
|
client->setMoveDestination(mChokeLocation);
|
|
}
|
|
}
|
|
|
|
//else we either have LOS, or we're moving to the target's last known location...
|
|
else
|
|
{
|
|
client->setMoveDestination(losLocation);
|
|
|
|
//if we can see the target, and we're close enough, don't get any closer
|
|
if (mClearLOSToTarget && client->mDistToTarg2D < 15.0f)
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
else
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//RangeObject step
|
|
AIStepRangeObject::AIStepRangeObject(GameBase *targetObject, const char *projectile, F32 *minDist, F32 *maxDist, Point3F *fromLocation)
|
|
{
|
|
mInitialized = false;
|
|
mTargetObject = targetObject;
|
|
mCheckLOSCounter = 0;
|
|
|
|
//set the range distances
|
|
F32 minDistance = 0;
|
|
F32 maxDistance = 1000;
|
|
if (*minDist)
|
|
minDistance = *minDist;
|
|
if (*maxDist)
|
|
maxDistance = *maxDist;
|
|
|
|
mMinDistance = getMax(0.0f, getMin(minDistance, maxDistance));
|
|
mMaxDistance = getMax(1.0f, getMax(minDistance, maxDistance));
|
|
mFromLocation.set(-1, -1, -1);
|
|
if (fromLocation)
|
|
mFromLocation = *fromLocation;
|
|
mPrevLocation.set(0, 0, 0);
|
|
|
|
//set the mask
|
|
mLOSMask = TerrainObjectType | InteriorObjectType | PlayerObjectType | ForceFieldObjectType;
|
|
if (bool(mTargetObject))
|
|
{
|
|
Player *plyr = dynamic_cast<Player*>(targetObject);
|
|
if (bool(plyr))
|
|
mLOSMask = TerrainObjectType | InteriorObjectType | StaticShapeObjectType | ForceFieldObjectType;
|
|
}
|
|
|
|
if ((! projectile) || (! projectile[0]) || (! dStricmp(projectile, "NoAmmo")))
|
|
mProjectile = NULL;
|
|
else
|
|
{
|
|
if (! Sim::findObject(projectile, mProjectile))
|
|
{
|
|
Con::printf("AIStepRangeObject() failed - unable to find projectile datablock: %s", projectile);
|
|
mProjectile = NULL;
|
|
}
|
|
}
|
|
|
|
if (! bool(mTargetObject) || ! bool(mProjectile))
|
|
mStatus = Failed;
|
|
}
|
|
|
|
void AIStepRangeObject::process(AIConnection *client, Player *player)
|
|
{
|
|
//make sure we have a client and a player
|
|
if (!client || !player)
|
|
{
|
|
mStatus = Failed;
|
|
return;
|
|
}
|
|
|
|
//make sure we're not dead
|
|
if (! dStricmp(player->getStateName(), "Dead"))
|
|
{
|
|
mStatus = Failed;
|
|
return;
|
|
}
|
|
|
|
//make sure we still have a target object
|
|
if (! bool(mTargetObject) || ! bool(mProjectile))
|
|
{
|
|
mStatus = Failed;
|
|
return;
|
|
}
|
|
|
|
//make sure the target is set
|
|
if (! mInitialized)
|
|
{
|
|
mInitialized = true;
|
|
|
|
//set the target and the energy levels
|
|
client->setMoveTolerance(0.25f);
|
|
client->setEngageTarget(NULL);
|
|
|
|
mTargetObject->getWorldBox().getCenter(&mTargetPoint);
|
|
if (mFromLocation != Point3F(-1, -1, -1))
|
|
mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(mFromLocation, mMinDistance / 2.0f), mMaxDistance);
|
|
else
|
|
mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(client->mLocation, mMinDistance / 2.0f), mMaxDistance);
|
|
|
|
//if the range is small, the graph may not have enough resolution...
|
|
if (mMaxDistance <= 10.0f)
|
|
{
|
|
if ((mTargetPoint - mGraphDestination).len() > 10.0f)
|
|
mGraphDestination = mTargetPoint;
|
|
}
|
|
|
|
client->setMoveDestination(mGraphDestination);
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
mStatus = InProgress;
|
|
}
|
|
|
|
//only check every 6 frames
|
|
if (--mCheckLOSCounter <= 0)
|
|
{
|
|
mCheckLOSCounter = 6;
|
|
|
|
//see if we're within range, or if we're where the graph told us to go...
|
|
F32 directionDot = AIConnection::get2DDot(mGraphDestination - client->mLocation, mTargetPoint - client->mLocation);
|
|
F32 distToTarg = (mTargetPoint - client->mLocation).len();
|
|
if (((distToTarg <= mMaxDistance || directionDot <= 0.0f) &&
|
|
(distToTarg > mMinDistance || directionDot > 0.0f)) ||
|
|
((client->mLocation - mGraphDestination).len() < 8.0f))
|
|
{
|
|
Point3F aimVectorMin, aimVectorMax;
|
|
F32 timeMin, timeMax;
|
|
bool targetInRange = mProjectile->calculateAim(mTargetPoint, Point3F(0, 0, 0), client->mMuzzlePosition, client->mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax);
|
|
if (targetInRange)
|
|
{
|
|
//if we're shooting a ballistic projectile (outside), we don't need
|
|
bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0);
|
|
bool clearLOSToTarget = false;
|
|
if (mProjectile->isBallistic && playerIsOutdoors)
|
|
clearLOSToTarget = true;
|
|
else
|
|
{
|
|
//see if we have line of site
|
|
player->disableCollision();
|
|
RayInfo rayInfo;
|
|
Point3F startPt = client->mMuzzlePosition;
|
|
Point3F endPt = mTargetPoint;
|
|
if (! gServerContainer.castRay(startPt, endPt, mLOSMask, &rayInfo))
|
|
{
|
|
clearLOSToTarget = true;
|
|
}
|
|
else
|
|
{
|
|
//if we're here, castRay() hit something, and rayInfo should have been initialized...
|
|
if (bool(rayInfo.object) && rayInfo.object->getId() == mTargetObject->getId())
|
|
clearLOSToTarget = true;
|
|
}
|
|
player->enableCollision();
|
|
}
|
|
|
|
//reset the var based on LOS
|
|
targetInRange = clearLOSToTarget;
|
|
}
|
|
|
|
if (targetInRange)
|
|
{
|
|
//stop the client
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
mPrevLocation = client->mLocation;
|
|
if (mPrevLocation == client->mLocation)
|
|
mStatus = Finished;
|
|
else
|
|
mStatus = InProgress;
|
|
}
|
|
else
|
|
{
|
|
//if we're at our graph location, but still aren't within range...
|
|
if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop))
|
|
{
|
|
//try a shorter max distance and redo the graph query
|
|
mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance);
|
|
mInitialized = false;
|
|
}
|
|
|
|
//else keep moving
|
|
else
|
|
{
|
|
client->setMoveDestination(mGraphDestination);
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
mStatus = InProgress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//if we're at our graph location, but still aren't within range...
|
|
if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop))
|
|
{
|
|
//try a shorter max distance and redo the graph query
|
|
mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance);
|
|
mInitialized = false;
|
|
}
|
|
|
|
//else keep moving
|
|
else
|
|
{
|
|
client->setMoveDestination(mGraphDestination);
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
}
|
|
mStatus = InProgress;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//RangeObject step
|
|
AIStepIdlePatrol::AIStepIdlePatrol(Point3F *idleLocation)
|
|
{
|
|
mInitialized = false;
|
|
mIdleLocation.set(0, 0, 0);
|
|
if (idleLocation)
|
|
mIdleLocation = *idleLocation;
|
|
mStatus = InProgress;
|
|
}
|
|
|
|
void AIStepIdlePatrol::process(AIConnection *client, Player *player)
|
|
{
|
|
Parent::process(client, player);
|
|
if (mStatus != InProgress)
|
|
return;
|
|
|
|
//make sure the target is set
|
|
if (! mInitialized)
|
|
{
|
|
mInitialized = true;
|
|
|
|
if (mIdleLocation == Point3F(0, 0, 0))
|
|
mIdleLocation = client->mLocation;
|
|
|
|
NavigationGraph::getChokePoints(mIdleLocation, mChokePoints, 25, 65);
|
|
mOutdoorRadius = client->getOutdoorRadius(mIdleLocation);
|
|
|
|
mIdleState = MoveToLocation;
|
|
mStateInit = false;
|
|
mMoveLocation = mIdleLocation;
|
|
mHeadingHome = true;
|
|
return;
|
|
}
|
|
|
|
switch (mIdleState)
|
|
{
|
|
case MoveToLocation:
|
|
if (! mStateInit)
|
|
{
|
|
mStateInit = true;
|
|
client->setMoveDestination(mMoveLocation);
|
|
client->setMoveMode(AIConnection::ModeExpress);
|
|
client->setMoveTolerance(4.0f);
|
|
}
|
|
else if (client->getPathDistRemaining(20.0f) < 8.0f)
|
|
{
|
|
client->setMoveMode(AIConnection::ModeStop);
|
|
mIdleState = LookAround;
|
|
mStateInit = false;
|
|
}
|
|
break;
|
|
|
|
|
|
case LookAround:
|
|
{
|
|
S32 curTime = Sim::getCurrentTime();
|
|
if (! mStateInit)
|
|
{
|
|
mStateInit = true;
|
|
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
|
|
mIdleEndTime = curTime + 8000 + gRandGen.randF() * 8000;
|
|
}
|
|
|
|
//see if it's time to move somewhere
|
|
if (curTime > mIdleEndTime)
|
|
{
|
|
//choose a choke point
|
|
if (mHeadingHome)
|
|
{
|
|
mHeadingHome = false;
|
|
if (mChokePoints.size() == 0)
|
|
{
|
|
mMoveLocation = mIdleLocation;
|
|
if (mOutdoorRadius > 0.0f)
|
|
{
|
|
F32 noise = getMin(mOutdoorRadius, 30.0f);
|
|
mMoveLocation.x += -noise / 2 + gRandGen.randF() * noise;
|
|
mMoveLocation.y += -noise / 2 + gRandGen.randF() * noise;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
|
|
mMoveLocation = mChokePoints[mChokeIndex];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//40% chance of heading to the idle point
|
|
if (gRandGen.randF() < 0.4f || mChokePoints.size() <= 1)
|
|
{
|
|
mHeadingHome = true;
|
|
mMoveLocation = mIdleLocation;
|
|
}
|
|
else
|
|
{
|
|
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
|
|
mMoveLocation = mChokePoints[mChokeIndex];
|
|
}
|
|
}
|
|
|
|
//set the new state
|
|
mIdleState = MoveToLocation;
|
|
mStateInit = false;
|
|
}
|
|
|
|
//alternate between choosing a new place to look, and pausing to look there
|
|
else if (curTime > mIdleNextTime)
|
|
{
|
|
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
|
|
|
|
//see if we should look or play an animation
|
|
if (gRandGen.randF() < 0.06f)
|
|
{
|
|
player->setActionThread("pda", false, true, false);
|
|
}
|
|
else
|
|
{
|
|
Point3F lookLocation;
|
|
if (mChokePoints.size() == 0)
|
|
lookLocation = mIdleLocation;
|
|
else
|
|
{
|
|
S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9));
|
|
if (index == mChokePoints.size())
|
|
lookLocation = mIdleLocation;
|
|
else
|
|
lookLocation = mChokePoints[index];
|
|
}
|
|
|
|
//add some noise to the lookLocation
|
|
Point3F tempVector = lookLocation - client->mLocation;
|
|
tempVector.z = 0;
|
|
if (tempVector.len() < 0.001f)
|
|
tempVector.set(0, 1, 0);
|
|
tempVector.normalize();
|
|
tempVector *= 10.0f;
|
|
F32 noise = 2.0f;
|
|
tempVector.x += -noise + gRandGen.randF() * 2 * noise;
|
|
tempVector.y += -noise + gRandGen.randF() * 2 * noise;
|
|
tempVector.z = 1.5f + gRandGen.randF() * 2 * noise;
|
|
Point3F newAimLocation = client->mLocation + tempVector;
|
|
|
|
//now aim at that point
|
|
if (!client->scriptIsAiming())
|
|
client->setScriptAimLocation(newAimLocation, 3000);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|