mirror of
https://github.com/tribes2/engine.git
synced 2026-01-19 19:24:45 +00:00
486 lines
14 KiB
C++
486 lines
14 KiB
C++
//-----------------------------------------------------------------------------
|
|
// V12 Engine
|
|
//
|
|
// Copyright (c) 2001 GarageGames.Com
|
|
// Portions Copyright (c) 2001 by Sierra Online, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "ai/aiConnection.h"
|
|
#include "ai/aiNavJetting.h"
|
|
#include "ai/graphLOS.h"
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
#define GravityConstant 20.0
|
|
#define AmountInFront 0.37
|
|
#define FastVelocity 10.0
|
|
#define PullInPercent 0.63
|
|
#define HitPointThresh 1.2
|
|
#define JumpWaitCount 3
|
|
#define SeekAboveChute 17.0f
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
bool AIJetting::init(const Point3F& dest, bool intoMount, NavJetting * jetInfo)
|
|
{
|
|
mSeekDest = dest;
|
|
mFirstTime = true;
|
|
mWillBonk = false;
|
|
mLaunchSpeed = 0.1;
|
|
mIntoMount = intoMount;
|
|
mStatus = AIJetWorking;
|
|
mJetInfo = jetInfo;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Run LOS to see if we can safely jump to our destination.
|
|
|
|
static const U32 scBonkMask = InteriorObjectType|StaticShapeObjectType
|
|
|StaticObjectType|TerrainObjectType;
|
|
|
|
// We don't worry about this if we're jetting up more than 2 meters (in that case
|
|
// there shouldn't be a chance of bonking). Also, we don't want to suppress
|
|
// take-off jump for long hops (these edges should already be well checked). Mainly
|
|
// this check is for those chute connections which had the bonk check suppressed...
|
|
bool AIJetting::willBonk(Point3F src, Point3F dst)
|
|
{
|
|
if (dst.z - src.z < 2.0)
|
|
{
|
|
Point2F vec2D(src.x-dst.x, src.y-dst.y);
|
|
if (vec2D.lenSquared() < (LiberalBonkXY * LiberalBonkXY))
|
|
{
|
|
src.z = dst.z = (getMax(src.z, dst.z) + 3.7);
|
|
|
|
Loser los(scBonkMask);
|
|
|
|
if (!los.haveLOS(src, dst))
|
|
return false; // Disable temporarily...
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// Called whenever we move the state along.
|
|
|
|
void AIJetting::newState(S16 state, S16 counter /*=0*/)
|
|
{
|
|
mCounter = counter;
|
|
mState = state;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
// Get distance from landing "wall" - with (dist < 0) meaning we're that much beyond it
|
|
F32 AIJetting::distFromWall(const Point3F& loc)
|
|
{
|
|
VectorF vecToWall = (mWallPoint - loc);
|
|
vecToWall.z = 0;
|
|
return mDot(vecToWall, mWallNormal);
|
|
}
|
|
|
|
// Assuming we're jetting up - see if it looks like the top of a chute up there.
|
|
static bool upChute(const Point3F& from, F32 top, const VectorF& normal, Point3F& soln)
|
|
{
|
|
RayInfo coll;
|
|
Point3F to(from.x, from.y, top + 20.0f);
|
|
|
|
if (gServerContainer.castRay(from, to, InteriorObjectType, &coll)) {
|
|
if (mDot(normal, coll.normal) > 0.05) {
|
|
coll.normal.z = 0;
|
|
coll.normal.normalize();
|
|
if (mDot(normal, coll.normal) > 0.9) { // ~ 25 deg
|
|
soln = coll.point;
|
|
// return true;
|
|
return false; // this has problems with larger chutes... oops.
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AIJetting::figureLandingDist(AIConnection* ai, F32& dist)
|
|
{
|
|
F32 A = -(GravityConstant * 0.5);
|
|
F32 B = ai->mVelocity.z;
|
|
F32 C = (ai->mLocation.z - mLandPoint.z);
|
|
F32 solutions[2];
|
|
U32 N = mSolveQuadratic(A, B, C, solutions);
|
|
|
|
if (N > 0)
|
|
{
|
|
// use the larger time solution (first will be negative, or coming up on soln)
|
|
F32 T = solutions[N-1];
|
|
|
|
// see where this will put us in XY -
|
|
dist = T * ai->mVelocity2D.len();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Do first time setup, plus handle other processing on each frame.
|
|
bool AIJetting::firstTimeStuff(AIConnection* ai, Player * player)
|
|
{
|
|
if (mFirstTime)
|
|
{
|
|
mFirstTime = false;
|
|
mCanInterupt = true;
|
|
mWillBonk = false;
|
|
mLandPoint = mSeekDest;
|
|
mWallNormal = (mSeekDest - ai->mLocation);
|
|
mWallNormal.z = 0;
|
|
if ((mTotal2D = mWallNormal.len()) < GraphJetFailXY) {
|
|
mStatus = AIJetFail;
|
|
return false;
|
|
}
|
|
|
|
F32 zDiff = (mSeekDest.z - ai->mLocation.z);
|
|
|
|
mSlope = (zDiff / mTotal2D);
|
|
|
|
// Set our desired launch speed based on slope. Zero slope -> full speed,
|
|
// Steep slope -> Zero speed. Maps negative slopes to full speed.
|
|
mLaunchSpeed = mapValueQuadratic(mSlope, 1.3f, 0.0f, 0.0f, 1.0f);
|
|
|
|
// Wall normal points along our path in XY plane.
|
|
mWallNormal /= mTotal2D;
|
|
|
|
mUpChute = (mJetInfo && mJetInfo->mChuteUp);
|
|
|
|
// our dest will be a unit or so beyond for sake of aiming
|
|
mWallPoint = mLandPoint;
|
|
mSeekDest += (mWallNormal * 1.1);
|
|
|
|
// Hop over walls-
|
|
if (mJetInfo)
|
|
mSeekDest.z += mJetInfo->mHopOver;
|
|
|
|
newState(AssureClear);
|
|
}
|
|
|
|
// Variable that is set when they should look where their heading for right effect-
|
|
mShouldAim = false;
|
|
|
|
if (mIntoMount && player->isMounted()) {
|
|
mStatus = AIJetSuccess;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
// Local function - call it when you should aim - bool variable is always cleared
|
|
// unless this is called.
|
|
void AIJetting::setAim(const Point3F& /*always mSeekDest now*/)
|
|
{
|
|
mShouldAim = true;
|
|
}
|
|
|
|
// Public function - find out if should and where at.
|
|
bool AIJetting::shouldAimAt(Point3F& atWhere)
|
|
{
|
|
if (mShouldAim)
|
|
atWhere = mSeekDest;
|
|
return mShouldAim;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// We may still need to nudge a little bit to assure we have clearance (the generated
|
|
// jetting edges need to sometimes hug a bit close (else some makeable connections get
|
|
// get thrown out) so this is how we handle the occasional problem).
|
|
|
|
static const U32 sLOSMask = InteriorObjectType | StaticShapeObjectType |
|
|
StaticObjectType | TerrainObjectType;
|
|
static const F32 sClearDistance = 1.4f;
|
|
|
|
bool AIJetting::assureClear(AIConnection* ai, Player* )
|
|
{
|
|
// Get loc a little bit above the feet-
|
|
Point3F botLoc(ai->mLocation.x, ai->mLocation.y, ai->mLocation.z + 0.2);
|
|
|
|
if (botLoc.z < mSeekDest.z - 2.0)
|
|
{
|
|
// This shouldn't go on very long, just need a nudge if anything...
|
|
if (++mCounter < 32)
|
|
{
|
|
Point3F vec = botLoc;
|
|
(vec -= mSeekDest).z = 0.0f;
|
|
|
|
// Usual checks just in case....
|
|
F32 len = vec.len();
|
|
if (len > 0.1)
|
|
{
|
|
Loser loser(sLOSMask);
|
|
Point3F clearLoc(botLoc.x, botLoc.y, mSeekDest.z);
|
|
|
|
// Compute vector to come in by-
|
|
vec *= (sClearDistance / len);
|
|
clearLoc -= vec;
|
|
|
|
if (!loser.haveLOS(botLoc, clearLoc))
|
|
{
|
|
// Seek away, our vec contains which way to go...
|
|
ai->setMoveLocation(botLoc += vec);
|
|
ai->setMoveSpeed(0.6);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mWillBonk = willBonk(ai->mLocation, mSeekDest);
|
|
|
|
newState(AwaitEnergy);
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
// Wait for amount of energy we think we need.
|
|
bool AIJetting::awaitEnergy(AIConnection* ai, Player* player)
|
|
{
|
|
ai->setMoveLocation(mSeekDest);
|
|
if (ai->mVelocity.lenSquared() < 0.2)
|
|
{
|
|
if (player->getEnergyValue() > 0.99)
|
|
{
|
|
newState(PrepareToJump);
|
|
}
|
|
else
|
|
{
|
|
// Run the energy calculation every frame. We need to use the same methods
|
|
// that the graph uses to decide that a given hop is makeable with a certain
|
|
// amount of energy ability configuration.
|
|
F32 ratings[2];
|
|
JetManager::Ability ability;
|
|
|
|
// Tribes player jetting was remove from the player class.
|
|
#if 0
|
|
ability.dur = player->getJetAbility(ability.acc, ability.dur, ability.v0);
|
|
#else
|
|
ability.acc = 0;
|
|
ability.dur = 0;
|
|
ability.v0 = 0;
|
|
#endif
|
|
gNavGraph->jetManager().calcJetRatings(ratings, ability);
|
|
|
|
F32 jetD = gNavGraph->jetManager().jetDistance(ai->mLocation, mSeekDest);
|
|
|
|
if (jetD < ratings[!mWillBonk && player->canJump()])
|
|
newState(PrepareToJump);
|
|
}
|
|
}
|
|
else
|
|
ai->setMoveSpeed(0);
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
bool AIJetting::prepareToJump(AIConnection* ai, Player* player)
|
|
{
|
|
ai->setMoveLocation(mSeekDest);
|
|
ai->setMoveSpeed(0);
|
|
if (++mCounter >= JumpWaitCount)
|
|
{
|
|
// Can't check if can jump so often right now - so just do it once...
|
|
bool jumpReady = (mCounter==JumpWaitCount) && (player->haveContact() || player->isMounted());
|
|
|
|
// HACK to remedy problem with never finding a contact surface sometimes.
|
|
if (!jumpReady && (mCounter > JumpWaitCount * 8))
|
|
jumpReady = (ai->mVelocity.lenSquared() < 0.04);
|
|
|
|
if (jumpReady)
|
|
{
|
|
mJumpPoint = ai->mLocation;
|
|
Point3F here = ai->mLocation;
|
|
here.z += 100;
|
|
ai->setMoveLocation(here);
|
|
if (!mWillBonk || player->isMounted())
|
|
ai->pressJump();
|
|
ai->pressJet();
|
|
newState(InTheAir);
|
|
mCanInterupt = false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
bool AIJetting::inTheAir(AIConnection* ai, Player* player)
|
|
{
|
|
bool advanceState = false;
|
|
|
|
if (player->haveContact()) {
|
|
if (++mCounter >= 2) {
|
|
mStatus = AIJetFail;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
mCounter = 0;
|
|
|
|
ai->setMoveLocation(mSeekDest);
|
|
|
|
if (mUpChute) {
|
|
// Basically jet until bonk as long as we're going vertically
|
|
if (ai->mVelocity2D.lenSquared() > 0.04 || ai->mVelocity.z < -0.1)
|
|
mUpChute = false;
|
|
else {
|
|
ai->setMoveSpeed(0.0f);
|
|
ai->pressJet();
|
|
}
|
|
}
|
|
|
|
if (!mUpChute) {
|
|
if (ai->mLocation.z > mSeekDest.z) {
|
|
// Must monitor our Z velocity-
|
|
if (ai->mVelocity.z < 0)
|
|
ai->setMoveSpeed(0.0f);
|
|
else
|
|
ai->setMoveSpeed(1.0f);
|
|
ai->pressJet();
|
|
}
|
|
else {
|
|
ai->setMoveSpeed(0.0f);
|
|
F32 zSpeed = ai->mVelocity.z;
|
|
F32 howHigh = zSpeed * (zSpeed / GravityConstant);
|
|
if ((zSpeed < 0.01) || (ai->mLocation.z + howHigh) < (mSeekDest.z + 1.2))
|
|
ai->pressJet();
|
|
}
|
|
}
|
|
|
|
// get 2D distance to wall:
|
|
F32 wallD = distFromWall(ai->mLocation);
|
|
|
|
if (wallD < mTotal2D * 0.5)
|
|
setAim(mSeekDest);
|
|
|
|
if (wallD < 0.1)
|
|
advanceState = true;
|
|
|
|
// We need a good check for failure. One simple measure for failure might be to
|
|
// look at component of lateral velocity along the vector to the destination.
|
|
// Also, if we run out of energy and are far from destination...
|
|
if (!advanceState)
|
|
{
|
|
F32 landD;
|
|
if (AIJetting::figureLandingDist(ai, landD))
|
|
if( landD > (wallD - AmountInFront))
|
|
advanceState = true;
|
|
}
|
|
|
|
if (advanceState)
|
|
newState(SlowToLand);
|
|
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
bool AIJetting::slowToLand(AIConnection* ai, Player* player)
|
|
{
|
|
if (player->haveContact())
|
|
{
|
|
// First clause meant to handle case where we didn't get off the ledge-
|
|
if (ai->mLocation.z - mLandPoint.z > 1.3 && distFromWall(ai->mLocation) > 1.0) {
|
|
newState(PrepareToJump, JumpWaitCount-1);
|
|
}
|
|
//==> We need to use some volume information from the destination.
|
|
else {
|
|
if (!within(ai->mLocation, mLandPoint, 4.0))
|
|
{
|
|
mStatus = AIJetFail;
|
|
return true;
|
|
}
|
|
else {
|
|
mCanInterupt = true;
|
|
newState(WalkToPoint);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Point2F vel2(ai->mVelocity.x, ai->mVelocity.y);
|
|
F32 speed2 = vel2.len();
|
|
|
|
setAim(mSeekDest);
|
|
|
|
// Use velocity checks to finish it up-
|
|
F32 zvel = mFabs(ai->mVelocity.z);
|
|
if (speed2 < 0.1 && zvel < 0.1)
|
|
return true;
|
|
|
|
F32 wallD = distFromWall(ai->mLocation), landD;
|
|
bool beyond = figureLandingDist(ai, landD) && (landD > wallD- AmountInFront);
|
|
bool pullIn = (speed2 * PullInPercent) > wallD && speed2 > 0.7;
|
|
|
|
if (beyond && pullIn) // try to slow down
|
|
{
|
|
ai->setMoveLocation(mJumpPoint);
|
|
ai->setMoveSpeed(1.0f);
|
|
ai->pressJet();
|
|
}
|
|
else
|
|
{
|
|
ai->setMoveLocation(ai->mLocation);
|
|
ai->setMoveSpeed(0.0f);
|
|
if(! beyond)
|
|
ai->pressJet();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
bool AIJetting::walkToPoint(AIConnection* ai, Player*)
|
|
{
|
|
#if 0
|
|
mStatus = AIJetSuccess;
|
|
ai->setMoveSpeed(0.0f);
|
|
return true;
|
|
#else
|
|
ai->setMoveLocation(mLandPoint);
|
|
ai->setMoveTolerance(HitPointThresh * 0.6);
|
|
ai->setMoveSpeed(0.3f);
|
|
if (within_2D(ai->mLocation, mLandPoint, HitPointThresh) || ++mCounter > 10)
|
|
{
|
|
// wound up under or over the point...
|
|
if (mFabs(ai->mLocation.z - mLandPoint.z) > HitPointThresh)
|
|
mStatus = AIJetFail;
|
|
else
|
|
mStatus = AIJetSuccess;
|
|
ai->setMoveSpeed(0.0f);
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
|
|
// Returns true when done.
|
|
bool AIJetting::process(AIConnection* ai, Player* player)
|
|
{
|
|
if (!firstTimeStuff(ai, player))
|
|
return true;
|
|
|
|
switch(mState)
|
|
{
|
|
case AssureClear: return assureClear(ai, player);
|
|
case AwaitEnergy: return awaitEnergy(ai, player);
|
|
case PrepareToJump: return prepareToJump(ai, player);
|
|
case InTheAir: return inTheAir(ai, player);
|
|
case SlowToLand: return slowToLand(ai, player);
|
|
case WalkToPoint: return walkToPoint(ai, player);
|
|
}
|
|
return true;
|
|
}
|
|
|