BULLET.CPP (52480B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 16 /* $Header: /CounterStrike/BULLET.CPP 1 3/03/97 10:24a Joe_bostic $ */ 17 /*********************************************************************************************** 18 *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** 19 *********************************************************************************************** 20 * * 21 * Project Name : Command & Conquer * 22 * * 23 * File Name : BULLET.CPP * 24 * * 25 * Programmer : Joe L. Bostic * 26 * * 27 * Start Date : April 23, 1994 * 28 * * 29 * Last Update : October 10, 1996 [JLB] * 30 * * 31 *---------------------------------------------------------------------------------------------* 32 * Functions: * 33 * BulletClass::AI -- Logic processing for bullet. * 34 * BulletClass::BulletClass -- Bullet constructor. * 35 * BulletClass::Bullet_Explodes -- Performs bullet explosion logic. * 36 * BulletClass::Detach -- Removes specified target from this bullet's targeting system. * 37 * BulletClass::Draw_It -- Displays the bullet at location specified. * 38 * BulletClass::In_Which_Layer -- Fetches the layer that the bullet resides in. * 39 * BulletClass::Init -- Clears the bullets array for scenario preparation. * 40 * BulletClass::Is_Forced_To_Explode -- Checks if bullet should explode NOW. * 41 * BulletClass::Mark -- Performs related map refreshing under bullet. * 42 * BulletClass::Occupy_List -- Determines the bullet occupation list. * 43 * BulletClass::Shape_Number -- Fetches the shape number for the bullet object. * 44 * BulletClass::Sort_Y -- Sort coordinate for bullet rendering. * 45 * BulletClass::Target_Coord -- Fetches coordinate to use when firing on this object. * 46 * BulletClass::Unlimbo -- Transitions a bullet object into the game render/logic system. * 47 * BulletClass::delete -- Bullet memory delete. * 48 * BulletClass::new -- Allocates memory for bullet object. * 49 * BulletClass::~BulletClass -- Destructor for bullet objects. * 50 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 51 52 #include "function.h" 53 54 55 /*********************************************************************************************** 56 * BulletClass::BulletClass -- Bullet constructor. * 57 * * 58 * This is the constructor for the bullet class. It handles all * 59 * initialization of the bullet and starting it in motion toward its * 60 * target. * 61 * * 62 * INPUT: id -- The type of bullet this is (could be missile). * 63 * * 64 * OUTPUT: none * 65 * * 66 * WARNINGS: none * 67 * * 68 * HISTORY: * 69 * 05/02/1994 JLB : Created. * 70 * 06/20/1994 JLB : Firer is a base class pointer. * 71 * 12/10/1994 JLB : Auto calculate range optional. * 72 * 12/12/1994 JLB : Handles small arms as an instantaneous effect. * 73 * 12/23/1994 JLB : Fixed scatter algorithm for non-homing projectiles. * 74 * 12/31/1994 JLB : Removed range parameter (not needed). * 75 *=============================================================================================*/ 76 BulletClass::BulletClass(BulletType id, TARGET target, TechnoClass * payback, int strength, WarheadType warhead, int speed) : 77 ObjectClass(RTTI_BULLET, Bullets.ID(this)), 78 Class(BulletTypes.Ptr((int)id)), 79 Payback(payback), 80 PrimaryFacing(DIR_N), 81 IsInaccurate(false), 82 IsToAnimate(false), 83 IsLocked(true), 84 TarCom(target), 85 MaxSpeed(speed), 86 Warhead(warhead) 87 { 88 Strength = strength; 89 Height = FLIGHT_LEVEL; 90 } 91 92 93 /*********************************************************************************************** 94 * BulletClass::~BulletClass -- Destructor for bullet objects. * 95 * * 96 * The bullet destructor must detect if a dog has been attached to this bullet. If so, * 97 * then the attached dog must be unlimboed back onto the map. This operation is necessary * 98 * because, unlike other objects, the dog flies with the bullet it fires. * 99 * * 100 * INPUT: none * 101 * * 102 * OUTPUT: none * 103 * * 104 * WARNINGS: none * 105 * * 106 * HISTORY: * 107 * 07/06/1996 JLB : Created. * 108 *=============================================================================================*/ 109 BulletClass::~BulletClass(void) 110 { 111 if (GameActive) { 112 113 /* 114 ** SPECIAL CASE: 115 ** The dog is attached to the dog bullet in a limbo state. When the bullet is 116 ** destroyed, the dog must come back out of limbo at the closest location possible to 117 ** the bullet. 118 */ 119 if (Payback != NULL && Payback->What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)Payback)->Class->IsDog) { 120 121 InfantryClass * dog = (InfantryClass *)Payback; 122 if (dog) { 123 bool unlimbo = false; 124 DirType dogface = dog->PrimaryFacing; 125 COORDINATE newcoord = Coord; 126 127 /* 128 ** Ensure that the coordinate, that the dog is to appear at, is legal. If not, 129 ** then find a nearby legal location. 130 */ 131 if (Can_Enter_Cell(newcoord) != MOVE_OK) { 132 newcoord = Map.Nearby_Location(Coord_Cell(newcoord), dog->Class->Speed); 133 } 134 135 /* 136 ** Try to put the dog down where the target impacted. If we can't 137 ** put it in that cell, then scan through the adjacent cells, 138 ** starting with our current heading, until we find a place where 139 ** we can put him down. If all 8 adjacent cell checks fail, then 140 ** just delete the dog. 141 */ 142 for (int i = -1; i < 8; i++) { 143 if (i != -1) { 144 newcoord = Adjacent_Cell(Coord, FacingType(i)); 145 } 146 ScenarioInit++; 147 if (dog->Unlimbo(newcoord, dog->PrimaryFacing)) { 148 dog->Mark(MARK_DOWN); 149 dog->Do_Action(DO_DOG_MAUL, true); 150 if (dog->WasSelected) { 151 dog->Select(); 152 } 153 ScenarioInit--; 154 155 unlimbo = true; 156 break; 157 } 158 ScenarioInit--; 159 } 160 161 Payback = 0; 162 163 if (!unlimbo) { 164 delete dog; 165 } 166 } 167 } 168 BulletClass::Limbo(); 169 } 170 171 Class=0; 172 Payback=0; 173 } 174 175 176 /*********************************************************************************************** 177 * BulletClass::new -- Allocates memory for bullet object. * 178 * * 179 * This function will "allocate" a block of memory for a bullet object. * 180 * This memory block is merely lifted from a fixed pool of blocks. * 181 * * 182 * INPUT: size -- The size of the memory block needed. * 183 * * 184 * OUTPUT: Returns with a pointer to an available bullet object block. * 185 * * 186 * WARNINGS: none * 187 * * 188 * HISTORY: * 189 * 05/02/1994 JLB : Created. * 190 *=============================================================================================*/ 191 void * BulletClass::operator new(size_t ) 192 { 193 void * ptr = Bullets.Allocate(); 194 if (ptr) { 195 ((BulletClass *)ptr)->Set_Active(); 196 } 197 return(ptr); 198 } 199 200 201 /*********************************************************************************************** 202 * BulletClass::delete -- Bullet memory delete. * 203 * * 204 * Since bullets memory is merely "allocated" out of a pool, it never * 205 * actually gets deleted. * 206 * * 207 * INPUT: ptr -- Generic pointer to bullet object. * 208 * * 209 * OUTPUT: none * 210 * * 211 * WARNINGS: none * 212 * * 213 * HISTORY: * 214 * 05/02/1994 JLB : Created. * 215 *=============================================================================================*/ 216 void BulletClass::operator delete(void * ptr) 217 { 218 if (ptr) { 219 ((BulletClass *)ptr)->IsActive = false; 220 } 221 Bullets.Free((BulletClass *)ptr); 222 } 223 224 225 /*********************************************************************************************** 226 * BulletClass::Occupy_List -- Determines the bullet occupation list. * 227 * * 228 * This function will determine the cell occupation list and return a pointer to it. Most * 229 * bullets are small and the list is usually short, but on occasion, it can be a list that * 230 * rivals the size of regular vehicles. * 231 * * 232 * INPUT: none * 233 * * 234 * OUTPUT: Returns with a pointer to the cell offset list that covers all the cells a bullet * 235 * is over. * 236 * * 237 * WARNINGS: none * 238 * * 239 * HISTORY: * 240 * 06/20/1994 JLB : Created. * 241 * 01/05/1995 JLB : Handles projectiles with altitude. * 242 *=============================================================================================*/ 243 short const * BulletClass::Occupy_List(bool) const 244 { 245 assert(Bullets.ID(this) == ID); 246 assert(IsActive); 247 248 /* 249 ** Super-gigundo units use the >= 64 coord spillage list logic. 250 */ 251 if (Class->IsGigundo) { 252 static short _list[] = { 253 -1, 0, 1, 254 MAP_CELL_W*1-1, MAP_CELL_W*1, MAP_CELL_W*1+1, 255 -MAP_CELL_W*1-1, -MAP_CELL_W*1, -MAP_CELL_W*1+1, 256 MAP_CELL_W*2-1, MAP_CELL_W*2, MAP_CELL_W*2+1, 257 -MAP_CELL_W*2-1, -MAP_CELL_W*2, -MAP_CELL_W*2+1, 258 -MAP_CELL_W*3-1, -MAP_CELL_W*3, -MAP_CELL_W*3+1, 259 REFRESH_EOL 260 }; 261 return(_list); 262 // return(Coord_Spillage_List(Coord, 64)); 263 } 264 265 /* 266 ** Flying units need a special adjustment to the spillage list to take into account 267 ** that the bullet imagery and the shadow are widely separated. 268 */ 269 if (Height > 0) { 270 static short _list[25]; 271 const short * ptr = Coord_Spillage_List(Coord, 5); 272 int index = 0; 273 CELL cell1 = Coord_Cell(Coord); 274 275 while (ptr[index] != REFRESH_EOL) { 276 _list[index] = ptr[index]; 277 index++; 278 } 279 280 COORDINATE coord = Coord_Move(Coord, DIR_N, Height); 281 CELL cell2 = Coord_Cell(coord); 282 ptr = Coord_Spillage_List(coord, 5); 283 while (*ptr != REFRESH_EOL) { 284 _list[index++] = *ptr + (cell2 - cell1); 285 ptr++; 286 } 287 _list[index] = REFRESH_EOL; 288 return(_list); 289 } 290 291 return(Coord_Spillage_List(Coord, 10)); 292 } 293 294 295 /*********************************************************************************************** 296 * BulletClass::Mark -- Performs related map refreshing under bullet. * 297 * * 298 * This routine marks the objects under the bullet so that they will * 299 * be redrawn. This is necessary as the bullet moves -- objects under * 300 * its path need to be restored. * 301 * * 302 * INPUT: none * 303 * * 304 * OUTPUT: none * 305 * * 306 * WARNINGS: none * 307 * * 308 * HISTORY: * 309 * 05/02/1994 JLB : Created. * 310 *=============================================================================================*/ 311 bool BulletClass::Mark(MarkType mark) 312 { 313 assert(Bullets.ID(this) == ID); 314 assert(IsActive); 315 316 if (ObjectClass::Mark(mark)) { 317 if (!Class->IsInvisible) { 318 Map.Refresh_Cells(Coord_Cell(Coord), Occupy_List()); 319 } 320 return(true); 321 } 322 return(false); 323 } 324 325 326 /*********************************************************************************************** 327 * BulletClass::AI -- Logic processing for bullet. * 328 * * 329 * This routine will perform all logic (flight) logic on the bullet. * 330 * Primarily this is motion, fuse tracking, and detonation logic. Call * 331 * this routine no more than once per bullet per game tick. * 332 * * 333 * INPUT: none * 334 * * 335 * OUTPUT: none * 336 * * 337 * WARNINGS: none * 338 * * 339 * HISTORY: * 340 * 05/02/1994 JLB : Created. * 341 *=============================================================================================*/ 342 void BulletClass::AI(void) 343 { 344 assert(Bullets.ID(this) == ID); 345 assert(IsActive); 346 347 COORDINATE coord; 348 349 ObjectClass::AI(); 350 351 if (!IsActive) return; 352 353 /* 354 ** Ballistic objects are handled here. 355 */ 356 bool forced = false; // Forced explosion. 357 if ((Class->IsArcing || Class->IsDropping) && !IsFalling) { 358 forced = true; 359 } 360 361 /* 362 ** Homing projectiles constantly change facing to face toward the target but 363 ** they only do so every other game frame (improves game speed and makes 364 ** missiles not so deadly). 365 */ 366 if ((Frame & 0x01) && Class->ROT != 0 && Target_Legal(TarCom)) { 367 PrimaryFacing.Set_Desired(Direction256(Coord, ::As_Coord(TarCom))); 368 } 369 370 /* 371 ** Move the projectile forward according to its speed 372 ** and direction. 373 */ 374 coord = Coord; 375 if (Class->IsFlameEquipped) { 376 if (IsToAnimate) { 377 if (stricmp(Class->GraphicName, "FB1") == 0) { 378 new AnimClass(ANIM_FBALL_FADE, coord, 1); 379 } else { 380 new AnimClass(ANIM_SMOKE_PUFF, coord, 1); 381 } 382 } 383 IsToAnimate = !IsToAnimate; 384 } 385 386 /* 387 ** Handle any body rotation at this time. This process must 388 ** occur every game fame in order to achieve smooth rotation. 389 */ 390 if (PrimaryFacing.Is_Rotating()) { 391 PrimaryFacing.Rotation_Adjust(Class->ROT); 392 } 393 switch (Physics(coord, PrimaryFacing)) { 394 /* 395 ** When a projectile reaches the edge of the world, it 396 ** vanishes from existence -- presumed to explode off 397 ** map. 398 */ 399 case IMPACT_EDGE: 400 Mark(); 401 if (Payback != NULL && Class->Type == BULLET_GPS_SATELLITE) { 402 403 bool reveal = false; 404 if (Session.Type != GAME_GLYPHX_MULTIPLAYER) { 405 if (Payback->House == PlayerPtr) { 406 reveal = true; 407 } 408 } else { 409 if (Payback->House->IsHuman) { 410 reveal = true; 411 } 412 } 413 if (reveal) { 414 if (!Map.Is_Radar_Active()) { 415 Map.Radar_Activate(1); 416 } 417 for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { 418 Map.Map_Cell(cell, Payback->House); 419 } 420 Map.RadarClass::Flag_To_Redraw(true); 421 } 422 Payback->House->IsGPSActive = true; 423 Payback->House->IsVisionary = true; 424 } 425 #ifdef OBSOLETE 426 /* 427 ** Hack: If it's the artificial nukes, don't let the bullets come down (as 428 ** they're the only ones that blow up). We know it's artificial if you're 429 ** at tech level 10 or below, because you can't build the nuclear silo until 430 ** tech level 15 or so. 431 */ 432 if (Payback != NULL && Class->Type == BULLET_NUKE_UP && Payback->House->Control.TechLevel <= 10) { 433 BulletClass * bullet = new BulletClass(BULLET_NUKE_DOWN, ::As_Target(Payback->House->NukeDest), Payback, 200, WARHEAD_NUKE, MPH_VERY_FAST); 434 if (bullet) { 435 int celly = Cell_Y(Payback->House->NukeDest); 436 celly -= 15; 437 if (celly < 1) celly = 1; 438 COORDINATE start = Cell_Coord(XY_Cell(Cell_X(Payback->House->NukeDest), celly)); 439 if (!bullet->Unlimbo(start, DIR_S)) { 440 delete bullet; 441 } 442 } 443 } 444 #endif 445 delete this; 446 break; 447 448 default: 449 case IMPACT_NONE: 450 451 /* 452 ** The projectile has moved. Check its fuse. If detonation 453 ** is signaled, then do so. Otherwise, just move. 454 */ 455 case IMPACT_NORMAL: 456 Mark(); 457 // if(Class->Type == BULLET_NUKE_DOWN) { 458 // Render(true); 459 // } 460 if (Class->Type == BULLET_NUKE_UP) { 461 if (Payback != NULL) { 462 if (Distance(Payback->As_Target()) > 0x0C00) { 463 delete this; 464 return; 465 } 466 } 467 } 468 Coord = coord; 469 470 /* 471 ** See if the bullet should be forced to explode now in spite of what 472 ** the fuse would otherwise indicate. Maybe the bullet hit a wall? 473 */ 474 if (!forced) { 475 forced = Is_Forced_To_Explode(Coord); 476 } 477 478 /* 479 ** If the bullet is not to explode, then perform normal flight 480 ** maintenance (usually nothing). Otherwise, explode and then 481 ** delete the bullet. 482 */ 483 if (!forced && (Class->IsDropping || !Fuse_Checkup(Coord))) { 484 /* 485 ** Certain projectiles lose strength when they travel. 486 */ 487 if (Class->IsDegenerate && Strength > 5) { 488 Strength--; 489 } 490 491 } else { 492 Bullet_Explodes(forced); 493 delete this; 494 } 495 break; 496 } 497 498 } 499 500 501 /*********************************************************************************************** 502 * BulletClass::Shape_Number -- Fetches the shape number for the bullet object. * 503 * * 504 * Use this routine to fetch a shape number to use for this bullet object. * 505 * * 506 * INPUT: none * 507 * * 508 * OUTPUT: Returns with the shape number to use when drawing this bullet. * 509 * * 510 * WARNINGS: none * 511 * * 512 * HISTORY: * 513 * 08/06/1996 JLB : Created. * 514 *=============================================================================================*/ 515 int BulletClass::Shape_Number(void) const 516 { 517 int shapenum = 0; 518 519 if (!Class->IsFaceless) { 520 shapenum = UnitClass::BodyShape[Dir_To_32(PrimaryFacing)]; 521 } 522 523 /* 524 ** For tumbling projectiles, fetch offset stage. 525 */ 526 if (Class->Tumble > 0) { 527 shapenum += (long)Frame % Class->Tumble; 528 } 529 530 return(shapenum); 531 } 532 533 534 /*********************************************************************************************** 535 * BulletClass::Draw_It -- Displays the bullet at location specified. * 536 * * 537 * This routine displays the bullet visual at the location specified. * 538 * * 539 * INPUT: x,y -- The center coordinate to render the bullet at. * 540 * * 541 * window -- The window to clip to. * 542 * * 543 * OUTPUT: none * 544 * * 545 * WARNINGS: none * 546 * * 547 * HISTORY: * 548 * 06/20/1994 JLB : Created. * 549 * 06/27/1994 JLB : Takes a window clipping parameter. * 550 * 01/08/1995 JLB : Handles translucent colors if necessary. * 551 *=============================================================================================*/ 552 void BulletClass::Draw_It(int x, int y, WindowNumberType window) const 553 { 554 assert(Bullets.ID(this) == ID); 555 assert(IsActive); 556 557 /* 558 ** Certain projectiles aren't visible. This includes small bullets (which are actually 559 ** invisible) and flame thrower flames (which are rendered as an animation instead of a projectile). 560 */ 561 if (Class->IsInvisible) return; 562 563 /* 564 ** If there is no shape loaded for this object, then 565 ** it obviously can't be rendered -- just bail. 566 */ 567 void const * shapeptr = Get_Image_Data(); 568 if (shapeptr == NULL) return; 569 570 /* 571 ** Get the basic shape number for this projectile. 572 */ 573 int shapenum = Shape_Number(); 574 575 /* 576 ** For flying projectiles, draw the shadow and adjust the actual projectile body 577 ** render position. 578 */ 579 if (Height > 0 && Class->IsShadow) { 580 581 if (Class->IsParachuted) { 582 // Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019 583 CC_Draw_Shape(this, AnimTypeClass::As_Reference(ANIM_PARA_BOMB).Get_Image_Data(), 1, x+Lepton_To_Pixel(Height/2), y+10, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, NULL, DisplayClass::UnitShadow); 584 } else { 585 // Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019 586 CC_Draw_Shape(this, shapeptr, shapenum, x, y, window, SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, NULL, DisplayClass::UnitShadow); 587 } 588 y -= Lepton_To_Pixel(Height); 589 } 590 591 /* 592 ** Draw the main body of the projectile. 593 */ 594 ShapeFlags_Type flags = SHAPE_NORMAL; 595 if (Class->IsTranslucent) { 596 flags = SHAPE_GHOST; 597 } 598 if (Class->IsSubSurface) 599 { 600 // Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019 601 CC_Draw_Shape(this, shapeptr, shapenum, x, y, window, flags|SHAPE_PREDATOR|SHAPE_CENTER|SHAPE_WIN_REL|SHAPE_FADING, NULL, DisplayClass::FadingShade); 602 } else { 603 // Add 'this' parameter to call new shape draw intercept. ST - 5/22/2019 604 CC_Draw_Shape(this, shapeptr, shapenum, x, y, window, flags|SHAPE_CENTER|SHAPE_WIN_REL, NULL, DisplayClass::UnitShadow); 605 } 606 } 607 608 609 /*********************************************************************************************** 610 * BulletClass::Init -- Clears the bullets array for scenario preparation. * 611 * * 612 * This routine will zero out the bullet tracking list and object array in preparation for * 613 * the start of a new scenario. All bullets cease to exists after this function is * 614 * called. * 615 * * 616 * INPUT: none * 617 * * 618 * OUTPUT: none * 619 * * 620 * WARNINGS: none * 621 * * 622 * HISTORY: * 623 * 08/15/1994 JLB : Created. * 624 *=============================================================================================*/ 625 void BulletClass::Init(void) 626 { 627 Bullets.Free_All(); 628 } 629 630 631 /*********************************************************************************************** 632 * BulletClass::Detach -- Removes specified target from this bullet's targeting system. * 633 * * 634 * When an object is removed from the game system, it must be removed all targeting and * 635 * tracking systems as well. This routine is used to remove the specified object from the * 636 * bullet. If the object isn't part of this bullet's tracking system, then no action is * 637 * performed. * 638 * * 639 * INPUT: target -- The target to remove from this tracking system. * 640 * * 641 * all -- Is the target going away for good as opposed to just cloaking/hiding? * 642 * * 643 * OUTPUT: none * 644 * * 645 * WARNINGS: none * 646 * * 647 * HISTORY: * 648 * 09/24/1994 JLB : Created. * 649 *=============================================================================================*/ 650 void BulletClass::Detach(TARGET target, bool all) 651 { 652 assert(Bullets.ID(this) == ID); 653 assert(IsActive); 654 655 ObjectClass * obj = As_Object(target); 656 if (Payback != NULL && obj == Payback) { 657 658 /* 659 ** If we're being called as a result of the dog that fired us being put 660 ** in limbo, then don't detach. If for any other reason, detach. 661 */ 662 if (Payback->What_Am_I() != RTTI_INFANTRY || !((InfantryClass *)Payback)->Class->IsDog) { 663 Payback = 0; 664 } 665 } 666 667 if (all && target == TarCom) { 668 TarCom = TARGET_NONE; 669 } 670 } 671 672 673 /*********************************************************************************************** 674 * BulletClass::Unlimbo -- Transitions a bullet object into the game render/logic system. * 675 * * 676 * This routine is used to take a bullet object that is in limbo and transition it to the * 677 * game system. A bullet object so transitioned, will be drawn and logic processing * 678 * performed. In effect, it comes into existence. * 679 * * 680 * INPUT: coord -- The location where the bullet object is to appear. * 681 * * 682 * dir -- The initial facing for the bullet object. * 683 * * 684 * OUTPUT: bool; Was the unlimbo successful? * 685 * * 686 * WARNINGS: none * 687 * * 688 * HISTORY: * 689 * 01/10/1995 JLB : Created. * 690 *=============================================================================================*/ 691 bool BulletClass::Unlimbo(COORDINATE coord, DirType dir) 692 { 693 assert(Bullets.ID(this) == ID); 694 assert(IsActive); 695 696 /* 697 ** Try to unlimbo the bullet as far as the base class is concerned. Use the already 698 ** set direction and strength if the "punt" values were passed in. This allows a bullet 699 ** to be setup prior to being launched. 700 */ 701 if (!Class->IsHigh) { 702 Height = 0; 703 } 704 if (ObjectClass::Unlimbo(coord)) { 705 Map.Remove(this, In_Which_Layer()); 706 707 COORDINATE tcoord = As_Coord(TarCom); 708 709 /* 710 ** Homing projectiles (missiles) do NOT override facing. They just fire in the 711 ** direction specified and let the chips fall where they may. 712 */ 713 if (Class->ROT == 0 && !Class->IsDropping) { 714 dir = Direction(tcoord); 715 } 716 717 /* 718 ** Possibly adjust the target if this projectile is inaccurate. This occurs whenever 719 ** certain weapons are trained upon targets they were never designed to attack. Example: when 720 ** turrets or anti-tank missiles are fired at infantry. Indirect 721 ** fire is inherently inaccurate. 722 */ 723 if (IsInaccurate || Class->IsInaccurate || 724 ((Is_Target_Cell(TarCom) || Is_Target_Infantry(TarCom)) && (Warhead == WARHEAD_AP || Class->IsFueled))) { 725 726 /* 727 ** Inaccuracy for low velocity or homing projectiles manifests itself as a standard 728 ** Circular Error of Probability (CEP) algorithm. High speed projectiles usually 729 ** just overshoot the target by extending the straight line flight. 730 */ 731 if (/*Class->ROT != 0 ||*/ Class->IsArcing) { 732 int scatterdist = (::Distance(coord, tcoord)/16)-0x0040; 733 scatterdist = min(scatterdist, Rule.HomingScatter); 734 scatterdist = max(scatterdist, 0); 735 736 dir = (DirType)((dir + (Random_Pick(0, 10)-5)) & 0x00FF); 737 tcoord = Coord_Scatter(tcoord, Random_Pick(0, scatterdist)); 738 } else { 739 int scatterdist = (::Distance(coord, tcoord)/16)-0x0040; 740 scatterdist = min(scatterdist, Rule.BallisticScatter); 741 scatterdist = max(scatterdist, 0); 742 tcoord = Coord_Move(tcoord, dir, Random_Pick(0, scatterdist)); 743 } 744 } 745 746 /* 747 ** For very fast and invisible projectiles, just make the projectile exist at the target 748 ** location and dispense with the actual flight. 749 */ 750 if (MaxSpeed == MPH_LIGHT_SPEED && Class->IsInvisible) { 751 Coord = tcoord; 752 } 753 754 /* 755 ** Set the range equal to either the class defined range or the calculated 756 ** number of game frames it would take for the projectile to reach the target. 757 */ 758 int range = 0xFF; 759 if (!Class->IsDropping) { 760 range = (::Distance(tcoord, Coord) / MaxSpeed) + 4; 761 } 762 763 /* 764 ** Projectile speed is usually the default value for that projectile, but 765 ** certain projectiles alter speed according to the distance to the 766 ** target. 767 */ 768 int speed = MaxSpeed; 769 if (speed == MPH_LIGHT_SPEED) speed = MPH_IMMOBILE; 770 if (Class->IsArcing) { 771 speed = MaxSpeed + (Distance(tcoord) / 32); 772 773 /* 774 ** Set minimum speed (i.e., distance) for arcing projectiles. 775 */ 776 speed = max(speed, 25); 777 } 778 if (!Class->IsDropping) { 779 Fly_Speed(255, (MPHType)speed); 780 } 781 782 /* 783 ** Arm the fuse. 784 */ 785 Arm_Fuse(Coord, tcoord, range, ((As_Aircraft(TarCom)!=0) ? 0 : Class->Arming)); 786 787 /* 788 ** Projectiles that make a ballistic flight to impact point must determine a 789 ** vertical component for the projectile launch. This is crudely simulated 790 ** by biasing ground speed according to target distance and then giving 791 ** enough vertical velocity to keep the projectile airborne for the 792 ** desired amount of time. The mathematically correct solution would be to 793 ** calculate launch angle (given fixed projectile velocity) and then derive 794 ** the vertical and horizontal components. That solution would require a 795 ** square root and an arcsine lookup table. OUCH! 796 */ 797 Riser = 0; 798 if (Class->IsArcing) { 799 IsFalling = true; 800 Height = 1; 801 Riser = ((Distance(tcoord)/2) / (speed+1)) * Rule.Gravity; 802 Riser = max(Riser, 10); 803 } 804 if (Class->IsDropping) { 805 IsFalling = true; 806 Height = FLIGHT_LEVEL; 807 // Height = Pixel_To_Lepton(24); 808 Riser = 0; 809 if (Class->IsParachuted) { 810 AnimClass * anim = new AnimClass(ANIM_PARA_BOMB, Target_Coord()); 811 // AnimClass * anim = new AnimClass(ANIM_PARACHUTE, Target_Coord()); 812 if (anim) { 813 anim->Attach_To(this); 814 } 815 } 816 } 817 Map.Submit(this, In_Which_Layer()); 818 819 PrimaryFacing = dir; 820 return(true); 821 } 822 return(false); 823 } 824 825 826 /*********************************************************************************************** 827 * BulletClass::Target_Coord -- Fetches coordinate to use when firing on this object. * 828 * * 829 * * 830 * INPUT: none * 831 * * 832 * OUTPUT: Returns with the coordinate that should be used when firing at the object. * 833 * * 834 * WARNINGS: none * 835 * * 836 * HISTORY: * 837 * 09/21/1995 JLB : Created. * 838 *=============================================================================================*/ 839 COORDINATE BulletClass::Target_Coord(void) const 840 { 841 assert(Bullets.ID(this) == ID); 842 assert(IsActive); 843 844 return(Coord_Add(XY_Coord(0, -Height), Coord)); 845 } 846 847 848 /*********************************************************************************************** 849 * BulletClass::Sort_Y -- Sort coordinate for bullet rendering. * 850 * * 851 * This will return the coordinate to use when sorting this bullet in the display list. * 852 * Typically, this only occurs for bullets in the ground layer. Since bullets are to be * 853 * seen a bit more than the normal sorting order would otherwise imply, bias the sort * 854 * value such that bullets will tend to be drawn on top of the objects. * 855 * * 856 * INPUT: none * 857 * * 858 * OUTPUT: Returns with the coordinate to use when sorting this bullet in the display list. * 859 * * 860 * WARNINGS: none * 861 * * 862 * HISTORY: * 863 * 10/02/1996 JLB : Created. * 864 *=============================================================================================*/ 865 COORDINATE BulletClass::Sort_Y(void) const 866 { 867 assert(this != 0); 868 assert(IsActive); 869 870 return(Coord_Move(Coord, DIR_S, CELL_LEPTON_H/2)); 871 } 872 873 874 /*********************************************************************************************** 875 * BulletClass::In_Which_Layer -- Fetches the layer that the bullet resides in. * 876 * * 877 * This examines the bullet to determine what rendering layer it should be in. The * 878 * normal logic applies unless this is a torpedo. A torpedo is always in the surface * 879 * layer. * 880 * * 881 * INPUT: none * 882 * * 883 * OUTPUT: Returns with the render layer that this bullet should reside in. * 884 * * 885 * WARNINGS: none * 886 * * 887 * HISTORY: * 888 * 10/10/1996 JLB : Created. * 889 *=============================================================================================*/ 890 LayerType BulletClass::In_Which_Layer(void) const 891 { 892 if (Class->IsSubSurface) { 893 return(LAYER_SURFACE); 894 } 895 return(ObjectClass::In_Which_Layer()); 896 } 897 898 899 /*********************************************************************************************** 900 * BulletClass::Is_Forced_To_Explode -- Checks if bullet should explode NOW. * 901 * * 902 * This routine will examine the bullet and where it is travelling in order to determine * 903 * if it should prematurely explode. Typical of this would be when a bullet hits a wall * 904 * or a torpedo hits a ship -- regardless of where the projectile was originally aimed. * 905 * * 906 * INPUT: coord -- The new coordinate to place the bullet at presuming it is forced to * 907 * explode and a modification of the bullet's coordinate is needed. * 908 * Otherwise, the coordinate is not modified. * 909 * * 910 * OUTPUT: bool; Should the bullet explode now? * 911 * * 912 * WARNINGS: none * 913 * * 914 * HISTORY: * 915 * 10/10/1996 JLB : Created. * 916 *=============================================================================================*/ 917 bool BulletClass::Is_Forced_To_Explode(COORDINATE & coord) const 918 { 919 coord = Coord; 920 CellClass const * cellptr = &Map[coord]; 921 922 /* 923 ** Check for impact on a wall or other high obstacle. 924 */ 925 if (!Class->IsHigh && cellptr->Overlay != OVERLAY_NONE && OverlayTypeClass::As_Reference(cellptr->Overlay).IsHigh) { 926 coord = Cell_Coord(Coord_Cell(coord)); 927 return(true); 928 } 929 930 /* 931 ** Check to make sure that underwater projectiles (torpedoes) will not 932 ** travel in anything but water. 933 */ 934 if (Class->IsSubSurface) { 935 int d = ::Distance(Coord_Fraction(coord), XY_Coord(CELL_LEPTON_W/2, CELL_LEPTON_W/2)); 936 if (cellptr->Land_Type() != LAND_WATER || (d < CELL_LEPTON_W/3 && cellptr->Cell_Techno() != NULL && cellptr->Cell_Techno() != Payback)) { 937 938 /* 939 ** Force explosion to be at center of techno object if one is present. 940 */ 941 if (cellptr->Cell_Techno() != NULL) { 942 coord = cellptr->Cell_Techno()->Target_Coord(); 943 } 944 945 /* 946 ** However, if the torpedo was blocked by a bridge, then force the 947 ** torpedo to explode on top of that bridge cell. 948 */ 949 if (cellptr->Is_Bridge_Here()) { 950 coord = Coord_Snap(coord); 951 } 952 953 return(true); 954 } 955 } 956 957 /* 958 ** Bullets are generally more effective when they are fired at aircraft. 959 */ 960 if (Class->IsAntiAircraft && As_Aircraft(TarCom) && Distance(TarCom) < 0x0080) { 961 return(true); 962 } 963 964 /* 965 ** No reason for forced explosion was detected, so return 'false' to 966 ** indicate that no forced explosion is required. 967 */ 968 return(false); 969 } 970 971 972 /*********************************************************************************************** 973 * BulletClass::Bullet_Explodes -- Performs bullet explosion logic. * 974 * * 975 * This handles the exploding bullet action. It will generate the animation and the * 976 * damage as necessary. * 977 * * 978 * INPUT: none * 979 * * 980 * OUTPUT: none * 981 * * 982 * WARNINGS: The bullet should be deleted after this routine is called. * 983 * * 984 * HISTORY: * 985 * 10/10/1996 JLB : Created. * 986 *=============================================================================================*/ 987 void BulletClass::Bullet_Explodes(bool forced) 988 { 989 /* 990 ** When the target is reached, explode and do the damage 991 ** required of it. For homing objects, don't force the explosion to 992 ** match the target position. Non-homing projectiles adjust position so 993 ** that they hit the target. This compensates for the error in line of 994 ** flight logic. 995 */ 996 if ( (Payback != NULL && Payback->What_Am_I() == RTTI_INFANTRY && ((InfantryClass *)Payback)->Class->IsDog) || 997 (!forced && !Class->IsArcing && Class->ROT == 0 && Fuse_Target())) { 998 Coord = Fuse_Target(); 999 } 1000 1001 /* 1002 ** Non-aircraft targets apply damage to the ground. 1003 */ 1004 if (!Is_Target_Aircraft(TarCom) || As_Aircraft(TarCom)->In_Which_Layer() == LAYER_GROUND) { 1005 Explosion_Damage(Coord, Strength, Payback, Warhead); 1006 if (!IsActive) return; 1007 1008 } else { 1009 1010 /* 1011 ** Special damage apply for SAM missiles. This is the only way that missile 1012 ** damage affects the aircraft target. 1013 */ 1014 if (Distance(TarCom) < 0x0080) { 1015 AircraftClass * object = As_Aircraft(TarCom); 1016 1017 int str = Strength; 1018 if (object) object->Take_Damage(str, 0, Warhead, Payback); 1019 } 1020 } 1021 1022 /* 1023 ** For projectiles that are invisible while travelling toward the target, 1024 ** allow scatter effect for the impact animation. 1025 */ 1026 if (Class->IsInvisible) { 1027 Coord = Coord_Scatter(Coord, 0x0020); 1028 } 1029 1030 /* 1031 ** Fetch the land type that the explosion will be upon. Special case for 1032 ** flying aircraft targets, their land type will be LAND_NONE. 1033 */ 1034 CellClass const * cellptr = &Map[Coord]; 1035 LandType land = cellptr->Land_Type(); 1036 if (Is_Target_Aircraft(TarCom) && As_Aircraft(TarCom)->In_Which_Layer() == LAYER_TOP) { 1037 land = LAND_NONE; 1038 } 1039 1040 AnimType anim = Combat_Anim(Strength, Warhead, land); 1041 1042 /* 1043 ** If it's a water explosion that's going to play, don't play it 1044 ** if its cell is the same as the center cell of the target ship. 1045 */ 1046 if (anim >= ANIM_WATER_EXP1 && anim <= ANIM_WATER_EXP3 && Is_Target_Vessel(TarCom)) { 1047 if (Coord_Cell(Coord) == Coord_Cell(As_Vessel(TarCom)->Center_Coord())) { 1048 anim = (AnimType) (ANIM_VEH_HIT1 + (anim - ANIM_WATER_EXP1)); 1049 } 1050 } 1051 1052 if (anim != ANIM_NONE) { 1053 AnimClass * aptr = new AnimClass(anim, Coord); 1054 if (aptr) { 1055 aptr->Sort_Above(TarCom); 1056 } 1057 /* 1058 ** Special case trap: if they're making the nuclear explosion, 1059 ** and no anim is available, force the nuclear damage anyway 1060 ** because nuke damage is done in the middle of the animation 1061 ** and if there's no animation, there won't be any damage. 1062 */ 1063 if (!aptr && anim == ANIM_ATOM_BLAST) { 1064 GlyphX_Debug_Print("FAILED to create ANIM_ATOM_BLAST"); 1065 HousesType house = HOUSE_NONE; 1066 if (Payback) { 1067 house = Payback->House->Class->House; 1068 } 1069 AnimClass::Do_Atom_Damage(house, Coord_Cell(Coord)); 1070 } 1071 1072 // MBL 05.20.2020 1073 // Fix for Nuke or Atom Bomb killing structures and units during animation sequence and not getting kills tracked 1074 // Per https://jaas.ea.com/browse/TDRA-6610 1075 // 1076 else if (aptr && anim == ANIM_ATOM_BLAST && aptr->OwnerHouse == HOUSE_NONE) { 1077 if (Payback && Payback->House && Payback->House->Class) { 1078 aptr->Set_Owner(Payback->House->Class->House); 1079 } 1080 } 1081 } 1082 1083 // if (Payback && Payback->House == PlayerPtr && stricmp(Class->Name(), "GPSSATELLITE") == 0) { 1084 if (Payback && Class->Type == BULLET_GPS_SATELLITE) { 1085 1086 bool reveal = false; 1087 if (Session.Type != GAME_GLYPHX_MULTIPLAYER) { 1088 if (Payback->House == PlayerPtr) { 1089 reveal = true; 1090 } 1091 } else { 1092 if (Payback->House->IsHuman) { 1093 reveal = true; 1094 } 1095 } 1096 1097 if (reveal) { 1098 if (!Map.Is_Radar_Active()) { 1099 Map.Radar_Activate(1); 1100 } 1101 for (CELL cell = 0; cell < MAP_CELL_TOTAL; cell++) { 1102 Map.Map_Cell(cell, Payback->House); 1103 } 1104 Map.RadarClass::Flag_To_Redraw(true); 1105 } 1106 // Sound_Effect(VOC_SATTACT2); 1107 Payback->House->IsGPSActive = true; 1108 Payback->House->IsVisionary = true; 1109 } 1110 }