mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-26 14:11:20 +00:00
844 lines
22 KiB
C++
844 lines
22 KiB
C++
//
|
|
// NPC_move.cpp
|
|
//
|
|
|
|
// leave this line at the top for all NPC_xxxx.cpp files...
|
|
#include "g_headers.h"
|
|
|
|
#include "b_local.h"
|
|
#include "g_nav.h"
|
|
#include "anims.h"
|
|
|
|
extern qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal );
|
|
extern qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f );
|
|
|
|
void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color );
|
|
|
|
qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2);
|
|
extern int GetTime ( int lastTime );
|
|
|
|
navInfo_t frameNavInfo;
|
|
extern qboolean FlyingCreature( gentity_t *ent );
|
|
extern qboolean PM_InKnockDown( playerState_t *ps );
|
|
|
|
extern cvar_t *g_navSafetyChecks;
|
|
|
|
extern qboolean Boba_Flying( gentity_t *self );
|
|
extern qboolean PM_InRoll( playerState_t *ps );
|
|
|
|
#define APEX_HEIGHT 200.0f
|
|
#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT))
|
|
#define JUMP_SPEED 200.0f
|
|
|
|
|
|
|
|
|
|
static qboolean NPC_TryJump();
|
|
|
|
|
|
|
|
|
|
static qboolean NPC_Jump( vec3_t dest, int goalEntNum )
|
|
{//FIXME: if land on enemy, knock him down & jump off again
|
|
float targetDist, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed,
|
|
float originalShotSpeed, shotSpeed, speedStep = 50.0f, minShotSpeed = 30.0f, maxShotSpeed = 500.0f;
|
|
qboolean belowBlocked = qfalse, aboveBlocked = qfalse;
|
|
vec3_t targetDir, shotVel, failCase;
|
|
trace_t trace;
|
|
trajectory_t tr;
|
|
qboolean blocked;
|
|
int elapsedTime, timeStep = 250, hitCount = 0, aboveTries = 0, belowTries = 0, maxHits = 10;
|
|
vec3_t lastPos, testPos, bottom;
|
|
|
|
VectorSubtract( dest, NPC->currentOrigin, targetDir );
|
|
targetDist = VectorNormalize( targetDir );
|
|
//make our shotSpeed reliant on the distance
|
|
originalShotSpeed = targetDist;//DistanceHorizontal( dest, NPC->currentOrigin )/2.0f;
|
|
if ( originalShotSpeed > maxShotSpeed )
|
|
{
|
|
originalShotSpeed = maxShotSpeed;
|
|
}
|
|
else if ( originalShotSpeed < minShotSpeed )
|
|
{
|
|
originalShotSpeed = minShotSpeed;
|
|
}
|
|
shotSpeed = originalShotSpeed;
|
|
|
|
while ( hitCount < maxHits )
|
|
{
|
|
VectorScale( targetDir, shotSpeed, shotVel );
|
|
travelTime = targetDist/shotSpeed;
|
|
shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity;
|
|
|
|
if ( !hitCount )
|
|
{//save the first one as the worst case scenario
|
|
VectorCopy( shotVel, failCase );
|
|
}
|
|
|
|
if ( 1 )//tracePath )
|
|
{//do a rough trace of the path
|
|
blocked = qfalse;
|
|
|
|
VectorCopy( NPC->currentOrigin, tr.trBase );
|
|
VectorCopy( shotVel, tr.trDelta );
|
|
tr.trType = TR_GRAVITY;
|
|
tr.trTime = level.time;
|
|
travelTime *= 1000.0f;
|
|
VectorCopy( NPC->currentOrigin, lastPos );
|
|
|
|
//This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down?
|
|
for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep )
|
|
{
|
|
if ( (float)elapsedTime > travelTime )
|
|
{//cap it
|
|
elapsedTime = floor( travelTime );
|
|
}
|
|
EvaluateTrajectory( &tr, level.time + elapsedTime, testPos );
|
|
//FUCK IT, always check for do not enter...
|
|
gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
|
|
/*
|
|
if ( testPos[2] < lastPos[2]
|
|
&& elapsedTime < floor( travelTime ) )
|
|
{//going down, haven't reached end, ignore botclip
|
|
gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask );
|
|
}
|
|
else
|
|
{//going up, check for botclip
|
|
gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP );
|
|
}
|
|
*/
|
|
|
|
if ( trace.allsolid || trace.startsolid )
|
|
{//started in solid
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND );
|
|
}
|
|
return qfalse;//you're hosed, dude
|
|
}
|
|
if ( trace.fraction < 1.0f )
|
|
{//hit something
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND ); // TryJump
|
|
}
|
|
if ( trace.entityNum == goalEntNum )
|
|
{//hit the enemy, that's bad!
|
|
blocked = qtrue;
|
|
/*
|
|
if ( g_entities[goalEntNum].client && g_entities[goalEntNum].client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{//bah, would collide in mid-air, no good
|
|
blocked = qtrue;
|
|
}
|
|
else
|
|
{//he's on the ground, good enough, I guess
|
|
//Hmm, don't want to land on him, though...?
|
|
}
|
|
*/
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if ( trace.contents & CONTENTS_BOTCLIP )
|
|
{//hit a do-not-enter brush
|
|
blocked = qtrue;
|
|
break;
|
|
}
|
|
if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay
|
|
{//close enough!
|
|
break;
|
|
}
|
|
else
|
|
{//FIXME: maybe find the extents of this brush and go above or below it on next try somehow?
|
|
impactDist = DistanceSquared( trace.endpos, dest );
|
|
if ( impactDist < bestImpactDist )
|
|
{
|
|
bestImpactDist = impactDist;
|
|
VectorCopy( shotVel, failCase );
|
|
}
|
|
blocked = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( lastPos, testPos, EDGE_WHITE_TWOSECOND ); // TryJump
|
|
}
|
|
}
|
|
if ( elapsedTime == floor( travelTime ) )
|
|
{//reached end, all clear
|
|
if ( trace.fraction >= 1.0f )
|
|
{//hmm, make sure we'll land on the ground...
|
|
//FIXME: do we care how far below ourselves or our dest we'll land?
|
|
VectorCopy( trace.endpos, bottom );
|
|
bottom[2] -= 128;
|
|
gi.trace( &trace, trace.endpos, NPC->mins, NPC->maxs, bottom, NPC->s.number, NPC->clipmask );
|
|
if ( trace.fraction >= 1.0f )
|
|
{//would fall too far
|
|
blocked = qtrue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//all clear, try next slice
|
|
VectorCopy( testPos, lastPos );
|
|
}
|
|
}
|
|
if ( blocked )
|
|
{//hit something, adjust speed (which will change arc)
|
|
hitCount++;
|
|
//alternate back and forth between trying an arc slightly above or below the ideal
|
|
if ( (hitCount%2) && !belowBlocked )
|
|
{//odd
|
|
belowTries++;
|
|
shotSpeed = originalShotSpeed - (belowTries*speedStep);
|
|
}
|
|
else if ( !aboveBlocked )
|
|
{//even
|
|
aboveTries++;
|
|
shotSpeed = originalShotSpeed + (aboveTries*speedStep);
|
|
}
|
|
else
|
|
{//can't go any higher or lower
|
|
hitCount = maxHits;
|
|
break;
|
|
}
|
|
if ( shotSpeed > maxShotSpeed )
|
|
{
|
|
shotSpeed = maxShotSpeed;
|
|
aboveBlocked = qtrue;
|
|
}
|
|
else if ( shotSpeed < minShotSpeed )
|
|
{
|
|
shotSpeed = minShotSpeed;
|
|
belowBlocked = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{//made it!
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{//no need to check the path, go with first calc
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hitCount >= maxHits )
|
|
{//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?)
|
|
return qfalse;
|
|
//NOTE: or try failcase?
|
|
//VectorCopy( failCase, NPC->client->ps.velocity );
|
|
//return qtrue;
|
|
}
|
|
VectorCopy( shotVel, NPC->client->ps.velocity );
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define NPC_JUMP_PREP_BACKUP_DIST 34.0f
|
|
|
|
trace_t mJumpTrace;
|
|
|
|
|
|
|
|
qboolean NPC_CanTryJump()
|
|
{
|
|
if (!(NPCInfo->scriptFlags&SCF_NAV_CAN_JUMP) || // Can't Jump
|
|
(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) || // If Can't Jump At All
|
|
(level.time<NPCInfo->jumpBackupTime) || // If Backing Up, Don't Try The Jump Again
|
|
(level.time<NPCInfo->jumpNextCheckTime) || // Don't Even Try To Jump Again For This Amount Of Time
|
|
(NPCInfo->jumpTime) || // Don't Jump If Already Going
|
|
(PM_InKnockDown(&NPC->client->ps)) || // Don't Jump If In Knockdown
|
|
(PM_InRoll(&NPC->client->ps)) || // ... Or Roll
|
|
(NPC->client->ps.groundEntityNum==ENTITYNUM_NONE) // ... Or In The Air
|
|
)
|
|
{
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
qboolean NPC_TryJump(const vec3_t& pos, float max_xy_dist, float max_z_diff)
|
|
{
|
|
if (NPC_CanTryJump())
|
|
{
|
|
NPCInfo->jumpNextCheckTime = level.time + Q_irand(1000, 2000);
|
|
|
|
VectorCopy(pos, NPCInfo->jumpDest);
|
|
|
|
// Can't Try To Jump At A Point In The Air
|
|
//-----------------------------------------
|
|
{
|
|
vec3_t groundTest;
|
|
VectorCopy(pos, groundTest);
|
|
groundTest[2] += (NPC->mins[2]*3);
|
|
gi.trace(&mJumpTrace, NPCInfo->jumpDest, vec3_origin, vec3_origin, groundTest, NPC->s.number, NPC->clipmask );
|
|
if (mJumpTrace.fraction >= 1.0f)
|
|
{
|
|
return qfalse; //no ground = no jump
|
|
}
|
|
}
|
|
NPCInfo->jumpTarget = 0;
|
|
NPCInfo->jumpMaxXYDist = (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750);
|
|
NPCInfo->jumpMazZDist = (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-450);
|
|
NPCInfo->jumpTime = 0;
|
|
NPCInfo->jumpBackupTime = 0;
|
|
return NPC_TryJump();
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean NPC_TryJump(gentity_t *goal, float max_xy_dist, float max_z_diff)
|
|
{
|
|
if (NPC_CanTryJump())
|
|
{
|
|
NPCInfo->jumpNextCheckTime = level.time + Q_irand(1000, 3000);
|
|
|
|
// Can't Jump At Targets In The Air
|
|
//---------------------------------
|
|
if (goal->client && goal->client->ps.groundEntityNum==ENTITYNUM_NONE)
|
|
{
|
|
return qfalse;
|
|
}
|
|
VectorCopy(goal->currentOrigin, NPCInfo->jumpDest);
|
|
NPCInfo->jumpTarget = goal;
|
|
NPCInfo->jumpMaxXYDist = (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750);
|
|
NPCInfo->jumpMazZDist = (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-400);
|
|
NPCInfo->jumpTime = 0;
|
|
NPCInfo->jumpBackupTime = 0;
|
|
return NPC_TryJump();
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
void NPC_JumpAnimation()
|
|
{
|
|
int jumpAnim = BOTH_JUMP1;
|
|
|
|
if ( NPC->client->NPC_class == CLASS_BOBAFETT
|
|
|| (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER)
|
|
|| NPC->client->NPC_class == CLASS_ROCKETTROOPER
|
|
||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) )
|
|
{//can't do acrobatics
|
|
jumpAnim = BOTH_FORCEJUMP1;
|
|
}
|
|
else if (NPC->client->NPC_class != CLASS_HOWLER)
|
|
{
|
|
if ( NPC->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) )
|
|
{
|
|
jumpAnim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 );
|
|
}
|
|
else
|
|
{
|
|
jumpAnim = BOTH_FLIP_F;
|
|
}
|
|
}
|
|
NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
}
|
|
|
|
extern void JET_FlyStart(gentity_t* actor);
|
|
|
|
void NPC_JumpSound()
|
|
{
|
|
if ( NPC->client->NPC_class == CLASS_HOWLER )
|
|
{
|
|
//FIXME: can I delay the actual jump so that it matches the anim...?
|
|
}
|
|
else if ( NPC->client->NPC_class == CLASS_BOBAFETT
|
|
|| NPC->client->NPC_class == CLASS_ROCKETTROOPER )
|
|
{
|
|
// does this really need to be here?
|
|
JET_FlyStart(NPC);
|
|
}
|
|
else
|
|
{
|
|
G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" );
|
|
}
|
|
}
|
|
|
|
qboolean NPC_TryJump()
|
|
{
|
|
vec3_t targetDirection;
|
|
float targetDistanceXY;
|
|
float targetDistanceZ;
|
|
|
|
// Get The Direction And Distances To The Target
|
|
//-----------------------------------------------
|
|
VectorSubtract(NPCInfo->jumpDest, NPC->currentOrigin, targetDirection);
|
|
targetDirection[2] = 0.0f;
|
|
targetDistanceXY = VectorNormalize(targetDirection);
|
|
targetDistanceZ = NPCInfo->jumpDest[2] - NPC->currentOrigin[2];
|
|
|
|
if ((targetDistanceXY>NPCInfo->jumpMaxXYDist) ||
|
|
(targetDistanceZ<NPCInfo->jumpMazZDist))
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
// Test To See If There Is A Wall Directly In Front Of Actor, If So, Backup Some
|
|
//-------------------------------------------------------------------------------
|
|
if (TIMER_Done(NPC, "jumpBackupDebounce"))
|
|
{
|
|
vec3_t actorProjectedTowardTarget;
|
|
VectorMA(NPC->currentOrigin, NPC_JUMP_PREP_BACKUP_DIST, targetDirection, actorProjectedTowardTarget);
|
|
gi.trace(&mJumpTrace, NPC->currentOrigin, vec3_origin, vec3_origin, actorProjectedTowardTarget, NPC->s.number, NPC->clipmask);
|
|
if ((mJumpTrace.fraction < 1.0f) ||
|
|
(mJumpTrace.allsolid) ||
|
|
(mJumpTrace.startsolid))
|
|
{
|
|
if (NAVDEBUG_showCollision)
|
|
{
|
|
CG_DrawEdge(NPC->currentOrigin, actorProjectedTowardTarget, EDGE_RED_TWOSECOND); // TryJump
|
|
}
|
|
|
|
// TODO: We may want to test to see if it is safe to back up here?
|
|
NPCInfo->jumpBackupTime = level.time + 1000;
|
|
TIMER_Set(NPC, "jumpBackupDebounce", 5000);
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
|
|
// bool Wounded = (NPC->health < 150);
|
|
// bool OnLowerLedge = ((targetDistanceZ<-80.0f) && (targetDistanceZ>-200.0f));
|
|
// bool WithinNormalJumpRange = ((targetDistanceZ<32.0f) && (targetDistanceXY<200.0f));
|
|
bool WithinForceJumpRange = ((fabsf(targetDistanceZ)>0) || (targetDistanceXY>128));
|
|
|
|
/* if (Wounded && OnLowerLedge)
|
|
{
|
|
ucmd.forwardmove = 127;
|
|
VectorClear(NPC->client->ps.moveDir);
|
|
TIMER_Set(NPC, "duck", -level.time);
|
|
return qtrue;
|
|
}
|
|
|
|
if (WithinNormalJumpRange)
|
|
{
|
|
ucmd.upmove = 127;
|
|
ucmd.forwardmove = 127;
|
|
VectorClear(NPC->client->ps.moveDir);
|
|
TIMER_Set(NPC, "duck", -level.time);
|
|
return qtrue;
|
|
}
|
|
*/
|
|
|
|
if (!WithinForceJumpRange)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
|
|
// If There Is Any Chance That This Jump Will Land On An Enemy, Try 8 Different Traces Around The Target
|
|
//-------------------------------------------------------------------------------------------------------
|
|
if (NPCInfo->jumpTarget)
|
|
{
|
|
float minSafeRadius = (NPC->maxs[0]*1.5f) + (NPCInfo->jumpTarget->maxs[0]*1.5f);
|
|
float minSafeRadiusSq = (minSafeRadius * minSafeRadius);
|
|
|
|
if (DistanceSquared(NPCInfo->jumpDest, NPCInfo->jumpTarget->currentOrigin)<minSafeRadiusSq)
|
|
{
|
|
vec3_t startPos;
|
|
vec3_t floorPos;
|
|
VectorCopy(NPCInfo->jumpDest, startPos);
|
|
|
|
floorPos[2] = NPCInfo->jumpDest[2] + (NPC->mins[2]-32);
|
|
|
|
for (int sideTryCount=0; sideTryCount<8; sideTryCount++)
|
|
{
|
|
NPCInfo->jumpSide++;
|
|
if ( NPCInfo->jumpSide > 7 )
|
|
{
|
|
NPCInfo->jumpSide = 0;
|
|
}
|
|
|
|
switch ( NPCInfo->jumpSide )
|
|
{
|
|
case 0:
|
|
NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1];
|
|
break;
|
|
case 1:
|
|
NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
|
|
break;
|
|
case 2:
|
|
NPCInfo->jumpDest[0] = startPos[0];
|
|
NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
|
|
break;
|
|
case 3:
|
|
NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius;
|
|
break;
|
|
case 4:
|
|
NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1];
|
|
break;
|
|
case 5:
|
|
NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius;
|
|
break;
|
|
case 6:
|
|
NPCInfo->jumpDest[0] = startPos[0];
|
|
NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius;
|
|
break;
|
|
case 7:
|
|
NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius;
|
|
NPCInfo->jumpDest[1] = startPos[1] -=minSafeRadius;
|
|
break;
|
|
}
|
|
|
|
floorPos[0] = NPCInfo->jumpDest[0];
|
|
floorPos[1] = NPCInfo->jumpDest[1];
|
|
|
|
gi.trace(&mJumpTrace, NPCInfo->jumpDest, NPC->mins, NPC->maxs, floorPos, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number), (NPC->clipmask|CONTENTS_BOTCLIP));
|
|
if ((mJumpTrace.fraction<1.0f) &&
|
|
(!mJumpTrace.allsolid) &&
|
|
(!mJumpTrace.startsolid))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( NAVDEBUG_showCollision )
|
|
{
|
|
CG_DrawEdge( NPCInfo->jumpDest, floorPos, EDGE_RED_TWOSECOND );
|
|
}
|
|
}
|
|
|
|
// If All Traces Failed, Just Try Going Right Back At The Target Location
|
|
//------------------------------------------------------------------------
|
|
if ((mJumpTrace.fraction>=1.0f) ||
|
|
(mJumpTrace.allsolid) ||
|
|
(mJumpTrace.startsolid))
|
|
{
|
|
VectorCopy(startPos, NPCInfo->jumpDest);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now, Actually Try The Jump To The Dest Target
|
|
//-----------------------------------------------
|
|
if (NPC_Jump(NPCInfo->jumpDest, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number)))
|
|
{
|
|
// We Made IT!
|
|
//-------------
|
|
NPC_JumpAnimation();
|
|
NPC_JumpSound();
|
|
|
|
NPC->client->ps.forceJumpZStart = NPC->currentOrigin[2];
|
|
NPC->client->ps.pm_flags |= PMF_JUMPING;
|
|
NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer;
|
|
NPC->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION );
|
|
ucmd.forwardmove = 0;
|
|
NPCInfo->jumpTime = 1;
|
|
|
|
VectorClear(NPC->client->ps.moveDir);
|
|
TIMER_Set(NPC, "duck", -level.time);
|
|
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean NPC_Jumping()
|
|
{
|
|
if ( NPCInfo->jumpTime )
|
|
{
|
|
if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart )
|
|
&& !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED))
|
|
{//landed
|
|
NPCInfo->jumpTime = 0;
|
|
}
|
|
else
|
|
{
|
|
// if (NPCInfo->jumpTarget)
|
|
// {
|
|
// NPC_FaceEntity(NPCInfo->jumpTarget, qtrue);
|
|
// }
|
|
// else
|
|
{
|
|
NPC_FacePosition(NPCInfo->jumpDest, qtrue);
|
|
}
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean NPC_JumpBackingUp()
|
|
{
|
|
if (NPCInfo->jumpBackupTime)
|
|
{
|
|
if (level.time<NPCInfo->jumpBackupTime)
|
|
{
|
|
STEER::Activate(NPC);
|
|
STEER::Flee(NPC, NPCInfo->jumpDest);
|
|
STEER::DeActivate(NPC, &ucmd);
|
|
NPC_FacePosition(NPCInfo->jumpDest, qtrue);
|
|
NPC_UpdateAngles( qfalse, qtrue );
|
|
return qtrue;
|
|
}
|
|
|
|
NPCInfo->jumpBackupTime = 0;
|
|
return NPC_TryJump();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_CheckCombatMove
|
|
-------------------------
|
|
*/
|
|
|
|
inline qboolean NPC_CheckCombatMove( void )
|
|
{
|
|
//return NPCInfo->combatMove;
|
|
if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if ( NPCInfo->goalEntity && NPCInfo->watchTarget )
|
|
{
|
|
if ( NPCInfo->goalEntity != NPCInfo->watchTarget )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_LadderMove
|
|
-------------------------
|
|
*/
|
|
|
|
static void NPC_LadderMove( vec3_t dir )
|
|
{
|
|
//FIXME: this doesn't guarantee we're facing ladder
|
|
//ALSO: Need to be able to get off at top
|
|
//ALSO: Need to play an anim
|
|
//ALSO: Need transitionary anims?
|
|
|
|
if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) )
|
|
{
|
|
//Set our movement direction
|
|
ucmd.upmove = (dir[2] > 0) ? 127 : -127;
|
|
|
|
//Don't move around on XY
|
|
ucmd.forwardmove = ucmd.rightmove = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_GetMoveInformation
|
|
-------------------------
|
|
*/
|
|
|
|
inline qboolean NPC_GetMoveInformation( vec3_t dir, float *distance )
|
|
{
|
|
//NOTENOTE: Use path stacks!
|
|
|
|
//Make sure we have somewhere to go
|
|
if ( NPCInfo->goalEntity == NULL )
|
|
return qfalse;
|
|
|
|
//Get our move info
|
|
VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
|
|
*distance = VectorNormalize( dir );
|
|
|
|
VectorCopy( NPCInfo->goalEntity->currentOrigin, NPCInfo->blockedTargetPosition );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NAV_GetLastMove
|
|
-------------------------
|
|
*/
|
|
|
|
void NAV_GetLastMove( navInfo_t &info )
|
|
{
|
|
info = frameNavInfo;
|
|
}
|
|
|
|
|
|
void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir )
|
|
{
|
|
vec3_t forward, right;
|
|
|
|
AngleVectors( self->currentAngles, forward, right, NULL );
|
|
|
|
dir[2] = 0;
|
|
VectorNormalize( dir );
|
|
//NPCs cheat and store this directly because converting movement into a ucmd loses precision
|
|
VectorCopy( dir, self->client->ps.moveDir );
|
|
|
|
float fDot = DotProduct( forward, dir ) * 127.0f;
|
|
float rDot = DotProduct( right, dir ) * 127.0f;
|
|
//Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte
|
|
if ( fDot > 127.0f )
|
|
{
|
|
fDot = 127.0f;
|
|
}
|
|
if ( fDot < -127.0f )
|
|
{
|
|
fDot = -127.0f;
|
|
}
|
|
if ( rDot > 127.0f )
|
|
{
|
|
rDot = 127.0f;
|
|
}
|
|
if ( rDot < -127.0f )
|
|
{
|
|
rDot = -127.0f;
|
|
}
|
|
cmd->forwardmove = floor(fDot);
|
|
cmd->rightmove = floor(rDot);
|
|
|
|
/*
|
|
vec3_t wishvel;
|
|
for ( int i = 0 ; i < 3 ; i++ )
|
|
{
|
|
wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove;
|
|
}
|
|
VectorNormalize( wishvel );
|
|
if ( !VectorCompare( wishvel, dir ) )
|
|
{
|
|
Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) );
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_MoveToGoal
|
|
|
|
Now assumes goal is goalEntity, was no reason for it to be otherwise
|
|
-------------------------
|
|
*/
|
|
|
|
#if AI_TIMERS
|
|
extern int navTime;
|
|
#endif// AI_TIMERS
|
|
qboolean NPC_MoveToGoal( qboolean tryStraight ) //FIXME: tryStraight not even used! Stop passing it
|
|
{
|
|
#if AI_TIMERS
|
|
int startTime = GetTime(0);
|
|
#endif// AI_TIMERS
|
|
|
|
if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->client->ps.legsAnim >= BOTH_PAIN1 ) && ( NPC->client->ps.legsAnim <= BOTH_PAIN18 ) && NPC->client->ps.legsAnimTimer > 0 ) )
|
|
{//If taking full body pain, don't move
|
|
return qtrue;
|
|
}
|
|
|
|
if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON )
|
|
{//If in an emplaced gun, never try to navigate!
|
|
return qtrue;
|
|
}
|
|
|
|
if( NPC->s.eFlags & EF_HELD_BY_RANCOR )
|
|
{//If in a rancor's hand, never try to navigate!
|
|
return qtrue;
|
|
}
|
|
if( NPC->s.eFlags & EF_HELD_BY_WAMPA )
|
|
{//If in a wampa's hand, never try to navigate!
|
|
return qtrue;
|
|
}
|
|
if( NPC->s.eFlags & EF_HELD_BY_SAND_CREATURE )
|
|
{//If in a worm's mouth, never try to navigate!
|
|
return qtrue;
|
|
}
|
|
|
|
if ( NPC->watertype & CONTENTS_LADDER )
|
|
{//Do we still want to do this?
|
|
vec3_t dir;
|
|
VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir );
|
|
VectorNormalize( dir );
|
|
NPC_LadderMove( dir );
|
|
}
|
|
|
|
|
|
bool moveSuccess = true;
|
|
STEER::Activate(NPC);
|
|
{
|
|
// Attempt To Steer Directly To Our Goal
|
|
//---------------------------------------
|
|
moveSuccess = STEER::GoTo(NPC, NPCInfo->goalEntity, NPCInfo->goalRadius);
|
|
|
|
// Perhaps Not Close Enough? Try To Use The Navigation Grid
|
|
//-----------------------------------------------------------
|
|
if (!moveSuccess)
|
|
{
|
|
moveSuccess = NAV::GoTo(NPC, NPCInfo->goalEntity);
|
|
if (!moveSuccess)
|
|
{
|
|
STEER::Stop(NPC);
|
|
}
|
|
}
|
|
}
|
|
STEER::DeActivate(NPC, &ucmd);
|
|
|
|
|
|
#if AI_TIMERS
|
|
navTime += GetTime( startTime );
|
|
#endif// AI_TIMERS
|
|
return moveSuccess;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
void NPC_SlideMoveToGoal( void )
|
|
|
|
Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func
|
|
-------------------------
|
|
*/
|
|
qboolean NPC_SlideMoveToGoal( void )
|
|
{
|
|
float saveYaw = NPC->client->ps.viewangles[YAW];
|
|
|
|
NPCInfo->combatMove = qtrue;
|
|
|
|
qboolean ret = NPC_MoveToGoal( qtrue );
|
|
|
|
NPCInfo->desiredYaw = saveYaw;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_ApplyRoff
|
|
-------------------------
|
|
*/
|
|
|
|
void NPC_ApplyRoff(void)
|
|
{
|
|
PlayerStateToEntityState( &NPC->client->ps, &NPC->s );
|
|
VectorCopy ( NPC->currentOrigin, NPC->lastOrigin );
|
|
|
|
// use the precise origin for linking
|
|
gi.linkentity(NPC);
|
|
}
|
|
|