Files
DoomRTX/neo/prey/Prey/physics_preyai.cpp
2026-05-05 15:01:01 -07:00

574 lines
15 KiB
C++

#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
// HUMANHEAD nla - Class constants
const int hhPhysics_AI::NO_FLY_DIRECTION = 1000;
CLASS_DECLARATION( idPhysics_Monster, hhPhysics_AI )
END_CLASS
/*
=====================
hhPhysics_AI::hhPhysics_AI
=====================
*/
hhPhysics_AI::hhPhysics_AI() {
// HUMANHEAD nla
flyStepDirection = NO_FLY_DIRECTION;
//HUMANHEAD END
lastMoveTouch = NULL;
useGravity = TRUE;
bGravClipModelAxis = false;
}
/*
=====================
hhPhysics_AI::SlideMove
=====================
// HUMANHEAD nla - if touched is NULL, the touched entities will be added
// directly. Otherwise they will be added to the list
*/
monsterMoveResult_t hhPhysics_AI::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta, idList<int> *touched ) {
trace_t tr;
idVec3 move;
int i;
blockingEntity = NULL;
move = delta;
for( i = 0; i < 3; i++ ) {
gameLocal.clip.Translation( tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self );
if ( tr.c.entityNum != ENTITYNUM_WORLD && tr.c.entityNum != ENTITYNUM_NONE && gameLocal.entities[ tr.c.entityNum ] )
lastMoveTouch = gameLocal.entities[ tr.c.entityNum ];
start = tr.endpos;
if ( tr.fraction == 1.0f ) {
if ( i > 0 ) {
return MM_SLIDING;
}
return MM_OK;
}
if ( tr.c.entityNum != ENTITYNUM_NONE ) {
blockingEntity = gameLocal.entities[ tr.c.entityNum ];
}
/*
// clip the movement delta and velocity
move.ProjectOntoPlane( tr.c.normal, OVERCLIP );
velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP );
*/
// HUMANHEAD nla - Added logic to allow monsters to push
// if we can push other entities and not blocked by the world. Added PushCosine
if ( self->Pushes() && ( tr.c.entityNum != ENTITYNUM_WORLD ) ) {
// Early out if we aren't facing enough towards the object
idVec3 normVelocity( velocity );
normVelocity.NormalizeFast();
/*
gameLocal.Printf( "%d Trying %s * %s = %.2f vs %.2f\n", gameLocal.time,
normVelocity.ToString(), tr.c.normal.ToString(),
idMath::Fabs( normVelocity * tr.c.normal ), self->PushCosine() );
*/
if ( idMath::Fabs( normVelocity * tr.c.normal ) >= self->PushCosine() ) {
trace_t trPush;
int pushFlags;
float totalMass;
clipModel->SetPosition( start, clipModel->GetAxis() );
// clip movement, only push idMoveables, don't push entities the player is standing on
// apply impact to pushed objects
pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES;
// clip & push
totalMass = gameLocal.push.ClipTranslationalPush( trPush, self, pushFlags, start + move, move );
if ( totalMass > 0.0f ) {
// decrease velocity based on the total mass of the objects being pushed ?
/*? Put back in later?
if ( velocity.LengthSqr() > Square( crouchSpeed * 0.8f ) ) {
velocity *= crouchSpeed * 0.8f / velocity.Length();
}
pushed = true;
*/
}
// Added logic to prevent pushing
tr = trPush;
start = tr.endpos;
// time_left -= time_left * trace.fraction;
// if moved the entire distance
// Changed from orig.
/*
if ( trace.fraction >= 1.0f ) {
break;
}
*/
// Changed to this! = )
if ( tr.fraction == 1.0f ) {
if ( i > 0 ) {
return MM_SLIDING;
}
return MM_OK;
}
}
}
// HUMANHEAD END
// clip the movement delta and velocity
move.ProjectOntoPlane( tr.c.normal, OVERCLIP );
velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP );
}
/* NOTE: Underlying code changed some, fit this back in if needed.
//HUMANHEAD nla
if (touched == NULL) {
if (tr.fraction < 1.0f) {
AddTouchEnt(tr.c.entityNum);
}
}
else {
touched->Append(tr.c.entityNum);
}
*/
return MM_BLOCKED;
}
// HUMANHEAD nla
/*
=====================
hhPhysics_AI::FlyMove
move start into the delta direction
the velocity is clipped conform any collisions
=====================
*/
#define NUM_DIRECTIONS 2
monsterMoveResult_t hhPhysics_AI::FlyMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) {
trace_t tr;
idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel, dirGrav;
monsterMoveResult_t result1, result2;
idVec3 originalStart, originalVelocity, bestStart, bestVelocity;
float noStepDistSq, distSq, bestDistSq;
int bestFlyStepDirection;
monsterMoveResult_t bestReturn;
idList<int> noStepEntities, stepEntities, bestEntities;
if ( delta == vec3_origin ) {
return MM_OK;
}
// Initialize
originalStart = start;
originalVelocity = velocity;
bestFlyStepDirection = NO_FLY_DIRECTION;
// try to move without stepping up
noStepPos = start;
noStepVel = velocity;
result1 = SlideMove( noStepPos, noStepVel, delta, &noStepEntities );
if ( result1 == MM_OK ) {
AddTouchEntList( noStepEntities );
start = noStepPos;
velocity = noStepVel;
return MM_OK;
}
// Assume no stepping is the best
bestStart = noStepPos;
bestVelocity = noStepVel;
bestReturn = result1;
bestEntities = noStepEntities;
// Add small fudge factor to take into account float issues
noStepDistSq = ((noStepPos - originalStart) * 1.001f).LengthSqr();
bestDistSq = noStepDistSq;
// Try to move around the obstacle
for ( int direction = 0; direction < NUM_DIRECTIONS ; direction++ ) {
stepEntities.Clear();
//! Maybe make this relative to the clip model?
if (direction == 0) { // Set the direction. Assumes 2x
dirGrav.Set(0, 0, -1);
}
else {
dirGrav *= -1;
}
// try to step "up"
up = start - dirGrav * maxStepHeight;
gameLocal.clip.Translation( tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self );
if ( tr.fraction == 0.0f ) {
start = originalStart; // Reset
velocity = originalVelocity;
continue;
}
// Add the entity touched
if ( tr.fraction < 1.0f ) {
stepEntities.Append( tr.c.entityNum );
}
// try to move at the stepped up position
stepPos = tr.endpos;
stepVel = velocity;
result2 = SlideMove( stepPos, stepVel, delta, &stepEntities );
if ( result2 == MM_BLOCKED ) { // Couldn't move all the way at stepped pos
start = originalStart; // Reset
velocity = originalVelocity;
distSq = (stepPos - tr.endpos).LengthSqr(); // See if we moved further up there. Ignore the step up
if ((distSq > bestDistSq)) {
bestStart = stepPos;
bestVelocity = stepVel;
bestReturn = result2;
bestEntities = stepEntities;
bestDistSq = distSq;
bestFlyStepDirection = direction;
}
continue;
}
// Stepping is the best move
start = originalStart; // Reset
velocity = originalVelocity;
distSq = ( stepPos - originalStart ).LengthSqr(); // Include the distance up we traveled
if ( ( direction == flyStepDirection ) || // Going the same direction as last time
( ( ( direction < flyStepDirection ) || // Haven't tested the last direction
( bestFlyStepDirection != flyStepDirection ) ) && // Have tested the last, but coundn't go that way
( distSq > bestDistSq ) ) // Closer than the best
) {
bestStart = stepPos; // Save the no step version
bestVelocity = stepVel;
bestReturn = MM_STEPPED;
bestEntities = stepEntities;
bestDistSq = distSq;
bestFlyStepDirection = direction;
}
} //. Direction loop
// Didn't work, use the no step return
start = bestStart;
velocity = bestVelocity;
flyStepDirection = bestFlyStepDirection;
AddTouchEntList( bestEntities );
return( bestReturn );
}
/*
================
hhPhysics_AI::Evaluate
================
*/
bool hhPhysics_AI::Evaluate( int timeStepMSec, int endTimeMSec ) {
idVec3 masterOrigin, oldOrigin;
idMat3 masterAxis;
float timeStep;
float oldMasterYaw, oldMasterDeltaYaw; // HUMANHEAD pdm
timeStep = MS2SEC( timeStepMSec );
moveResult = MM_OK;
blockingEntity = NULL;
oldOrigin = current.origin;
oldMasterYaw = masterYaw; // HUMANHEAD pdm
oldMasterDeltaYaw = masterDeltaYaw; // HUMANHEAD pdm
//HUMANHEAD: aob
HadGroundContacts( HasGroundContacts() );
//HUMANHEAD END
// if bound to a master
if ( masterEntity ) {
self->GetMasterPosition( masterOrigin, masterAxis );
current.origin = masterOrigin + current.localOrigin * masterAxis;
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
//HUMANHEAD rww
if (!timeStep) {
current.velocity = vec3_origin;
}
else {
//HUMANHEAD END
current.velocity = ( current.origin - oldOrigin ) / timeStep;
}
masterDeltaYaw = masterYaw;
masterYaw = masterAxis[0].ToYaw();
masterDeltaYaw = masterYaw - masterDeltaYaw;
// HUMANHEAD pdm: If we haven't moved and master is at rest, put me to rest
if (current.origin == oldOrigin && masterYaw == oldMasterYaw && masterDeltaYaw == oldMasterDeltaYaw) {
if (masterEntity->IsAtRest()) {
Rest();
}
return false;
}
// HUMANHEAD END
return true;
}
// if the monster is at rest
if ( current.atRest >= 0 ) {
return false;
}
assert(timeStep != 0.0f); //HUMANHEAD rww
ActivateContactEntities();
// move the monster velocity into the frame of a pusher
current.velocity -= current.pushVelocity;
clipModel->Unlink();
// check if on the ground
//HUMANHEAD: aob - moved ground check to after movement
// if not on the ground or moving upwards
float upspeed;
if ( gravityNormal != vec3_zero ) {
upspeed = -( current.velocity * gravityNormal );
} else {
upspeed = current.velocity.z;
}
if ( fly || self->fl.isTractored || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) {
if ( upspeed < 0.0f ) {
moveResult = MM_FALLING;
}
else {
current.onGround = false;
moveResult = MM_OK;
}
delta = current.velocity * timeStep;
if ( delta != vec3_origin ) {
//HUMANHEAD: aob - removed scope hardcode
moveResult = SlideMove( current.origin, current.velocity, delta );
delta.Zero();
}
if ( !fly && !self->fl.isTractored && IsGravityEnabled()) { // HUMANHEAD JRM: Gravity toggle
current.velocity += gravityVector * timeStep;
}
} else {
if ( useVelocityMove ) {
delta = current.velocity * timeStep;
} else {
current.velocity = delta / timeStep;
}
current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal;
if ( delta == vec3_origin ) {
Rest();
} else {
// try moving into the desired direction
//HUMANHEAD: aob
current.velocity = delta / timeStep;
//HUMANHEAD END
//HUMANHEAD: aob - removed scope hardcode
//moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta );
if( IsGravityEnabled() ) {
moveResult = StepMove( current.origin, current.velocity, delta );
} else {
moveResult = SlideMove( current.origin, current.velocity, delta );
}
delta.Zero();
}
}
//HUMANHEAD: aob - for changing gravity zones
idVec3 rotationCheckOrigin = GetOrigin() + GetAxis()[2] * GetBounds()[1].z;
IterativeRotateMove( GetAxis()[2], -GetGravityNormal(), GetOrigin(), rotationCheckOrigin, p_iterRotMoveNumIterations.GetInteger() );
//HUMANHEAD END
clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() );
// check if on the ground
//HUMANHEAD: aob - removed scope hardcode
CheckGround( current );
// get all the ground contacts
EvaluateContacts();
// move the monster velocity back into the world frame
current.velocity += current.pushVelocity;
current.pushVelocity.Zero();
if ( IsOutsideWorld() ) {
// HUMANHEAD pdm: Allow some things to go outside world without warning
if (!self->IsType(hhWraith::Type) && !self->IsType( hhTalon::Type ) ) {
gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) );
}
Rest();
}
return ( current.origin != oldOrigin );
}
/*
==================
hhPhysics_AI::ApplyFriction
Handles both ground friction and water friction
//HUMANHEAD: aob
==================
*/
idVec3 hhPhysics_AI::ApplyFriction( const idVec3& vel, const float deltaTime ) {
float speed = 0.0f;
float newSpeed = 0.0f;
float control = 0.0f;
float drop = 0.0f;
idVec3 velocity = vel;
speed = velocity.Length();
if( speed <= VECTOR_EPSILON ) {
return vec3_origin;
}
if( fly || self->fl.isTractored ) {
drop += speed * PM_FLYFRICTION * deltaTime;
}
else if( !current.onGround ) {
drop += speed * PM_AIRFRICTION * deltaTime;
}
else if( current.onGround ) {
if( !(GetGroundSurfaceFlags() & SURF_SLICK) ) {
drop += speed * PM_FRICTION * deltaTime;
}
}
// scale the velocity
newSpeed = speed - drop;
if( newSpeed < 0.0f ) {
newSpeed = 0.0f;
}
return vel * ( newSpeed / speed );
}
/*
================
hhPhysics_AI::AddForce
HUMANHEAD pdm: added so that springs will work on monsters
================
*/
void hhPhysics_AI::AddForce( const int id, const idVec3 &point, const idVec3 &force ) {
// HUMANHEAD pdm: so we can use forces on them
current.velocity += (force / GetMass()) * USERCMD_ONE_OVER_HZ;
Activate();
}
/*
================
hhPhysics_AI::SetLinearVelocity
================
*/
void hhPhysics_AI::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) {
current.velocity = newLinearVelocity;
Activate();
}
/*
================
hhPhysics_AI::GetLinearVelocity
================
*/
const idVec3& hhPhysics_AI::GetLinearVelocity( int id ) const {
return current.velocity;
}
/*
================
hhPhysics_AI::ApplyImpulse
================
*/
void hhPhysics_AI::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) {
// HUMANHEAD: aob - so we can use forces on them (crane)
current.velocity += impulse / GetMass();
// HUMANHEAD END
Activate();
}
/*
================
hhPhysics_AI::SetMaster
overridden to set localAxis
================
*/
void hhPhysics_AI::SetMaster( idEntity *master, const bool orientated ) {
idVec3 masterOrigin;
idMat3 masterAxis;
if ( master ) {
if ( !masterEntity ) {
// transform from world space to master space
self->GetMasterPosition( masterOrigin, masterAxis );
current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose();
masterEntity = master;
masterYaw = masterAxis[0].ToYaw();
idAI *entAI = static_cast<idAI*>(self);
if ( entAI ) {
idAngles angles( 0, entAI->spawnArgs.GetFloat( "angle" ), 0 );
localAxis = angles.ToMat3() * masterAxis.Transpose();
}
}
ClearContacts();
}
else {
if ( masterEntity ) {
masterEntity = NULL;
Activate();
}
}
}
/*
================
hhPhysics_AI::SetGravity
overridden to call SetClipModelAxis for asteroid gravity
================
*/
void hhPhysics_AI::SetGravity( const idVec3 &newGravity ) {
idPhysics_Monster::SetGravity( newGravity );
if ( bGravClipModelAxis ) {
SetClipModelAxis();
}
}
//================
//hhPhysics_AI::Save
//================
void hhPhysics_AI::Save( idSaveGame *savefile ) const {
savefile->WriteInt( flyStepDirection );
lastMoveTouch.Save( savefile );
savefile->WriteBool( useGravity );
savefile->WriteMat3( localAxis );
savefile->WriteBool( bGravClipModelAxis );
}
//================
//hhPhysics_AI::Restore
//================
void hhPhysics_AI::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( flyStepDirection );
lastMoveTouch.Restore( savefile );
savefile->ReadBool( useGravity );
savefile->ReadMat3( localAxis );
savefile->ReadBool( bGravClipModelAxis );
}