UNIT.CPP (224086B)
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/UNIT.CPP 1 3/03/97 10:26a 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 : UNIT.CPP * 24 * * 25 * Programmer : Joe L. Bostic * 26 * * 27 * Start Date : September 10, 1993 * 28 * * 29 * Last Update : November 3, 1996 [JLB] * 30 * * 31 *---------------------------------------------------------------------------------------------* 32 * Functions: * 33 * Recoil_Adjust -- Adjust pixel values in direction specified. * 34 * UnitClass::AI -- AI processing for the unit. * 35 * UnitClass::APC_Close_Door -- Closes an APC door. * 36 * UnitClass::APC_Open_Door -- Opens an APC door. * 37 * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possib* 38 * UnitClass::Active_Click_With -- Performs specified action on specified cell. * 39 * UnitClass::Approach_Target -- Handles approaching the target in order to attack it. * 40 * UnitClass::Assign_Destination -- Assign a destination to a unit. * 41 * UnitClass::Blocking_Object -- Determines how a object blocks a unit * 42 * UnitClass::Can_Enter_Cell -- Determines cell entry legality. * 43 * UnitClass::Can_Fire -- Determines if turret can fire upon target. * 44 * UnitClass::Click_With -- Handles player map clicking while this unit is selected. * 45 * UnitClass::Credit_Load -- Fetch the full credit value of cargo carried. * 46 * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * 47 * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * 48 * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * 49 * UnitClass::Draw_It -- Draws a unit object. * 50 * UnitClass::Edge_Of_World_AI -- Check for falling off the edge of the world. * 51 * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * 52 * UnitClass::Fire_Direction -- Determines the direction of firing. * 53 * UnitClass::Firing_AI -- Handle firing logic for this unit. * 54 * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * 55 * UnitClass::Flag_Remove -- Removes the house flag from this unit. * 56 * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * 57 * UnitClass::Goto_Tiberium -- Search for and head toward nearest available Tiberium patch. * 58 * UnitClass::Greatest_Threat -- Fetches the greatest threat for this unit. * 59 * UnitClass::Harvesting -- Harvests tiberium at the current location. * 60 * UnitClass::Init -- Clears all units for scenario preparation. * 61 * UnitClass::Limbo -- Limbo this unit. * 62 * UnitClass::Mission_Guard -- Special guard mission override processor. * 63 * UnitClass::Mission_Guard_Area -- Guard area logic for units. * 64 * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * 65 * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * 66 * UnitClass::Mission_Move -- Handles special move mission overrides. * 67 * UnitClass::Mission_Repair -- Handles finding and proceeding on a repair mission. * 68 * UnitClass::Mission_Unload -- Handles unloading cargo. * 69 * UnitClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. * 70 * UnitClass::Ok_To_Move -- Queries whether the vehicle can move. * 71 * UnitClass::Overlap_List -- Determines overlap list for units. * 72 * UnitClass::Overrun_Square -- Handles vehicle overrun of a cell. * 73 * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * 74 * UnitClass::Pip_Count -- Fetches the number of pips to display on unit. * 75 * UnitClass::Random_Animate -- Handles random idle animation for the unit. * 76 * UnitClass::Read_INI -- Reads units from scenario INI file. * 77 * UnitClass::Receive_Message -- Handles receiving a radio message. * 78 * UnitClass::Reload_AI -- Perform reload logic for this unit. * 79 * UnitClass::Rotation_AI -- Process any turret or body rotation. * 80 * UnitClass::Scatter -- Causes the unit to scatter to a nearby location. * 81 * UnitClass::Set_Speed -- Initiate unit movement physics. * 82 * UnitClass::Shape_Number -- Fetch the shape number to use for this unit. * 83 * UnitClass::Should_Crush_It -- Determines if this unit should crush an object. * 84 * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * 85 * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * 86 * UnitClass::Take_Damage -- Inflicts damage points on a unit. * 87 * UnitClass::Tiberium_Check -- Search for and head toward nearest available Tiberium patch. * 88 * UnitClass::Tiberium_Load -- Determine the Tiberium load as a percentage. * 89 * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * 90 * UnitClass::UnitClass -- Constructor for units. * 91 * UnitClass::Unlimbo -- Removes unit from stasis. * 92 * UnitClass::What_Action -- Determines action to perform on specified cell. * 93 * UnitClass::What_Action -- Determines what action would occur if clicked on object. * 94 * UnitClass::Write_INI -- Store the units to the INI database. * 95 * UnitClass::delete -- Deletion operator for units. * 96 * UnitClass::new -- Allocate a unit slot and adjust access arrays. * 97 * UnitClass::~UnitClass -- Destructor for unit objects. * 98 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 99 100 #include "function.h" 101 #include "COORDA.h" 102 103 104 extern void Logic_Switch_Player_Context(ObjectClass *object); 105 extern void Logic_Switch_Player_Context(HouseClass *object); 106 extern void On_Special_Weapon_Targetting(const HouseClass* player_ptr, SpecialWeaponType weapon_type); 107 extern bool Is_Legacy_Render_Enabled(void); 108 109 110 static int _GapShroudXTable[]={ 111 -1, 0, 1, 112 -2,-1, 0, 1, 2, 113 -2,-1, 0, 1, 2, 114 -2,-1, 0, 1, 2, 115 -2,-1, 0, 1, 2, 116 -2,-1, 0, 1, 2, 117 -1, 0, 1 118 }; 119 static int _GapShroudYTable[]={ 120 -3,-3,-3, 121 -2,-2,-2,-2,-2, 122 -1,-1,-1,-1,-1, 123 0, 0, 0, 0, 0, 124 1, 1, 1, 1, 1, 125 2, 2, 2, 2, 2, 126 3, 3, 3 127 }; 128 129 /*********************************************************************************************** 130 * Recoil_Adjust -- Adjust pixel values in direction specified. * 131 * * 132 * This is a helper routine that modifies the pixel coordinates provided according to the * 133 * direction specified. The effect is the simulate recoil effects by moving an object 'back'* 134 * one pixel. Since the pixels moved depend on facing, this routine handles the pixel * 135 * adjustment quickly. * 136 * * 137 * INPUT: dir -- The direction to base the recoil on. * 138 * * 139 * x,y -- References to the pixel coordinates that will be adjusted. * 140 * * 141 * OUTPUT: none * 142 * * 143 * WARNINGS: none * 144 * * 145 * HISTORY: * 146 * 05/08/1995 JLB : Created. * 147 *=============================================================================================*/ 148 void Recoil_Adjust(DirType dir, int &x, int &y) 149 { 150 static struct { 151 signed char X,Y; 152 } _adjust[32] = { 153 {0, 1}, // N 154 {0, 1}, 155 {0, 1}, 156 {-1, 1}, 157 {-1, 1}, // NE 158 {-1, 1}, 159 {-1, 0}, 160 {-1, 0}, 161 {-1, 0}, // E 162 {-1, 0}, 163 {-1, -1}, 164 {-1, -1}, 165 {-1, -1}, // SE 166 {-1, -1}, 167 {-1, -1}, 168 {0, -1}, 169 {0, -1}, // S 170 {0, -1}, 171 {0, -1}, 172 {1, -1}, 173 {1, -1}, // SW 174 {1, -1}, 175 {1, 0}, 176 {1, 0}, 177 {1, 0}, // W 178 {1, 0}, 179 {1, 1}, 180 {1, 1}, 181 {1, 1}, // NW 182 {1, 1}, 183 {0, 1}, 184 {0, 1} 185 }; 186 187 int index = Dir_To_32(dir); 188 x += _adjust[index].X; 189 y += _adjust[index].Y; 190 } 191 192 193 /*********************************************************************************************** 194 * UnitClass::new -- Allocate a unit slot and adjust access arrays. * 195 * * 196 * This routine will allocate a unit from the available unit pool and * 197 * fixup all the access lists to match. It will allocate a unit slot * 198 * from within the range allowed for the specified unit type. If no * 199 * slot was found, then it will fail. * 200 * * 201 * INPUT: none * 202 * * 203 * OUTPUT: Returns with a pointer to the allocated unit. * 204 * * 205 * WARNINGS: none * 206 * * 207 * HISTORY: * 208 * 04/11/1994 JLB : Created. * 209 * 04/21/1994 JLB : Converted to operator new. * 210 *=============================================================================================*/ 211 void * UnitClass::operator new(size_t) 212 { 213 void * ptr = Units.Alloc(); 214 if (ptr != NULL) { 215 ((UnitClass *)ptr)->Set_Active(); 216 } 217 return(ptr); 218 } 219 220 221 /*********************************************************************************************** 222 * UnitClass::delete -- Deletion operator for units. * 223 * * 224 * This removes the unit from the local allocation system. Since this * 225 * is a fixed block of memory, not much has to be done to delete the * 226 * unit. Merely marking it as inactive is enough. * 227 * * 228 * INPUT: ptr -- Pointer to the unit to delete. * 229 * * 230 * OUTPUT: none * 231 * * 232 * WARNINGS: none * 233 * * 234 * HISTORY: * 235 * 04/21/1994 JLB : Created. * 236 *=============================================================================================*/ 237 void UnitClass::operator delete(void * ptr) 238 { 239 if (ptr != NULL) { 240 ((UnitClass *)ptr)->IsActive = false; 241 } 242 Units.Free((UnitClass *)ptr); 243 } 244 245 246 /*********************************************************************************************** 247 * UnitClass::~UnitClass -- Destructor for unit objects. * 248 * * 249 * This destructor will lower the unit count for the owning house as well as inform any * 250 * other units in communication, that this unit is about to leave reality. * 251 * * 252 * INPUT: none * 253 * * 254 * OUTPUT: none * 255 * * 256 * WARNINGS: none * 257 * * 258 * HISTORY: * 259 * 08/15/1994 JLB : Created. * 260 *=============================================================================================*/ 261 UnitClass::~UnitClass(void) 262 { 263 if (GameActive && Class.Is_Valid()) { 264 265 /* 266 ** Remove this member from any team it may be associated with. This must occur at the 267 ** top most level of the inheritance hierarchy because it may call virtual functions. 268 */ 269 if (Team.Is_Valid()) { 270 Team->Remove(this); 271 Team = NULL; 272 } 273 274 House->Tracking_Remove(this); 275 276 /* 277 ** If there are any cargo members, delete them. 278 */ 279 while (Is_Something_Attached()) { 280 delete Detach_Object(); 281 } 282 283 Limbo(); 284 } 285 ID = -1; 286 } 287 288 289 /*********************************************************************************************** 290 * UnitClass::UnitClass -- Constructor for units. * 291 * * 292 * This constructor for units will initialize the unit into the game * 293 * system. It will be placed in all necessary tracking lists. The initial condition will * 294 * be in a state of limbo. * 295 * * 296 * INPUT: classid -- The type of unit to create. * 297 * * 298 * house -- The house owner of this unit. * 299 * * 300 * OUTPUT: none * 301 * * 302 * WARNINGS: none * 303 * * 304 * HISTORY: * 305 * 04/21/1994 JLB : Created. * 306 *=============================================================================================*/ 307 UnitClass::UnitClass(UnitType classid, HousesType house) : 308 DriveClass(RTTI_UNIT, Units.ID(this), house), 309 Class(UnitTypes.Ptr((int)classid)), 310 Flagged(HOUSE_NONE), 311 IsDumping(false), 312 Gems(0), 313 Gold(0), 314 Tiberium(0), 315 IsToScatter(false), 316 ShroudBits(0xFFFFFFFFUL), 317 ShroudCenter(0), 318 Reload(0), 319 SecondaryFacing(PrimaryFacing), 320 TiberiumUnloadRefinery(TARGET_NONE) 321 { 322 Reload = 0; 323 House->Tracking_Add(this); 324 Ammo = Class->MaxAmmo; 325 IsCloakable = Class->IsCloakable; 326 if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3)); 327 328 /* 329 ** For two shooters, clear out the second shot flag -- it will be set the first time 330 ** the object fires. For non two shooters, set the flag since it will never be cleared 331 ** and the second shot flag tells the system that normal rearm times apply -- this is 332 ** what is desired for non two shooters. 333 */ 334 IsSecondShot = !Class->Is_Two_Shooter(); 335 Strength = Class->MaxStrength; 336 337 /* 338 ** Keep count of the number of units created. 339 */ 340 // if (Session.Type == GAME_INTERNET) { 341 // House->UnitTotals->Increment_Unit_Total((int)classid); 342 // } 343 } 344 345 346 #ifdef CHEAT_KEYS 347 /*********************************************************************************************** 348 * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * 349 * * 350 * This displays the current status of the unit class to the mono monitor. By this display * 351 * bugs may be tracked down or prevented. * 352 * * 353 * INPUT: none * 354 * * 355 * OUTPUT: none * 356 * * 357 * WARNINGS: none * 358 * * 359 * HISTORY: * 360 * 06/02/1994 JLB : Created. * 361 *=============================================================================================*/ 362 void UnitClass::Debug_Dump(MonoClass * mono) const 363 { 364 assert(Units.ID(this) == ID); 365 assert(IsActive); 366 367 mono->Set_Cursor(0, 0); 368 mono->Print(Text_String(TXT_DEBUG_VEHICLE)); 369 mono->Set_Cursor(47, 5);mono->Printf("%02X:%02X", SecondaryFacing.Current(), SecondaryFacing.Desired()); 370 371 mono->Set_Cursor(1, 11);mono->Printf("%03", Gems); 372 mono->Set_Cursor(7, 11);mono->Printf("%03", Gold); 373 374 mono->Fill_Attrib(66, 13, 12, 1, IsDumping ? MonoClass::INVERSE : MonoClass::NORMAL); 375 376 DriveClass::Debug_Dump(mono); 377 } 378 #endif 379 380 381 /*********************************************************************************************** 382 * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * 383 * * 384 * This routine is used by the rendering system in order to sort the * 385 * game objects in a back to front order. This is now the correct * 386 * overlap effect is achieved. * 387 * * 388 * INPUT: none * 389 * * 390 * OUTPUT: Returns with a coordinate value that can be used for sorting. * 391 * * 392 * WARNINGS: none * 393 * * 394 * HISTORY: * 395 * 05/17/1994 JLB : Created. * 396 *=============================================================================================*/ 397 COORDINATE UnitClass::Sort_Y(void) const 398 { 399 assert(Units.ID(this) == ID); 400 assert(IsActive); 401 402 return(Coord_Add(Coord, 0x00800000L)); 403 } 404 405 406 /*********************************************************************************************** 407 * UnitClass::AI -- AI processing for the unit. * 408 * * 409 * This routine will perform the AI processing necessary for the unit. These are non- * 410 * graphic related operations. * 411 * * 412 * INPUT: none * 413 * * 414 * OUTPUT: none * 415 * * 416 * WARNINGS: none * 417 * * 418 * HISTORY: * 419 * 05/31/1994 JLB : Created. * 420 *=============================================================================================*/ 421 void UnitClass::AI(void) 422 { 423 assert(Units.ID(this) == ID); 424 assert(IsActive); 425 /* 426 ** Act on new orders if the unit is at a good position to do so. 427 */ 428 if (Height == 0 && !IsDumping && !IsDriving && Is_Door_Closed() /*&& Mission != MISSION_UNLOAD*/) { 429 // if (MissionQueue == MISSION_NONE) Enter_Idle_Mode(); 430 Commence(); 431 } 432 DriveClass::AI(); 433 if (!IsActive || Height > 0) { 434 return; 435 } 436 437 /* 438 ** Hack check to ensure that a harvester won't harvest if it is not harvesting. 439 */ 440 if (Mission != MISSION_HARVEST) { 441 IsHarvesting = false; 442 } 443 444 /* 445 ** Handle combat logic for this unit. It will determine if it has a target and 446 ** if so, if conditions are favorable for firing. When conditions permit, the 447 ** unit will fire upon its target. 448 */ 449 Firing_AI(); 450 #ifdef FIXIT_CSII // checked - ajw 9/28/98 451 if (!IsActive) { 452 return; 453 } 454 #endif 455 /* 456 ** Turret rotation processing. Handles rotating radar dish 457 ** as well as conventional turrets if present. If no turret present, but 458 ** it decides that the body should face its target, then body rotation 459 ** would occur by this process as well. 460 */ 461 Rotation_AI(); 462 463 /* 464 ** Scatter units off buildings in guard modes. 465 */ 466 if (!IsTethered && !IsFiring && !IsDriving && !IsRotating && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord].Cell_Building() != NULL) { 467 Scatter(0, true, true); 468 } 469 470 /* 471 ** Delete this unit if it finds itself off the edge of the map and it is in 472 ** guard or other static mission mode. 473 */ 474 if (Edge_Of_World_AI()) { 475 return; 476 } 477 478 /* 479 ** Units will reload every so often if they are under the burden of 480 ** being required to reload between shots. 481 */ 482 Reload_AI(); 483 484 /* 485 ** Transporters require special logic handled here since there isn't a MISSION_WAIT_FOR_PASSENGERS 486 ** mission that they can follow. Passenger loading is merely a part of their normal operation. 487 */ 488 if (Class->Max_Passengers() > 0) { 489 490 /* 491 ** Double check that there is a passenger that is trying to load or unload. 492 ** If not, then close the door. 493 */ 494 if (!Is_Door_Closed() && Mission != MISSION_UNLOAD && Transmit_Message(RADIO_TRYING_TO_LOAD) != RADIO_ROGER) { 495 APC_Close_Door(); 496 } 497 } 498 499 /* 500 ** Don't start a new mission unless the vehicle is in the center of 501 ** a cell (not driving) and the door (if any) is closed. 502 */ 503 if (!IsDumping && !IsDriving && Is_Door_Closed()/*&& Mission != MISSION_UNLOAD*/) { 504 Commence(); 505 } 506 507 /* 508 ** A cloaked object that is carrying the flag will always shimmer. 509 */ 510 if (Cloak == CLOAKED && Flagged != HOUSE_NONE) { 511 Do_Shimmer(); 512 } 513 514 /* 515 ** Mobile gap generators regenerate their gap every so often (just in case). 516 */ 517 if (Class->IsGapper && !IsDriving && (Frame % TICKS_PER_SECOND) == 0) { 518 Shroud_Regen(); 519 } 520 } 521 522 523 /*********************************************************************************************** 524 * UnitClass::Rotation_AI -- Process any turret or body rotation. * 525 * * 526 * This routine will handle the rotation logic for the unit's turret (if it has one) as * 527 * well as its normal body shape. * 528 * * 529 * INPUT: none * 530 * * 531 * OUTPUT: none * 532 * * 533 * WARNINGS: none * 534 * * 535 * HISTORY: * 536 * 07/30/1996 JLB : Created. * 537 *=============================================================================================*/ 538 void UnitClass::Rotation_AI(void) 539 { 540 if (Target_Legal(TarCom) && !IsRotating) { 541 DirType dir = Direction(TarCom); 542 543 if (Class->IsTurretEquipped) { 544 SecondaryFacing.Set_Desired(dir); 545 } else { 546 547 /* 548 ** Non turret equipped vehicles will rotate their body to face the target only 549 ** if the vehicle isn't currently moving or facing the correct direction. This 550 ** applies only to tracked vehicles. Wheeled vehicles never rotate to face the 551 ** target, since they aren't maneuverable enough. 552 */ 553 if ((Class->Speed == SPEED_TRACK /* || *this == UNIT_BIKE */ ) && !Target_Legal(NavCom) && !IsDriving && PrimaryFacing.Difference(dir)) { 554 PrimaryFacing.Set_Desired(dir); 555 } 556 } 557 } 558 559 if (Class->IsRadarEquipped) { 560 Mark(MARK_CHANGE_REDRAW); 561 SecondaryFacing.Set((DirType)(SecondaryFacing.Current() + 8)); 562 Mark(MARK_CHANGE_REDRAW); 563 } else { 564 565 IsRotating = false; 566 if (Class->IsTurretEquipped) { 567 if (IsTurretLockedDown) { 568 SecondaryFacing.Set_Desired(PrimaryFacing.Current()); 569 } 570 571 if (SecondaryFacing.Is_Rotating()) { 572 Mark(MARK_CHANGE_REDRAW); 573 if (SecondaryFacing.Rotation_Adjust(Class->ROT+1)) { 574 Mark(MARK_CHANGE_REDRAW); 575 } 576 577 /* 578 ** If no further rotation is necessary, flag that the rotation 579 ** has stopped. 580 */ 581 if (!Class->IsRadarEquipped) { 582 IsRotating = SecondaryFacing.Is_Rotating(); 583 } 584 } else { 585 if (!IsTurretLockedDown && !Target_Legal(TarCom)) { 586 if (!Target_Legal(NavCom)) { 587 SecondaryFacing.Set_Desired(PrimaryFacing.Current()); 588 } else { 589 SecondaryFacing.Set_Desired(Direction(NavCom)); 590 } 591 } 592 } 593 } 594 } 595 } 596 597 598 /*********************************************************************************************** 599 * UnitClass::Edge_Of_World_AI -- Check for falling off the edge of the world. * 600 * * 601 * When a unit leaves the map it will be eliminated. This routine checks for this case * 602 * and eliminates the unit accordingly. * 603 * * 604 * INPUT: none * 605 * * 606 * OUTPUT: bool; Was the unit eliminated by this routine? * 607 * * 608 * WARNINGS: Be sure to check for the return value and if 'true' abort any further processing* 609 * of the unit since it is dead. Only call this routine once per unit per * 610 * game logic loop. * 611 * * 612 * HISTORY: * 613 * 07/30/1996 JLB : Created. * 614 *=============================================================================================*/ 615 bool UnitClass::Edge_Of_World_AI(void) 616 { 617 if (Mission == MISSION_GUARD && !Map.In_Radar(Coord_Cell(Coord)) && IsLocked) { 618 if (Team.Is_Valid()) Team->IsLeaveMap = true; 619 Stun(); 620 delete this; 621 return(true); 622 } 623 return(false); 624 } 625 626 627 /*********************************************************************************************** 628 * UnitClass::Reload_AI -- Perform reload logic for this unit. * 629 * * 630 * Some units require special reload logic. The V2 rocket launcher in particular. Perform * 631 * this reload logic with this routine. * 632 * * 633 * INPUT: none * 634 * * 635 * OUTPUT: none * 636 * * 637 * WARNINGS: Only call this routine once per unit per game logic loop. * 638 * * 639 * HISTORY: * 640 * 07/30/1996 JLB : Created. * 641 *=============================================================================================*/ 642 void UnitClass::Reload_AI(void) 643 { 644 if (*this == UNIT_V2_LAUNCHER && Ammo < Class->MaxAmmo) { 645 if (IsDriving) { 646 Reload = Reload + 1; 647 } else { 648 if (Reload == 0) { 649 Ammo++; 650 if (Ammo < Class->MaxAmmo) { 651 Reload = TICKS_PER_SECOND*30; 652 } 653 Mark(MARK_CHANGE); 654 } 655 } 656 } 657 } 658 659 660 /*********************************************************************************************** 661 * UnitClass::Firing_AI -- Handle firing logic for this unit. * 662 * * 663 * This routine wil check for and perform any firing logic required of this unit. * 664 * * 665 * INPUT: none * 666 * * 667 * OUTPUT: none * 668 * * 669 * WARNINGS: This should be called only once per unit per game logic loop. * 670 * * 671 * HISTORY: * 672 * 07/30/1996 JLB : Created. * 673 *=============================================================================================*/ 674 void UnitClass::Firing_AI(void) 675 { 676 if (Target_Legal(TarCom) && Class->PrimaryWeapon != NULL) { 677 678 /* 679 ** Determine which weapon can fire. First check for the primary weapon. If that weapon 680 ** cannot fire, then check any secondary weapon. If neither weapon can fire, then the 681 ** failure code returned is that from the primary weapon. 682 */ 683 int primary = What_Weapon_Should_I_Use(TarCom); 684 FireErrorType ok = Can_Fire(TarCom, primary); 685 switch (ok) { 686 case FIRE_OK: 687 if (!((UnitClass *)this)->Class->IsFireAnim) { 688 Mark(MARK_OVERLAP_UP); 689 IsFiring = false; 690 Mark(MARK_OVERLAP_DOWN); 691 } 692 693 Fire_At(TarCom, primary); 694 break; 695 696 case FIRE_FACING: 697 #ifdef FIXIT_CSII // checked - ajw 9/28/98 698 if (Class->IsLockTurret || Class->Type == UNIT_DEMOTRUCK) { 699 #else 700 if (Class->IsLockTurret) { 701 #endif 702 if (!Target_Legal(NavCom) && !IsDriving) { 703 PrimaryFacing.Set_Desired(Direction(TarCom)); 704 SecondaryFacing.Set_Desired(PrimaryFacing.Desired()); 705 } 706 } else { 707 SecondaryFacing.Set_Desired(Direction(TarCom)); 708 } 709 break; 710 711 case FIRE_CLOAKED: 712 Mark(MARK_OVERLAP_UP); 713 IsFiring = false; 714 Mark(MARK_OVERLAP_DOWN); 715 Do_Uncloak(); 716 break; 717 } 718 } 719 } 720 721 722 /*********************************************************************************************** 723 * UnitClass::Receive_Message -- Handles receiving a radio message. * 724 * * 725 * This is the handler function for when a unit receives a radio * 726 * message. Typical use of this is when a unit unloads from a hover * 727 * class so that clearing of the transport is successful. * 728 * * 729 * INPUT: from -- Pointer to the originator of the message. * 730 * * 731 * message -- The radio message received. * 732 * * 733 * param -- Reference to an optional parameter the might be needed to return * 734 * information back to the originator of the message. * 735 * * 736 * OUTPUT: Returns with the radio message response. * 737 * * 738 * WARNINGS: none * 739 * * 740 * HISTORY: * 741 * 05/22/1994 JLB : Created. * 742 *=============================================================================================*/ 743 RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) 744 { 745 assert(Units.ID(this) == ID); 746 assert(IsActive); 747 748 switch (message) { 749 /* 750 ** Checks to see if this object is in need of service depot processing. 751 */ 752 case RADIO_NEED_REPAIR: 753 if (!IsDriving && !Target_Legal(NavCom) && (Health_Ratio() >= 1 && (*this != UNIT_MINELAYER || Ammo >= Class->MaxAmmo))) return(RADIO_NEGATIVE); 754 break; 755 // return(RADIO_ROGER); 756 757 /* 758 ** Asks if the passenger can load on this transport. 759 */ 760 case RADIO_CAN_LOAD: 761 if (Class->Max_Passengers() == 0 || from == NULL || !House->Is_Ally(from->Owner())) return(RADIO_STATIC); 762 if (How_Many() < Class->Max_Passengers()) { 763 return(RADIO_ROGER); 764 } 765 return(RADIO_NEGATIVE); 766 767 /* 768 ** The refinery has told this harvester that it should begin the backup procedure 769 ** so that proper unloading may take place. 770 */ 771 case RADIO_BACKUP_NOW: 772 DriveClass::Receive_Message(from, message, param); 773 if (!IsRotating && PrimaryFacing != DIR_W) { 774 Do_Turn(DIR_W); 775 } else { 776 if (!IsDriving) { 777 TechnoClass * whom = Contact_With_Whom(); 778 if (IsTethered && whom != NULL) { 779 if (whom->What_Am_I() == RTTI_BUILDING && Mission == MISSION_ENTER) { 780 if (Transmit_Message(RADIO_IM_IN, whom) == RADIO_ROGER) { 781 Transmit_Message(RADIO_UNLOADED, whom); 782 } 783 } 784 } 785 } 786 } 787 return(RADIO_ROGER); 788 789 /* 790 ** This message is sent by the passenger when it determines that it has 791 ** entered the transport. 792 */ 793 case RADIO_IM_IN: 794 if (How_Many() == Class->Max_Passengers()) { 795 APC_Close_Door(); 796 } 797 return(RADIO_ATTACH); 798 799 /* 800 ** Docking maintenance message received. Check to see if new orders should be given 801 ** to the impatient unit. 802 */ 803 case RADIO_DOCKING: 804 805 /* 806 ** If this transport is moving, then always abort the docking request. 807 */ 808 if (IsDriving || Target_Legal(NavCom)) { 809 return(RADIO_NEGATIVE); 810 } 811 812 /* 813 ** Check for the case of a docking message arriving from a unit that does not 814 ** have formal radio contact established. This might be a unit that is standing 815 ** by. If this transport is free to proceed with normal docking operation, then 816 ** establish formal contact now. If the transport is completely full, then break 817 ** off contact. In all other cases, just tell the pending unit to stand by. 818 */ 819 if (Contact_With_Whom() != from) { 820 821 /* 822 ** Can't ever load up so tell the passenger to bug off. 823 */ 824 if (How_Many() >= Class->Max_Passengers()) { 825 return(RADIO_NEGATIVE); 826 } 827 828 /* 829 ** Establish contact and let the loading process proceed normally. 830 */ 831 if (!In_Radio_Contact()) { 832 Transmit_Message(RADIO_HELLO, from); 833 } else { 834 835 /* 836 ** This causes the potential passenger to think that all is ok and to 837 ** hold on for a bit. 838 */ 839 return(RADIO_ROGER); 840 } 841 } 842 843 if (Class->Max_Passengers() > 0 && How_Many() < Class->Max_Passengers()) { 844 DriveClass::Receive_Message(from, message, param); 845 846 if (!IsDriving && !IsRotating && !IsTethered) { 847 848 /* 849 ** If the potential passenger needs someplace to go, then figure out a good 850 ** spot and tell it to go. 851 */ 852 if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { 853 854 CELL cell; 855 DirType dir = Desired_Load_Dir(from, cell); 856 857 /* 858 ** If no adjacent free cells are detected, then passenger loading 859 ** cannot occur. Break radio contact. 860 */ 861 if (cell == 0) { 862 Transmit_Message(RADIO_OVER_OUT, from); 863 } else { 864 param = (long)::As_Target(cell); 865 Do_Turn(dir); 866 867 /* 868 ** If it is now facing the correct direction, then open the 869 ** transport doors. Close the doors if the transport is or needs 870 ** to rotate. 871 */ 872 #ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 873 if (*this == UNIT_APC || *this == UNIT_PHASE) { 874 #else 875 if (*this == UNIT_APC) { 876 #endif 877 if (IsRotating) { 878 if (!Is_Door_Closed()) { 879 APC_Close_Door(); 880 } 881 } else { 882 if (!Is_Door_Open()) { 883 APC_Open_Door(); 884 } 885 } 886 } 887 888 /* 889 ** Tell the potential passenger where it should go. If the passenger is 890 ** already at the staging location, then tell it to move onto the transport 891 ** directly. 892 */ 893 if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { 894 #ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 895 if ( (*this != UNIT_APC && *this != UNIT_PHASE) || Is_Door_Open()) { 896 #else 897 if (*this != UNIT_APC || Is_Door_Open()) { 898 #endif 899 param = (long)As_Target(); 900 Transmit_Message(RADIO_TETHER); 901 if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { 902 Transmit_Message(RADIO_OVER_OUT, from); 903 } else { 904 Contact_With_Whom()->Unselect(); 905 } 906 } 907 } 908 } 909 } 910 } 911 return(RADIO_ROGER); 912 } 913 break; 914 915 /* 916 ** Something bad has happened to the object in contact with. Abort any coordinated 917 ** activity with this object. Basically, ... run away! Run away! 918 */ 919 case RADIO_RUN_AWAY: 920 if (Class->IsToHarvest && In_Radio_Contact() && Mission == MISSION_ENTER) { 921 TechnoClass * contact = Contact_With_Whom(); 922 if (contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) == STRUCT_REFINERY) { 923 // Slight hack; set a target so the harvest mission knows to skip to finding home state 924 Assign_Mission(MISSION_HARVEST); 925 TarCom = As_Target(); 926 return(RADIO_ROGER); 927 } 928 } 929 return(DriveClass::Receive_Message(from, message, param)); 930 931 /* 932 ** When this message is received, it means that the other object 933 ** has already turned its radio off. Turn this radio off as well. 934 */ 935 case RADIO_OVER_OUT: 936 if (Mission == MISSION_RETURN) { 937 Assign_Mission(MISSION_GUARD); 938 } 939 DriveClass::Receive_Message(from, message, param); 940 return(RADIO_ROGER); 941 942 } 943 return(DriveClass::Receive_Message(from, message, param)); 944 } 945 946 947 /*********************************************************************************************** 948 * UnitClass::Unlimbo -- Removes unit from stasis. * 949 * * 950 * This routine will place a unit into the game and out of its limbo * 951 * state. This occurs whenever a unit is unloaded from a transport. * 952 * * 953 * INPUT: coord -- The coordinate to make the unit appear. * 954 * * 955 * dir -- The initial facing to impart upon the unit. * 956 * * 957 * OUTPUT: bool; Was the unit unlimboed successfully? If the desired * 958 * coordinate is illegal, then this might very well return * 959 * false. * 960 * * 961 * WARNINGS: none * 962 * * 963 * HISTORY: * 964 * 05/22/1994 JLB : Created. * 965 *=============================================================================================*/ 966 bool UnitClass::Unlimbo(COORDINATE coord, DirType dir) 967 { 968 assert(Units.ID(this) == ID); 969 assert(IsActive); 970 971 /* 972 ** All units must start out facing one of the 8 major directions. 973 */ 974 dir = Facing_Dir(Dir_Facing(dir)); 975 if (DriveClass::Unlimbo(coord, dir)) { 976 977 SecondaryFacing = dir; 978 /* 979 ** Ensure that the owning house knows about the 980 ** new object. 981 */ 982 House->UScan |= (1L << Class->Type); 983 House->ActiveUScan |= (1L << Class->Type); 984 985 /* 986 ** If it starts off the edge of the map, then it already starts cloaked. 987 */ 988 if (IsCloakable && !IsLocked) Cloak = CLOAKED; 989 990 /* 991 ** Units default to no special animation. 992 */ 993 Set_Rate(0); 994 Set_Stage(0); 995 996 return(true); 997 } 998 return(false); 999 } 1000 1001 1002 /*********************************************************************************************** 1003 * UnitClass::Take_Damage -- Inflicts damage points on a unit. * 1004 * * 1005 * This routine will inflict the specified number of damage points on * 1006 * the given unit. If the unit is destroyed, then this routine will * 1007 * remove the unit cleanly from the game. The return value indicates * 1008 * whether the unit was destroyed. This will allow appropriate death * 1009 * animation or whatever. * 1010 * * 1011 * INPUT: damage-- The number of damage points to inflict. * 1012 * * 1013 * distance -- The distance from the damage center point to the object's center point.* 1014 * * 1015 * warhead--The type of damage to inflict. * 1016 * * 1017 * source -- Who is responsible for this damage? * 1018 * * 1019 * OUTPUT: Returns the result of the damage process. This can range from RESULT_NONE up to * 1020 * RESULT_DESTROYED. * 1021 * * 1022 * WARNINGS: none * 1023 * * 1024 * HISTORY: * 1025 * 05/30/1991 JLB : Created. * 1026 * 07/12/1991 JLB : Script initiated by unit destruction. * 1027 * 04/15/1994 JLB : Converted to member function. * 1028 * 04/16/1994 JLB : Warhead modifier. * 1029 * 06/03/1994 JLB : Added the source of the damage target value. * 1030 * 06/20/1994 JLB : Source is a base class pointer. * 1031 * 11/22/1994 JLB : Shares base damage handler for techno objects. * 1032 * 06/30/1995 JLB : Lasers do maximum damage against gunboat. * 1033 * 08/16/1995 JLB : Harvester crushing doesn't occur on early missions. * 1034 *=============================================================================================*/ 1035 ResultType UnitClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source, bool forced) 1036 { 1037 assert(Units.ID(this) == ID); 1038 assert(IsActive); 1039 1040 ResultType res = RESULT_NONE; 1041 1042 /* 1043 ** Remember if this object was selected. If it was and it gets destroyed and it has 1044 ** passengers that pop out, then the passengers will inherit the select state. 1045 */ 1046 //bool select = (IsSelected && House->IsPlayerControl); 1047 bool select = (Is_Selected_By_Player() );//&& House->IsPlayerControl); 1048 1049 /* 1050 ** In order for a this to be damaged, it must either be a unit 1051 ** with a crew or a sandworm. 1052 */ 1053 res = DriveClass::Take_Damage(damage, distance, warhead, source, forced); 1054 1055 if (res == RESULT_DESTROYED) { 1056 Death_Announcement(source); 1057 Shroud_Regen(); // remove the shroud if it's a gap generator 1058 if (Class->Explosion != ANIM_NONE) { 1059 AnimType anim = Class->Explosion; 1060 1061 /* 1062 ** SSM launchers will really explode big if they are carrying 1063 ** missiles at the time of the explosion. 1064 */ 1065 if (*this == UNIT_V2_LAUNCHER && Ammo) { 1066 anim = ANIM_NAPALM3; 1067 } 1068 1069 new AnimClass(anim, Coord); 1070 1071 /* 1072 ** Harvesters explode with a force equal to the amount of 1073 ** Tiberium they are carrying. 1074 */ 1075 if (Tiberium > 0 && Rule.IsExplosiveHarvester) { 1076 Wide_Area_Damage(Coord, CELL_LEPTON_W + CELL_LEPTON_W/2, Credit_Load()+Class->MaxStrength, this, WARHEAD_HE); 1077 } 1078 1079 /* 1080 ** Very strong units that have an explosion will also rock the 1081 ** screen when they are destroyed. 1082 */ 1083 if (Class->MaxStrength > 400) { 1084 Shake_The_Screen(3, Owner()); 1085 if (source && Owner() != source->Owner()) { 1086 Shake_The_Screen(3, source->Owner()); 1087 } 1088 } 1089 } 1090 1091 /* 1092 ** Possibly have the crew member run away. 1093 */ 1094 CELL cell = Coord_Cell(Center_Coord()); 1095 Mark(MARK_UP); 1096 1097 if (Class->IsCrew && Class->Max_Passengers() == 0) { 1098 if (Percent_Chance(50)) { 1099 InfantryClass * i = 0; 1100 1101 if (Class->PrimaryWeapon == NULL) { 1102 i = new InfantryClass(INFANTRY_C1, House->Class->House); 1103 if (i != NULL) i->IsTechnician = true; 1104 } else { 1105 i = new InfantryClass(INFANTRY_E1, House->Class->House); 1106 } 1107 if (i != NULL) { 1108 if (i->Unlimbo(Coord, DIR_N)) { 1109 i->Strength = Random_Pick(5, (int)i->Class->MaxStrength/2); 1110 i->Scatter(0, true); 1111 if (!House->IsHuman) { 1112 i->Assign_Mission(MISSION_HUNT); 1113 } else { 1114 i->Assign_Mission(MISSION_GUARD); 1115 } 1116 if (select) i->Select(); 1117 } else { 1118 delete i; 1119 } 1120 } 1121 } 1122 } else { 1123 while (Is_Something_Attached()) { 1124 FootClass * object = Detach_Object(); 1125 1126 if (object == NULL) break; // How can this happen? 1127 1128 /* 1129 ** Only infantry can run from a destroyed vehicle. Even then, it is not a sure 1130 ** thing. 1131 */ 1132 if (object->Is_Infantry() && object->Unlimbo(Coord, DIR_N)) { 1133 object->Look(false); 1134 object->Scatter(0, true); 1135 if (select) object->Select(); 1136 } else { 1137 object->Record_The_Kill(source); 1138 delete object; 1139 } 1140 } 1141 } 1142 1143 /* 1144 ** If this is a truck, there is a possibility that a crate will drop out 1145 ** if the scenario so indicates and there is room. 1146 */ 1147 if (Scen.IsTruckCrate && *this == UNIT_TRUCK) { 1148 cell = Nearby_Location(); 1149 if (cell != 0) { 1150 new OverlayClass(OVERLAY_WOOD_CRATE, cell); 1151 } 1152 } 1153 1154 if (*this == UNIT_MCV) { 1155 if (House) { 1156 House->Check_Pertinent_Structures(); 1157 } 1158 } 1159 1160 /* 1161 ** Finally, delete the vehicle. 1162 */ 1163 delete this; 1164 1165 } else { 1166 1167 /* 1168 ** When damaged and below half strength, start smoking if 1169 ** it isn't already smoking. 1170 */ 1171 if (Health_Ratio() <= Rule.ConditionYellow && !IsAnimAttached) { 1172 #ifdef FIXIT_ANTS 1173 if (*this != UNIT_ANT1 && *this != UNIT_ANT2 && *this != UNIT_ANT3) { 1174 #endif 1175 AnimClass * anim = new AnimClass(ANIM_SMOKE_M, Coord_Add(Coord, XYP_Coord(0, -8))); 1176 if (anim) anim->Attach_To(this); 1177 #ifdef FIXIT_ANTS 1178 } 1179 #endif 1180 } 1181 1182 /* 1183 ** Try to crush anyone that fires on this unit if possible. The harvester 1184 ** typically is the only one that will qualify here. 1185 */ 1186 if (!Team.Is_Valid() && source != NULL && !IsTethered && !House->Is_Ally(source) && (!House->IsHuman || Rule.IsAutoCrush)) { 1187 1188 /* 1189 ** Try to crush the attacker if it can be crushed by this unit and this unit is 1190 ** not equipped with a flame type weapon. If this unit has a weapon and the target 1191 ** is not very close, then fire on it instead. In easy mode, they never run over the 1192 ** player. In hard mode, they always do. In normal mode, they only overrun past 1193 ** mission #8. 1194 */ 1195 if (Should_Crush_It(source)) { 1196 Assign_Destination(source->As_Target()); 1197 Assign_Mission(MISSION_MOVE); 1198 } else { 1199 1200 /* 1201 ** Try to return to base if possible. 1202 */ 1203 if (*this == UNIT_HARVESTER && Pip_Count() && Health_Ratio() <= Rule.ConditionYellow) { 1204 1205 /* 1206 ** Find nearby refinery and head to it? 1207 */ 1208 BuildingClass * building = Find_Docking_Bay(STRUCT_REFINERY, false); 1209 1210 /* 1211 ** Since the refinery said it was ok to load, establish radio 1212 ** contact with the refinery and then await docking orders. 1213 */ 1214 if (building != NULL && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { 1215 Assign_Mission(MISSION_ENTER); 1216 } 1217 } 1218 } 1219 } 1220 1221 /* 1222 ** Computer controlled harvester will radio for help if they are attacked. 1223 */ 1224 if (*this == UNIT_HARVESTER && !House->IsHuman && source) { 1225 Base_Is_Attacked(source); 1226 } 1227 } 1228 return(res); 1229 } 1230 1231 1232 /*********************************************************************************************** 1233 * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possible* 1234 * * 1235 * This routine intercepts the active click operation. It check to see if this is a self * 1236 * deployment request (MCV's have this ability). If it is, then the object is initiated * 1237 * to self deploy. In the other cases, it passes the operation down to the lower * 1238 * classes for processing. * 1239 * * 1240 * INPUT: action -- The action requested of the unit. * 1241 * * 1242 * object -- The object that the mouse pointer is over. * 1243 * * 1244 * OUTPUT: none * 1245 * * 1246 * WARNINGS: none * 1247 * * 1248 * HISTORY: * 1249 * 03/10/1995 JLB : Created. * 1250 *=============================================================================================*/ 1251 void UnitClass::Active_Click_With(ActionType action, ObjectClass * object) 1252 { 1253 assert(Units.ID(this) == ID); 1254 assert(IsActive); 1255 1256 if (action != What_Action(object)) { 1257 action = What_Action(object); 1258 switch (action) { 1259 case ACTION_SABOTAGE: 1260 case ACTION_CAPTURE: 1261 action = ACTION_ATTACK; 1262 break; 1263 1264 case ACTION_ENTER: 1265 action = ACTION_MOVE; 1266 break; 1267 1268 default: 1269 break; 1270 } 1271 } 1272 1273 /* 1274 ** Short circuit out if trying to tell a unit to "nomove" to itself. This bypass of the 1275 ** normal active click with logic prevents any disturbance to the vehicle's state. Without 1276 ** this bypass, a unit on a repair bay would stop repairing because it would break radio 1277 ** contact. 1278 */ 1279 if (object == this && action == ACTION_NOMOVE) { 1280 return; 1281 } 1282 #ifdef FIXIT_CSII // checked - ajw 9/28/98 1283 if (*this == UNIT_MAD && (IsDumping || Gold)) { 1284 } else { 1285 DriveClass::Active_Click_With(action, object); 1286 } 1287 #else 1288 DriveClass::Active_Click_With(action, object); 1289 #endif 1290 } 1291 1292 1293 /*********************************************************************************************** 1294 * UnitClass::Active_Click_With -- Performs specified action on specified cell. * 1295 * * 1296 * This routine is called when the mouse has been clicked over a cell and this unit must * 1297 * now respond. Notice that this is merely a placeholder function that exists because there * 1298 * is another function of the same name that needs to be overloaded. C++ has scoping * 1299 * restrictions when there are two identically named functions that are overridden in * 1300 * different classes -- it handles it badly, hence the existence of this routine. * 1301 * * 1302 * INPUT: action -- The action to perform on the cell specified. * 1303 * * 1304 * cell -- The cell that the action is to be performed on. * 1305 * * 1306 * OUTPUT: none * 1307 * * 1308 * WARNINGS: none * 1309 * * 1310 * HISTORY: * 1311 * 09/21/1995 JLB : Created. * 1312 *=============================================================================================*/ 1313 void UnitClass::Active_Click_With(ActionType action, CELL cell) 1314 { 1315 assert(Units.ID(this) == ID); 1316 assert(IsActive); 1317 1318 #ifdef FIXIT_CSII // checked - ajw 9/28/98 1319 if (*this == UNIT_MAD && (IsDumping || Gold)) { 1320 } else { 1321 DriveClass::Active_Click_With(action, cell); 1322 } 1323 #else 1324 DriveClass::Active_Click_With(action, cell); 1325 #endif 1326 } 1327 1328 1329 void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) 1330 { 1331 assert(Units.ID(this) == ID); 1332 assert(IsActive); 1333 1334 if (mission == MISSION_HARVEST) { 1335 ArchiveTarget = TARGET_NONE; 1336 } else if (mission == MISSION_ENTER) { 1337 BuildingClass* building = As_Building(destination); 1338 if (building != NULL && *building == STRUCT_REFINERY && building->In_Radio_Contact()) { 1339 building->Transmit_Message(RADIO_OVER_OUT); 1340 } 1341 } 1342 DriveClass::Player_Assign_Mission(mission, target, destination); 1343 } 1344 1345 1346 /*********************************************************************************************** 1347 * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * 1348 * * 1349 * This routine is called when the unit completes one mission but does not have a clear * 1350 * follow up mission to perform. In such a case, the unit should enter a default idle * 1351 * state. This idle state varies depending on what the current internal computer * 1352 * settings of the unit is as well as what kind of unit it is. * 1353 * * 1354 * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * 1355 * or is initially placed on the map? * 1356 * * 1357 * OUTPUT: none * 1358 * * 1359 * WARNINGS: none * 1360 * * 1361 * HISTORY: * 1362 * 05/31/1994 JLB : Created. * 1363 * 06/03/1994 JLB : Fixed to handle non-combat vehicles. * 1364 * 06/18/1995 JLB : Allows a harvester to stop harvesting. * 1365 *=============================================================================================*/ 1366 void UnitClass::Enter_Idle_Mode(bool initial) 1367 { 1368 assert(Units.ID(this) == ID); 1369 assert(IsActive); 1370 1371 MissionType order = MISSION_GUARD; 1372 1373 if (IsToScatter) { 1374 IsToScatter = false; 1375 Scatter(0, true); 1376 } 1377 1378 /* 1379 ** A movement mission without a NavCom would be pointless to have a radio contact since 1380 ** no radio coordination occurs on a just a simple movement mission. 1381 */ 1382 if (Mission == MISSION_MOVE && !Target_Legal(NavCom)) { 1383 Transmit_Message(RADIO_OVER_OUT); 1384 } 1385 1386 Handle_Navigation_List(); 1387 if (Target_Legal(NavCom)) { 1388 order = MISSION_MOVE; 1389 } else { 1390 1391 if (!Is_Weapon_Equipped()) { 1392 if (Class->IsToHarvest) { 1393 if (!In_Radio_Contact() && Mission != MISSION_HARVEST && MissionQueue != MISSION_HARVEST) { 1394 if (initial || !House->IsHuman || Map[Coord].Land_Type() == LAND_TIBERIUM) { 1395 order = MISSION_HARVEST; 1396 } else { 1397 order = MISSION_GUARD; 1398 } 1399 Assign_Target(TARGET_NONE); 1400 Assign_Destination(TARGET_NONE); 1401 } else { 1402 return; 1403 } 1404 } else { 1405 if (IsALoaner && Class->Max_Passengers() > 0 && Is_Something_Attached() && !Team.Is_Valid()) { 1406 order = MISSION_UNLOAD; 1407 } else { 1408 #ifdef FIXIT_CSII // checked - ajw 9/28/98 1409 if(*this == UNIT_MAD && Mission == MISSION_UNLOAD) { 1410 order = MISSION_UNLOAD; 1411 } else { 1412 #endif 1413 order = MISSION_GUARD; 1414 Assign_Target(TARGET_NONE); 1415 Assign_Destination(TARGET_NONE); 1416 } 1417 } 1418 #ifdef FIXIT_CSII // checked - ajw 9/28/98 1419 } 1420 #endif 1421 } else { 1422 1423 if (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA || MissionControl[Mission].IsParalyzed || MissionControl[Mission].IsZombie) { 1424 return; 1425 } 1426 1427 if (House->IQ < Rule.IQGuardArea || Team.Is_Valid()) { 1428 order = MISSION_GUARD; 1429 } else { 1430 order = MISSION_GUARD_AREA; 1431 } 1432 } 1433 } 1434 Assign_Mission(order); 1435 } 1436 1437 1438 /*********************************************************************************************** 1439 * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * 1440 * * 1441 * This routine is used by the MCV to find a clear spot to deploy. If a clear spot * 1442 * is found, then the MCV will assign that location to its navigation computer. This only * 1443 * occurs if the MCV isn't already heading toward a spot. * 1444 * * 1445 * INPUT: none * 1446 * * 1447 * OUTPUT: bool; Is the located at a spot where it can deploy? * 1448 * * 1449 * WARNINGS: none * 1450 * * 1451 * HISTORY: * 1452 * 06/27/1994 JLB : Created. * 1453 *=============================================================================================*/ 1454 bool UnitClass::Goto_Clear_Spot(void) 1455 { 1456 assert(Units.ID(this) == ID); 1457 assert(IsActive); 1458 1459 Mark(MARK_UP); 1460 if (!Target_Legal(NavCom) && BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Adjacent_Cell(Coord_Cell(Center_Coord()), FACING_NW))) { 1461 Mark(MARK_DOWN); 1462 return(true); 1463 } 1464 1465 if (!Target_Legal(NavCom)) { 1466 /* 1467 ** This scan table is skewed to north scanning only. This should 1468 ** probably be converted to a more flexible method. 1469 */ 1470 static int _offsets[] = { 1471 -MAP_CELL_W*1, 1472 -MAP_CELL_W*2, 1473 -(MAP_CELL_W*2)+1, 1474 -(MAP_CELL_W*2)-1, 1475 -MAP_CELL_W*3, 1476 -(MAP_CELL_W*3)+1, 1477 -(MAP_CELL_W*3)-1, 1478 -(MAP_CELL_W*3)+2, 1479 -(MAP_CELL_W*3)-2, 1480 -MAP_CELL_W*4, 1481 -(MAP_CELL_W*4)+1, 1482 -(MAP_CELL_W*4)-1, 1483 -(MAP_CELL_W*4)+2, 1484 -(MAP_CELL_W*4)-2, 1485 //BG: Added south scanning 1486 MAP_CELL_W*1, 1487 MAP_CELL_W*2, 1488 (MAP_CELL_W*2)+1, 1489 (MAP_CELL_W*2)-1, 1490 MAP_CELL_W*3, 1491 (MAP_CELL_W*3)+1, 1492 (MAP_CELL_W*3)-1, 1493 (MAP_CELL_W*3)+2, 1494 (MAP_CELL_W*3)-2, 1495 MAP_CELL_W*4, 1496 (MAP_CELL_W*4)+1, 1497 (MAP_CELL_W*4)-1, 1498 (MAP_CELL_W*4)+2, 1499 (MAP_CELL_W*4)-2, 1500 1501 //BG: Added some token east/west scanning 1502 -1,-2,-3,-4, 1503 1504 1, 2, 3, 4, 1505 0 1506 }; 1507 int * ptr; 1508 1509 ptr = &_offsets[0]; 1510 while (*ptr) { 1511 CELL cell = Coord_Cell(Coord)+*ptr++; 1512 CELL check_cell = Adjacent_Cell(cell, FACING_NW); 1513 if (BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(check_cell)) { 1514 Assign_Destination(::As_Target(cell)); 1515 break; 1516 } 1517 } 1518 } 1519 Mark(MARK_DOWN); 1520 1521 /* 1522 ** If we couldn't find a destination to go to, let's try random movement 1523 ** to see if that brings us to a better spot. 1524 */ 1525 if(!Target_Legal(NavCom) && !House->IsHuman) { 1526 Scatter(0); 1527 } 1528 1529 return(false); 1530 } 1531 1532 1533 /*********************************************************************************************** 1534 * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * 1535 * * 1536 * Certain units have the ability to deploy into a building. When this routine is called * 1537 * for one of those units, it will attempt to deploy at its current location. If the unit * 1538 * is in motion to a destination or it isn't one of the special units that can deploy or * 1539 * it isn't allowed to deploy at this location for some reason it won't deploy. In all * 1540 * other cases, it will begin to deploy and once it begins only a player abort action will * 1541 * stop it. * 1542 * * 1543 * INPUT: none * 1544 * * 1545 * OUTPUT: bool; Was deployment begun? * 1546 * * 1547 * WARNINGS: none * 1548 * * 1549 * HISTORY: * 1550 * 06/18/1994 JLB : Created. * 1551 *=============================================================================================*/ 1552 bool UnitClass::Try_To_Deploy(void) 1553 { 1554 assert(Units.ID(this) == ID); 1555 assert(IsActive); 1556 1557 if (!Target_Legal(NavCom) && !IsRotating) { 1558 if (*this == UNIT_MCV) { 1559 1560 /* 1561 ** Determine if it is legal to deploy at this location. If not, tell the 1562 ** player. 1563 */ 1564 Mark(MARK_UP); 1565 CELL cell = Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)); 1566 if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(cell)) { 1567 if (PlayerPtr == House) { 1568 Speak(VOX_DEPLOY); 1569 } 1570 if (!House->IsHuman) { 1571 BuildingTypeClass::As_Reference(STRUCT_CONST).Flush_For_Placement(cell, House); 1572 } 1573 Mark(MARK_DOWN); 1574 IsDeploying = false; 1575 return(false); 1576 } 1577 Mark(MARK_DOWN); 1578 1579 /* 1580 ** If the unit is not facing the correct direction, then start it rotating 1581 ** toward the right facing, but still flag it as if it had deployed. This is 1582 ** because it will deploy as soon as it reaches the correct facing. 1583 */ 1584 if (PrimaryFacing.Current() != DIR_SW) { 1585 Do_Turn(DIR_SW); 1586 // PrimaryFacing.Set_Desired(DIR_SW); 1587 IsDeploying = true; 1588 return(true); 1589 } 1590 1591 /* 1592 ** Since the unit is already facing the correct direction, actually do the 1593 ** deploy logic. If for some reason this cannot occur, then don't delete the 1594 ** unit, just mark it as not deploying. 1595 */ 1596 Mark(MARK_UP); 1597 BuildingClass * building = new BuildingClass(STRUCT_CONST, House->Class->House); 1598 if (building != NULL) { 1599 if (building->Unlimbo(Adjacent_Cell(Coord, FACING_NW))) { 1600 1601 /* 1602 ** Play the buildup sound for the player if this is the players 1603 ** MCV. 1604 */ 1605 if (building->House == PlayerPtr) { 1606 Sound_Effect(VOC_PLACE_BUILDING_DOWN, Center_Coord()); 1607 } else { 1608 building->IsToRebuild = true; 1609 building->IsToRepair = true; 1610 } 1611 1612 /* 1613 ** Always reveal the construction yard to the player that owned the 1614 ** mobile construction vehicle. 1615 */ 1616 building->Revealed(House); 1617 1618 /* 1619 ** When the MCV deploys, always consider production to have started 1620 ** for the owning house. This ensures that in multiplay, computer 1621 ** opponents will begin construction as soon as they start their 1622 ** base. 1623 */ 1624 House->IsStarted = true; 1625 1626 /* 1627 ** Force the newly placed construction yard to be in the same strength 1628 ** ratio as the MCV that deployed into it. 1629 */ 1630 building->Strength = Health_Ratio() * (int)building->Class->MaxStrength; 1631 1632 /* 1633 ** Force the MCV to drop any flag it was carrying. This will also set 1634 ** the owner house's flag home cell (since the house's FlagHome is 1635 ** presumably 0 at this point). 1636 */ 1637 Stun(); 1638 1639 /* 1640 ** If this MCV was teleported here, clear the gray flag so 1641 ** the screen will go back to color. 1642 */ 1643 if (IsMoebius && !Scen.IsFadingColor) { 1644 Scen.IsFadingBW = false; 1645 Scen.IsFadingColor = true; 1646 Scen.FadeTimer = GRAYFADETIME; 1647 } 1648 delete this; 1649 return(true); 1650 } else { 1651 1652 /* 1653 ** Could not deploy the construction yard at this location! Just revert 1654 ** back to normal "just sitting there" mode and await further instructions. 1655 */ 1656 delete building; 1657 } 1658 } 1659 Mark(MARK_DOWN); 1660 IsDeploying = false; 1661 } 1662 } 1663 return(false); 1664 } 1665 1666 1667 /*********************************************************************************************** 1668 * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * 1669 * * 1670 * This routine will perform the operations necessary that occur when a unit is at the * 1671 * center of a cell. These operations could entail deploying into a construction yard, * 1672 * radioing a transport unit, and looking around for the enemy. * 1673 * * 1674 * INPUT: why -- Specifies the circumstances under which this routine was called. * 1675 * * 1676 * OUTPUT: none * 1677 * * 1678 * WARNINGS: none * 1679 * * 1680 * HISTORY: * 1681 * 06/18/1994 JLB : Created. * 1682 * 06/17/1995 JLB : Handles case when building says "NO!" * 1683 * 06/30/1995 JLB : Gunboats head back and forth now. * 1684 *=============================================================================================*/ 1685 void UnitClass::Per_Cell_Process(PCPType why) 1686 { 1687 assert(Units.ID(this) == ID); 1688 assert(IsActive); 1689 1690 CELL cell = Coord_Cell(Coord); 1691 HousesType house; 1692 1693 if (why == PCP_END || why == PCP_ROTATION) { 1694 /* 1695 ** Check to see if this is merely the end of a rotation for the MCV as it is 1696 ** preparing to deploy. In this case, it should begin its deploy process. 1697 */ 1698 if (IsDeploying) { 1699 Try_To_Deploy(); 1700 if (!IsActive) return; // Unit no longer exists -- bail. 1701 } 1702 } 1703 1704 BStart(BENCH_PCP); 1705 if (why == PCP_END) { 1706 /* 1707 ** If this is a unit that is driving onto a building then the unit must enter 1708 ** the building as the final step. 1709 */ 1710 TechnoClass * whom = Contact_With_Whom(); 1711 if (IsTethered && whom != NULL) { 1712 if (whom->What_Am_I() == RTTI_BUILDING && Mission == MISSION_ENTER) { 1713 if (whom == Map[CELL(cell-MAP_CELL_W)].Cell_Building()) { 1714 switch (Transmit_Message(RADIO_IM_IN, whom)) { 1715 case RADIO_ROGER: 1716 break; 1717 1718 case RADIO_ATTACH: 1719 break; 1720 1721 default: 1722 Scatter(0, true); 1723 break; 1724 } 1725 } 1726 } 1727 } 1728 1729 /* 1730 ** Unit entering a transport vehicle will break radio contact 1731 ** and attach itself to the transporter. 1732 */ 1733 TechnoClass * techno = Contact_With_Whom(); 1734 if (Mission == MISSION_ENTER && techno && Coord_Cell(Coord) == Coord_Cell(techno->Coord) && techno == As_Techno(NavCom)) { 1735 if (Transmit_Message(RADIO_IM_IN) == RADIO_ATTACH) { 1736 Limbo(); 1737 techno->Attach(this); 1738 } 1739 BEnd(BENCH_PCP); 1740 return; 1741 } 1742 1743 /* 1744 ** When breaking away from a transport object or building, possibly 1745 ** scatter or otherwise begin normal unit operations. 1746 */ 1747 if (IsTethered && (Mission != MISSION_ENTER || 1748 (As_Techno(NavCom) != NULL && Contact_With_Whom() != As_Techno(NavCom)) 1749 ) && 1750 Mission != MISSION_UNLOAD) { 1751 1752 /* 1753 ** Special hack check to make sure that even though it has moved one 1754 ** cell, if it is still on the building (e.g., service depot), have 1755 ** it scatter again. 1756 */ 1757 if (Map[Coord].Cell_Building() != NULL && !Target_Legal(NavCom)) { 1758 Scatter(0, true, true); 1759 } else { 1760 TechnoClass * contact = Contact_With_Whom(); 1761 if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) { 1762 if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) != STRUCT_REPAIR) { 1763 Assign_Mission(MISSION_HARVEST); 1764 } else if (!Target_Legal(NavCom)) { 1765 Scatter(0, true); 1766 } else { 1767 1768 /* 1769 ** Special case hack to allow automatic transition to loading 1770 ** onto a transport (or other situation) if the destination 1771 ** so indicates. 1772 */ 1773 TechnoClass * techno = As_Techno(NavCom); 1774 if (techno != NULL) { 1775 Transmit_Message(RADIO_DOCKING, techno); 1776 } 1777 } 1778 } else { 1779 if (*this == UNIT_HARVESTER) { 1780 if (Target_Legal(ArchiveTarget)) { 1781 Assign_Mission(MISSION_HARVEST); 1782 Assign_Destination(ArchiveTarget); 1783 ArchiveTarget = TARGET_NONE; 1784 } else { 1785 1786 /* 1787 ** Since there is no place to go, move away to clear 1788 ** the pad for another harvester. 1789 */ 1790 if (!Target_Legal(NavCom)) { 1791 Scatter(0, true); 1792 } 1793 } 1794 } 1795 } 1796 } 1797 } 1798 1799 /* 1800 ** If this is a loaner unit and is is off the edge of the 1801 ** map, then it gets eliminated. That is, unless it is carrying cargo. This means that 1802 ** it is probably carrying an incoming reinforcement and it should not be eliminated. 1803 */ 1804 if (Edge_Of_World_AI()) { 1805 BEnd(BENCH_PCP); 1806 return; 1807 } 1808 1809 /* 1810 ** The unit performs looking around at this time. If the 1811 ** unit moved further than one square during the last track 1812 ** move, don't do an incremental look. Do a full look around 1813 ** instead. 1814 */ 1815 if (IsPlanningToLook) { 1816 IsPlanningToLook = false; 1817 Look(false); 1818 } else { 1819 Look(true); 1820 } 1821 1822 /* 1823 ** If this is a mobile gap generator, restore the shroud where appropriate 1824 ** and re-shroud around us. 1825 */ 1826 if (Class->IsGapper && !House->IsPlayerControl) { 1827 Shroud_Regen(); 1828 } 1829 1830 /* 1831 ** Act on new orders if the unit is at a good position to do so. 1832 */ 1833 if (!IsDumping) { 1834 Commence(); 1835 } 1836 1837 /* 1838 ** Certain units require some setup time after they come to a halt. 1839 */ 1840 if (!Target_Legal(NavCom) && Path[0] == FACING_NONE) { 1841 if (Class->IsNoFireWhileMoving) { 1842 Arm = Rearm_Delay(true)/4; 1843 } 1844 } 1845 1846 /* 1847 ** If there is a house flag here, then this unit just might pick it up. 1848 */ 1849 if (Flagged == HOUSE_NONE) { 1850 1851 if (Map[cell].IsFlagged && !House->Is_Ally(Map[cell].Owner)) { 1852 HouseClass::As_Pointer(Map[cell].Owner)->Flag_Attach(this); 1853 } 1854 } 1855 1856 /* 1857 ** If this is the unit's own flag-home-cell and the unit is carrying 1858 ** a flag, destroy the house of the flag the unit is carrying. 1859 */ 1860 if (Flagged != HOUSE_NONE) { 1861 1862 /* 1863 ** If this vehicle is carrying your flag, then it will reveal the 1864 ** map for you as well as itself. This gives you and opportunity to 1865 ** attack the unit. 1866 */ 1867 if (!IsOwnedByPlayer && Flagged == PlayerPtr->Class->House) { 1868 Map.Sight_From(Coord_Cell(Coord), Class->SightRange, House, true); 1869 } 1870 1871 /* 1872 ** If the flag reaches the home cell for the player, then the flag's 1873 ** owner will be destroyed. 1874 */ 1875 if (cell == HouseClass::As_Pointer(Owner())->FlagHome) { 1876 house = Flagged; // Flag_Remove will clear 'Flagged', so save it 1877 HouseClass::As_Pointer(house)->Flag_Remove(As_Target(), true); 1878 HouseClass::As_Pointer(house)->Flag_To_Die(); 1879 } 1880 } 1881 1882 /* 1883 ** If entering a cell with a land mine in it, blow up the mine. 1884 */ 1885 BuildingClass * bldng = Map[cell].Cell_Building(); 1886 if (bldng != NULL && (*bldng == STRUCT_AVMINE || *bldng == STRUCT_APMINE) && !bldng->House->Is_Ally(this)) { 1887 1888 /* 1889 ** Special case: if it's a land mine deployer, and it ran over the 1890 ** type of mine it deploys (only possible if it just dropped it 1891 ** down) then ignore the mine. 1892 */ 1893 if (*this != UNIT_MINELAYER || bldng->House != House) { 1894 1895 COORDINATE blcoord = bldng->Center_Coord(); 1896 1897 new AnimClass(ANIM_MINE_EXP1, blcoord); 1898 // new AnimClass(Combat_Anim(Rule.AVMineDamage, WARHEAD_HE, Map[cell].Land_Type()), blcoord); 1899 1900 /* 1901 ** Vehicles blow up both mines, but they only take significant damage from AV mines. 1902 */ 1903 if (*bldng == STRUCT_AVMINE) { 1904 int damage = Rule.AVMineDamage; 1905 Take_Damage(damage, 0, WARHEAD_HE); 1906 } else { 1907 int damage = 10; 1908 Take_Damage(damage, 0, WARHEAD_HE); 1909 } 1910 delete bldng; 1911 if (!IsActive) { 1912 BEnd(BENCH_PCP); 1913 return; 1914 } 1915 } 1916 } 1917 1918 /* 1919 ** If after all is said and done, the unit finishes its move on an impassable cell, then 1920 ** it must presume that it is in the case of a unit driving onto a bridge that blows up 1921 ** before the unit completes it's move. In such a case the unit should have been destroyed 1922 ** anyway, so blow it up now. 1923 */ 1924 LandType land = Map[Coord].Land_Type(); 1925 if (!IsDriving && IsMovingOntoBridge && (land == LAND_ROCK || land == LAND_WATER || land == LAND_RIVER)) { 1926 new AnimClass(Combat_Anim(Strength, WARHEAD_AP, land), Coord); 1927 int damage = Strength; 1928 Take_Damage(damage, 0, WARHEAD_AP, NULL, true); 1929 return; 1930 } 1931 } 1932 1933 /* 1934 ** Destroy any crushable wall that is driven over by a tracked vehicle. 1935 */ 1936 CellClass * cellptr = &Map[cell]; 1937 if (Class->IsCrusher && cellptr->Overlay != OVERLAY_NONE) { 1938 // if (Class->Speed == SPEED_TRACK && cellptr->Overlay != OVERLAY_NONE) { 1939 OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); 1940 1941 if (optr->IsCrushable) { 1942 if (optr->Type == OVERLAY_SANDBAG_WALL) { 1943 Sound_Effect(VOC_SANDBAG, Center_Coord()); 1944 } else { 1945 Sound_Effect(VOC_WALLKILL2, Center_Coord()); 1946 } 1947 cellptr->Reduce_Wall(-1); 1948 } 1949 } 1950 1951 /* 1952 ** Check to see if crushing of any unfortunate infantry is warranted. 1953 */ 1954 Overrun_Square(Coord_Cell(Coord), false); 1955 1956 if (!IsActive) { 1957 BEnd(BENCH_PCP); 1958 return; 1959 } 1960 DriveClass::Per_Cell_Process(why); 1961 BEnd(BENCH_PCP); 1962 } 1963 1964 1965 /*********************************************************************************************** 1966 * UnitClass::Shape_Number -- Fetch the shape number to use for this unit. * 1967 * * 1968 * This routine will calculate the shape number for this unit. The shape number is used * 1969 * for the body of the unit. * 1970 * * 1971 * INPUT: none * 1972 * * 1973 * OUTPUT: Returns with the shape number to be used for this unit. * 1974 * * 1975 * WARNINGS: none * 1976 * * 1977 * HISTORY: * 1978 * 07/29/1996 JLB : Created. * 1979 *=============================================================================================*/ 1980 int UnitClass::Shape_Number(void) const 1981 { 1982 assert(Units.ID(this) == ID); 1983 assert(IsActive); 1984 1985 int shapenum; // Working shape number. 1986 int facing = Dir_To_32(PrimaryFacing); 1987 int tfacing = Dir_To_32(SecondaryFacing); 1988 DirType rotation = DIR_N; 1989 1990 #ifdef FIXIT_ANTS 1991 /* 1992 ** This handles the ant case. 1993 */ 1994 if (Class->Rotation == 8) { 1995 1996 /* 1997 ** The starting frame is based on the facing of the unit. 1998 */ 1999 shapenum = ((UnitClass::BodyShape[facing]+2)/4) & 0x07; 2000 2001 /* 2002 ** If the unit is driving, then it has an animation adjustment to the frame number. 2003 */ 2004 if (IsDriving) { 2005 shapenum = 8 + (shapenum * 8) + ((::Frame+ID)/2) % 8; 2006 } else { 2007 2008 /* 2009 ** If in combat, then do combat anims. 2010 */ 2011 if (Arm > 0) { 2012 shapenum = 8 + 64 + (shapenum * 4) + ((::Frame+ID)/2) % 4; 2013 } 2014 } 2015 } else { 2016 #endif 2017 2018 /* 2019 ** Fetch the harvesting animation stage as appropriate. 2020 */ 2021 if (IsHarvesting && !PrimaryFacing.Is_Rotating() && !NavCom && !IsDriving) { 2022 // static char _hstage[] = {0, 1, 2, 3, 4, 5, 6, 7, 0}; 2023 unsigned stage = Fetch_Stage(); 2024 if (stage >= ARRAY_SIZE(Class->Harvester_Load_List)) stage = ARRAY_SIZE(Class->Harvester_Load_List)-1; 2025 shapenum = 32 + (((UnitClass::BodyShape[facing]+2)/4)*Class->Harvester_Load_Count)+Class->Harvester_Load_List[stage]; 2026 } else { 2027 /* 2028 ** If the harvester's dumping a load of ore, show that animation 2029 */ 2030 if (IsDumping) { 2031 unsigned stage = Fetch_Stage(); 2032 #ifdef FIXIT_CSII // checked - ajw 9/28/98 2033 if (*this == UNIT_MAD) { 2034 if (stage >= 8) { 2035 stage = 7; 2036 } 2037 shapenum = 32 + stage + (UnitClass::BodyShape[facing]/4)*8; 2038 } else { 2039 if (stage >= ARRAY_SIZE(Class->Harvester_Dump_List)) stage = ARRAY_SIZE(Class->Harvester_Dump_List)-1; 2040 shapenum = Class->Harvester_Dump_List[stage]+96; 2041 } 2042 #else 2043 if (stage >= ARRAY_SIZE(Class->Harvester_Dump_List)) stage = ARRAY_SIZE(Class->Harvester_Dump_List)-1; 2044 shapenum = Class->Harvester_Dump_List[stage]+96; 2045 #endif 2046 } else { 2047 shapenum = UnitClass::BodyShape[facing]; 2048 2049 if (Class->IsAnimating) { 2050 shapenum = Fetch_Stage(); 2051 } 2052 2053 /* 2054 ** Door opening and closing animation must be handled carefully. There are only 2055 ** certain directions where this door animation will work. 2056 */ 2057 if (!Is_Door_Closed() && (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE)) { 2058 if (PrimaryFacing == DIR_NE) { 2059 shapenum = 32; 2060 } else { 2061 if (PrimaryFacing == DIR_NW) { 2062 shapenum = 35; 2063 } 2064 } 2065 shapenum += Door_Stage(); 2066 } 2067 } 2068 } 2069 2070 #ifdef FIXIT_ANTS 2071 } 2072 #endif 2073 2074 /* 2075 ** The body of the V2 launcher indicates whether it is loaded with a missile 2076 ** or not. 2077 */ 2078 if (*this == UNIT_V2_LAUNCHER) { 2079 if (Ammo == 0) shapenum += 32; 2080 } 2081 2082 return(shapenum); 2083 } 2084 2085 2086 /*********************************************************************************************** 2087 * UnitClass::Draw_It -- Draws a unit object. * 2088 * * 2089 * This routine is the one that actually draws a unit object. It displays the unit * 2090 * according to its current state flags and centered at the location specified. * 2091 * * 2092 * INPUT: x,y -- The X and Y coordinate of where to draw the unit. * 2093 * * 2094 * window -- The clipping window to use. * 2095 * * 2096 * OUTPUT: none * 2097 * * 2098 * WARNINGS: none * 2099 * * 2100 * HISTORY: * 2101 * 06/20/1994 JLB : Created. * 2102 * 06/27/1994 JLB : Takes a window parameter. * 2103 * 08/15/1994 JLB : Removed infantry support. * 2104 * 01/07/1995 JLB : Harvester animation support. * 2105 * 07/08/1995 JLB : Uses general purpose draw routine. * 2106 *=============================================================================================*/ 2107 void UnitClass::Draw_It(int x, int y, WindowNumberType window) const 2108 { 2109 assert(Units.ID(this) == ID); 2110 assert(IsActive); 2111 2112 int shapenum; // Working shape number. 2113 void const * shapefile; // Working shape file pointer. 2114 int facing = Dir_To_32(PrimaryFacing); 2115 int tfacing = Dir_To_32(SecondaryFacing); 2116 DirType rotation = DIR_N; 2117 int scale = 0x0100; 2118 2119 /* 2120 ** Verify the legality of the unit class. 2121 */ 2122 shapefile = Get_Image_Data(); 2123 if (shapefile == NULL) return; 2124 2125 /* 2126 ** If drawing of this unit is not explicitly prohibited, then proceed 2127 ** with the render process. 2128 */ 2129 const bool is_hidden = (Visual_Character() == VISUAL_HIDDEN) && (window != WINDOW_VIRTUAL); 2130 if (!is_hidden) { 2131 shapenum = Shape_Number(); 2132 2133 /* 2134 ** The artillery unit should have its entire body recoil when it fires. 2135 */ 2136 if (*this == UNIT_ARTY && IsInRecoilState) { 2137 Recoil_Adjust(PrimaryFacing.Current(), x, y); 2138 } 2139 2140 /* 2141 ** Actually perform the draw. Overlay an optional shimmer effect as necessary. 2142 */ 2143 Techno_Draw_Object(shapefile, shapenum, x, y, window, rotation, scale); 2144 2145 /* 2146 ** If there is a rotating radar dish, draw it now. 2147 */ 2148 if (Class->IsRadarEquipped) { 2149 if (*this == UNIT_MGG) { 2150 int x2 = x, y2 = y; 2151 shapenum = 32 + (Frame & 7); 2152 Class->Turret_Adjust(PrimaryFacing, x2, y2); 2153 Techno_Draw_Object(shapefile, shapenum, x2, y2, window); 2154 } else { 2155 //#ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 2156 // if (*this == UNIT_PHASE) { 2157 // shapenum = 38 + (Frame & 7); 2158 // } else { 2159 // shapenum = 32 + (Frame % 32); 2160 // } 2161 //#else 2162 shapenum = 32 + (Frame % 32); 2163 //#endif 2164 #ifdef FIXIT_CSII // checked - ajw 9/28/98 2165 if (*this == UNIT_TESLATANK) { 2166 Techno_Draw_Object(shapefile, shapenum, x, y, window); 2167 } else { 2168 Techno_Draw_Object(shapefile, shapenum, x, y-5, window); 2169 } 2170 #else 2171 Techno_Draw_Object(shapefile, shapenum, x, y-5, window); 2172 #endif 2173 } 2174 } 2175 2176 /* 2177 ** If there is a turret, then it must be rendered as well. This may include 2178 ** firing animation if required. 2179 */ 2180 if (/*!Class->IsChunkyShape &&*/ Class->IsTurretEquipped) { 2181 int xx = x; 2182 int yy = y; 2183 2184 /* 2185 ** Determine which turret shape to use. This depends on if there 2186 ** is any firing animation in progress. 2187 */ 2188 shapenum = TechnoClass::BodyShape[tfacing]+32; 2189 #ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 2190 if (*this == UNIT_PHASE) { 2191 shapenum += 6; 2192 } 2193 #endif 2194 /* 2195 ** A recoiling turret moves "backward" one pixel. 2196 */ 2197 if (IsInRecoilState) { 2198 Recoil_Adjust(SecondaryFacing, xx, yy); 2199 } 2200 2201 Class->Turret_Adjust(PrimaryFacing, xx, yy); 2202 2203 /* 2204 ** Actually perform the draw. Overlay an optional shimmer effect as necessary. 2205 */ 2206 Techno_Draw_Object(shapefile, shapenum, xx, yy, window); 2207 } 2208 } 2209 2210 /* 2211 ** If this unit is carrying the flag, then draw that on top of everything else. 2212 */ 2213 if (Flagged != HOUSE_NONE) { 2214 shapefile = MFCD::Retrieve("FLAGFLY.SHP"); 2215 int flag_x = x + (ICON_PIXEL_W / 2) - 2; 2216 int flag_y = y + (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); 2217 CC_Draw_Shape(this, "FLAGFLY", shapefile, Frame % 14, flag_x, flag_y, window, SHAPE_CENTER|SHAPE_FADING|SHAPE_GHOST, HouseClass::As_Pointer(Flagged)->Remap_Table(false, Class->Remap), Map.UnitShadow, DIR_N, 0x0100, Flagged); 2218 } 2219 2220 DriveClass::Draw_It(x, y, window); 2221 } 2222 2223 2224 /*********************************************************************************************** 2225 * UnitClass::Tiberium_Check -- Search for and head toward nearest available Tiberium patch. * 2226 * * 2227 * This routine is used to move a harvester to a place where it can load up with * 2228 * Tiberium. It will return true only if it can start harvesting. Otherwise, it sets * 2229 * the navigation computer toward the nearest Tiberium and lets the unit head there * 2230 * automatically. * 2231 * * 2232 * INPUT: center -- Reference to the center of the radius scan. * 2233 * * 2234 * x,y -- Relative offset from the center cell to perform the check upon. * 2235 * * 2236 * OUTPUT: int; Amount of Tiberium at this location. * 2237 * * 2238 * WARNINGS: none * 2239 * * 2240 * HISTORY: * 2241 * 07/18/1994 JLB : Created. * 2242 *=============================================================================================*/ 2243 int UnitClass::Tiberium_Check(CELL & center, int x, int y) 2244 { 2245 assert(Units.ID(this) == ID); 2246 assert(IsActive); 2247 2248 /* 2249 ** If the specified offset from the origin will cause it 2250 ** to spill past the map edge, then abort this cell check. 2251 */ 2252 if (Cell_X(center)+x < Map.MapCellX) return(0); 2253 if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(0); 2254 if (Cell_Y(center)+y < Map.MapCellY) return(0); 2255 if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(0); 2256 2257 center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y); 2258 2259 if ((Session.Type != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Mapped(PlayerPtr)))) { 2260 if (Map[Coord].Zones[Class->MZone] != Map[center].Zones[Class->MZone]) return(0); 2261 if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { 2262 int value = 0; 2263 switch (Map[center].Overlay) { 2264 case OVERLAY_GOLD1: 2265 case OVERLAY_GOLD2: 2266 case OVERLAY_GOLD3: 2267 case OVERLAY_GOLD4: 2268 value = Rule.GoldValue; 2269 break; 2270 case OVERLAY_GEMS1: 2271 case OVERLAY_GEMS2: 2272 case OVERLAY_GEMS3: 2273 case OVERLAY_GEMS4: 2274 value = Rule.GemValue*4; 2275 break; 2276 } 2277 return((Map[center].OverlayData+1)*value); 2278 } 2279 } 2280 return(0); 2281 } 2282 2283 2284 /*********************************************************************************************** 2285 * UnitClass::Goto_Tiberium -- Searches for and heads toward tiberium. * 2286 * * 2287 * This routine will cause the unit to search for and head toward nearby Tiberium. When * 2288 * the Tiberium is reached, then this routine should not be called again until such time * 2289 * as additional harvesting is required. When this routine returns false, then it should * 2290 * be called again until such time as it returns true. * 2291 * * 2292 * INPUT: rad = size of ring to search * 2293 * * 2294 * OUTPUT: Has the unit reached Tiberium and harvesting should begin? * 2295 * * 2296 * WARNINGS: none * 2297 * * 2298 * HISTORY: * 2299 * 09/22/1995 JLB : Created. * 2300 *=============================================================================================*/ 2301 bool UnitClass::Goto_Tiberium(int rad) 2302 { 2303 assert(Units.ID(this) == ID); 2304 assert(IsActive); 2305 2306 if (!Target_Legal(NavCom)) { 2307 CELL center = Coord_Cell(Center_Coord()); 2308 if (Map[center].Land_Type() == LAND_TIBERIUM) { 2309 return(true); 2310 } else { 2311 2312 /* 2313 ** Perform a ring search outward from the center. 2314 */ 2315 for (int radius = 1; radius < rad; radius++) { 2316 CELL cell = center; 2317 CELL bestcell = 0; 2318 int tiberium = 0; 2319 int besttiberium = 0; 2320 for (int x = -radius; x <= radius; x++) { 2321 2322 /* 2323 ** Randomize the corners. 2324 */ 2325 int corner[2]; 2326 int corners[4][2] = { 2327 {x, -radius}, 2328 {x, +radius}, 2329 {-radius, x}, 2330 {+radius, x} 2331 }; 2332 for (int i = 0; i < 3; i++) { 2333 int j = i + rand() / (RAND_MAX / (4 - i) + 1); 2334 memcpy(&corner, &corners[j], sizeof(corner)); 2335 memcpy(&corners[j], &corners[i], sizeof(corner)); 2336 memcpy(&corners[i], corner, sizeof(corner)); 2337 } 2338 2339 cell = center; 2340 tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]); 2341 if (tiberium > besttiberium) { 2342 bestcell = cell; 2343 besttiberium = tiberium; 2344 } 2345 2346 cell = center; 2347 tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]); 2348 if (tiberium > besttiberium) { 2349 bestcell = cell; 2350 besttiberium = tiberium; 2351 } 2352 2353 cell = center; 2354 tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]); 2355 if (tiberium > besttiberium) { 2356 bestcell = cell; 2357 besttiberium = tiberium; 2358 } 2359 2360 cell = center; 2361 tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]); 2362 if (tiberium > besttiberium) { 2363 bestcell = cell; 2364 besttiberium = tiberium; 2365 } 2366 } 2367 if (bestcell) { 2368 Assign_Destination(::As_Target(bestcell)); 2369 return(false); 2370 } 2371 } 2372 } 2373 } 2374 2375 return(false); 2376 } 2377 2378 2379 /*********************************************************************************************** 2380 * UnitClass::Harvesting -- Harvests tiberium at the current location. * 2381 * * 2382 * This routine is used to by the harvester to harvest Tiberium at the current location. * 2383 * When harvesting is complete, this routine will return true. * 2384 * * 2385 * INPUT: none * 2386 * * 2387 * OUTPUT: bool; Is harvesting complete? * 2388 * * 2389 * WARNINGS: none * 2390 * * 2391 * HISTORY: * 2392 * 07/18/1994 JLB : Created. * 2393 *=============================================================================================*/ 2394 bool UnitClass::Harvesting(void) 2395 { 2396 assert(Units.ID(this) == ID); 2397 assert(IsActive); 2398 2399 CELL cell = Coord_Cell(Coord); 2400 CellClass * ptr = &Map[cell]; 2401 2402 /* 2403 ** Keep waiting if still heading toward a spot to harvest. 2404 */ 2405 if (Target_Legal(NavCom)) return(true); 2406 2407 if (Tiberium_Load() < 1 && ptr->Land_Type() == LAND_TIBERIUM) { 2408 2409 /* 2410 ** Lift some Tiberium from the ground. Try to lift a complete 2411 ** "level" of Tiberium. A level happens to be 6 steps. If there 2412 ** is a partial level, then lift that instead. Never lift more 2413 ** than the harvester can carry. 2414 */ 2415 // int reducer = (ptr->OverlayData % 6) + 1; 2416 int reducer = 1; 2417 OverlayType overlay = ptr->Overlay; 2418 reducer = ptr->Reduce_Tiberium(min(reducer, Rule.BailCount-Tiberium)); 2419 Tiberium += reducer; 2420 switch (overlay) { 2421 case OVERLAY_GOLD1: 2422 case OVERLAY_GOLD2: 2423 case OVERLAY_GOLD3: 2424 case OVERLAY_GOLD4: 2425 Gold += reducer; 2426 break; 2427 2428 case OVERLAY_GEMS1: 2429 case OVERLAY_GEMS2: 2430 case OVERLAY_GEMS3: 2431 case OVERLAY_GEMS4: 2432 Gems += reducer; 2433 if (Rule.BailCount > Tiberium) {Gems++;Tiberium++;} 2434 if (Rule.BailCount > Tiberium) {Gems++;Tiberium++;} 2435 if (Rule.BailCount > Tiberium) {Gems++;Tiberium++;} 2436 break; 2437 2438 default: 2439 break; 2440 } 2441 Set_Stage(0); 2442 Set_Rate(Rule.OreDumpRate); 2443 2444 } else { 2445 2446 /* 2447 ** If the harvester is stopped on a non Tiberium field and the harvester 2448 ** isn't loaded with Tiberium, then no further action can be performed 2449 ** by this logic routine. Bail with a failure and thus cause a branch to 2450 ** a better suited logic processor. 2451 */ 2452 Set_Stage(0); 2453 Set_Rate(0); 2454 return(false); 2455 } 2456 return(true); 2457 } 2458 2459 2460 /*********************************************************************************************** 2461 * UnitClass::Mission_Unload -- Handles unloading cargo. * 2462 * * 2463 * This is the AI control sequence for when a transport desires to unload its cargo and * 2464 * then exit the map. * 2465 * * 2466 * INPUT: none * 2467 * * 2468 * OUTPUT: Returns with the delay before calling this routine again. * 2469 * * 2470 * WARNINGS: none * 2471 * * 2472 * HISTORY: * 2473 * 07/18/1994 JLB : Created. * 2474 *=============================================================================================*/ 2475 int UnitClass::Mission_Unload(void) 2476 { 2477 assert(Units.ID(this) == ID); 2478 assert(IsActive); 2479 2480 enum { 2481 INITIAL_CHECK, 2482 MANEUVERING, 2483 OPENING_DOOR, 2484 UNLOADING, 2485 CLOSING_DOOR 2486 }; 2487 DirType dir; 2488 CELL cell; 2489 2490 switch (Class->Type) { 2491 case UNIT_HARVESTER: 2492 if (PrimaryFacing != DIR_W) { 2493 if (!IsRotating) { 2494 Do_Turn(DIR_W); 2495 } 2496 return(5); 2497 } 2498 2499 if (!IsDumping) { 2500 IsDumping = true; 2501 Set_Stage(0); 2502 Set_Rate(Rule.OreDumpRate); 2503 break; 2504 } 2505 if (Fetch_Stage() < ARRAY_SIZE(Class->Harvester_Dump_List)-1) break; 2506 2507 IsDumping = false; 2508 if (Tiberium) { 2509 Tiberium = 0; 2510 int credits = Credit_Load(); 2511 House->Harvested(credits); 2512 Tiberium = Gold = Gems = 0; 2513 } 2514 Transmit_Message(RADIO_OVER_OUT); 2515 2516 Assign_Mission(MISSION_HARVEST); 2517 break; 2518 2519 case UNIT_TRUCK: 2520 switch (Status) { 2521 case INITIAL_CHECK: 2522 dir = Desired_Load_Dir(NULL, cell); 2523 if (How_Many() && cell != 0) { 2524 Do_Turn(dir); 2525 Status = MANEUVERING; 2526 return(1); 2527 } else { 2528 Assign_Mission(MISSION_GUARD); 2529 } 2530 break; 2531 2532 case MANEUVERING: 2533 if (!IsRotating) { 2534 Status = UNLOADING; 2535 return(1); 2536 } 2537 break; 2538 2539 case UNLOADING: 2540 if (How_Many()) { 2541 FootClass * passenger = Detach_Object(); 2542 2543 if (passenger != NULL) { 2544 DirType toface = DIR_S + PrimaryFacing; 2545 bool placed = false; 2546 2547 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 2548 DirType newface = toface + Facing_Dir(face); 2549 CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface); 2550 2551 if (passenger->Can_Enter_Cell(newcell) == MOVE_OK) { 2552 ScenarioInit++; 2553 passenger->Unlimbo(Coord_Move(Coord, newface, 0x0080), newface); 2554 ScenarioInit--; 2555 passenger->Assign_Mission(MISSION_MOVE); 2556 passenger->Assign_Destination(::As_Target(newcell)); 2557 placed = true; 2558 break; 2559 } 2560 } 2561 2562 /* 2563 ** If the attached unit could NOT be deployed, then re-attach 2564 ** it and then bail out of this deploy process. 2565 */ 2566 if (!placed) { 2567 Attach(passenger); 2568 Status = CLOSING_DOOR; 2569 } 2570 else { 2571 passenger->Look(false); 2572 } 2573 } 2574 } else { 2575 Status = CLOSING_DOOR; 2576 } 2577 break; 2578 2579 /* 2580 ** Close APC door in preparation for normal operation. 2581 */ 2582 case CLOSING_DOOR: 2583 Assign_Mission(MISSION_GUARD); 2584 break; 2585 } 2586 break; 2587 2588 case UNIT_APC: 2589 #ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 2590 case UNIT_PHASE: 2591 #endif 2592 switch (Status) { 2593 case INITIAL_CHECK: 2594 dir = Desired_Load_Dir(NULL, cell); 2595 if (How_Many() && cell != 0) { 2596 Do_Turn(dir); 2597 Status = MANEUVERING; 2598 return(1); 2599 } else { 2600 Assign_Mission(MISSION_GUARD); 2601 } 2602 break; 2603 2604 case MANEUVERING: 2605 if (!IsRotating) { 2606 APC_Open_Door(); 2607 if (Is_Door_Opening()) { 2608 Status = OPENING_DOOR; 2609 return(1); 2610 } 2611 } 2612 break; 2613 2614 case OPENING_DOOR: 2615 if (Is_Door_Open()) { 2616 Status = UNLOADING; 2617 return(1); 2618 } else { 2619 if (!Is_Door_Opening()) { 2620 Status = INITIAL_CHECK; 2621 } 2622 } 2623 break; 2624 2625 case UNLOADING: 2626 if (How_Many()) { 2627 FootClass * passenger = Detach_Object(); 2628 2629 if (passenger != NULL) { 2630 DirType toface = DIR_S + PrimaryFacing; 2631 bool placed = false; 2632 2633 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 2634 DirType newface = toface + Facing_Dir(face); 2635 CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface); 2636 2637 if (passenger->Can_Enter_Cell(newcell) == MOVE_OK) { 2638 ScenarioInit++; 2639 passenger->Unlimbo(Coord_Move(Coord, newface, 0x0080), newface); 2640 ScenarioInit--; 2641 passenger->Assign_Mission(MISSION_MOVE); 2642 passenger->Assign_Destination(::As_Target(newcell)); 2643 placed = true; 2644 break; 2645 } 2646 } 2647 2648 /* 2649 ** If the attached unit could NOT be deployed, then re-attach 2650 ** it and then bail out of this deploy process. 2651 */ 2652 if (!placed) { 2653 Attach(passenger); 2654 Status = CLOSING_DOOR; 2655 } 2656 else { 2657 passenger->Look(false); 2658 } 2659 } 2660 } else { 2661 Status = CLOSING_DOOR; 2662 } 2663 break; 2664 2665 /* 2666 ** Close APC door in preparation for normal operation. 2667 */ 2668 case CLOSING_DOOR: 2669 if (Is_Door_Open()) { 2670 APC_Close_Door(); 2671 } 2672 if (Is_Door_Closed()) { 2673 Assign_Mission(MISSION_GUARD); 2674 } 2675 break; 2676 } 2677 break; 2678 2679 case UNIT_MCV: 2680 switch (Status) { 2681 case 0: 2682 Path[0] = FACING_NONE; 2683 Status = 1; 2684 break; 2685 2686 case 1: 2687 if (!IsDriving) { 2688 Try_To_Deploy(); 2689 if (IsActive) { 2690 if (IsDeploying) { 2691 Status = 2; 2692 } else { 2693 if (!House->IsHuman && Session.Type != GAME_NORMAL) { 2694 Assign_Mission(MISSION_HUNT); 2695 } else { 2696 Assign_Mission(MISSION_GUARD); 2697 } 2698 } 2699 } 2700 } 2701 break; 2702 2703 case 2: 2704 if (!IsDeploying) { 2705 Assign_Mission(MISSION_GUARD); 2706 } 2707 break; 2708 } 2709 return(1); 2710 2711 case UNIT_MINELAYER: 2712 switch (Status) { 2713 case INITIAL_CHECK: 2714 dir = DIR_NE; 2715 if (Ammo > 0) { 2716 Do_Turn(dir); 2717 Status = MANEUVERING; 2718 return(1); 2719 } else { 2720 Assign_Mission(MISSION_GUARD); 2721 } 2722 break; 2723 2724 case MANEUVERING: 2725 if (!IsRotating) { 2726 APC_Open_Door(); 2727 if (Is_Door_Opening()) { 2728 Status = OPENING_DOOR; 2729 return(1); 2730 } 2731 } 2732 break; 2733 2734 case OPENING_DOOR: 2735 if (Is_Door_Open()) { 2736 Status = UNLOADING; 2737 return(1); 2738 } else { 2739 if (!Is_Door_Opening()) { 2740 Status = INITIAL_CHECK; 2741 } 2742 } 2743 break; 2744 2745 case UNLOADING: 2746 if (Ammo > 0) { 2747 if (!Map[Center_Coord()].Cell_Building()) { 2748 Mark(MARK_UP); 2749 BuildingClass * building = new BuildingClass((House->ActLike == HOUSE_USSR || House->ActLike == HOUSE_UKRAINE || House->ActLike == HOUSE_BAD) ? STRUCT_APMINE : STRUCT_AVMINE, House->Class->House); 2750 if (building != NULL) { 2751 ScenarioInit = true; 2752 if (building->Unlimbo(Coord)) { 2753 Sound_Effect(VOC_MINELAY1, Coord); 2754 ScenarioInit = false; 2755 building->Revealed(House); 2756 Ammo--; 2757 } 2758 ScenarioInit = false; 2759 } 2760 Status = CLOSING_DOOR; 2761 Mark(MARK_DOWN); 2762 } else { 2763 Status = CLOSING_DOOR; 2764 } 2765 } else { 2766 Status = CLOSING_DOOR; 2767 } 2768 break; 2769 2770 /* 2771 ** Close APC door in preparation for normal operation. 2772 */ 2773 case CLOSING_DOOR: 2774 if (Is_Door_Open()) { 2775 APC_Close_Door(); 2776 } 2777 if (Is_Door_Closed()) { 2778 Assign_Mission(MISSION_GUARD); 2779 } 2780 break; 2781 } 2782 break; 2783 2784 #ifdef FIXIT_CSII // checked - ajw 9/28/98 2785 case UNIT_MAD: 2786 if (!Gems && !IsDumping) { 2787 Gems = 1; 2788 Gold = 0; 2789 Arm = QuakeDelay * House->ROFBias; 2790 #ifdef ENGLISH 2791 Speak(VOX_MADTANK_DEPLOYED); // this voice only exists in English 2792 #else 2793 Sound_Effect(VOC_BUZZY1,Center_Coord()); 2794 #endif 2795 Set_Stage(0); 2796 Set_Rate(Rule.OreDumpRate*2); 2797 IsDumping = true; 2798 2799 #if 1 2800 InfantryClass *crew = new InfantryClass(INFANTRY_C1, House->Class->House); 2801 if (crew != NULL) crew->IsTechnician = true; 2802 2803 if (crew != NULL) { 2804 DirType toface = DIR_S + PrimaryFacing; 2805 2806 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 2807 DirType newface = toface + Facing_Dir(face); 2808 CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface); 2809 if (crew->Can_Enter_Cell(newcell) == MOVE_OK) { 2810 ScenarioInit++; 2811 crew->Unlimbo(Coord_Move(Coord, newface, 0x0080), newface); 2812 ScenarioInit--; 2813 crew->Assign_Mission(MISSION_MOVE); 2814 crew->Assign_Destination(::As_Target(newcell)); 2815 break; 2816 } 2817 } 2818 } 2819 #endif 2820 } 2821 2822 if ( (Arm && !Gold) || IronCurtainCountDown) { 2823 Set_Stage(Fetch_Stage() & 1); 2824 return(1); 2825 } 2826 2827 if (!Gold) { 2828 Sound_Effect(VOC_MAD_CHARGE, Center_Coord()); 2829 Set_Stage(0); 2830 Gold = 1; 2831 return(1); 2832 } 2833 2834 if (Fetch_Stage() < 7) { 2835 return(1); 2836 } 2837 2838 IsDumping = false; 2839 2840 Sound_Effect(VOC_MAD_EXPLODE, Center_Coord()); 2841 2842 Strength = 1; // assure destruction 2843 PendingTimeQuake = true; // trigger a time quake 2844 TimeQuakeCenter = ::As_Target(Center_Coord()); 2845 break; 2846 2847 case UNIT_CHRONOTANK: 2848 if (IsOwnedByPlayer) { 2849 Map.IsTargettingMode = SPC_CHRONO2; 2850 HouseClass* old_player_ptr = PlayerPtr; 2851 Logic_Switch_Player_Context(this); 2852 Unselect_All(); 2853 On_Special_Weapon_Targetting(PlayerPtr, Map.IsTargettingMode); 2854 Logic_Switch_Player_Context(old_player_ptr); 2855 } 2856 House->UnitToTeleport = As_Target(); 2857 2858 Assign_Mission(MISSION_GUARD); 2859 break; 2860 #endif 2861 default: 2862 break; 2863 } 2864 return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2)); 2865 } 2866 2867 2868 /*********************************************************************************************** 2869 * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * 2870 * * 2871 * This is the AI process used by harvesters when they are doing their harvesting action. * 2872 * This entails searching for nearby Tiberium, heading there, harvesting, and then * 2873 * returning to a refinery for unloading. * 2874 * * 2875 * INPUT: none * 2876 * * 2877 * OUTPUT: Returns with the delay before calling this routine again. * 2878 * * 2879 * WARNINGS: none * 2880 * * 2881 * HISTORY: * 2882 * 07/18/1994 JLB : Created. * 2883 * 06/21/1995 JLB : Force guard mode if no Tiberium found. * 2884 * 09/28/1995 JLB : Aborts harvesting if there are no more refineries. * 2885 *=============================================================================================*/ 2886 int UnitClass::Mission_Harvest(void) 2887 { 2888 assert(Units.ID(this) == ID); 2889 assert(IsActive); 2890 2891 enum { 2892 LOOKING, 2893 HARVESTING, 2894 FINDHOME, 2895 HEADINGHOME, 2896 GOINGTOIDLE, 2897 }; 2898 2899 /* 2900 ** A non-harvesting type unit will just sit still if it is given the harvest mission. This 2901 ** allows combat units to act "brain dead". 2902 */ 2903 if (!Class->IsToHarvest) return(TICKS_PER_SECOND*30); 2904 2905 /* 2906 ** If there are no more refineries, then drop into guard mode. 2907 */ 2908 if (!(House->ActiveBScan & STRUCTF_REFINERY)) { 2909 Assign_Mission(MISSION_GUARD); 2910 return(1); 2911 } 2912 2913 switch (Status) { 2914 2915 /* 2916 ** Go and find a Tiberium field to harvest. 2917 */ 2918 case LOOKING: 2919 /* 2920 ** Slightly hacky; if TarCom is set then skip to finding home state. 2921 */ 2922 if (Target_Legal(TarCom)) { 2923 Assign_Target(TARGET_NONE); 2924 Status = FINDHOME; 2925 return(1); 2926 } 2927 2928 /* 2929 ** Look for ore where we last found some - mine the same patch 2930 */ 2931 if (Target_Legal(ArchiveTarget)) { 2932 Assign_Destination(ArchiveTarget); 2933 ArchiveTarget = 0; 2934 } 2935 IsHarvesting = false; 2936 if (Goto_Tiberium(Rule.TiberiumLongScan / CELL_LEPTON_W)) { 2937 IsHarvesting = true; 2938 Set_Rate(2); 2939 Set_Stage(0); 2940 Status = HARVESTING; 2941 ArchiveTarget = ::As_Target(Coord_Cell(Coord)); 2942 return(1); 2943 } else { 2944 2945 /* 2946 ** If the harvester isn't on Tiberium and it is not heading toward Tiberium, then 2947 ** force it to go into guard mode. This will prevent the harvester from repeatedly 2948 ** searching for Tiberium. 2949 */ 2950 if (!Target_Legal(NavCom)) { 2951 2952 /* 2953 ** If the archive target is legal, then head there since it is presumed 2954 ** that the archive target points to the last place it harvested at. This might 2955 ** solve the case where the harvester gets stuck and can't find Tiberium just because 2956 ** it is greater than 32 squares away. 2957 */ 2958 if (Target_Legal(ArchiveTarget)) { 2959 Assign_Destination(ArchiveTarget); 2960 } else { 2961 Status = GOINGTOIDLE; 2962 IsUseless = true; 2963 House->IsTiberiumShort = true; 2964 return(TICKS_PER_SECOND*7); 2965 } 2966 } else { 2967 IsUseless = false; 2968 } 2969 } 2970 break; 2971 2972 /* 2973 ** Harvest at current location until full or Tiberium exhausted. 2974 */ 2975 case HARVESTING: 2976 // if (Fetch_Stage() > ARRAY_SIZE(Class->Harvester_Load_List)) { 2977 // Set_Stage(0); 2978 // } 2979 if (Fetch_Rate() == 0) { 2980 Set_Stage(0); 2981 Set_Rate(Rule.OreDumpRate); 2982 } 2983 2984 if (Fetch_Stage() < ARRAY_SIZE(Class->Harvester_Load_List)) return(1); 2985 if (!Harvesting()) { 2986 IsHarvesting = false; 2987 if (Tiberium_Load() == 1) { 2988 Status = FINDHOME; 2989 } else { 2990 if (!Goto_Tiberium(Rule.TiberiumShortScan / CELL_LEPTON_W) && !Target_Legal(NavCom)) { 2991 ArchiveTarget = TARGET_NONE; 2992 Status = FINDHOME; 2993 } else { 2994 Status = HARVESTING; 2995 IsHarvesting = true; 2996 } 2997 } 2998 return(1); 2999 } else if (!Target_Legal(NavCom) && ArchiveTarget == TARGET_NONE) { 3000 ArchiveTarget = ::As_Target(Coord_Cell(Coord)); 3001 } 3002 return(1); 3003 // return(TICKS_PER_SECOND*Rule.OreDumpRate); 3004 3005 /* 3006 ** Find and head to refinery. 3007 */ 3008 case FINDHOME: 3009 if (!Target_Legal(NavCom)) { 3010 3011 /* 3012 ** Find best refinery. 3013 */ 3014 BuildingClass * nearest = Find_Best_Refinery(); 3015 3016 /* 3017 ** Since the refinery said it was ok to load, establish radio 3018 ** contact with the refinery and then await docking orders. 3019 */ 3020 if (nearest != NULL && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { 3021 Status = HEADINGHOME; 3022 if (nearest->House == PlayerPtr && (PlayerPtr->Capacity - PlayerPtr->Tiberium) < 300 && PlayerPtr->Capacity > 500 && (PlayerPtr->ActiveBScan & (STRUCTF_REFINERY | STRUCTF_CONST))) { 3023 Speak(VOX_NEED_MO_CAPACITY); 3024 } 3025 } else { 3026 ScenarioInit++; 3027 nearest = Find_Best_Refinery(); 3028 ScenarioInit--; 3029 if (nearest != NULL) { 3030 Assign_Destination(::As_Target(Nearby_Location(nearest))); 3031 } 3032 } 3033 } 3034 break; 3035 3036 /* 3037 ** In communication with refinery so that it will successfully dock and 3038 ** unload. If, for some reason, radio contact was lost, then hunt for 3039 ** another refinery to unload at. 3040 */ 3041 case HEADINGHOME: 3042 Assign_Mission(MISSION_ENTER); 3043 return(1); 3044 3045 /* 3046 ** The harvester has nothing to do. There is no Tiberium nearby and 3047 ** no where to go. 3048 */ 3049 case GOINGTOIDLE: 3050 if (IsUseless) { 3051 if (House->ActiveBScan & STRUCTF_REPAIR) { 3052 Assign_Mission(MISSION_REPAIR); 3053 } else { 3054 Assign_Mission(MISSION_HUNT); 3055 } 3056 } 3057 Assign_Mission(MISSION_GUARD); 3058 break; 3059 3060 } 3061 return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2)); 3062 } 3063 3064 3065 /*********************************************************************************************** 3066 * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * 3067 * * 3068 * Computer controlled units must be intelligent enough to find enemies as well as to * 3069 * attack them. This AI process will handle both the simple attack process as well as the * 3070 * scanning for enemy units to attack. * 3071 * * 3072 * INPUT: none * 3073 * * 3074 * OUTPUT: Returns with the delay before calling this routine again. * 3075 * * 3076 * WARNINGS: none * 3077 * * 3078 * HISTORY: * 3079 * 07/18/1994 JLB : Created. * 3080 *=============================================================================================*/ 3081 int UnitClass::Mission_Hunt(void) 3082 { 3083 assert(Units.ID(this) == ID); 3084 assert(IsActive); 3085 3086 if (*this == UNIT_MCV) { 3087 enum { 3088 FIND_SPOT, 3089 WAITING 3090 }; 3091 3092 switch (Status) { 3093 3094 /* 3095 ** This stage handles locating a convenient spot, rotating to face the correct 3096 ** direction and then commencing the deployment operation. 3097 */ 3098 case FIND_SPOT: 3099 if (Goto_Clear_Spot()) { 3100 if (Try_To_Deploy()) { 3101 Status = WAITING; 3102 } 3103 } 3104 break; 3105 3106 /* 3107 ** This stage watchdogs the deployment operation and if for some reason, the deployment 3108 ** is aborted (the IsDeploying flag becomes false), then it reverts back to hunting for 3109 ** a convenient spot to deploy. 3110 */ 3111 case WAITING: 3112 if (!IsDeploying) { 3113 Status = FIND_SPOT; 3114 } 3115 break; 3116 } 3117 } else { 3118 3119 return(DriveClass::Mission_Hunt()); 3120 } 3121 return(MissionControl[Mission].Normal_Delay()+Random_Pick(0, 2)); 3122 } 3123 3124 3125 /*********************************************************************************************** 3126 * UnitClass::Overlap_List -- Determines overlap list for units. * 3127 * * 3128 * The unit overlap list is used to keep track of which cells are to * 3129 * be marked for redraw. This is critical in order to keep the units * 3130 * displayed correctly. * 3131 * * 3132 * INPUT: none * 3133 * * 3134 * OUTPUT: Returns with the overlap list pointer for the unit at its * 3135 * present position. * 3136 * * 3137 * WARNINGS: none * 3138 * * 3139 * HISTORY: * 3140 * 05/26/1994 JLB : Created. * 3141 * 06/19/1994 JLB : Uses Coord_Spillable_List function. * 3142 *=============================================================================================*/ 3143 short const * UnitClass::Overlap_List(bool redraw) const 3144 { 3145 assert(Units.ID(this) == ID); 3146 assert(IsActive); 3147 3148 #ifdef PARTIAL 3149 if (Height == 0 && redraw && Class->DimensionData != NULL) { 3150 Rect rect; 3151 int shapenum = Shape_Number(); 3152 if (Class->DimensionData[shapenum].Is_Valid()) { 3153 rect = Class->DimensionData[shapenum]; 3154 } else { 3155 rect = Class->DimensionData[shapenum] = Shape_Dimensions(Get_Image_Data(), shapenum); 3156 } 3157 3158 if (Is_Selected_By_Player()) { 3159 rect = Union(rect, Rect(-15, -15, 30, 30)); 3160 } 3161 3162 if (Class->IsTurretEquipped || Class->IsRadarEquipped) { 3163 rect = Union(rect, Rect(-15, -15, 30, 30)); 3164 } 3165 3166 return(Coord_Spillage_List(Coord, rect, true)); 3167 } 3168 #else 3169 redraw = redraw; 3170 #endif 3171 3172 int size = ICON_PIXEL_W; 3173 3174 if (redraw && (Is_Selected_By_Player() || IsFiring)) { 3175 size += 24; 3176 } 3177 if (Class->IsGigundo || IsAnimAttached) { 3178 size = ICON_PIXEL_W*2; 3179 } 3180 3181 return(Coord_Spillage_List(Coord, size)+1); 3182 } 3183 3184 3185 /*********************************************************************************************** 3186 * UnitClass::Can_Enter_Cell -- Determines cell entry legality. * 3187 * * 3188 * Use this routine to determine if the unit can enter the cell * 3189 * specified and given the direction of entry specified. Typically, * 3190 * this is used when determining unit travel path. * 3191 * * 3192 * INPUT: cell -- The cell to examine. * 3193 * * 3194 * facing -- The facing that the unit would enter the specified * 3195 * cell. If this value is -1, then don't consider * 3196 * facing when performing the check. * 3197 * * 3198 * OUTPUT: Returns the reason why it couldn't enter the cell or MOVE_OK if movement is * 3199 * allowed. * 3200 * * 3201 * WARNINGS: none * 3202 * * 3203 * HISTORY: * 3204 * 09/07/1992 JLB : Created. * 3205 * 04/16/1994 JLB : Converted to member function. * 3206 * 07/04/1995 JLB : Allowed to drive on building trying to enter it. * 3207 *=============================================================================================*/ 3208 MoveType UnitClass::Can_Enter_Cell(CELL cell, FacingType ) const 3209 { 3210 assert(Units.ID(this) == ID); 3211 assert(IsActive); 3212 3213 bool cancrush = false; 3214 3215 CellClass const * cellptr = &Map[cell]; 3216 3217 if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO); 3218 3219 /* 3220 ** Moving off the edge of the map is not allowed unless 3221 ** this is a loaner vehicle. 3222 */ 3223 if (!ScenarioInit && !Map.In_Radar(cell) && !Is_Allowed_To_Leave_Map() && IsLocked) { 3224 return(MOVE_NO); 3225 } 3226 3227 MoveType retval = MOVE_OK; 3228 3229 /* 3230 ** Certain vehicles can drive over walls. Check for this case and 3231 ** and return the appropriate flag. Other units treat walls as impassable. 3232 */ 3233 if (cellptr->Overlay != OVERLAY_NONE) { 3234 OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); 3235 3236 if (optr->IsCrate && !((Session.Type == GAME_NORMAL) ? House->IsPlayerControl : House->IsHuman) && Session.Type == GAME_NORMAL) { 3237 return(MOVE_NO); 3238 } 3239 3240 if (optr->IsWall) { 3241 3242 /* 3243 ** If the blocking wall is crushable (and not owned by this player or one of this players 3244 ** allies, then record that it is crushable and let the normal logic take over. The end 3245 ** result should cause this unit to consider the cell passable. 3246 */ 3247 if (optr->IsCrushable && Class->IsCrusher) { 3248 cancrush = !House->Is_Ally(cellptr->Owner); 3249 } 3250 3251 if (!cancrush && Is_Weapon_Equipped()) { 3252 WarheadTypeClass const * whead = Class->PrimaryWeapon->WarheadPtr; 3253 3254 if (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)) { 3255 // if (!House->IsHuman && !House->Is_Ally(cellptr->Owner)) { 3256 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3257 // } else { 3258 // return(MOVE_NO); 3259 // } 3260 } else { 3261 return(MOVE_NO); 3262 } 3263 } 3264 } 3265 } 3266 3267 /* 3268 ** Loop through all of the objects in the square setting a bit 3269 ** for how they affect movement. 3270 */ 3271 bool crushable = false; 3272 ObjectClass * obj = cellptr->Cell_Occupier(); 3273 while (obj != NULL) { 3274 3275 if (obj != this) { 3276 3277 /* 3278 ** If object is a land mine, allow movement if possible. 3279 */ 3280 if (obj->What_Am_I() == RTTI_BUILDING && (!Rule.IsMineAware || !((BuildingClass *)obj)->House->Is_Ally(House))) { 3281 if ((*(BuildingClass *)obj) == STRUCT_APMINE) return(MOVE_OK); 3282 if ((*(BuildingClass *)obj) == STRUCT_AVMINE) return(MOVE_OK); 3283 } 3284 3285 /* 3286 ** Always allow entry if trying to move on a cell with 3287 ** authorization from the occupier. 3288 */ 3289 if (obj == Contact_With_Whom() && (IsTethered || (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_REPAIR))) { 3290 return(MOVE_OK); 3291 } 3292 3293 /* 3294 ** Special check to allow entry into the sea transport this vehicle 3295 ** is trying to enter. 3296 */ 3297 if (Mission == MISSION_ENTER && obj->As_Target() == NavCom && IsTethered) { 3298 return(MOVE_OK); 3299 } 3300 3301 /* 3302 ** Guard area should not allow the guarding unit to enter the cell with the 3303 ** guarded unit. 3304 */ 3305 if (Mission == MISSION_GUARD_AREA && ArchiveTarget == obj->As_Target()) { 3306 return(MOVE_NO); 3307 } 3308 3309 bool is_moving = obj->Is_Foot() && 3310 (Target_Legal(((FootClass *)obj)->NavCom) || ((FootClass *)obj)->PrimaryFacing.Is_Rotating() || ((FootClass *)obj)->IsDriving); 3311 // (((FootClass *)obj)->PrimaryFacing.Is_Rotating() || ((FootClass *)obj)->IsDriving); 3312 // (((FootClass *)obj)->IsRotating || ((FootClass *)obj)->IsDriving); 3313 // (Target_Legal(((FootClass *)obj)->NavCom) || ((FootClass *)obj)->IsDriving); 3314 3315 if (House->Is_Ally(obj)) { 3316 if (is_moving) { 3317 int face = Dir_Facing(PrimaryFacing); 3318 int techface = Dir_Facing(((FootClass const *)obj)->PrimaryFacing) ^4; 3319 if (face == techface && Distance((AbstractClass const *)obj) <= 0x1FF) { 3320 return(MOVE_NO); 3321 } 3322 if (retval < MOVE_MOVING_BLOCK) retval = MOVE_MOVING_BLOCK; 3323 } else { 3324 if (obj->What_Am_I() == RTTI_BUILDING) return(MOVE_NO); 3325 3326 /* 3327 ** If the blocking object is not in the same zone, then it certainly 3328 ** isn't a temporary block, it is a permanent one. 3329 */ 3330 if (Map[Coord].Zones[Class->MZone] != cellptr->Zones[Class->MZone]) return(MOVE_NO); 3331 3332 if (retval < MOVE_TEMP) retval = MOVE_TEMP; 3333 } 3334 } else { 3335 3336 /* 3337 ** Cloaked enemy objects are not considered if this is a Find_Path() 3338 ** call. 3339 */ 3340 if (!obj->Is_Techno() || !((TechnoClass *)obj)->Is_Cloaked(this)) { 3341 3342 /* 3343 ** If this unit can crush infantry, and there is an enemy infantry in the 3344 ** cell, don't consider the cell impassible. This is true even if the unit 3345 ** doesn't contain a legitimate weapon. 3346 */ 3347 bool crusher = Class->IsCrusher; 3348 if (!crusher || !obj->Class_Of().IsCrushable) { 3349 3350 /* 3351 ** Any non-allied blockage is considered impassable if the unit 3352 ** is not equipped with a weapon. 3353 */ 3354 if (Class->PrimaryWeapon == NULL) return(MOVE_NO); 3355 3356 /* 3357 ** Some kinds of terrain are considered destroyable if the unit is equipped 3358 ** with the weapon that can destroy it. Otherwise, the terrain is considered 3359 ** impassable. 3360 */ 3361 switch (obj->What_Am_I()) { 3362 case RTTI_TERRAIN: 3363 3364 #ifdef TOFIX 3365 if (((TerrainClass *)obj)->Class->Armor == ARMOR_WOOD && 3366 Class->PrimaryWeapon->WarheadPtr->IsWoodDestroyer) { 3367 3368 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3369 } else { 3370 return(MOVE_NO); 3371 } 3372 break; 3373 #else 3374 return(MOVE_NO); 3375 #endif 3376 3377 default: 3378 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3379 break; 3380 } 3381 } else { 3382 crushable = true; 3383 } 3384 } else { 3385 if (retval < MOVE_CLOAK) retval = MOVE_CLOAK; 3386 } 3387 } 3388 } 3389 3390 /* 3391 ** Move to next object in chain. 3392 */ 3393 obj = obj->Next; 3394 } 3395 3396 /* 3397 ** If the cell is out and out impassable because of underlying terrain, then 3398 ** return this immutable fact. 3399 */ 3400 if (!cancrush && retval != MOVE_DESTROYABLE && Ground[cellptr->Land_Type()].Cost[Class->Speed] == 0) { 3401 return(MOVE_NO); 3402 } 3403 3404 /* 3405 ** If some allied object has reserved the cell, then consider the cell 3406 ** as blocked by a moving object. 3407 */ 3408 if (retval == MOVE_OK && !crushable && (cellptr->Flag.Composite & 0x3F) != 0) { 3409 3410 /* 3411 ** If reserved by a vehicle, then consider this blocked terrain. 3412 */ 3413 if (cellptr->Flag.Occupy.Vehicle) { 3414 retval = MOVE_MOVING_BLOCK; 3415 } else { 3416 if (cellptr->InfType != HOUSE_NONE && House->Is_Ally(cellptr->InfType)) { 3417 retval = MOVE_MOVING_BLOCK; 3418 } else { 3419 3420 /* 3421 ** Enemy infantry have reserved the cell. If this unit can crush 3422 ** infantry, consider the cell passable. If not, then consider the 3423 ** cell destroyable if it has a weapon. If neither case applies, then 3424 ** this vehicle should avoid the cell altogether. 3425 */ 3426 if (!Class->IsCrusher) { 3427 if (Class->PrimaryWeapon != NULL && Class->PrimaryWeapon->Bullet->IsAntiGround) { 3428 retval = MOVE_DESTROYABLE; 3429 } else { 3430 return(MOVE_NO); 3431 } 3432 } 3433 } 3434 } 3435 } 3436 3437 /* 3438 ** If its ok to move into the cell because we can crush whats in the cell, then 3439 ** make sure no one else is already moving into the cell to crush something. 3440 */ 3441 if (retval == MOVE_OK && crushable && cellptr->Flag.Occupy.Vehicle) { 3442 3443 /* 3444 ** However, if the cell is occupied by a crushable vehicle, then we can 3445 ** never be sure if some other friendly vehicle is also trying to crush 3446 ** the cell at the same time. In the case of a crushable vehicle in the 3447 ** cell, then allow entry. 3448 */ 3449 if (!cellptr->Cell_Unit() || !cellptr->Cell_Unit()->Class->IsCrushable) { 3450 return(MOVE_MOVING_BLOCK); 3451 } 3452 } 3453 3454 /* 3455 ** Return with the most severe reason why this cell would be impassable. 3456 */ 3457 return(retval); 3458 } 3459 3460 3461 /*********************************************************************************************** 3462 * UnitClass::Init -- Clears all units for scenario preparation. * 3463 * * 3464 * This routine will zero out the unit list and unit objects. This routine is typically * 3465 * used in preparation for a new scenario load. All units are guaranteed to be eliminated * 3466 * by this routine. * 3467 * * 3468 * INPUT: none * 3469 * * 3470 * OUTPUT: none * 3471 * * 3472 * WARNINGS: none * 3473 * * 3474 * HISTORY: * 3475 * 08/15/1994 JLB : Created. * 3476 *=============================================================================================*/ 3477 void UnitClass::Init(void) 3478 { 3479 Units.Free_All(); 3480 } 3481 3482 3483 /*********************************************************************************************** 3484 * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * 3485 * * 3486 * This routine will start the vehicle moving by marking the destination cell as * 3487 * reserved. Cells must be reserved in this fashion or else they might become occupied as * 3488 * the vehicle is proceeding toward it. * 3489 * * 3490 * INPUT: headto -- The location where the vehicle will be heading. * 3491 * * 3492 * OUTPUT: bool; Was the vehicle started to move? Failure could be the result of the cell * 3493 * being occupied. * 3494 * * 3495 * WARNINGS: none * 3496 * * 3497 * HISTORY: * 3498 * 12/22/1994 JLB : Created. * 3499 *=============================================================================================*/ 3500 bool UnitClass::Start_Driver(COORDINATE & headto) 3501 { 3502 assert(Units.ID(this) == ID); 3503 assert(IsActive); 3504 3505 if (DriveClass::Start_Driver(headto) && IsActive) {//BG IsActive can be cleared by Start_Driver 3506 Mark_Track(headto, MARK_DOWN); 3507 return(true); 3508 } 3509 return(false); 3510 } 3511 3512 3513 /*********************************************************************************************** 3514 * UnitClass::What_Action -- Determines what action would occur if clicked on object. * 3515 * * 3516 * Use this function to determine what action would likely occur if the specified object * 3517 * were clicked on while this unit was selected as current. This function controls, not * 3518 * only the action to perform, but indirectly controls the cursor shape to use as well. * 3519 * * 3520 * INPUT: object -- The object that to check for against "this" object. * 3521 * * 3522 * OUTPUT: Returns with the default action to perform. If no clear action can be determined, * 3523 * then ACTION_NONE is returned. * 3524 * * 3525 * WARNINGS: none * 3526 * * 3527 * HISTORY: * 3528 * 01/11/1995 JLB : Created. * 3529 *=============================================================================================*/ 3530 ActionType UnitClass::What_Action(ObjectClass const * object) const 3531 { 3532 assert(Units.ID(this) == ID); 3533 assert(IsActive); 3534 3535 ActionType action = DriveClass::What_Action(object); 3536 3537 /* 3538 ** Allow units to move onto land mines. 3539 */ 3540 if (action == ACTION_NONE && object->What_Am_I() == RTTI_BUILDING) { 3541 StructType blah = *((BuildingClass *)object); 3542 if (blah == STRUCT_AVMINE || blah == STRUCT_APMINE) return(ACTION_MOVE); 3543 } 3544 3545 /* 3546 ** If the unit doesn't have a weapon, but can crush the object, then consider 3547 ** the object as a movable location. 3548 */ 3549 if (action == ACTION_ATTACK && !Can_Player_Fire()) { 3550 if (Class->IsCrusher && object->Class_Of().IsCrushable) { 3551 action = ACTION_MOVE; 3552 } else { 3553 action = ACTION_SELECT; 3554 } 3555 } 3556 3557 /* 3558 ** Don't allow special deploy action unless there is something to deploy. 3559 */ 3560 if (action == ACTION_SELF) { 3561 if (*this == UNIT_MCV) { 3562 3563 /* 3564 ** The MCV will get the no-deploy cursor if it couldn't 3565 ** deploy at its current location. 3566 */ 3567 ((ObjectClass &)(*this)).Mark(MARK_UP); 3568 if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { 3569 action = ACTION_NO_DEPLOY; 3570 } 3571 ((ObjectClass &)(*this)).Mark(MARK_DOWN); 3572 3573 } else { 3574 3575 /* 3576 ** The mine layer can "deploy" its mines if it currently isn't 3577 ** sitting on top of a mine and it still has mines available. 3578 */ 3579 if (*this == UNIT_MINELAYER) { 3580 if (!Ammo || Map[Center_Coord()].Cell_Building() || (Map[Center_Coord()].Smudge != SMUDGE_NONE && SmudgeTypeClass::As_Reference(Map[Center_Coord()].Smudge).IsBib)) { 3581 action = ACTION_NO_DEPLOY; 3582 } 3583 } else { 3584 #ifdef FIXIT_CSII // checked - ajw 9/28/98 3585 if (*this == UNIT_CHRONOTANK || *this == UNIT_MAD) { 3586 if (*this == UNIT_CHRONOTANK) { 3587 // If the chrono tank's counter is still charging up, don't allow deploy. Or, 3588 // if it's a player-controlled chrono tank, and the player's currently trying 3589 // to teleport a different unit, don't allow teleporting this unit. 3590 if(MoebiusCountDown || (IsOwnedByPlayer && House->UnitToTeleport && Map.IsTargettingMode == SPC_CHRONO2)) { 3591 action = ACTION_NO_DEPLOY; 3592 } 3593 } 3594 } else { 3595 #endif 3596 /* 3597 ** All other units can "deploy" their passengers if they in-fact have 3598 ** passengers and are a transport vehicle. Otherwise, they cannot 3599 ** perform any self action. 3600 */ 3601 if (Class->Max_Passengers() > 0) { 3602 if (How_Many() == 0) { 3603 action = ACTION_NO_DEPLOY; 3604 } 3605 } else { 3606 action = ACTION_NONE; 3607 } 3608 #ifdef FIXIT_CSII // checked - ajw 9/28/98 3609 } 3610 #endif 3611 } 3612 } 3613 } 3614 3615 /* 3616 ** Special return to friendly refinery action. 3617 */ 3618 bool is_player_controlled = (Session.Type == GAME_NORMAL) 3619 ? (House->IsPlayerControl && object->Owner() != HOUSE_NONE && HouseClass::As_Pointer(object->Owner())->IsPlayerControl) 3620 : (Is_Owned_By_Player() && House->Class->House == object->Owner()); 3621 if (is_player_controlled && object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { 3622 action = ACTION_ENTER; 3623 } 3624 3625 /* 3626 ** Special return to friendly repair factory action. 3627 */ 3628 if (is_player_controlled && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { 3629 BuildingClass * building = (BuildingClass *)object; 3630 if (building->Class->Type == STRUCT_REPAIR && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, building) == RADIO_ROGER && !building->In_Radio_Contact() && !building->Is_Something_Attached()) { 3631 action = ACTION_MOVE; 3632 } 3633 } 3634 3635 /* 3636 ** Check to see if it can enter a transporter. 3637 */ 3638 if ( 3639 House->Is_Ally(object) && 3640 House->IsPlayerControl && object->Is_Techno() && object->What_Am_I() == RTTI_VESSEL) { 3641 #ifdef FIXIT_CARRIER // checked - ajw 9/28/98 3642 if( *(VesselClass *)object != VESSEL_CARRIER) { 3643 #endif 3644 switch (((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object)) { 3645 case RADIO_ROGER: 3646 action = ACTION_ENTER; 3647 break; 3648 3649 case RADIO_NEGATIVE: 3650 action = ACTION_NO_ENTER; 3651 break; 3652 3653 default: 3654 action = ACTION_NONE; 3655 break; 3656 } 3657 #ifdef FIXIT_CARRIER // checked - ajw 9/28/98 3658 } 3659 #endif 3660 } 3661 3662 #ifdef FIXIT_CSII // checked - ajw 9/28/98 3663 if (*this == UNIT_MAD && (IsDumping || Gold)) { 3664 action = ACTION_NONE; 3665 } 3666 #endif 3667 /* 3668 ** If it doesn't know what to do with the object, then just 3669 ** say it can't move there. 3670 */ 3671 if (action == ACTION_NONE) action = ACTION_NOMOVE; 3672 3673 return(action); 3674 } 3675 3676 3677 /*********************************************************************************************** 3678 * UnitClass::What_Action -- Determines action to perform on specified cell. * 3679 * * 3680 * This routine will determine what action to perform if the mouse were clicked over the * 3681 * cell specified. At the unit level, only the harvester is checked for. The lower * 3682 * classes determine the regular action response. * 3683 * * 3684 * INPUT: cell -- The cell that the mouse might be clicked on. * 3685 * * 3686 * OUTPUT: Returns with the action type that this unit will perform if the mouse were * 3687 * clicked of the cell specified. * 3688 * * 3689 * WARNINGS: none * 3690 * * 3691 * HISTORY: * 3692 * 09/21/1995 JLB : Created. * 3693 *=============================================================================================*/ 3694 ActionType UnitClass::What_Action(CELL cell) const 3695 { 3696 assert(Units.ID(this) == ID); 3697 assert(IsActive); 3698 3699 ActionType action = DriveClass::What_Action(cell); 3700 if (action == ACTION_MOVE && Map[cell].Land_Type() == LAND_TIBERIUM && Class->IsToHarvest) { 3701 return(ACTION_HARVEST); 3702 } 3703 #ifdef FIXIT_CSII // checked - ajw 9/28/98 3704 if (*this == UNIT_MAD && (IsDumping || Gold)) { 3705 action = ACTION_NOMOVE; 3706 } 3707 #endif 3708 return(action); 3709 } 3710 3711 3712 /*********************************************************************************************** 3713 * UnitClass::Exit_Repair -- Drive the unit off the repair facility. * 3714 * * 3715 * INPUT: none * 3716 * * 3717 * OUTPUT: none * 3718 * * 3719 * WARNINGS: none * 3720 * * 3721 * HISTORY: * 3722 * 04/03/1995 BWG : Created. * 3723 *=============================================================================================*/ 3724 #define XYCELL(x, y) (y*MAP_CELL_W+x) 3725 void UnitClass::Exit_Repair(void) 3726 { 3727 assert(Units.ID(this) == ID); 3728 assert(IsActive); 3729 3730 int i; 3731 CELL cell; 3732 bool found = false; 3733 static short const ExitRepair[] = { 3734 XYCELL(0, -2), 3735 XYCELL(1, -1), 3736 XYCELL(2, 0), 3737 XYCELL(1, 1), 3738 XYCELL(0, 2), 3739 XYCELL(-1, 1), 3740 XYCELL(-2, 0), 3741 XYCELL(-1, -1) 3742 }; 3743 3744 cell = Coord_Cell(Coord) + ExitRepair[Dir_Facing(PrimaryFacing.Current())]; 3745 if (Can_Enter_Cell(cell) == MOVE_OK) found = true; 3746 3747 if (!found) for (i=0; i<8; i++) { 3748 cell = Coord_Cell(Coord) + ExitRepair[i]; 3749 if (Can_Enter_Cell(cell) == MOVE_OK) { 3750 found = true; 3751 break; 3752 } 3753 } 3754 if (found) { 3755 // DirType dir = Direction(cell); 3756 3757 Assign_Mission(MISSION_MOVE); 3758 Assign_Destination(::As_Target(cell)); 3759 } 3760 } 3761 3762 3763 /*********************************************************************************************** 3764 * UnitClass::Mission_Guard -- Special guard mission override processor. * 3765 * * 3766 * Handles the guard mission for the unit. If the IQ is high enough and the unit is * 3767 * a harvester, it will begin to harvest automatically. An MCV might autodeploy. * 3768 * * 3769 * INPUT: none * 3770 * * 3771 * OUTPUT: Returns the time delay before this command is executed again. * 3772 * * 3773 * WARNINGS: none * 3774 * * 3775 * HISTORY: * 3776 * 05/08/1995 JLB : Created. * 3777 * 05/08/1995 JLB : Fixes gunboat problems. * 3778 *=============================================================================================*/ 3779 int UnitClass::Mission_Guard(void) 3780 { 3781 assert(Units.ID(this) == ID); 3782 assert(IsActive); 3783 if (/*House->IsBaseBuilding &&*/ !House->IsHuman && Class->IsToHarvest && House->Get_Quantity(STRUCT_REFINERY) > 0 && !House->IsTiberiumShort) { 3784 Assign_Mission(MISSION_HARVEST); 3785 return(1); 3786 // return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2)); 3787 } 3788 3789 if (*this == UNIT_MCV && House->IsBaseBuilding) { 3790 Assign_Mission(MISSION_UNLOAD); 3791 return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2)); 3792 } 3793 return(DriveClass::Mission_Guard()); 3794 } 3795 3796 3797 /*********************************************************************************************** 3798 * UnitClass::Mission_Move -- Handles special move mission overrides. * 3799 * * 3800 * This routine intercepts the normal move mission and if a gunboat is being processed, * 3801 * changes its mission to hunt. This is an attempt to keep the gunboat on the hunt mission * 3802 * regardless of what the player did. * 3803 * * 3804 * INPUT: none * 3805 * * 3806 * OUTPUT: Returns the number of ticks before this routine should be called again. * 3807 * * 3808 * WARNINGS: none * 3809 * * 3810 * HISTORY: * 3811 * 05/09/1995 JLB : Created. * 3812 * 09/28/1995 JLB : Harvester stick in guard mode if no more refineries. * 3813 *=============================================================================================*/ 3814 int UnitClass::Mission_Move(void) 3815 { 3816 assert(Units.ID(this) == ID); 3817 assert(IsActive); 3818 3819 IsHarvesting = false; 3820 3821 /* 3822 ** Always make sure that that transport door is closed if the vehicle is moving. 3823 */ 3824 if (!Is_Door_Closed()) { 3825 APC_Close_Door(); 3826 } 3827 3828 return(DriveClass::Mission_Move()); 3829 } 3830 3831 3832 int UnitClass::Mission_Enter(void) 3833 { 3834 assert(Units.ID(this) == ID); 3835 assert(IsActive); 3836 3837 if (Class->IsToHarvest) { 3838 TechnoClass * contact = Contact_With_Whom(); 3839 if (contact == NULL) { 3840 contact = As_Techno(ArchiveTarget); 3841 } 3842 if (contact != NULL && 3843 contact->What_Am_I() == RTTI_BUILDING && 3844 *((BuildingClass*)contact) == STRUCT_REFINERY) { 3845 TiberiumUnloadRefinery = contact->As_Target(); 3846 } 3847 } 3848 3849 return(DriveClass::Mission_Enter()); 3850 } 3851 3852 3853 /*********************************************************************************************** 3854 * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * 3855 * * 3856 * This routine examines the unit and adjacent cells in order to find the best facing * 3857 * for the transport and best staging cell for the potential passengers. This location is * 3858 * modified by adjacent cell passability and direction of the potential passenger. * 3859 * * 3860 * INPUT: passenger -- Pointer to the potential passenger. * 3861 * * 3862 * moveto -- Reference to the cell number that specifies where the potential * 3863 * passenger should move to first. * 3864 * * 3865 * OUTPUT: Returns with the direction the transport should face before opening the transport * 3866 * door. * 3867 * * 3868 * WARNINGS: none * 3869 * * 3870 * HISTORY: * 3871 * 05/23/1995 JLB : Created. * 3872 *=============================================================================================*/ 3873 DirType UnitClass::Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const 3874 { 3875 assert(Units.ID(this) == ID); 3876 assert(IsActive); 3877 3878 /* 3879 ** Determine the ideal facing that provides the least resistance. This would be the direction 3880 ** of the potential passenger or the current transport facing if it is going to unload. 3881 */ 3882 DirType faceto; 3883 if (passenger != NULL) { 3884 faceto = Direction(passenger); 3885 } else { 3886 faceto = PrimaryFacing.Current() + DIR_S; 3887 } 3888 3889 /* 3890 ** Sweep through the adjacent cells in order to find the best candidate. 3891 */ 3892 FacingType bestdir = FACING_N; 3893 int bestval = -1; 3894 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 3895 int value = 0; 3896 CELL cellnum = Adjacent_Cell(Coord_Cell(Coord), face); 3897 3898 /* 3899 ** Base the initial value of the potential cell according to whether the passenger is 3900 ** allowed to enter the cell. If it can't, then give such a negative value to the 3901 ** cell so that it is prevented from ever choosing that cell for load/unload. 3902 */ 3903 if (passenger != NULL) { 3904 value = (passenger->Can_Enter_Cell(cellnum) == MOVE_OK || Coord_Cell(passenger->Coord) == cellnum) ? 128 : -128; 3905 } else { 3906 CellClass * cell = &Map[cellnum]; 3907 if (Ground[cell->Land_Type()].Cost[SPEED_FOOT] == 0 || cell->Flag.Occupy.Building || cell->Flag.Occupy.Vehicle || cell->Flag.Occupy.Monolith || (cell->Flag.Composite & 0x01F) == 0x01F) { 3908 value = -128; 3909 } else { 3910 if (cell->Cell_Techno() && !House->Is_Ally(cell->Cell_Techno())) { 3911 value = -128; 3912 } else { 3913 value = 128; 3914 } 3915 } 3916 } 3917 3918 /* 3919 ** Give more weight to the cells that require the least rotation of the transport or the 3920 ** least roundabout movement for the potential passenger. 3921 */ 3922 value -= (int)ABS((int)(signed char)Facing_Dir(face) - (int)(signed char)faceto); 3923 if (face == FACING_S) { 3924 value -= 100; 3925 } 3926 if (face == FACING_SW || face == FACING_SE) value += 64; 3927 3928 /* 3929 ** If the value for the potential cell is greater than the last recorded potential 3930 ** value, then record this cell as the best candidate. 3931 */ 3932 if (bestval == -1 || value > bestval) { 3933 bestval = value; 3934 bestdir = face; 3935 } 3936 } 3937 3938 /* 3939 ** If a suitable direction was found, then return with the direction value. 3940 */ 3941 moveto = 0; 3942 if (bestval > 0) { 3943 static DirType _desired_to_actual[FACING_COUNT] = {DIR_S, DIR_SW, DIR_NW, DIR_NW, DIR_NE, DIR_NE, DIR_NE, DIR_SE}; 3944 3945 moveto = Adjacent_Cell(Coord_Cell(Coord), bestdir); 3946 return(_desired_to_actual[bestdir]); 3947 } 3948 return(DIR_S); 3949 } 3950 3951 3952 /*********************************************************************************************** 3953 * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * 3954 * * 3955 * This routine will attach a house flag to this unit. * 3956 * * 3957 * INPUT: house -- The house that is having its flag attached to it. * 3958 * * 3959 * OUTPUT: Was the house flag successfully attached to this unit? * 3960 * * 3961 * WARNINGS: A unit can only carry one flag at a time. This might be a reason for failure * 3962 * of this routine. * 3963 * * 3964 * HISTORY: * 3965 * 05/23/1995 JLB : Created. * 3966 *=============================================================================================*/ 3967 bool UnitClass::Flag_Attach(HousesType house) 3968 { 3969 assert(Units.ID(this) == ID); 3970 assert(IsActive); 3971 3972 if (house != HOUSE_NONE && Flagged == HOUSE_NONE) { 3973 Flagged = house; 3974 Mark(MARK_CHANGE); 3975 return(true); 3976 } 3977 return(false); 3978 } 3979 3980 3981 /*********************************************************************************************** 3982 * UnitClass::Flag_Remove -- Removes the house flag from this unit. * 3983 * * 3984 * This routine will remove the house flag that is attached to this unit. * 3985 * * 3986 * INPUT: none * 3987 * * 3988 * OUTPUT: Was the flag successfully removed? * 3989 * * 3990 * WARNINGS: This routine doesn't put the flag into a new location. That operation must * 3991 * be performed or else the house flag will cease to exist. * 3992 * * 3993 * HISTORY: * 3994 * 05/23/1995 JLB : Created. * 3995 *=============================================================================================*/ 3996 bool UnitClass::Flag_Remove(void) 3997 { 3998 assert(Units.ID(this) == ID); 3999 assert(IsActive); 4000 4001 if (Flagged != HOUSE_NONE) { 4002 Flagged = HOUSE_NONE; 4003 Mark(MARK_CHANGE); 4004 return(true); 4005 } 4006 return(false); 4007 } 4008 4009 4010 /*********************************************************************************************** 4011 * UnitClass::Pip_Count -- Fetches the number of pips to display on unit. * 4012 * * 4013 * This routine is used to fetch the number of "fullness" pips to display on the unit. * 4014 * This will either be the number of passengers or the percentage full (in 1/5ths) of * 4015 * a harvester. * 4016 * * 4017 * INPUT: none * 4018 * * 4019 * OUTPUT: Returns with the number of pips to draw on this unit. * 4020 * * 4021 * WARNINGS: none * 4022 * * 4023 * HISTORY: * 4024 * 06/25/1995 JLB : Created. * 4025 *=============================================================================================*/ 4026 int UnitClass::Pip_Count(void) const 4027 { 4028 assert(Units.ID(this) == ID); 4029 assert(IsActive); 4030 4031 if (Class->Max_Passengers() > 0) { 4032 return(How_Many()); 4033 } 4034 4035 if (*this == UNIT_MINELAYER) { 4036 int retval = 0; 4037 if (Ammo > 0) { 4038 retval = Class->Max_Pips() * fixed(Ammo, Class->MaxAmmo); 4039 if (!retval) retval = 1; 4040 } 4041 return(retval); 4042 } 4043 4044 if (*this == UNIT_HARVESTER) { 4045 return((Gold + Gems) / 4); 4046 } 4047 4048 #ifdef FIXIT_CSII // checked - ajw 9/28/98 4049 if (*this == UNIT_CHRONOTANK) { 4050 int fulldur = ChronoTankDuration * TICKS_PER_MINUTE; 4051 return( (fulldur - MoebiusCountDown) / (fulldur / 5)); 4052 } 4053 #endif 4054 return(0); 4055 } 4056 4057 4058 /*********************************************************************************************** 4059 * UnitClass::APC_Close_Door -- Closes an APC door. * 4060 * * 4061 * This routine will initiate closing of the APC door. * 4062 * * 4063 * INPUT: none * 4064 * * 4065 * OUTPUT: none * 4066 * * 4067 * WARNINGS: none * 4068 * * 4069 * HISTORY: * 4070 * 06/25/1995 JLB : Created. * 4071 *=============================================================================================*/ 4072 void UnitClass::APC_Close_Door(void) 4073 { 4074 assert(Units.ID(this) == ID); 4075 assert(IsActive); 4076 4077 Close_Door(10, 2); 4078 } 4079 4080 4081 /*********************************************************************************************** 4082 * UnitClass::APC_Open_Door -- Opens an APC door. * 4083 * * 4084 * This routine will initiate opening of the APC door. * 4085 * * 4086 * INPUT: none * 4087 * * 4088 * OUTPUT: none * 4089 * * 4090 * WARNINGS: none * 4091 * * 4092 * HISTORY: * 4093 * 06/25/1995 JLB : Created. * 4094 *=============================================================================================*/ 4095 void UnitClass::APC_Open_Door(void) 4096 { 4097 assert(Units.ID(this) == ID); 4098 assert(IsActive); 4099 4100 if (!IsDriving && !IsRotating) { 4101 if (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE) { 4102 Open_Door(10, 2); 4103 } else { 4104 Open_Door(1, 2); 4105 } 4106 } 4107 } 4108 4109 4110 /*********************************************************************************************** 4111 * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * 4112 * * 4113 * When a unit is destroyed, a crew member might be generated. This routine will return * 4114 * with the infantry type to produce for this unit. This routine will be called for every * 4115 * survivor that is generated. * 4116 * * 4117 * INPUT: none * 4118 * * 4119 * OUTPUT: Returns with a suggested infantry type to generate as a survivor from this unit. * 4120 * * 4121 * WARNINGS: none * 4122 * * 4123 * HISTORY: * 4124 * 08/13/1995 JLB : Created. * 4125 *=============================================================================================*/ 4126 InfantryType UnitClass::Crew_Type(void) const 4127 { 4128 assert(Units.ID(this) == ID); 4129 assert(IsActive); 4130 4131 if (Class->PrimaryWeapon == NULL) { 4132 if (Percent_Chance(50)) { 4133 return(INFANTRY_C1); 4134 } else { 4135 return(INFANTRY_C7); 4136 } 4137 } 4138 return(DriveClass::Crew_Type()); 4139 } 4140 4141 4142 /*********************************************************************************************** 4143 * UnitClass::Mission_Repair -- Handles finding and proceeding on a repair mission. * 4144 * * 4145 * This mission handler will look for a repair facility. If one is found then contact * 4146 * is established and then the normal Mission_Enter logic is performed. The repair facility * 4147 * will take over the actual repair coordination process. * 4148 * * 4149 * INPUT: none * 4150 * * 4151 * OUTPUT: Returns the number of game frames to delay before calling this routine again. * 4152 * * 4153 * WARNINGS: none * 4154 * * 4155 * HISTORY: * 4156 * 10/02/1995 JLB : Created. * 4157 *=============================================================================================*/ 4158 int UnitClass::Mission_Repair(void) 4159 { 4160 assert(Units.ID(this) == ID); 4161 assert(IsActive); 4162 4163 BuildingClass * nearest = Find_Docking_Bay(STRUCT_REFINERY, true); 4164 4165 IsHarvesting = false; 4166 4167 /* 4168 ** If there is no available repair facility, then check to see if there 4169 ** are any repair facilities at all. If not, then enter this unit 4170 ** into idle state. 4171 */ 4172 if (nearest == NULL) { 4173 if (!(House->ActiveBScan & STRUCTF_REFINERY)) { 4174 Enter_Idle_Mode(); 4175 } 4176 } else { 4177 4178 /* 4179 ** Try to establish radio contact with the repair facility. If contact 4180 ** was established, then proceed with normal enter mission, which handles 4181 ** the repair process. 4182 */ 4183 if (Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { 4184 Assign_Mission(MISSION_ENTER); 4185 return(1); 4186 } 4187 } 4188 4189 /* 4190 ** If no action could be performed at this time, then wait 4191 ** around for a bit before trying again. 4192 */ 4193 return(MissionControl[Mission].Normal_Delay()); 4194 } 4195 4196 4197 /*********************************************************************************************** 4198 * UnitClass::Fire_Direction -- Determines the direction of firing. * 4199 * * 4200 * This routine will return with the facing that a projectile will travel if it was * 4201 * fired at this instant. The facing should match the turret facing for those units * 4202 * equipped with a turret. If the unit doesn't have a turret, then it will be the facing * 4203 * of the body. * 4204 * * 4205 * INPUT: none * 4206 * * 4207 * OUTPUT: Returns with the default firing direction for a projectile. * 4208 * * 4209 * WARNINGS: none * 4210 * * 4211 * HISTORY: * 4212 * 06/25/1995 JLB : Created. * 4213 *=============================================================================================*/ 4214 DirType UnitClass::Fire_Direction(void) const 4215 { 4216 assert(Units.ID(this) == ID); 4217 assert(IsActive); 4218 4219 if (Class->IsTurretEquipped) { 4220 if (*this == UNIT_V2_LAUNCHER) { 4221 int diff1 = SecondaryFacing.Difference(DIR_E); 4222 int diff2 = SecondaryFacing.Difference(DIR_W); 4223 diff1 = ABS(diff1); 4224 diff2 = ABS(diff2); 4225 int diff = min(diff1, diff2); 4226 int adj = Fixed_To_Cardinal(ABS(SecondaryFacing.Difference(DIR_N)), 64-diff); 4227 if (SecondaryFacing.Difference(DIR_N) < 0) { 4228 return(DirType)(SecondaryFacing - (DirType)adj); 4229 } else { 4230 return(DirType)(SecondaryFacing + (DirType)adj); 4231 } 4232 } 4233 return(SecondaryFacing.Current()); 4234 } 4235 4236 return(DriveClass::Fire_Direction()); 4237 } 4238 4239 4240 /*********************************************************************************************** 4241 * UnitClass::Ok_To_Move -- Queries whether the vehicle can move. * 4242 * * 4243 * This virtual routine is used to determine if the vehicle is allowed * 4244 * to start moving. It is typically called when the vehicle desires * 4245 * to move but needs confirmation from the turret logic before * 4246 * proceeding. This happens when dealing with a vehicle that must have * 4247 * its turret face the same direction as the body before the vehicle * 4248 * may begin movement. * 4249 * * 4250 * INPUT: dir -- The facing the unit wants to travel in. * 4251 * * 4252 * OUTPUT: bool; Can the unit begin forward movement now? * 4253 * * 4254 * WARNINGS: none * 4255 * * 4256 * HISTORY: * 4257 * 05/12/1994 JLB : Created. * 4258 *=============================================================================================*/ 4259 bool UnitClass::Ok_To_Move(DirType dir) const 4260 { 4261 assert(Units.ID(this) == ID); 4262 assert(IsActive); 4263 4264 if (Class->IsLockTurret) { 4265 if (IsRotating) { 4266 return(false); 4267 } else { 4268 if (SecondaryFacing.Difference(dir)) { 4269 ((UnitClass *)this)->SecondaryFacing.Set_Desired(dir); 4270 return(false); 4271 } 4272 } 4273 } 4274 return(true); 4275 } 4276 4277 4278 /*********************************************************************************************** 4279 * UnitClass::Can_Fire -- Determines if turret can fire upon target. * 4280 * * 4281 * This routine determines if the turret can fire upon the target * 4282 * specified. * 4283 * * 4284 * INPUT: target -- The target to fire upon. * 4285 * * 4286 * which -- Which weapon to use to determine legality to fire. 0=primary, * 4287 * 1=secondary. * 4288 * * 4289 * OUTPUT: Returns the fire status type that indicates if firing is allowed and if not, why. * 4290 * * 4291 * WARNINGS: none * 4292 * * 4293 * HISTORY: * 4294 * 04/26/1994 JLB : Created. * 4295 * 06/01/1994 JLB : Returns reason why it can't fire. * 4296 *=============================================================================================*/ 4297 FireErrorType UnitClass::Can_Fire(TARGET target, int which) const 4298 { 4299 assert(Units.ID(this) == ID); 4300 assert(IsActive); 4301 4302 DirType dir; // The facing to impart upon the projectile. 4303 int diff; 4304 FireErrorType fire = DriveClass::Can_Fire(target, which); 4305 4306 if (fire == FIRE_OK) { 4307 WeaponTypeClass const * weapon = (which == 0) ? Class->PrimaryWeapon : Class->SecondaryWeapon; 4308 4309 /* 4310 ** If this unit cannot fire while moving, then bail. 4311 */ 4312 if ((Class->IsNoFireWhileMoving /*!Class->IsTurretEquipped || Class->IsLockTurret*/) && Target_Legal(NavCom)) { 4313 return(FIRE_MOVING); 4314 } 4315 4316 /* 4317 ** If the turret is rotating and the projectile isn't a homing type, then 4318 ** firing must be delayed until the rotation stops. 4319 */ 4320 if (!IsFiring && IsRotating && weapon->Bullet->ROT == 0) { 4321 return(FIRE_ROTATING); 4322 } 4323 4324 dir = Direction(target); 4325 4326 /* 4327 ** Determine if the turret facing isn't too far off of facing the target. 4328 */ 4329 if (Class->IsTurretEquipped) { 4330 diff = SecondaryFacing.Difference(dir); 4331 } else { 4332 diff = PrimaryFacing.Difference(dir); 4333 } 4334 diff = ABS(diff); 4335 4336 if (weapon->Bullet->ROT != 0) { 4337 diff >>= 2; 4338 } 4339 if (diff < 8) { 4340 return(DriveClass::Can_Fire(target, which)); 4341 } 4342 return(FIRE_FACING); 4343 } 4344 return(fire); 4345 } 4346 4347 4348 /*********************************************************************************************** 4349 * UnitClass::Fire_At -- Try to fire upon the target specified. * 4350 * * 4351 * This routine is the auto-fire logic for the turret. It will check * 4352 * to see if firing is technically legal given the specified target. * 4353 * If it is legal to fire, it does so. It is safe to call this routine * 4354 * every game tick. * 4355 * * 4356 * INPUT: target -- The target to fire upon. * 4357 * * 4358 * which -- Which weapon to use when firing. 0=primary, 1=secondary. * 4359 * * 4360 * OUTPUT: bool; Did firing occur? * 4361 * * 4362 * WARNINGS: none * 4363 * * 4364 * HISTORY: * 4365 * 04/26/1994 JLB : Created. * 4366 *=============================================================================================*/ 4367 BulletClass * UnitClass::Fire_At(TARGET target, int which) 4368 { 4369 assert(Units.ID(this) == ID); 4370 assert(IsActive); 4371 4372 BulletClass * bullet = NULL; 4373 WeaponTypeClass const * weap = (which == 0) ? Class->PrimaryWeapon : Class->SecondaryWeapon; 4374 if (weap == NULL) return(NULL); 4375 4376 if (Can_Fire(target, which) == FIRE_OK) { 4377 bullet = DriveClass::Fire_At(target, which); 4378 4379 if (bullet != NULL) { 4380 #ifdef FIXIT_CSII // checked - ajw 9/28/98 4381 if(Class->Type == UNIT_DEMOTRUCK && IsActive) delete this; 4382 #endif 4383 /* 4384 ** Possible reload timer set. 4385 */ 4386 if ((*this == UNIT_V2_LAUNCHER) && Reload == 0) { 4387 Reload = TICKS_PER_SECOND * 30; 4388 } 4389 } 4390 } 4391 4392 return(bullet); 4393 } 4394 4395 4396 /*********************************************************************************************** 4397 * UnitClass::Class_Of -- Fetches a reference to the class type for this object. * 4398 * * 4399 * This routine will fetch a reference to the TypeClass of this object. * 4400 * * 4401 * INPUT: none * 4402 * * 4403 * OUTPUT: Returns with reference to the type class of this object. * 4404 * * 4405 * WARNINGS: none * 4406 * * 4407 * HISTORY: * 4408 * 07/29/1995 JLB : Created. * 4409 *=============================================================================================*/ 4410 ObjectTypeClass const & UnitClass::Class_Of(void) const 4411 { 4412 assert(Units.ID(this) == ID); 4413 assert(IsActive); 4414 4415 return(*Class); 4416 } 4417 4418 4419 /*********************************************************************************************** 4420 * UnitClass::Tiberium_Load -- Determine the Tiberium load as a percentage. * 4421 * * 4422 * Use this routine to determine what the Tiberium load is (as a fixed point percentage). * 4423 * * 4424 * INPUT: none * 4425 * * 4426 * OUTPUT: Returns with the current "fullness" rating for the object. * 4427 * * 4428 * WARNINGS: none * 4429 * * 4430 * HISTORY: * 4431 * 03/17/1995 JLB : Created. * 4432 *=============================================================================================*/ 4433 fixed UnitClass::Tiberium_Load(void) const 4434 { 4435 assert(IsActive); 4436 4437 if (*this == UNIT_HARVESTER) { 4438 return(fixed(Tiberium, Rule.BailCount)); 4439 } 4440 return(0); 4441 } 4442 4443 4444 BuildingClass* UnitClass::Find_Best_Refinery(void) const 4445 { 4446 /* 4447 ** Remember our last refinery and prefer that one, if still available and valid. 4448 */ 4449 if (Target_Legal(TiberiumUnloadRefinery)) { 4450 BuildingClass * refinery = As_Building(TiberiumUnloadRefinery); 4451 if (refinery != NULL && 4452 refinery->House == House && 4453 !refinery->IsInLimbo && 4454 refinery->Mission != MISSION_DECONSTRUCTION && 4455 *refinery == STRUCT_REFINERY && 4456 Map[refinery->Center_Coord()].Zones[Techno_Type_Class()->MZone] == Map[Center_Coord()].Zones[Techno_Type_Class()->MZone]) { 4457 return refinery; 4458 } else { 4459 TiberiumUnloadRefinery = TARGET_NONE; 4460 } 4461 } 4462 4463 /* 4464 ** Find nearby refinery and head to it? 4465 */ 4466 return Find_Docking_Bay(STRUCT_REFINERY, false); 4467 } 4468 4469 4470 /*********************************************************************************************** 4471 * UnitClass::Offload_Tiberium_Bail -- Offloads one Tiberium quantum from the object. * 4472 * * 4473 * This routine will offload one Tiberium packet/quantum/bail from the object. Multiple * 4474 * calls to this routine are needed in order to fully offload all Tiberium. * 4475 * * 4476 * INPUT: none * 4477 * * 4478 * OUTPUT: Returns with the number of credits offloaded for the one call. If zero is returned,* 4479 * then this indicates that all Tiberium has been offloaded. * 4480 * * 4481 * WARNINGS: none * 4482 * * 4483 * HISTORY: * 4484 * 07/19/1995 JLB : Created. * 4485 *=============================================================================================*/ 4486 int UnitClass::Offload_Tiberium_Bail(void) 4487 { 4488 assert(IsActive); 4489 4490 if (Tiberium) { 4491 Tiberium--; 4492 4493 // MBL 05.15.2020: Note, if the below code is ever reeanbled for some ready, make sure to see fix in 4494 // Tiberian Dawn's DriveClass::Offload_Tiberium_Bail() for AI players 4495 4496 #ifdef TOFIX 4497 if (House->IsHuman) { 4498 return(UnitTypeClass::FULL_LOAD_CREDITS/UnitTypeClass::STEP_COUNT); 4499 } 4500 return(UnitTypeClass::FULL_LOAD_CREDITS+(UnitTypeClass::FULL_LOAD_CREDITS/3)/UnitTypeClass::STEP_COUNT); 4501 #endif 4502 } 4503 return(0); 4504 } 4505 4506 4507 /*********************************************************************************************** 4508 * UnitClass::Approach_Target -- Handles approaching the target in order to attack it. * 4509 * * 4510 * This routine will check to see if the target is infantry and it can be overrun. It will * 4511 * try to overrun the infantry rather than attack it. This only applies to computer * 4512 * controlled vehicles. If it isn't the infantry overrun case, then it falls into the * 4513 * base class for normal (complex) approach algorithm. * 4514 * * 4515 * INPUT: none * 4516 * * 4517 * OUTPUT: none * 4518 * * 4519 * WARNINGS: none * 4520 * * 4521 * HISTORY: * 4522 * 03/17/1995 JLB : Created. * 4523 * 07/12/1995 JLB : Flamethrower tanks don't overrun -- their weapon is better. * 4524 *=============================================================================================*/ 4525 void UnitClass::Approach_Target(void) 4526 { 4527 assert(IsActive); 4528 4529 /* 4530 ** Only if there is a legal target should the approach check occur. 4531 */ 4532 if (!House->IsHuman && Target_Legal(TarCom) && !Target_Legal(NavCom)) { 4533 4534 /* 4535 ** Special case: 4536 ** If this is for a unit that can crush infantry, and the target is 4537 ** infantry, AND the infantry is pretty darn close, then just try 4538 ** to drive over the infantry instead of firing on it. 4539 */ 4540 TechnoClass * target = As_Techno(TarCom); 4541 if (Class->IsCrusher && Distance(TarCom) < Rule.CrushDistance && target && ((TechnoTypeClass const &)(target->Class_Of())).IsCrushable) { 4542 Assign_Destination(TarCom); 4543 return; 4544 } 4545 } 4546 4547 /* 4548 ** In the other cases, uses the more complex "get to just within weapon range" 4549 ** algorithm. 4550 */ 4551 DriveClass::Approach_Target(); 4552 } 4553 4554 4555 /*********************************************************************************************** 4556 * DriveClass::Overrun_Square -- Handles vehicle overrun of a cell. * 4557 * * 4558 * This routine is called when a vehicle enters a square or when it is about to enter a * 4559 * square (controlled by parameter). When a vehicle that can crush infantry enters a * 4560 * cell that contains infantry, then the infantry will be destroyed (regardless of * 4561 * affiliation). When a vehicle threatens to overrun a square, all occupying infantry * 4562 * will attempt to get out of the way. * 4563 * * 4564 * INPUT: cell -- The cell that is, or soon will be, entered by a vehicle. * 4565 * * 4566 * threaten -- Don't kill, but just threaten to enter the cell. * 4567 * * 4568 * OUTPUT: none * 4569 * * 4570 * WARNINGS: none * 4571 * * 4572 * HISTORY: * 4573 * 01/19/1995 JLB : Created. * 4574 *=============================================================================================*/ 4575 void UnitClass::Overrun_Square(CELL cell, bool threaten) 4576 { 4577 assert(IsActive); 4578 4579 CellClass * cellptr = &Map[cell]; 4580 4581 if (Class->IsCrusher) { 4582 if (threaten) { 4583 4584 /* 4585 ** If the cell contains infantry, then they will panic when a vehicle tries 4586 ** drive over them. Have the infantry run away instead. 4587 */ 4588 if (cellptr->Flag.Composite & 0x1F) { 4589 4590 /* 4591 ** Scattering is controlled by the game difficulty level. 4592 */ 4593 cellptr->Incoming(0, true); 4594 } 4595 } else { 4596 ObjectClass * object = cellptr->Cell_Occupier(); 4597 int crushed = false; 4598 while (object != NULL) { 4599 if (object->Class_Of().IsCrushable && !House->Is_Ally(object) && Distance(object->Center_Coord()) < CELL_LEPTON_W/2) { 4600 4601 #ifdef OBSOLETE 4602 /* 4603 ** If we're running over infantry, let's see if the infantry we're 4604 ** squashing is a thief trying to capture us. If so, let him succeed. 4605 */ 4606 if (object->What_Am_I() == RTTI_INFANTRY && *((InfantryClass *)object) == INFANTRY_THIEF && ((InfantryClass *)object)->NavCom == As_Target()) { 4607 ObjectClass * next = object->Next; 4608 IsOwnedByPlayer = ((InfantryClass *)object)->IsOwnedByPlayer; 4609 House = ((InfantryClass *)object)->House; 4610 delete object; 4611 object = next; 4612 } else { 4613 #endif 4614 ObjectClass * next = object->Next; 4615 crushed = true; 4616 4617 /* 4618 ** Record credit for the kill(s) 4619 */ 4620 Sound_Effect(VOC_SQUISH, Coord); 4621 if (object->Height == 0) { 4622 AnimClass* anim = new AnimClass(ANIM_CORPSE1, object->Center_Coord()); 4623 if (anim != NULL) { 4624 anim->Set_Owner(object->Owner()); 4625 } 4626 } 4627 object->Record_The_Kill(this); 4628 object->Mark(MARK_UP); 4629 object->Limbo(); 4630 delete object; 4631 //new OverlayClass(OVERLAY_SQUISH, Coord_Cell(Coord)); 4632 4633 object = next; 4634 #ifdef OBSOLETE 4635 } 4636 #endif 4637 } else { 4638 object = object->Next; 4639 } 4640 } 4641 if (crushed) Do_Uncloak(); 4642 } 4643 } 4644 } 4645 4646 4647 /*********************************************************************************************** 4648 * UnitClass::Assign_Destination -- Assign a destination to a unit. * 4649 * * 4650 * This will assign the specified destination to the unit. It is presumed that doing is * 4651 * is all that is needed in order to cause the unit to move to the specified destination. * 4652 * * 4653 * INPUT: target -- The target (location) to move to. * 4654 * * 4655 * OUTPUT: none * 4656 * * 4657 * WARNINGS: none * 4658 * * 4659 * HISTORY: * 4660 * 07/09/1996 JLB : Created. * 4661 *=============================================================================================*/ 4662 void UnitClass::Assign_Destination(TARGET target) 4663 { 4664 assert(IsActive); 4665 4666 /* 4667 ** Abort early if there is anything wrong with the parameters 4668 ** or the unit already is assigned the specified destination. 4669 */ 4670 if (target == NavCom) return; 4671 4672 /* 4673 ** Transport vehicles must tell all passengers that are about to load, that they 4674 ** cannot proceed. This is accomplished with a radio message to this effect. 4675 */ 4676 if (In_Radio_Contact() && Class->Max_Passengers() > 0 && Contact_With_Whom()->Is_Infantry()) { 4677 Transmit_Message(RADIO_OVER_OUT); 4678 } 4679 4680 BuildingClass * b = As_Building(target); 4681 4682 /* 4683 ** Handle entry logic here. 4684 */ 4685 if (Mission == MISSION_ENTER || MissionQueue == MISSION_ENTER) { 4686 4687 /* 4688 ** If not already in radio contact (presumed with the transport), then 4689 ** either try to establish contact if allowed, or just move close and 4690 ** wait until radio contact can be established. 4691 */ 4692 if (!In_Radio_Contact()) { 4693 if (b != NULL) { 4694 4695 /* 4696 ** Determine if the transport is already in radio contact. If so, then just move 4697 ** toward the transport and try to establish contact at a later time. 4698 */ 4699 if (b->In_Radio_Contact()) { 4700 // TCTCTC -- call for an update from the transport to get a good rendezvous position. 4701 ArchiveTarget = target; 4702 4703 /* 4704 ** HACK ALERT: The repair bay is counting on the assignment of the NavCom by this routine. 4705 ** The refinery must NOT have the navcom assigned by this routine. 4706 */ 4707 if (*b != STRUCT_REPAIR) { 4708 target = TARGET_NONE; 4709 } 4710 } else { 4711 if (Transmit_Message(RADIO_DOCKING, b) != RADIO_ROGER) { 4712 Transmit_Message(RADIO_OVER_OUT); 4713 if (*b == STRUCT_REPAIR) { 4714 ArchiveTarget = target; 4715 } 4716 } 4717 if (*b != STRUCT_REPAIR) { 4718 ArchiveTarget = target; 4719 target = TARGET_NONE; 4720 } 4721 } 4722 } else { 4723 TechnoClass * techno = As_Techno(target); 4724 if (techno != NULL) { 4725 4726 /* 4727 ** Determine if the transport is already in radio contact. If so, then just move 4728 ** toward the transport and try to establish contact at a later time. 4729 */ 4730 if (techno->In_Radio_Contact()) { 4731 // TCTCTC -- call for an update from the transport to get a good rendezvous position. 4732 4733 ArchiveTarget = target; 4734 } else { 4735 if (Transmit_Message(RADIO_HELLO, techno) == RADIO_ROGER) { 4736 if (Transmit_Message(RADIO_DOCKING) != RADIO_ROGER) { 4737 Transmit_Message(RADIO_OVER_OUT); 4738 } else { 4739 //BG: keep retransmitted navcom from radio-move-here. 4740 return; 4741 } 4742 } 4743 } 4744 } 4745 4746 } 4747 } else { 4748 Path[0] = FACING_NONE; 4749 } 4750 } else { 4751 Path[0] = FACING_NONE; 4752 } 4753 4754 /* 4755 ** If the player clicked on a friendly repair facility and the repair 4756 ** facility is currently not involved with some other unit (radio or unloading). 4757 */ 4758 if (b != NULL && *b == STRUCT_REPAIR) { 4759 if (b->In_Radio_Contact() && (b->Contact_With_Whom() != this) ) { 4760 // if (target != NULL) { 4761 ArchiveTarget = target; 4762 // } 4763 // target = TARGET_NONE; 4764 } else { 4765 4766 /* 4767 ** Establish radio contact protocol. If the facility responds correctly, 4768 ** then remain in radio contact and proceed toward the desired destination. 4769 */ 4770 if (Transmit_Message(RADIO_HELLO, b) == RADIO_ROGER) { 4771 4772 /* 4773 ** Last check to make sure that the loading square is free from permanent 4774 ** occupation (such as a building). 4775 */ 4776 CELL cell = (CELL)(Coord_Cell(b->Center_Coord()) + (MAP_CELL_W-1)); 4777 if (Ground[Map[cell].Land_Type()].Cost[Techno_Type_Class()->Speed] > 0) { 4778 if (Transmit_Message(RADIO_DOCKING) == RADIO_ROGER) { 4779 FootClass::Assign_Destination(target); 4780 Path[0] = FACING_NONE; 4781 return; 4782 } 4783 4784 /* 4785 ** Failure to establish a docking relationship with the refinery. 4786 ** Bail & await further instructions. 4787 */ 4788 Transmit_Message(RADIO_OVER_OUT); 4789 } 4790 } 4791 } 4792 } 4793 4794 DriveClass::Assign_Destination(target); 4795 } 4796 4797 4798 /*********************************************************************************************** 4799 * UnitClass::Greatest_Threat -- Fetches the greatest threat for this unit. * 4800 * * 4801 * This routine will search the map looking for a good target to attack. It takes into * 4802 * consideration the type of weapon it is equipped with. * 4803 * * 4804 * INPUT: threat -- The threat type to search for. * 4805 * * 4806 * OUTPUT: Returns with a target value of the target that this unit should pursue. If there * 4807 * is no suitable target, then TARGET_NONE is returned. * 4808 * * 4809 * WARNINGS: none * 4810 * * 4811 * HISTORY: * 4812 * 07/09/1996 JLB : Created. * 4813 *=============================================================================================*/ 4814 TARGET UnitClass::Greatest_Threat(ThreatType threat) const 4815 { 4816 assert(IsActive); 4817 if (Class->PrimaryWeapon != NULL) { 4818 threat = threat | Class->PrimaryWeapon->Allowed_Threats(); 4819 } 4820 if (Class->SecondaryWeapon != NULL) { 4821 threat = threat | Class->SecondaryWeapon->Allowed_Threats(); 4822 } 4823 4824 #ifdef OBSOLETE 4825 if (House->IsHuman) { 4826 threat = threat & ~THREAT_BUILDINGS; 4827 } 4828 #endif 4829 4830 return(FootClass::Greatest_Threat(threat)); 4831 } 4832 4833 4834 /*********************************************************************************************** 4835 * UnitClass::Read_INI -- Reads units from scenario INI file. * 4836 * * 4837 * This routine is used to read all the starting units from the * 4838 * scenario control INI file. The units are created and placed on the * 4839 * map by this routine. * 4840 * * 4841 * INI entry format: * 4842 * Housename, Typename, Strength, Coord, Facingnum, Missionname, Triggername * 4843 * * 4844 * INPUT: buffer -- Pointer to the loaded scenario INI file. * 4845 * * 4846 * OUTPUT: none * 4847 * * 4848 * WARNINGS: none * 4849 * * 4850 * HISTORY: * 4851 * 05/24/1994 JLB : Created. * 4852 *=============================================================================================*/ 4853 void UnitClass::Read_INI(CCINIClass & ini) 4854 { 4855 UnitClass * unit; // Working unit pointer. 4856 HousesType inhouse; // Unit house. 4857 UnitType classid; // Unit class. 4858 char buf[128]; 4859 4860 int len = ini.Entry_Count(INI_Name()); 4861 4862 for (int index = 0; index < len; index++) { 4863 char const * entry = ini.Get_Entry(INI_Name(), index); 4864 4865 ini.Get_String(INI_Name(), entry, NULL, buf, sizeof(buf)); 4866 4867 inhouse = HouseTypeClass::From_Name(strtok(buf, ",")); 4868 if (inhouse != HOUSE_NONE) { 4869 classid = UnitTypeClass::From_Name(strtok(NULL, ",")); 4870 4871 if (classid != UNIT_NONE) { 4872 4873 if (HouseClass::As_Pointer(inhouse) != NULL) { 4874 unit = new UnitClass(classid, inhouse); 4875 if (unit != NULL) { 4876 4877 /* 4878 ** Read the raw data. 4879 */ 4880 int strength = atoi(strtok(NULL, ",\r\n")); 4881 4882 CELL cell = atoi(strtok(NULL, ",\r\n")); 4883 4884 COORDINATE coord = Cell_Coord(cell); 4885 4886 DirType dir = (DirType)atoi(strtok(NULL, ",\r\n")); 4887 MissionType mission = MissionClass::Mission_From_Name(strtok(NULL, ",\n\r")); 4888 4889 unit->Trigger = NULL; 4890 TriggerTypeClass * tp = TriggerTypeClass::From_Name(strtok(NULL,",\r\n")); 4891 if (tp != NULL) { 4892 TriggerClass * tt = Find_Or_Make(tp); 4893 if (tt != NULL) { 4894 tt->AttachCount++; 4895 unit->Trigger = tt; 4896 } 4897 } 4898 4899 if (unit->Unlimbo(coord, dir)) { 4900 unit->Strength = (int)unit->Class->MaxStrength * fixed(strength, 256); 4901 if (unit->Strength > unit->Class->MaxStrength-3) unit->Strength = unit->Class->MaxStrength; 4902 if (Session.Type == GAME_NORMAL || unit->House->IsHuman) { 4903 unit->Assign_Mission(mission); 4904 unit->Commence(); 4905 } else { 4906 unit->Enter_Idle_Mode(); 4907 } 4908 4909 } else { 4910 4911 /* 4912 ** If the unit could not be unlimboed, then this is a catastrophic error 4913 ** condition. Delete the unit. 4914 */ 4915 delete unit; 4916 } 4917 } 4918 } 4919 } 4920 } 4921 } 4922 } 4923 4924 4925 /*********************************************************************************************** 4926 * UnitClass::Write_INI -- Store the units to the INI database. * 4927 * * 4928 * This routine will store all the unit data to the INI database. * 4929 * * 4930 * INPUT: ini -- Reference to the INI database object to store to. * 4931 * * 4932 * OUTPUT: none * 4933 * * 4934 * WARNINGS: none * 4935 * * 4936 * HISTORY: * 4937 * 07/03/1996 JLB : Created. * 4938 *=============================================================================================*/ 4939 void UnitClass::Write_INI(CCINIClass & ini) 4940 { 4941 /* 4942 ** First, clear out all existing unit data from the ini file. 4943 */ 4944 ini.Clear(INI_Name()); 4945 4946 /* 4947 ** Write the unit data out. 4948 */ 4949 for (int index = 0; index < Units.Count(); index++) { 4950 UnitClass * unit = Units.Ptr(index); 4951 if (unit != NULL && !unit->IsInLimbo && unit->IsActive) { 4952 char uname[10]; 4953 char buf[128]; 4954 4955 sprintf(uname, "%d", index); 4956 sprintf(buf, "%s,%s,%d,%u,%d,%s,%s", 4957 unit->House->Class->IniName, 4958 unit->Class->IniName, 4959 unit->Health_Ratio()*256, 4960 Coord_Cell(unit->Coord), 4961 unit->PrimaryFacing.Current(), 4962 MissionClass::Mission_Name(unit->Mission), 4963 unit->Trigger.Is_Valid() ? unit->Trigger->Class->IniName : "None" 4964 ); 4965 ini.Put_String(INI_Name(), uname, buf); 4966 } 4967 } 4968 } 4969 4970 4971 /*********************************************************************************************** 4972 * UnitClass::Credit_Load -- Fetch the full credit value of cargo carried. * 4973 * * 4974 * This will determine the value of the cargo carried (limited to considering only gold * 4975 * and gems) and return that value. Use this to determine how 'valuable' a harvester is. * 4976 * * 4977 * INPUT: none * 4978 * * 4979 * OUTPUT: Returns with the credit value of the cargo load of this unit (harvester). * 4980 * * 4981 * WARNINGS: none * 4982 * * 4983 * HISTORY: * 4984 * 07/29/1996 JLB : Created. * 4985 *=============================================================================================*/ 4986 int UnitClass::Credit_Load(void) const 4987 { 4988 return((Gold * Rule.GoldValue) + (Gems * Rule.GemValue)); 4989 } 4990 4991 4992 /*********************************************************************************************** 4993 * UnitClass::Should_Crush_It -- Determines if this unit should crush an object. * 4994 * * 4995 * Call this routine to determine if this unit should crush the object specified. The * 4996 * test for crushable action depends on proximity and ability of the unit. If a unit * 4997 * should crush the object, then it should be given a movement order to enter the cell * 4998 * where the object is located. * 4999 * * 5000 * INPUT: it -- The object to see if it should be crushed. * 5001 * * 5002 * OUTPUT: bool; Should "it" be crushed by this unit? * 5003 * * 5004 * WARNINGS: none * 5005 * * 5006 * HISTORY: * 5007 * 07/29/1996 JLB : Created. * 5008 *=============================================================================================*/ 5009 bool UnitClass::Should_Crush_It(TechnoClass const * it) const 5010 { 5011 assert(IsActive); 5012 5013 /* 5014 ** If this unit cannot crush anything or the candidate object cannot be crushed, 5015 ** then it obviously should not try to crush it -- return negative answer. 5016 */ 5017 if (!Class->IsCrusher || it == NULL || !it->Techno_Type_Class()->IsCrushable) return(false); 5018 5019 /* 5020 ** Objects that are far away should really be fired upon rather than crushed. 5021 */ 5022 if (Distance(it) > Rule.CrushDistance) return(false); 5023 5024 /* 5025 ** Human controlled units don't automatically crush. Neither do computer controlled ones 5026 ** if they are at difficult setting. 5027 */ 5028 if (House->IsHuman || House->Difficulty == DIFF_HARD) return(false); 5029 5030 /* 5031 ** If the weapon this unit is equipped with is very good against crushable objects then 5032 ** fire the weapon instead. It is presumed that a wood destroying weapon is good against 5033 ** most crushable object types (infantry). 5034 */ 5035 if (Class->PrimaryWeapon != NULL && Class->PrimaryWeapon->WarheadPtr->IsWoodDestroyer) return(false); 5036 5037 /* 5038 ** If the house IQ indicates that crushing should not be allowed, then don't 5039 ** suggest that crushing be done. 5040 */ 5041 if (House->IQ < Rule.IQCrush) return(false); 5042 5043 /* 5044 ** Don't allow crushing of spies by computer-controlled vehicles. 5045 */ 5046 if (it->What_Am_I() == RTTI_INFANTRY && *(InfantryClass *)it == INFANTRY_SPY) { 5047 return(false); 5048 } 5049 5050 return(true); 5051 } 5052 5053 5054 /*********************************************************************************************** 5055 * UnitClass::Scatter -- Causes the unit to scatter to a nearby location. * 5056 * * 5057 * This scatter logic will actually look for a nearby location rather than an adjacent * 5058 * free location. This is necessary because sometimes a unit is required to scatter more * 5059 * than one cell. A vehicle on a service depot is a prime example. * 5060 * * 5061 * INPUT: threat -- The coordinate that a potential threat resides. If this is a non * 5062 * threat related scatter, then this parameter will be zero. * 5063 * * 5064 * forced -- Should the scatter be performed even if it would be otherwise * 5065 * inconvenient? * 5066 * * 5067 * nokidding-- Should the scatter be performed even if it would otherwise be * 5068 * illegal? * 5069 * * 5070 * OUTPUT: none * 5071 * * 5072 * WARNINGS: none * 5073 * * 5074 * HISTORY: * 5075 * 10/02/1996 JLB : Created. * 5076 *=============================================================================================*/ 5077 void UnitClass::Scatter(COORDINATE threat, bool forced, bool nokidding) 5078 { 5079 assert(IsActive); 5080 5081 if (Mission == MISSION_SLEEP || Mission == MISSION_STICKY || Mission == MISSION_UNLOAD) return; 5082 5083 /* 5084 ** Certain missions prevent scattering regardless of whether it would be 5085 ** a good idea or not. 5086 */ 5087 if (!MissionControl[Mission].IsScatter && !forced) return; 5088 5089 if (PrimaryFacing.Is_Rotating()) return; 5090 // if (IsRotating) return; 5091 5092 if (Target_Legal(NavCom) && !nokidding) return; 5093 5094 if (threat == 0) { 5095 Assign_Destination(::As_Target(Map.Nearby_Location(Coord_Cell(Coord), Class->Speed))); 5096 } else { 5097 DriveClass::Scatter(threat, forced, nokidding); 5098 } 5099 } 5100 5101 5102 /*********************************************************************************************** 5103 * UnitClass::Limbo -- Limbo this unit. * 5104 * * 5105 * This will cause the unit to go into a limbo state. If it was carrying a flag, then * 5106 * the flag will be dropped where the unit is at. * 5107 * * 5108 * INPUT: none * 5109 * * 5110 * OUTPUT: bool; Was this unit limboed? * 5111 * * 5112 * WARNINGS: none * 5113 * * 5114 * HISTORY: * 5115 * 10/08/1996 JLB : Created. * 5116 *=============================================================================================*/ 5117 bool UnitClass::Limbo(void) 5118 { 5119 if (DriveClass::Limbo()) { 5120 if (Flagged != HOUSE_NONE) { 5121 HouseClass::As_Pointer(Flagged)->Flag_Attach(Coord_Cell(Coord)); 5122 Flagged = HOUSE_NONE; 5123 } 5124 return(true); 5125 } 5126 return(false); 5127 } 5128 5129 5130 5131 /*********************************************************************************************** 5132 * UnitClass::Apply_Temporary_Jamming_Shroud -- Apply a temporary gap generator shroud effect * 5133 * * 5134 * This is intended as a temporary effect that is active only during export of the * 5135 * shroud data * 5136 * * 5137 * INPUT: House to apply effect for * 5138 * * 5139 * OUTPUT: Bitmask of cells that effect was applied to * 5140 * * 5141 * WARNINGS: none * 5142 * * 5143 * HISTORY: * 5144 * 8/19/2020 12:13PM ST : Created. * 5145 *=============================================================================================*/ 5146 unsigned int UnitClass::Apply_Temporary_Jamming_Shroud(HouseClass *house_to_apply_for) 5147 { 5148 unsigned int shroud_bits_applied = 0; 5149 5150 if (!IsActive || !Strength) { 5151 return shroud_bits_applied; 5152 } 5153 5154 if (!Class->IsGapper) { 5155 return shroud_bits_applied; 5156 } 5157 5158 CELL shroud_center = Coord_Cell(Center_Coord()); 5159 int centerx = Cell_X(shroud_center); 5160 int centery = Cell_Y(shroud_center); 5161 CELL trycell; 5162 5163 for (int index = 0; index < 31; index++) { 5164 shroud_bits_applied <<= 1; 5165 trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); 5166 if (Map[trycell].Is_Mapped(house_to_apply_for)) { 5167 Map.Jam_Cell(trycell, House); 5168 shroud_bits_applied |= 1; 5169 } 5170 } 5171 5172 if (shroud_bits_applied) { 5173 Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, house_to_apply_for); 5174 } 5175 5176 return shroud_bits_applied; 5177 } 5178 5179 5180 5181 /*********************************************************************************************** 5182 * UnitClass::Unapply_Temporary_Jamming_Shroud -- Remove temporary gap generator shroud effect * 5183 * * 5184 * Remove gap effect added by Apply_Temporary_Jamming_Shroud * 5185 * * 5186 * INPUT: House to unapply effect for * 5187 * Bitmask of cells that effect was applied to * 5188 * * 5189 * OUTPUT: * 5190 * * 5191 * WARNINGS: none * 5192 * * 5193 * HISTORY: * 5194 * 8/19/2020 12:16PM ST : Created. * 5195 *=============================================================================================*/ 5196 void UnitClass::Unapply_Temporary_Jamming_Shroud(HouseClass *house_to_unapply_for, unsigned int shroud_bits_applied) 5197 { 5198 if (!IsActive || !Strength) { 5199 return; 5200 } 5201 5202 if (!Class->IsGapper) { 5203 return; 5204 } 5205 5206 CELL shroud_center = Coord_Cell(Center_Coord()); 5207 int centerx = Cell_X(shroud_center); 5208 int centery = Cell_Y(shroud_center); 5209 CELL trycell; 5210 5211 for (int index = 30; index >= 0 && shroud_bits_applied; index--) { 5212 if (shroud_bits_applied & 1) { 5213 trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); 5214 Map.UnJam_Cell(trycell, House); 5215 Map.Map_Cell(trycell, house_to_unapply_for); 5216 } 5217 shroud_bits_applied >>= 1; 5218 } 5219 } 5220 5221 5222 5223 /* 5224 ** Updated for client/server multiplayer - ST 8/12/2019 11:46AM 5225 */ 5226 void UnitClass::Shroud_Regen(void) 5227 { 5228 if (Class->IsGapper/*KO && !House->IsPlayerControl*/) { 5229 5230 int index; 5231 int centerx, centery; 5232 CELL trycell; 5233 5234 if (Session.Type != GAME_GLYPHX_MULTIPLAYER || Is_Legacy_Render_Enabled()) { 5235 // Only restore under the shroud if it's a valid field. 5236 if (ShroudBits != (unsigned)-1L) { 5237 centerx = Cell_X(ShroudCenter); 5238 centery = Cell_Y(ShroudCenter); 5239 for (index = 30; index >= 0 && ShroudBits; index--) { 5240 if (ShroudBits & 1) { 5241 trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); 5242 #if(0) 5243 Map.Map_Cell(trycell, PlayerPtr); 5244 #else 5245 Map.UnJam_Cell(trycell, House); 5246 Map.Map_Cell(trycell, House); 5247 #endif 5248 } 5249 ShroudBits >>= 1; 5250 } 5251 } 5252 5253 if(IsActive && Strength) { 5254 // Now shroud around the new center 5255 ShroudBits = 0L; 5256 ShroudCenter = Coord_Cell(Center_Coord()); 5257 centerx = Cell_X(ShroudCenter); 5258 centery = Cell_Y(ShroudCenter); 5259 for (index = 0; index < 31; index++) { 5260 ShroudBits <<= 1; 5261 trycell = XY_Cell(centerx + _GapShroudXTable[index], centery + _GapShroudYTable[index]); 5262 if (Map[trycell].Is_Mapped(House)) { 5263 Map.Jam_Cell(trycell, House); 5264 ShroudBits |= 1; 5265 } 5266 } 5267 } 5268 } 5269 5270 /* 5271 ** Updated for client/server multiplayer. ST - 8/12/2019 3:25PM 5272 */ 5273 if (Session.Type != GAME_GLYPHX_MULTIPLAYER) { 5274 if (House->IsPlayerControl) { 5275 Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, PlayerPtr); 5276 } 5277 5278 } else { 5279 5280 if (Is_Legacy_Render_Enabled()) { 5281 for (int i = 0; i < Session.Players.Count(); i++) { 5282 HouseClass *player_house = HouseClass::As_Pointer(Session.Players[i]->Player.ID); 5283 if (player_house->IsHuman && player_house != House) { 5284 Map.Constrained_Look(Coord, 5 * CELL_LEPTON_W, player_house); 5285 } 5286 } 5287 } 5288 } 5289 } 5290 } 5291 5292 5293 /*********************************************************************************************** 5294 * UnitClass::Mission_Guard_Area -- Guard area logic for units. * 5295 * * 5296 * This logic is similar to normal guard area except that APCs owned by the computer will * 5297 * try to load up with nearby infantry. This will give the computer some fake intelligence * 5298 * when playing in skirmish mode. * 5299 * * 5300 * INPUT: none * 5301 * * 5302 * OUTPUT: Returns with the delay to use before calling this routine again. * 5303 * * 5304 * WARNINGS: none * 5305 * * 5306 * HISTORY: * 5307 * 11/03/1996 JLB : Created. * 5308 *=============================================================================================*/ 5309 int UnitClass::Mission_Guard_Area(void) 5310 { 5311 assert(IsActive); 5312 5313 /* 5314 ** Check to see if this is an APC that is largely empty and not otherwise doing anything. 5315 ** Such an APC should load up with infantry. 5316 */ 5317 if (Session.Type != GAME_NORMAL && 5318 #ifdef FIXIT_PHASETRANSPORT // checked - ajw 9/28/98 5319 (*this == UNIT_APC || *this == UNIT_PHASE ) && 5320 #else 5321 *this == UNIT_APC && 5322 #endif 5323 !Target_Legal(TarCom) && 5324 !In_Radio_Contact() && 5325 House->Which_Zone(this) != ZONE_NONE && 5326 !House->IsHuman) { 5327 5328 5329 int needed = Class->Max_Passengers() - How_Many(); 5330 for (int index = 0; index < Infantry.Count(); index++) { 5331 if (needed == 0) break; 5332 5333 InfantryClass * infantry = Infantry.Ptr(index); 5334 5335 if (infantry != NULL && 5336 infantry->IsActive && 5337 !infantry->IsInLimbo && 5338 infantry->Strength > 0 && 5339 infantry->House == House && 5340 !Target_Legal(infantry->TarCom) && 5341 !Target_Legal(infantry->NavCom) && 5342 Distance(infantry) < 7 * CELL_LEPTON_W && 5343 (infantry->Mission == MISSION_GUARD || infantry->Mission == MISSION_GUARD_AREA)) { 5344 5345 infantry->Assign_Mission(MISSION_ENTER); 5346 infantry->ArchiveTarget = As_Target(); 5347 needed--; 5348 } 5349 } 5350 } 5351 return(DriveClass::Mission_Guard_Area()); 5352 }