| | 1 | #include "system.h" |
| | 2 | #include "prototyp.h" |
| | 3 | #include "stratdef.h" |
| | 4 | #include "dynamics.h" |
| | 5 | #include "pfarlocs.h" |
| | 6 | #include "pheromon.h" |
| | 7 | #include "bh_types.h" |
| | 8 | #include "pvisible.h" |
| | 9 | #include "npc_marine.h" |
| | 10 | #include "npc_alien.h" |
| | 11 | #include "npc_predator.h" |
| | 12 | #include "npc_xenoborg.h" |
| | 13 | #include "npc_sentrygun.h" |
| | 14 | #include "weapons.h" |
| | 15 | #include "weaponbehaviour.h" |
| | 16 | #include "los.h" |
| | 17 | #include "targeting.h" |
| | 18 | #include <assert.h> |
| | 19 | #include <stdio.h> |
| | 20 | #include "bh_light.h" |
| | 21 | |
| | 22 | extern int GlobalFrameCounter; |
| | 23 | extern int RouteFinder_CallsThisFrame; |
| | 24 | extern DEATH_DATA Alien_Deaths[]; |
| | 25 | extern DEATH_DATA Marine_Deaths[]; |
| | 26 | extern DEATH_DATA Predator_Deaths[]; |
| | 27 | extern DEATH_DATA Xenoborg_Deaths[]; |
| | 28 | extern ATTACK_DATA Alien_Attacks[]; |
| | 29 | extern ATTACK_DATA Wristblade_Attacks[]; |
| | 30 | extern ATTACK_DATA PredStaff_Attacks[]; |
| | 31 | |
| | 32 | extern void QNormalise(QUAT *q); |
| | 33 | extern int IsAIModuleVisibleFromAIModule(AIMODULE *source,AIMODULE *target); |
| | 34 | |
| | 35 | typedef enum paq_far_bhstate |
| | 36 | { |
| | 37 | PAQFS_Wait, |
| | 38 | PAQFS_Hunt, |
| | 39 | PAQFS_Wander, |
| | 40 | PAQFS_Dying |
| | 41 | |
| | 42 | } PAQ_FAR_BHSTATE; |
| | 43 | |
| | 44 | typedef enum paq_near_bhstate |
| | 45 | { |
| | 46 | PAQNS_Wait, |
| | 47 | PAQNS_Approach, |
| | 48 | PAQNS_Attack, |
| | 49 | PAQNS_Wander, |
| | 50 | PAQNS_Avoidance, |
| | 51 | PAQNS_Dying |
| | 52 | |
| | 53 | } PAQ_NEAR_BHSTATE; |
| | 54 | |
| | 55 | typedef struct paqStatusBlock |
| | 56 | { |
| | 57 | signed int health; |
| | 58 | signed int nearSpeed; |
| | 59 | signed int damageInflicted; |
| | 60 | PAQ_FAR_BHSTATE FarBehaviourState; |
| | 61 | PAQ_NEAR_BHSTATE NearBehaviourState; |
| | 62 | int stateTimer; |
| | 63 | HMODELCONTROLLER HModelController; |
| | 64 | NPC_MOVEMENTDATA moveData; |
| | 65 | NPC_WANDERDATA wanderData; |
| | 66 | |
| | 67 | } PAQ_STATUS_BLOCK; |
| | 68 | |
| | 69 | int CheckAdjacencyValidity(AIMODULE *target, AIMODULE *source, int alien) |
| | 70 | { |
| | 71 | FARENTRYPOINT *thisEp = GetAIModuleEP(target, source); |
| | 72 | |
| | 73 | return (thisEp) ? !(!alien && thisEp->alien_only) : 0; |
| | 74 | } |
| | 75 | |
| | 76 | /* First number is max speed, in mm/s. */ |
| | 77 | /* Second number is acceleration, in mm/s^2. */ |
| | 78 | /* Individual marines vary this by +/- 12.5%. */ |
| | 79 | /* Predators and xenoborgs don't at the moment. */ |
| | 80 | |
| | 81 | static const MOVEMENT_DATA Movement_Stats[] = { |
| | 82 | { |
| | 83 | MDI_Marine_Mooch_Bored, |
| | 84 | 1500, |
| | 85 | 1500, |
| | 86 | }, |
| | 87 | { |
| | 88 | MDI_Marine_Mooch_Alert, |
| | 89 | 1500, |
| | 90 | 1500, |
| | 91 | }, |
| | 92 | { |
| | 93 | MDI_Marine_Combat, |
| | 94 | 4000, |
| | 95 | 4000, |
| | 96 | }, |
| | 97 | { |
| | 98 | MDI_Marine_Sprint, |
| | 99 | 10000, |
| | 100 | 10000, |
| | 101 | }, |
| | 102 | { |
| | 103 | MDI_Civilian_Mooch_Bored, |
| | 104 | 1500, |
| | 105 | 1500, |
| | 106 | }, |
| | 107 | { |
| | 108 | MDI_Civilian_Mooch_Alert, |
| | 109 | 1500, |
| | 110 | 1500, |
| | 111 | }, |
| | 112 | { |
| | 113 | MDI_Civilian_Combat, |
| | 114 | 4000, |
| | 115 | 4000, |
| | 116 | }, |
| | 117 | { |
| | 118 | MDI_Civilian_Sprint, |
| | 119 | 10000, |
| | 120 | 10000, |
| | 121 | }, |
| | 122 | { |
| | 123 | MDI_Predator, |
| | 124 | 8000, |
| | 125 | 10000, |
| | 126 | }, |
| | 127 | { |
| | 128 | MDI_Casual_Predator, |
| | 129 | 3000, |
| | 130 | 3000, |
| | 131 | }, |
| | 132 | { |
| | 133 | MDI_Xenoborg, |
| | 134 | 1000, |
| | 135 | 1000, |
| | 136 | }, |
| | 137 | { |
| | 138 | MDI_End, |
| | 139 | 0, |
| | 140 | 0, |
| | 141 | }, |
| | 142 | }; |
| | 143 | |
| | 144 | const MOVEMENT_DATA *GetThisMovementData(MOVEMENT_DATA_INDEX index) |
| | 145 | { |
| | 146 | return &Movement_Stats[index]; |
| | 147 | } |
| | 148 | |
| | 149 | int NPC_IsDead(STRATEGYBLOCK *sbPtr) |
| | 150 | { |
| | 151 | if(sbPtr->please_destroy_me) |
| | 152 | return 1; |
| | 153 | |
| | 154 | switch(sbPtr->type) |
| | 155 | { |
| | 156 | case I_BehaviourMarine: |
| | 157 | case I_BehaviourAlien: |
| | 158 | return 0; |
| | 159 | case I_BehaviourAlienPlayer: |
| | 160 | case I_BehaviourPredatorPlayer: |
| | 161 | case I_BehaviourMarinePlayer: |
| | 162 | return !PlayerStatus.Alive || Observer; |
| | 163 | case I_BehaviourPredator: |
| | 164 | { |
| | 165 | PREDATOR_STATUS_BLOCK *predatorStatusPointer = (PREDATOR_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 166 | assert(predatorStatusPointer); |
| | 167 | |
| | 168 | return (predatorStatusPointer->behaviourState == PBS_Dying); |
| | 169 | } |
| | 170 | break; |
| | 171 | case I_BehaviourQueenAlien: |
| | 172 | { |
| | 173 | PAQ_STATUS_BLOCK *paqStatusPointer = (PAQ_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 174 | assert(paqStatusPointer); |
| | 175 | |
| | 176 | return (paqStatusPointer->NearBehaviourState == PAQNS_Dying); |
| | 177 | } |
| | 178 | break; |
| | 179 | case I_BehaviourHierarchicalFragment: |
| | 180 | case I_BehaviourCorpse: |
| | 181 | /* Corpses are always dead :-) */ |
| | 182 | return 1; |
| | 183 | case I_BehaviourAutoGun: |
| | 184 | { |
| | 185 | AUTOGUN_STATUS_BLOCK *agunStatusPointer = (AUTOGUN_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 186 | assert(agunStatusPointer); |
| | 187 | |
| | 188 | return (agunStatusPointer->behaviourState == I_disabled); |
| | 189 | } |
| | 190 | case I_BehaviourPlacedLight: |
| | 191 | { |
| | 192 | if(!sbPtr->maintainVisibility || sbPtr->DamageBlock.Indestructable) |
| | 193 | { |
| | 194 | return 1; |
| | 195 | } |
| | 196 | else |
| | 197 | { |
| | 198 | PLACED_LIGHT_BEHAV_BLOCK* pl_bhv = (PLACED_LIGHT_BEHAV_BLOCK*)sbPtr->dataptr; |
| | 199 | |
| | 200 | return ((Light_State_Broken == pl_bhv->state) || (Light_OnOff_Off == pl_bhv->on_off_state)); |
| | 201 | } |
| | 202 | } |
| | 203 | case I_BehaviourFlare: |
| | 204 | { |
| | 205 | FLARE_BEHAV_BLOCK *bbPtr = (FLARE_BEHAV_BLOCK * ) sbPtr->dataptr; |
| | 206 | return bbPtr->LifeTimeRemaining < 0; |
| | 207 | } |
| | 208 | case I_BehaviourXenoborg: |
| | 209 | case I_BehaviourFaceHugger: |
| | 210 | return 0; |
| | 211 | default: |
| | 212 | return 0; |
| | 213 | } |
| | 214 | } |
| | 215 | |
| | 216 | void NPC_InitMovementData(NPC_MOVEMENTDATA *moveData) |
| | 217 | { |
| | 218 | assert(moveData); |
| | 219 | |
| | 220 | moveData->numObstructiveCollisions = 0; |
| | 221 | moveData->avoidanceDirn.vx = moveData->avoidanceDirn.vy = moveData->avoidanceDirn.vz = 0; |
| | 222 | moveData->lastTarget.vx = moveData->lastTarget.vy = moveData->lastTarget.vz = 0; |
| | 223 | moveData->lastVelocity.vx = moveData->lastVelocity.vy = moveData->lastVelocity.vz = 0; |
| | 224 | moveData->numReverses = 0; |
| | 225 | moveData->lastModule = NULL; |
| | 226 | } |
| | 227 | |
| | 228 | void NPC_IsObstructed(STRATEGYBLOCK *sbPtr, NPC_MOVEMENTDATA *moveData, NPC_OBSTRUCTIONREPORT *details, STRATEGYBLOCK **destructableObject) |
| | 229 | { |
| | 230 | assert(destructableObject); |
| | 231 | assert(details); |
| | 232 | assert(moveData); |
| | 233 | assert(sbPtr); |
| | 234 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 235 | assert(dynPtr); |
| | 236 | struct collisionreport *nextReport = dynPtr->CollisionReportPtr; |
| | 237 | |
| | 238 | /* init destructable object pointer, etc... */ |
| | 239 | *destructableObject = NULL; |
| | 240 | details->environment = 0; |
| | 241 | details->destructableObject = 0; |
| | 242 | details->otherCharacter = 0; |
| | 243 | details->anySingleObstruction = 0; |
| | 244 | |
| | 245 | /* check our velocity: if we haven't got one, we can't be obstructed, so just return */ |
| | 246 | if(!sbPtr->DynPtr->LinVelocity.vx && !sbPtr->DynPtr->LinVelocity.vy && !sbPtr->DynPtr->LinVelocity.vz) |
| | 247 | { |
| | 248 | moveData->numObstructiveCollisions = 0; |
| | 249 | return; |
| | 250 | } |
| | 251 | |
| | 252 | /* get my velocity and behaviour type */ |
| | 253 | VECTORCH velDirn = dynPtr->LinVelocity; |
| | 254 | Normalise(&velDirn); |
| | 255 | |
| | 256 | /* walk the collision report list, looking for collisions that obstruct our movement... |
| | 257 | excluding objects of our own type and the player */ |
| | 258 | |
| | 259 | while(nextReport) |
| | 260 | { |
| | 261 | int IsCharacterOrPlayer = 0; |
| | 262 | /* int dotWithGravity; */ |
| | 263 | int normalDotWithVelocity; |
| | 264 | |
| | 265 | if(nextReport->ObstacleSBPtr) |
| | 266 | { |
| | 267 | if(nextReport->ObstacleSBPtr == PlayerStatus.sbptr) |
| | 268 | IsCharacterOrPlayer = 1; |
| | 269 | |
| | 270 | if(nextReport->ObstacleSBPtr->type == sbPtr->type) |
| | 271 | { |
| | 272 | IsCharacterOrPlayer = 1; |
| | 273 | details->otherCharacter = 1; |
| | 274 | details->anySingleObstruction = 1; |
| | 275 | } |
| | 276 | } |
| | 277 | |
| | 278 | { |
| | 279 | VECTORCH normVelocity = sbPtr->DynPtr->LinVelocity; |
| | 280 | Normalise(&normVelocity); |
| | 281 | normalDotWithVelocity = DotProduct(&nextReport->ObstacleNormal, &normVelocity); |
| | 282 | } |
| | 283 | |
| | 284 | // if((normalDotWithVelocity < -46341) && !IsCharacterOrPlayer) |
| | 285 | /* So aliens can break through windows, 19/5/98 CDF */ |
| | 286 | |
| | 287 | if(!IsCharacterOrPlayer && ((normalDotWithVelocity < -46341) || nextReport->ObstacleSBPtr)) |
| | 288 | { |
| | 289 | /* aha... got one....*/ |
| | 290 | moveData->numObstructiveCollisions++; |
| | 291 | |
| | 292 | if(moveData->numObstructiveCollisions > NPC_IMPEDING_COL_THRESHOLD) |
| | 293 | { |
| | 294 | moveData->numObstructiveCollisions = 0; |
| | 295 | details->anySingleObstruction = 1; |
| | 296 | details->environment = 1; |
| | 297 | |
| | 298 | if(nextReport->ObstacleSBPtr) |
| | 299 | { |
| | 300 | if(nextReport->ObstacleSBPtr->type == I_BehaviourInanimateObject) |
| | 301 | { |
| | 302 | if(!nextReport->ObstacleSBPtr->DamageBlock.Indestructable) |
| | 303 | { |
| | 304 | INANIMATEOBJECT_STATUSBLOCK* objectstatusptr = nextReport->ObstacleSBPtr->dataptr; |
| | 305 | |
| | 306 | if(objectstatusptr) |
| | 307 | { |
| | 308 | /* aha: an object which the npc can destroy... */ |
| | 309 | *destructableObject = nextReport->ObstacleSBPtr; |
| | 310 | details->destructableObject = 1; |
| | 311 | details->environment = 0; |
| | 312 | } |
| | 313 | } |
| | 314 | } |
| | 315 | } |
| | 316 | } |
| | 317 | /* if we have an obstructive collision, then return at this point |
| | 318 | to avoid resetting numObstructiveCollisions. NB we only want to |
| | 319 | record one per frame anyway... */ |
| | 320 | return; |
| | 321 | } |
| | 322 | |
| | 323 | nextReport = nextReport->NextCollisionReportPtr; |
| | 324 | } |
| | 325 | |
| | 326 | /* no obstructions this frame, then ... */ |
| | 327 | moveData->numObstructiveCollisions = 0; |
| | 328 | } |
| | 329 | |
| | 330 | int NPC_CannotReachTarget(NPC_MOVEMENTDATA *moveData, VECTORCH* thisTarget, VECTORCH* thisVelocity) |
| | 331 | { |
| | 332 | assert(moveData); |
| | 333 | assert(thisTarget); |
| | 334 | assert(thisVelocity); |
| | 335 | |
| | 336 | /* if movement data has zero velocity, update and return */ |
| | 337 | if(!moveData->lastVelocity.vx && !moveData->lastVelocity.vy && !moveData->lastVelocity.vz) |
| | 338 | { |
| | 339 | moveData->lastVelocity = *thisVelocity; |
| | 340 | moveData->lastTarget = *thisTarget; |
| | 341 | moveData->numReverses = 0; |
| | 342 | return 0; |
| | 343 | } |
| | 344 | |
| | 345 | /* if new velocity is zero, update and return */ |
| | 346 | if(!thisVelocity->vx && !thisVelocity->vy && !thisVelocity->vz) |
| | 347 | { |
| | 348 | moveData->lastVelocity = *thisVelocity; |
| | 349 | moveData->lastTarget = *thisTarget; |
| | 350 | moveData->numReverses = 0; |
| | 351 | return 0; |
| | 352 | } |
| | 353 | |
| | 354 | /* if move data target and this target are different, update and return*/ |
| | 355 | if((thisTarget->vx != moveData->lastTarget.vx)|| |
| | 356 | (thisTarget->vy != moveData->lastTarget.vy)|| |
| | 357 | (thisTarget->vz != moveData->lastTarget.vz)) |
| | 358 | { |
| | 359 | moveData->lastVelocity = *thisVelocity; |
| | 360 | moveData->lastTarget = *thisTarget; |
| | 361 | moveData->numReverses = 0; |
| | 362 | return 0; |
| | 363 | } |
| | 364 | |
| | 365 | /* at this point we have a previous velocity, a new velocity, |
| | 366 | and the previous target is the same as the current target... |
| | 367 | so compare previous and new velocities... */ |
| | 368 | |
| | 369 | if(DotProduct(&moveData->lastVelocity, thisVelocity) < (-56000)) /* 30 degrees */ |
| | 370 | { |
| | 371 | moveData->lastVelocity = *thisVelocity; |
| | 372 | moveData->lastTarget = *thisTarget; |
| | 373 | moveData->numReverses++; |
| | 374 | |
| | 375 | if(moveData->numReverses > 1) |
| | 376 | { |
| | 377 | moveData->numReverses = 0; |
| | 378 | return 1; |
| | 379 | } |
| | 380 | else |
| | 381 | return 0; |
| | 382 | } |
| | 383 | |
| | 384 | /* just update */ |
| | 385 | moveData->lastVelocity = *thisVelocity; |
| | 386 | moveData->lastTarget = *thisTarget; |
| | 387 | moveData->numReverses = 0; |
| | 388 | |
| | 389 | return 0; |
| | 390 | } |
| | 391 | |
| | 392 | /*------------------------------Patrick 14/2/97----------------------------------- |
| | 393 | Returns direction of movement to avoid obstructive collision in current |
| | 394 | direction of movement... |
| | 395 | -------------------------------------------------------------------------------*/ |
| | 396 | |
| | 397 | /* in this new version, the direction is taken from the npc's current direction (so |
| | 398 | that it will work in 3d, for aliens)... and is returned in velocityDirection */ |
| | 399 | |
| | 400 | void NPCGetAvoidanceDirection(STRATEGYBLOCK *sbPtr, VECTORCH *velocityDirection, NPC_OBSTRUCTIONREPORT *details) |
| | 401 | { |
| | 402 | assert(sbPtr); |
| | 403 | assert(sbPtr->DynPtr); |
| | 404 | assert(velocityDirection); |
| | 405 | |
| | 406 | /* init velcity direction */ |
| | 407 | velocityDirection->vx = velocityDirection->vy = velocityDirection->vz = 0; |
| | 408 | |
| | 409 | /* just in case */ |
| | 410 | if(!sbPtr->containingModule) |
| | 411 | return; |
| | 412 | |
| | 413 | if(details->environment || details->destructableObject || details->otherCharacter || details->anySingleObstruction) |
| | 414 | { |
| | 415 | VECTORCH newDirection1; |
| | 416 | VECTORCH newDirection2; |
| | 417 | int dir1dist = 0; |
| | 418 | int dir2dist = 0; |
| | 419 | /* going for a 90 degree turn + back a bit */ |
| | 420 | |
| | 421 | /* construct the direction(s)... |
| | 422 | start with object's local x unit vector (from local coo-ord system in world space) */ |
| | 423 | newDirection1.vx = sbPtr->DynPtr->OrientMat.mat11; |
| | 424 | newDirection1.vy = sbPtr->DynPtr->OrientMat.mat12; |
| | 425 | newDirection1.vz = sbPtr->DynPtr->OrientMat.mat13; |
| | 426 | newDirection2.vx = -newDirection1.vx; |
| | 427 | newDirection2.vy = -newDirection1.vy; |
| | 428 | newDirection2.vz = -newDirection1.vz; |
| | 429 | /* ...and add on 1/4 of the -z direction...*/ |
| | 430 | newDirection1.vx -= (sbPtr->DynPtr->OrientMat.mat31/4); |
| | 431 | newDirection1.vy -= (sbPtr->DynPtr->OrientMat.mat32/4); |
| | 432 | newDirection1.vz -= (sbPtr->DynPtr->OrientMat.mat33/4); |
| | 433 | newDirection2.vx -= (sbPtr->DynPtr->OrientMat.mat31/4); |
| | 434 | newDirection2.vy -= (sbPtr->DynPtr->OrientMat.mat32/4); |
| | 435 | newDirection2.vz -= (sbPtr->DynPtr->OrientMat.mat33/4); |
| | 436 | Normalise(&newDirection1); |
| | 437 | Normalise(&newDirection2); |
| | 438 | |
| | 439 | /* test how far we could go in each direction... */ |
| | 440 | { |
| | 441 | VECTORCH startingPosition = sbPtr->DynPtr->Position; |
| | 442 | VECTORCH testDirn = newDirection1; |
| | 443 | |
| | 444 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 445 | |
| | 446 | dir1dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 447 | |
| | 448 | startingPosition = sbPtr->DynPtr->Position; |
| | 449 | testDirn = newDirection2; |
| | 450 | |
| | 451 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 452 | |
| | 453 | dir2dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 454 | } |
| | 455 | |
| | 456 | *velocityDirection = (dir1dist > dir2dist) ? newDirection1 : newDirection2; |
| | 457 | } |
| | 458 | } |
| | 459 | |
| | 460 | /*------------------------Patrick 3/2/97--------------------------------- |
| | 461 | Sets an NPC's orientation so that it's z axis aligns to a given vector |
| | 462 | (modified from kevin's alien align to velocity routine) |
| | 463 | -----------------------------------------------------------------------*/ |
| | 464 | |
| | 465 | int NPCOrientateToVector(STRATEGYBLOCK *sbPtr, VECTORCH *zAxisVector, int turnspeed) |
| | 466 | { |
| | 467 | int turnThisFrame; |
| | 468 | int orientatedOk = 0; |
| | 469 | |
| | 470 | assert(sbPtr); |
| | 471 | assert(sbPtr->DynPtr); |
| | 472 | assert(zAxisVector); |
| | 473 | |
| | 474 | /* zero vector: nothing to do */ |
| | 475 | if(!zAxisVector->vx && !zAxisVector->vy && !zAxisVector->vz) |
| | 476 | return 1; |
| | 477 | |
| | 478 | VECTORCH localZAxisVector = *zAxisVector; |
| | 479 | |
| | 480 | /* rotate world zAxisVector into local space */ |
| | 481 | MATRIXCH toLocal = sbPtr->DynPtr->OrientMat; |
| | 482 | |
| | 483 | TransposeMatrixCH(&toLocal); |
| | 484 | RotateVector(&localZAxisVector, &toLocal); |
| | 485 | Normalise(&localZAxisVector); |
| | 486 | |
| | 487 | int maxTurnThisFrame = MUL_FIXED(NormalFrameTime, turnspeed); |
| | 488 | int localZVecEulerY = ArcTan(localZAxisVector.vx, localZAxisVector.vz); |
| | 489 | assert((localZVecEulerY >= 0) && (localZVecEulerY <= 4096)); |
| | 490 | |
| | 491 | /* if euler-y is 0 we are already aligned: nothing to do */ |
| | 492 | if(!localZVecEulerY || localZVecEulerY == 4096) |
| | 493 | return 1; |
| | 494 | |
| | 495 | if(localZVecEulerY > 2048) |
| | 496 | { |
| | 497 | if(localZVecEulerY > (4096 - maxTurnThisFrame)) |
| | 498 | { |
| | 499 | turnThisFrame = localZVecEulerY; |
| | 500 | orientatedOk = 1; |
| | 501 | } |
| | 502 | else |
| | 503 | { |
| | 504 | turnThisFrame = (4096 - maxTurnThisFrame); |
| | 505 | orientatedOk = 0; |
| | 506 | } |
| | 507 | } |
| | 508 | else |
| | 509 | { |
| | 510 | if(localZVecEulerY > maxTurnThisFrame) |
| | 511 | { |
| | 512 | turnThisFrame = maxTurnThisFrame; |
| | 513 | orientatedOk = 0; |
| | 514 | } |
| | 515 | else |
| | 516 | { |
| | 517 | turnThisFrame = localZVecEulerY; |
| | 518 | orientatedOk = 1; |
| | 519 | } |
| | 520 | } |
| | 521 | |
| | 522 | /* now convert into a matrix & multiply existing orientation by it ... */ |
| | 523 | { |
| | 524 | MATRIXCH mat; |
| | 525 | int cos = GetCos(turnThisFrame); |
| | 526 | int sin = GetSin(turnThisFrame); |
| | 527 | mat.mat11 = cos; |
| | 528 | mat.mat12 = 0; |
| | 529 | mat.mat13 = -sin; |
| | 530 | mat.mat21 = 0; |
| | 531 | mat.mat22 = 65536; |
| | 532 | mat.mat23 = 0; |
| | 533 | mat.mat31 = sin; |
| | 534 | mat.mat32 = 0; |
| | 535 | mat.mat33 = cos; |
| | 536 | |
| | 537 | MatrixMultiply(&sbPtr->DynPtr->OrientMat, &mat, &sbPtr->DynPtr->OrientMat); |
| | 538 | MatrixToEuler(&sbPtr->DynPtr->OrientMat, &sbPtr->DynPtr->OrientEuler); |
| | 539 | } |
| | 540 | |
| | 541 | return orientatedOk; |
| | 542 | } |
| | 543 | |
| | 544 | int NPCOrientateToVector2(STRATEGYBLOCK *sbPtr, VECTORCH *zAxisVector, int turnspeed) |
| | 545 | { |
| | 546 | /* zero vector: nothing to do */ |
| | 547 | if(!zAxisVector->vx && !zAxisVector->vy && !zAxisVector->vz) |
| | 548 | return 1; |
| | 549 | |
| | 550 | VECTORCH localZAxisVector = *zAxisVector; |
| | 551 | |
| | 552 | /* rotate world zAxisVector into local space */ |
| | 553 | MATRIXCH toLocal = sbPtr->DynPtr->OrientMat; |
| | 554 | |
| | 555 | TransposeMatrixCH(&toLocal); |
| | 556 | RotateVector(&localZAxisVector, &toLocal); |
| | 557 | Normalise(&localZAxisVector); |
| | 558 | |
| | 559 | int maxTurnThisFrame = MUL_FIXED(NormalFrameTime, turnspeed); |
| | 560 | int localZVecEulerY = ArcTan(localZAxisVector.vx, localZAxisVector.vz); |
| | 561 | assert((localZVecEulerY >= 0) && (localZVecEulerY <= 4096)); |
| | 562 | |
| | 563 | /* if euler-y is 0 we are already aligned: nothing to do */ |
| | 564 | if(!localZVecEulerY || localZVecEulerY == 4096) |
| | 565 | return 1; |
| | 566 | |
| | 567 | if(localZVecEulerY > 2048) |
| | 568 | return (localZVecEulerY > (4096 - maxTurnThisFrame)); |
| | 569 | else |
| | 570 | return !(localZVecEulerY > maxTurnThisFrame); |
| | 571 | } |
| | 572 | |
| | 573 | /* this function returns a target point for firing a projectile at the player*/ |
| | 574 | void NPCGetTargetPosition(VECTORCH *targetPoint, STRATEGYBLOCK *target) |
| | 575 | { |
| | 576 | assert(target); |
| | 577 | assert(targetPoint); |
| | 578 | |
| | 579 | if (target->DisplayBlock) |
| | 580 | { |
| | 581 | GetTargetingPointOfObject(target->DisplayBlock, targetPoint); |
| | 582 | } |
| | 583 | else |
| | 584 | { |
| | 585 | *targetPoint = target->DynPtr->Position; |
| | 586 | //targetPoint->vy -= 500; /* Just to be on the safe side. */ |
| | 587 | } |
| | 588 | } |
| | 589 | |
| | 590 | /*------------------------Patrick 31/1/97----------------------------- |
| | 591 | Returns 2d approach velocity and time for given NPC, target, and speed |
| | 592 | targetDirn must be a normalised vector direction. |
| | 593 | --------------------------------------------------------------------*/ |
| | 594 | |
| | 595 | int NPCSetVelocity(STRATEGYBLOCK *sbPtr, VECTORCH* targetDirn, int in_speed) |
| | 596 | { |
| | 597 | int orientated; |
| | 598 | /* Set up speed as local, so we can tamper with it. */ |
| | 599 | int accelerationThisFrame; |
| | 600 | int speed = in_speed; |
| | 601 | |
| | 602 | assert(sbPtr); |
| | 603 | assert(sbPtr->DynPtr); |
| | 604 | assert(targetDirn); |
| | 605 | |
| | 606 | /* |
| | 607 | set targetDirn.vy to 0 just in case: if NPCGetTargetDirn is used get the target |
| | 608 | direction, the y component should always be 0, but other target directions may be |
| | 609 | passed... |
| | 610 | */ |
| | 611 | |
| | 612 | // Okay, that was old. But maybe we need to do that unless UseStandardGravity is unset. |
| | 613 | |
| | 614 | switch(sbPtr->type) |
| | 615 | { |
| | 616 | case I_BehaviourMarine: |
| | 617 | { |
| | 618 | MARINE_STATUS_BLOCK *marineStatusPointer = (MARINE_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 619 | assert(marineStatusPointer); |
| | 620 | accelerationThisFrame = marineStatusPointer->acceleration; |
| | 621 | } |
| | 622 | break; |
| | 623 | case I_BehaviourPredator: |
| | 624 | { |
| | 625 | /* May need to change this based on state at some point? */ |
| | 626 | PREDATOR_STATUS_BLOCK *predatorStatusPointer = (PREDATOR_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 627 | assert(predatorStatusPointer); |
| | 628 | |
| | 629 | const MOVEMENT_DATA *movementData; |
| | 630 | switch(predatorStatusPointer->behaviourState) |
| | 631 | { |
| | 632 | case PBS_Avoidance: |
| | 633 | { |
| | 634 | if (predatorStatusPointer->lastState != PBS_Wandering) |
| | 635 | { |
| | 636 | movementData = GetThisMovementData(MDI_Predator); |
| | 637 | break; |
| | 638 | } |
| | 639 | } // no break for you |
| | 640 | case PBS_Wandering: |
| | 641 | { |
| | 642 | movementData = GetThisMovementData(MDI_Casual_Predator); |
| | 643 | /* Fix reduced max speed. */ |
| | 644 | speed = movementData->maxSpeed; |
| | 645 | } |
| | 646 | break; |
| | 647 | default: |
| | 648 | movementData = GetThisMovementData(MDI_Predator); |
| | 649 | } |
| | 650 | |
| | 651 | accelerationThisFrame = movementData->acceleration; |
| | 652 | } |
| | 653 | break; |
| | 654 | case I_BehaviourXenoborg: |
| | 655 | { |
| | 656 | const MOVEMENT_DATA *movementData = GetThisMovementData(MDI_Xenoborg); |
| | 657 | accelerationThisFrame = movementData->acceleration; |
| | 658 | } |
| | 659 | break; |
| | 660 | default: |
| | 661 | { |
| | 662 | if (sbPtr->DynPtr->UseStandardGravity) |
| | 663 | { |
| | 664 | targetDirn->vy = 0; |
| | 665 | |
| | 666 | /* first check for zero direction vector */ |
| | 667 | if(!targetDirn->vx && !targetDirn->vz) |
| | 668 | { |
| | 669 | sbPtr->DynPtr->LinVelocity.vx = sbPtr->DynPtr->LinVelocity.vy = sbPtr->DynPtr->LinVelocity.vz = 0; |
| | 670 | return 1; |
| | 671 | } |
| | 672 | } |
| | 673 | |
| | 674 | orientated = NPCOrientateToVector(sbPtr, targetDirn, NPC_TURNRATE); |
| | 675 | |
| | 676 | { |
| | 677 | VECTORCH velocity; |
| | 678 | VECTORCH yDirection; |
| | 679 | |
| | 680 | yDirection.vx = sbPtr->DynPtr->OrientMat.mat21; |
| | 681 | yDirection.vy = sbPtr->DynPtr->OrientMat.mat22; |
| | 682 | yDirection.vz = sbPtr->DynPtr->OrientMat.mat23; |
| | 683 | |
| | 684 | int dotProduct = DotProduct(&yDirection,targetDirn); |
| | 685 | |
| | 686 | velocity.vx = targetDirn->vx - MUL_FIXED(yDirection.vx,dotProduct); |
| | 687 | velocity.vy = targetDirn->vy - MUL_FIXED(yDirection.vy,dotProduct); |
| | 688 | velocity.vz = targetDirn->vz - MUL_FIXED(yDirection.vz,dotProduct); |
| | 689 | |
| | 690 | if ( !velocity.vx && !velocity.vy && !velocity.vz) |
| | 691 | { |
| | 692 | sbPtr->DynPtr->LinVelocity.vx = sbPtr->DynPtr->LinVelocity.vy = sbPtr->DynPtr->LinVelocity.vz = 0; |
| | 693 | return orientated; |
| | 694 | } |
| | 695 | |
| | 696 | Normalise(&velocity); |
| | 697 | |
| | 698 | sbPtr->DynPtr->LinVelocity.vx = MUL_FIXED(velocity.vx,speed); |
| | 699 | sbPtr->DynPtr->LinVelocity.vy = MUL_FIXED(velocity.vy,speed); |
| | 700 | sbPtr->DynPtr->LinVelocity.vz = MUL_FIXED(velocity.vz,speed); |
| | 701 | } |
| | 702 | return orientated; |
| | 703 | } |
| | 704 | } |
| | 705 | |
| | 706 | { |
| | 707 | VECTORCH deltaV,targetV,yDirection,movementOffset; |
| | 708 | |
| | 709 | /* Mode 2, for marines 'n' predators. And xenoborgs. */ |
| | 710 | |
| | 711 | /* I'll still believe in 'Speed'... but look up acceleration. */ |
| | 712 | /* I'll get that from outside in a minute, okay? */ |
| | 713 | /* Assume you're ground-based. */ |
| | 714 | |
| | 715 | targetDirn->vy = 0; |
| | 716 | |
| | 717 | /* first check for zero direction vector */ |
| | 718 | |
| | 719 | if(!targetDirn->vx && !targetDirn->vz) |
| | 720 | { |
| | 721 | /* For the moment, you can still stop on a dime. */ |
| | 722 | sbPtr->DynPtr->LinVelocity.vx = sbPtr->DynPtr->LinVelocity.vy = sbPtr->DynPtr->LinVelocity.vz = 0; |
| | 723 | return 1; |
| | 724 | } |
| | 725 | |
| | 726 | accelerationThisFrame = MUL_FIXED(accelerationThisFrame, NormalFrameTime); |
| | 727 | /* u+at, and all that, wot? */ |
| | 728 | |
| | 729 | yDirection.vx = sbPtr->DynPtr->OrientMat.mat21; |
| | 730 | yDirection.vy = sbPtr->DynPtr->OrientMat.mat22; |
| | 731 | yDirection.vz = sbPtr->DynPtr->OrientMat.mat23; |
| | 732 | |
| | 733 | int dotProduct = DotProduct(&yDirection,targetDirn); |
| | 734 | |
| | 735 | targetV.vx = targetDirn->vx - MUL_FIXED(yDirection.vx,dotProduct); |
| | 736 | targetV.vy = targetDirn->vy - MUL_FIXED(yDirection.vy,dotProduct); |
| | 737 | targetV.vz = targetDirn->vz - MUL_FIXED(yDirection.vz,dotProduct); |
| | 738 | |
| | 739 | if ( !targetV.vx && !targetV.vy && !targetV.vz) |
| | 740 | { |
| | 741 | sbPtr->DynPtr->LinVelocity.vx = sbPtr->DynPtr->LinVelocity.vy = sbPtr->DynPtr->LinVelocity.vz = 0; |
| | 742 | return 1; |
| | 743 | } |
| | 744 | |
| | 745 | Normalise(&targetV); |
| | 746 | |
| | 747 | targetV.vx = MUL_FIXED(targetV.vx,speed); |
| | 748 | targetV.vy = MUL_FIXED(targetV.vy,speed); |
| | 749 | targetV.vz = MUL_FIXED(targetV.vz,speed); |
| | 750 | |
| | 751 | deltaV.vx = targetV.vx-sbPtr->DynPtr->LinVelocity.vx; |
| | 752 | deltaV.vy = targetV.vy-sbPtr->DynPtr->LinVelocity.vy; |
| | 753 | deltaV.vz = targetV.vz-sbPtr->DynPtr->LinVelocity.vz; |
| | 754 | |
| | 755 | /* Now, deltaV is what we need to match. */ |
| | 756 | |
| | 757 | int deltaVMag = Approximate3dMagnitude(&deltaV); |
| | 758 | |
| | 759 | if (deltaVMag <= accelerationThisFrame) |
| | 760 | { |
| | 761 | int dotP,magV; |
| | 762 | VECTORCH normVelocity; |
| | 763 | |
| | 764 | sbPtr->DynPtr->LinVelocity = targetV; |
| | 765 | |
| | 766 | movementOffset.vx = sbPtr->DynPtr->Position.vx - sbPtr->DynPtr->PrevPosition.vx; |
| | 767 | movementOffset.vy = sbPtr->DynPtr->Position.vy - sbPtr->DynPtr->PrevPosition.vy; |
| | 768 | movementOffset.vz = sbPtr->DynPtr->Position.vz - sbPtr->DynPtr->PrevPosition.vz; |
| | 769 | |
| | 770 | normVelocity = sbPtr->DynPtr->LinVelocity; |
| | 771 | magV = Approximate3dMagnitude(&sbPtr->DynPtr->LinVelocity); |
| | 772 | |
| | 773 | Normalise(&movementOffset); |
| | 774 | Normalise(&normVelocity); |
| | 775 | dotP = DotProduct(&normVelocity,&movementOffset); |
| | 776 | |
| | 777 | /* To get a reasonable value out... */ |
| | 778 | if (magV < (speed >> 2)) |
| | 779 | { |
| | 780 | orientated = NPCOrientateToVector(sbPtr, &targetV, NPC_TURNRATE); |
| | 781 | } |
| | 782 | else if ((dotP > 40000) || (!movementOffset.vx && !movementOffset.vy && !movementOffset.vz)) |
| | 783 | { |
| | 784 | orientated = NPCOrientateToVector(sbPtr, &normVelocity, NPC_TURNRATE); |
| | 785 | } |
| | 786 | else |
| | 787 | { |
| | 788 | orientated = NPCOrientateToVector(sbPtr, &movementOffset, NPC_TURNRATE); |
| | 789 | } |
| | 790 | |
| | 791 | return orientated; |
| | 792 | } |
| | 793 | |
| | 794 | /* If we're here, we can't make the target velocity yet. */ |
| | 795 | Normalise(&deltaV); |
| | 796 | deltaV.vx = MUL_FIXED(deltaV.vx,accelerationThisFrame); |
| | 797 | deltaV.vy = MUL_FIXED(deltaV.vy,accelerationThisFrame); |
| | 798 | deltaV.vz = MUL_FIXED(deltaV.vz,accelerationThisFrame); |
| | 799 | |
| | 800 | sbPtr->DynPtr->LinVelocity.vx += deltaV.vx; |
| | 801 | sbPtr->DynPtr->LinVelocity.vy += deltaV.vy; |
| | 802 | sbPtr->DynPtr->LinVelocity.vz += deltaV.vz; |
| | 803 | |
| | 804 | movementOffset.vx = sbPtr->DynPtr->Position.vx - sbPtr->DynPtr->PrevPosition.vx; |
| | 805 | movementOffset.vy = sbPtr->DynPtr->Position.vy - sbPtr->DynPtr->PrevPosition.vy; |
| | 806 | movementOffset.vz = sbPtr->DynPtr->Position.vz - sbPtr->DynPtr->PrevPosition.vz; |
| | 807 | |
| | 808 | VECTORCH normVelocity = sbPtr->DynPtr->LinVelocity; |
| | 809 | int magV = Approximate3dMagnitude(&sbPtr->DynPtr->LinVelocity); |
| | 810 | |
| | 811 | Normalise(&movementOffset); |
| | 812 | Normalise(&normVelocity); |
| | 813 | int dotP = DotProduct(&normVelocity,&movementOffset); |
| | 814 | |
| | 815 | /* To get a reasonable value out... */ |
| | 816 | if (magV < (speed >> 2)) |
| | 817 | { |
| | 818 | orientated = NPCOrientateToVector(sbPtr, &targetV, NPC_TURNRATE); |
| | 819 | } |
| | 820 | else if ((dotP > 40000) || ((movementOffset.vx == 0) && (movementOffset.vy == 0) && (movementOffset.vz == 0))) |
| | 821 | { |
| | 822 | orientated = NPCOrientateToVector(sbPtr, &normVelocity, NPC_TURNRATE); |
| | 823 | } |
| | 824 | else |
| | 825 | { |
| | 826 | orientated = NPCOrientateToVector(sbPtr, &movementOffset, NPC_TURNRATE); |
| | 827 | } |
| | 828 | } |
| | 829 | |
| | 830 | return orientated; |
| | 831 | } |
| | 832 | |
| | 833 | int AIModuleIsVisible(AIMODULE *aimodule) |
| | 834 | { |
| | 835 | /* D'oh! */ |
| | 836 | MODULE **module_list = aimodule->m_module_ptrs; |
| | 837 | |
| | 838 | while (*module_list) |
| | 839 | { |
| | 840 | if (ModuleCurrVisArray[(*module_list)->m_index]) |
| | 841 | return 1; |
| | 842 | |
| | 843 | module_list++; |
| | 844 | } |
| | 845 | |
| | 846 | return 0; |
| | 847 | } |
| | 848 | |
| | 849 | /*------------------------Patrick 1/2/97----------------------------- |
| | 850 | Tries to find an ep in an adjacent module which can be used as a |
| | 851 | movement target for NPC. |
| | 852 | --------------------------------------------------------------------*/ |
| | 853 | |
| | 854 | static int NPCFindTargetEP(STRATEGYBLOCK *sbPtr, VECTORCH *targetPosn, AIMODULE **targetModule, int alien) |
| | 855 | { |
| | 856 | int aTargetExists = 0; |
| | 857 | |
| | 858 | AIMODULE **AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs; |
| | 859 | |
| | 860 | /* check if there is a module adjacency list */ |
| | 861 | if(AdjModuleRefPtr) |
| | 862 | { |
| | 863 | FARENTRYPOINT *bestEp; |
| | 864 | AIMODULE *bestModule = NULL; |
| | 865 | unsigned int bestSmell = 0; |
| | 866 | |
| | 867 | do |
| | 868 | { |
| | 869 | AIMODULE *nextAdjModule = *AdjModuleRefPtr++; |
| | 870 | |
| | 871 | if (AIModuleIsVisible(nextAdjModule)) |
| | 872 | { |
| | 873 | /* it is adjacent & visible ... */ |
| | 874 | FARENTRYPOINT *thisEp = GetAIModuleEP(nextAdjModule, sbPtr->containingModule->m_aimodule); |
| | 875 | |
| | 876 | if(thisEp && !(!alien && thisEp->alien_only)) |
| | 877 | { |
| | 878 | /* ... and has an ep, so test it's pheromone level */ |
| | 879 | if(PherPl_ReadBuf[(nextAdjModule->m_index)] > bestSmell) |
| | 880 | { |
| | 881 | bestSmell = PherPl_ReadBuf[(nextAdjModule->m_index)]; |
| | 882 | bestEp = thisEp; |
| | 883 | bestModule = nextAdjModule; |
| | 884 | aTargetExists = 1; |
| | 885 | } |
| | 886 | } |
| | 887 | } |
| | 888 | |
| | 889 | } while(*AdjModuleRefPtr != 0); |
| | 890 | |
| | 891 | if(aTargetExists) |
| | 892 | { |
| | 893 | *targetPosn = bestEp->position; |
| | 894 | *targetModule = bestModule; |
| | 895 | targetPosn->vx += bestModule->m_world.vx; |
| | 896 | targetPosn->vy += bestModule->m_world.vy; |
| | 897 | targetPosn->vz += bestModule->m_world.vz; |
| | 898 | assert(bestEp->donorIndex == sbPtr->containingModule->m_aimodule->m_index); |
| | 899 | } |
| | 900 | } |
| | 901 | |
| | 902 | return aTargetExists; |
| | 903 | } |
| | 904 | |
| | 905 | /*------------------------Patrick 11/2/97----------------------------- |
| | 906 | If we're not in the same module as the player, find an ep for a |
| | 907 | suitable target module, and return this. If cannot find an ep, |
| | 908 | or if the player is in our module, return the player's position. |
| | 909 | --------------------------------------------------------------------*/ |
| | 910 | |
| | 911 | void NPCGetMovementTarget(STRATEGYBLOCK *sbPtr, STRATEGYBLOCK *target, VECTORCH *targetPosition,int *targetIsAirduct,int alien) |
| | 912 | { |
| | 913 | assert(sbPtr); |
| | 914 | assert(targetPosition); |
| | 915 | assert(targetIsAirduct); |
| | 916 | assert(target); |
| | 917 | |
| | 918 | if (target == PlayerStatus.sbptr) |
| | 919 | { |
| | 920 | if(sbPtr->containingModule->m_aimodule != PlayerStatus.sbptr->containingModule->m_aimodule) |
| | 921 | { |
| | 922 | VECTORCH epPosn; |
| | 923 | AIMODULE *epModule; |
| | 924 | |
| | 925 | if(NPCFindTargetEP(sbPtr, &epPosn, &epModule, alien)) |
| | 926 | { |
| | 927 | *targetPosition = epPosn; |
| | 928 | *targetIsAirduct = ((*epModule->m_module_ptrs)->m_flags & MODULEFLAG_AIRDUCT); |
| | 929 | return; |
| | 930 | } |
| | 931 | } |
| | 932 | |
| | 933 | /* in same module as player, or can't find an entry point */ |
| | 934 | //*targetPosition = PlayerStatus.DisplayBlock->ObWorld; |
| | 935 | GetTargetingPointOfObject(PlayerStatus.DisplayBlock, targetPosition); |
| | 936 | *targetIsAirduct = 0; |
| | 937 | } |
| | 938 | else |
| | 939 | { |
| | 940 | /* Improve this presently */ |
| | 941 | //*targetPosition = target->DynPtr->Position; |
| | 942 | GetTargetingPointOfObject_Far(target, targetPosition); |
| | 943 | *targetIsAirduct = 0; |
| | 944 | } |
| | 945 | } |
| | 946 | |
| | 947 | /*------------------------------Patrick 24/3/97----------------------------------- |
| | 948 | Calculates best direction of movement for NPC, from our main target direction: |
| | 949 | 1. If on same poly as player, move towards him/her; |
| | 950 | 2. Otherwise, find best adjacent floor polygon to move towards our target. |
| | 951 | |
| | 952 | NB works in 2d... |
| | 953 | -------------------------------------------------------------------------------*/ |
| | 954 | extern int SetupPolygonAccessFromShapeIndex(int shapeIndex); |
| | 955 | |
| | 956 | static VECTORCH GMD_myPolyPoints[4]; |
| | 957 | static int GMD_myPolyNumPoints; |
| | 958 | static VECTORCH myPolyEdgePoints[4]; |
| | 959 | static VECTORCH myPolyEdgeDirections[4]; |
| | 960 | static VECTORCH myPolyEdgeNormals[4]; |
| | 961 | static VECTORCH myPolyMidPoint; |
| | 962 | static int myPolyEdgeMoveDistances[4]; |
| | 963 | int ShowPredoStats = 0; |
| | 964 | |
| | 965 | static int IsMyPolyRidiculous() |
| | 966 | { |
| | 967 | int a = 0; |
| | 968 | |
| | 969 | /* Please make sure the globals are sensible! */ |
| | 970 | |
| | 971 | for (; a < GMD_myPolyNumPoints; a++) |
| | 972 | { |
| | 973 | VECTORCH side; |
| | 974 | int sideend; |
| | 975 | |
| | 976 | if (a >= (GMD_myPolyNumPoints-1)) |
| | 977 | sideend = 0; |
| | 978 | else |
| | 979 | sideend = a + 1; |
| | 980 | |
| | 981 | side.vx = GMD_myPolyPoints[a].vx-GMD_myPolyPoints[sideend].vx; |
| | 982 | side.vy = GMD_myPolyPoints[a].vy-GMD_myPolyPoints[sideend].vy; |
| | 983 | side.vz = GMD_myPolyPoints[a].vz-GMD_myPolyPoints[sideend].vz; |
| | 984 | |
| | 985 | /* Stoopid! */ |
| | 986 | if (Approximate3dMagnitude(&side) < 1000) |
| | 987 | return 1; |
| | 988 | } |
| | 989 | |
| | 990 | return 0; |
| | 991 | } |
| | 992 | |
| | 993 | /* Patrick 12/6/97: This function is an auxilary function to NPCGetMovementDirection(), and |
| | 994 | adds a curve to the edge point direction, weighted towards the centre of the current poly */ |
| | 995 | |
| | 996 | static void NPCFindCurveToEdgePoint(STRATEGYBLOCK *sbPtr, int edgeIndex, VECTORCH *velocityDirection) |
| | 997 | { |
| | 998 | VECTORCH curvedPath; |
| | 999 | VECTORCH dirnToCentre; |
| | 1000 | |
| | 1001 | assert(sbPtr); |
| | 1002 | assert(velocityDirection); |
| | 1003 | |
| | 1004 | /* default direction- just in case something goes wrong */ |
| | 1005 | *velocityDirection = myPolyEdgeDirections[edgeIndex]; |
| | 1006 | |
| | 1007 | /* find dirn to centre of poly */ |
| | 1008 | dirnToCentre.vx = myPolyMidPoint.vx - sbPtr->DynPtr->Position.vx; |
| | 1009 | dirnToCentre.vz = myPolyMidPoint.vz - sbPtr->DynPtr->Position.vz; |
| | 1010 | dirnToCentre.vy = 0; |
| | 1011 | |
| | 1012 | if(!dirnToCentre.vx && !dirnToCentre.vz) |
| | 1013 | return; |
| | 1014 | |
| | 1015 | Normalise(&dirnToCentre); |
| | 1016 | |
| | 1017 | /* calculated weighted vector to centre */ |
| | 1018 | int weighting = DotProduct(&myPolyEdgeDirections[edgeIndex],&dirnToCentre); |
| | 1019 | |
| | 1020 | if(!weighting) |
| | 1021 | return; |
| | 1022 | |
| | 1023 | dirnToCentre.vx = WideMulNarrowDiv(dirnToCentre.vx,weighting,ONE_FIXED); |
| | 1024 | dirnToCentre.vz = WideMulNarrowDiv(dirnToCentre.vz,weighting,ONE_FIXED); |
| | 1025 | |
| | 1026 | /* add them to find curved direction path */ |
| | 1027 | curvedPath.vx = myPolyEdgeDirections[edgeIndex].vx + dirnToCentre.vx; |
| | 1028 | curvedPath.vz = myPolyEdgeDirections[edgeIndex].vz + dirnToCentre.vz; |
| | 1029 | curvedPath.vy = 0; |
| | 1030 | Normalise(&curvedPath); |
| | 1031 | *velocityDirection = curvedPath; |
| | 1032 | } |
| | 1033 | |
| | 1034 | /*------------------------Patrick 24/3/97----------------------------- |
| | 1035 | This function is used to determine an intersection between 2 line |
| | 1036 | segments. The passed segment end points have been transformed into |
| | 1037 | a space where the second segment is aligned to the z axis, and extends |
| | 1038 | from 0 to the passed extent parameter. This makes the intersection |
| | 1039 | test easy... |
| | 1040 | --------------------------------------------------------------------*/ |
| | 1041 | |
| | 1042 | static int VectorIntersects2dZVector(VECTORCH *vecStart,VECTORCH *vecEnd, int zExtent) |
| | 1043 | { |
| | 1044 | int vecIntercept; |
| | 1045 | assert(vecStart); |
| | 1046 | assert(vecEnd); |
| | 1047 | |
| | 1048 | if((vecStart->vx < 0) && (vecEnd->vx < 0)) |
| | 1049 | return 0; |
| | 1050 | |
| | 1051 | if((vecStart->vx > 0) && (vecEnd->vx > 0)) |
| | 1052 | return 0; |
| | 1053 | |
| | 1054 | if((vecStart->vz < 0) && (vecEnd->vz < 0)) |
| | 1055 | return 0; |
| | 1056 | |
| | 1057 | if((vecStart->vz > zExtent)&&(vecEnd->vz > zExtent)) |
| | 1058 | return 0; |
| | 1059 | |
| | 1060 | vecIntercept = vecStart->vz + WideMulNarrowDiv((vecEnd->vz - vecStart->vz),vecStart->vx,(vecEnd->vx - vecStart->vx)); |
| | 1061 | |
| | 1062 | if((vecIntercept > 0) && (vecIntercept < zExtent)) |
| | 1063 | return 1; /* (deliberately ignoring endpoints) */ |
| | 1064 | return 0; |
| | 1065 | } |
| | 1066 | |
| | 1067 | /*------------------------Patrick 28/1/99----------------------------- |
| | 1068 | Tries to find a floor polygon for a given world space location in |
| | 1069 | a given module. Early exit if the location isn't even in the module... |
| | 1070 | --------------------------------------------------------------------*/ |
| | 1071 | |
| | 1072 | static int CheckMyFloorPoly(VECTORCH* currentPosition, MODULE* currentModule) |
| | 1073 | { |
| | 1074 | struct ColPolyTag polygonData; |
| | 1075 | int positionPoints[2]; |
| | 1076 | VECTORCH localPosition; |
| | 1077 | int numPolys; |
| | 1078 | int polyCounter; |
| | 1079 | int polyFound = 0; |
| | 1080 | int polyFoundIndex = 0; |
| | 1081 | |
| | 1082 | assert(currentPosition); |
| | 1083 | assert(currentModule); |
| | 1084 | |
| | 1085 | /* first, get the local position */ |
| | 1086 | localPosition.vx = currentPosition->vx - currentModule->m_world.vx; |
| | 1087 | localPosition.vy = currentPosition->vy - currentModule->m_world.vy; |
| | 1088 | localPosition.vz = currentPosition->vz - currentModule->m_world.vz; |
| | 1089 | |
| | 1090 | if(!PointIsInModule(currentModule, &localPosition) ) |
| | 1091 | { |
| | 1092 | // Whoops ! I'm looking in the wrong module. Better just forget it. |
| | 1093 | return NPC_GMD_NOPOLY; |
| | 1094 | } |
| | 1095 | |
| | 1096 | /* set up position points for object*/ |
| | 1097 | positionPoints[0] = localPosition.vx; |
| | 1098 | positionPoints[1] = localPosition.vz; |
| | 1099 | |
| | 1100 | numPolys = SetupPolygonAccessFromShapeIndex(currentModule->m_mapptr->MapShape); |
| | 1101 | polyCounter = numPolys; |
| | 1102 | |
| | 1103 | /* loop through the item list, then ... */ |
| | 1104 | while((polyCounter > 0) && !polyFound) |
| | 1105 | { |
| | 1106 | { |
| | 1107 | extern int **ItemArrayPtr; |
| | 1108 | extern POLYHEADER *PolyheaderPtr; |
| | 1109 | int *itemPtr = *(ItemArrayPtr++); |
| | 1110 | PolyheaderPtr = (POLYHEADER *) itemPtr; |
| | 1111 | } |
| | 1112 | |
| | 1113 | GetPolygonVertices(&polygonData); |
| | 1114 | GetPolygonNormal(&polygonData); |
| | 1115 | |
| | 1116 | /* first of all, reject any that don't have an up normal */ |
| | 1117 | if(polygonData.PolyNormal.vy < 0) |
| | 1118 | { |
| | 1119 | /* set up poly points for containment test */ |
| | 1120 | int polyPoints[10]; |
| | 1121 | int numPtsInPoly = polygonData.NumberOfVertices; |
| | 1122 | int i = 0; |
| | 1123 | |
| | 1124 | for(; i < numPtsInPoly; i++) |
| | 1125 | { |
| | 1126 | polyPoints[(i*2)] = polygonData.PolyPoint[i].vx; |
| | 1127 | polyPoints[((i*2)+1)] = polygonData.PolyPoint[i].vz; |
| | 1128 | } |
| | 1129 | |
| | 1130 | if (PointInPolygon(&positionPoints[0], &polyPoints[0], numPtsInPoly,2)) |
| | 1131 | { |
| | 1132 | polyFound = 1; |
| | 1133 | polyFoundIndex = numPolys - polyCounter; |
| | 1134 | } |
| | 1135 | } |
| | 1136 | |
| | 1137 | polyCounter--; |
| | 1138 | } |
| | 1139 | |
| | 1140 | /* Init some globals */ |
| | 1141 | { |
| | 1142 | int i=0; |
| | 1143 | GMD_myPolyNumPoints = 0; |
| | 1144 | |
| | 1145 | for(; i < 4; i++) |
| | 1146 | GMD_myPolyPoints[i].vx = GMD_myPolyPoints[i].vy = GMD_myPolyPoints[i].vz = -1; |
| | 1147 | } |
| | 1148 | |
| | 1149 | /* if we haven't found a poly, return NPC_GMD_NOPOLY. Otherwise, return the index and fill |
| | 1150 | out some globals... */ |
| | 1151 | |
| | 1152 | if(!polyFound) |
| | 1153 | return NPC_GMD_NOPOLY; |
| | 1154 | |
| | 1155 | assert(polyFoundIndex >= 0); |
| | 1156 | assert(polyFound < numPolys); |
| | 1157 | |
| | 1158 | GMD_myPolyNumPoints = polygonData.NumberOfVertices; |
| | 1159 | GMD_myPolyPoints[0] = polygonData.PolyPoint[0]; |
| | 1160 | GMD_myPolyPoints[1] = polygonData.PolyPoint[1]; |
| | 1161 | GMD_myPolyPoints[2] = polygonData.PolyPoint[2]; |
| | 1162 | |
| | 1163 | if(GMD_myPolyNumPoints > 3) |
| | 1164 | GMD_myPolyPoints[3] = polygonData.PolyPoint[3]; |
| | 1165 | |
| | 1166 | return polyFoundIndex; |
| | 1167 | } |
| | 1168 | |
| | 1169 | void NPCGetMovementDirection(STRATEGYBLOCK *sbPtr, VECTORCH *velocityDirection, VECTORCH *targetPosition, WAYPOINT_MANAGER *waypointManager) |
| | 1170 | { |
| | 1171 | VECTORCH targetDirection; |
| | 1172 | int i; |
| | 1173 | int playerPoly = NPC_GMD_NOPOLY; |
| | 1174 | int ourPolyThisFrame = NPC_GMD_NOPOLY; |
| | 1175 | |
| | 1176 | assert(sbPtr); |
| | 1177 | assert(sbPtr->DynPtr); |
| | 1178 | assert(velocityDirection); |
| | 1179 | assert(targetPosition); |
| | 1180 | |
| | 1181 | if (sbPtr->containingModule == NULL) |
| | 1182 | { |
| | 1183 | /* Oops. */ |
| | 1184 | targetDirection = *targetPosition; |
| | 1185 | targetDirection.vx -= sbPtr->DynPtr->Position.vx; |
| | 1186 | targetDirection.vz -= sbPtr->DynPtr->Position.vz; |
| | 1187 | targetDirection.vy = 0; |
| | 1188 | Normalise(&targetDirection); |
| | 1189 | *velocityDirection = targetDirection; |
| | 1190 | return; |
| | 1191 | } |
| | 1192 | else if ((sbPtr->containingModule->m_aimodule->m_waypoints != NULL) && waypointManager) |
| | 1193 | { |
| | 1194 | if (NPCGetWaypointDirection(sbPtr->containingModule->m_aimodule->m_waypoints, sbPtr, velocityDirection, targetPosition, waypointManager)) |
| | 1195 | return; /* Success! */ |
| | 1196 | } |
| | 1197 | |
| | 1198 | /* First get the (2d) direction of the main target */ |
| | 1199 | targetDirection = *targetPosition; |
| | 1200 | targetDirection.vx -= sbPtr->DynPtr->Position.vx; |
| | 1201 | targetDirection.vz -= sbPtr->DynPtr->Position.vz; |
| | 1202 | /* If we got here, we *should* not be a crawling alien... */ |
| | 1203 | |
| | 1204 | if ((sbPtr->type == I_BehaviourAlien) && !sbPtr->DynPtr->UseStandardGravity) |
| | 1205 | { |
| | 1206 | targetDirection.vy -= sbPtr->DynPtr->Position.vy; |
| | 1207 | } |
| | 1208 | else |
| | 1209 | { |
| | 1210 | /* |
| | 1211 | // Non-planar adjacency warnings? |
| | 1212 | targetDirection.vy -= sbPtr->DynPtr->Position.vy; |
| | 1213 | Normalise(&targetDirection); |
| | 1214 | |
| | 1215 | if (targetDirection.vy < -46000) |
| | 1216 | printf("Non-planar adjacency!\n"); |
| | 1217 | */ |
| | 1218 | |
| | 1219 | targetDirection.vy = 0; |
| | 1220 | } |
| | 1221 | |
| | 1222 | Normalise(&targetDirection); |
| | 1223 | |
| | 1224 | /* first- a hack to cope with stairs and al those little polygons: |
| | 1225 | if we're in stairs just return the target direction. This is okay aslong as we don't |
| | 1226 | have curved stairs*/ |
| | 1227 | |
| | 1228 | if(sbPtr->containingModule->m_flags & MODULEFLAG_STAIRS) |
| | 1229 | { |
| | 1230 | *velocityDirection = targetDirection; |
| | 1231 | return; |
| | 1232 | } |
| | 1233 | |
| | 1234 | playerPoly = CheckMyFloorPoly(&PlayerStatus.DisplayBlock->ObWorld, PlayerStatus.sbptr->containingModule); |
| | 1235 | ourPolyThisFrame = CheckMyFloorPoly(&(sbPtr->DynPtr->Position), sbPtr->containingModule); |
| | 1236 | |
| | 1237 | /* Check for not having a current poly: this seems to happen occasionally |
| | 1238 | around module boundaries- npc just needs a jolt... */ |
| | 1239 | |
| | 1240 | if((ourPolyThisFrame == NPC_GMD_NOPOLY) || IsMyPolyRidiculous()) |
| | 1241 | { |
| | 1242 | *velocityDirection = targetDirection; |
| | 1243 | return; |
| | 1244 | } |
| | 1245 | |
| | 1246 | /* 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 */ |
| | 1247 | |
| | 1248 | if((sbPtr != PlayerStatus.sbptr) && (sbPtr->containingModule == PlayerStatus.sbptr->containingModule) && (playerPoly !=
NPC_GMD_NOPOLY)) |
| | 1249 | { |
| | 1250 | if(playerPoly == ourPolyThisFrame) |
| | 1251 | { |
| | 1252 | /* cripes- we're on the same poly */ |
| | 1253 | *velocityDirection = targetDirection; |
| | 1254 | return; |
| | 1255 | } |
| | 1256 | } |
| | 1257 | |
| | 1258 | /* Now get all the data we need: |
| | 1259 | 1. World space coords of poly edge mid points |
| | 1260 | 2. Directions from npc to those points |
| | 1261 | 3. Midpoint of our polygon. |
| | 1262 | 3a Some error checking. |
| | 1263 | 4. Distances we could move from poly midpoint to edge midpoint before hitting something |
| | 1264 | */ |
| | 1265 | |
| | 1266 | for(i=0; i < GMD_myPolyNumPoints; i++) |
| | 1267 | { |
| | 1268 | int point1 = i; |
| | 1269 | int point2 = i+1; |
| | 1270 | |
| | 1271 | if(point2 >= GMD_myPolyNumPoints) |
| | 1272 | point2 = 0; |
| | 1273 | |
| | 1274 | { |
| | 1275 | /* find the edge out normal - NB this won't work for clockwise polygons (2d) */ |
| | 1276 | VECTORCH upNormal = {0,-65536,0}; |
| | 1277 | VECTORCH edgeVector; |
| | 1278 | |
| | 1279 | edgeVector.vx = GMD_myPolyPoints[point2].vx - GMD_myPolyPoints[point1].vx; |
| | 1280 | edgeVector.vy = 0; |
| | 1281 | edgeVector.vz = GMD_myPolyPoints[point2].vz - GMD_myPolyPoints[point1].vz; |
| | 1282 | |
| | 1283 | CrossProduct(&edgeVector,&upNormal,&myPolyEdgeNormals[i]); |
| | 1284 | Normalise(&myPolyEdgeNormals[i]); |
| | 1285 | assert(myPolyEdgeNormals[i].vy == 0); |
| | 1286 | } |
| | 1287 | |
| | 1288 | /* 1 : Calculate edge midpoint (2d) */ |
| | 1289 | myPolyEdgePoints[i].vx = ((GMD_myPolyPoints[point1].vx + GMD_myPolyPoints[point2].vx)/2)+MUL_FIXED(myPolyEdgeNormals[i].vx,50); |
| | 1290 | myPolyEdgePoints[i].vy = 0; |
| | 1291 | myPolyEdgePoints[i].vz = ((GMD_myPolyPoints[point1].vz + GMD_myPolyPoints[point2].vz)/2)+MUL_FIXED(myPolyEdgeNormals[i].vz,50); |
| | 1292 | /* Into world space*/ |
| | 1293 | myPolyEdgePoints[i].vx += sbPtr->containingModule->m_world.vx; |
| | 1294 | myPolyEdgePoints[i].vz += sbPtr->containingModule->m_world.vz; |
| | 1295 | |
| | 1296 | /* 2 : Directions to those points */ |
| | 1297 | myPolyEdgeDirections[i].vx = myPolyEdgePoints[i].vx - sbPtr->DynPtr->Position.vx; |
| | 1298 | myPolyEdgeDirections[i].vy = 0; |
| | 1299 | myPolyEdgeDirections[i].vz = myPolyEdgePoints[i].vz - sbPtr->DynPtr->Position.vz; |
| | 1300 | Normalise(&myPolyEdgeDirections[i]); |
| | 1301 | } |
| | 1302 | |
| | 1303 | /* 3 : Poly midpoint- actually just an approximation, but doesn't matter |
| | 1304 | as long as its inside the poly (in world space): |
| | 1305 | NB |
| | 1306 | Quads are done by finding the midpoint of a diagonal. |
| | 1307 | Triangles bisect a side then take the midpoint of that and the third point, else |
| | 1308 | they can end up with a midpoint on one of their sides which buggers things up.*/ |
| | 1309 | |
| | 1310 | if(GMD_myPolyNumPoints == 3) |
| | 1311 | { |
| | 1312 | VECTORCH bisect; |
| | 1313 | bisect.vy = ((GMD_myPolyPoints[1].vy + GMD_myPolyPoints[2].vy)/2); |
| | 1314 | bisect.vx = ((GMD_myPolyPoints[1].vx + GMD_myPolyPoints[2].vx)/2); |
| | 1315 | bisect.vz = ((GMD_myPolyPoints[1].vz + GMD_myPolyPoints[2].vz)/2); |
| | 1316 | myPolyMidPoint.vy = ((GMD_myPolyPoints[0].vy + bisect.vy)/2)+sbPtr->containingModule->m_world.vy; |
| | 1317 | myPolyMidPoint.vx = ((GMD_myPolyPoints[0].vx + bisect.vx)/2)+sbPtr->containingModule->m_world.vx; |
| | 1318 | myPolyMidPoint.vz = ((GMD_myPolyPoints[0].vz + bisect.vz)/2)+sbPtr->containingModule->m_world.vz; |
| | 1319 | } |
| | 1320 | else |
| | 1321 | { |
| | 1322 | myPolyMidPoint.vy = ((GMD_myPolyPoints[0].vy + GMD_myPolyPoints[2].vy)/2)+sbPtr->containingModule->m_world.vy; |
| | 1323 | myPolyMidPoint.vx = ((GMD_myPolyPoints[0].vx + GMD_myPolyPoints[2].vx)/2)+sbPtr->containingModule->m_world.vx; |
| | 1324 | myPolyMidPoint.vz = ((GMD_myPolyPoints[0].vz + GMD_myPolyPoints[2].vz)/2)+sbPtr->containingModule->m_world.vz; |
| | 1325 | } |
| | 1326 | |
| | 1327 | /* Error trapping: |
| | 1328 | 1. if midpoint is not in our polygon, just move to target |
| | 1329 | 2. If main target is in our poly, just move to target |
| | 1330 | 3. If any edge points are in our poly, just move to the target also |
| | 1331 | */ |
| | 1332 | { |
| | 1333 | int edgePoint[2]; |
| | 1334 | int polyPoints[10]; |
| | 1335 | int j; |
| | 1336 | |
| | 1337 | for(j=0; j < GMD_myPolyNumPoints; j++) |
| | 1338 | { |
| | 1339 | polyPoints[(j*2)] = GMD_myPolyPoints[j].vx; |
| | 1340 | polyPoints[(j*2)+1] = GMD_myPolyPoints[j].vz; |
| | 1341 | } |
| | 1342 | |
| | 1343 | /* Edge points (in poly local space) */ |
| | 1344 | for(j=0; j < GMD_myPolyNumPoints; j++) |
| | 1345 | { |
| | 1346 | edgePoint[0] = myPolyEdgePoints[j].vx - sbPtr->containingModule->m_world.vx; |
| | 1347 | edgePoint[1] = myPolyEdgePoints[j].vz - sbPtr->containingModule->m_world.vz; |
| | 1348 | |
| | 1349 | if(PointInPolygon(&edgePoint[0], &polyPoints[0], GMD_myPolyNumPoints, 2)) |
| | 1350 | { |
| | 1351 | /* one of the edge points is inside our poly */ |
| | 1352 | *velocityDirection = targetDirection; |
| | 1353 | return; |
| | 1354 | } |
| | 1355 | } |
| | 1356 | |
| | 1357 | /* Mid point (in poly local space) */ |
| | 1358 | edgePoint[0] = myPolyMidPoint.vx - sbPtr->containingModule->m_world.vx; |
| | 1359 | edgePoint[1] = myPolyMidPoint.vz - sbPtr->containingModule->m_world.vz; |
| | 1360 | |
| | 1361 | if(!PointInPolygon(&edgePoint[0], &polyPoints[0], GMD_myPolyNumPoints,2)) |
| | 1362 | { |
| | 1363 | /* the midpoint is inside our poly */ |
| | 1364 | *velocityDirection = targetDirection; |
| | 1365 | return; |
| | 1366 | } |
| | 1367 | |
| | 1368 | /* Target point (in poly local space)*/ |
| | 1369 | edgePoint[0] = targetPosition->vx - sbPtr->containingModule->m_world.vx; |
| | 1370 | edgePoint[1] = targetPosition->vz - sbPtr->containingModule->m_world.vz; |
| | 1371 | |
| | 1372 | if(PointInPolygon(&edgePoint[0], &polyPoints[0], GMD_myPolyNumPoints,2)) |
| | 1373 | { |
| | 1374 | /* the main target point is inside our poly:- this shouldn't happen, |
| | 1375 | as we have already tested for the player earlier on */ |
| | 1376 | *velocityDirection = targetDirection; |
| | 1377 | return; |
| | 1378 | } |
| | 1379 | } |
| | 1380 | |
| | 1381 | /* 4 : Finally, the distances that we can move from the poly midpoint beyond |
| | 1382 | each edge midpoint before we hit something... |
| | 1383 | */ |
| | 1384 | { |
| | 1385 | for(i=0; i < GMD_myPolyNumPoints; i++) |
| | 1386 | { |
| | 1387 | VECTORCH centreToEdgeVector; |
| | 1388 | int centreToEdgeDistance; |
| | 1389 | |
| | 1390 | centreToEdgeVector.vx = myPolyEdgePoints[i].vx - myPolyMidPoint.vx; |
| | 1391 | centreToEdgeVector.vz = myPolyEdgePoints[i].vz - myPolyMidPoint.vz; |
| | 1392 | centreToEdgeVector.vy = 0; |
| | 1393 | centreToEdgeDistance = Magnitude(¢reToEdgeVector); |
| | 1394 | |
| | 1395 | /* how far we can move along the centre to edge direction */ |
| | 1396 | { |
| | 1397 | VECTORCH startingPosition = myPolyMidPoint; |
| | 1398 | VECTORCH testDirn = centreToEdgeVector; |
| | 1399 | |
| | 1400 | /* poly mid point is in 3d world space: we need to do this |
| | 1401 | test at the height of the npc*/ |
| | 1402 | startingPosition.vy = myPolyMidPoint.vy - 1500; |
| | 1403 | /*startingPosition.vy = sbPtr->DynPtr->Position.vy;*/ |
| | 1404 | /* Normalise the test direction */ |
| | 1405 | Normalise(&testDirn); |
| | 1406 | |
| | 1407 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 1408 | |
| | 1409 | myPolyEdgeMoveDistances[i] = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 1410 | } |
| | 1411 | |
| | 1412 | /* how far we can move beyond the poly edge */ |
| | 1413 | myPolyEdgeMoveDistances[i] -= centreToEdgeDistance; |
| | 1414 | |
| | 1415 | /* quick check to eliminate Saturn type dodgy triangle points: |
| | 1416 | by setting move distance to zero, we should never select this edge as our |
| | 1417 | target edge... */ |
| | 1418 | |
| | 1419 | { |
| | 1420 | int point1 = i; |
| | 1421 | int point2 = i+1; |
| | 1422 | |
| | 1423 | if(point2 >= GMD_myPolyNumPoints) |
| | 1424 | point2 = 0; |
| | 1425 | |
| | 1426 | if( (GMD_myPolyPoints[point1].vx == GMD_myPolyPoints[point2].vx) && |
| | 1427 | (GMD_myPolyPoints[point1].vy == GMD_myPolyPoints[point2].vy) && |
| | 1428 | (GMD_myPolyPoints[point1].vz == GMD_myPolyPoints[point2].vz)) |
| | 1429 | { |
| | 1430 | assert(1==0); |
| | 1431 | |
| | 1432 | myPolyEdgeMoveDistances[i] = 0; |
| | 1433 | } |
| | 1434 | } |
| | 1435 | } |
| | 1436 | } |
| | 1437 | |
| | 1438 | /* Now the crucial bit:- try to find an edge in our polygon that we can move towards |
| | 1439 | and which is intersected by the vector from the mid point to the main target. If |
| | 1440 | we find one, this is our edge! */ |
| | 1441 | |
| | 1442 | for(i=0; i < GMD_myPolyNumPoints; i++) |
| | 1443 | { |
| | 1444 | if(myPolyEdgeMoveDistances[i] > NPC_MIN_MOVEFROMPOLYDIST) |
| | 1445 | { |
| | 1446 | int vecExtent; |
| | 1447 | int ePoint1, ePoint2; |
| | 1448 | VECTORCH endPoint1, endPoint2; |
| | 1449 | VECTORCH edgeVector; |
| | 1450 | MATRIXCH edgeMatrix; |
| | 1451 | |
| | 1452 | ePoint1 = i; |
| | 1453 | ePoint2 = i+1; |
| | 1454 | |
| | 1455 | if(ePoint2>=GMD_myPolyNumPoints) |
| | 1456 | ePoint2 = 0; |
| | 1457 | |
| | 1458 | edgeVector.vx = GMD_myPolyPoints[ePoint2].vx - GMD_myPolyPoints[ePoint1].vx; |
| | 1459 | edgeVector.vy = 0; |
| | 1460 | edgeVector.vz = GMD_myPolyPoints[ePoint2].vz - GMD_myPolyPoints[ePoint1].vz; |
| | 1461 | |
| | 1462 | Normalise(&edgeVector); |
| | 1463 | vecExtent = Magnitude(&edgeVector); |
| | 1464 | |
| | 1465 | /* This IS the right way around */ |
| | 1466 | edgeMatrix.mat11 = myPolyEdgeNormals[i].vx; |
| | 1467 | edgeMatrix.mat21 = 0; |
| | 1468 | edgeMatrix.mat31 = myPolyEdgeNormals[i].vz; |
| | 1469 | edgeMatrix.mat12 = 0; |
| | 1470 | edgeMatrix.mat22 = 65536; |
| | 1471 | edgeMatrix.mat32 = 0; |
| | 1472 | edgeMatrix.mat13 = edgeVector.vx; |
| | 1473 | edgeMatrix.mat23 = 0; |
| | 1474 | edgeMatrix.mat33 = edgeVector.vz; |
| | 1475 | |
| | 1476 | /* set up the test vector */ |
| | 1477 | endPoint1 = myPolyMidPoint; |
| | 1478 | endPoint1.vx = endPoint1.vx - sbPtr->containingModule->m_world.vx - GMD_myPolyPoints[ePoint1].vx; |
| | 1479 | endPoint1.vy = 0; |
| | 1480 | endPoint1.vz = endPoint1.vz - sbPtr->containingModule->m_world.vz - GMD_myPolyPoints[ePoint1].vz; |
| | 1481 | RotateVector(&endPoint1, &edgeMatrix); |
| | 1482 | |
| | 1483 | endPoint2 = *targetPosition; |
| | 1484 | endPoint2.vx = endPoint2.vx - sbPtr->containingModule->m_world.vx - GMD_myPolyPoints[ePoint1].vx; |
| | 1485 | endPoint2.vy = 0; |
| | 1486 | endPoint2.vz = endPoint2.vz - sbPtr->containingModule->m_world.vz - GMD_myPolyPoints[ePoint1].vz; |
| | 1487 | RotateVector(&endPoint2, &edgeMatrix); |
| | 1488 | |
| | 1489 | if(VectorIntersects2dZVector(&endPoint1,&endPoint2,vecExtent)) |
| | 1490 | { |
| | 1491 | /* that'll do nicely */ |
| | 1492 | |
| | 1493 | /* test */ |
| | 1494 | // if(sbPtr == PlayerStatus.sbptr) |
| | 1495 | // printf("intersection test\n"); |
| | 1496 | |
| | 1497 | NPCFindCurveToEdgePoint(sbPtr,i,velocityDirection); |
| | 1498 | |
| | 1499 | return; |
| | 1500 | } |
| | 1501 | } |
| | 1502 | } |
| | 1503 | |
| | 1504 | /* test */ |
| | 1505 | // if(sbPtr == PlayerStatus.sbptr) |
| | 1506 | // printf("nearest edge test\n"); |
| | 1507 | |
| | 1508 | /* Didn't find an intersection edge, so just pick the nearest |
| | 1509 | edge point that we can traverse */ |
| | 1510 | { |
| | 1511 | int directionFound = 0; |
| | 1512 | VECTORCH bestDirection; |
| | 1513 | int closestDistance = 1000000; /* something very big */ |
| | 1514 | |
| | 1515 | for(i=0; i < GMD_myPolyNumPoints; i++) |
| | 1516 | { |
| | 1517 | if(myPolyEdgeMoveDistances[i] > NPC_MIN_MOVEFROMPOLYDIST) |
| | 1518 | { |
| | 1519 | int myDist = (VectorDistance(&myPolyEdgePoints[i],targetPosition)); |
| | 1520 | |
| | 1521 | if(myDist < closestDistance) |
| | 1522 | { |
| | 1523 | bestDirection = myPolyEdgeDirections[i]; |
| | 1524 | closestDistance = myDist; |
| | 1525 | directionFound = 1; |
| | 1526 | } |
| | 1527 | } |
| | 1528 | } |
| | 1529 | |
| | 1530 | /* return best direction, if we have one */ |
| | 1531 | if(directionFound) |
| | 1532 | { |
| | 1533 | assert(bestDirection.vy == 0); |
| | 1534 | *velocityDirection = bestDirection; |
| | 1535 | return; |
| | 1536 | } |
| | 1537 | } |
| | 1538 | |
| | 1539 | /* We have utterly failed to find a suitable direction */ |
| | 1540 | *velocityDirection = targetDirection; |
| | 1541 | } |
| | 1542 | |
| | 1543 | /* Patrick 23/8/97 ----------------------------------------------------- |
| | 1544 | A couple of functions for wandering |
| | 1545 | -----------------------------------------------------------------------*/ |
| | 1546 | void NPC_InitWanderData(NPC_WANDERDATA *wanderData) |
| | 1547 | { |
| | 1548 | assert(wanderData); |
| | 1549 | wanderData->currentModule = NPC_NOWANDERMODULE; |
| | 1550 | wanderData->worldPosition.vx = wanderData->worldPosition.vy = wanderData->worldPosition.vz = 0; |
| | 1551 | } |
| | 1552 | |
| | 1553 | /* Patrick: 26/8/97 |
| | 1554 | Finding a suitable target module- look thro' all the visible non-airduct modules |
| | 1555 | connected to the npc's current module. pick one, and use it's ep as a |
| | 1556 | target. |
| | 1557 | We take a random adjacent ep as our target, but reject the most 'backward' one |
| | 1558 | (compared to our last velocity, as recorded in npc_movedata) as our last choice |
| | 1559 | */ |
| | 1560 | |
| | 1561 | void NPC_FindAIWanderTarget(STRATEGYBLOCK *sbPtr, NPC_WANDERDATA *wanderData, NPC_MOVEMENTDATA *moveData, int alien) |
| | 1562 | { |
| | 1563 | AIMODULE* chosenModule = NULL; |
| | 1564 | VECTORCH chosenEpWorld; |
| | 1565 | int numFound = 0; |
| | 1566 | |
| | 1567 | AIMODULE* worstModule = NULL; |
| | 1568 | VECTORCH worstEpWorld; |
| | 1569 | int worstEpDot; |
| | 1570 | |
| | 1571 | AIMODULE **AdjModuleRefPtr; |
| | 1572 | VECTORCH lastVelocityDirection; |
| | 1573 | int gotLastVelocityDirection = 0; |
| | 1574 | |
| | 1575 | assert(sbPtr); |
| | 1576 | assert(sbPtr->DynPtr); |
| | 1577 | assert(wanderData); |
| | 1578 | assert(moveData); |
| | 1579 | |
| | 1580 | /* init the wander data block now, and we only have to fill in the correct |
| | 1581 | values if we get them... */ |
| | 1582 | NPC_InitWanderData(wanderData); |
| | 1583 | |
| | 1584 | /* do we have a current module? */ |
| | 1585 | if(!sbPtr->containingModule->m_aimodule) |
| | 1586 | return; /* no containing module */ |
| | 1587 | |
| | 1588 | AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs; |
| | 1589 | /* check if there is a module adjacency list */ |
| | 1590 | |
| | 1591 | if(!AdjModuleRefPtr) |
| | 1592 | return; |
| | 1593 | |
| | 1594 | /* try to get our last velocity direction */ |
| | 1595 | if(moveData->lastVelocity.vx || moveData->lastVelocity.vz || moveData->lastVelocity.vy) |
| | 1596 | { |
| | 1597 | lastVelocityDirection = moveData->lastVelocity; |
| | 1598 | Normalise(&lastVelocityDirection); |
| | 1599 | gotLastVelocityDirection = 1; |
| | 1600 | } |
| | 1601 | else |
| | 1602 | { |
| | 1603 | gotLastVelocityDirection = 0; |
| | 1604 | } |
| | 1605 | |
| | 1606 | /* if we've got a previous velocity, go through each adjacent module, |
| | 1607 | and try to find the worst one */ |
| | 1608 | |
| | 1609 | while(*AdjModuleRefPtr != 0) |
| | 1610 | { |
| | 1611 | AIMODULE *nextAdjModule = *AdjModuleRefPtr; |
| | 1612 | |
| | 1613 | if (AIModuleIsVisible(nextAdjModule) && !((*(nextAdjModule->m_module_ptrs))->m_flags & MODULEFLAG_AIRDUCT) && (nextAdjModule
!= moveData->lastModule)) |
| | 1614 | { |
| | 1615 | /* it is adjacent & visible & not an airduct: |
| | 1616 | try to find the ep position from this module... */ |
| | 1617 | FARENTRYPOINT *thisEp = GetAIModuleEP(nextAdjModule, sbPtr->containingModule->m_aimodule); |
| | 1618 | |
| | 1619 | if(thisEp) |
| | 1620 | { |
| | 1621 | if (!(!alien && thisEp->alien_only)) |
| | 1622 | { |
| | 1623 | /* aha. an ep!... */ |
| | 1624 | VECTORCH thisEpWorld = thisEp->position; |
| | 1625 | |
| | 1626 | thisEpWorld.vx += nextAdjModule->m_world.vx; |
| | 1627 | thisEpWorld.vy += nextAdjModule->m_world.vy; |
| | 1628 | thisEpWorld.vz += nextAdjModule->m_world.vz; |
| | 1629 | |
| | 1630 | if(gotLastVelocityDirection) |
| | 1631 | { |
| | 1632 | VECTORCH thisEpDirection; |
| | 1633 | int thisEpDot; |
| | 1634 | |
| | 1635 | thisEpDirection = thisEpWorld; |
| | 1636 | thisEpDirection.vx -= sbPtr->DynPtr->Position.vx; |
| | 1637 | thisEpDirection.vy -= sbPtr->DynPtr->Position.vy; |
| | 1638 | thisEpDirection.vz -= sbPtr->DynPtr->Position.vz; |
| | 1639 | Normalise(&thisEpDirection); |
| | 1640 | thisEpDot = DotProduct(&thisEpDirection,&lastVelocityDirection); |
| | 1641 | |
| | 1642 | if(!worstModule) |
| | 1643 | { |
| | 1644 | worstModule = nextAdjModule; |
| | 1645 | worstEpWorld = thisEpWorld; |
| | 1646 | worstEpDot = thisEpDot; |
| | 1647 | } |
| | 1648 | else |
| | 1649 | { |
| | 1650 | numFound++; |
| | 1651 | |
| | 1652 | if(thisEpDot<worstEpDot) |
| | 1653 | { |
| | 1654 | /* worse than our worst, so meld current worst with current, |
| | 1655 | and set new worst */ |
| | 1656 | |
| | 1657 | if(FastRandom()%numFound == 0) |
| | 1658 | { |
| | 1659 | /* take this one */ |
| | 1660 | chosenModule = worstModule; |
| | 1661 | chosenEpWorld = worstEpWorld; |
| | 1662 | } |
| | 1663 | |
| | 1664 | worstModule = nextAdjModule; |
| | 1665 | worstEpWorld = thisEpWorld; |
| | 1666 | worstEpDot = thisEpDot; |
| | 1667 | } |
| | 1668 | else |
| | 1669 | { |
| | 1670 | /* better than our worst... */ |
| | 1671 | if(FastRandom()%numFound == 0) |
| | 1672 | { |
| | 1673 | /* take this one */ |
| | 1674 | chosenModule = nextAdjModule; |
| | 1675 | chosenEpWorld = thisEpWorld; |
| | 1676 | } |
| | 1677 | } |
| | 1678 | } |
| | 1679 | } |
| | 1680 | else |
| | 1681 | { |
| | 1682 | /* don't bother with worst... */ |
| | 1683 | numFound++; |
| | 1684 | |
| | 1685 | if(FastRandom() % numFound == 0) |
| | 1686 | { |
| | 1687 | /* take this one */ |
| | 1688 | chosenModule = nextAdjModule; |
| | 1689 | chosenEpWorld = thisEpWorld; |
| | 1690 | } |
| | 1691 | } |
| | 1692 | } |
| | 1693 | } |
| | 1694 | } |
| | 1695 | |
| | 1696 | AdjModuleRefPtr++; |
| | 1697 | } |
| | 1698 | |
| | 1699 | if(chosenModule) |
| | 1700 | { |
| | 1701 | assert(numFound >= 1); |
| | 1702 | wanderData->currentModule = sbPtr->containingModule->m_aimodule->m_index; |
| | 1703 | wanderData->worldPosition = chosenEpWorld; |
| | 1704 | } |
| | 1705 | else if(worstModule) |
| | 1706 | { |
| | 1707 | wanderData->currentModule = sbPtr->containingModule->m_aimodule->m_index; |
| | 1708 | wanderData->worldPosition = worstEpWorld; |
| | 1709 | } |
| | 1710 | } |
| | 1711 | |
| | 1712 | #define NEARLINK_QUEUE_LENGTH 100 |
| | 1713 | |
| | 1714 | typedef struct nl_route_queue |
| | 1715 | { |
| | 1716 | int depth; |
| | 1717 | AIMODULE *aimodule; |
| | 1718 | AIMODULE *first_step; |
| | 1719 | |
| | 1720 | } NL_ROUTE_QUEUE; |
| | 1721 | |
| | 1722 | NL_ROUTE_QUEUE NearLink_Route_Queue[NEARLINK_QUEUE_LENGTH]; |
| | 1723 | |
| | 1724 | int NL_Queue_End,NL_Queue_Exec; |
| | 1725 | |
| | 1726 | AIMODULE *GetNextModuleForLink_Core(AIMODULE *source, AIMODULE *target, int max_depth, int visibility_check, int alien) |
| | 1727 | { |
| | 1728 | /* Recursively search AIModule tree, trying to connect source and target. * |
| | 1729 | * Return NULL on failure. */ |
| | 1730 | |
| | 1731 | if (source == target) |
| | 1732 | return(source); |
| | 1733 | |
| | 1734 | /* Clear the start. */ |
| | 1735 | |
| | 1736 | NearLink_Route_Queue[0].depth = 0; |
| | 1737 | NearLink_Route_Queue[0].aimodule = source; |
| | 1738 | NearLink_Route_Queue[0].first_step = NULL; |
| | 1739 | NearLink_Route_Queue[1].aimodule = NULL; /* To set a standard. */ |
| | 1740 | |
| | 1741 | NL_Queue_End = 1; |
| | 1742 | NL_Queue_Exec = 0; |
| | 1743 | |
| | 1744 | RouteFinder_CallsThisFrame++; |
| | 1745 | |
| | 1746 | while (NearLink_Route_Queue[NL_Queue_Exec].aimodule != NULL) |
| | 1747 | { |
| | 1748 | AIMODULE *thisModule = NearLink_Route_Queue[NL_Queue_Exec].aimodule; |
| | 1749 | AIMODULE **AdjModuleRefPtr = thisModule->m_link_ptrs; |
| | 1750 | |
| | 1751 | if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */ |
| | 1752 | { |
| | 1753 | while(*AdjModuleRefPtr != 0) |
| | 1754 | { |
| | 1755 | /* Probably want some validity test for the link. */ |
| | 1756 | if (AIModuleIsPhysical(*AdjModuleRefPtr) |
| | 1757 | && AIModuleAdmitsPheromones(*AdjModuleRefPtr) |
| | 1758 | && CheckAdjacencyValidity((*AdjModuleRefPtr), thisModule, alien) |
| | 1759 | && (!visibility_check || IsAIModuleVisibleFromAIModule(source, *AdjModuleRefPtr))) |
| | 1760 | { |
| | 1761 | |
| | 1762 | /* Is this the target? */ |
| | 1763 | if (*AdjModuleRefPtr == target) |
| | 1764 | { |
| | 1765 | /* Yes!!! */ |
| | 1766 | return (NearLink_Route_Queue[NL_Queue_Exec].first_step) ? (NearLink_Route_Queue[NL_Queue_Exec].first_step) : target; |
| | 1767 | } |
| | 1768 | else if ( (NearLink_Route_Queue[NL_Queue_Exec].depth < max_depth) |
| | 1769 | &&( /* Test for 'used this time round' */ |
| | 1770 | ((*AdjModuleRefPtr)->RouteFinder_FrameStamp != GlobalFrameCounter) |
| | 1771 | ||((*AdjModuleRefPtr)->RouteFinder_IterationNumber != RouteFinder_CallsThisFrame))) |
| | 1772 | { |
| | 1773 | /* Add to queue. */ |
| | 1774 | NearLink_Route_Queue[NL_Queue_End].aimodule = (*AdjModuleRefPtr); |
| | 1775 | NearLink_Route_Queue[NL_Queue_End].depth = NearLink_Route_Queue[NL_Queue_Exec].depth+1; |
| | 1776 | |
| | 1777 | /* Remember first step. */ |
| | 1778 | if (NearLink_Route_Queue[NL_Queue_Exec].first_step == NULL) |
| | 1779 | NearLink_Route_Queue[NL_Queue_End].first_step = (*AdjModuleRefPtr); |
| | 1780 | else |
| | 1781 | NearLink_Route_Queue[NL_Queue_End].first_step = NearLink_Route_Queue[NL_Queue_Exec].first_step; |
| | 1782 | |
| | 1783 | /* Stamp as used. */ |
| | 1784 | (*AdjModuleRefPtr)->RouteFinder_FrameStamp = GlobalFrameCounter; |
| | 1785 | (*AdjModuleRefPtr)->RouteFinder_IterationNumber = RouteFinder_CallsThisFrame; |
| | 1786 | NL_Queue_End++; |
| | 1787 | |
| | 1788 | if (NL_Queue_End >= NEARLINK_QUEUE_LENGTH) |
| | 1789 | { |
| | 1790 | NL_Queue_End = 0; |
| | 1791 | //printf("Wrapping Nearlink Queue!\n"); |
| | 1792 | } |
| | 1793 | |
| | 1794 | NearLink_Route_Queue[NL_Queue_End].aimodule = NULL; |
| | 1795 | |
| | 1796 | if (NL_Queue_End == NL_Queue_Exec) |
| | 1797 | { |
| | 1798 | printf("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth =
%d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth); |
| | 1799 | assert(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer |
| | 1800 | } |
| | 1801 | } |
| | 1802 | } |
| | 1803 | |
| | 1804 | /* next adjacent module reference pointer */ |
| | 1805 | AdjModuleRefPtr++; |
| | 1806 | } |
| | 1807 | } |
| | 1808 | |
| | 1809 | /* Done all the links. */ |
| | 1810 | |
| | 1811 | NL_Queue_Exec++; |
| | 1812 | if (NL_Queue_Exec >= NEARLINK_QUEUE_LENGTH) |
| | 1813 | NL_Queue_Exec = 0; |
| | 1814 | } |
| | 1815 | |
| | 1816 | /* Still here? Must have hit the end, then. */ |
| | 1817 | return NULL; |
| | 1818 | } |
| | 1819 | |
| | 1820 | AIMODULE *GetNextModuleForLink(AIMODULE *source,AIMODULE *target,int max_depth,int alien) |
| | 1821 | { |
| | 1822 | return GetNextModuleForLink_Core(source, target, max_depth, 0, alien); |
| | 1823 | } |
| | 1824 | |
| | 1825 | int PathArraySize; |
| | 1826 | PATHHEADER* PathArray; |
| | 1827 | |
| | 1828 | int GetNextModuleInPath(int current_module, int path) |
| | 1829 | { |
| | 1830 | assert(path>=0 && path < PathArraySize); |
| | 1831 | assert(PathArray); |
| | 1832 | assert(PathArray[path].path_length); |
| | 1833 | |
| | 1834 | return (current_module + 1) % PathArray[path].path_length; |
| | 1835 | } |
| | 1836 | |
| | 1837 | AIMODULE *TranslatePathIndex(int current_module, int path) |
| | 1838 | { |
| | 1839 | assert(path >= 0 && path < PathArraySize); |
| | 1840 | assert(PathArray); |
| | 1841 | assert(current_module < PathArray[path].path_length); |
| | 1842 | |
| | 1843 | return (PathArray[path].modules_in_path[current_module]); |
| | 1844 | } |
| | 1845 | |
| | 1846 | /* Death Shell */ |
| | 1847 | |
| | 1848 | static int CheckDeathValidity(HMODELCONTROLLER *controller, const SECTION *TemplateRoot, DEATH_DATA *ThisDeath, int wound_flags, int priority_wounds, |
| | 1849 | int hurtiness, HIT_FACING *facing, int burning, int crouching, int electrical) |
| | 1850 | { |
| | 1851 | if (crouching) |
| | 1852 | { |
| | 1853 | if (!ThisDeath->Crouching) |
| | 1854 | return 0; |
| | 1855 | } |
| | 1856 | else |
| | 1857 | { |
| | 1858 | if (ThisDeath->Crouching) |
| | 1859 | return 0; |
| | 1860 | } |
| | 1861 | |
| | 1862 | if (burning) |
| | 1863 | { |
| | 1864 | if (!ThisDeath->Burning) |
| | 1865 | return 0; |
| | 1866 | } |
| | 1867 | else |
| | 1868 | { |
| | 1869 | if (ThisDeath->Burning) |
| | 1870 | return 0; |
| | 1871 | } |
| | 1872 | |
| | 1873 | if (electrical) |
| | 1874 | { |
| | 1875 | if (!ThisDeath->Electrical) |
| | 1876 | return 0; |
| | 1877 | } |
| | 1878 | else |
| | 1879 | { |
| | 1880 | if (ThisDeath->Electrical) |
| | 1881 | return 0; |
| | 1882 | } |
| | 1883 | |
| | 1884 | switch(hurtiness) |
| | 1885 | { |
| | 1886 | case 0: |
| | 1887 | default: |
| | 1888 | if (ThisDeath->MinorBoom) |
| | 1889 | return 0; |
| | 1890 | |
| | 1891 | if (ThisDeath->MajorBoom) |
| | 1892 | return 0; |
| | 1893 | break; |
| | 1894 | case 1: |
| | 1895 | if (!ThisDeath->MinorBoom) |
| | 1896 | return 0; |
| | 1897 | break; |
| | 1898 | case 2: |
| | 1899 | if (!ThisDeath->MajorBoom) |
| | 1900 | return 0; |
| | 1901 | } |
| | 1902 | |
| | 1903 | /* Facing. Complex one... */ |
| | 1904 | /* Input facing must contain at least all the flags in the death. */ |
| | 1905 | if (facing) |
| | 1906 | { |
| | 1907 | if (ThisDeath->Facing.Front && !facing->Front) |
| | 1908 | return 0; |
| | 1909 | |
| | 1910 | if (ThisDeath->Facing.Back && !facing->Back) |
| | 1911 | return 0; |
| | 1912 | |
| | 1913 | if (ThisDeath->Facing.Left && !facing->Left) |
| | 1914 | return 0; |
| | 1915 | |
| | 1916 | if (ThisDeath->Facing.Right && !facing->Right) |
| | 1917 | return 0; |
| | 1918 | } |
| | 1919 | else |
| | 1920 | { |
| | 1921 | if ( ThisDeath->Facing.Front || ThisDeath->Facing.Back || ThisDeath->Facing.Left || ThisDeath->Facing.Right ) |
| | 1922 | return 0; |
| | 1923 | } |
| | 1924 | |
| | 1925 | /* Wound flags. Also quite odd. */ |
| | 1926 | /* If wound_flags are specified in the death, the input must contain them. */ |
| | 1927 | if (ThisDeath->wound_flags) |
| | 1928 | { |
| | 1929 | if ((ThisDeath->wound_flags & wound_flags) != ThisDeath->wound_flags) |
| | 1930 | return 0; |
| | 1931 | } |
| | 1932 | |
| | 1933 | /* Priority wound flags. As above, but backwards. */ |
| | 1934 | /* If the input has priority wounds, the death must contain them. */ |
| | 1935 | if (priority_wounds) |
| | 1936 | { |
| | 1937 | if ((ThisDeath->priority_wounds & priority_wounds) != priority_wounds) |
| | 1938 | return 0; |
| | 1939 | } |
| | 1940 | |
| | 1941 | /* Finally, sequence validity. */ |
| | 1942 | if (ThisDeath->Template) |
| | 1943 | { |
| | 1944 | if (!HModelSequence_Exists_FromRoot(TemplateRoot, ThisDeath->Sequence_Type, ThisDeath->Sub_Sequence)) |
| | 1945 | return 0; |
| | 1946 | } |
| | 1947 | else |
| | 1948 | { |
| | 1949 | if (!HModelSequence_Exists(controller, ThisDeath->Sequence_Type, ThisDeath->Sub_Sequence)) |
| | 1950 | return 0; |
| | 1951 | } |
| | 1952 | |
| | 1953 | return 1; |
| | 1954 | } |
| | 1955 | |
| | 1956 | static int CountValidDeaths(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,DEATH_DATA *FirstDeath,int wound_flags,int priority_wounds, |
| | 1957 | int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) |
| | 1958 | { |
| | 1959 | int number_of_candidates = 0; |
| | 1960 | DEATH_DATA *this_death = FirstDeath; |
| | 1961 | |
| | 1962 | while (this_death->Sequence_Type >= 0) |
| | 1963 | { |
| | 1964 | if (CheckDeathValidity(controller, TemplateRoot, this_death ,wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical)) |
| | 1965 | number_of_candidates++; |
| | 1966 | |
| | 1967 | this_death++; |
| | 1968 | } |
| | 1969 | |
| | 1970 | return number_of_candidates; |
| | 1971 | } |
| | 1972 | |
| | 1973 | DEATH_DATA *GetThisDeath(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,DEATH_DATA *FirstDeath,int wound_flags,int priority_wounds, |
| | 1974 | int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical, int index) |
| | 1975 | { |
| | 1976 | /* Extract 'index' from the valid deaths. */ |
| | 1977 | DEATH_DATA *this_death = FirstDeath; |
| | 1978 | int number = 0; |
| | 1979 | |
| | 1980 | while (this_death->Sequence_Type >= 0) |
| | 1981 | { |
| | 1982 | if (CheckDeathValidity(controller, TemplateRoot, this_death, wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical)) |
| | 1983 | { |
| | 1984 | if (number == index) |
| | 1985 | return this_death; |
| | 1986 | else |
| | 1987 | number++; |
| | 1988 | } |
| | 1989 | |
| | 1990 | this_death++; |
| | 1991 | } |
| | 1992 | |
| | 1993 | return NULL; |
| | 1994 | } |
| | 1995 | |
| | 1996 | DEATH_DATA *GetThisDeath_FromCode(DEATH_DATA *FirstDeath,int code) |
| | 1997 | { |
| | 1998 | /* Extract 'code' from the valid deaths. */ |
| | 1999 | DEATH_DATA *this_death = FirstDeath; |
| | 2000 | |
| | 2001 | while (this_death->Sequence_Type >= 0) |
| | 2002 | { |
| | 2003 | if (this_death->Multiplayer_Code == code) |
| | 2004 | return this_death; |
| | 2005 | |
| | 2006 | this_death++; |
| | 2007 | } |
| | 2008 | |
| | 2009 | return NULL; |
| | 2010 | } |
| | 2011 | |
| | 2012 | DEATH_DATA *GetThisDeath_FromUniqueCode(int code) |
| | 2013 | { |
| | 2014 | extern DEATH_DATA Predator_Special_SelfDestruct_Death; |
| | 2015 | |
| | 2016 | DEATH_DATA* this_death = NULL; |
| | 2017 | |
| | 2018 | switch (code >> 16) |
| | 2019 | { |
| | 2020 | case 0: |
| | 2021 | this_death = &Alien_Deaths[0]; |
| | 2022 | break; |
| | 2023 | case 1: |
| | 2024 | this_death = &Marine_Deaths[0]; |
| | 2025 | break; |
| | 2026 | case 2: |
| | 2027 | return &Predator_Special_SelfDestruct_Death; |
| | 2028 | case 3: |
| | 2029 | this_death = &Predator_Deaths[0]; |
| | 2030 | break; |
| | 2031 | case 4: |
| | 2032 | this_death = &Xenoborg_Deaths[0]; |
| | 2033 | break; |
| | 2034 | default: |
| | 2035 | return 0; |
| | 2036 | } |
| | 2037 | |
| | 2038 | while (this_death->Sequence_Type >= 0) |
| | 2039 | { |
| | 2040 | if (this_death->Unique_Code == code) |
| | 2041 | return this_death; |
| | 2042 | |
| | 2043 | this_death++; |
| | 2044 | } |
| | 2045 | |
| | 2046 | return 0; |
| | 2047 | } |
| | 2048 | |
| | 2049 | static DEATH_DATA *GetDeathSequence(HMODELCONTROLLER *controller, const SECTION *TemplateRoot, DEATH_DATA *FirstDeath, int wound_flags, int priority_wounds, |
| | 2050 | int hurtiness, HIT_FACING *facing, int burning, int crouching, int electrical) |
| | 2051 | { |
| | 2052 | int index; |
| | 2053 | int number_of_candidates = 0; |
| | 2054 | int use_priority_wounds = priority_wounds; |
| | 2055 | int use_wound_flags = wound_flags; |
| | 2056 | int use_hurtiness = hurtiness; |
| | 2057 | HIT_FACING *use_facing = facing; |
| | 2058 | int use_burning = burning; |
| | 2059 | int use_crouching = crouching; |
| | 2060 | int use_electrical = electrical; |
| | 2061 | |
| | 2062 | while (!number_of_candidates) |
| | 2063 | { |
| | 2064 | /* Iterate, making simplifications, until there are valid deaths. */ |
| | 2065 | |
| | 2066 | number_of_candidates = CountValidDeaths(controller, TemplateRoot, FirstDeath, use_wound_flags, use_priority_wounds, use_hurtiness, use_facing, use_burning,
use_crouching, use_electrical); |
| | 2067 | |
| | 2068 | if (!number_of_candidates) |
| | 2069 | { |
| | 2070 | /* Right. Make a change. Priority wounds first. */ |
| | 2071 | if (use_priority_wounds) |
| | 2072 | { |
| | 2073 | use_priority_wounds = 0; |
| | 2074 | continue; |
| | 2075 | } |
| | 2076 | /* Wound flags next. */ |
| | 2077 | if (use_wound_flags != 0xffffffff) |
| | 2078 | { |
| | 2079 | use_wound_flags = 0xffffffff; |
| | 2080 | continue; |
| | 2081 | } |
| | 2082 | /* Now facing. */ |
| | 2083 | if (use_facing) |
| | 2084 | { |
| | 2085 | use_facing = NULL; |
| | 2086 | continue; |
| | 2087 | } |
| | 2088 | /* Now hurtiness. */ |
| | 2089 | if (use_hurtiness) |
| | 2090 | { |
| | 2091 | use_hurtiness--; |
| | 2092 | continue; |
| | 2093 | } |
| | 2094 | /* Now electrical. */ |
| | 2095 | if (use_electrical) |
| | 2096 | { |
| | 2097 | use_electrical = 0; |
| | 2098 | continue; |
| | 2099 | } |
| | 2100 | /* Finally, burning. */ |
| | 2101 | if (use_burning) |
| | 2102 | { |
| | 2103 | use_burning = 0; |
| | 2104 | continue; |
| | 2105 | } |
| | 2106 | /* Only crouch is left! */ |
| | 2107 | //puts("DEATH SELECTION FAILURE!\n"); |
| | 2108 | if (use_crouching) |
| | 2109 | { |
| | 2110 | use_crouching = 0; |
| | 2111 | continue; |
| | 2112 | } |
| | 2113 | //puts("I REALLY MEAN IT!\n"); |
| | 2114 | /* Here goes nothing. */ |
| | 2115 | return FirstDeath; |
| | 2116 | } |
| | 2117 | } |
| | 2118 | |
| | 2119 | /* Right, by now we should have a number of candidates. */ |
| | 2120 | assert(number_of_candidates); |
| | 2121 | |
| | 2122 | index = FastRandom() % number_of_candidates; |
| | 2123 | |
| | 2124 | return GetThisDeath(controller, TemplateRoot, FirstDeath, use_wound_flags, use_priority_wounds, use_hurtiness, use_facing, use_burning, use_crouching,
use_electrical, index); |
| | 2125 | } |
| | 2126 | |
| | 2127 | DEATH_DATA *GetAlienDeathSequence(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,int wound_flags,int priority_wounds, |
| | 2128 | int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) |
| | 2129 | { |
| | 2130 | return GetDeathSequence(controller, TemplateRoot, Alien_Deaths, wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical); |
| | 2131 | } |
| | 2132 | |
| | 2133 | DEATH_DATA *GetMarineDeathSequence(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,int wound_flags,int priority_wounds, |
| | 2134 | int hurtiness,HIT_FACING *facing,int burning,int crouching, int electrical) |
| | 2135 | { |
| | 2136 | return GetDeathSequence(controller, TemplateRoot, Marine_Deaths, wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical); |
| | 2137 | } |
| | 2138 | |
| | 2139 | DEATH_DATA *GetPredatorDeathSequence(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,int wound_flags,int priority_wounds, |
| | 2140 | int hurtiness,HIT_FACING *facing,int burning,int crouching,int electrical) |
| | 2141 | { |
| | 2142 | return GetDeathSequence(controller, TemplateRoot, Predator_Deaths, wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical); |
| | 2143 | } |
| | 2144 | |
| | 2145 | DEATH_DATA *GetXenoborgDeathSequence(HMODELCONTROLLER *controller, const SECTION *TemplateRoot,int wound_flags,int priority_wounds, |
| | 2146 | int hurtiness,HIT_FACING *facing,int burning,int crouching,int electrical) |
| | 2147 | { |
| | 2148 | return GetDeathSequence(controller, TemplateRoot, Xenoborg_Deaths, wound_flags, priority_wounds, hurtiness, facing, burning, crouching, electrical); |
| | 2149 | } |
| | 2150 | |
| | 2151 | static int CheckAttackValidity(HMODELCONTROLLER *controller,ATTACK_DATA *ThisAttack,int wound_flags, int crouching,int pouncing) |
| | 2152 | { |
| | 2153 | if (crouching) |
| | 2154 | { |
| | 2155 | if (!ThisAttack->Crouching) |
| | 2156 | return 0; |
| | 2157 | } |
| | 2158 | else |
| | 2159 | { |
| | 2160 | if (ThisAttack->Crouching) |
| | 2161 | return 0; |
| | 2162 | } |
| | 2163 | |
| | 2164 | if (pouncing) |
| | 2165 | { |
| | 2166 | if (!ThisAttack->Pouncing) |
| | 2167 | return 0; |
| | 2168 | } |
| | 2169 | else |
| | 2170 | { |
| | 2171 | if (ThisAttack->Pouncing) |
| | 2172 | return 0; |
| | 2173 | } |
| | 2174 | |
| | 2175 | /* Wound flags. Quite odd, and different to deaths. */ |
| | 2176 | /* If wound_flags are specified in the death, the input must NOT contain them. */ |
| | 2177 | |
| | 2178 | if (ThisAttack->wound_flags && (ThisAttack->wound_flags & wound_flags)) |
| | 2179 | return 0; |
| | 2180 | |
| | 2181 | /* Finally, sequence validity. */ |
| | 2182 | if (!HModelSequence_Exists(controller,ThisAttack->Sequence_Type,ThisAttack->Sub_Sequence)) |
| | 2183 | return 0; |
| | 2184 | |
| | 2185 | return 1; |
| | 2186 | } |
| | 2187 | |
| | 2188 | static int CountValidAttacks(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags, int crouching, int pouncing) |
| | 2189 | { |
| | 2190 | ATTACK_DATA *this_attack = FirstAttack; |
| | 2191 | int number_of_candidates = 0; |
| | 2192 | |
| | 2193 | while (this_attack->Sequence_Type >= 0) |
| | 2194 | { |
| | 2195 | if (CheckAttackValidity(controller,this_attack,wound_flags,crouching,pouncing)) |
| | 2196 | number_of_candidates++; |
| | 2197 | |
| | 2198 | this_attack++; |
| | 2199 | } |
| | 2200 | |
| | 2201 | return number_of_candidates; |
| | 2202 | } |
| | 2203 | |
| | 2204 | ATTACK_DATA *GetThisAttack(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags, int crouching, int pouncing, int index) |
| | 2205 | { |
| | 2206 | /* Extract 'index' from the valid attacks. */ |
| | 2207 | ATTACK_DATA *this_attack = FirstAttack; |
| | 2208 | int number = 0; |
| | 2209 | |
| | 2210 | while (this_attack->Sequence_Type >= 0) |
| | 2211 | { |
| | 2212 | if (CheckAttackValidity(controller,this_attack,wound_flags,crouching,pouncing)) |
| | 2213 | { |
| | 2214 | if (number == index) |
| | 2215 | return this_attack; |
| | 2216 | else |
| | 2217 | number++; |
| | 2218 | } |
| | 2219 | |
| | 2220 | this_attack++; |
| | 2221 | } |
| | 2222 | |
| | 2223 | return NULL; |
| | 2224 | } |
| | 2225 | |
| | 2226 | ATTACK_DATA *GetThisAttack_FromUniqueCode(int code) |
| | 2227 | { |
| | 2228 | extern ATTACK_DATA Alien_Special_Gripping_Attack; |
| | 2229 | //search for an attack using a code that should be unique across all attacks |
| | 2230 | //(used for loading) |
| | 2231 | |
| | 2232 | if(code < 0) |
| | 2233 | return NULL; |
| | 2234 | |
| | 2235 | ///try the alien attacks |
| | 2236 | ATTACK_DATA *this_attack = &Alien_Attacks[0]; |
| | 2237 | |
| | 2238 | while (this_attack->Sequence_Type >= 0) |
| | 2239 | { |
| | 2240 | if (this_attack->Unique_Code == code) |
| | 2241 | { |
| | 2242 | return this_attack; |
| | 2243 | break; |
| | 2244 | } |
| | 2245 | |
| | 2246 | this_attack++; |
| | 2247 | } |
| | 2248 | |
| | 2249 | //try the wristblade attacks |
| | 2250 | this_attack = &Wristblade_Attacks[0]; |
| | 2251 | |
| | 2252 | while (this_attack->Sequence_Type >= 0) |
| | 2253 | { |
| | 2254 | if (this_attack->Unique_Code == code) |
| | 2255 | { |
| | 2256 | return this_attack; |
| | 2257 | break; |
| | 2258 | } |
| | 2259 | |
| | 2260 | this_attack++; |
| | 2261 | } |
| | 2262 | |
| | 2263 | //try the staff attacks |
| | 2264 | this_attack = &PredStaff_Attacks[0]; |
| | 2265 | |
| | 2266 | while (this_attack->Sequence_Type >= 0) |
| | 2267 | { |
| | 2268 | if (this_attack->Unique_Code == code) |
| | 2269 | { |
| | 2270 | return this_attack; |
| | 2271 | break; |
| | 2272 | } |
| | 2273 | |
| | 2274 | this_attack++; |
| | 2275 | } |
| | 2276 | |
| | 2277 | //try gripping attack |
| | 2278 | if(Alien_Special_Gripping_Attack.Unique_Code == code) |
| | 2279 | return &Alien_Special_Gripping_Attack; |
| | 2280 | |
| | 2281 | //no such attack |
| | 2282 | return NULL; |
| | 2283 | } |
| | 2284 | |
| | 2285 | static ATTACK_DATA *GetAttackSequence(HMODELCONTROLLER *controller,ATTACK_DATA *FirstAttack,int wound_flags,int crouching, int pouncing) |
| | 2286 | { |
| | 2287 | int number_of_candidates = 0; |
| | 2288 | int use_crouching = crouching; |
| | 2289 | int use_wound_flags = wound_flags; |
| | 2290 | |
| | 2291 | while (!number_of_candidates) |
| | 2292 | { |
| | 2293 | /* Iterate, making simplifications, until there are valid deaths. */ |
| | 2294 | number_of_candidates = CountValidAttacks(controller,FirstAttack,use_wound_flags,use_crouching,pouncing); |
| | 2295 | |
| | 2296 | if (!number_of_candidates) |
| | 2297 | { |
| | 2298 | /* Wound flags first. */ |
| | 2299 | if (use_wound_flags != 0) |
| | 2300 | { |
| | 2301 | use_wound_flags = 0; |
| | 2302 | continue; |
| | 2303 | } |
| | 2304 | /* Only crouch is left! */ |
| | 2305 | if (use_crouching) |
| | 2306 | { |
| | 2307 | use_crouching = 0; |
| | 2308 | continue; |
| | 2309 | } |
| | 2310 | /* Now, pounce is absolutely inviolate. */ |
| | 2311 | if (pouncing) |
| | 2312 | { |
| | 2313 | /* If we're looking for a pounce, and there is none, return NULL. */ |
| | 2314 | return NULL; |
| | 2315 | } |
| | 2316 | //GADGET_NewOnScreenMessage("ATTACK SELECTION FAILURE!\n"); |
| | 2317 | /* Here goes nothing. */ |
| | 2318 | return FirstAttack; |
| | 2319 | } |
| | 2320 | } |
| | 2321 | |
| | 2322 | /* Right, by now we should have a number of candidates. */ |
| | 2323 | assert(number_of_candidates); |
| | 2324 | |
| | 2325 | return GetThisAttack(controller, FirstAttack, use_wound_flags, use_crouching, pouncing, FastRandom() % number_of_candidates); |
| | 2326 | } |
| | 2327 | |
| | 2328 | ATTACK_DATA *GetAlienAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) |
| | 2329 | { |
| | 2330 | return GetAttackSequence(controller, Alien_Attacks, wound_flags, crouching, 0); |
| | 2331 | } |
| | 2332 | |
| | 2333 | ATTACK_DATA *GetAlienPounceAttack(HMODELCONTROLLER *controller,int wound_flags,int crouching) |
| | 2334 | { |
| | 2335 | return GetAttackSequence(controller, Alien_Attacks, wound_flags, crouching, 1); |
| | 2336 | } |
| | 2337 | |
| | 2338 | ATTACK_DATA *GetWristbladeAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) |
| | 2339 | { |
| | 2340 | return GetAttackSequence(controller, Wristblade_Attacks, wound_flags, crouching, 0); |
| | 2341 | } |
| | 2342 | |
| | 2343 | ATTACK_DATA *GetPredStaffAttackSequence(HMODELCONTROLLER *controller,int wound_flags,int crouching) |
| | 2344 | { |
| | 2345 | return GetAttackSequence(controller, PredStaff_Attacks, wound_flags, crouching, 0); |
| | 2346 | } |
| | 2347 | |
| | 2348 | AIMODULE *NearNPC_GetTargetAIModuleForRetreat(STRATEGYBLOCK *sbPtr, NPC_MOVEMENTDATA *moveData) |
| | 2349 | { |
| | 2350 | extern unsigned int PlayerSmell; |
| | 2351 | |
| | 2352 | AIMODULE* targetModule = NULL; |
| | 2353 | unsigned int targetSmell = PlayerSmell + 1; /* should be higher than any smell anywhere this frame */ |
| | 2354 | unsigned int targetNumAdj = 0; |
| | 2355 | int targetEpDot = -ONE_FIXED; |
| | 2356 | VECTORCH lastVelocityDirection; |
| | 2357 | int gotLastVelocityDirection = 0; |
| | 2358 | |
| | 2359 | assert(sbPtr); |
| | 2360 | |
| | 2361 | if(sbPtr->containingModule == NULL) |
| | 2362 | return targetModule; |
| | 2363 | |
| | 2364 | AIMODULE **AdjModuleRefPtr = sbPtr->containingModule->m_aimodule->m_link_ptrs; |
| | 2365 | |
| | 2366 | /* try to get our last velocity direction */ |
| | 2367 | if(moveData->lastVelocity.vx || moveData->lastVelocity.vz || moveData->lastVelocity.vy) |
| | 2368 | { |
| | 2369 | lastVelocityDirection = moveData->lastVelocity; |
| | 2370 | Normalise(&lastVelocityDirection); |
| | 2371 | gotLastVelocityDirection = 1; |
| | 2372 | } |
| | 2373 | |
| | 2374 | /* check that there is a list of adjacent modules, and that it is not empty (ie points to zero) */ |
| | 2375 | |
| | 2376 | if(AdjModuleRefPtr) |
| | 2377 | { |
| | 2378 | while(*AdjModuleRefPtr != 0) |
| | 2379 | { |
| | 2380 | /* get the index */ |
| | 2381 | int AdjModuleIndex = (*AdjModuleRefPtr)->m_index; |
| | 2382 | unsigned int AdjModuleSmell = PherPl_ReadBuf[AdjModuleIndex]; |
| | 2383 | FARENTRYPOINT *thisEp = GetAIModuleEP((*AdjModuleRefPtr), sbPtr->containingModule->m_aimodule); |
| | 2384 | int thisEpDot = -ONE_FIXED; |
| | 2385 | int chooseThisOne = 0; |
| | 2386 | |
| | 2387 | if(thisEp) |
| | 2388 | { |
| | 2389 | /* aha. an ep!... */ |
| | 2390 | VECTORCH thisEpWorld = thisEp->position; |
| | 2391 | |
| | 2392 | thisEpWorld.vx += (*AdjModuleRefPtr)->m_world.vx; |
| | 2393 | thisEpWorld.vy += (*AdjModuleRefPtr)->m_world.vy; |
| | 2394 | thisEpWorld.vz += (*AdjModuleRefPtr)->m_world.vz; |
| | 2395 | |
| | 2396 | if(gotLastVelocityDirection) |
| | 2397 | { |
| | 2398 | VECTORCH thisEpDirection; |
| | 2399 | |
| | 2400 | thisEpDirection = thisEpWorld; |
| | 2401 | thisEpDirection.vx -= sbPtr->DynPtr->Position.vx; |
| | 2402 | thisEpDirection.vy -= sbPtr->DynPtr->Position.vy; |
| | 2403 | thisEpDirection.vz -= sbPtr->DynPtr->Position.vz; |
| | 2404 | Normalise(&thisEpDirection); |
| | 2405 | thisEpDot = DotProduct(&thisEpDirection,&lastVelocityDirection); |
| | 2406 | } |
| | 2407 | } |
| | 2408 | |
| | 2409 | /* if this adjacent module's smell value is lower than |
| | 2410 | the current 'highest smell' record the new module as the |
| | 2411 | target. |
| | 2412 | Tie break on best direction. */ |
| | 2413 | |
| | 2414 | if (!targetModule) |
| | 2415 | { |
| | 2416 | chooseThisOne = 1; |
| | 2417 | } |
| | 2418 | else |
| | 2419 | { |
| | 2420 | if (AdjModuleSmell < targetSmell) |
| | 2421 | { |
| | 2422 | chooseThisOne = 1; |
| | 2423 | } |
| | 2424 | else if (AdjModuleSmell == targetSmell) |
| | 2425 | { |
| | 2426 | if (thisEpDot > targetEpDot) |
| | 2427 | chooseThisOne = 1; |
| | 2428 | } |
| | 2429 | } |
| | 2430 | |
| | 2431 | if (chooseThisOne) |
| | 2432 | { |
| | 2433 | targetSmell = PherPl_ReadBuf[AdjModuleIndex]; |
| | 2434 | targetModule = *AdjModuleRefPtr; |
| | 2435 | targetNumAdj = NumAdjacentModules(*AdjModuleRefPtr); |
| | 2436 | targetEpDot = thisEpDot; |
| | 2437 | } |
| | 2438 | /* next adjacent module reference pointer */ |
| | 2439 | AdjModuleRefPtr++; |
| | 2440 | } |
| | 2441 | } |
| | 2442 | |
| | 2443 | return targetModule; |
| | 2444 | } |
| | 2445 | |
| | 2446 | static AIMODULE *General_GetRetreatModule_Core(AIMODULE *source,int max_depth) |
| | 2447 | { |
| | 2448 | AIMODULE **AdjModuleRefPtr; |
| | 2449 | AIMODULE *deepest_target = NULL; |
| | 2450 | NL_ROUTE_QUEUE deepest_route; |
| | 2451 | |
| | 2452 | /* Note this DOES NOT set the CallsThisFrame variable. That MUST be set before the call. */ |
| | 2453 | |
| | 2454 | /* Clear the start. */ |
| | 2455 | |
| | 2456 | deepest_route.depth = 0; |
| | 2457 | deepest_route.aimodule = NULL; |
| | 2458 | deepest_route.first_step = NULL; |
| | 2459 | |
| | 2460 | NearLink_Route_Queue[0].depth = 0; |
| | 2461 | NearLink_Route_Queue[0].aimodule = source; |
| | 2462 | NearLink_Route_Queue[0].first_step = NULL; |
| | 2463 | NearLink_Route_Queue[1].aimodule = NULL; /* To set a standard. */ |
| | 2464 | |
| | 2465 | NL_Queue_End = 1; |
| | 2466 | NL_Queue_Exec = 0; |
| | 2467 | |
| | 2468 | while (NearLink_Route_Queue[NL_Queue_Exec].aimodule!=NULL) |
| | 2469 | { |
| | 2470 | AIMODULE *thisModule = NearLink_Route_Queue[NL_Queue_Exec].aimodule; |
| | 2471 | |
| | 2472 | AdjModuleRefPtr = thisModule->m_link_ptrs; |
| | 2473 | |
| | 2474 | if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */ |
| | 2475 | { |
| | 2476 | while(*AdjModuleRefPtr != 0) |
| | 2477 | { |
| | 2478 | /* Probably want some validity test for the link. */ |
| | 2479 | if (AIModuleIsPhysical(*AdjModuleRefPtr) && AIModuleAdmitsPheromones(*AdjModuleRefPtr)) |
| | 2480 | { |
| | 2481 | /* No visibility check? */ |
| | 2482 | /* Consider depth? */ |
| | 2483 | |
| | 2484 | if (NearLink_Route_Queue[NL_Queue_Exec].depth < deepest_route.depth) |
| | 2485 | { |
| | 2486 | deepest_route= NearLink_Route_Queue[NL_Queue_Exec]; |
| | 2487 | deepest_target = (*AdjModuleRefPtr); |
| | 2488 | } |
| | 2489 | |
| | 2490 | /* Process link. */ |
| | 2491 | if ((NearLink_Route_Queue[NL_Queue_Exec].depth < max_depth) |
| | 2492 | &&( /* Test for 'used this time round' */ |
| | 2493 | ((*AdjModuleRefPtr)->RouteFinder_FrameStamp!=GlobalFrameCounter) |
| | 2494 | ||((*AdjModuleRefPtr)->RouteFinder_IterationNumber!=RouteFinder_CallsThisFrame) |
| | 2495 | )) |
| | 2496 | { |
| | 2497 | /* Add to queue. */ |
| | 2498 | NearLink_Route_Queue[NL_Queue_End].aimodule = (*AdjModuleRefPtr); |
| | 2499 | NearLink_Route_Queue[NL_Queue_End].depth = NearLink_Route_Queue[NL_Queue_Exec].depth+1; |
| | 2500 | /* Remember first step. */ |
| | 2501 | |
| | 2502 | if (NearLink_Route_Queue[NL_Queue_Exec].first_step == NULL) |
| | 2503 | NearLink_Route_Queue[NL_Queue_End].first_step = (*AdjModuleRefPtr); |
| | 2504 | else |
| | 2505 | NearLink_Route_Queue[NL_Queue_End].first_step = NearLink_Route_Queue[NL_Queue_Exec].first_step; |
| | 2506 | |
| | 2507 | /* Stamp as used. */ |
| | 2508 | (*AdjModuleRefPtr)->RouteFinder_FrameStamp = GlobalFrameCounter; |
| | 2509 | (*AdjModuleRefPtr)->RouteFinder_IterationNumber = RouteFinder_CallsThisFrame; |
| | 2510 | NL_Queue_End++; |
| | 2511 | |
| | 2512 | if (NL_Queue_End >= NEARLINK_QUEUE_LENGTH) |
| | 2513 | { |
| | 2514 | NL_Queue_End = 0; |
| | 2515 | //printf("Wrapping Nearlink Queue!\n"); |
| | 2516 | } |
| | 2517 | |
| | 2518 | NearLink_Route_Queue[NL_Queue_End].aimodule=NULL; |
| | 2519 | |
| | 2520 | if (NL_Queue_End == NL_Queue_Exec) |
| | 2521 | { |
| | 2522 | printf("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth = %d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth); |
| | 2523 | assert(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer |
| | 2524 | } |
| | 2525 | } |
| | 2526 | else if (NearLink_Route_Queue[NL_Queue_Exec].depth >= max_depth) |
| | 2527 | { |
| | 2528 | /* That's well deep. Let's return. */ |
| | 2529 | return(*AdjModuleRefPtr); |
| | 2530 | } |
| | 2531 | } |
| | 2532 | /* next adjacent module reference pointer */ |
| | 2533 | AdjModuleRefPtr++; |
| | 2534 | } |
| | 2535 | } |
| | 2536 | |
| | 2537 | /* Done all the links. */ |
| | 2538 | NL_Queue_Exec++; |
| | 2539 | if (NL_Queue_Exec >= NEARLINK_QUEUE_LENGTH) |
| | 2540 | NL_Queue_Exec = 0; |
| | 2541 | } |
| | 2542 | |
| | 2543 | /* Split up for easier debugging... */ |
| | 2544 | if (deepest_target) |
| | 2545 | return deepest_target; |
| | 2546 | else |
| | 2547 | return NULL; /* There's nowhere to retreat to! */ |
| | 2548 | } |
| | 2549 | |
| | 2550 | AIMODULE *General_GetAIModuleForRetreat(STRATEGYBLOCK *sbPtr,AIMODULE *fearModule,int max_depth) |
| | 2551 | { |
| | 2552 | AIMODULE **AdjModuleRefPtr; |
| | 2553 | int success = 0; |
| | 2554 | |
| | 2555 | assert(sbPtr->containingModule); |
| | 2556 | AIMODULE *my_module = sbPtr->containingModule->m_aimodule; |
| | 2557 | assert(my_module); |
| | 2558 | |
| | 2559 | if (fearModule == NULL) |
| | 2560 | return NULL; |
| | 2561 | |
| | 2562 | /* Step one: search down till we get to my_module, checking off modules. */ |
| | 2563 | |
| | 2564 | if (my_module == fearModule) |
| | 2565 | { |
| | 2566 | /* Cripes! */ |
| | 2567 | AIMODULE *targetModule; |
| | 2568 | RouteFinder_CallsThisFrame++; |
| | 2569 | targetModule = General_GetRetreatModule_Core(my_module, max_depth); |
| | 2570 | return targetModule; |
| | 2571 | } |
| | 2572 | |
| | 2573 | /* Clear the start. */ |
| | 2574 | |
| | 2575 | NearLink_Route_Queue[0].depth = 0; |
| | 2576 | NearLink_Route_Queue[0].aimodule = fearModule; |
| | 2577 | NearLink_Route_Queue[0].first_step = NULL; |
| | 2578 | NearLink_Route_Queue[1].aimodule = NULL; /* To set a standard. */ |
| | 2579 | |
| | 2580 | NL_Queue_End = 1; |
| | 2581 | NL_Queue_Exec = 0; |
| | 2582 | |
| | 2583 | /* Hijack RouteFinder. */ |
| | 2584 | RouteFinder_CallsThisFrame++; |
| | 2585 | |
| | 2586 | while (NearLink_Route_Queue[NL_Queue_Exec].aimodule != NULL) |
| | 2587 | { |
| | 2588 | AIMODULE *thisModule = NearLink_Route_Queue[NL_Queue_Exec].aimodule; |
| | 2589 | |
| | 2590 | AdjModuleRefPtr = thisModule->m_link_ptrs; |
| | 2591 | |
| | 2592 | if(AdjModuleRefPtr) /* check that there is a list of adjacent modules */ |
| | 2593 | { |
| | 2594 | while(*AdjModuleRefPtr != 0) |
| | 2595 | { |
| | 2596 | /* Probably want some validity test for the link. */ |
| | 2597 | if ((AIModuleIsPhysical(*AdjModuleRefPtr)) &&(AIModuleAdmitsPheromones(*AdjModuleRefPtr))) |
| | 2598 | /* No visibility check. */ |
| | 2599 | { |
| | 2600 | /* Is this my_module? */ |
| | 2601 | if ( (*AdjModuleRefPtr) == my_module) |
| | 2602 | { |
| | 2603 | /* Yes!!! Break out. */ |
| | 2604 | success = 1; |
| | 2605 | break; |
| | 2606 | } |
| | 2607 | else if ( (NearLink_Route_Queue[NL_Queue_Exec].depth<max_depth) |
| | 2608 | &&( /* Test for 'used this time round' */ |
| | 2609 | ((*AdjModuleRefPtr)->RouteFinder_FrameStamp!=GlobalFrameCounter) |
| | 2610 | ||((*AdjModuleRefPtr)->RouteFinder_IterationNumber!=RouteFinder_CallsThisFrame))) |
| | 2611 | { |
| | 2612 | success = 0; |
| | 2613 | /* Add to queue. */ |
| | 2614 | NearLink_Route_Queue[NL_Queue_End].aimodule = (*AdjModuleRefPtr); |
| | 2615 | NearLink_Route_Queue[NL_Queue_End].depth = NearLink_Route_Queue[NL_Queue_Exec].depth+1; |
| | 2616 | /* Remember first step. */ |
| | 2617 | |
| | 2618 | if (NearLink_Route_Queue[NL_Queue_Exec].first_step == NULL) |
| | 2619 | NearLink_Route_Queue[NL_Queue_End].first_step = (*AdjModuleRefPtr); |
| | 2620 | else |
| | 2621 | NearLink_Route_Queue[NL_Queue_End].first_step = NearLink_Route_Queue[NL_Queue_Exec].first_step; |
| | 2622 | |
| | 2623 | /* Stamp as used. */ |
| | 2624 | (*AdjModuleRefPtr)->RouteFinder_FrameStamp = GlobalFrameCounter; |
| | 2625 | (*AdjModuleRefPtr)->RouteFinder_IterationNumber = RouteFinder_CallsThisFrame; |
| | 2626 | NL_Queue_End++; |
| | 2627 | |
| | 2628 | if (NL_Queue_End >= NEARLINK_QUEUE_LENGTH) |
| | 2629 | { |
| | 2630 | NL_Queue_End = 0; |
| | 2631 | //printf("Wrapping Nearlink Queue!\n"); |
| | 2632 | } |
| | 2633 | |
| | 2634 | NearLink_Route_Queue[NL_Queue_End].aimodule = NULL; |
| | 2635 | |
| | 2636 | #if DEBUG |
| | 2637 | if (NL_Queue_End == NL_Queue_Exec) |
| | 2638 | { |
| | 2639 | printf("Oh, no. NearLinkQueue screwed. NL_Queue_End=%d, depth =
%d\n",NL_Queue_End,NearLink_Route_Queue[NL_Queue_Exec].depth); |
| | 2640 | assert(NL_Queue_End!=NL_Queue_Exec); //if this happens the queue probably needs to be longer |
| | 2641 | } |
| | 2642 | #endif |
| | 2643 | } |
| | 2644 | } |
| | 2645 | /* next adjacent module reference pointer */ |
| | 2646 | AdjModuleRefPtr++; |
| | 2647 | } |
| | 2648 | } |
| | 2649 | |
| | 2650 | /* Done all the links. */ |
| | 2651 | |
| | 2652 | if (success) // Continue break out. |
| | 2653 | break; |
| | 2654 | |
| | 2655 | NL_Queue_Exec++; |
| | 2656 | |
| | 2657 | if (NL_Queue_Exec >= NEARLINK_QUEUE_LENGTH) |
| | 2658 | NL_Queue_Exec = 0; |
| | 2659 | } |
| | 2660 | |
| | 2661 | /* By now, we should have broken out... or maxed out the range. */ |
| | 2662 | if (success) |
| | 2663 | return General_GetRetreatModule_Core(my_module,max_depth); |
| | 2664 | else |
| | 2665 | return NULL; |
| | 2666 | } |
| | 2667 | |
| | 2668 | static void ClearThirdAvoidance(NPC_AVOIDANCEMANAGER *manager) |
| | 2669 | { |
| | 2670 | manager->baseVector.vx = manager->baseVector.vy = manager->baseVector.vz = 0; |
| | 2671 | manager->currentVector = manager->baseVector; |
| | 2672 | manager->avoidanceDirection.vx = manager->avoidanceDirection.vy = manager->avoidanceDirection.vz = 0; |
| | 2673 | manager->bestVector.vx = manager->bestVector.vy = manager->bestVector.vz = 0; |
| | 2674 | manager->basePoint = manager->baseVector; |
| | 2675 | manager->stage = 0; |
| | 2676 | manager->bestDistance = 0; |
| | 2677 | manager->bestStage = 0; |
| | 2678 | /* Er... ignore the rotmat for now... */ |
| | 2679 | } |
| | 2680 | |
| | 2681 | void Initialise_AvoidanceManager(NPC_AVOIDANCEMANAGER *manager) |
| | 2682 | { |
| | 2683 | assert(manager); |
| | 2684 | |
| | 2685 | ClearThirdAvoidance(manager); |
| | 2686 | |
| | 2687 | manager->avoidanceDirection.vx = manager->avoidanceDirection.vy = manager->avoidanceDirection.vz = 0; |
| | 2688 | manager->incidenceDirection.vx = manager->incidenceDirection.vy = manager->incidenceDirection.vz = 0; |
| | 2689 | manager->incidentPoint.vx = manager->incidentPoint.vy = manager->incidentPoint.vz = 0; |
| | 2690 | manager->aggregateNormal.vx = manager->aggregateNormal.vy = manager->aggregateNormal.vz = 0; |
| | 2691 | |
| | 2692 | manager->recommendedDistance = 0; |
| | 2693 | manager->timer = 0; |
| | 2694 | manager->primaryCollision = NULL; |
| | 2695 | manager->substate = AvSS_FreeMovement; |
| | 2696 | |
| | 2697 | /* Allows destruction of explosive objects. */ |
| | 2698 | manager->ClearanceDamage = AMMO_NPC_OBSTACLE_CLEAR; |
| | 2699 | } |
| | 2700 | |
| | 2701 | static void New_GetAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal) |
| | 2702 | { |
| | 2703 | VECTORCH spaceNormal,transverse; |
| | 2704 | VECTORCH direction[4]; |
| | 2705 | |
| | 2706 | assert(manager); |
| | 2707 | assert(sbPtr); |
| | 2708 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 2709 | assert(dynPtr); |
| | 2710 | |
| | 2711 | /* aggregateNormal should be normalised, and should point away from the collisions. */ |
| | 2712 | /* First dot it with gravity... */ |
| | 2713 | |
| | 2714 | if (dynPtr->UseStandardGravity) |
| | 2715 | { |
| | 2716 | dynPtr->GravityDirection.vx = 0; |
| | 2717 | dynPtr->GravityDirection.vy = 65536; |
| | 2718 | dynPtr->GravityDirection.vz = 0; |
| | 2719 | } |
| | 2720 | |
| | 2721 | int dot = -(DotProduct(&dynPtr->GravityDirection,aggregateNormal)); |
| | 2722 | |
| | 2723 | /* Hold that thought. */ |
| | 2724 | spaceNormal.vx = (aggregateNormal->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx)); |
| | 2725 | spaceNormal.vy = (aggregateNormal->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy)); |
| | 2726 | spaceNormal.vz = (aggregateNormal->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz)); |
| | 2727 | |
| | 2728 | Normalise(&spaceNormal); |
| | 2729 | /* Now, spaceNormal should be in the plane we want to consider. */ |
| | 2730 | CrossProduct(&spaceNormal,&dynPtr->GravityDirection,&transverse); |
| | 2731 | Normalise(&transverse); |
| | 2732 | /* ...And 'transverse' should be at 90degs to it. */ |
| | 2733 | |
| | 2734 | /* For now, emulate the old avoidance code... */ |
| | 2735 | |
| | 2736 | direction[0] = transverse; |
| | 2737 | direction[1].vx = -transverse.vx; |
| | 2738 | direction[1].vy = -transverse.vy; |
| | 2739 | direction[1].vz = -transverse.vz; |
| | 2740 | |
| | 2741 | // Added by Alex - see if we can get a better direction this way. |
| | 2742 | direction[2] = spaceNormal; |
| | 2743 | direction[3].vx = -direction[2].vx; |
| | 2744 | direction[3].vy = -direction[2].vy; |
| | 2745 | direction[3].vz = -direction[2].vz; |
| | 2746 | |
| | 2747 | direction[0].vx += (spaceNormal.vx/4); |
| | 2748 | direction[0].vy += (spaceNormal.vy/4); |
| | 2749 | direction[0].vz += (spaceNormal.vz/4); |
| | 2750 | direction[1].vx += (spaceNormal.vx/4); |
| | 2751 | direction[1].vy += (spaceNormal.vy/4); |
| | 2752 | direction[1].vz += (spaceNormal.vz/4); |
| | 2753 | |
| | 2754 | direction[2].vx -= (transverse.vx/4); |
| | 2755 | direction[2].vy -= (transverse.vy/4); |
| | 2756 | direction[2].vz -= (transverse.vz/4); |
| | 2757 | direction[3].vx -= (transverse.vx/4); |
| | 2758 | direction[3].vy -= (transverse.vy/4); |
| | 2759 | direction[3].vz -= (transverse.vz/4); |
| | 2760 | |
| | 2761 | Normalise(&direction[0]); |
| | 2762 | Normalise(&direction[1]); |
| | 2763 | Normalise(&direction[2]); |
| | 2764 | Normalise(&direction[3]); |
| | 2765 | |
| | 2766 | { |
| | 2767 | int this_distance, i; |
| | 2768 | int best_distance_so_far = 0; |
| | 2769 | int best_direction_so_far = 0; |
| | 2770 | |
| | 2771 | /* test how far we could go in each direction... */ |
| | 2772 | for( i=0; i < 4; i++ ) |
| | 2773 | { |
| | 2774 | VECTORCH startingPosition = sbPtr->DynPtr->Position; |
| | 2775 | VECTORCH testDirn = direction[i]; |
| | 2776 | |
| | 2777 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition,&testDirn); |
| | 2778 | |
| | 2779 | this_distance = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 2780 | |
| | 2781 | if( this_distance > best_distance_so_far ) |
| | 2782 | { |
| | 2783 | // What follows is an attempt to make sure we don't jump off any cliffs... |
| | 2784 | VECTORCH test_location; |
| | 2785 | testDirn.vx *= this_distance; |
| | 2786 | testDirn.vy *= this_distance; |
| | 2787 | testDirn.vz *= this_distance; |
| | 2788 | test_location.vx = startingPosition.vx + testDirn.vx; |
| | 2789 | test_location.vy = startingPosition.vy + testDirn.vy; |
| | 2790 | test_location.vz = startingPosition.vz + testDirn.vz; |
| | 2791 | |
| | 2792 | if( CheckMyFloorPoly(&test_location, sbPtr->containingModule) != NPC_GMD_NOPOLY) |
| | 2793 | { |
| | 2794 | best_direction_so_far = i; |
| | 2795 | best_distance_so_far = this_distance; |
| | 2796 | } |
| | 2797 | } |
| | 2798 | } |
| | 2799 | |
| | 2800 | manager->avoidanceDirection = direction[best_direction_so_far]; |
| | 2801 | } |
| | 2802 | } |
| | 2803 | |
| | 2804 | static void New_GetSecondAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager, VECTORCH *aggregateNormal) |
| | 2805 | { |
| | 2806 | VECTORCH spaceNormal,transverse; |
| | 2807 | VECTORCH direction1,direction2; |
| | 2808 | /* Yeesh. */ |
| | 2809 | |
| | 2810 | assert(manager); |
| | 2811 | assert(sbPtr); |
| | 2812 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 2813 | assert(dynPtr); |
| | 2814 | |
| | 2815 | /* aggregateNormal should be normalised, and should point away from the collisions. */ |
| | 2816 | /* First dot it with gravity... */ |
| | 2817 | |
| | 2818 | int dot = -(DotProduct(&dynPtr->GravityDirection,aggregateNormal)); |
| | 2819 | /* Hold that thought. */ |
| | 2820 | spaceNormal.vx = (aggregateNormal->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx)); |
| | 2821 | spaceNormal.vy = (aggregateNormal->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy)); |
| | 2822 | spaceNormal.vz = (aggregateNormal->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz)); |
| | 2823 | |
| | 2824 | Normalise(&spaceNormal); |
| | 2825 | /* Now, spaceNormal should be in the plane we want to consider. */ |
| | 2826 | CrossProduct(&spaceNormal,&dynPtr->GravityDirection,&transverse); |
| | 2827 | Normalise(&transverse); |
| | 2828 | /* ...And 'transverse' should be at 90degs to it. */ |
| | 2829 | |
| | 2830 | /* For now, emulate the old avoidance code... */ |
| | 2831 | |
| | 2832 | direction1 = transverse; |
| | 2833 | direction2.vx = -transverse.vx; |
| | 2834 | direction2.vy = -transverse.vy; |
| | 2835 | direction2.vz = -transverse.vz; |
| | 2836 | |
| | 2837 | if ((FastRandom() & 65535) < 32767) |
| | 2838 | { |
| | 2839 | direction1.vx += (spaceNormal.vx/2); |
| | 2840 | direction1.vy += (spaceNormal.vy/2); |
| | 2841 | direction1.vz += (spaceNormal.vz/2); |
| | 2842 | direction2.vx += (spaceNormal.vx/2); |
| | 2843 | direction2.vy += (spaceNormal.vy/2); |
| | 2844 | direction2.vz += (spaceNormal.vz/2); |
| | 2845 | } |
| | 2846 | else |
| | 2847 | { |
| | 2848 | direction1.vx += (spaceNormal.vx); |
| | 2849 | direction1.vy += (spaceNormal.vy); |
| | 2850 | direction1.vz += (spaceNormal.vz); |
| | 2851 | direction2.vx += (spaceNormal.vx); |
| | 2852 | direction2.vy += (spaceNormal.vy); |
| | 2853 | direction2.vz += (spaceNormal.vz); |
| | 2854 | } |
| | 2855 | |
| | 2856 | Normalise(&direction1); |
| | 2857 | Normalise(&direction2); |
| | 2858 | |
| | 2859 | { |
| | 2860 | int dir1dist,dir2dist; |
| | 2861 | /* test how far we could go in each direction... */ |
| | 2862 | { |
| | 2863 | VECTORCH startingPosition = sbPtr->DynPtr->Position; |
| | 2864 | VECTORCH testDirn = direction1; |
| | 2865 | |
| | 2866 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 2867 | |
| | 2868 | dir1dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 2869 | |
| | 2870 | startingPosition = sbPtr->DynPtr->Position; |
| | 2871 | testDirn = direction2; |
| | 2872 | |
| | 2873 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 2874 | |
| | 2875 | dir2dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 2876 | } |
| | 2877 | |
| | 2878 | manager->avoidanceDirection = (dir1dist > dir2dist) ? direction1 : direction2; |
| | 2879 | } |
| | 2880 | } |
| | 2881 | |
| | 2882 | void AlignVelocityToGravity(STRATEGYBLOCK *sbPtr,VECTORCH *velocity) |
| | 2883 | { |
| | 2884 | assert(sbPtr); |
| | 2885 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 2886 | assert(dynPtr); |
| | 2887 | |
| | 2888 | if (dynPtr->UseStandardGravity) |
| | 2889 | { |
| | 2890 | dynPtr->GravityDirection.vx = 0; |
| | 2891 | dynPtr->GravityDirection.vy = 65536; |
| | 2892 | dynPtr->GravityDirection.vz = 0; |
| | 2893 | } |
| | 2894 | |
| | 2895 | int dot = -(DotProduct(&dynPtr->GravityDirection,velocity)); |
| | 2896 | /* Hold that thought. */ |
| | 2897 | velocity->vx = (velocity->vx + MUL_FIXED(dot,dynPtr->GravityDirection.vx)); |
| | 2898 | velocity->vy = (velocity->vy + MUL_FIXED(dot,dynPtr->GravityDirection.vy)); |
| | 2899 | velocity->vz = (velocity->vz + MUL_FIXED(dot,dynPtr->GravityDirection.vz)); |
| | 2900 | |
| | 2901 | if (!velocity->vx && !velocity->vy && !velocity->vz) |
| | 2902 | { |
| | 2903 | /* That can't be good. Can it? */ |
| | 2904 | //velocity->vx = ONE_FIXED; |
| | 2905 | return; |
| | 2906 | } |
| | 2907 | |
| | 2908 | Normalise(velocity); |
| | 2909 | } |
| | 2910 | |
| | 2911 | static void InitialiseThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) |
| | 2912 | { |
| | 2913 | assert(manager); |
| | 2914 | assert(sbPtr); |
| | 2915 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 2916 | assert(dynPtr); |
| | 2917 | |
| | 2918 | if (dynPtr->UseStandardGravity) |
| | 2919 | { |
| | 2920 | dynPtr->GravityDirection.vx = 0; |
| | 2921 | dynPtr->GravityDirection.vy = 65536; |
| | 2922 | dynPtr->GravityDirection.vz = 0; |
| | 2923 | } |
| | 2924 | |
| | 2925 | /* Setup base vector. */ |
| | 2926 | { |
| | 2927 | /* Try using positive... z. */ |
| | 2928 | manager->baseVector.vx = sbPtr->DynPtr->OrientMat.mat31; |
| | 2929 | manager->baseVector.vy = sbPtr->DynPtr->OrientMat.mat32; |
| | 2930 | manager->baseVector.vz = sbPtr->DynPtr->OrientMat.mat33; |
| | 2931 | |
| | 2932 | int dot = (DotProduct(&dynPtr->GravityDirection,&manager->baseVector)); |
| | 2933 | |
| | 2934 | if (!((dot < 65000) && (dot > -65000))) |
| | 2935 | { |
| | 2936 | /* Too close. Let's use x. */ |
| | 2937 | manager->baseVector.vx = sbPtr->DynPtr->OrientMat.mat11; |
| | 2938 | manager->baseVector.vy = sbPtr->DynPtr->OrientMat.mat12; |
| | 2939 | manager->baseVector.vz = sbPtr->DynPtr->OrientMat.mat13; |
| | 2940 | } |
| | 2941 | |
| | 2942 | AlignVelocityToGravity(sbPtr,&manager->baseVector); |
| | 2943 | } |
| | 2944 | |
| | 2945 | manager->currentVector = manager->baseVector; |
| | 2946 | |
| | 2947 | /* Keep still! */ |
| | 2948 | manager->avoidanceDirection.vx = manager->avoidanceDirection.vy = manager->avoidanceDirection.vz = 0; |
| | 2949 | manager->bestVector.vx = manager->bestVector.vy = manager->bestVector.vz = 0; |
| | 2950 | |
| | 2951 | manager->basePoint = dynPtr->Position; |
| | 2952 | manager->stage = 0; |
| | 2953 | manager->bestDistance = 0; |
| | 2954 | manager->bestStage = 0; |
| | 2955 | |
| | 2956 | /* Finally, generate a matrix to rotate 22.5degs about GravityDirection. */ |
| | 2957 | |
| | 2958 | { |
| | 2959 | QUAT deltaRotQ; |
| | 2960 | /* Angle is 256, halfangle is 128. */ |
| | 2961 | int cosHalfAngle = GetCos(128); |
| | 2962 | int sinHalfAngle = GetSin(128); |
| | 2963 | |
| | 2964 | deltaRotQ.quatw = cosHalfAngle; |
| | 2965 | deltaRotQ.quatx = MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vx); |
| | 2966 | deltaRotQ.quaty = MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vy); |
| | 2967 | deltaRotQ.quatz = MUL_FIXED(sinHalfAngle,dynPtr->GravityDirection.vz); |
| | 2968 | |
| | 2969 | QNormalise(&deltaRotQ); |
| | 2970 | QuatToMat(&deltaRotQ,&manager->rotationMatrix); |
| | 2971 | } |
| | 2972 | |
| | 2973 | /* Say we're in Third Avoidance. */ |
| | 2974 | manager->substate = AvSS_ThirdAvoidance; |
| | 2975 | } |
| | 2976 | |
| | 2977 | static int SimpleEdgeDetectionTest(STRATEGYBLOCK *sbPtr, COLLISIONREPORT *vcr) |
| | 2978 | { |
| | 2979 | VECTORCH alpha,beta,tvec; |
| | 2980 | |
| | 2981 | GetTargetingPointOfObject_Far(sbPtr,&alpha); |
| | 2982 | /* Now add half a metre in positive z. */ |
| | 2983 | |
| | 2984 | tvec.vx = sbPtr->DynPtr->OrientMat.mat31; |
| | 2985 | tvec.vy = sbPtr->DynPtr->OrientMat.mat32; |
| | 2986 | tvec.vz = sbPtr->DynPtr->OrientMat.mat33; |
| | 2987 | |
| | 2988 | tvec.vx = MUL_FIXED(tvec.vx,1000); |
| | 2989 | tvec.vy = MUL_FIXED(tvec.vy,1000); |
| | 2990 | tvec.vz = MUL_FIXED(tvec.vz,1000); |
| | 2991 | |
| | 2992 | alpha.vx += tvec.vx; |
| | 2993 | alpha.vy += tvec.vy; |
| | 2994 | alpha.vz += tvec.vz; |
| | 2995 | |
| | 2996 | beta.vx = sbPtr->DynPtr->OrientMat.mat21; |
| | 2997 | beta.vy = sbPtr->DynPtr->OrientMat.mat22; |
| | 2998 | beta.vz = sbPtr->DynPtr->OrientMat.mat23; |
| | 2999 | Normalise(&beta); |
| | 3000 | |
| | 3001 | /* Now do an LOS test. */ |
| | 3002 | FindPolygonInLineOfSight(&beta, &alpha, 0, sbPtr->DisplayBlock); |
| | 3003 | |
| | 3004 | /* Pass the test if the test hit something within a metre (y) of dynPtr->Position. */ |
| | 3005 | |
| | 3006 | if (LOS_ObjectHitPtr) |
| | 3007 | { |
| | 3008 | VECTORCH offset; |
| | 3009 | int dot; |
| | 3010 | /* Examine LOS_Point. */ |
| | 3011 | offset.vx = LOS_Point.vx - sbPtr->DynPtr->Position.vx; |
| | 3012 | offset.vy = LOS_Point.vy - sbPtr->DynPtr->Position.vy; |
| | 3013 | offset.vz = LOS_Point.vz - sbPtr->DynPtr->Position.vz; |
| | 3014 | |
| | 3015 | dot = DotProduct(&offset, &beta); |
| | 3016 | |
| | 3017 | //printf("Dot %d\n",dot); |
| | 3018 | |
| | 3019 | if ((dot > -1000) && (dot < 1000)) |
| | 3020 | return 0; |
| | 3021 | } |
| | 3022 | |
| | 3023 | /* Must be about to hit a rail or an edge? */ |
| | 3024 | vcr->ObstacleSBPtr = NULL; |
| | 3025 | vcr->ObstacleNormal.vx = -sbPtr->DynPtr->OrientMat.mat31; |
| | 3026 | vcr->ObstacleNormal.vy = -sbPtr->DynPtr->OrientMat.mat32; |
| | 3027 | vcr->ObstacleNormal.vz = -sbPtr->DynPtr->OrientMat.mat33; |
| | 3028 | vcr->ObstaclePoint = LOS_Point; |
| | 3029 | vcr->NextCollisionReportPtr = NULL; |
| | 3030 | |
| | 3031 | return 1; |
| | 3032 | } |
| | 3033 | |
| | 3034 | static int SBIsEnvironment(STRATEGYBLOCK *sbPtr) |
| | 3035 | { |
| | 3036 | return (sbPtr == NULL) ? 1 : (sbPtr->DisplayBlock && sbPtr->DisplayBlock->Module); |
| | 3037 | } |
| | 3038 | |
| | 3039 | int New_NPC_IsObstructed(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager) |
| | 3040 | { |
| | 3041 | VECTORCH myVelocityDirection; |
| | 3042 | VECTORCH aggregateNormal = { 0,0,0 }; |
| | 3043 | int numObstructiveCollisions = 0; |
| | 3044 | STRATEGYBLOCK *highestPriorityCollision = (STRATEGYBLOCK *)-1; |
| | 3045 | |
| | 3046 | assert(manager); |
| | 3047 | assert(sbPtr); |
| | 3048 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 3049 | assert(dynPtr); |
| | 3050 | struct collisionreport *nextReport = dynPtr->CollisionReportPtr; |
| | 3051 | |
| | 3052 | /* check our velocity: if we haven't got one, we can't be obstructed, so just return */ |
| | 3053 | if(!sbPtr->DynPtr->LinVelocity.vx && !sbPtr->DynPtr->LinVelocity.vy && !sbPtr->DynPtr->LinVelocity.vz) |
| | 3054 | return 0; |
| | 3055 | |
| | 3056 | if (sbPtr->type == I_BehaviourMarine) |
| | 3057 | { |
| | 3058 | COLLISIONREPORT vcr; |
| | 3059 | |
| | 3060 | if (SimpleEdgeDetectionTest(sbPtr, &vcr)) |
| | 3061 | { |
| | 3062 | vcr.NextCollisionReportPtr = nextReport; |
| | 3063 | nextReport = &vcr; |
| | 3064 | } |
| | 3065 | } |
| | 3066 | |
| | 3067 | /* Trivial reject to save time. */ |
| | 3068 | if (nextReport == NULL) |
| | 3069 | return 0; |
| | 3070 | |
| | 3071 | /* get my velocity direction, normalised... */ |
| | 3072 | myVelocityDirection = dynPtr->LinVelocity; |
| | 3073 | Normalise(&myVelocityDirection); |
| | 3074 | |
| | 3075 | if (manager->substate == AvSS_FreeMovement) |
| | 3076 | Initialise_AvoidanceManager(manager); |
| | 3077 | |
| | 3078 | /* Walk the collision report list. */ |
| | 3079 | while(nextReport) |
| | 3080 | { |
| | 3081 | int normalDotWithVelocity = DotProduct(&nextReport->ObstacleNormal, &myVelocityDirection); |
| | 3082 | |
| | 3083 | if (normalDotWithVelocity < -32768) |
| | 3084 | { |
| | 3085 | /* If we're in FirstAvoidance already, might want to disregard the same collision again. */ |
| | 3086 | if (manager->substate == AvSS_FirstAvoidance) |
| | 3087 | { |
| | 3088 | if (!SBIsEnvironment(manager->primaryCollision)) |
| | 3089 | { |
| | 3090 | if (manager->primaryCollision == nextReport->ObstacleSBPtr) |
| | 3091 | { |
| | 3092 | /* Advance and continue. */ |
| | 3093 | nextReport = nextReport->NextCollisionReportPtr; |
| | 3094 | continue; |
| | 3095 | } |
| | 3096 | } |
| | 3097 | } |
| | 3098 | |
| | 3099 | /* We have detected a collision with a strategy, or an obstructive environment bit. */ |
| | 3100 | numObstructiveCollisions++; |
| | 3101 | aggregateNormal.vx += nextReport->ObstacleNormal.vx; |
| | 3102 | aggregateNormal.vy += nextReport->ObstacleNormal.vy; |
| | 3103 | aggregateNormal.vz += nextReport->ObstacleNormal.vz; |
| | 3104 | |
| | 3105 | /* Sort out highest priority collision. */ |
| | 3106 | if (highestPriorityCollision == (STRATEGYBLOCK *)-1) |
| | 3107 | { |
| | 3108 | highestPriorityCollision = nextReport->ObstacleSBPtr; |
| | 3109 | } |
| | 3110 | else |
| | 3111 | { |
| | 3112 | /* If this collision is with the environment, and the older one was not, replace it. */ |
| | 3113 | |
| | 3114 | if (SBIsEnvironment(nextReport->ObstacleSBPtr) && !SBIsEnvironment(highestPriorityCollision)) |
| | 3115 | highestPriorityCollision = nextReport->ObstacleSBPtr; |
| | 3116 | } |
| | 3117 | |
| | 3118 | if(nextReport->ObstacleSBPtr) |
| | 3119 | { |
| | 3120 | if(nextReport->ObstacleSBPtr->type == I_BehaviourInanimateObject) |
| | 3121 | { |
| | 3122 | if(!nextReport->ObstacleSBPtr->DamageBlock.Indestructable) |
| | 3123 | { |
| | 3124 | INANIMATEOBJECT_STATUSBLOCK* objectstatusptr = nextReport->ObstacleSBPtr->dataptr; |
| | 3125 | |
| | 3126 | if (objectstatusptr) |
| | 3127 | { |
| | 3128 | /* Consider explosive objects as obstructions to most things. */ |
| | 3129 | |
| | 3130 | if (!objectstatusptr->explosionType || (manager->ClearanceDamage != AMMO_NPC_OBSTACLE_CLEAR)) |
| | 3131 | { |
| | 3132 | /* aha: an object which the npc can destroy... damage it, and return zero. */ |
| | 3133 | CauseDamageToObject(nextReport->ObstacleSBPtr, &TemplateAmmo[manager->ClearanceDamage].MaxDamage, ONE_FIXED,NULL); |
| | 3134 | return 0; |
| | 3135 | /* After a few frames of that, there'll just be real obstructions. */ |
| | 3136 | } |
| | 3137 | } |
| | 3138 | } |
| | 3139 | } |
| | 3140 | } |
| | 3141 | } |
| | 3142 | |
| | 3143 | nextReport = nextReport->NextCollisionReportPtr; |
| | 3144 | } |
| | 3145 | |
| | 3146 | if (!numObstructiveCollisions) |
| | 3147 | return 0; /* No collisions! Woohoo! But don't reset the substate... */ |
| | 3148 | |
| | 3149 | switch (manager->substate) |
| | 3150 | { |
| | 3151 | case AvSS_FreeMovement: |
| | 3152 | default: |
| | 3153 | { |
| | 3154 | /* Right, we've run into something all right. */ |
| | 3155 | assert(highestPriorityCollision != (STRATEGYBLOCK *)-1); |
| | 3156 | |
| | 3157 | manager->primaryCollision = highestPriorityCollision; |
| | 3158 | manager->incidenceDirection = myVelocityDirection; |
| | 3159 | manager->incidentPoint = dynPtr->Position; |
| | 3160 | /* Decide on a distance... */ |
| | 3161 | |
| | 3162 | if (SBIsEnvironment(manager->primaryCollision)) |
| | 3163 | manager->recommendedDistance = 3000; |
| | 3164 | else |
| | 3165 | manager->recommendedDistance = 2000; |
| | 3166 | |
| | 3167 | manager->timer = STANDARD_AVOIDANCE_TIME; |
| | 3168 | |
| | 3169 | /* ...and a direction. */ |
| | 3170 | Normalise(&aggregateNormal); |
| | 3171 | manager->aggregateNormal = aggregateNormal; |
| | 3172 | |
| | 3173 | New_GetAvoidanceDirection(sbPtr,manager,&aggregateNormal); |
| | 3174 | |
| | 3175 | manager->substate = AvSS_FirstAvoidance; |
| | 3176 | } |
| | 3177 | return 1; |
| | 3178 | case AvSS_FirstAvoidance: |
| | 3179 | { |
| | 3180 | /* Right, we've run into something again. */ |
| | 3181 | /* Retain point, direction and distance. */ |
| | 3182 | /* ...but get a new direction. */ |
| | 3183 | |
| | 3184 | aggregateNormal.vx += manager->aggregateNormal.vx; |
| | 3185 | aggregateNormal.vy += manager->aggregateNormal.vy; |
| | 3186 | aggregateNormal.vz += manager->aggregateNormal.vz; |
| | 3187 | |
| | 3188 | /* Add the new aggregatenormal to the old one, and normalise... */ |
| | 3189 | |
| | 3190 | Normalise(&aggregateNormal); |
| | 3191 | |
| | 3192 | /* Then pass that number into the second direction system. */ |
| | 3193 | New_GetSecondAvoidanceDirection(sbPtr,manager,&aggregateNormal); |
| | 3194 | |
| | 3195 | manager->substate = AvSS_SecondAvoidance; |
| | 3196 | manager->timer = STANDARD_AVOIDANCE_TIME; |
| | 3197 | } |
| | 3198 | return 1; |
| | 3199 | case AvSS_SecondAvoidance: |
| | 3200 | case AvSS_ThirdAvoidance: |
| | 3201 | { |
| | 3202 | /* Right, we've run into something again again. (Again.) */ |
| | 3203 | |
| | 3204 | /* Retain point, direction and distance, and go directly to third avoidance. */ |
| | 3205 | |
| | 3206 | manager->timer = STANDARD_AVOIDANCE_TIME; |
| | 3207 | InitialiseThirdAvoidance(sbPtr,manager); |
| | 3208 | |
| | 3209 | return 1; |
| | 3210 | } |
| | 3211 | } |
| | 3212 | } |
| | 3213 | |
| | 3214 | static int ExecuteThirdAvoidance(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) |
| | 3215 | { |
| | 3216 | assert(manager); |
| | 3217 | assert(sbPtr); |
| | 3218 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 3219 | assert(dynPtr); |
| | 3220 | |
| | 3221 | if (manager->stage < 9) |
| | 3222 | { |
| | 3223 | /* Still in the spin. Increment stage NOW! */ |
| | 3224 | manager->stage++; |
| | 3225 | /* Now raycast. */ |
| | 3226 | { |
| | 3227 | VECTORCH testDirn = manager->currentVector; |
| | 3228 | |
| | 3229 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &manager->basePoint, &testDirn); |
| | 3230 | |
| | 3231 | if (LOS_ObjectHitPtr) |
| | 3232 | { |
| | 3233 | /* Hit environment! */ |
| | 3234 | if (LOS_Lambda>manager->bestDistance) |
| | 3235 | { |
| | 3236 | /* Register this as best. */ |
| | 3237 | manager->bestDistance = LOS_Lambda; |
| | 3238 | manager->bestStage = manager->stage; |
| | 3239 | manager->bestVector = testDirn; |
| | 3240 | } |
| | 3241 | } |
| | 3242 | |
| | 3243 | testDirn.vx = -manager->currentVector.vx; |
| | 3244 | testDirn.vy = -manager->currentVector.vy; |
| | 3245 | testDirn.vz = -manager->currentVector.vz; |
| | 3246 | |
| | 3247 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &manager->basePoint, &testDirn); |
| | 3248 | |
| | 3249 | if (LOS_ObjectHitPtr) |
| | 3250 | { |
| | 3251 | /* Hit environment! */ |
| | 3252 | if (LOS_Lambda>manager->bestDistance) |
| | 3253 | { |
| | 3254 | /* Register this as best. */ |
| | 3255 | manager->bestDistance = LOS_Lambda; |
| | 3256 | manager->bestStage = -manager->stage; |
| | 3257 | manager->bestVector = testDirn; |
| | 3258 | } |
| | 3259 | } |
| | 3260 | } |
| | 3261 | |
| | 3262 | /* Now, rotate vector round. */ |
| | 3263 | RotateVector(&manager->currentVector,&manager->rotationMatrix); |
| | 3264 | } |
| | 3265 | else if (manager->stage == 9) |
| | 3266 | { |
| | 3267 | /* Now, we must have checked all 16 directions. */ |
| | 3268 | if (manager->bestStage == 0) |
| | 3269 | { |
| | 3270 | /* That's a bit of a mystery. Return fubared. */ |
| | 3271 | return -1; |
| | 3272 | } |
| | 3273 | |
| | 3274 | /* Now, my beautiful assistant, open the envelope! */ |
| | 3275 | if (manager->bestDistance < THIRD_AVOIDANCE_MINDIST) |
| | 3276 | { |
| | 3277 | /* Whatta loada junk. */ |
| | 3278 | return -1; |
| | 3279 | } |
| | 3280 | else |
| | 3281 | { |
| | 3282 | /* Go, my child, in the direction of destiny! */ |
| | 3283 | manager->avoidanceDirection = manager->bestVector; |
| | 3284 | Normalise(&manager->avoidanceDirection); |
| | 3285 | manager->stage++; |
| | 3286 | return 0; |
| | 3287 | } |
| | 3288 | } |
| | 3289 | else |
| | 3290 | { |
| | 3291 | /* In stage 10, we're going that way and trying to escape. */ |
| | 3292 | if (New_NPC_IsObstructed(sbPtr,manager) == 1) |
| | 3293 | { |
| | 3294 | VECTORCH offset; |
| | 3295 | /* We're obstructed AGAIN! Gordon Bennet... restart? */ |
| | 3296 | offset.vx = dynPtr->Position.vx-manager->basePoint.vx; |
| | 3297 | offset.vy = dynPtr->Position.vy-manager->basePoint.vy; |
| | 3298 | offset.vz = dynPtr->Position.vz-manager->basePoint.vz; |
| | 3299 | |
| | 3300 | if (Approximate3dMagnitude(&offset) < 300) |
| | 3301 | { |
| | 3302 | /* I suspect restarting will get us nowhere, * |
| | 3303 | * something's in the primary path. Fail! */ |
| | 3304 | return -1; |
| | 3305 | } |
| | 3306 | |
| | 3307 | InitialiseThirdAvoidance(sbPtr,manager); |
| | 3308 | return 0; |
| | 3309 | } |
| | 3310 | |
| | 3311 | /* Are we far enough away? */ |
| | 3312 | { |
| | 3313 | VECTORCH offset; |
| | 3314 | |
| | 3315 | offset.vx = dynPtr->Position.vx-manager->incidentPoint.vx; |
| | 3316 | offset.vy = dynPtr->Position.vy-manager->incidentPoint.vy; |
| | 3317 | offset.vz = dynPtr->Position.vz-manager->incidentPoint.vz; |
| | 3318 | |
| | 3319 | if (Approximate3dMagnitude(&offset) > manager->recommendedDistance) |
| | 3320 | { |
| | 3321 | /* Now let's check against third avoidance. */ |
| | 3322 | offset.vx = dynPtr->Position.vx-manager->basePoint.vx; |
| | 3323 | offset.vy = dynPtr->Position.vy-manager->basePoint.vy; |
| | 3324 | offset.vz = dynPtr->Position.vz-manager->basePoint.vz; |
| | 3325 | |
| | 3326 | if (Approximate3dMagnitude(&offset) > (THIRD_AVOIDANCE_MINDIST >> 1)) |
| | 3327 | { |
| | 3328 | /* Exit with success! Glory be! */ |
| | 3329 | return 1; |
| | 3330 | } |
| | 3331 | } |
| | 3332 | else |
| | 3333 | { |
| | 3334 | /* Still check, to stop repeated third avoidance bouncing. */ |
| | 3335 | offset.vx = dynPtr->Position.vx-manager->basePoint.vx; |
| | 3336 | offset.vy = dynPtr->Position.vy-manager->basePoint.vy; |
| | 3337 | offset.vz = dynPtr->Position.vz-manager->basePoint.vz; |
| | 3338 | |
| | 3339 | if (Approximate3dMagnitude(&offset) > ((manager->bestDistance) >> 1)) |
| | 3340 | return 1; /* Exit with success, before we look stupid! */ |
| | 3341 | } |
| | 3342 | } |
| | 3343 | /* Still here, hmm? */ |
| | 3344 | return 0; |
| | 3345 | } |
| | 3346 | |
| | 3347 | return 0; |
| | 3348 | } |
| | 3349 | |
| | 3350 | AVOIDANCE_RETURN_CONDITION AllNewAvoidanceKernel(STRATEGYBLOCK *sbPtr,NPC_AVOIDANCEMANAGER *manager) |
| | 3351 | { |
| | 3352 | /* Velocity must be set deliberately... even if it's null. */ |
| | 3353 | |
| | 3354 | assert(manager); |
| | 3355 | assert(sbPtr); |
| | 3356 | DYNAMICSBLOCK *dynPtr = sbPtr->DynPtr; |
| | 3357 | assert(dynPtr); |
| | 3358 | |
| | 3359 | /* Want to split here based on substate. */ |
| | 3360 | |
| | 3361 | switch (manager->substate) |
| | 3362 | { |
| | 3363 | case AvSS_FreeMovement: |
| | 3364 | { |
| | 3365 | /* Really shouldn't be here. Go'way. */ |
| | 3366 | Initialise_AvoidanceManager(manager); |
| | 3367 | } |
| | 3368 | return AvRC_Clear; |
| | 3369 | case AvSS_FirstAvoidance: |
| | 3370 | { |
| | 3371 | if (New_NPC_IsObstructed(sbPtr,manager) == 1) |
| | 3372 | return AvRC_Avoidance; /* We're obstructed AGAIN! */ |
| | 3373 | |
| | 3374 | /* Are we far enough away? */ |
| | 3375 | { |
| | 3376 | VECTORCH offset; |
| | 3377 | |
| | 3378 | offset.vx = dynPtr->Position.vx-manager->incidentPoint.vx; |
| | 3379 | offset.vy = dynPtr->Position.vy-manager->incidentPoint.vy; |
| | 3380 | offset.vz = dynPtr->Position.vz-manager->incidentPoint.vz; |
| | 3381 | |
| | 3382 | if (Approximate3dMagnitude(&offset) > manager->recommendedDistance) |
| | 3383 | { |
| | 3384 | /* Exit! */ |
| | 3385 | Initialise_AvoidanceManager(manager); |
| | 3386 | return AvRC_Clear; |
| | 3387 | } |
| | 3388 | } |
| | 3389 | |
| | 3390 | manager->timer -= NormalFrameTime; |
| | 3391 | |
| | 3392 | if (manager->timer <= 0) |
| | 3393 | { |
| | 3394 | /* Ooh, we're in a fix here... */ |
| | 3395 | /* Probably need Avoidance 3 for this. */ |
| | 3396 | InitialiseThirdAvoidance(sbPtr,manager); |
| | 3397 | return AvRC_Avoidance; |
| | 3398 | } |
| | 3399 | } |
| | 3400 | break; |
| | 3401 | case AvSS_SecondAvoidance: |
| | 3402 | { |
| | 3403 | if (New_NPC_IsObstructed(sbPtr,manager) == 1) |
| | 3404 | return(AvRC_Avoidance); /* Should be in Third Avoidance here. */ |
| | 3405 | |
| | 3406 | /* Are we far enough away? */ |
| | 3407 | { |
| | 3408 | VECTORCH offset; |
| | 3409 | |
| | 3410 | offset.vx = dynPtr->Position.vx-manager->incidentPoint.vx; |
| | 3411 | offset.vy = dynPtr->Position.vy-manager->incidentPoint.vy; |
| | 3412 | offset.vz = dynPtr->Position.vz-manager->incidentPoint.vz; |
| | 3413 | |
| | 3414 | if (Approximate3dMagnitude(&offset) > manager->recommendedDistance) |
| | 3415 | { |
| | 3416 | /* Exit! */ |
| | 3417 | Initialise_AvoidanceManager(manager); |
| | 3418 | return AvRC_Clear; |
| | 3419 | } |
| | 3420 | } |
| | 3421 | |
| | 3422 | manager->timer -= NormalFrameTime; |
| | 3423 | |
| | 3424 | if (manager->timer <= 0) |
| | 3425 | { |
| | 3426 | /* Ooh, we're in a fix here... */ |
| | 3427 | /* Probably need Avoidance 3 for this. */ |
| | 3428 | InitialiseThirdAvoidance(sbPtr,manager); |
| | 3429 | return AvRC_Avoidance; |
| | 3430 | } |
| | 3431 | } |
| | 3432 | break; |
| | 3433 | case AvSS_ThirdAvoidance: |
| | 3434 | { |
| | 3435 | /* Let's have a whole function here! */ |
| | 3436 | int result = ExecuteThirdAvoidance(sbPtr,manager); |
| | 3437 | |
| | 3438 | if (result == -1) |
| | 3439 | { |
| | 3440 | /* Totally fubared. Return failure. */ |
| | 3441 | Initialise_AvoidanceManager(manager); |
| | 3442 | return AvRC_Failure; |
| | 3443 | } |
| | 3444 | else if (result == 1) |
| | 3445 | { |
| | 3446 | /* Success! Return clear. */ |
| | 3447 | Initialise_AvoidanceManager(manager); |
| | 3448 | return AvRC_Clear; |
| | 3449 | } |
| | 3450 | else |
| | 3451 | { |
| | 3452 | /* Still going, return avoidance. */ |
| | 3453 | return AvRC_Avoidance; |
| | 3454 | } |
| | 3455 | break; |
| | 3456 | } |
| | 3457 | default: |
| | 3458 | assert(0); |
| | 3459 | break; |
| | 3460 | } |
| | 3461 | |
| | 3462 | return AvRC_Avoidance; |
| | 3463 | } |
| | 3464 | |
| | 3465 | int GetAvoidanceDirection(STRATEGYBLOCK *sbPtr, NPC_AVOIDANCEMANAGER *manager) |
| | 3466 | { |
| | 3467 | VECTORCH newDirection1, newDirection2; |
| | 3468 | int dir1dist, dir2dist; |
| | 3469 | MATRIXCH matrix; |
| | 3470 | EULER euler; |
| | 3471 | |
| | 3472 | assert(manager); |
| | 3473 | assert(sbPtr); |
| | 3474 | |
| | 3475 | /* just in case */ |
| | 3476 | if(!sbPtr->containingModule) |
| | 3477 | return 0; |
| | 3478 | |
| | 3479 | /* going for a 90 degree turn + back a bit */ |
| | 3480 | newDirection1 = manager->incidenceDirection; |
| | 3481 | newDirection2 = newDirection1; |
| | 3482 | euler.EulerX = 0; |
| | 3483 | euler.EulerY = 1024; |
| | 3484 | euler.EulerZ = 0; |
| | 3485 | CreateEulerMatrix( &euler, &matrix ); |
| | 3486 | RotateVector( &newDirection1, &matrix ); |
| | 3487 | euler.EulerY = 3072; |
| | 3488 | CreateEulerMatrix( &euler, &matrix ); |
| | 3489 | RotateVector( &newDirection2, &matrix ); |
| | 3490 | |
| | 3491 | Normalise(&newDirection1); |
| | 3492 | Normalise(&newDirection2); |
| | 3493 | |
| | 3494 | /* test how far we could go in each direction... */ |
| | 3495 | { |
| | 3496 | VECTORCH startingPosition = sbPtr->DynPtr->Position; |
| | 3497 | VECTORCH testDirn = newDirection1; |
| | 3498 | |
| | 3499 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 3500 | |
| | 3501 | dir1dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 3502 | |
| | 3503 | startingPosition = sbPtr->DynPtr->Position; |
| | 3504 | testDirn = newDirection2; |
| | 3505 | |
| | 3506 | CheckForVectorIntersectionWith3dObject(sbPtr->containingModule->m_dptr, &startingPosition, &testDirn); |
| | 3507 | |
| | 3508 | dir2dist = LOS_ObjectHitPtr ? LOS_Lambda : NPC_MAX_VIEWRANGE; |
| | 3509 | } |
| | 3510 | |
| | 3511 | manager->avoidanceDirection = (dir1dist > dir2dist) ? newDirection1 : newDirection2; |
| | 3512 | |
| | 3513 | return 1; |
| | 3514 | } |
| | 3515 | |
| | 3516 | static int FrisbeeSight_FrustrumReject(VECTORCH *localOffset) |
| | 3517 | { |
| | 3518 | //printf("Local Offset: %d %d %d\n",localOffset->vx,localOffset->vy,localOffset->vz); |
| | 3519 | |
| | 3520 | VECTORCH fixed_offset = *localOffset; |
| | 3521 | |
| | 3522 | if (((fixed_offset.vx < 0) && ( (fixed_offset.vy < -fixed_offset.vx) && (fixed_offset.vy >= 0))) |
| | 3523 | || (((fixed_offset.vy < 0) && (-fixed_offset.vy < -fixed_offset.vx )) && ( fixed_offset.vz > 0 ) )) |
| | 3524 | { |
| | 3525 | /* 90 horizontal, 90 vertical? */ |
| | 3526 | return 1; |
| | 3527 | } |
| | 3528 | |
| | 3529 | return 0; |
| | 3530 | } |
| | 3531 | |
| | 3532 | int NPCCanSeeTarget(STRATEGYBLOCK *sbPtr, STRATEGYBLOCK *target) |
| | 3533 | { |
| | 3534 | int frustrum_test; |
| | 3535 | |
| | 3536 | if ((target->containingModule == NULL) || (sbPtr->containingModule == NULL)) |
| | 3537 | return 0; |
| | 3538 | |
| | 3539 | if ((target->DisplayBlock == NULL) || (sbPtr->DisplayBlock == NULL)) |
| | 3540 | return IsModuleVisibleFromModule(target->containingModule, sbPtr->containingModule); |
| | 3541 | |
| | 3542 | switch (sbPtr->type) |
| | 3543 | { |
| | 3544 | case I_BehaviourMarine: |
| | 3545 | { |
| | 3546 | MATRIXCH WtoL; |
| | 3547 | VECTORCH offset, sourcepos, targetpos; |
| | 3548 | |
| | 3549 | MARINE_STATUS_BLOCK *marineStatusPointer = (MARINE_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 3550 | assert(marineStatusPointer); |
| | 3551 | /* Arc reject. */ |
| | 3552 | |
| | 3553 | SECTION_DATA *head_sec = GetThisSectionData(marineStatusPointer->HModelController.section_data, "head"); |
| | 3554 | |
| | 3555 | if (head_sec) |
| | 3556 | { |
| | 3557 | WtoL = head_sec->SecMat; |
| | 3558 | sourcepos = head_sec->World_Offset; |
| | 3559 | } |
| | 3560 | else |
| | 3561 | { |
| | 3562 | WtoL = sbPtr->DynPtr->OrientMat; |
| | 3563 | GetTargetingPointOfObject_Far(sbPtr,&sourcepos); |
| | 3564 | } |
| | 3565 | |
| | 3566 | GetTargetingPointOfObject_Far(target,&targetpos); |
| | 3567 | |
| | 3568 | offset.vx = sourcepos.vx - targetpos.vx; |
| | 3569 | offset.vy = sourcepos.vy - targetpos.vy; |
| | 3570 | offset.vz = sourcepos.vz - targetpos.vz; |
| | 3571 | |
| | 3572 | TransposeMatrixCH(&WtoL); |
| | 3573 | RotateVector(&offset,&WtoL); |
| | 3574 | |
| | 3575 | frustrum_test = MarineSight_FrustrumReject(sbPtr,&offset,target); |
| | 3576 | } |
| | 3577 | break; |
| | 3578 | case I_BehaviourAutoGun: |
| | 3579 | { |
| | 3580 | /* Less pretentious, based on the SB. */ |
| | 3581 | MATRIXCH WtoL; |
| | 3582 | VECTORCH offset, sourcepos, targetpos; |
| | 3583 | /* Arc reject. */ |
| | 3584 | |
| | 3585 | WtoL = sbPtr->DynPtr->OrientMat; |
| | 3586 | GetTargetingPointOfObject_Far(sbPtr,&sourcepos); |
| | 3587 | GetTargetingPointOfObject_Far(target,&targetpos); |
| | 3588 | |
| | 3589 | offset.vx = sourcepos.vx-targetpos.vx; |
| | 3590 | offset.vy = sourcepos.vy-targetpos.vy; |
| | 3591 | offset.vz = sourcepos.vz-targetpos.vz; |
| | 3592 | |
| | 3593 | TransposeMatrixCH(&WtoL); |
| | 3594 | RotateVector(&offset,&WtoL); |
| | 3595 | |
| | 3596 | frustrum_test = AGunSight_FrustrumReject(&offset); |
| | 3597 | } |
| | 3598 | break; |
| | 3599 | case I_BehaviourFrisbee: |
| | 3600 | { |
| | 3601 | MATRIXCH WtoL; |
| | 3602 | VECTORCH offset, sourcepos, targetpos; |
| | 3603 | |
| | 3604 | FRISBEE_BEHAV_BLOCK *frisbeeStatusPointer = (FRISBEE_BEHAV_BLOCK *)(sbPtr->dataptr); |
| | 3605 | assert(frisbeeStatusPointer); |
| | 3606 | /* Arc reject. */ |
| | 3607 | |
| | 3608 | SECTION_DATA *disc_sec = GetThisSectionData(frisbeeStatusPointer->HModelController.section_data, "Mdisk"); |
| | 3609 | |
| | 3610 | if (disc_sec) |
| | 3611 | { |
| | 3612 | WtoL = disc_sec->SecMat; |
| | 3613 | sourcepos = disc_sec->World_Offset; |
| | 3614 | } |
| | 3615 | else |
| | 3616 | { |
| | 3617 | WtoL = sbPtr->DynPtr->OrientMat; |
| | 3618 | GetTargetingPointOfObject_Far(sbPtr,&sourcepos); |
| | 3619 | } |
| | 3620 | |
| | 3621 | GetTargetingPointOfObject_Far(target,&targetpos); |
| | 3622 | |
| | 3623 | offset.vx = sourcepos.vx-targetpos.vx; |
| | 3624 | offset.vy = sourcepos.vy-targetpos.vy; |
| | 3625 | offset.vz = sourcepos.vz-targetpos.vz; |
| | 3626 | |
| | 3627 | TransposeMatrixCH(&WtoL); |
| | 3628 | RotateVector(&offset,&WtoL); |
| | 3629 | |
| | 3630 | frustrum_test = FrisbeeSight_FrustrumReject(&offset); |
| | 3631 | } |
| | 3632 | break; |
| | 3633 | case I_BehaviourXenoborg: |
| | 3634 | { |
| | 3635 | MATRIXCH WtoL; |
| | 3636 | VECTORCH offset, sourcepos, targetpos; |
| | 3637 | |
| | 3638 | XENO_STATUS_BLOCK *xenoStatusPointer = (XENO_STATUS_BLOCK *)(sbPtr->dataptr); |
| | 3639 | assert(xenoStatusPointer); |
| | 3640 | /* Arc reject. */ |
| | 3641 | |
| | 3642 | SECTION_DATA *head_sec = GetThisSectionData(xenoStatusPointer->HModelController.section_data, "head"); |
| | 3643 | |
| | 3644 | if (head_sec) |
| | 3645 | { |
| | 3646 | WtoL = head_sec->SecMat; |
| | 3647 | sourcepos = head_sec->World_Offset; |
| | 3648 | } |
| | 3649 | else |
| | 3650 | { |
| | 3651 | WtoL = sbPtr->DynPtr->OrientMat; |
| | 3652 | GetTargetingPointOfObject_Far(sbPtr,&sourcepos); |
| | 3653 | } |
| | 3654 | |
| | 3655 | GetTargetingPointOfObject_Far(target,&targetpos); |
| | 3656 | |
| | 3657 | offset.vx = sourcepos.vx-targetpos.vx; |
| | 3658 | offset.vy = sourcepos.vy-targetpos.vy; |
| | 3659 | offset.vz = sourcepos.vz-targetpos.vz; |
| | 3660 | |
| | 3661 | TransposeMatrixCH(&WtoL); |
| | 3662 | RotateVector(&offset,&WtoL); |
| | 3663 | |
| | 3664 | frustrum_test = XenoSight_FrustrumReject(sbPtr, &offset); |
| | 3665 | } |
| | 3666 | break; |
| | 3667 | default: |
| | 3668 | frustrum_test = 1; |
| | 3669 | } |
| | 3670 | |
| | 3671 | if (frustrum_test) |
| | 3672 | { |
| | 3673 | /* connect eyeposition to head */ |
| | 3674 | VECTORCH eyePosition = {0, -1500, 0}; |
| | 3675 | RotateVector(&eyePosition, &sbPtr->DynPtr->OrientMat); |
| | 3676 | |
| | 3677 | eyePosition.vx += sbPtr->DynPtr->Position.vx; |
| | 3678 | eyePosition.vy += sbPtr->DynPtr->Position.vy; |
| | 3679 | eyePosition.vz += sbPtr->DynPtr->Position.vz; |
| | 3680 | |
| | 3681 | return IsThisObjectVisibleFromThisPosition_WithIgnore(target->DisplayBlock, sbPtr->DisplayBlock, &eyePosition); |
| | 3682 | } |
| | 3683 | |
| | 3684 | return 0; |
| | 3685 | } |