2000-03-16 11:25:00 +01:00
|
|
|
/* CDF 11/6/98 - AI support functions moved out of bh_pred. */
|
|
|
|
|
|
|
|
#include "3dc.h"
|
|
|
|
#include "inline.h"
|
|
|
|
#include "module.h"
|
|
|
|
#include "stratdef.h"
|
|
|
|
#include "gamedef.h"
|
|
|
|
#include "comp_shp.h"
|
|
|
|
#include "dynblock.h"
|
|
|
|
#include "dynamics.h"
|
|
|
|
#include "pfarlocs.h"
|
|
|
|
#include "pheromon.h"
|
|
|
|
#include "bh_types.h"
|
|
|
|
#include "pvisible.h"
|
|
|
|
#include "bh_far.h"
|
|
|
|
#include "bh_debri.h"
|
|
|
|
#include "bh_pred.h"
|
|
|
|
#include "bh_paq.h"
|
|
|
|
#include "bh_queen.h"
|
|
|
|
#include "bh_marin.h"
|
|
|
|
#include "bh_alien.h"
|
|
|
|
#include "bh_agun.h"
|
|
|
|
#include "lighting.h"
|
|
|
|
#include "bh_weap.h"
|
|
|
|
#include "weapons.h"
|
|
|
|
#include "psnd.h"
|
|
|
|
#include "equipmnt.h"
|
|
|
|
#include "los.h"
|
2001-07-28 01:21:46 +00:00
|
|
|
#include "ai_sight.h"
|
2000-03-16 11:25:00 +01:00
|
|
|
#include "targeting.h"
|
|
|
|
#include "dxlog.h"
|
2001-07-28 01:21:46 +00:00
|
|
|
#include "showcmds.h"
|
2000-03-16 11:25:00 +01:00
|
|
|
#include "huddefs.h"
|
|
|
|
|
|
|
|
#define UseLocalAssert Yes
|
|
|
|
#include "ourasert.h"
|
|
|
|
|
|
|
|
/* external global variables used in this file */
|
|
|
|
extern int ModuleArraySize;
|
|
|
|
extern char *ModuleCurrVisArray;
|
|
|
|
extern int NormalFrameTime;
|
|
|
|
extern SECTION_DATA* LOS_HModel_Section; /* Section of HModel hit */
|
|
|
|
extern void HandleWeaponImpact(VECTORCH *positionPtr, STRATEGYBLOCK *sbPtr, enum AMMO_ID AmmoID, VECTORCH *directionPtr, int multiple, SECTION_DATA *section_pointer);
|
|
|
|
extern SECTION * GetNamedHierarchyFromLibrary(const char * rif_name, const char * hier_name);
|
|
|
|
extern int GlobalFrameCounter;
|
|
|
|
extern int RouteFinder_CallsThisFrame;
|
|
|
|
extern DEATH_DATA Alien_Deaths[];
|
|
|
|
extern DEATH_DATA Marine_Deaths[];
|
|
|
|
extern DEATH_DATA Predator_Deaths[];
|
|
|
|
extern DEATH_DATA Xenoborg_Deaths[];
|
|
|
|
extern ATTACK_DATA Alien_Attacks[];
|
|
|
|
extern ATTACK_DATA Wristblade_Attacks[];
|
|
|
|
extern ATTACK_DATA PredStaff_Attacks[];
|
|
|
|
extern int SBIsEnvironment(STRATEGYBLOCK *sbPtr);
|
|
|
|
|
|
|
|
/* From HModel.c! */
|
|
|
|
extern void QNormalise(QUAT *q);
|
|
|
|
extern int IsAIModuleVisibleFromAIModule(AIMODULE *source,AIMODULE *target);
|
|
|
|
|
|
|
|
int IsMyPolyRidiculous(void);
|
|
|
|
int New_GetAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal);
|
|
|
|
int New_GetSecondAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal);
|
|
|
|
void InitialiseThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager);
|
|
|
|
void ClearThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager);
|
|
|
|
int ExecuteThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager);
|
|
|
|
int SimpleEdgeDetectionTest(STRATEGYBLOCK *sbPtr, COLLISIONREPORT *vcr);
|
|
|
|
|
|
|
|
void AlignVelocityToGravity(STRATEGYBLOCK *sbPtr,VECTORCH *velocity);
|
|
|
|
|
|
|
|
int PathArraySize;
|
|
|
|
PATHHEADER* PathArray;
|
|
|
|
/* Patrick 4/7/97 --------------------------------------------------
|
|
|
|
|
|
|
|
BEHAVIOUR SUPPORT FUNCTIONS THAT MAY BE USED BY ANY NPC
|
|
|
|
|
|
|
|
---------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
int CheckAdjacencyValidity(AIMODULE *target,AIMODULE *source,int alien) {
|
|
|
|
|
|
|
|
FARENTRYPOINT *thisEp = GetAIModuleEP(target, source);
|
|
|
|
if(thisEp) {
|
|
|
|
if ((!alien)&&(thisEp->alien_only)) {
|
|
|
|
return(0);
|
|
|
|
} else {
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Patrick-------------------------------------------------------------
|
|
|
|
For chris, or anyone else who wants it...
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
int NPC_IsDead(STRATEGYBLOCK *sbPtr)
|
|
|
|
{
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
|
|
|
|
if(sbPtr->SBflags.please_destroy_me==1) return 1;
|
|
|
|
|
|
|
|
switch(sbPtr->I_SBtype)
|
|
|
|
{
|
|
|
|
case I_BehaviourAlienPlayer:
|
|
|
|
case I_BehaviourPredatorPlayer:
|
|
|
|
case I_BehaviourMarinePlayer:
|
|
|
|
{
|
|
|
|
/* For now. */
|
|
|
|
if (PlayerStatusPtr->IsAlive) {
|
|
|
|
return(0);
|
|
|
|
} else {
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case(I_BehaviourPredator):
|
|
|
|
{
|
|
|
|
PREDATOR_STATUS_BLOCK *predatorStatusPointer;
|
|
|
|
predatorStatusPointer = (PREDATOR_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(predatorStatusPointer);
|
|
|
|
if(predatorStatusPointer->behaviourState==PBS_Dying) return 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case(I_BehaviourMarine):
|
|
|
|
case(I_BehaviourSeal):
|
|
|
|
{
|
|
|
|
MARINE_STATUS_BLOCK *marineStatusPointer;
|
|
|
|
marineStatusPointer = (MARINE_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(marineStatusPointer);
|
|
|
|
if(marineStatusPointer->behaviourState==MBS_Dying) return 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case(I_BehaviourAlien):
|
|
|
|
{
|
|
|
|
ALIEN_STATUS_BLOCK *alienStatusPointer;
|
|
|
|
alienStatusPointer = (ALIEN_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(alienStatusPointer);
|
|
|
|
if(alienStatusPointer->BehaviourState==ABS_Dying) return 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case(I_BehaviourPredatorAlien):
|
|
|
|
case(I_BehaviourQueenAlien):
|
|
|
|
{
|
|
|
|
PAQ_STATUS_BLOCK *paqStatusPointer;
|
|
|
|
paqStatusPointer = (PAQ_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(paqStatusPointer);
|
|
|
|
if(paqStatusPointer->NearBehaviourState==PAQNS_Dying) return 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case(I_BehaviourNetCorpse):
|
|
|
|
/* Corpses are always dead :-) */
|
|
|
|
return(1);
|
|
|
|
break;
|
|
|
|
case I_BehaviourAutoGun:
|
|
|
|
{
|
|
|
|
AUTOGUN_STATUS_BLOCK *agunStatusPointer;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
agunStatusPointer = (AUTOGUN_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(agunStatusPointer);
|
|
|
|
|
|
|
|
if (agunStatusPointer->behaviourState==I_disabled) {
|
|
|
|
return(1);
|
|
|
|
} else {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Patrick-------------------------------------------------------------
|
|
|
|
This set of 3 functions is used by all npcs in checking for obstructive
|
|
|
|
collisions, or targets we cannot reach
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
void NPC_InitMovementData(NPC_MOVEMENTDATA *moveData)
|
|
|
|
{
|
|
|
|
LOCALASSERT(moveData);
|
|
|
|
|
|
|
|
moveData->numObstructiveCollisions = 0;
|
|
|
|
|
|
|
|
moveData->avoidanceDirn.vx = 0;
|
|
|
|
moveData->avoidanceDirn.vy = 0;
|
|
|
|
moveData->avoidanceDirn.vz = 0;
|
|
|
|
|
|
|
|
moveData->lastTarget.vx = 0;
|
|
|
|
moveData->lastTarget.vy = 0;
|
|
|
|
moveData->lastTarget.vz = 0;
|
|
|
|
|
|
|
|
moveData->lastVelocity.vx = 0;
|
|
|
|
moveData->lastVelocity.vy = 0;
|
|
|
|
moveData->lastVelocity.vz = 0;
|
|
|
|
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
|
|
|
|
moveData->lastModule=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NPC_IsObstructed(STRATEGYBLOCK *sbPtr, NPC_MOVEMENTDATA *moveData, NPC_OBSTRUCTIONREPORT *details, STRATEGYBLOCK **destructableObject)
|
|
|
|
{
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
struct collisionreport *nextReport;
|
|
|
|
VECTORCH velDirn;
|
|
|
|
AVP_BEHAVIOUR_TYPE myType;
|
|
|
|
|
|
|
|
LOCALASSERT(destructableObject);
|
|
|
|
LOCALASSERT(details);
|
|
|
|
LOCALASSERT(moveData);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
nextReport = dynPtr->CollisionReportPtr;
|
|
|
|
|
|
|
|
/* init destructable object pointer, etc... */
|
|
|
|
*destructableObject = NULL;
|
|
|
|
details->environment = 0;
|
|
|
|
details->destructableObject = 0;
|
|
|
|
details->otherCharacter = 0;
|
|
|
|
details->anySingleObstruction = 0;
|
|
|
|
|
|
|
|
/* check our velocity: if we haven't got one, we can't be obstructed, so just return */
|
|
|
|
if((sbPtr->DynPtr->LinVelocity.vx==0)&&(sbPtr->DynPtr->LinVelocity.vy==0)&&(sbPtr->DynPtr->LinVelocity.vz==0))
|
|
|
|
{
|
|
|
|
moveData->numObstructiveCollisions = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get my velocity and behaviour type */
|
|
|
|
velDirn = dynPtr->LinVelocity;
|
|
|
|
Normalise(&velDirn);
|
|
|
|
myType = sbPtr->I_SBtype;
|
|
|
|
|
|
|
|
/* walk the collision report list, looking for collisions that obstruct our movement...
|
|
|
|
excluding objects of our own type and the player */
|
|
|
|
while(nextReport)
|
|
|
|
{
|
|
|
|
int IsCharacterOrPlayer = 0;
|
|
|
|
/* int dotWithGravity; */
|
|
|
|
int normalDotWithVelocity;
|
|
|
|
|
|
|
|
if(nextReport->ObstacleSBPtr)
|
|
|
|
{
|
|
|
|
if(nextReport->ObstacleSBPtr==Player->ObStrategyBlock) IsCharacterOrPlayer = 1;
|
|
|
|
if(nextReport->ObstacleSBPtr->I_SBtype==myType)
|
|
|
|
{
|
|
|
|
IsCharacterOrPlayer = 1;
|
|
|
|
details->otherCharacter = 1;
|
|
|
|
details->anySingleObstruction = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
VECTORCH normVelocity = sbPtr->DynPtr->LinVelocity;
|
|
|
|
Normalise(&normVelocity);
|
|
|
|
normalDotWithVelocity = DotProduct(&(nextReport->ObstacleNormal),&(normVelocity));
|
|
|
|
}
|
|
|
|
|
|
|
|
// if((normalDotWithVelocity < -46341)&&(!IsCharacterOrPlayer))
|
|
|
|
/* So aliens can break through windows, 19/5/98 CDF */
|
|
|
|
if((!IsCharacterOrPlayer)&&((normalDotWithVelocity < -46341)||(nextReport->ObstacleSBPtr)))
|
|
|
|
{
|
|
|
|
/* aha... got one....*/
|
|
|
|
moveData->numObstructiveCollisions++;
|
|
|
|
if(moveData->numObstructiveCollisions > NPC_IMPEDING_COL_THRESHOLD)
|
|
|
|
{
|
|
|
|
moveData->numObstructiveCollisions = 0;
|
|
|
|
details->anySingleObstruction = 1;
|
|
|
|
details->environment = 1;
|
|
|
|
|
|
|
|
if(nextReport->ObstacleSBPtr)
|
|
|
|
{
|
|
|
|
if(nextReport->ObstacleSBPtr->I_SBtype==I_BehaviourInanimateObject)
|
|
|
|
{
|
|
|
|
INANIMATEOBJECT_STATUSBLOCK* objectstatusptr = nextReport->ObstacleSBPtr->SBdataptr;
|
|
|
|
if((objectstatusptr)&&(objectstatusptr->Indestructable == 0))
|
|
|
|
{
|
|
|
|
/* aha: an object which the npc can destroy... */
|
|
|
|
*destructableObject = nextReport->ObstacleSBPtr;
|
|
|
|
details->destructableObject = 1;
|
|
|
|
details->environment = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* if we have an obstructive collision, then return at this point
|
|
|
|
to avoid resetting numObstructiveCollisions. NB we only want to
|
|
|
|
record one per frame anyway... */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nextReport = nextReport->NextCollisionReportPtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no obstructions this frame, then ... */
|
|
|
|
moveData->numObstructiveCollisions = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
int NPCIsExperiencingObstructiveCollision(STRATEGYBLOCK *sbPtr, VECTORCH *velocityDirection)
|
|
|
|
{
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
struct collisionreport *nextReport;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
nextReport = dynPtr->CollisionReportPtr;
|
|
|
|
|
|
|
|
/* check our velocity: if we haven't got one, we can't be obstructed, so just return */
|
|
|
|
if((velocityDirection->vx==0)&&(velocityDirection->vy==0)&&(velocityDirection->vz==0)) return 0;
|
|
|
|
|
|
|
|
/* walk the collision report list, looking for collisions against inanimate objects */
|
|
|
|
while(nextReport)
|
|
|
|
{
|
|
|
|
if(nextReport->ObstacleNormal.vy > -46341) return 1;
|
|
|
|
nextReport = nextReport->NextCollisionReportPtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no collision, then ... */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int NPC_CannotReachTarget(NPC_MOVEMENTDATA *moveData, VECTORCH* thisTarget, VECTORCH* thisVelocity)
|
|
|
|
{
|
|
|
|
LOCALASSERT(moveData);
|
|
|
|
LOCALASSERT(thisTarget);
|
|
|
|
LOCALASSERT(thisVelocity);
|
|
|
|
|
|
|
|
/* if movement data has zero velocity, update and return */
|
|
|
|
if((moveData->lastVelocity.vx == 0)&&
|
|
|
|
(moveData->lastVelocity.vy == 0)&&
|
|
|
|
(moveData->lastVelocity.vz == 0))
|
|
|
|
{
|
|
|
|
moveData->lastVelocity = *thisVelocity;
|
|
|
|
moveData->lastTarget = *thisTarget;
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if new velocity is zero, update and return */
|
|
|
|
if((thisVelocity->vx == 0)&&
|
|
|
|
(thisVelocity->vy == 0)&&
|
|
|
|
(thisVelocity->vz == 0))
|
|
|
|
{
|
|
|
|
moveData->lastVelocity = *thisVelocity;
|
|
|
|
moveData->lastTarget = *thisTarget;
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if move data target and this target are different, update and return*/
|
|
|
|
if((thisTarget->vx!=moveData->lastTarget.vx)||
|
|
|
|
(thisTarget->vy!=moveData->lastTarget.vy)||
|
|
|
|
(thisTarget->vz!=moveData->lastTarget.vz))
|
|
|
|
{
|
|
|
|
moveData->lastVelocity = *thisVelocity;
|
|
|
|
moveData->lastTarget = *thisTarget;
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* at this point we have a previous velocity, a new velocity,
|
|
|
|
and the previous target is the same as the current target...
|
|
|
|
so compare previous and new velocities... */
|
|
|
|
if(DotProduct(&(moveData->lastVelocity),thisVelocity)<(-56000)) /* 30 degrees */
|
|
|
|
{
|
|
|
|
moveData->lastVelocity = *thisVelocity;
|
|
|
|
moveData->lastTarget = *thisTarget;
|
|
|
|
moveData->numReverses++;
|
|
|
|
if(moveData->numReverses>1)
|
|
|
|
{
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
#if 1
|
|
|
|
return 1;
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* just update */
|
|
|
|
moveData->lastVelocity = *thisVelocity;
|
|
|
|
moveData->lastTarget = *thisTarget;
|
|
|
|
moveData->numReverses = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------------Patrick 14/2/97-----------------------------------
|
|
|
|
Returns direction of movement to avoid obstructive collision in current
|
|
|
|
direction of movement...
|
|
|
|
-------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* in this new version, the direction is taken from the npc's current direction (so
|
|
|
|
that it will work in 3d, for aliens)... and is returned in velocityDirection */
|
|
|
|
void NPCGetAvoidanceDirection(STRATEGYBLOCK *sbPtr, VECTORCH *velocityDirection, NPC_OBSTRUCTIONREPORT *details)
|
|
|
|
{
|
|
|
|
VECTORCH newDirection1;
|
|
|
|
VECTORCH newDirection2;
|
|
|
|
int dir1dist = 0;
|
|
|
|
int dir2dist = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(sbPtr->DynPtr);
|
|
|
|
LOCALASSERT(velocityDirection);
|
|
|
|
|
|
|
|
/* init velcity direction */
|
|
|
|
velocityDirection->vx = velocityDirection->vy = velocityDirection->vz = 0;
|
|
|
|
|
|
|
|
/* just in case */
|
|
|
|
if(!sbPtr->containingModule) return;
|
|
|
|
|
|
|
|
if((details->environment)||(details->destructableObject)||(details->otherCharacter)||(details->anySingleObstruction))
|
|
|
|
{
|
|
|
|
/* going for a 90 degree turn + back a bit */
|
|
|
|
|
|
|
|
/* construct the direction(s)...
|
|
|
|
start with object's local x unit vector (from local coo-ord system in world space) */
|
|
|
|
newDirection1.vx = sbPtr->DynPtr->OrientMat.mat11;
|
|
|
|
newDirection1.vy = sbPtr->DynPtr->OrientMat.mat12;
|
|
|
|
newDirection1.vz = sbPtr->DynPtr->OrientMat.mat13;
|
|
|
|
newDirection2.vx = -newDirection1.vx;
|
|
|
|
newDirection2.vy = -newDirection1.vy;
|
|
|
|
newDirection2.vz = -newDirection1.vz;
|
|
|
|
/* ...and add on 1/4 of the -z direction...*/
|
|
|
|
newDirection1.vx -= (sbPtr->DynPtr->OrientMat.mat31/4);
|
|
|
|
newDirection1.vy -= (sbPtr->DynPtr->OrientMat.mat32/4);
|
|
|
|
newDirection1.vz -= (sbPtr->DynPtr->OrientMat.mat33/4);
|
|
|
|
newDirection2.vx -= (sbPtr->DynPtr->OrientMat.mat31/4);
|
|
|
|
newDirection2.vy -= (sbPtr->DynPtr->OrientMat.mat32/4);
|
|
|
|
newDirection2.vz -= (sbPtr->DynPtr->OrientMat.mat33/4);
|
|
|
|
Normalise(&newDirection1);
|
|
|
|
Normalise(&newDirection2);
|
|
|
|
|
|
|
|
/* test how far we could go in each direction... */
|
|
|
|
{
|
|
|
|
VECTORCH startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
VECTORCH testDirn = newDirection1;
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir1dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir1dist = LOS_Lambda;
|
|
|
|
|
|
|
|
startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
testDirn = newDirection2;
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir2dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir2dist = LOS_Lambda;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(dir1dist > dir2dist) *velocityDirection = newDirection1;
|
|
|
|
else *velocityDirection = newDirection2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Project actual shot? */
|
|
|
|
void ProjectNPCShot(STRATEGYBLOCK *sbPtr, STRATEGYBLOCK *target, VECTORCH *muzzlepos, MATRIXCH *muzzleorient, enum AMMO_ID AmmoID, int multiple) {
|
|
|
|
|
|
|
|
VECTORCH shotVector; /* direction of view-line */
|
|
|
|
VECTORCH targetPos;
|
|
|
|
int mag;
|
|
|
|
|
|
|
|
GetTargetingPointOfObject_Far(target,&targetPos);
|
|
|
|
|
|
|
|
shotVector.vx = targetPos.vx - muzzlepos->vx;
|
|
|
|
shotVector.vy = targetPos.vy - muzzlepos->vy;
|
|
|
|
shotVector.vz = targetPos.vz - muzzlepos->vz;
|
|
|
|
|
|
|
|
mag=Approximate3dMagnitude(&shotVector);
|
|
|
|
|
|
|
|
shotVector.vx = MUL_FIXED(muzzleorient->mat31,mag);
|
|
|
|
shotVector.vy = MUL_FIXED(muzzleorient->mat32,mag);
|
|
|
|
shotVector.vz = MUL_FIXED(muzzleorient->mat33,mag);
|
|
|
|
|
|
|
|
|
|
|
|
mag>>=2; /* For now. */
|
|
|
|
|
|
|
|
/* Random tweak. */
|
|
|
|
shotVector.vx+=((FastRandom()%mag)-(mag>>1));
|
|
|
|
shotVector.vy+=((FastRandom()%mag)-(mag>>1));
|
|
|
|
shotVector.vz+=((FastRandom()%mag)-(mag>>1));
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
LOS_ObjectHitPtr = 0;
|
|
|
|
LOS_HModel_Section=NULL;
|
|
|
|
{
|
|
|
|
extern int NumActiveBlocks;
|
|
|
|
extern DISPLAYBLOCK* ActiveBlockList[];
|
|
|
|
int numberOfObjects = NumActiveBlocks;
|
|
|
|
|
|
|
|
while (numberOfObjects--)
|
|
|
|
{
|
|
|
|
DISPLAYBLOCK* objectPtr = ActiveBlockList[numberOfObjects];
|
|
|
|
VECTORCH alpha = *muzzlepos;
|
|
|
|
VECTORCH beta = shotVector;
|
|
|
|
GLOBALASSERT(objectPtr);
|
|
|
|
|
|
|
|
if ((objectPtr!=sbPtr->SBdptr)&&(objectPtr!=target->SBdptr)) {
|
|
|
|
/* Can't hit target or self. */
|
|
|
|
CheckForVectorIntersectionWith3dObject(objectPtr, &alpha, &beta,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
FindPolygonInLineOfSight_TwoIgnores(&shotVector,muzzlepos,0,sbPtr->SBdptr,target->SBdptr);
|
|
|
|
#endif
|
|
|
|
/* Now deal with LOS_ObjectHitPtr. */
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
if (LOS_HModel_Section) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock->SBdptr) {
|
|
|
|
GLOBALASSERT(LOS_ObjectHitPtr->ObStrategyBlock->SBdptr->HModelControlBlock==LOS_HModel_Section->my_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* this fn needs updating to take amount of damage into account etc. */
|
|
|
|
HandleWeaponImpact(&LOS_Point,LOS_ObjectHitPtr->ObStrategyBlock,AmmoID,&shotVector, multiple*ONE_FIXED, LOS_HModel_Section);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void CastLOSProjectile(STRATEGYBLOCK *sbPtr, VECTORCH *muzzlepos, VECTORCH *in_shotvector, enum AMMO_ID AmmoID, int multiple, int inaccurate) {
|
|
|
|
|
|
|
|
VECTORCH shotVector;
|
|
|
|
DISPLAYBLOCK *self;
|
|
|
|
|
|
|
|
shotVector=*in_shotvector;
|
|
|
|
|
|
|
|
if (sbPtr) {
|
|
|
|
self=sbPtr->SBdptr;
|
|
|
|
} else {
|
|
|
|
self=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
|
|
|
|
if (inaccurate) {
|
|
|
|
/* Random tweak. */
|
|
|
|
shotVector.vx+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
shotVector.vy+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
shotVector.vz+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
LOS_ObjectHitPtr = 0;
|
|
|
|
LOS_HModel_Section=NULL;
|
|
|
|
{
|
|
|
|
extern int NumActiveBlocks;
|
|
|
|
extern DISPLAYBLOCK* ActiveBlockList[];
|
|
|
|
int numberOfObjects = NumActiveBlocks;
|
|
|
|
|
|
|
|
while (numberOfObjects--)
|
|
|
|
{
|
|
|
|
DISPLAYBLOCK* objectPtr = ActiveBlockList[numberOfObjects];
|
|
|
|
VECTORCH alpha = *muzzlepos;
|
|
|
|
VECTORCH beta = shotVector;
|
|
|
|
GLOBALASSERT(objectPtr);
|
|
|
|
|
|
|
|
if (objectPtr!=self) {
|
|
|
|
/* Can't hit self. */
|
|
|
|
CheckForVectorIntersectionWith3dObject(objectPtr, &alpha, &beta,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
FindPolygonInLineOfSight(&shotVector,muzzlepos,0,self);
|
|
|
|
#endif
|
|
|
|
/* Now deal with LOS_ObjectHitPtr. */
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
if (LOS_HModel_Section) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock->SBdptr) {
|
|
|
|
GLOBALASSERT(LOS_ObjectHitPtr->ObStrategyBlock->SBdptr->HModelControlBlock==LOS_HModel_Section->my_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* this fn needs updating to take amount of damage into account etc. */
|
|
|
|
HandleWeaponImpact(&LOS_Point,LOS_ObjectHitPtr->ObStrategyBlock,AmmoID,&shotVector, multiple*ONE_FIXED, LOS_HModel_Section);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int VerifyHitShot(STRATEGYBLOCK *sbPtr, STRATEGYBLOCK *target, VECTORCH *muzzlepos, VECTORCH *in_shotvector, enum AMMO_ID AmmoID, int multiple, int maxrange) {
|
|
|
|
|
|
|
|
VECTORCH shotVector;
|
|
|
|
DISPLAYBLOCK *self,*target_dptr;
|
|
|
|
|
|
|
|
shotVector=*in_shotvector;
|
|
|
|
|
|
|
|
if (sbPtr) {
|
|
|
|
self=sbPtr->SBdptr;
|
|
|
|
} else {
|
|
|
|
self=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target) {
|
|
|
|
target_dptr=target->SBdptr;
|
|
|
|
} else {
|
|
|
|
target_dptr=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
|
|
|
|
FindPolygonInLineOfSight(&shotVector,muzzlepos,0,self);
|
|
|
|
|
|
|
|
/* Now deal with LOS_ObjectHitPtr. */
|
|
|
|
if (LOS_Lambda>maxrange) {
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
if (LOS_HModel_Section) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock->SBdptr) {
|
|
|
|
GLOBALASSERT(LOS_ObjectHitPtr->ObStrategyBlock->SBdptr->HModelControlBlock==LOS_HModel_Section->my_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ((LOS_ObjectHitPtr==target_dptr)&&(target_dptr!=NULL))||(SBIsEnvironment(LOS_ObjectHitPtr->ObStrategyBlock))) {
|
|
|
|
return(1);
|
|
|
|
} else {
|
|
|
|
HandleWeaponImpact(&LOS_Point,LOS_ObjectHitPtr->ObStrategyBlock,AmmoID,&shotVector, multiple*ONE_FIXED, LOS_HModel_Section);
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this function returns a target point for firing a projectile at the player*/
|
|
|
|
void NPCGetTargetPosition(VECTORCH *targetPoint, STRATEGYBLOCK *target)
|
|
|
|
{
|
|
|
|
GLOBALASSERT(target);
|
|
|
|
GLOBALASSERT(targetPoint);
|
|
|
|
|
|
|
|
if (target->SBdptr)
|
|
|
|
{
|
|
|
|
GetTargetingPointOfObject_Far(target, targetPoint);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*targetPoint = target->DynPtr->Position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------Patrick 31/1/97-----------------------------
|
|
|
|
Returns 2d approach velocity and time for given NPC, target, and speed
|
|
|
|
targetDirn must be a normalised vector direction.
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
int NPCSetVelocity(STRATEGYBLOCK *sbPtr, VECTORCH* targetDirn, int in_speed)
|
|
|
|
{
|
|
|
|
int orientated,speed;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(sbPtr->DynPtr);
|
|
|
|
LOCALASSERT(targetDirn);
|
|
|
|
|
|
|
|
/* set targetDirn.vy to 0 just in case: if NPCGetTargetDirn is used get the target
|
|
|
|
direction, the y component should always be 0, but other target directions may be
|
|
|
|
passed... */
|
|
|
|
/* Okay, that was old. But maybe we need to do that unless UseStandardGravity is unset. */
|
|
|
|
|
|
|
|
/* Set up speed as local, so we can tamper with it. */
|
|
|
|
speed=in_speed;
|
|
|
|
|
|
|
|
if ((sbPtr->I_SBtype!=I_BehaviourMarine)&&(sbPtr->I_SBtype!=I_BehaviourPredator)
|
|
|
|
&&(sbPtr->I_SBtype!=I_BehaviourXenoborg)) {
|
|
|
|
|
|
|
|
if (sbPtr->DynPtr->UseStandardGravity) {
|
|
|
|
targetDirn->vy = 0;
|
|
|
|
|
|
|
|
/* first check for zero direction vector */
|
|
|
|
if((targetDirn->vx==0)&&(targetDirn->vz==0))
|
|
|
|
{
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz = 0;
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, targetDirn,NPC_TURNRATE,NULL);
|
|
|
|
|
|
|
|
{
|
|
|
|
VECTORCH velocity;
|
|
|
|
VECTORCH yDirection;
|
|
|
|
int dotProduct;
|
|
|
|
|
|
|
|
yDirection.vx = sbPtr->DynPtr->OrientMat.mat21;
|
|
|
|
yDirection.vy = sbPtr->DynPtr->OrientMat.mat22;
|
|
|
|
yDirection.vz = sbPtr->DynPtr->OrientMat.mat23;
|
|
|
|
|
|
|
|
dotProduct = DotProduct(&yDirection,targetDirn);
|
|
|
|
|
|
|
|
velocity.vx = targetDirn->vx - MUL_FIXED(yDirection.vx,dotProduct);
|
|
|
|
velocity.vy = targetDirn->vy - MUL_FIXED(yDirection.vy,dotProduct);
|
|
|
|
velocity.vz = targetDirn->vz - MUL_FIXED(yDirection.vz,dotProduct);
|
|
|
|
|
|
|
|
if ( (velocity.vx==0) && (velocity.vy==0) && (velocity.vz==0) ) {
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz = 0;
|
|
|
|
return(orientated);
|
|
|
|
}
|
|
|
|
|
|
|
|
Normalise(&velocity);
|
|
|
|
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx = MUL_FIXED(velocity.vx,speed);
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy = MUL_FIXED(velocity.vy,speed);
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz = MUL_FIXED(velocity.vz,speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(orientated);
|
|
|
|
} else {
|
|
|
|
int accelerationThisFrame,deltaVMag,dotProduct;
|
|
|
|
VECTORCH deltaV,targetV,yDirection,movementOffset;
|
|
|
|
MOVEMENT_DATA *movementData;
|
|
|
|
|
|
|
|
/* Mode 2, for marines 'n' predators. And xenoborgs. */
|
|
|
|
|
|
|
|
/* I'll still believe in 'Speed'... but look up acceleration. */
|
|
|
|
switch (sbPtr->I_SBtype) {
|
|
|
|
case I_BehaviourPredator:
|
|
|
|
/* May need to change this based on state at some point? */
|
|
|
|
{
|
|
|
|
PREDATOR_STATUS_BLOCK *predatorStatusPointer;
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
predatorStatusPointer = (PREDATOR_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(predatorStatusPointer);
|
|
|
|
|
|
|
|
if ((predatorStatusPointer->behaviourState==PBS_Wandering)
|
|
|
|
||((predatorStatusPointer->behaviourState==PBS_Avoidance)&&(predatorStatusPointer->lastState==PBS_Wandering))) {
|
|
|
|
movementData=GetThisMovementData(MDI_Casual_Predator);
|
|
|
|
/* Fix reduced max speed. */
|
|
|
|
speed=movementData->maxSpeed;
|
|
|
|
} else {
|
|
|
|
movementData=GetThisMovementData(MDI_Predator);
|
|
|
|
}
|
|
|
|
accelerationThisFrame=movementData->acceleration;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case I_BehaviourMarine:
|
|
|
|
{
|
|
|
|
MARINE_STATUS_BLOCK *marineStatusPointer;
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
marineStatusPointer = (MARINE_STATUS_BLOCK *)(sbPtr->SBdataptr);
|
|
|
|
LOCALASSERT(marineStatusPointer);
|
|
|
|
|
|
|
|
accelerationThisFrame=marineStatusPointer->acceleration;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case I_BehaviourXenoborg:
|
|
|
|
movementData=GetThisMovementData(MDI_Xenoborg);
|
|
|
|
accelerationThisFrame=movementData->acceleration;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
GLOBALASSERT(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* I'll get that from outside in a minute, okay? */
|
|
|
|
/* Assume you're ground-based. */
|
|
|
|
targetDirn->vy = 0;
|
|
|
|
|
|
|
|
/* first check for zero direction vector */
|
|
|
|
if((targetDirn->vx==0)&&(targetDirn->vz==0))
|
|
|
|
{
|
|
|
|
/* For the moment, you can still stop on a dime. */
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz = 0;
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
accelerationThisFrame=MUL_FIXED(accelerationThisFrame,NormalFrameTime);
|
|
|
|
/* u+at, and all that, wot? */
|
|
|
|
|
|
|
|
yDirection.vx = sbPtr->DynPtr->OrientMat.mat21;
|
|
|
|
yDirection.vy = sbPtr->DynPtr->OrientMat.mat22;
|
|
|
|
yDirection.vz = sbPtr->DynPtr->OrientMat.mat23;
|
|
|
|
|
|
|
|
dotProduct = DotProduct(&yDirection,targetDirn);
|
|
|
|
|
|
|
|
targetV.vx = targetDirn->vx - MUL_FIXED(yDirection.vx,dotProduct);
|
|
|
|
targetV.vy = targetDirn->vy - MUL_FIXED(yDirection.vy,dotProduct);
|
|
|
|
targetV.vz = targetDirn->vz - MUL_FIXED(yDirection.vz,dotProduct);
|
|
|
|
|
|
|
|
if ( (targetV.vx==0) && (targetV.vy==0) && (targetV.vz==0) ) {
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy = 0;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz = 0;
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Normalise(&targetV);
|
|
|
|
|
|
|
|
targetV.vx=MUL_FIXED(targetV.vx,speed);
|
|
|
|
targetV.vy=MUL_FIXED(targetV.vy,speed);
|
|
|
|
targetV.vz=MUL_FIXED(targetV.vz,speed);
|
|
|
|
|
|
|
|
deltaV.vx=targetV.vx-sbPtr->DynPtr->LinVelocity.vx;
|
|
|
|
deltaV.vy=targetV.vy-sbPtr->DynPtr->LinVelocity.vy;
|
|
|
|
deltaV.vz=targetV.vz-sbPtr->DynPtr->LinVelocity.vz;
|
|
|
|
/* Now, deltaV is what we need to match. */
|
|
|
|
deltaVMag=Approximate3dMagnitude(&deltaV);
|
|
|
|
if (deltaVMag<=accelerationThisFrame) {
|
|
|
|
int dotP,magV;
|
|
|
|
VECTORCH normVelocity;
|
|
|
|
|
|
|
|
sbPtr->DynPtr->LinVelocity=targetV;
|
|
|
|
|
|
|
|
movementOffset.vx=sbPtr->DynPtr->Position.vx-sbPtr->DynPtr->PrevPosition.vx;
|
|
|
|
movementOffset.vy=sbPtr->DynPtr->Position.vy-sbPtr->DynPtr->PrevPosition.vy;
|
|
|
|
movementOffset.vz=sbPtr->DynPtr->Position.vz-sbPtr->DynPtr->PrevPosition.vz;
|
|
|
|
|
|
|
|
normVelocity=sbPtr->DynPtr->LinVelocity;
|
|
|
|
magV=Approximate3dMagnitude(&sbPtr->DynPtr->LinVelocity);
|
|
|
|
|
|
|
|
Normalise(&movementOffset);
|
|
|
|
Normalise(&normVelocity);
|
|
|
|
dotP=DotProduct(&normVelocity,&movementOffset);
|
|
|
|
|
|
|
|
/* To get a reasonable value out... */
|
|
|
|
if (magV<(speed>>2)) {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &targetV,NPC_TURNRATE,NULL);
|
|
|
|
} else if ((dotP>40000)||((movementOffset.vx==0)&&(movementOffset.vy==0)&&(movementOffset.vz==0))) {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &normVelocity,NPC_TURNRATE,NULL);
|
|
|
|
} else {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &movementOffset,NPC_TURNRATE,NULL);
|
|
|
|
}
|
|
|
|
return(orientated);
|
|
|
|
}
|
|
|
|
/* If we're here, we can't make the target velocity yet. */
|
|
|
|
Normalise(&deltaV);
|
|
|
|
deltaV.vx=MUL_FIXED(deltaV.vx,accelerationThisFrame);
|
|
|
|
deltaV.vy=MUL_FIXED(deltaV.vy,accelerationThisFrame);
|
|
|
|
deltaV.vz=MUL_FIXED(deltaV.vz,accelerationThisFrame);
|
|
|
|
|
|
|
|
sbPtr->DynPtr->LinVelocity.vx+=deltaV.vx;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vy+=deltaV.vy;
|
|
|
|
sbPtr->DynPtr->LinVelocity.vz+=deltaV.vz;
|
|
|
|
|
|
|
|
{
|
|
|
|
int dotP,magV;
|
|
|
|
VECTORCH normVelocity;
|
|
|
|
|
|
|
|
movementOffset.vx=sbPtr->DynPtr->Position.vx-sbPtr->DynPtr->PrevPosition.vx;
|
|
|
|
movementOffset.vy=sbPtr->DynPtr->Position.vy-sbPtr->DynPtr->PrevPosition.vy;
|
|
|
|
movementOffset.vz=sbPtr->DynPtr->Position.vz-sbPtr->DynPtr->PrevPosition.vz;
|
|
|
|
|
|
|
|
normVelocity=sbPtr->DynPtr->LinVelocity;
|
|
|
|
magV=Approximate3dMagnitude(&sbPtr->DynPtr->LinVelocity);
|
|
|
|
|
|
|
|
Normalise(&movementOffset);
|
|
|
|
Normalise(&normVelocity);
|
|
|
|
dotP=DotProduct(&normVelocity,&movementOffset);
|
|
|
|
|
|
|
|
/* To get a reasonable value out... */
|
|
|
|
if (magV<(speed>>2)) {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &targetV,NPC_TURNRATE,NULL);
|
|
|
|
} else if ((dotP>40000)||((movementOffset.vx==0)&&(movementOffset.vy==0)&&(movementOffset.vz==0))) {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &normVelocity,NPC_TURNRATE,NULL);
|
|
|
|
} else {
|
|
|
|
orientated=NPCOrientateToVector(sbPtr, &movementOffset,NPC_TURNRATE,NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return(orientated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------Patrick 3/2/97---------------------------------
|
|
|
|
Sets an NPC's orientation so that it's z axis aligns to a given vector
|
|
|
|
(modified from kevin's alien align to velocity routine)
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
int NPCOrientateToVector(STRATEGYBLOCK *sbPtr, VECTORCH *zAxisVector,int turnspeed, VECTORCH *offset)
|
|
|
|
{
|
|
|
|
extern int cosine[], sine[];
|
|
|
|
|
|
|
|
int maxTurnThisFrame;
|
|
|
|
int turnThisFrame;
|
|
|
|
VECTORCH localZAxisVector;
|
|
|
|
int localZVecEulerY;
|
|
|
|
MATRIXCH toLocal;
|
|
|
|
int orientatedOk = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(sbPtr->DynPtr);
|
|
|
|
LOCALASSERT(zAxisVector);
|
|
|
|
|
|
|
|
localZAxisVector = *zAxisVector;
|
|
|
|
|
|
|
|
/* zero vector: nothing to do */
|
|
|
|
if((localZAxisVector.vx==0)&&(localZAxisVector.vy==0)&&(localZAxisVector.vz==0)) return 1;
|
|
|
|
|
|
|
|
/* rotate world zAxisVector into local space */
|
|
|
|
toLocal = sbPtr->DynPtr->OrientMat;
|
|
|
|
|
|
|
|
TransposeMatrixCH(&toLocal);
|
|
|
|
RotateVector(&localZAxisVector, &toLocal);
|
|
|
|
Normalise(&localZAxisVector);
|
|
|
|
|
|
|
|
// maxTurnThisFrame = WideMulNarrowDiv(NormalFrameTime,4096,NPC_TURNRATE);
|
|
|
|
maxTurnThisFrame = MUL_FIXED(NormalFrameTime,turnspeed);
|
|
|
|
localZVecEulerY = ArcTan(localZAxisVector.vx, localZAxisVector.vz);
|
|
|
|
LOCALASSERT((localZVecEulerY>=0)&&(localZVecEulerY<=4096));
|
|
|
|
|
|
|
|
if(localZVecEulerY==0||localZVecEulerY==4096)
|
|
|
|
{
|
|
|
|
/* if euler-y is 0 we are already aligned: nothing to do */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(localZVecEulerY>2048)
|
|
|
|
{
|
|
|
|
if(localZVecEulerY>(4096-maxTurnThisFrame))
|
|
|
|
{
|
|
|
|
turnThisFrame = localZVecEulerY;
|
|
|
|
orientatedOk = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
turnThisFrame = (4096-maxTurnThisFrame);
|
|
|
|
orientatedOk = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(localZVecEulerY>maxTurnThisFrame)
|
|
|
|
{
|
|
|
|
turnThisFrame = maxTurnThisFrame;
|
|
|
|
orientatedOk = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
turnThisFrame = localZVecEulerY;
|
|
|
|
orientatedOk = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* now convert into a matrix & multiply existing orientation by it ... */
|
|
|
|
{
|
|
|
|
MATRIXCH mat;
|
|
|
|
int cos = GetCos(turnThisFrame);
|
|
|
|
int sin = GetSin(turnThisFrame);
|
|
|
|
mat.mat11 = cos;
|
|
|
|
mat.mat12 = 0;
|
|
|
|
mat.mat13 = -sin;
|
|
|
|
mat.mat21 = 0;
|
|
|
|
mat.mat22 = 65536;
|
|
|
|
mat.mat23 = 0;
|
|
|
|
mat.mat31 = sin;
|
|
|
|
mat.mat32 = 0;
|
|
|
|
mat.mat33 = cos;
|
|
|
|
|
|
|
|
// NOTE : It seems like OrientMat is not being set per frame which
|
|
|
|
// leads to inaccuracy build-up from the matrix multiplies. When this
|
|
|
|
// is fixed, the following PSXAccurateMatrixMultiply can be removed
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
VECTORCH new_offset,delta_offset;
|
|
|
|
//MATRIXCH reverse;
|
|
|
|
|
|
|
|
/* Code to attempt spinning on one foot. */
|
|
|
|
|
|
|
|
RotateAndCopyVector(offset,&new_offset,&mat);
|
|
|
|
|
|
|
|
//reverse=mat;
|
|
|
|
//TransposeMatrixCH(&reverse);
|
|
|
|
//
|
|
|
|
//RotateAndCopyVector(offset,&new_offset,&reverse);
|
|
|
|
|
|
|
|
delta_offset.vx=(offset->vx-new_offset.vx);
|
|
|
|
delta_offset.vy=(offset->vy-new_offset.vy);
|
|
|
|
delta_offset.vz=(offset->vz-new_offset.vz);
|
|
|
|
/* delta_offset is in local space? */
|
|
|
|
|
|
|
|
//RotateVector(&delta_offset,&sbPtr->DynPtr->OrientMat);
|
|
|
|
|
|
|
|
/* Now change position. Bear in mind that many calls overwrite this change. */
|
|
|
|
|
|
|
|
sbPtr->DynPtr->Displacement.vx=delta_offset.vx;
|
|
|
|
sbPtr->DynPtr->Displacement.vy=delta_offset.vy;
|
|
|
|
sbPtr->DynPtr->Displacement.vz=delta_offset.vz;
|
|
|
|
sbPtr->DynPtr->UseDisplacement=1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#if PSX
|
|
|
|
PSXAccurateMatrixMultiply(&sbPtr->DynPtr->OrientMat,&mat,&sbPtr->DynPtr->OrientMat);
|
|
|
|
#else
|
|
|
|
MatrixMultiply(&sbPtr->DynPtr->OrientMat,&mat,&sbPtr->DynPtr->OrientMat);
|
|
|
|
#endif
|
|
|
|
MatrixToEuler(&sbPtr->DynPtr->OrientMat, &sbPtr->DynPtr->OrientEuler);
|
|
|
|
}
|
|
|
|
|
|
|
|
return orientatedOk;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------Patrick 1/2/97-----------------------------
|
|
|
|
Tries to find an ep in an adjacent module which can be used as a
|
|
|
|
movement target for NPC.
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
int NPCFindTargetEP(STRATEGYBLOCK *sbPtr, VECTORCH *targetPosn, AIMODULE **targetModule, int alien)
|
|
|
|
{
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
FARENTRYPOINT *bestEp;
|
|
|
|
int bestSmell = 0;
|
|
|
|
int aTargetExists = 0;
|
|
|
|
AIMODULE *bestModule = (AIMODULE *)0;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(targetPosn);
|
|
|
|
LOCALASSERT(targetModule);
|
|
|
|
|
|
|
|
if(!(sbPtr->containingModule)) return 0; /* just in case */
|
|
|
|
AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs;
|
|
|
|
|
|
|
|
/* check if there is a module adjacency list */
|
|
|
|
if(!AdjModuleRefPtr) return 0;
|
|
|
|
|
|
|
|
/* go through each adjacent module */
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
AIMODULE *nextAdjModule = *AdjModuleRefPtr;
|
|
|
|
if (AIModuleIsVisible(nextAdjModule))
|
|
|
|
{
|
|
|
|
/* it is adjacent & visible ... */
|
|
|
|
FARENTRYPOINT *thisEp = GetAIModuleEP(nextAdjModule, sbPtr->containingModule->m_aimodule);
|
|
|
|
if(thisEp)
|
|
|
|
{
|
|
|
|
if (!((!alien)&(thisEp->alien_only))) {
|
|
|
|
/* ... and has an ep, so test it's pheromone level */
|
|
|
|
if(PherPl_ReadBuf[(nextAdjModule->m_index)] > bestSmell)
|
|
|
|
{
|
|
|
|
bestSmell = PherPl_ReadBuf[(nextAdjModule->m_index)];
|
|
|
|
bestEp = thisEp;
|
|
|
|
bestModule = nextAdjModule;
|
|
|
|
aTargetExists = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return the result, if there is one */
|
|
|
|
if(aTargetExists)
|
|
|
|
{
|
|
|
|
*targetPosn = bestEp->position;
|
|
|
|
*targetModule = bestModule;
|
|
|
|
targetPosn->vx += bestModule->m_world.vx;
|
|
|
|
targetPosn->vy += bestModule->m_world.vy;
|
|
|
|
targetPosn->vz += bestModule->m_world.vz;
|
|
|
|
LOCALASSERT(bestEp->donorIndex == sbPtr->containingModule->m_aimodule->m_index);
|
|
|
|
}
|
|
|
|
return aTargetExists;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*------------------------Patrick 11/2/97-----------------------------
|
|
|
|
If we're not in the same module as the player, find an ep for a
|
|
|
|
suitable target module, and return this. If cannot find an ep,
|
|
|
|
or if the player is in our module, return the player's position.
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
void NPCGetMovementTarget(STRATEGYBLOCK *sbPtr, STRATEGYBLOCK *target, VECTORCH *targetPosition,int *targetIsAirduct,int alien)
|
|
|
|
{
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(targetPosition);
|
|
|
|
LOCALASSERT(targetIsAirduct);
|
|
|
|
LOCALASSERT(target);
|
|
|
|
GLOBALASSERT(playerPherModule);
|
|
|
|
|
|
|
|
if (target==Player->ObStrategyBlock) {
|
|
|
|
|
|
|
|
if(sbPtr->containingModule->m_aimodule != playerPherModule->m_aimodule)
|
|
|
|
{
|
|
|
|
int epFound;
|
|
|
|
VECTORCH epPosn;
|
|
|
|
AIMODULE *epModule;
|
|
|
|
epFound = NPCFindTargetEP(sbPtr, &epPosn, &epModule,alien);
|
|
|
|
if(epFound)
|
|
|
|
{
|
|
|
|
*targetPosition = epPosn;
|
|
|
|
if((*epModule->m_module_ptrs)->m_flags & MODULEFLAG_AIRDUCT) *targetIsAirduct = 1;
|
|
|
|
else *targetIsAirduct = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* in same module as player, or can't find an entry point */
|
|
|
|
//*targetPosition = Player->ObWorld;
|
|
|
|
GetTargetingPointOfObject(Player, targetPosition);
|
|
|
|
*targetIsAirduct = 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* Improve this presently */
|
|
|
|
//*targetPosition = target->DynPtr->Position;
|
|
|
|
GetTargetingPointOfObject_Far(target, targetPosition);
|
|
|
|
*targetIsAirduct = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------------Patrick 24/3/97-----------------------------------
|
|
|
|
Calculates best direction of movement for NPC, from our main target direction:
|
|
|
|
1. If on same poly as player, move towards him/her;
|
|
|
|
2. Otherwise, find best adjacent floor polygon to move towards our target.
|
|
|
|
|
|
|
|
NB works in 2d...
|
|
|
|
-------------------------------------------------------------------------------*/
|
|
|
|
static int FindMyFloorPoly(VECTORCH* currentPosition, MODULE* currentModule);
|
|
|
|
static int CheckMyFloorPoly(VECTORCH* currentPosition, MODULE* currentModule);
|
|
|
|
static int VectorIntersects2dZVector(VECTORCH *vecStart,VECTORCH *vecEnd, int zExtent);
|
|
|
|
extern int SetupPolygonAccessFromShapeIndex(int shapeIndex);
|
|
|
|
extern int SetupPointAccessFromShapeIndex(int shapeIndex);
|
|
|
|
|
|
|
|
/* These globals are filled out by FindMyFloorPoly() */
|
|
|
|
static VECTORCH GMD_myPolyPoints[4];
|
|
|
|
static int GMD_myPolyNumPoints;
|
|
|
|
/* these Globals are used by NPCGetMovementDirection() */
|
|
|
|
VECTORCH myPolyEdgePoints[4];
|
|
|
|
VECTORCH myPolyEdgeDirections[4];
|
|
|
|
VECTORCH myPolyEdgeNormals[4];
|
|
|
|
VECTORCH myPolyMidPoint;
|
|
|
|
int myPolyEdgeMoveDistances[4];
|
|
|
|
extern int ShowNearSquad;
|
|
|
|
extern int ShowSquadState;
|
|
|
|
extern int ShowPredoStats;
|
|
|
|
|
|
|
|
/* a quick prototype */
|
|
|
|
static void NPCFindCurveToEdgePoint(STRATEGYBLOCK *sbPtr, int index, VECTORCH *velocityDirection);
|
|
|
|
|
|
|
|
void NPCGetMovementDirection(STRATEGYBLOCK *sbPtr, VECTORCH *velocityDirection, VECTORCH *targetPosition, WAYPOINT_MANAGER *waypointManager)
|
|
|
|
{
|
|
|
|
VECTORCH targetDirection;
|
|
|
|
int i;
|
|
|
|
int playerPoly = NPC_GMD_NOPOLY;
|
|
|
|
int ourPolyThisFrame = NPC_GMD_NOPOLY;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(sbPtr->DynPtr);
|
|
|
|
LOCALASSERT(velocityDirection);
|
|
|
|
LOCALASSERT(targetPosition);
|
|
|
|
|
|
|
|
if (sbPtr->containingModule==NULL) {
|
|
|
|
/* Oops. */
|
|
|
|
targetDirection = *targetPosition;
|
|
|
|
targetDirection.vx -= sbPtr->DynPtr->Position.vx;
|
|
|
|
targetDirection.vz -= sbPtr->DynPtr->Position.vz;
|
|
|
|
targetDirection.vy = 0;
|
|
|
|
Normalise(&targetDirection);
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
if ((sbPtr->containingModule->m_aimodule->m_waypoints!=NULL)&&(waypointManager)) {
|
|
|
|
if (NPCGetWaypointDirection(sbPtr->containingModule->m_aimodule->m_waypoints,sbPtr,velocityDirection,targetPosition,waypointManager)) {
|
|
|
|
/* Success! */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First get the (2d) direction of the main target */
|
|
|
|
targetDirection = *targetPosition;
|
|
|
|
targetDirection.vx -= sbPtr->DynPtr->Position.vx;
|
|
|
|
targetDirection.vz -= sbPtr->DynPtr->Position.vz;
|
|
|
|
/* If we got here, we *should* not be a crawling alien... */
|
|
|
|
if (AlienIsCrawling(sbPtr)) {
|
|
|
|
targetDirection.vy -= sbPtr->DynPtr->Position.vy;
|
|
|
|
} else {
|
|
|
|
/* Non-planar adjacency warnings? */
|
|
|
|
if ((ShowSquadState)||(ShowPredoStats)||(ShowNearSquad)) {
|
|
|
|
targetDirection.vy -= sbPtr->DynPtr->Position.vy;
|
|
|
|
Normalise(&targetDirection);
|
|
|
|
if (targetDirection.vy<-46000) {
|
|
|
|
PrintDebuggingText("Non-planar adjacency!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
targetDirection.vy = 0;
|
|
|
|
}
|
|
|
|
#if 1
|
|
|
|
Normalise(&targetDirection);
|
|
|
|
#else
|
|
|
|
AlignVelocityToGravity(sbPtr,&targetDirection);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* first- a hack to cope with stairs and al those little polygons:
|
|
|
|
if we're in stairs just return the target direction. This is okay aslong as we don't
|
|
|
|
have curved stairs*/
|
|
|
|
if(sbPtr->containingModule->m_flags&MODULEFLAG_STAIRS)
|
|
|
|
{
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get the player's poly index, and ours:
|
|
|
|
in the unlikely case that the player or npc doesnt have a current containingg module,
|
|
|
|
just return the target direction */
|
|
|
|
if(playerPherModule==NULL)
|
|
|
|
{
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
playerPoly = FindMyFloorPoly(&(Player->ObWorld), playerPherModule);
|
|
|
|
ourPolyThisFrame = FindMyFloorPoly(&(sbPtr->DynPtr->Position), sbPtr->containingModule);
|
|
|
|
|
|
|
|
/* Check for not having a current poly: this seems to happen occasionally
|
|
|
|
around module boundaries- npc just needs a jolt... */
|
|
|
|
if(ourPolyThisFrame == NPC_GMD_NOPOLY)
|
|
|
|
{
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* CDF 9/8/98 So we've got a poly, but is it stupid? */
|
|
|
|
if (IsMyPolyRidiculous()) {
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now check for player on our poly
|
|
|
|
NB only do this is we are not the player: as this function is used in player demo */
|
|
|
|
|
|
|
|
if((sbPtr != Player->ObStrategyBlock)&&(sbPtr->containingModule==playerPherModule)&&(playerPoly != NPC_GMD_NOPOLY))
|
|
|
|
{
|
|
|
|
if(playerPoly == ourPolyThisFrame)
|
|
|
|
{
|
|
|
|
/* cripes- we're on the same poly */
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test */
|
|
|
|
if(sbPtr == Player->ObStrategyBlock)
|
|
|
|
{
|
|
|
|
textprint("player poly %d \n",ourPolyThisFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now get all the data we need:
|
|
|
|
1. World space coords of poly edge mid points
|
|
|
|
2. Directions from npc to those points
|
|
|
|
3. Midpoint of our polygon.
|
|
|
|
3a Some error checking.
|
|
|
|
4. Distances we could move from poly midpoint to edge midpoint before hitting something
|
|
|
|
*/
|
|
|
|
for(i=0;i<GMD_myPolyNumPoints;i++)
|
|
|
|
{
|
|
|
|
int point1 = i;
|
|
|
|
int point2 = i+1;
|
|
|
|
if(point2 >= GMD_myPolyNumPoints) point2 = 0;
|
|
|
|
{
|
|
|
|
/* find the edge out normal - NB this won't work for clockwise polygons (2d) */
|
|
|
|
VECTORCH upNormal = {0,-65536,0};
|
|
|
|
VECTORCH edgeVector;
|
|
|
|
|
|
|
|
edgeVector.vx = GMD_myPolyPoints[point2].vx - GMD_myPolyPoints[point1].vx;
|
|
|
|
edgeVector.vy = 0;
|
|
|
|
edgeVector.vz = GMD_myPolyPoints[point2].vz - GMD_myPolyPoints[point1].vz;
|
|
|
|
|
|
|
|
CrossProduct(&edgeVector,&upNormal,&myPolyEdgeNormals[i]);
|
|
|
|
Normalise(&myPolyEdgeNormals[i]);
|
|
|
|
LOCALASSERT(myPolyEdgeNormals[i].vy == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 1 : Calculate edge midpoint (2d) */
|
|
|
|
myPolyEdgePoints[i].vx = ((GMD_myPolyPoints[point1].vx + GMD_myPolyPoints[point2].vx)/2)+MUL_FIXED(myPolyEdgeNormals[i].vx,50);
|
|
|
|
myPolyEdgePoints[i].vy = 0;
|
|
|
|
myPolyEdgePoints[i].vz = ((GMD_myPolyPoints[point1].vz + GMD_myPolyPoints[point2].vz)/2)+MUL_FIXED(myPolyEdgeNormals[i].vz,50);
|
|
|
|
/* Into world space*/
|
|
|
|
myPolyEdgePoints[i].vx += sbPtr->containingModule->m_world.vx;
|
|
|
|
myPolyEdgePoints[i].vz += sbPtr->containingModule->m_world.vz;
|
|
|
|
|
|
|
|
/* 2 : Directions to those points */
|
|
|
|
myPolyEdgeDirections[i].vx = myPolyEdgePoints[i].vx - sbPtr->DynPtr->Position.vx;
|
|
|
|
myPolyEdgeDirections[i].vy = 0;
|
|
|
|
myPolyEdgeDirections[i].vz = myPolyEdgePoints[i].vz - sbPtr->DynPtr->Position.vz;
|
|
|
|
Normalise(&myPolyEdgeDirections[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 3 : Poly midpoint- actually just an approximation, but doesn't matter
|
|
|
|
as long as its inside the poly (in world space):
|
|
|
|
NB
|
|
|
|
Quads are done by finding the midpoint of a diagonal.
|
|
|
|
Triangles bisect a side then take the midpoint of that and the third point, else
|
|
|
|
they can end up with a midpoint on one of their sides which buggers things up.*/
|
|
|
|
if(GMD_myPolyNumPoints==3)
|
|
|
|
{
|
|
|
|
VECTORCH bisect;
|
|
|
|
bisect.vy = ((GMD_myPolyPoints[1].vy + GMD_myPolyPoints[2].vy)/2);
|
|
|
|
bisect.vx = ((GMD_myPolyPoints[1].vx + GMD_myPolyPoints[2].vx)/2);
|
|
|
|
bisect.vz = ((GMD_myPolyPoints[1].vz + GMD_myPolyPoints[2].vz)/2);
|
|
|
|
myPolyMidPoint.vy = ((GMD_myPolyPoints[0].vy + bisect.vy)/2)+sbPtr->containingModule->m_world.vy;
|
|
|
|
myPolyMidPoint.vx = ((GMD_myPolyPoints[0].vx + bisect.vx)/2)+sbPtr->containingModule->m_world.vx;
|
|
|
|
myPolyMidPoint.vz = ((GMD_myPolyPoints[0].vz + bisect.vz)/2)+sbPtr->containingModule->m_world.vz;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
myPolyMidPoint.vy = ((GMD_myPolyPoints[0].vy + GMD_myPolyPoints[2].vy)/2)+sbPtr->containingModule->m_world.vy;
|
|
|
|
myPolyMidPoint.vx = ((GMD_myPolyPoints[0].vx + GMD_myPolyPoints[2].vx)/2)+sbPtr->containingModule->m_world.vx;
|
|
|
|
myPolyMidPoint.vz = ((GMD_myPolyPoints[0].vz + GMD_myPolyPoints[2].vz)/2)+sbPtr->containingModule->m_world.vz;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Error trapping:
|
|
|
|
1. if midpoint is not in our polygon, just move to target
|
|
|
|
2. If main target is in our poly, just move to target
|
|
|
|
3. If any edge points are in our poly, just move to the target also
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
int edgePoint[2];
|
|
|
|
int polyPoints[10];
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for(j=0;j<GMD_myPolyNumPoints;j++)
|
|
|
|
{
|
|
|
|
polyPoints[(j*2)] = GMD_myPolyPoints[j].vx;
|
|
|
|
polyPoints[(j*2)+1] = GMD_myPolyPoints[j].vz;
|
|
|
|
}
|
|
|
|
/* Edge points (in poly local space) */
|
|
|
|
for(j=0;j<GMD_myPolyNumPoints;j++)
|
|
|
|
{
|
|
|
|
edgePoint[0] = myPolyEdgePoints[j].vx - sbPtr->containingModule->m_world.vx;
|
|
|
|
edgePoint[1] = myPolyEdgePoints[j].vz - sbPtr->containingModule->m_world.vz;
|
|
|
|
|
|
|
|
if(PointInPolygon(&edgePoint[0],&polyPoints[0],GMD_myPolyNumPoints,2))
|
|
|
|
{
|
|
|
|
/* one of the edge points is inside our poly */
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Mid point (in poly local space) */
|
|
|
|
edgePoint[0] = myPolyMidPoint.vx - sbPtr->containingModule->m_world.vx;
|
|
|
|
edgePoint[1] = myPolyMidPoint.vz - sbPtr->containingModule->m_world.vz;
|
|
|
|
if(!(PointInPolygon(&edgePoint[0],&polyPoints[0],GMD_myPolyNumPoints,2)))
|
|
|
|
{
|
|
|
|
/* the midpoint is inside our poly */
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Target point (in poly local space)*/
|
|
|
|
edgePoint[0] = targetPosition->vx - sbPtr->containingModule->m_world.vx;
|
|
|
|
edgePoint[1] = targetPosition->vz - sbPtr->containingModule->m_world.vz;
|
|
|
|
if(PointInPolygon(&edgePoint[0],&polyPoints[0],GMD_myPolyNumPoints,2))
|
|
|
|
{
|
|
|
|
/* the main target point is inside our poly:- this shouldn't happen,
|
|
|
|
as we have already tested for the player earlier on */
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 4 : Finally, the distances that we can move from the poly midpoint beyond
|
|
|
|
each edge midpoint before we hit something...
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
for(i=0;i<GMD_myPolyNumPoints;i++)
|
|
|
|
{
|
|
|
|
VECTORCH centreToEdgeVector;
|
|
|
|
int centreToEdgeDistance;
|
|
|
|
|
|
|
|
centreToEdgeVector.vx = myPolyEdgePoints[i].vx - myPolyMidPoint.vx;
|
|
|
|
centreToEdgeVector.vz = myPolyEdgePoints[i].vz - myPolyMidPoint.vz;
|
|
|
|
centreToEdgeVector.vy = 0;
|
|
|
|
centreToEdgeDistance = Magnitude(¢reToEdgeVector);
|
|
|
|
|
|
|
|
/* how far we can move along the centre to edge direction */
|
|
|
|
{
|
|
|
|
VECTORCH startingPosition = myPolyMidPoint;
|
|
|
|
VECTORCH testDirn = centreToEdgeVector;
|
|
|
|
|
|
|
|
/* poly mid point is in 3d world space: we need to do this
|
|
|
|
test at the height of the npc*/
|
|
|
|
startingPosition.vy = myPolyMidPoint.vy - 1500;
|
|
|
|
/*startingPosition.vy = sbPtr->DynPtr->Position.vy;*/
|
|
|
|
/* Normalise the test direction */
|
|
|
|
Normalise(&testDirn);
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) myPolyEdgeMoveDistances[i] = NPC_MAX_VIEWRANGE;
|
|
|
|
else myPolyEdgeMoveDistances[i] = LOS_Lambda;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* how far we can move beyond the poly edge */
|
|
|
|
myPolyEdgeMoveDistances[i] -= centreToEdgeDistance;
|
|
|
|
|
|
|
|
/* quick check to eliminate Saturn type dodgy triangle points:
|
|
|
|
by setting move distance to zero, we should never select this edge as our
|
|
|
|
target edge... */
|
|
|
|
{
|
|
|
|
int point1 = i;
|
|
|
|
int point2 = i+1;
|
|
|
|
if(point2 >= GMD_myPolyNumPoints) point2 = 0;
|
|
|
|
if( (GMD_myPolyPoints[point1].vx == GMD_myPolyPoints[point2].vx) &&
|
|
|
|
(GMD_myPolyPoints[point1].vy == GMD_myPolyPoints[point2].vy) &&
|
|
|
|
(GMD_myPolyPoints[point1].vz == GMD_myPolyPoints[point2].vz))
|
|
|
|
{
|
|
|
|
#if (!Saturn)
|
|
|
|
LOCALASSERT(1==0);
|
|
|
|
#endif
|
|
|
|
myPolyEdgeMoveDistances[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now the crucial bit:- try to find an edge in our polygon that we can move towards
|
|
|
|
and which is intersected by the vector from the mid point to the main target. If
|
|
|
|
we find one, this is our edge! */
|
|
|
|
for(i=0;i<GMD_myPolyNumPoints;i++)
|
|
|
|
{
|
|
|
|
if(myPolyEdgeMoveDistances[i] > NPC_MIN_MOVEFROMPOLYDIST)
|
|
|
|
{
|
|
|
|
int vecExtent;
|
|
|
|
int ePoint1, ePoint2;
|
|
|
|
VECTORCH endPoint1, endPoint2;
|
|
|
|
VECTORCH edgeVector;
|
|
|
|
MATRIXCH edgeMatrix;
|
|
|
|
|
|
|
|
ePoint1 = i;
|
|
|
|
ePoint2 = i+1;
|
|
|
|
if(ePoint2>=GMD_myPolyNumPoints) ePoint2 = 0;
|
|
|
|
edgeVector.vx = GMD_myPolyPoints[ePoint2].vx - GMD_myPolyPoints[ePoint1].vx;
|
|
|
|
edgeVector.vy = 0;
|
|
|
|
edgeVector.vz = GMD_myPolyPoints[ePoint2].vz - GMD_myPolyPoints[ePoint1].vz;
|
|
|
|
|
|
|
|
Normalise(&edgeVector);
|
|
|
|
vecExtent = Magnitude(&edgeVector);
|
|
|
|
|
|
|
|
/* This IS the right way around */
|
|
|
|
edgeMatrix.mat11 = myPolyEdgeNormals[i].vx;
|
|
|
|
edgeMatrix.mat21 = 0;
|
|
|
|
edgeMatrix.mat31 = myPolyEdgeNormals[i].vz;
|
|
|
|
edgeMatrix.mat12 = 0;
|
|
|
|
edgeMatrix.mat22 = 65536;
|
|
|
|
edgeMatrix.mat32 = 0;
|
|
|
|
edgeMatrix.mat13 = edgeVector.vx;
|
|
|
|
edgeMatrix.mat23 = 0;
|
|
|
|
edgeMatrix.mat33 = edgeVector.vz;
|
|
|
|
|
|
|
|
/* set up the test vector */
|
|
|
|
endPoint1 = myPolyMidPoint;
|
|
|
|
endPoint1.vx = endPoint1.vx - sbPtr->containingModule->m_world.vx - GMD_myPolyPoints[ePoint1].vx;
|
|
|
|
endPoint1.vy = 0;
|
|
|
|
endPoint1.vz = endPoint1.vz - sbPtr->containingModule->m_world.vz - GMD_myPolyPoints[ePoint1].vz;
|
|
|
|
RotateVector(&endPoint1, &edgeMatrix);
|
|
|
|
|
|
|
|
endPoint2 = *targetPosition;
|
|
|
|
endPoint2.vx = endPoint2.vx - sbPtr->containingModule->m_world.vx - GMD_myPolyPoints[ePoint1].vx;
|
|
|
|
endPoint2.vy = 0;
|
|
|
|
endPoint2.vz = endPoint2.vz - sbPtr->containingModule->m_world.vz - GMD_myPolyPoints[ePoint1].vz;
|
|
|
|
RotateVector(&endPoint2, &edgeMatrix);
|
|
|
|
|
|
|
|
if(VectorIntersects2dZVector(&endPoint1,&endPoint2,vecExtent))
|
|
|
|
{
|
|
|
|
/* that'll do nicely */
|
|
|
|
|
|
|
|
/* test */
|
|
|
|
if(sbPtr == Player->ObStrategyBlock)
|
|
|
|
{
|
|
|
|
textprint("intersection test\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
*velocityDirection = myPolyEdgeDirections[i];
|
|
|
|
#else
|
|
|
|
NPCFindCurveToEdgePoint(sbPtr,i,velocityDirection);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* test */
|
|
|
|
if(sbPtr == Player->ObStrategyBlock)
|
|
|
|
{
|
|
|
|
textprint("nearest edge test\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Didn't find an intersection edge, so just pick the nearest
|
|
|
|
edge point that we can traverse */
|
|
|
|
{
|
|
|
|
int directionFound = 0;
|
|
|
|
VECTORCH bestDirection;
|
|
|
|
int closestDistance = 1000000; /* something very big */
|
|
|
|
|
|
|
|
for(i=0;i<GMD_myPolyNumPoints;i++)
|
|
|
|
{
|
|
|
|
if(myPolyEdgeMoveDistances[i] > NPC_MIN_MOVEFROMPOLYDIST)
|
|
|
|
{
|
|
|
|
int myDist = (VectorDistance(&myPolyEdgePoints[i],targetPosition));
|
|
|
|
if(myDist < closestDistance)
|
|
|
|
{
|
|
|
|
bestDirection = myPolyEdgeDirections[i];
|
|
|
|
closestDistance = myDist;
|
|
|
|
directionFound = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* return best direction, if we have one */
|
|
|
|
if(directionFound)
|
|
|
|
{
|
|
|
|
LOCALASSERT(bestDirection.vy == 0);
|
|
|
|
*velocityDirection = bestDirection;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We have utterly failed to find a suitable direction */
|
|
|
|
#if 0
|
|
|
|
velocityDirection->vx = velocityDirection->vy = velocityDirection->vz = 0;
|
|
|
|
#else
|
|
|
|
*velocityDirection = targetDirection;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Patrick 12/6/97: This function is an auxilary function to NPCGetMovementDirection(), and
|
|
|
|
adds a curve to the edge point direction, weighted towards the centre of the current poly */
|
|
|
|
static void NPCFindCurveToEdgePoint(STRATEGYBLOCK *sbPtr, int edgeIndex, VECTORCH *velocityDirection)
|
|
|
|
{
|
|
|
|
VECTORCH curvedPath;
|
|
|
|
VECTORCH dirnToCentre;
|
|
|
|
int weighting;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(velocityDirection);
|
|
|
|
|
|
|
|
/* default direction- just in case something goes wrong */
|
|
|
|
*velocityDirection = myPolyEdgeDirections[edgeIndex];
|
|
|
|
|
|
|
|
/* find dirn to centre of poly */
|
|
|
|
dirnToCentre.vx = myPolyMidPoint.vx - sbPtr->DynPtr->Position.vx;
|
|
|
|
dirnToCentre.vz = myPolyMidPoint.vz - sbPtr->DynPtr->Position.vz;
|
|
|
|
dirnToCentre.vy = 0;
|
|
|
|
if((dirnToCentre.vx==0)&&(dirnToCentre.vz==0)) return;
|
|
|
|
Normalise(&dirnToCentre);
|
|
|
|
|
|
|
|
/* calculated weighted vector to centre */
|
|
|
|
weighting = DotProduct(&myPolyEdgeDirections[edgeIndex],&dirnToCentre);
|
|
|
|
if(weighting==0) return;
|
|
|
|
|
|
|
|
dirnToCentre.vx = WideMulNarrowDiv(dirnToCentre.vx,weighting,ONE_FIXED);
|
|
|
|
dirnToCentre.vz = WideMulNarrowDiv(dirnToCentre.vz,weighting,ONE_FIXED);
|
|
|
|
|
|
|
|
/* add them to find curved direction path */
|
|
|
|
curvedPath.vx = myPolyEdgeDirections[edgeIndex].vx + dirnToCentre.vx;
|
|
|
|
curvedPath.vz = myPolyEdgeDirections[edgeIndex].vz + dirnToCentre.vz;
|
|
|
|
curvedPath.vy = 0;
|
|
|
|
Normalise(&curvedPath);
|
|
|
|
*velocityDirection = curvedPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*------------------------Patrick 24/3/97-----------------------------
|
|
|
|
This function is used to determine an intersection between 2 line
|
|
|
|
segments. The passed segment end points have been transformed into
|
|
|
|
a space where the second segment is aligned to the z axis, and extends
|
|
|
|
from 0 to the passed extent parameter. This makes the intersection
|
|
|
|
test easy...
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
static int VectorIntersects2dZVector(VECTORCH *vecStart,VECTORCH *vecEnd, int zExtent)
|
|
|
|
{
|
|
|
|
int vecIntercept;
|
|
|
|
LOCALASSERT(vecStart);
|
|
|
|
LOCALASSERT(vecEnd);
|
|
|
|
|
|
|
|
if((vecStart->vx < 0)&&(vecEnd->vx < 0)) return 0;
|
|
|
|
if((vecStart->vx > 0)&&(vecEnd->vx > 0)) return 0;
|
|
|
|
if((vecStart->vz < 0)&&(vecEnd->vz < 0)) return 0;
|
|
|
|
if((vecStart->vz > zExtent)&&(vecEnd->vz > zExtent)) return 0;
|
|
|
|
vecIntercept = vecStart->vz +
|
|
|
|
WideMulNarrowDiv((vecEnd->vz - vecStart->vz),vecStart->vx,(vecEnd->vx - vecStart->vx));
|
|
|
|
if((vecIntercept > 0)&&(vecIntercept < zExtent)) return 1; /* (deliberately ignoring endpoints) */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------Patrick 11/2/97-----------------------------
|
|
|
|
Tries to find a floor polygon for a given world space location in
|
|
|
|
a given module
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
int FindMyFloorPoly(VECTORCH* currentPosition, MODULE* currentModule)
|
|
|
|
{
|
|
|
|
struct ColPolyTag polygonData;
|
|
|
|
int positionPoints[2];
|
|
|
|
VECTORCH localPosition;
|
|
|
|
int numPolys;
|
|
|
|
int polyCounter;
|
|
|
|
int polyFound = 0;
|
|
|
|
int polyFoundIndex = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(currentPosition);
|
|
|
|
LOCALASSERT(currentModule);
|
|
|
|
|
|
|
|
/* first, get the local position */
|
|
|
|
localPosition.vx = currentPosition->vx - currentModule->m_world.vx;
|
|
|
|
localPosition.vy = currentPosition->vy - currentModule->m_world.vy;
|
|
|
|
localPosition.vz = currentPosition->vz - currentModule->m_world.vz;
|
|
|
|
LOCALASSERT(PointIsInModule(currentModule,&localPosition));
|
|
|
|
|
|
|
|
/* set up position points for object*/
|
|
|
|
positionPoints[0] = localPosition.vx;
|
|
|
|
positionPoints[1] = localPosition.vz;
|
|
|
|
|
|
|
|
numPolys = SetupPolygonAccessFromShapeIndex(currentModule->m_mapptr->MapShape);
|
|
|
|
polyCounter = numPolys;
|
|
|
|
|
|
|
|
/* loop through the item list, then ... */
|
|
|
|
while((polyCounter > 0) && (!polyFound))
|
|
|
|
{
|
|
|
|
AccessNextPolygon();
|
|
|
|
GetPolygonVertices(&polygonData);
|
|
|
|
GetPolygonNormal(&polygonData);
|
|
|
|
|
|
|
|
/* first of all, reject any that don't have an up normal */
|
|
|
|
if(polygonData.PolyNormal.vy < 0)
|
|
|
|
{
|
|
|
|
/* set up poly points for containment test */
|
|
|
|
int polyPoints[10];
|
|
|
|
int numPtsInPoly = polygonData.NumberOfVertices;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i=0;i<numPtsInPoly;i++)
|
|
|
|
{
|
|
|
|
polyPoints[(i*2)] = polygonData.PolyPoint[i].vx;
|
|
|
|
polyPoints[((i*2)+1)] = polygonData.PolyPoint[i].vz;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PointInPolygon(&positionPoints[0],&polyPoints[0],numPtsInPoly,2))
|
|
|
|
{
|
|
|
|
polyFound = 1;
|
|
|
|
polyFoundIndex = numPolys - polyCounter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
polyCounter--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Init some globals */
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
GMD_myPolyNumPoints = 0;
|
|
|
|
for(i=0;i<4;i++)
|
|
|
|
{
|
|
|
|
GMD_myPolyPoints[i].vx = GMD_myPolyPoints[i].vy = GMD_myPolyPoints[i].vz = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* if we haven't found a poly, return NPC_GMD_NOPOLY. Otherwise, return the index and fill
|
|
|
|
out some globals... */
|
|
|
|
if(!polyFound) return NPC_GMD_NOPOLY;
|
|
|
|
|
|
|
|
LOCALASSERT(polyFoundIndex >= 0);
|
|
|
|
LOCALASSERT(polyFound < numPolys);
|
|
|
|
|
|
|
|
GMD_myPolyNumPoints = polygonData.NumberOfVertices;
|
|
|
|
GMD_myPolyPoints[0] = polygonData.PolyPoint[0];
|
|
|
|
GMD_myPolyPoints[1] = polygonData.PolyPoint[1];
|
|
|
|
GMD_myPolyPoints[2] = polygonData.PolyPoint[2];
|
|
|
|
|
|
|
|
if(GMD_myPolyNumPoints > 3)
|
|
|
|
{
|
|
|
|
GMD_myPolyPoints[3] = polygonData.PolyPoint[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
return(polyFoundIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*------------------------Patrick 28/1/99-----------------------------
|
|
|
|
Tries to find a floor polygon for a given world space location in
|
|
|
|
a given module. Early exit if the location isn't even in the module...
|
|
|
|
--------------------------------------------------------------------*/
|
|
|
|
int CheckMyFloorPoly(VECTORCH* currentPosition, MODULE* currentModule)
|
|
|
|
{
|
|
|
|
struct ColPolyTag polygonData;
|
|
|
|
int positionPoints[2];
|
|
|
|
VECTORCH localPosition;
|
|
|
|
int numPolys;
|
|
|
|
int polyCounter;
|
|
|
|
int polyFound = 0;
|
|
|
|
int polyFoundIndex = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(currentPosition);
|
|
|
|
LOCALASSERT(currentModule);
|
|
|
|
|
|
|
|
/* first, get the local position */
|
|
|
|
localPosition.vx = currentPosition->vx - currentModule->m_world.vx;
|
|
|
|
localPosition.vy = currentPosition->vy - currentModule->m_world.vy;
|
|
|
|
localPosition.vz = currentPosition->vz - currentModule->m_world.vz;
|
|
|
|
if( !PointIsInModule(currentModule,&localPosition) )
|
|
|
|
{
|
|
|
|
// Whoops ! I'm looking in the wrong module. Better just forget it.
|
|
|
|
return NPC_GMD_NOPOLY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set up position points for object*/
|
|
|
|
positionPoints[0] = localPosition.vx;
|
|
|
|
positionPoints[1] = localPosition.vz;
|
|
|
|
|
|
|
|
numPolys = SetupPolygonAccessFromShapeIndex(currentModule->m_mapptr->MapShape);
|
|
|
|
polyCounter = numPolys;
|
|
|
|
|
|
|
|
/* loop through the item list, then ... */
|
|
|
|
while((polyCounter > 0) && (!polyFound))
|
|
|
|
{
|
|
|
|
AccessNextPolygon();
|
|
|
|
GetPolygonVertices(&polygonData);
|
|
|
|
GetPolygonNormal(&polygonData);
|
|
|
|
|
|
|
|
/* first of all, reject any that don't have an up normal */
|
|
|
|
if(polygonData.PolyNormal.vy < 0)
|
|
|
|
{
|
|
|
|
/* set up poly points for containment test */
|
|
|
|
int polyPoints[10];
|
|
|
|
int numPtsInPoly = polygonData.NumberOfVertices;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i=0;i<numPtsInPoly;i++)
|
|
|
|
{
|
|
|
|
polyPoints[(i*2)] = polygonData.PolyPoint[i].vx;
|
|
|
|
polyPoints[((i*2)+1)] = polygonData.PolyPoint[i].vz;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PointInPolygon(&positionPoints[0],&polyPoints[0],numPtsInPoly,2))
|
|
|
|
{
|
|
|
|
polyFound = 1;
|
|
|
|
polyFoundIndex = numPolys - polyCounter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
polyCounter--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Init some globals */
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
GMD_myPolyNumPoints = 0;
|
|
|
|
for(i=0;i<4;i++)
|
|
|
|
{
|
|
|
|
GMD_myPolyPoints[i].vx = GMD_myPolyPoints[i].vy = GMD_myPolyPoints[i].vz = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* if we haven't found a poly, return NPC_GMD_NOPOLY. Otherwise, return the index and fill
|
|
|
|
out some globals... */
|
|
|
|
if(!polyFound) return NPC_GMD_NOPOLY;
|
|
|
|
|
|
|
|
LOCALASSERT(polyFoundIndex >= 0);
|
|
|
|
LOCALASSERT(polyFound < numPolys);
|
|
|
|
|
|
|
|
GMD_myPolyNumPoints = polygonData.NumberOfVertices;
|
|
|
|
GMD_myPolyPoints[0] = polygonData.PolyPoint[0];
|
|
|
|
GMD_myPolyPoints[1] = polygonData.PolyPoint[1];
|
|
|
|
GMD_myPolyPoints[2] = polygonData.PolyPoint[2];
|
|
|
|
|
|
|
|
if(GMD_myPolyNumPoints > 3)
|
|
|
|
{
|
|
|
|
GMD_myPolyPoints[3] = polygonData.PolyPoint[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
return(polyFoundIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Patrick 23/8/97 -----------------------------------------------------
|
|
|
|
A couple of functions for wandering
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
void NPC_InitWanderData(NPC_WANDERDATA *wanderData)
|
|
|
|
{
|
|
|
|
LOCALASSERT(wanderData);
|
|
|
|
wanderData->currentModule = NPC_NOWANDERMODULE;
|
|
|
|
wanderData->worldPosition.vx = wanderData->worldPosition.vy = wanderData->worldPosition.vz = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Patrick: 26/8/97
|
|
|
|
Finding a suitable target module- look thro' all the visible non-airduct modules
|
|
|
|
connected to the npc's current module. pick one, and use it's ep as a
|
|
|
|
target.
|
|
|
|
We take a random adjacent ep as our target, but reject the most 'backward' one
|
|
|
|
(compared to our last velocity, as recorded in npc_movedata) as our last choice
|
|
|
|
*/
|
|
|
|
|
|
|
|
void NPC_FindAIWanderTarget(STRATEGYBLOCK *sbPtr, NPC_WANDERDATA *wanderData, NPC_MOVEMENTDATA *moveData, int alien)
|
|
|
|
{
|
|
|
|
AIMODULE* chosenModule = NULL;
|
|
|
|
VECTORCH chosenEpWorld;
|
|
|
|
int numFound = 0;
|
|
|
|
|
|
|
|
AIMODULE* worstModule = NULL;
|
|
|
|
VECTORCH worstEpWorld;
|
|
|
|
int worstEpDot;
|
|
|
|
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
VECTORCH lastVelocityDirection;
|
|
|
|
int gotLastVelocityDirection = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
LOCALASSERT(sbPtr->DynPtr);
|
|
|
|
LOCALASSERT(wanderData);
|
|
|
|
LOCALASSERT(moveData);
|
|
|
|
|
|
|
|
/* init the wander data block now, and we only have to fill in the correct
|
|
|
|
values if we get them... */
|
|
|
|
NPC_InitWanderData(wanderData);
|
|
|
|
|
|
|
|
/* do we have a current module? */
|
|
|
|
if(!(sbPtr->containingModule->m_aimodule)) return; /* no containing module */
|
|
|
|
|
|
|
|
AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs;
|
|
|
|
/* check if there is a module adjacency list */
|
|
|
|
if(!AdjModuleRefPtr) return;
|
|
|
|
|
|
|
|
/* try to get our last velocity direction */
|
|
|
|
if((moveData->lastVelocity.vx!=0)||(moveData->lastVelocity.vz!=0)||(moveData->lastVelocity.vy!=0))
|
|
|
|
{
|
|
|
|
lastVelocityDirection = moveData->lastVelocity;
|
|
|
|
Normalise(&lastVelocityDirection);
|
|
|
|
gotLastVelocityDirection = 1;
|
|
|
|
}
|
|
|
|
else gotLastVelocityDirection = 0;
|
|
|
|
|
|
|
|
/* if we've got a previous velocity, go through each adjacent module,
|
|
|
|
and try to find the worst one */
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
AIMODULE *nextAdjModule = *AdjModuleRefPtr;
|
|
|
|
if ((AIModuleIsVisible(nextAdjModule))&&
|
|
|
|
(((*(nextAdjModule->m_module_ptrs))->m_flags&MODULEFLAG_AIRDUCT)==0)
|
|
|
|
&&(nextAdjModule!=moveData->lastModule))
|
|
|
|
{
|
|
|
|
/* it is adjacent & visible & not an airduct:
|
|
|
|
try to find the ep position from this module... */
|
|
|
|
FARENTRYPOINT *thisEp = GetAIModuleEP(nextAdjModule, sbPtr->containingModule->m_aimodule);
|
|
|
|
if(thisEp)
|
|
|
|
{
|
|
|
|
if (!((!alien)&&(thisEp->alien_only))) {
|
|
|
|
/* aha. an ep!... */
|
|
|
|
VECTORCH thisEpWorld = thisEp->position;
|
|
|
|
|
|
|
|
thisEpWorld.vx += nextAdjModule->m_world.vx;
|
|
|
|
thisEpWorld.vy += nextAdjModule->m_world.vy;
|
|
|
|
thisEpWorld.vz += nextAdjModule->m_world.vz;
|
|
|
|
|
|
|
|
if(gotLastVelocityDirection)
|
|
|
|
{
|
|
|
|
VECTORCH thisEpDirection;
|
|
|
|
int thisEpDot;
|
|
|
|
|
|
|
|
thisEpDirection = thisEpWorld;
|
|
|
|
thisEpDirection.vx -= sbPtr->DynPtr->Position.vx;
|
|
|
|
thisEpDirection.vy -= sbPtr->DynPtr->Position.vy;
|
|
|
|
thisEpDirection.vz -= sbPtr->DynPtr->Position.vz;
|
|
|
|
Normalise(&thisEpDirection);
|
|
|
|
thisEpDot = DotProduct(&thisEpDirection,&lastVelocityDirection);
|
|
|
|
|
|
|
|
if(!worstModule)
|
|
|
|
{
|
|
|
|
worstModule = nextAdjModule;
|
|
|
|
worstEpWorld = thisEpWorld;
|
|
|
|
worstEpDot = thisEpDot;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
numFound++;
|
|
|
|
if(thisEpDot<worstEpDot)
|
|
|
|
{
|
|
|
|
/* worse than our worst, so meld current worst with current,
|
|
|
|
and set new worst */
|
|
|
|
if(FastRandom()%numFound==0)
|
|
|
|
{
|
|
|
|
/* take this one */
|
|
|
|
chosenModule = worstModule;
|
|
|
|
chosenEpWorld = worstEpWorld;
|
|
|
|
}
|
|
|
|
worstModule = nextAdjModule;
|
|
|
|
worstEpWorld = thisEpWorld;
|
|
|
|
worstEpDot = thisEpDot;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* better than our worst... */
|
|
|
|
if(FastRandom()%numFound==0)
|
|
|
|
{
|
|
|
|
/* take this one */
|
|
|
|
chosenModule = nextAdjModule;
|
|
|
|
chosenEpWorld = thisEpWorld;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* don't bother with worst... */
|
|
|
|
numFound++;
|
|
|
|
if(FastRandom()%numFound==0)
|
|
|
|
{
|
|
|
|
/* take this one */
|
|
|
|
chosenModule = nextAdjModule;
|
|
|
|
chosenEpWorld = thisEpWorld;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(chosenModule)
|
|
|
|
{
|
|
|
|
LOCALASSERT(numFound>=1);
|
|
|
|
wanderData->currentModule = sbPtr->containingModule->m_aimodule->m_index;
|
|
|
|
wanderData->worldPosition = chosenEpWorld;
|
|
|
|
}
|
|
|
|
else if(worstModule)
|
|
|
|
{
|
|
|
|
wanderData->currentModule = sbPtr->containingModule->m_aimodule->m_index;
|
|
|
|
wanderData->worldPosition = worstEpWorld;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define NEARLINK_QUEUE_LENGTH 100
|
|
|
|
|
|
|
|
typedef struct nl_route_queue {
|
|
|
|
int depth;
|
|
|
|
AIMODULE *aimodule;
|
|
|
|
AIMODULE *first_step;
|
|
|
|
} NL_ROUTE_QUEUE;
|
|
|
|
|
|
|
|
NL_ROUTE_QUEUE NearLink_Route_Queue[NEARLINK_QUEUE_LENGTH];
|
|
|
|
|
|
|
|
int NL_Queue_End,NL_Queue_Exec;
|
|
|
|
|
|
|
|
AIMODULE *GetNextModuleForLink(AIMODULE *source,AIMODULE *target,int max_depth,int alien) {
|
|
|
|
|
|
|
|
return(GetNextModuleForLink_Core(source,target,max_depth,0,alien));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
AIMODULE *GetNextModuleForLink_Core(AIMODULE *source,AIMODULE *target,int max_depth,int visibility_check,int alien) {
|
|
|
|
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
|
|
|
|
/* Recursively search AIModule tree, trying to connect source and target. *
|
|
|
|
* Return NULL on failure. */
|
|
|
|
|
|
|
|
if (source==target) {
|
|
|
|
return(source);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear the start. */
|
|
|
|
|
|
|
|
NearLink_Route_Queue[0].depth=0;
|
|
|
|
NearLink_Route_Queue[0].aimodule=source;
|
|
|
|
NearLink_Route_Queue[0].first_step=NULL;
|
|
|
|
NearLink_Route_Queue[1].aimodule=NULL; /* To set a standard. */
|
|
|
|
|
|
|
|
NL_Queue_End=1;
|
|
|
|
NL_Queue_Exec=0;
|
|
|
|
|
|
|
|
RouteFinder_CallsThisFrame++;
|
|
|
|
|
|
|
|
while (NearLink_Route_Queue[NL_Queue_Exec].aimodule!=NULL) {
|
|
|
|
|
|
|
|
AIMODULE *thisModule;
|
|
|
|
|
|
|
|
thisModule=NearLink_Route_Queue[NL_Queue_Exec].aimodule;
|
|
|
|
|
|
|
|
AdjModuleRefPtr = thisModule->m_link_ptrs;
|
|
|
|
|
|
|
|
if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */
|
|
|
|
{
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
/* Probably want some validity test for the link. */
|
|
|
|
if ((AIModuleIsPhysical(*AdjModuleRefPtr))
|
|
|
|
&&(AIModuleAdmitsPheromones(*AdjModuleRefPtr))
|
|
|
|
&&(CheckAdjacencyValidity((*AdjModuleRefPtr),thisModule,alien))
|
|
|
|
&&((visibility_check==0)||(IsAIModuleVisibleFromAIModule(source,
|
|
|
|
(*AdjModuleRefPtr)))
|
|
|
|
)) {
|
|
|
|
/* Is this the target? */
|
|
|
|
if ( (*AdjModuleRefPtr)==target) {
|
|
|
|
/* Yes!!! */
|
|
|
|
if (NearLink_Route_Queue[NL_Queue_Exec].first_step) {
|
|
|
|
return(NearLink_Route_Queue[NL_Queue_Exec].first_step);
|
|
|
|
} else {
|
|
|
|
/* Must be the next one. */
|
|
|
|
return(target);
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
(NearLink_Route_Queue[NL_Queue_Exec].depth<max_depth)
|
|
|
|
&&( /* Test for 'used this time round' */
|
|
|
|
((*AdjModuleRefPtr)->RouteFinder_FrameStamp!=GlobalFrameCounter)
|
|
|
|
||((*AdjModuleRefPtr)->RouteFinder_IterationNumber!=RouteFinder_CallsThisFrame)
|
|
|
|
)) {
|
|
|
|
/* Add to queue. */
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=(*AdjModuleRefPtr);
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].depth=NearLink_Route_Queue[NL_Queue_Exec].depth+1;
|
|
|
|
/* Remember first step. */
|
|
|
|
if (NearLink_Route_Queue[NL_Queue_Exec].first_step==NULL) {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=(*AdjModuleRefPtr);
|
|
|
|
} else {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=NearLink_Route_Queue[NL_Queue_Exec].first_step;
|
|
|
|
}
|
|
|
|
/* Stamp as used. */
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_FrameStamp=GlobalFrameCounter;
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_IterationNumber=RouteFinder_CallsThisFrame;
|
|
|
|
NL_Queue_End++;
|
|
|
|
if (NL_Queue_End>=NEARLINK_QUEUE_LENGTH) {
|
|
|
|
NL_Queue_End=0;
|
|
|
|
textprint("Wrapping Nearlink Queue!\n");
|
|
|
|
}
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=NULL;
|
|
|
|
if (NL_Queue_End==NL_Queue_Exec) {
|
|
|
|
LOGDXFMT(("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth = %d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth));
|
|
|
|
LOCALASSERT(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* next adjacent module reference pointer */
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Done all the links. */
|
|
|
|
|
|
|
|
NL_Queue_Exec++;
|
|
|
|
if (NL_Queue_Exec>=NEARLINK_QUEUE_LENGTH) NL_Queue_Exec=0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Still here? Must have hit the end, then. */
|
|
|
|
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int GetNextModuleInPath(int current_module, int path) {
|
|
|
|
|
|
|
|
GLOBALASSERT(path>=0 && path<PathArraySize);
|
|
|
|
GLOBALASSERT(PathArray);
|
|
|
|
GLOBALASSERT(PathArray[path].path_length);
|
|
|
|
|
|
|
|
return (current_module+1)%PathArray[path].path_length;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
AIMODULE *TranslatePathIndex(int current_module, int path) {
|
|
|
|
|
|
|
|
GLOBALASSERT(path>=0 && path<PathArraySize);
|
|
|
|
GLOBALASSERT(PathArray);
|
|
|
|
GLOBALASSERT(current_module<PathArray[path].path_length);
|
|
|
|
|
|
|
|
return (PathArray[path].modules_in_path[current_module]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetClosestStepInPath(int path,MODULE* current_module)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
PATHHEADER* path_head;
|
|
|
|
GLOBALASSERT(path>=0 && path<PathArraySize);
|
|
|
|
GLOBALASSERT(PathArray);
|
|
|
|
if(!current_module) return 0;
|
|
|
|
|
|
|
|
path_head=&PathArray[path];
|
|
|
|
GLOBALASSERT(path_head->path_length);
|
|
|
|
|
|
|
|
//see if enemy is currently in any of the modules in the path
|
|
|
|
{
|
|
|
|
AIMODULE* current_aimodule=current_module->m_aimodule;
|
|
|
|
for(i=0;i<path_head->path_length;i++)
|
|
|
|
{
|
|
|
|
if(current_aimodule==path_head->modules_in_path[i])
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//enemy not on path , so try to find the closest module on the path
|
|
|
|
{
|
|
|
|
int closest_distance=0x7fffffff;
|
|
|
|
int closest_point=0;
|
|
|
|
VECTORCH* current_pos=¤t_module->m_world;
|
|
|
|
VECTORCH diff;
|
|
|
|
|
|
|
|
for(i=0;i<path_head->path_length;i++)
|
|
|
|
{
|
|
|
|
int distance;
|
|
|
|
diff=path_head->modules_in_path[i]->m_world;
|
|
|
|
SubVector(current_pos,&diff);
|
|
|
|
distance=Approximate3dMagnitude(&diff);
|
|
|
|
|
|
|
|
if(distance<closest_distance)
|
|
|
|
{
|
|
|
|
closest_distance=distance;
|
|
|
|
closest_point=i;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return closest_point;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Death Shell */
|
|
|
|
|
|
|
|
int CheckDeathValidity(HMODELCONTROLLER *controller,SECTION *TemplateRoot,DEATH_DATA *ThisDeath,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) {
|
|
|
|
|
|
|
|
/* Check against many things. Harshly. */
|
|
|
|
|
|
|
|
/* Crouching test. */
|
|
|
|
if (crouching) {
|
|
|
|
if (ThisDeath->Crouching==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ThisDeath->Crouching!=0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Burning test. */
|
|
|
|
if (burning) {
|
|
|
|
if (ThisDeath->Burning==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ThisDeath->Burning!=0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Electrical test. */
|
|
|
|
if (electrical) {
|
|
|
|
if (ThisDeath->Electrical==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ThisDeath->Electrical!=0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hurtiness. */
|
|
|
|
switch(hurtiness) {
|
|
|
|
case 0:
|
|
|
|
default:
|
|
|
|
/* Pain case. */
|
|
|
|
if (ThisDeath->MinorBoom) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
if (ThisDeath->MajorBoom) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
/* Minor Boom. */
|
|
|
|
if (!ThisDeath->MinorBoom) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
/* Major Boom. */
|
|
|
|
if (!ThisDeath->MajorBoom) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Facing. Complex one... */
|
|
|
|
/* Input facing must contain at least all the flags in the death. */
|
|
|
|
if (facing) {
|
|
|
|
if (ThisDeath->Facing.Front) {
|
|
|
|
if (facing->Front==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ThisDeath->Facing.Back) {
|
|
|
|
if (facing->Back==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ThisDeath->Facing.Left) {
|
|
|
|
if (facing->Left==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ThisDeath->Facing.Right) {
|
|
|
|
if (facing->Right==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( (ThisDeath->Facing.Front)||(ThisDeath->Facing.Back)||(ThisDeath->Facing.Left)||(ThisDeath->Facing.Right) ) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Wound flags. Also quite odd. */
|
|
|
|
/* If wound_flags are specified in the death, the input must contain them. */
|
|
|
|
if (ThisDeath->wound_flags) {
|
|
|
|
if ((ThisDeath->wound_flags&wound_flags)!=ThisDeath->wound_flags) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Priority wound flags. As above, but backwards. */
|
|
|
|
/* If the input has priority wounds, the death must contain them. */
|
|
|
|
if (priority_wounds) {
|
|
|
|
if ((ThisDeath->priority_wounds&priority_wounds)!=priority_wounds) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Finally, sequence validity. */
|
|
|
|
if (ThisDeath->Template) {
|
|
|
|
if (!HModelSequence_Exists_FromRoot(TemplateRoot,ThisDeath->Sequence_Type,ThisDeath->Sub_Sequence)) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!HModelSequence_Exists(controller,ThisDeath->Sequence_Type,ThisDeath->Sub_Sequence)) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It got through! */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int CountValidDeaths(HMODELCONTROLLER *controller,SECTION *TemplateRoot,DEATH_DATA *FirstDeath,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) {
|
|
|
|
|
|
|
|
DEATH_DATA *this_death;
|
|
|
|
int number_of_candidates;
|
|
|
|
|
|
|
|
number_of_candidates=0;
|
|
|
|
this_death=FirstDeath;
|
|
|
|
|
|
|
|
while (this_death->Sequence_Type>=0) {
|
|
|
|
if (CheckDeathValidity(controller,TemplateRoot,this_death,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical)) {
|
|
|
|
number_of_candidates++;
|
|
|
|
}
|
|
|
|
this_death++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(number_of_candidates);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetThisDeath(HMODELCONTROLLER *controller,SECTION *TemplateRoot,DEATH_DATA *FirstDeath,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical, int index) {
|
|
|
|
|
|
|
|
/* Extract 'index' from the valid deaths. */
|
|
|
|
DEATH_DATA *retval;
|
|
|
|
DEATH_DATA *this_death;
|
|
|
|
int number;
|
|
|
|
|
|
|
|
retval=NULL;
|
|
|
|
|
|
|
|
number=0;
|
|
|
|
this_death=FirstDeath;
|
|
|
|
|
|
|
|
while (this_death->Sequence_Type>=0) {
|
|
|
|
if (CheckDeathValidity(controller,TemplateRoot,this_death,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical)) {
|
|
|
|
if (number==index) {
|
|
|
|
retval=this_death;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
number++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this_death++;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLOBALASSERT(retval);
|
|
|
|
return(retval);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetThisDeath_FromCode(HMODELCONTROLLER *controller,DEATH_DATA *FirstDeath,int code) {
|
|
|
|
|
|
|
|
/* Extract 'code' from the valid deaths. */
|
|
|
|
DEATH_DATA *retval;
|
|
|
|
DEATH_DATA *this_death;
|
|
|
|
|
|
|
|
retval=NULL;
|
|
|
|
|
|
|
|
this_death=FirstDeath;
|
|
|
|
|
|
|
|
while (this_death->Sequence_Type>=0) {
|
|
|
|
if (this_death->Multiplayer_Code==code) {
|
|
|
|
retval=this_death;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this_death++;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLOBALASSERT(retval);
|
|
|
|
return(retval);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetThisDeath_FromUniqueCode(int code) {
|
|
|
|
|
|
|
|
extern DEATH_DATA Alien_Deaths[];
|
|
|
|
extern DEATH_DATA Marine_Deaths[];
|
|
|
|
extern DEATH_DATA Predator_Special_SelfDestruct_Death;
|
|
|
|
extern DEATH_DATA Predator_Deaths[];
|
|
|
|
extern DEATH_DATA Xenoborg_Deaths[];
|
|
|
|
|
|
|
|
DEATH_DATA* this_death = NULL;
|
|
|
|
|
|
|
|
switch (code>>16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
this_death = &Alien_Deaths[0];
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
this_death = &Marine_Deaths[0];
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
return &Predator_Special_SelfDestruct_Death;
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
this_death = &Predator_Deaths[0];
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
this_death = &Xenoborg_Deaths[0];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
while (this_death->Sequence_Type>=0) {
|
|
|
|
if (this_death->Unique_Code==code) {
|
|
|
|
return this_death;
|
|
|
|
}
|
|
|
|
this_death++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEATH_DATA *GetDeathSequence(HMODELCONTROLLER *controller,SECTION *TemplateRoot,DEATH_DATA *FirstDeath,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching,int electrical) {
|
|
|
|
|
|
|
|
int number_of_candidates;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
int use_wound_flags;
|
|
|
|
int use_priority_wounds;
|
|
|
|
int use_hurtiness;
|
|
|
|
HIT_FACING *use_facing;
|
|
|
|
int use_burning;
|
|
|
|
int use_crouching;
|
|
|
|
int use_electrical;
|
|
|
|
|
|
|
|
use_priority_wounds=priority_wounds;
|
|
|
|
use_wound_flags=wound_flags;
|
|
|
|
use_hurtiness=hurtiness;
|
|
|
|
use_facing=facing;
|
|
|
|
use_burning=burning;
|
|
|
|
use_crouching=crouching;
|
|
|
|
use_electrical=electrical;
|
|
|
|
|
|
|
|
number_of_candidates=0;
|
|
|
|
|
|
|
|
while (number_of_candidates==0) {
|
|
|
|
/* Iterate, making simplifications, until there are valid deaths. */
|
|
|
|
number_of_candidates=CountValidDeaths(controller,TemplateRoot,FirstDeath,use_wound_flags,use_priority_wounds,use_hurtiness,use_facing,use_burning,use_crouching,use_electrical);
|
|
|
|
if (number_of_candidates==0) {
|
|
|
|
/* Right. Make a change. Priority wounds first. */
|
|
|
|
if (use_priority_wounds) {
|
|
|
|
use_priority_wounds=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Wound flags next. */
|
|
|
|
if (use_wound_flags!=0xffffffff) {
|
|
|
|
use_wound_flags=0xffffffff;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Now facing. */
|
|
|
|
if (use_facing) {
|
|
|
|
use_facing=NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Now hurtiness. */
|
|
|
|
if (use_hurtiness) {
|
|
|
|
use_hurtiness--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Now electrical. */
|
|
|
|
if (use_electrical) {
|
|
|
|
use_electrical=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Finally, burning. */
|
|
|
|
if (use_burning) {
|
|
|
|
use_burning=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Only crouch is left! */
|
|
|
|
//NewOnScreenMessage("DEATH SELECTION FAILURE!\n");
|
|
|
|
if (use_crouching) {
|
|
|
|
use_crouching=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//NewOnScreenMessage("I REALLY MEAN IT!\n");
|
|
|
|
/* Here goes nothing. */
|
|
|
|
return(FirstDeath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Right, by now we should have a number of candidates. */
|
|
|
|
GLOBALASSERT(number_of_candidates);
|
|
|
|
|
|
|
|
index=FastRandom()%number_of_candidates;
|
|
|
|
|
|
|
|
return(GetThisDeath(controller,TemplateRoot,FirstDeath,use_wound_flags,use_priority_wounds,
|
|
|
|
use_hurtiness,use_facing,use_burning,use_crouching,use_electrical,index));
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetAlienDeathSequence(HMODELCONTROLLER *controller,SECTION *TemplateRoot,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) {
|
|
|
|
|
|
|
|
return(GetDeathSequence(controller,TemplateRoot,Alien_Deaths,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetMarineDeathSequence(HMODELCONTROLLER *controller,SECTION *TemplateRoot,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) {
|
|
|
|
|
|
|
|
return(GetDeathSequence(controller,TemplateRoot,Marine_Deaths,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetPredatorDeathSequence(HMODELCONTROLLER *controller,SECTION *TemplateRoot,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching,int electrical) {
|
|
|
|
|
|
|
|
return(GetDeathSequence(controller,TemplateRoot,Predator_Deaths,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DEATH_DATA *GetXenoborgDeathSequence(HMODELCONTROLLER *controller,SECTION *TemplateRoot,int wound_flags,int priority_wounds,
|
|
|
|
int hurtiness,HIT_FACING *facing,int burning,int crouching,int electrical) {
|
|
|
|
|
|
|
|
return(GetDeathSequence(controller,TemplateRoot,Xenoborg_Deaths,wound_flags,priority_wounds,hurtiness,facing,burning,crouching,electrical));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attack Shell */
|
|
|
|
|
|
|
|
int CheckAttackValidity(HMODELCONTROLLER *controller,ATTACK_DATA *ThisAttack,int wound_flags,
|
|
|
|
int crouching,int pouncing) {
|
|
|
|
|
|
|
|
/* Check against many things. Harshly. */
|
|
|
|
|
|
|
|
/* Crouching test. */
|
|
|
|
if (crouching) {
|
|
|
|
if (ThisAttack->Crouching==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ThisAttack->Crouching!=0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Pouncing test. */
|
|
|
|
if (pouncing) {
|
|
|
|
if (ThisAttack->Pouncing==0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ThisAttack->Pouncing!=0) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wound flags. Quite odd, and different to deaths. */
|
|
|
|
/* If wound_flags are specified in the death, the input must NOT contain them. */
|
|
|
|
if (ThisAttack->wound_flags) {
|
|
|
|
if (ThisAttack->wound_flags&wound_flags) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Finally, sequence validity. */
|
|
|
|
if (!HModelSequence_Exists(controller,ThisAttack->Sequence_Type,ThisAttack->Sub_Sequence)) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It got through! */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int CountValidAttacks(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags,
|
|
|
|
int crouching, int pouncing) {
|
|
|
|
|
|
|
|
ATTACK_DATA *this_attack;
|
|
|
|
int number_of_candidates;
|
|
|
|
|
|
|
|
number_of_candidates=0;
|
|
|
|
this_attack=FirstAttack;
|
|
|
|
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (CheckAttackValidity(controller,this_attack,wound_flags,crouching,pouncing)) {
|
|
|
|
number_of_candidates++;
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(number_of_candidates);
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetThisAttack(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags,
|
|
|
|
int crouching, int pouncing, int index) {
|
|
|
|
|
|
|
|
/* Extract 'index' from the valid attacks. */
|
|
|
|
ATTACK_DATA *retval;
|
|
|
|
ATTACK_DATA *this_attack;
|
|
|
|
int number;
|
|
|
|
|
|
|
|
retval=NULL;
|
|
|
|
|
|
|
|
number=0;
|
|
|
|
this_attack=FirstAttack;
|
|
|
|
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (CheckAttackValidity(controller,this_attack,wound_flags,crouching,pouncing)) {
|
|
|
|
if (number==index) {
|
|
|
|
retval=this_attack;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
number++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLOBALASSERT(retval);
|
|
|
|
return(retval);
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetThisAttack_FromUniqueCode(int code)
|
|
|
|
{
|
|
|
|
extern ATTACK_DATA Alien_Special_Gripping_Attack;
|
|
|
|
//search for an attack using a code that should be unique across all attacks
|
|
|
|
//(used for loading)
|
|
|
|
|
|
|
|
ATTACK_DATA *this_attack;
|
|
|
|
if(code<0) return NULL;
|
|
|
|
|
|
|
|
|
|
|
|
///try the alien attacks
|
|
|
|
this_attack = &Alien_Attacks[0];
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (this_attack->Unique_Code==code) {
|
|
|
|
return this_attack;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//try the wristblade attacks
|
|
|
|
this_attack = &Wristblade_Attacks[0];
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (this_attack->Unique_Code==code) {
|
|
|
|
return this_attack;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//try the staff attacks
|
|
|
|
this_attack = &PredStaff_Attacks[0];
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (this_attack->Unique_Code==code) {
|
|
|
|
return this_attack;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//try gripping attack
|
|
|
|
if(Alien_Special_Gripping_Attack.Unique_Code==code)
|
|
|
|
{
|
|
|
|
return &Alien_Special_Gripping_Attack;
|
|
|
|
}
|
|
|
|
|
|
|
|
//no such attack
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ATTACK_DATA *GetThisAttack_FromCode(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int code) {
|
|
|
|
|
|
|
|
/* Extract 'code' from the valid attacks. */
|
|
|
|
ATTACK_DATA *retval;
|
|
|
|
ATTACK_DATA *this_attack;
|
|
|
|
|
|
|
|
retval=NULL;
|
|
|
|
|
|
|
|
this_attack=FirstAttack;
|
|
|
|
|
|
|
|
while (this_attack->Sequence_Type>=0) {
|
|
|
|
if (this_attack->Multiplayer_Code==code) {
|
|
|
|
retval=this_attack;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this_attack++;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLOBALASSERT(retval);
|
|
|
|
return(retval);
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetAttackSequence(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags,int crouching, int pouncing) {
|
|
|
|
|
|
|
|
int number_of_candidates;
|
|
|
|
int index;
|
|
|
|
|
|
|
|
int use_wound_flags;
|
|
|
|
int use_crouching;
|
|
|
|
|
|
|
|
use_wound_flags=wound_flags;
|
|
|
|
use_crouching=crouching;
|
|
|
|
|
|
|
|
number_of_candidates=0;
|
|
|
|
|
|
|
|
while (number_of_candidates==0) {
|
|
|
|
/* Iterate, making simplifications, until there are valid deaths. */
|
|
|
|
number_of_candidates=CountValidAttacks(controller,FirstAttack,use_wound_flags,use_crouching,pouncing);
|
|
|
|
if (number_of_candidates==0) {
|
|
|
|
/* Wound flags first. */
|
|
|
|
if (use_wound_flags!=0) {
|
|
|
|
use_wound_flags=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Only crouch is left! */
|
|
|
|
if (use_crouching) {
|
|
|
|
use_crouching=0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Now, pounce is absolutely inviolate. */
|
|
|
|
if (pouncing) {
|
|
|
|
/* If we're looking for a pounce, and there is none, return NULL. */
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
//NewOnScreenMessage("ATTACK SELECTION FAILURE!\n");
|
|
|
|
/* Here goes nothing. */
|
|
|
|
return(FirstAttack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Right, by now we should have a number of candidates. */
|
|
|
|
GLOBALASSERT(number_of_candidates);
|
|
|
|
|
|
|
|
index=FastRandom()%number_of_candidates;
|
|
|
|
|
|
|
|
return(GetThisAttack(controller,FirstAttack,use_wound_flags,
|
|
|
|
use_crouching,pouncing,index));
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetAlienAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) {
|
|
|
|
|
|
|
|
return(GetAttackSequence(controller,Alien_Attacks,wound_flags,crouching,0));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetAlienPounceAttack(HMODELCONTROLLER *controller,int wound_flags,int crouching) {
|
|
|
|
|
|
|
|
return(GetAttackSequence(controller,Alien_Attacks,wound_flags,crouching,1));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetWristbladeAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) {
|
|
|
|
|
|
|
|
return(GetAttackSequence(controller,Wristblade_Attacks,wound_flags,crouching,0));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ATTACK_DATA *GetPredStaffAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) {
|
|
|
|
|
|
|
|
return(GetAttackSequence(controller,PredStaff_Attacks,wound_flags,crouching,0));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
AIMODULE *NearNPC_GetTargetAIModuleForRetreat(STRATEGYBLOCK *sbPtr, NPC_MOVEMENTDATA *moveData)
|
|
|
|
{
|
|
|
|
extern unsigned int PlayerSmell;
|
|
|
|
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
AIMODULE* targetModule = (AIMODULE *)0;
|
|
|
|
unsigned int targetSmell = PlayerSmell + 1; /* should be higher than any smell anywhere this frame */
|
|
|
|
unsigned int targetNumAdj = 0;
|
|
|
|
int targetEpDot=-ONE_FIXED;
|
|
|
|
VECTORCH lastVelocityDirection;
|
|
|
|
int gotLastVelocityDirection = 0;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
if(sbPtr->containingModule==NULL) return targetModule;
|
|
|
|
AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs;
|
|
|
|
|
|
|
|
/* try to get our last velocity direction */
|
|
|
|
if((moveData->lastVelocity.vx!=0)||(moveData->lastVelocity.vz!=0)||(moveData->lastVelocity.vy!=0))
|
|
|
|
{
|
|
|
|
lastVelocityDirection = moveData->lastVelocity;
|
|
|
|
Normalise(&lastVelocityDirection);
|
|
|
|
gotLastVelocityDirection = 1;
|
|
|
|
}
|
|
|
|
else gotLastVelocityDirection = 0;
|
|
|
|
|
|
|
|
/* check that there is a list of adjacent modules, and that it is not
|
|
|
|
empty (ie points to zero) */
|
|
|
|
if(AdjModuleRefPtr)
|
|
|
|
{
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
|
|
|
|
/* get the index */
|
|
|
|
int AdjModuleIndex = (*AdjModuleRefPtr)->m_index;
|
|
|
|
int AdjModuleSmell = PherPl_ReadBuf[AdjModuleIndex];
|
|
|
|
FARENTRYPOINT *thisEp = GetAIModuleEP((*AdjModuleRefPtr), sbPtr->containingModule->m_aimodule);
|
|
|
|
int thisEpDot = -ONE_FIXED;
|
|
|
|
int chooseThisOne = 0;
|
|
|
|
|
|
|
|
if(thisEp)
|
|
|
|
{
|
|
|
|
/* aha. an ep!... */
|
|
|
|
VECTORCH thisEpWorld = thisEp->position;
|
|
|
|
|
|
|
|
thisEpWorld.vx += (*AdjModuleRefPtr)->m_world.vx;
|
|
|
|
thisEpWorld.vy += (*AdjModuleRefPtr)->m_world.vy;
|
|
|
|
thisEpWorld.vz += (*AdjModuleRefPtr)->m_world.vz;
|
|
|
|
|
|
|
|
if(gotLastVelocityDirection)
|
|
|
|
{
|
|
|
|
VECTORCH thisEpDirection;
|
|
|
|
|
|
|
|
thisEpDirection = thisEpWorld;
|
|
|
|
thisEpDirection.vx -= sbPtr->DynPtr->Position.vx;
|
|
|
|
thisEpDirection.vy -= sbPtr->DynPtr->Position.vy;
|
|
|
|
thisEpDirection.vz -= sbPtr->DynPtr->Position.vz;
|
|
|
|
Normalise(&thisEpDirection);
|
|
|
|
thisEpDot = DotProduct(&thisEpDirection,&lastVelocityDirection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if this adjacent module's smell value is lower than
|
|
|
|
the current 'highest smell' record the new module as the
|
|
|
|
target.
|
|
|
|
Tie break on best direction. */
|
|
|
|
|
|
|
|
if (!targetModule) {
|
|
|
|
chooseThisOne=1;
|
|
|
|
} else {
|
|
|
|
if (AdjModuleSmell < targetSmell) {
|
|
|
|
chooseThisOne=1;
|
|
|
|
} else if (AdjModuleSmell == targetSmell) {
|
|
|
|
if (thisEpDot>targetEpDot) {
|
|
|
|
chooseThisOne=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chooseThisOne)
|
|
|
|
{
|
|
|
|
targetSmell = PherPl_ReadBuf[AdjModuleIndex];
|
|
|
|
targetModule = *AdjModuleRefPtr;
|
|
|
|
targetNumAdj = NumAdjacentModules(*AdjModuleRefPtr);
|
|
|
|
targetEpDot = thisEpDot;
|
|
|
|
}
|
|
|
|
/* next adjacent module reference pointer */
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return targetModule;
|
|
|
|
}
|
|
|
|
|
|
|
|
AIMODULE *General_GetRetreatModule_Core(STRATEGYBLOCK *sbPtr,AIMODULE *source,int max_depth) {
|
|
|
|
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
AIMODULE *deepest_target;
|
|
|
|
NL_ROUTE_QUEUE deepest_route;
|
|
|
|
|
|
|
|
/* Note this DOES NOT set the CallsThisFrame variable. That MUST be set before the call. */
|
|
|
|
|
|
|
|
/* Clear the start. */
|
|
|
|
|
|
|
|
deepest_route.depth=0;
|
|
|
|
deepest_route.aimodule=NULL;
|
|
|
|
deepest_route.first_step=NULL;
|
|
|
|
deepest_target=NULL;
|
|
|
|
|
|
|
|
NearLink_Route_Queue[0].depth=0;
|
|
|
|
NearLink_Route_Queue[0].aimodule=source;
|
|
|
|
NearLink_Route_Queue[0].first_step=NULL;
|
|
|
|
NearLink_Route_Queue[1].aimodule=NULL; /* To set a standard. */
|
|
|
|
|
|
|
|
NL_Queue_End=1;
|
|
|
|
NL_Queue_Exec=0;
|
|
|
|
|
|
|
|
while (NearLink_Route_Queue[NL_Queue_Exec].aimodule!=NULL) {
|
|
|
|
|
|
|
|
AIMODULE *thisModule;
|
|
|
|
|
|
|
|
thisModule=NearLink_Route_Queue[NL_Queue_Exec].aimodule;
|
|
|
|
|
|
|
|
AdjModuleRefPtr = thisModule->m_link_ptrs;
|
|
|
|
|
|
|
|
if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */
|
|
|
|
{
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
/* Probably want some validity test for the link. */
|
|
|
|
if ((AIModuleIsPhysical(*AdjModuleRefPtr))
|
|
|
|
&&(AIModuleAdmitsPheromones(*AdjModuleRefPtr))
|
|
|
|
/* No visibility check? */
|
|
|
|
) {
|
|
|
|
|
|
|
|
/* Consider depth? */
|
|
|
|
if (NearLink_Route_Queue[NL_Queue_Exec].depth<deepest_route.depth) {
|
|
|
|
deepest_route=NearLink_Route_Queue[NL_Queue_Exec];
|
|
|
|
deepest_target=(*AdjModuleRefPtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process link. */
|
|
|
|
if ((NearLink_Route_Queue[NL_Queue_Exec].depth<max_depth)
|
|
|
|
&&( /* Test for 'used this time round' */
|
|
|
|
((*AdjModuleRefPtr)->RouteFinder_FrameStamp!=GlobalFrameCounter)
|
|
|
|
||((*AdjModuleRefPtr)->RouteFinder_IterationNumber!=RouteFinder_CallsThisFrame)
|
|
|
|
)) {
|
|
|
|
/* Add to queue. */
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=(*AdjModuleRefPtr);
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].depth=NearLink_Route_Queue[NL_Queue_Exec].depth+1;
|
|
|
|
/* Remember first step. */
|
|
|
|
if (NearLink_Route_Queue[NL_Queue_Exec].first_step==NULL) {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=(*AdjModuleRefPtr);
|
|
|
|
} else {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=NearLink_Route_Queue[NL_Queue_Exec].first_step;
|
|
|
|
}
|
|
|
|
/* Stamp as used. */
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_FrameStamp=GlobalFrameCounter;
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_IterationNumber=RouteFinder_CallsThisFrame;
|
|
|
|
NL_Queue_End++;
|
|
|
|
if (NL_Queue_End>=NEARLINK_QUEUE_LENGTH) {
|
|
|
|
NL_Queue_End=0;
|
|
|
|
textprint("Wrapping Nearlink Queue!\n");
|
|
|
|
}
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=NULL;
|
|
|
|
if (NL_Queue_End==NL_Queue_Exec) {
|
|
|
|
LOGDXFMT(("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth = %d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth));
|
|
|
|
LOCALASSERT(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer
|
|
|
|
}
|
|
|
|
} else if (NearLink_Route_Queue[NL_Queue_Exec].depth>=max_depth) {
|
|
|
|
/* That's well deep. Let's return. */
|
|
|
|
|
|
|
|
return(*AdjModuleRefPtr);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* next adjacent module reference pointer */
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Done all the links. */
|
|
|
|
NL_Queue_Exec++;
|
|
|
|
if (NL_Queue_Exec>=NEARLINK_QUEUE_LENGTH) NL_Queue_Exec=0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deepest_target) {
|
|
|
|
/* Split up for easier debugging... */
|
|
|
|
return(deepest_target);
|
|
|
|
} else {
|
|
|
|
/* There's nowhere to retreat to! */
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
AIMODULE *General_GetAIModuleForRetreat(STRATEGYBLOCK *sbPtr,AIMODULE *fearModule,int max_depth) {
|
|
|
|
|
|
|
|
AIMODULE **AdjModuleRefPtr;
|
|
|
|
AIMODULE *my_module;
|
|
|
|
int success;
|
|
|
|
|
|
|
|
GLOBALASSERT(sbPtr->containingModule);
|
|
|
|
my_module=sbPtr->containingModule->m_aimodule;
|
|
|
|
GLOBALASSERT(my_module);
|
|
|
|
|
|
|
|
if (fearModule==NULL) {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Hmm... maybe we need to consider being in sight at some point. */
|
|
|
|
#if 0
|
|
|
|
/* Firstly, are we in sight of the fear module? */
|
|
|
|
if (IsModuleVisibleFromModule((*fearModule->m_module_ptrs),sbPtr->containingModule)) {
|
|
|
|
/* Hmm, still in sight. Try to get out of sight. */
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Step one: search down till we get to my_module, checking off modules. */
|
|
|
|
|
|
|
|
if (my_module==fearModule) {
|
|
|
|
/* Cripes! */
|
|
|
|
AIMODULE *targetModule;
|
|
|
|
|
|
|
|
RouteFinder_CallsThisFrame++;
|
|
|
|
targetModule=General_GetRetreatModule_Core(sbPtr,my_module,max_depth);
|
|
|
|
return(targetModule);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear the start. */
|
|
|
|
|
|
|
|
NearLink_Route_Queue[0].depth=0;
|
|
|
|
NearLink_Route_Queue[0].aimodule=fearModule;
|
|
|
|
NearLink_Route_Queue[0].first_step=NULL;
|
|
|
|
NearLink_Route_Queue[1].aimodule=NULL; /* To set a standard. */
|
|
|
|
|
|
|
|
NL_Queue_End=1;
|
|
|
|
NL_Queue_Exec=0;
|
|
|
|
success=0;
|
|
|
|
|
|
|
|
/* Hijack RouteFinder. */
|
|
|
|
RouteFinder_CallsThisFrame++;
|
|
|
|
|
|
|
|
while (NearLink_Route_Queue[NL_Queue_Exec].aimodule!=NULL) {
|
|
|
|
|
|
|
|
AIMODULE *thisModule;
|
|
|
|
|
|
|
|
thisModule=NearLink_Route_Queue[NL_Queue_Exec].aimodule;
|
|
|
|
|
|
|
|
AdjModuleRefPtr = thisModule->m_link_ptrs;
|
|
|
|
|
|
|
|
if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */
|
|
|
|
{
|
|
|
|
while(*AdjModuleRefPtr != 0)
|
|
|
|
{
|
|
|
|
/* Probably want some validity test for the link. */
|
|
|
|
if ((AIModuleIsPhysical(*AdjModuleRefPtr))
|
|
|
|
&&(AIModuleAdmitsPheromones(*AdjModuleRefPtr))
|
|
|
|
/* No visibility check. */
|
|
|
|
) {
|
|
|
|
/* Is this my_module? */
|
|
|
|
if ( (*AdjModuleRefPtr)==my_module) {
|
|
|
|
/* Yes!!! Break out. */
|
|
|
|
success=1;
|
|
|
|
break;
|
|
|
|
} else if (
|
|
|
|
(NearLink_Route_Queue[NL_Queue_Exec].depth<max_depth)
|
|
|
|
&&( /* Test for 'used this time round' */
|
|
|
|
((*AdjModuleRefPtr)->RouteFinder_FrameStamp!=GlobalFrameCounter)
|
|
|
|
||((*AdjModuleRefPtr)->RouteFinder_IterationNumber!=RouteFinder_CallsThisFrame)
|
|
|
|
)) {
|
|
|
|
|
|
|
|
success=0;
|
|
|
|
/* Add to queue. */
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=(*AdjModuleRefPtr);
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].depth=NearLink_Route_Queue[NL_Queue_Exec].depth+1;
|
|
|
|
/* Remember first step. */
|
|
|
|
if (NearLink_Route_Queue[NL_Queue_Exec].first_step==NULL) {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=(*AdjModuleRefPtr);
|
|
|
|
} else {
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].first_step=NearLink_Route_Queue[NL_Queue_Exec].first_step;
|
|
|
|
}
|
|
|
|
/* Stamp as used. */
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_FrameStamp=GlobalFrameCounter;
|
|
|
|
(*AdjModuleRefPtr)->RouteFinder_IterationNumber=RouteFinder_CallsThisFrame;
|
|
|
|
NL_Queue_End++;
|
|
|
|
if (NL_Queue_End>=NEARLINK_QUEUE_LENGTH) {
|
|
|
|
NL_Queue_End=0;
|
|
|
|
textprint("Wrapping Nearlink Queue!\n");
|
|
|
|
}
|
|
|
|
NearLink_Route_Queue[NL_Queue_End].aimodule=NULL;
|
|
|
|
if (NL_Queue_End==NL_Queue_Exec) {
|
|
|
|
LOGDXFMT(("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth = %d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth));
|
|
|
|
LOCALASSERT(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* next adjacent module reference pointer */
|
|
|
|
AdjModuleRefPtr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Done all the links. */
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
/* Continue break out. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
NL_Queue_Exec++;
|
|
|
|
if (NL_Queue_Exec>=NEARLINK_QUEUE_LENGTH) NL_Queue_Exec=0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* By now, we should have broken out... or maxed out the range. */
|
|
|
|
if (success) {
|
|
|
|
AIMODULE *targetModule;
|
|
|
|
targetModule=General_GetRetreatModule_Core(sbPtr,my_module,max_depth);
|
|
|
|
return(targetModule);
|
|
|
|
} else {
|
|
|
|
return(NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int AIModuleIsVisible(AIMODULE *aimodule) {
|
|
|
|
|
|
|
|
MODULE **module_list;
|
|
|
|
/* D'oh! */
|
|
|
|
module_list=aimodule->m_module_ptrs;
|
|
|
|
|
|
|
|
while (*module_list) {
|
|
|
|
if (ModuleCurrVisArray[(*module_list)->m_index]) {
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
module_list++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int IsMyPolyRidiculous(void) {
|
|
|
|
|
|
|
|
int a,sideend,distance;
|
|
|
|
VECTORCH side;
|
|
|
|
|
|
|
|
/* Please make sure the globals are sensible! */
|
|
|
|
|
|
|
|
a=0;
|
|
|
|
|
|
|
|
for (a=0; a<GMD_myPolyNumPoints; a++) {
|
|
|
|
|
|
|
|
if (a>=(GMD_myPolyNumPoints-1)) {
|
|
|
|
sideend=0;
|
|
|
|
} else {
|
|
|
|
sideend=a+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
side.vx=GMD_myPolyPoints[a].vx-GMD_myPolyPoints[sideend].vx;
|
|
|
|
side.vy=GMD_myPolyPoints[a].vy-GMD_myPolyPoints[sideend].vy;
|
|
|
|
side.vz=GMD_myPolyPoints[a].vz-GMD_myPolyPoints[sideend].vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&side);
|
|
|
|
|
|
|
|
if (distance<1000) {
|
|
|
|
/* Stoopid! */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Initialise_AvoidanceManager(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
ClearThirdAvoidance(sbPtr,manager);
|
|
|
|
|
|
|
|
manager->avoidanceDirection.vx=0;
|
|
|
|
manager->avoidanceDirection.vy=0;
|
|
|
|
manager->avoidanceDirection.vz=0;
|
|
|
|
|
|
|
|
manager->incidenceDirection.vx=0;
|
|
|
|
manager->incidenceDirection.vy=0;
|
|
|
|
manager->incidenceDirection.vz=0;
|
|
|
|
|
|
|
|
manager->incidentPoint.vx=0;
|
|
|
|
manager->incidentPoint.vy=0;
|
|
|
|
manager->incidentPoint.vz=0;
|
|
|
|
|
|
|
|
manager->aggregateNormal.vx=0;
|
|
|
|
manager->aggregateNormal.vy=0;
|
|
|
|
manager->aggregateNormal.vz=0;
|
|
|
|
|
|
|
|
manager->recommendedDistance=0;
|
|
|
|
manager->timer=0;
|
|
|
|
manager->primaryCollision=NULL;
|
|
|
|
manager->substate=AvSS_FreeMovement;
|
|
|
|
|
|
|
|
if ((sbPtr->I_SBtype==I_BehaviourAlien)||(sbPtr->I_SBtype==I_BehaviourFaceHugger)) {
|
|
|
|
/* Allows destruction of explosive objects. */
|
|
|
|
manager->ClearanceDamage=AMMO_ALIEN_OBSTACLE_CLEAR;
|
|
|
|
} else {
|
|
|
|
manager->ClearanceDamage=AMMO_NPC_OBSTACLE_CLEAR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int New_NPC_IsObstructed(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager)
|
|
|
|
{
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
struct collisionreport *nextReport;
|
|
|
|
VECTORCH myVelocityDirection,aggregateNormal;
|
|
|
|
int numObstructiveCollisions;
|
|
|
|
STRATEGYBLOCK *highestPriorityCollision;
|
|
|
|
COLLISIONREPORT vcr;
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
nextReport = dynPtr->CollisionReportPtr;
|
|
|
|
|
|
|
|
numObstructiveCollisions=0;
|
|
|
|
|
|
|
|
/* check our velocity: if we haven't got one, we can't be obstructed, so just return */
|
|
|
|
if((sbPtr->DynPtr->LinVelocity.vx==0)&&(sbPtr->DynPtr->LinVelocity.vy==0)&&(sbPtr->DynPtr->LinVelocity.vz==0))
|
|
|
|
{
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sbPtr->I_SBtype==I_BehaviourMarine) {
|
|
|
|
if (SimpleEdgeDetectionTest(sbPtr,&vcr)) {
|
|
|
|
vcr.NextCollisionReportPtr=nextReport;
|
|
|
|
nextReport=&vcr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextReport==NULL) {
|
|
|
|
/* Trivial reject to save time. */
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get my velocity direction, normalised... */
|
|
|
|
myVelocityDirection = dynPtr->LinVelocity;
|
|
|
|
Normalise(&myVelocityDirection);
|
|
|
|
|
|
|
|
highestPriorityCollision=(STRATEGYBLOCK *)-1;
|
|
|
|
aggregateNormal.vx=0;
|
|
|
|
aggregateNormal.vy=0;
|
|
|
|
aggregateNormal.vz=0;
|
|
|
|
|
|
|
|
if (manager->substate==AvSS_FreeMovement) {
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Walk the collision report list. */
|
|
|
|
while(nextReport)
|
|
|
|
{
|
|
|
|
int normalDotWithVelocity;
|
|
|
|
|
|
|
|
if(nextReport->ObstacleSBPtr)
|
|
|
|
{
|
|
|
|
/* Possible testing for type here. Personally, I don't care, apart from destructibles. */
|
|
|
|
}
|
|
|
|
|
|
|
|
normalDotWithVelocity = DotProduct(&(nextReport->ObstacleNormal),&myVelocityDirection);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
if(((normalDotWithVelocity < -46341)||
|
|
|
|
((nextReport->ObstacleSBPtr)&&(!SBIsEnvironment(nextReport->ObstacleSBPtr))&&(normalDotWithVelocity < -32768))))
|
|
|
|
/* 45 degs vs environment, 60 degs vs objects. */
|
|
|
|
#else
|
|
|
|
if (normalDotWithVelocity < -32768)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
/* If we're in FirstAvoidance already, might want to disregard the same collision again. */
|
|
|
|
if (manager->substate==AvSS_FirstAvoidance) {
|
|
|
|
if (!SBIsEnvironment(manager->primaryCollision)) {
|
|
|
|
if (manager->primaryCollision==nextReport->ObstacleSBPtr) {
|
|
|
|
/* Advance and continue. */
|
|
|
|
nextReport = nextReport->NextCollisionReportPtr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We have detected a collision with a strategy, or an obstructive environment bit. */
|
|
|
|
numObstructiveCollisions++;
|
|
|
|
aggregateNormal.vx+=nextReport->ObstacleNormal.vx;
|
|
|
|
aggregateNormal.vy+=nextReport->ObstacleNormal.vy;
|
|
|
|
aggregateNormal.vz+=nextReport->ObstacleNormal.vz;
|
|
|
|
|
|
|
|
/* Sort out highest priority collision. */
|
|
|
|
if (highestPriorityCollision==(STRATEGYBLOCK *)-1) {
|
|
|
|
highestPriorityCollision=nextReport->ObstacleSBPtr;
|
|
|
|
} else {
|
|
|
|
/* If this collision is with the environment, and the older one was not, replace it. */
|
|
|
|
if (SBIsEnvironment(nextReport->ObstacleSBPtr)&&(!SBIsEnvironment(highestPriorityCollision))) {
|
|
|
|
highestPriorityCollision=nextReport->ObstacleSBPtr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
if(nextReport->ObstacleSBPtr)
|
|
|
|
{
|
|
|
|
if(nextReport->ObstacleSBPtr->I_SBtype==I_BehaviourInanimateObject)
|
|
|
|
{
|
|
|
|
INANIMATEOBJECT_STATUSBLOCK* objectstatusptr = nextReport->ObstacleSBPtr->SBdataptr;
|
|
|
|
if (objectstatusptr) {
|
|
|
|
if (objectstatusptr->Indestructable == 0) {
|
|
|
|
/* Consider explosive objects as obstructions to most things. */
|
|
|
|
if ((objectstatusptr->explosionType==0)||(manager->ClearanceDamage!=AMMO_NPC_OBSTACLE_CLEAR)) {
|
|
|
|
/* aha: an object which the npc can destroy... damage it, and return zero. */
|
|
|
|
CauseDamageToObject(nextReport->ObstacleSBPtr,&TemplateAmmo[manager->ClearanceDamage].MaxDamage, ONE_FIXED,NULL);
|
|
|
|
return(0);
|
|
|
|
/* After a few frames of that, there'll just be real obstructions. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nextReport = nextReport->NextCollisionReportPtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numObstructiveCollisions==0) {
|
|
|
|
/* No collisions! Woohoo! But don't reset the substate... */
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (manager->substate) {
|
|
|
|
|
|
|
|
case AvSS_FreeMovement:
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
/* Right, we've run into something all right. */
|
|
|
|
GLOBALASSERT(highestPriorityCollision!=(STRATEGYBLOCK *)-1);
|
|
|
|
|
|
|
|
manager->primaryCollision=highestPriorityCollision;
|
|
|
|
manager->incidenceDirection=myVelocityDirection;
|
|
|
|
manager->incidentPoint=dynPtr->Position;
|
|
|
|
/* Decide on a distance... */
|
|
|
|
|
|
|
|
if (SBIsEnvironment(manager->primaryCollision)) {
|
|
|
|
manager->recommendedDistance=3000;
|
|
|
|
} else {
|
|
|
|
manager->recommendedDistance=2000;
|
|
|
|
}
|
|
|
|
manager->timer=STANDARD_AVOIDANCE_TIME;
|
|
|
|
|
|
|
|
/* ...and a direction. */
|
|
|
|
Normalise(&aggregateNormal);
|
|
|
|
manager->aggregateNormal=aggregateNormal;
|
|
|
|
|
|
|
|
if (New_GetAvoidanceDirection(sbPtr,manager,&aggregateNormal)==0) {
|
|
|
|
/* No valid directions in pass 1 - not dealt with yet! */
|
|
|
|
GLOBALASSERT(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
manager->substate=AvSS_FirstAvoidance;
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case AvSS_FirstAvoidance:
|
|
|
|
{
|
|
|
|
/* Right, we've run into something again. */
|
|
|
|
|
|
|
|
/* Retain point, direction and distance. */
|
|
|
|
|
|
|
|
/* ...but get a new direction. */
|
|
|
|
aggregateNormal.vx+=manager->aggregateNormal.vx;
|
|
|
|
aggregateNormal.vy+=manager->aggregateNormal.vy;
|
|
|
|
aggregateNormal.vz+=manager->aggregateNormal.vz;
|
|
|
|
/* Add the new aggregatenormal to the old one, and normalise... */
|
|
|
|
Normalise(&aggregateNormal);
|
|
|
|
/* Then pass that number into the second direction system. */
|
|
|
|
if (New_GetSecondAvoidanceDirection(sbPtr,manager,&aggregateNormal)==0) {
|
|
|
|
/* No valid directions in pass 2 - not dealt with yet! */
|
|
|
|
GLOBALASSERT(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
manager->substate=AvSS_SecondAvoidance;
|
|
|
|
manager->timer=STANDARD_AVOIDANCE_TIME;
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
case AvSS_SecondAvoidance:
|
|
|
|
case AvSS_ThirdAvoidance:
|
|
|
|
{
|
|
|
|
/* Right, we've run into something again again. (Again.) */
|
|
|
|
|
|
|
|
/* Retain point, direction and distance, and go directly to third avoidance. */
|
|
|
|
|
|
|
|
manager->timer=STANDARD_AVOIDANCE_TIME;
|
|
|
|
InitialiseThirdAvoidance(sbPtr,manager);
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AVOIDANCE_RETURN_CONDITION AllNewAvoidanceKernel(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
|
|
|
|
/* Velocity must be set deliberately... even if it's null. */
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
/* Want to split here based on substate. */
|
|
|
|
|
|
|
|
switch (manager->substate) {
|
|
|
|
case AvSS_FreeMovement:
|
|
|
|
{
|
|
|
|
/* Really shouldn't be here. Go'way. */
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
return(AvRC_Clear);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AvSS_FirstAvoidance:
|
|
|
|
{
|
|
|
|
if (New_NPC_IsObstructed(sbPtr,manager)==1) {
|
|
|
|
/* We're obstructed AGAIN! */
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Are we far enough away? */
|
|
|
|
{
|
|
|
|
int distance;
|
|
|
|
VECTORCH offset;
|
|
|
|
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->incidentPoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->incidentPoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->incidentPoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
|
|
|
|
if (distance>manager->recommendedDistance) {
|
|
|
|
/* Exit! */
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
return(AvRC_Clear);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manager->timer-=NormalFrameTime;
|
|
|
|
if (manager->timer<=0) {
|
|
|
|
/* Ooh, we're in a fix here... */
|
|
|
|
/* Probably need Avoidance 3 for this. */
|
|
|
|
InitialiseThirdAvoidance(sbPtr,manager);
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AvSS_SecondAvoidance:
|
|
|
|
{
|
|
|
|
if (New_NPC_IsObstructed(sbPtr,manager)==1) {
|
|
|
|
/* Should be in Third Avoidance here. */
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Are we far enough away? */
|
|
|
|
{
|
|
|
|
int distance;
|
|
|
|
VECTORCH offset;
|
|
|
|
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->incidentPoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->incidentPoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->incidentPoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
|
|
|
|
if (distance>manager->recommendedDistance) {
|
|
|
|
/* Exit! */
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
return(AvRC_Clear);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
manager->timer-=NormalFrameTime;
|
|
|
|
if (manager->timer<=0) {
|
|
|
|
/* Ooh, we're in a fix here... */
|
|
|
|
/* Probably need Avoidance 3 for this. */
|
|
|
|
InitialiseThirdAvoidance(sbPtr,manager);
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case AvSS_ThirdAvoidance:
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
/* Let's have a whole function here! */
|
|
|
|
result=ExecuteThirdAvoidance(sbPtr,manager);
|
|
|
|
if (result==-1) {
|
|
|
|
/* Totally fubared. Return failure. */
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
return(AvRC_Failure);
|
|
|
|
} else if (result==1) {
|
|
|
|
/* Success! Return clear. */
|
|
|
|
Initialise_AvoidanceManager(sbPtr,manager);
|
|
|
|
return(AvRC_Clear);
|
|
|
|
} else {
|
|
|
|
/* Still going, return avoidance. */
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
GLOBALASSERT(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(AvRC_Avoidance);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int New_GetAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
int dot;
|
|
|
|
VECTORCH spaceNormal,transverse;
|
|
|
|
VECTORCH direction[4];
|
|
|
|
/* Yeesh. */
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
/* aggregateNormal should be normalised, and should point away from the collisions. */
|
|
|
|
/* First dot it with gravity... */
|
|
|
|
|
|
|
|
if (dynPtr->UseStandardGravity) {
|
|
|
|
dynPtr->GravityDirection.vx=0;
|
|
|
|
dynPtr->GravityDirection.vy=65536;
|
|
|
|
dynPtr->GravityDirection.vz=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dot = -(DotProduct(&dynPtr->GravityDirection,aggregateNormal));
|
|
|
|
/* Hold that thought. */
|
|
|
|
spaceNormal.vx = (aggregateNormal->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx));
|
|
|
|
spaceNormal.vy = (aggregateNormal->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy));
|
|
|
|
spaceNormal.vz = (aggregateNormal->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz));
|
|
|
|
|
|
|
|
Normalise(&spaceNormal);
|
|
|
|
/* Now, spaceNormal should be in the plane we want to consider. */
|
|
|
|
CrossProduct(&spaceNormal,&dynPtr->GravityDirection,&transverse);
|
|
|
|
Normalise(&transverse);
|
|
|
|
/* ...And 'transverse' should be at 90degs to it. */
|
|
|
|
|
|
|
|
/* For now, emulate the old avoidance code... */
|
|
|
|
|
|
|
|
direction[0]=transverse;
|
|
|
|
direction[1].vx=-transverse.vx;
|
|
|
|
direction[1].vy=-transverse.vy;
|
|
|
|
direction[1].vz=-transverse.vz;
|
|
|
|
|
|
|
|
// Added by Alex - see if we can get a better direction this way.
|
|
|
|
direction[2] = spaceNormal;
|
|
|
|
direction[3].vx = -direction[2].vx;
|
|
|
|
direction[3].vy = -direction[2].vy;
|
|
|
|
direction[3].vz = -direction[2].vz;
|
|
|
|
|
|
|
|
direction[0].vx += (spaceNormal.vx/4);
|
|
|
|
direction[0].vy += (spaceNormal.vy/4);
|
|
|
|
direction[0].vz += (spaceNormal.vz/4);
|
|
|
|
direction[1].vx += (spaceNormal.vx/4);
|
|
|
|
direction[1].vy += (spaceNormal.vy/4);
|
|
|
|
direction[1].vz += (spaceNormal.vz/4);
|
|
|
|
|
|
|
|
direction[2].vx -= (transverse.vx/4);
|
|
|
|
direction[2].vy -= (transverse.vy/4);
|
|
|
|
direction[2].vz -= (transverse.vz/4);
|
|
|
|
direction[3].vx -= (transverse.vx/4);
|
|
|
|
direction[3].vy -= (transverse.vy/4);
|
|
|
|
direction[3].vz -= (transverse.vz/4);
|
|
|
|
|
|
|
|
Normalise(&direction[0]);
|
|
|
|
Normalise(&direction[1]);
|
|
|
|
Normalise(&direction[2]);
|
|
|
|
Normalise(&direction[3]);
|
|
|
|
|
|
|
|
{
|
|
|
|
int this_distance, i;
|
|
|
|
int best_distance_so_far = 0;
|
|
|
|
int best_direction_so_far = 0;
|
|
|
|
|
|
|
|
/* test how far we could go in each direction... */
|
|
|
|
for( i=0; i<4; i++ )
|
|
|
|
{
|
|
|
|
VECTORCH startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
VECTORCH testDirn = direction[i];
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) this_distance = NPC_MAX_VIEWRANGE;
|
|
|
|
else this_distance = LOS_Lambda;
|
|
|
|
|
|
|
|
if( this_distance > best_distance_so_far )
|
|
|
|
{
|
|
|
|
// What follows is an attempt to make sure we don't jump off any cliffs...
|
|
|
|
VECTORCH test_location;
|
|
|
|
testDirn.vx *= this_distance;
|
|
|
|
testDirn.vy *= this_distance;
|
|
|
|
testDirn.vz *= this_distance;
|
|
|
|
test_location.vx = startingPosition.vx + testDirn.vx;
|
|
|
|
test_location.vy = startingPosition.vy + testDirn.vy;
|
|
|
|
test_location.vz = startingPosition.vz + testDirn.vz;
|
|
|
|
if( CheckMyFloorPoly(&test_location, sbPtr->containingModule) != NPC_GMD_NOPOLY)
|
|
|
|
{
|
|
|
|
best_direction_so_far = i;
|
|
|
|
best_distance_so_far = this_distance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
manager->avoidanceDirection = direction[best_direction_so_far];
|
|
|
|
}
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager)
|
|
|
|
{
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
VECTORCH newDirection1, newDirection2;
|
|
|
|
int dir1dist, dir2dist;
|
|
|
|
MATRIXCH matrix;
|
|
|
|
EULER euler;
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
/* just in case */
|
|
|
|
if(!sbPtr->containingModule) return(0);
|
|
|
|
|
|
|
|
/* going for a 90 degree turn + back a bit */
|
|
|
|
newDirection1 = manager->incidenceDirection;
|
|
|
|
newDirection2 = newDirection1;
|
|
|
|
euler.EulerX = 0;
|
|
|
|
euler.EulerY = 1024;
|
|
|
|
euler.EulerZ = 0;
|
|
|
|
CreateEulerMatrix( &euler, &matrix );
|
|
|
|
RotateVector( &newDirection1, &matrix );
|
|
|
|
euler.EulerY = 3072;
|
|
|
|
CreateEulerMatrix( &euler, &matrix );
|
|
|
|
RotateVector( &newDirection2, &matrix );
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/* construct the direction(s)...
|
|
|
|
start with object's local x unit vector (from local coo-ord system in world space) */
|
|
|
|
newDirection1.vx = sbPtr->DynPtr->OrientMat.mat11;
|
|
|
|
newDirection1.vy = sbPtr->DynPtr->OrientMat.mat12;
|
|
|
|
newDirection1.vz = sbPtr->DynPtr->OrientMat.mat13;
|
|
|
|
newDirection2.vx = -newDirection1.vx;
|
|
|
|
newDirection2.vy = -newDirection1.vy;
|
|
|
|
newDirection2.vz = -newDirection1.vz;
|
|
|
|
/* ...and add on 1/4 of the -z direction...*/
|
|
|
|
newDirection1.vx -= (sbPtr->DynPtr->OrientMat.mat31/4);
|
|
|
|
newDirection1.vy -= (sbPtr->DynPtr->OrientMat.mat32/4);
|
|
|
|
newDirection1.vz -= (sbPtr->DynPtr->OrientMat.mat33/4);
|
|
|
|
newDirection2.vx -= (sbPtr->DynPtr->OrientMat.mat31/4);
|
|
|
|
newDirection2.vy -= (sbPtr->DynPtr->OrientMat.mat32/4);
|
|
|
|
newDirection2.vz -= (sbPtr->DynPtr->OrientMat.mat33/4);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
Normalise(&newDirection1);
|
|
|
|
Normalise(&newDirection2);
|
|
|
|
|
|
|
|
/* test how far we could go in each direction... */
|
|
|
|
{
|
|
|
|
VECTORCH startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
VECTORCH testDirn = newDirection1;
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir1dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir1dist = LOS_Lambda;
|
|
|
|
|
|
|
|
startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
testDirn = newDirection2;
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir2dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir2dist = LOS_Lambda;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(dir1dist > dir2dist) manager->avoidanceDirection = newDirection1;
|
|
|
|
else manager->avoidanceDirection = newDirection2;
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int New_GetSecondAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
int dot;
|
|
|
|
VECTORCH spaceNormal,transverse;
|
|
|
|
VECTORCH direction1,direction2;
|
|
|
|
/* Yeesh. */
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
/* aggregateNormal should be normalised, and should point away from the collisions. */
|
|
|
|
/* First dot it with gravity... */
|
|
|
|
|
|
|
|
dot = -(DotProduct(&dynPtr->GravityDirection,aggregateNormal));
|
|
|
|
/* Hold that thought. */
|
|
|
|
spaceNormal.vx = (aggregateNormal->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx));
|
|
|
|
spaceNormal.vy = (aggregateNormal->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy));
|
|
|
|
spaceNormal.vz = (aggregateNormal->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz));
|
|
|
|
|
|
|
|
Normalise(&spaceNormal);
|
|
|
|
/* Now, spaceNormal should be in the plane we want to consider. */
|
|
|
|
CrossProduct(&spaceNormal,&dynPtr->GravityDirection,&transverse);
|
|
|
|
Normalise(&transverse);
|
|
|
|
/* ...And 'transverse' should be at 90degs to it. */
|
|
|
|
|
|
|
|
/* For now, emulate the old avoidance code... */
|
|
|
|
|
|
|
|
direction1=transverse;
|
|
|
|
direction2.vx=-transverse.vx;
|
|
|
|
direction2.vy=-transverse.vy;
|
|
|
|
direction2.vz=-transverse.vz;
|
|
|
|
|
|
|
|
if ((FastRandom()&65535)<32767) {
|
|
|
|
direction1.vx += (spaceNormal.vx/2);
|
|
|
|
direction1.vy += (spaceNormal.vy/2);
|
|
|
|
direction1.vz += (spaceNormal.vz/2);
|
|
|
|
direction2.vx += (spaceNormal.vx/2);
|
|
|
|
direction2.vy += (spaceNormal.vy/2);
|
|
|
|
direction2.vz += (spaceNormal.vz/2);
|
|
|
|
} else {
|
|
|
|
direction1.vx += (spaceNormal.vx);
|
|
|
|
direction1.vy += (spaceNormal.vy);
|
|
|
|
direction1.vz += (spaceNormal.vz);
|
|
|
|
direction2.vx += (spaceNormal.vx);
|
|
|
|
direction2.vy += (spaceNormal.vy);
|
|
|
|
direction2.vz += (spaceNormal.vz);
|
|
|
|
}
|
|
|
|
|
|
|
|
Normalise(&direction1);
|
|
|
|
Normalise(&direction2);
|
|
|
|
|
|
|
|
{
|
|
|
|
int dir1dist,dir2dist;
|
|
|
|
/* test how far we could go in each direction... */
|
|
|
|
{
|
|
|
|
VECTORCH startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
VECTORCH testDirn = direction1;
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir1dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir1dist = LOS_Lambda;
|
|
|
|
|
|
|
|
startingPosition = sbPtr->DynPtr->Position;
|
|
|
|
testDirn = direction2;
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&startingPosition,&testDirn,0);
|
|
|
|
if(!LOS_ObjectHitPtr) dir2dist = NPC_MAX_VIEWRANGE;
|
|
|
|
else dir2dist = LOS_Lambda;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(dir1dist > dir2dist) manager->avoidanceDirection = direction1;
|
|
|
|
else manager->avoidanceDirection = direction2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AlignVelocityToGravity(STRATEGYBLOCK *sbPtr,VECTORCH *velocity) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
int dot;
|
|
|
|
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
if (dynPtr->UseStandardGravity) {
|
|
|
|
dynPtr->GravityDirection.vx=0;
|
|
|
|
dynPtr->GravityDirection.vy=65536;
|
|
|
|
dynPtr->GravityDirection.vz=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dot = -(DotProduct(&dynPtr->GravityDirection,velocity));
|
|
|
|
/* Hold that thought. */
|
|
|
|
velocity->vx = (velocity->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx));
|
|
|
|
velocity->vy = (velocity->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy));
|
|
|
|
velocity->vz = (velocity->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz));
|
|
|
|
|
|
|
|
if ((velocity->vx==0)&&(velocity->vy==0)&&(velocity->vz==0)) {
|
|
|
|
/* That can't be good. Can it? */
|
|
|
|
//velocity->vx=ONE_FIXED;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Normalise(velocity);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClearThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) {
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
|
|
|
|
manager->baseVector.vx=0;
|
|
|
|
manager->baseVector.vy=0;
|
|
|
|
manager->baseVector.vz=0;
|
|
|
|
manager->currentVector=manager->baseVector;
|
|
|
|
manager->avoidanceDirection.vx=0;
|
|
|
|
manager->avoidanceDirection.vy=0;
|
|
|
|
manager->avoidanceDirection.vz=0;
|
|
|
|
manager->bestVector.vx=0;
|
|
|
|
manager->bestVector.vy=0;
|
|
|
|
manager->bestVector.vz=0;
|
|
|
|
manager->basePoint=manager->baseVector;
|
|
|
|
manager->stage=0;
|
|
|
|
manager->bestDistance=0;
|
|
|
|
manager->bestStage=0;
|
|
|
|
/* Er... ignore the rotmat for now... */
|
|
|
|
}
|
|
|
|
|
|
|
|
void InitialiseThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
if (dynPtr->UseStandardGravity) {
|
|
|
|
dynPtr->GravityDirection.vx=0;
|
|
|
|
dynPtr->GravityDirection.vy=65536;
|
|
|
|
dynPtr->GravityDirection.vz=0;
|
|
|
|
}
|
|
|
|
/* Setup base vector. */
|
|
|
|
{
|
|
|
|
int dot;
|
|
|
|
/* Try using positive... z. */
|
|
|
|
manager->baseVector.vx=sbPtr->DynPtr->OrientMat.mat31;
|
|
|
|
manager->baseVector.vy=sbPtr->DynPtr->OrientMat.mat32;
|
|
|
|
manager->baseVector.vz=sbPtr->DynPtr->OrientMat.mat33;
|
|
|
|
|
|
|
|
dot = (DotProduct(&dynPtr->GravityDirection,&manager->baseVector));
|
|
|
|
|
|
|
|
if (!((dot<65000)&&(dot>-65000))) {
|
|
|
|
/* Too close. Let's use x. */
|
|
|
|
manager->baseVector.vx=sbPtr->DynPtr->OrientMat.mat11;
|
|
|
|
manager->baseVector.vy=sbPtr->DynPtr->OrientMat.mat12;
|
|
|
|
manager->baseVector.vz=sbPtr->DynPtr->OrientMat.mat13;
|
|
|
|
}
|
|
|
|
AlignVelocityToGravity(sbPtr,&manager->baseVector);
|
|
|
|
}
|
|
|
|
manager->currentVector=manager->baseVector;
|
|
|
|
|
|
|
|
/* Keep still! */
|
|
|
|
manager->avoidanceDirection.vx=0;
|
|
|
|
manager->avoidanceDirection.vy=0;
|
|
|
|
manager->avoidanceDirection.vz=0;
|
|
|
|
|
|
|
|
manager->bestVector.vx=0;
|
|
|
|
manager->bestVector.vy=0;
|
|
|
|
manager->bestVector.vz=0;
|
|
|
|
|
|
|
|
manager->basePoint=dynPtr->Position;
|
|
|
|
|
|
|
|
manager->stage=0;
|
|
|
|
manager->bestDistance=0;
|
|
|
|
manager->bestStage=0;
|
|
|
|
|
|
|
|
/* Finally, generate a matrix to rotate 22.5degs about GravityDirection. */
|
|
|
|
|
|
|
|
{
|
|
|
|
QUAT deltaRotQ;
|
|
|
|
/* Angle is 256, halfangle is 128. */
|
|
|
|
int cosHalfAngle = GetCos(128);
|
|
|
|
int sinHalfAngle = GetSin(128);
|
|
|
|
|
|
|
|
deltaRotQ.quatw=cosHalfAngle;
|
|
|
|
deltaRotQ.quatx=MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vx);
|
|
|
|
deltaRotQ.quaty=MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vy);
|
|
|
|
deltaRotQ.quatz=MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vz);
|
|
|
|
|
|
|
|
QNormalise(&deltaRotQ);
|
|
|
|
QuatToMat(&deltaRotQ,&manager->rotationMatrix);
|
|
|
|
|
|
|
|
}
|
|
|
|
/* Say we're in Third Avoidance. */
|
|
|
|
manager->substate=AvSS_ThirdAvoidance;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ExecuteThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) {
|
|
|
|
|
|
|
|
DYNAMICSBLOCK *dynPtr;
|
|
|
|
|
|
|
|
LOCALASSERT(manager);
|
|
|
|
LOCALASSERT(sbPtr);
|
|
|
|
dynPtr = sbPtr->DynPtr;
|
|
|
|
LOCALASSERT(dynPtr);
|
|
|
|
|
|
|
|
if (manager->stage<9) {
|
|
|
|
/* Still in the spin. Increment stage NOW! */
|
|
|
|
manager->stage++;
|
|
|
|
/* Now raycast. */
|
|
|
|
{
|
|
|
|
VECTORCH testDirn = manager->currentVector;
|
|
|
|
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&manager->basePoint,&testDirn,0);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
MakeParticle(&manager->basePoint,&testDirn,PARTICLE_PREDATOR_BLOOD);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
/* Hit environment! */
|
|
|
|
if (LOS_Lambda>manager->bestDistance) {
|
|
|
|
/* Register this as best. */
|
|
|
|
manager->bestDistance=LOS_Lambda;
|
|
|
|
manager->bestStage=manager->stage;
|
|
|
|
manager->bestVector=testDirn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
testDirn.vx = -manager->currentVector.vx;
|
|
|
|
testDirn.vy = -manager->currentVector.vy;
|
|
|
|
testDirn.vz = -manager->currentVector.vz;
|
|
|
|
LOS_ObjectHitPtr = (DISPLAYBLOCK *)0;
|
|
|
|
LOS_Lambda = NPC_MAX_VIEWRANGE;
|
|
|
|
CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr,&manager->basePoint,&testDirn,0);
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
MakeParticle(&manager->basePoint,&testDirn,PARTICLE_PREDATOR_BLOOD);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
/* Hit environment! */
|
|
|
|
if (LOS_Lambda>manager->bestDistance) {
|
|
|
|
/* Register this as best. */
|
|
|
|
manager->bestDistance=LOS_Lambda;
|
|
|
|
manager->bestStage=-manager->stage;
|
|
|
|
manager->bestVector=testDirn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Now, rotate vector round. */
|
|
|
|
RotateVector(&manager->currentVector,&manager->rotationMatrix);
|
|
|
|
} else if (manager->stage==9) {
|
|
|
|
/* Now, we must have checked all 16 directions. */
|
|
|
|
if (manager->bestStage==0) {
|
|
|
|
/* That's a bit of a mystery. Return fubared. */
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
/* Now, my beautiful assistant, open the envelope! */
|
|
|
|
if (manager->bestDistance<THIRD_AVOIDANCE_MINDIST) {
|
|
|
|
/* Whatta loada junk. */
|
|
|
|
return(-1);
|
|
|
|
} else {
|
|
|
|
/* Go, my child, in the direction of destiny! */
|
|
|
|
manager->avoidanceDirection=manager->bestVector;
|
|
|
|
Normalise(&manager->avoidanceDirection);
|
|
|
|
manager->stage++;
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* In stage 10, we're going that way and trying to escape. */
|
|
|
|
if (New_NPC_IsObstructed(sbPtr,manager)==1) {
|
|
|
|
int distance;
|
|
|
|
VECTORCH offset;
|
|
|
|
/* We're obstructed AGAIN! Gordon Bennet... restart? */
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->basePoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->basePoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->basePoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
if (distance<300) {
|
|
|
|
/* I suspect restarting will get us nowhere, *
|
|
|
|
* something's in the primary path. Fail! */
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
InitialiseThirdAvoidance(sbPtr,manager);
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Are we far enough away? */
|
|
|
|
{
|
|
|
|
int distance;
|
|
|
|
VECTORCH offset;
|
|
|
|
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->incidentPoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->incidentPoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->incidentPoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
if (distance>manager->recommendedDistance) {
|
|
|
|
/* Now let's check against third avoidance. */
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->basePoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->basePoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->basePoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
if (distance>(THIRD_AVOIDANCE_MINDIST>>1)) {
|
|
|
|
/* Exit with success! Glory be! */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Still check, to stop repeated third avoidance bouncing. */
|
|
|
|
offset.vx=dynPtr->Position.vx-manager->basePoint.vx;
|
|
|
|
offset.vy=dynPtr->Position.vy-manager->basePoint.vy;
|
|
|
|
offset.vz=dynPtr->Position.vz-manager->basePoint.vz;
|
|
|
|
|
|
|
|
distance=Approximate3dMagnitude(&offset);
|
|
|
|
if (distance>((manager->bestDistance)>>1)) {
|
|
|
|
/* Exit with success, before we look stupid! */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Still here, hmm? */
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void CastLOSSpear(STRATEGYBLOCK *sbPtr, VECTORCH *muzzlepos, VECTORCH *in_shotvector, enum AMMO_ID AmmoID, int multiple, int inaccurate) {
|
|
|
|
|
|
|
|
VECTORCH shotVector;
|
|
|
|
DISPLAYBLOCK *self;
|
|
|
|
|
|
|
|
shotVector=*in_shotvector;
|
|
|
|
|
|
|
|
if (sbPtr) {
|
|
|
|
self=sbPtr->SBdptr;
|
|
|
|
} else {
|
|
|
|
self=NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
|
|
|
|
if (inaccurate) {
|
|
|
|
/* Random tweak. */
|
|
|
|
shotVector.vx+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
shotVector.vy+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
shotVector.vz+=((FastRandom()%(ONE_FIXED>>2))-(ONE_FIXED>>3));
|
|
|
|
/* Normalise. */
|
|
|
|
Normalise(&shotVector);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FindPolygonInLineOfSight(&shotVector,muzzlepos,0,self);
|
|
|
|
|
|
|
|
/* Now deal with LOS_ObjectHitPtr. */
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
if (LOS_HModel_Section) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock) {
|
|
|
|
if (LOS_ObjectHitPtr->ObStrategyBlock->SBdptr) {
|
|
|
|
GLOBALASSERT(LOS_ObjectHitPtr->ObStrategyBlock->SBdptr->HModelControlBlock==LOS_HModel_Section->my_controller);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* this fn needs updating to take amount of damage into account etc. */
|
|
|
|
HandleSpearImpact(&LOS_Point,LOS_ObjectHitPtr->ObStrategyBlock,AmmoID,&shotVector, multiple, LOS_HModel_Section);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int SimpleEdgeDetectionTest(STRATEGYBLOCK *sbPtr, COLLISIONREPORT *vcr) {
|
|
|
|
|
|
|
|
VECTORCH alpha,beta,tvec;
|
|
|
|
|
|
|
|
GetTargetingPointOfObject_Far(sbPtr,&alpha);
|
|
|
|
/* Now add half a metre in positive z. */
|
|
|
|
|
|
|
|
tvec.vx=sbPtr->DynPtr->OrientMat.mat31;
|
|
|
|
tvec.vy=sbPtr->DynPtr->OrientMat.mat32;
|
|
|
|
tvec.vz=sbPtr->DynPtr->OrientMat.mat33;
|
|
|
|
|
|
|
|
tvec.vx=MUL_FIXED(tvec.vx,1000);
|
|
|
|
tvec.vy=MUL_FIXED(tvec.vy,1000);
|
|
|
|
tvec.vz=MUL_FIXED(tvec.vz,1000);
|
|
|
|
|
|
|
|
alpha.vx+=tvec.vx;
|
|
|
|
alpha.vy+=tvec.vy;
|
|
|
|
alpha.vz+=tvec.vz;
|
|
|
|
|
|
|
|
beta.vx=sbPtr->DynPtr->OrientMat.mat21;
|
|
|
|
beta.vy=sbPtr->DynPtr->OrientMat.mat22;
|
|
|
|
beta.vz=sbPtr->DynPtr->OrientMat.mat23;
|
|
|
|
Normalise(&beta);
|
|
|
|
|
|
|
|
/* Now do an LOS test. */
|
|
|
|
FindPolygonInLineOfSight(&beta,&alpha,0,sbPtr->SBdptr);
|
|
|
|
|
|
|
|
/* Pass the test if the test hit something within a metre (y) of dynPtr->Position. */
|
|
|
|
|
|
|
|
if (LOS_ObjectHitPtr) {
|
|
|
|
VECTORCH offset;
|
|
|
|
int dot;
|
|
|
|
/* Examine LOS_Point. */
|
|
|
|
offset.vx=LOS_Point.vx-sbPtr->DynPtr->Position.vx;
|
|
|
|
offset.vy=LOS_Point.vy-sbPtr->DynPtr->Position.vy;
|
|
|
|
offset.vz=LOS_Point.vz-sbPtr->DynPtr->Position.vz;
|
|
|
|
|
|
|
|
dot=DotProduct(&offset,&beta);
|
|
|
|
|
|
|
|
//ReleasePrintDebuggingText("Dot %d\n",dot);
|
|
|
|
|
|
|
|
if ((dot>-1000)&&(dot<1000)) {
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Must be about to hit a rail or an edge? */
|
|
|
|
vcr->ObstacleSBPtr=NULL;
|
|
|
|
vcr->ObstacleNormal.vx=-sbPtr->DynPtr->OrientMat.mat31;
|
|
|
|
vcr->ObstacleNormal.vy=-sbPtr->DynPtr->OrientMat.mat32;
|
|
|
|
vcr->ObstacleNormal.vz=-sbPtr->DynPtr->OrientMat.mat33;
|
|
|
|
vcr->ObstaclePoint=LOS_Point;
|
|
|
|
vcr->NextCollisionReportPtr=NULL;
|
|
|
|
|
|
|
|
return(1);
|
|
|
|
|
|
|
|
}
|