| | 1 | #include "system.h" |
| | 2 | #include "prototyp.h" |
| | 3 | #include "stratdef.h" |
| | 4 | #include "bh_types.h" |
| | 5 | #include "npc_marine.h" |
| | 6 | #include "weapons.h" |
| | 7 | #include "los.h" |
| | 8 | #include "paintball.h" |
| | 9 | #include <math.h> |
| | 10 | #include <stdio.h> |
| | 11 | #include <stdlib.h> |
| | 12 | #include <assert.h> |
| | 13 | #include "pldghost.h" |
| | 14 | |
| | 15 | extern int ObjectShouldAppearOnMotionTracker(STRATEGYBLOCK *sbPtr); |
| | 16 | /* |
| | 17 | |
| | 18 | Foley and Van Dam 2d distance function |
| | 19 | |
| | 20 | WARNING! Returns distance x 3 |
| | 21 | |
| | 22 | Here is the F & VD distance function: |
| | 23 | |
| | 24 | x + z + (max(x,z) * 2) |
| | 25 | ---------------------- |
| | 26 | 3 |
| | 27 | */ |
| | 28 | |
| | 29 | static int FandVD_Distance_2d(const VECTOR2D *v0, const VECTOR2D *v1) |
| | 30 | { |
| | 31 | int dx = abs(v1->vx - v0->vx); |
| | 32 | int dy = abs(v1->vy - v0->vy); |
| | 33 | |
| | 34 | if(dx > dy) |
| | 35 | return (dx + dy + (dx* 2)); |
| | 36 | else |
| | 37 | return (dx + dy + (dy* 2)); |
| | 38 | } |
| | 39 | |
| | 40 | |
| | 41 | /* |
| | 42 | Foley and Van Dam 3d distance function |
| | 43 | |
| | 44 | WARNING! Returns distance x 9 |
| | 45 | |
| | 46 | For a 3d version, calculate (f(f(x,y), y*3))/9 |
| | 47 | |
| | 48 | */ |
| | 49 | |
| | 50 | static int FandVD_Distance_3d(const VECTORCH *v0, const VECTORCH *v1) |
| | 51 | { |
| | 52 | int dz = abs((v1->vz - v0->vz) * 3); |
| | 53 | |
| | 54 | int dxy = FandVD_Distance_2d((VECTOR2D *) v0, (VECTOR2D *) v1); |
| | 55 | |
| | 56 | if(dxy > dz) |
| | 57 | return (dxy + dz + (dxy * 2)); |
| | 58 | else |
| | 59 | return (dxy + dz + (dz * 2)); |
| | 60 | } |
| | 61 | |
| | 62 | |
| | 63 | extern SCREENDESCRIPTORBLOCK ScreenDescriptorBlock; |
| | 64 | |
| | 65 | int CalculateFiringSolution(VECTORCH* firing_pos, VECTORCH* target_pos, VECTORCH* target_vel, int projectile_speed, VECTORCH* solution) |
| | 66 | { |
| | 67 | VECTORCH rotated_solution; |
| | 68 | MATRIXCH mat; |
| | 69 | |
| | 70 | assert(target_vel); |
| | 71 | |
| | 72 | //get a normalised vector from start to destination |
| | 73 | VECTORCH normal = *target_pos; //normal from firer to target |
| | 74 | |
| | 75 | normal.vx -= firing_pos->vx; |
| | 76 | normal.vy -= firing_pos->vy; |
| | 77 | normal.vz -= firing_pos->vz; |
| | 78 | |
| | 79 | if(!normal.vx && !normal.vy && !normal.vz) |
| | 80 | return 0; |
| | 81 | |
| | 82 | Approximate3dMagnitude(&normal); |
| | 83 | Normalise(&normal); |
| | 84 | |
| | 85 | //calculate a matrix that will rotate the normal to the zaxis |
| | 86 | { |
| | 87 | //normal will be the third row |
| | 88 | VECTORCH row1,row2; |
| | 89 | |
| | 90 | if(normal.vx > 30000 || normal.vx < -30000 || normal.vy > 30000 || normal.vy < -30000) |
| | 91 | { |
| | 92 | row1.vx = -normal.vy; |
| | 93 | row1.vy = normal.vx; |
| | 94 | row1.vz = 0; |
| | 95 | } |
| | 96 | else |
| | 97 | { |
| | 98 | row1.vx = -normal.vz; |
| | 99 | row1.vy = 0; |
| | 100 | row1.vz = normal.vx; |
| | 101 | } |
| | 102 | |
| | 103 | Normalise(&row1); |
| | 104 | |
| | 105 | CrossProduct(&normal,&row1,&row2); |
| | 106 | |
| | 107 | mat.mat11 = row1.vx; |
| | 108 | mat.mat21 = row1.vy; |
| | 109 | mat.mat31 = row1.vz; |
| | 110 | |
| | 111 | mat.mat12 = row2.vx; |
| | 112 | mat.mat22 = row2.vy; |
| | 113 | mat.mat32 = row2.vz; |
| | 114 | |
| | 115 | mat.mat13 = normal.vx; |
| | 116 | mat.mat23 = normal.vy; |
| | 117 | mat.mat33 = normal.vz; |
| | 118 | } |
| | 119 | |
| | 120 | //apply the rotation to the velocity |
| | 121 | VECTORCH rotated_vel = *target_vel; |
| | 122 | RotateVector(&rotated_vel,&mat); |
| | 123 | |
| | 124 | //is the target moving too fast? |
| | 125 | if(rotated_vel.vz >= projectile_speed || -rotated_vel.vz >= projectile_speed) |
| | 126 | return 0; |
| | 127 | |
| | 128 | //the x and y components of the rotated solution should match the rotated velocity |
| | 129 | //(scale down by projectile speed , because we want a normalised direction) |
| | 130 | |
| | 131 | rotated_solution.vx = DIV_FIXED(rotated_vel.vx, projectile_speed); |
| | 132 | rotated_solution.vy = DIV_FIXED(rotated_vel.vy, projectile_speed); |
| | 133 | |
| | 134 | //z=1-(x*x+y*y) |
| | 135 | { |
| | 136 | //not sure we have a fixed point square root |
| | 137 | float x = (float)rotated_solution.vx; |
| | 138 | float y = (float)rotated_solution.vy; |
| | 139 | float z_squared = 65536.0*65536.0-(x*x+y*y); |
| | 140 | |
| | 141 | if(z_squared < 0) |
| | 142 | { |
| | 143 | //target moving too fast to hit |
| | 144 | return 0; |
| | 145 | } |
| | 146 | |
| | 147 | rotated_solution.vz = (int)sqrt(z_squared); |
| | 148 | } |
| | 149 | |
| | 150 | //finally need to rotated solution back |
| | 151 | *solution = rotated_solution; |
| | 152 | TransposeMatrixCH(&mat); |
| | 153 | RotateVector(solution, &mat); |
| | 154 | |
| | 155 | //normalise solution to be on the safe side |
| | 156 | Normalise(solution); |
| | 157 | |
| | 158 | return 1; |
| | 159 | } |
| | 160 | |
| | 161 | void SmartTarget_GetCofM(DISPLAYBLOCK *target, VECTORCH *viewSpaceOutput) |
| | 162 | { |
| | 163 | AVP_BEHAVIOUR_TYPE targetType; |
| | 164 | |
| | 165 | /* Get smartgun aiming point. */ |
| | 166 | |
| | 167 | if (target->HModelControlBlock == NULL || target->HModelControlBlock->section_data == NULL) |
| | 168 | { |
| | 169 | *viewSpaceOutput = target->ObView; |
| | 170 | return; |
| | 171 | } |
| | 172 | |
| | 173 | /* Must be a hierarchy. */ |
| | 174 | |
| | 175 | switch(target->ObStrategyBlock->type) |
| | 176 | { |
| | 177 | case I_BehaviourNetGhost: |
| | 178 | { |
| | 179 | NETGHOSTDATABLOCK *dataptr = target->ObStrategyBlock->dataptr; |
| | 180 | targetType = dataptr->type; |
| | 181 | } |
| | 182 | break; |
| | 183 | default: |
| | 184 | targetType = target->ObStrategyBlock->type; |
| | 185 | } |
| | 186 | |
| | 187 | /* Now, switch case on targetType. */ |
| | 188 | |
| | 189 | switch (targetType) |
| | 190 | { |
| | 191 | case I_BehaviourMarine: |
| | 192 | case I_BehaviourMarinePlayer: |
| | 193 | case I_BehaviourPredator: |
| | 194 | case I_BehaviourPredatorPlayer: |
| | 195 | case I_BehaviourAlien: |
| | 196 | case I_BehaviourAlienPlayer: |
| | 197 | { |
| | 198 | SECTION_DATA *targetsection = GetThisSectionData(target->HModelControlBlock->section_data, "chest"); |
| | 199 | |
| | 200 | if (targetsection == NULL) |
| | 201 | targetsection = target->HModelControlBlock->section_data; |
| | 202 | |
| | 203 | if (targetsection->flags & section_data_view_init) |
| | 204 | *viewSpaceOutput = targetsection->View_Offset; |
| | 205 | else |
| | 206 | *viewSpaceOutput = target->ObView; /* Whoops. */ |
| | 207 | return; |
| | 208 | } |
| | 209 | default: |
| | 210 | { |
| | 211 | /* General case. */ |
| | 212 | if (target->HModelControlBlock->section_data->flags & section_data_view_init) |
| | 213 | *viewSpaceOutput = target->HModelControlBlock->section_data->View_Offset; |
| | 214 | else |
| | 215 | *viewSpaceOutput = target->ObView; /* Whoops. */ |
| | 216 | } |
| | 217 | } |
| | 218 | } |
| | 219 | |
| | 220 | static int SmartTarget_TargetFilter(STRATEGYBLOCK *candidate) |
| | 221 | { |
| | 222 | /* KJL 11:56:33 14/10/98 - the Predator's Shoulder cannon can only track objects when used in the correct |
| | 223 | vision mode, e.g. you can only target marines when in infrared mode */ |
| | 224 | |
| | 225 | switch(PlayerStatus.SelectedWeapon->WeaponIDNumber) |
| | 226 | { |
| | 227 | case WEAPON_PRED_SHOULDERCANNON: |
| | 228 | case WEAPON_PRED_DISC: |
| | 229 | { |
| | 230 | switch (PlayerStatus.VisionMode) |
| | 231 | { |
| | 232 | case VISION_MODE_PRED_SEEALIENS: |
| | 233 | { |
| | 234 | switch(candidate->type) |
| | 235 | { |
| | 236 | case I_BehaviourAlien: |
| | 237 | case I_BehaviourXenoborg: |
| | 238 | case I_BehaviourFaceHugger: |
| | 239 | case I_BehaviourQueenAlien: |
| | 240 | return !NPC_IsDead(candidate); |
| | 241 | case I_BehaviourNetGhost: |
| | 242 | { |
| | 243 | NETGHOSTDATABLOCK *ghostDataPtr = (NETGHOSTDATABLOCK *)candidate->dataptr; |
| | 244 | return (ghostDataPtr->type == I_BehaviourAlienPlayer || ghostDataPtr->type == I_BehaviourAlien); |
| | 245 | } |
| | 246 | default: |
| | 247 | return 0; |
| | 248 | } |
| | 249 | } |
| | 250 | case VISION_MODE_PRED_THERMAL: |
| | 251 | { |
| | 252 | switch(candidate->type) |
| | 253 | { |
| | 254 | case I_BehaviourMarine: |
| | 255 | { |
| | 256 | MARINE_STATUS_BLOCK *marineStatusPointer = (MARINE_STATUS_BLOCK *)(candidate->dataptr); |
| | 257 | return marineStatusPointer->My_Weapon->Android ? 0 : !NPC_IsDead(candidate); |
| | 258 | } |
| | 259 | case I_BehaviourNetGhost: |
| | 260 | { |
| | 261 | NETGHOSTDATABLOCK *ghostDataPtr = (NETGHOSTDATABLOCK *)candidate->dataptr; |
| | 262 | return (ghostDataPtr->type == I_BehaviourMarinePlayer || ghostDataPtr->type == I_BehaviourMarine); |
| | 263 | } |
| | 264 | default: |
| | 265 | return 0; |
| | 266 | } |
| | 267 | } |
| | 268 | case VISION_MODE_PRED_SEEPREDTECH: |
| | 269 | { |
| | 270 | switch(candidate->type) |
| | 271 | { |
| | 272 | case I_BehaviourPredator: |
| | 273 | return !NPC_IsDead(candidate); |
| | 274 | case I_BehaviourNetGhost: |
| | 275 | { |
| | 276 | NETGHOSTDATABLOCK *ghostDataPtr = (NETGHOSTDATABLOCK *)candidate->dataptr; |
| | 277 | |
| | 278 | switch(ghostDataPtr->type) |
| | 279 | { |
| | 280 | case I_BehaviourPredator: |
| | 281 | case I_BehaviourPredatorPlayer: |
| | 282 | { |
| | 283 | /* Check for game type? */ |
| | 284 | switch (netGameData.gameType) |
| | 285 | { |
| | 286 | case NGT_Coop: |
| | 287 | case NGT_CoopDeathmatch: |
| | 288 | case NGT_LastManStanding: |
| | 289 | return 0; |
| | 290 | //case NGT_Individual: |
| | 291 | //case NGT_PredatorTag: |
| | 292 | // return 1; |
| | 293 | default: |
| | 294 | return 1; |
| | 295 | } |
| | 296 | } |
| | 297 | default: |
| | 298 | return 0; |
| | 299 | } |
| | 300 | } |
| | 301 | case I_BehaviourFrisbee: |
| | 302 | case I_BehaviourGrenade: |
| | 303 | case I_BehaviourFragmentationGrenade: |
| | 304 | return 1; |
| | 305 | case I_BehaviourProximityGrenade: |
| | 306 | return DynamicObjectIsMoving(candidate->DynPtr); |
| | 307 | default: |
| | 308 | return 0; |
| | 309 | } |
| | 310 | } |
| | 311 | default: |
| | 312 | return 0; |
| | 313 | } |
| | 314 | } |
| | 315 | case WEAPON_SMARTGUN: |
| | 316 | { |
| | 317 | switch (candidate->type) |
| | 318 | { |
| | 319 | //case I_BehaviourMarine: |
| | 320 | case I_BehaviourMarinePlayer: |
| | 321 | return 0; |
| | 322 | default: |
| | 323 | return ObjectShouldAppearOnMotionTracker(candidate); |
| | 324 | /* |
| | 325 | case I_BehaviourAlien: |
| | 326 | case I_BehaviourPredator: |
| | 327 | case I_BehaviourQueenAlien: |
| | 328 | case I_BehaviourFaceHugger: |
| | 329 | case I_BehaviourXenoborg: |
| | 330 | return !NPC_IsDead(candidate); |
| | 331 | case I_BehaviourNetGhost: |
| | 332 | { |
| | 333 | NETGHOSTDATABLOCK *ghostDataPtr = (NETGHOSTDATABLOCK *)candidate->dataptr; |
| | 334 | |
| | 335 | // Don't target marines if Friendly Fire is disabled. |
| | 336 | //if (netGameData.disableFriendlyFire && (netGameData.gameType == NGT_CoopDeathmatch || netGameData.gameType == NGT_Coop)) |
| | 337 | |
| | 338 | switch (ghostDataPtr->type) |
| | 339 | { |
| | 340 | case I_BehaviourPredatorPlayer: |
| | 341 | case I_BehaviourAlienPlayer: |
| | 342 | case I_BehaviourAlien: |
| | 343 | return 1; |
| | 344 | default: |
| | 345 | return 0; |
| | 346 | } |
| | 347 | } |
| | 348 | default: |
| | 349 | return 0; |
| | 350 | */ |
| | 351 | } |
| | 352 | } |
| | 353 | break; |
| | 354 | default: |
| | 355 | return 0; |
| | 356 | } |
| | 357 | } |
| | 358 | |
| | 359 | #define SMART_TRACKABLE_TARGETS 100 |
| | 360 | #define SMART_TARGETING_RANGE 1000000 |
| | 361 | |
| | 362 | extern struct KObject VisibleObjects[maxobjects]; |
| | 363 | extern int numVisObjs; |
| | 364 | |
| | 365 | DISPLAYBLOCK *SmartTarget_GetNewTarget() |
| | 366 | { |
| | 367 | int a=0,b,c; |
| | 368 | int numberOfObjects = numVisObjs; |
| | 369 | |
| | 370 | DISPLAYBLOCK *track_array[SMART_TRACKABLE_TARGETS] = { NULL }; |
| | 371 | |
| | 372 | while (numberOfObjects--) |
| | 373 | { |
| | 374 | DISPLAYBLOCK* objectPtr = VisibleObjects[numberOfObjects].DispPtr; |
| | 375 | STRATEGYBLOCK* sbPtr = objectPtr->ObStrategyBlock; |
| | 376 | |
| | 377 | if (sbPtr && sbPtr->DynPtr) |
| | 378 | { |
| | 379 | VECTORCH viewPos; |
| | 380 | /* Arc reject. */ |
| | 381 | SmartTarget_GetCofM(objectPtr, &viewPos); |
| | 382 | |
| | 383 | if (viewPos.vz > 0) |
| | 384 | { |
| | 385 | if (SmartTarget_TargetFilter(sbPtr)) |
| | 386 | { |
| | 387 | if (a < SMART_TRACKABLE_TARGETS) |
| | 388 | { |
| | 389 | track_array[a] = objectPtr; |
| | 390 | a++; |
| | 391 | } |
| | 392 | } |
| | 393 | } |
| | 394 | } |
| | 395 | } |
| | 396 | |
| | 397 | /* Now, filter the track array. */ |
| | 398 | b = a; |
| | 399 | c = a; |
| | 400 | |
| | 401 | while (b--) |
| | 402 | { |
| | 403 | int notFar = -1; |
| | 404 | |
| | 405 | int nearestObjectDist = SMART_TARGETING_RANGE; |
| | 406 | DISPLAYBLOCK *nearestObjectPtr = NULL; |
| | 407 | |
| | 408 | a = c; |
| | 409 | |
| | 410 | while (a) |
| | 411 | { |
| | 412 | DISPLAYBLOCK* objectPtr = track_array[--a]; |
| | 413 | |
| | 414 | if (objectPtr) |
| | 415 | { |
| | 416 | /* calc distance to player - no parallel strat support yet */ |
| | 417 | /* 2d vector from player to object */ |
| | 418 | int dist = FandVD_Distance_3d(&objectPtr->ObWorld, &PlayerStatus.DisplayBlock->ObWorld); |
| | 419 | |
| | 420 | if (dist < nearestObjectDist) |
| | 421 | { |
| | 422 | nearestObjectDist = dist; |
| | 423 | nearestObjectPtr = objectPtr; |
| | 424 | notFar = a; |
| | 425 | } |
| | 426 | } |
| | 427 | } |
| | 428 | |
| | 429 | if (nearestObjectPtr) |
| | 430 | { |
| | 431 | assert(notFar != -1); |
| | 432 | |
| | 433 | if (IsThisObjectVisibleFromThisPosition_WithIgnore(track_array[notFar], PlayerStatus.DisplayBlock, &(Global_VDB.VDB_World))) |
| | 434 | return track_array[notFar]; /* Valid. */ |
| | 435 | else |
| | 436 | track_array[notFar] = NULL; /* Remove from list. */ |
| | 437 | } |
| | 438 | } |
| | 439 | |
| | 440 | return NULL; |
| | 441 | } |
| | 442 | |
| | 443 | int SmartTargetSightX, SmartTargetSightY; |
| | 444 | DISPLAYBLOCK *SmartTarget_Object; |
| | 445 | |
| | 446 | int SmartTarget(int speed, int projectile_speed) |
| | 447 | { |
| | 448 | DISPLAYBLOCK *trackedObject = SmartTarget_GetNewTarget(); |
| | 449 | int targetX = ScreenDescriptorBlock.SDB_Width >> 1; |
| | 450 | int targetY = ScreenDescriptorBlock.SDB_Height >> 1; |
| | 451 | SmartTarget_Object = NULL; |
| | 452 | |
| | 453 | /* If there is a valid near object which isn't so close as to cause a division by zero */ |
| | 454 | if (trackedObject && (trackedObject->ObView.vz != 0)) |
| | 455 | { |
| | 456 | VECTORCH targetView; |
| | 457 | |
| | 458 | /* Set targetView. */ |
| | 459 | SmartTarget_GetCofM(trackedObject, &targetView); |
| | 460 | |
| | 461 | if(projectile_speed) |
| | 462 | { |
| | 463 | //get a firing solution so that projectile should hit if target maintains curremt velocity |
| | 464 | if(trackedObject->ObStrategyBlock) |
| | 465 | { |
| | 466 | if(trackedObject->ObStrategyBlock->DynPtr) |
| | 467 | { |
| | 468 | DYNAMICSBLOCK *dynPtr = trackedObject->ObStrategyBlock->DynPtr; |
| | 469 | |
| | 470 | if(dynPtr->LinVelocity.vx || dynPtr->LinVelocity.vy || dynPtr->LinVelocity.vz) |
| | 471 | { |
| | 472 | VECTORCH velocity = dynPtr->LinVelocity; |
| | 473 | VECTORCH zero = {0,0,0}; |
| | 474 | VECTORCH solution; |
| | 475 | //rotate velocity into view space |
| | 476 | RotateVector(&velocity, &Global_VDB.VDB_Mat); |
| | 477 | |
| | 478 | if(CalculateFiringSolution(&zero, &targetView, &velocity, projectile_speed, &solution)) |
| | 479 | targetView = solution; |
| | 480 | } |
| | 481 | } |
| | 482 | } |
| | 483 | } |
| | 484 | |
| | 485 | if (targetView.vz > 0) |
| | 486 | { |
| | 487 | int screenX = WideMulNarrowDiv(targetView.vx, Global_VDB.VDB_ProjX, targetView.vz); |
| | 488 | int screenY = WideMulNarrowDiv(targetView.vy*4, Global_VDB.VDB_ProjY, targetView.vz*3); |
| | 489 | int maxRangeX = (ScreenDescriptorBlock.SDB_Width * 14) / 32; /* gives nearly 90% of screen */ |
| | 490 | |
| | 491 | if ((screenX > -maxRangeX) && (screenX < maxRangeX)) |
| | 492 | { |
| | 493 | int maxRangeY = (ScreenDescriptorBlock.SDB_Height * 14) / 32; /* gives nearly 90% of screen */ |
| | 494 | |
| | 495 | if ((screenY > -maxRangeY) && (screenY < maxRangeY)) |
| | 496 | { |
| | 497 | targetX += screenX; |
| | 498 | targetY += screenY; |
| | 499 | SmartTarget_Object = trackedObject; |
| | 500 | //printf("Tracking object %p \n", trackedObject); |
| | 501 | } |
| | 502 | } |
| | 503 | } |
| | 504 | } |
| | 505 | |
| | 506 | if (speed) |
| | 507 | { |
| | 508 | int targetingSpeed = NormalFrameTime * speed; |
| | 509 | |
| | 510 | /* KJL 14:08:50 09/20/96 - the targeting is FRI, but care has to be taken |
| | 511 | at very low frame rates to ensure that the sight doesn't jump about |
| | 512 | all over the place. */ |
| | 513 | if (targetingSpeed > 65536) |
| | 514 | targetingSpeed = 65536; |
| | 515 | |
| | 516 | int dx = MUL_FIXED( ((targetX << 16) - SmartTargetSightX), targetingSpeed ); |
| | 517 | int dy = MUL_FIXED( ((targetY << 16) - SmartTargetSightY), targetingSpeed ); |
| | 518 | |
| | 519 | /* If the x-coord difference between the sight and the target is small, |
| | 520 | just move the sight so that it has the same x-coord as the target. |
| | 521 | This stops the sight from hovering a pixel away from where it should be */ |
| | 522 | if (dx > 16384 || dx < -16384) |
| | 523 | SmartTargetSightX += dx; |
| | 524 | else |
| | 525 | SmartTargetSightX = targetX << 16; |
| | 526 | |
| | 527 | /* Similarly for the y-coord */ |
| | 528 | if (dy > 16384 || dy < -16384) |
| | 529 | SmartTargetSightY += dy; |
| | 530 | else |
| | 531 | SmartTargetSightY = targetY << 16; |
| | 532 | } |
| | 533 | else |
| | 534 | { |
| | 535 | SmartTargetSightX = targetX << 16; |
| | 536 | SmartTargetSightY = targetY << 16; |
| | 537 | } |
| | 538 | |
| | 539 | return SmartTarget_Object != NULL; |
| | 540 | } |
| | 541 | |
| | 542 | void GetTargetingPointOfObject(DISPLAYBLOCK *objectPtr, VECTORCH *targetPtr) |
| | 543 | { |
| | 544 | /* try to look at the centre of the object */ |
| | 545 | if (objectPtr->HModelControlBlock) |
| | 546 | { |
| | 547 | ProveHModel(objectPtr->HModelControlBlock, objectPtr); |
| | 548 | SECTION_DATA *firstSectionPtr = objectPtr->HModelControlBlock->section_data; |
| | 549 | assert(firstSectionPtr); |
| | 550 | assert(firstSectionPtr->flags§ion_data_initialised); |
| | 551 | |
| | 552 | /* look for the object's torso in preference */ |
| | 553 | SECTION_DATA *targetSectionPtr = GetThisSectionData(objectPtr->HModelControlBlock->section_data, "chest"); |
| | 554 | |
| | 555 | if (targetSectionPtr) |
| | 556 | { |
| | 557 | assert(targetSectionPtr->flags & section_data_initialised); |
| | 558 | *targetPtr = targetSectionPtr->World_Offset; |
| | 559 | } |
| | 560 | else /* just use the top of the hierarchy then */ |
| | 561 | { |
| | 562 | *targetPtr = firstSectionPtr->World_Offset; |
| | 563 | } |
| | 564 | } |
| | 565 | else |
| | 566 | { |
| | 567 | *targetPtr = objectPtr->ObWorld; |
| | 568 | } |
| | 569 | } |
| | 570 | |
| | 571 | void GetTargetingPointOfObject_Far(STRATEGYBLOCK *sbPtr, VECTORCH *targetPtr) |
| | 572 | { |
| | 573 | /* Can we use the near one? This is a more general shell. */ |
| | 574 | if (sbPtr->DisplayBlock) |
| | 575 | { |
| | 576 | GetTargetingPointOfObject(sbPtr->DisplayBlock,targetPtr); |
| | 577 | } |
| | 578 | else if(sbPtr->DynPtr) |
| | 579 | { |
| | 580 | *targetPtr = sbPtr->DynPtr->Position; |
| | 581 | targetPtr->vy -= 500; /* Just to be on the safe side. */ |
| | 582 | } |
| | 583 | #if DEBUG |
| | 584 | else |
| | 585 | { |
| | 586 | /* Aw, gawd! I don't know! There's NO position for this! */ |
| | 587 | assert(0); |
| | 588 | } |
| | 589 | #endif |
| | 590 | } |