UNIT.CPP (189228B)
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: F:\projects\c&c\vcs\code\unit.cpv 2.17 16 Oct 1995 16:48:28 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 : August 16, 1995 [JLB] * 30 * * 31 *---------------------------------------------------------------------------------------------* 32 * Functions: * 33 * Recoil_Adjust -- Adjust pixel values in direction specified. * 34 * Turret_Adjust -- Turret adjustment routine for MLRS and MSAM units. * 35 * UnitClass::AI -- AI processing for the unit. * 36 * UnitClass::APC_Close_Door -- Closes an APC door. * 37 * UnitClass::APC_Open_Door -- Opens an APC door. * 38 * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possib* 39 * UnitClass::As_Target -- Returns the unit as a target value. * 40 * UnitClass::Blocking_Object -- Determines how a object blocks a unit * 41 * UnitClass::Can_Enter_Building -- Determines building entry legality. * 42 * UnitClass::Can_Fire -- Determines if this unit can fire. * 43 * UnitClass::Can_Player_Move -- Determines if the player is legally allowed to move it. * 44 * UnitClass::Click_With -- Handles player map clicking while this unit is selected. * 45 * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * 46 * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * 47 * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * 48 * UnitClass::Draw_It -- Draws a unit object. * 49 * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * 50 * UnitClass::Find_LZ -- Maintenance function for transport units. * 51 * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * 52 * UnitClass::Flag_Remove -- Removes the house flag from this unit. * 53 * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * 54 * UnitClass::Goto_Tiberium -- Search for and head toward nearest available Tiberium patch. * 55 * UnitClass::Harvesting -- Harvests tiberium at the current location. * 56 * UnitClass::Init -- Clears all units for scenario preparation. * 57 * UnitClass::Limbo -- Prepares vehicle and then limbos it. * 58 * UnitClass::Look -- Perform map revelation from a unit's position. * 59 * UnitClass::Mission_Attack -- Handles the mission attack logic. * 60 * UnitClass::Mission_Guard -- Special guard mission override processor. * 61 * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * 62 * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * 63 * UnitClass::Mission_Move -- Handles special move mission overrides. * 64 * UnitClass::Mission_Unload -- Handles unloading cargo. * 65 * UnitClass::Overlap_List -- Determines overlap list for units. * 66 * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * 67 * UnitClass::Pip_Count -- Fetchs the number of pips to display on unit. * 68 * UnitClass::Random_Animate -- Handles random idle animation for the unit. * 69 * UnitClass::Read_INI -- Reads units from scenario INI file. * 70 * UnitClass::Receive_Message -- Handles receiving a radio message. * 71 * UnitClass::Remap_Table -- Fetches the remap table to use for this object. * 72 * UnitClass::Response_Attack -- Voice feedback when ordering the unit to attack a target. * 73 * UnitClass::Response_Move -- Voice feedback when ordering the unit to move. * 74 * UnitClass::Response_Select -- Voice feedback when selecting the unit. * 75 * UnitClass::Scatter -- Causes the unit to travel to a nearby safe cell. * 76 * UnitClass::Set_Speed -- Initiate unit movement physics. * 77 * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * 78 * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * 79 * UnitClass::Stop_Driver -- Handles removing occupation bits when driving stops. * 80 * UnitClass::Stun -- Stuns the unit in preparation for unit removal. * 81 * UnitClass::Take_Damage -- Inflicts damage points on a unit. * 82 * UnitClass::Target_Coord -- The coordinate to use when targeting this unit. * 83 * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * 84 * UnitClass::UnitClass -- Constructor for units. * 85 * UnitClass::Unlimbo -- Removes unit from stasis. * 86 * UnitClass::Unload_Hovercraft_Process -- Handles unloading hovercraft transport. * 87 * UnitClass::Validate -- validates unit pointer. * 88 * UnitClass::What_Action -- Determines what action would occur if clicked on object. * 89 * UnitClass::What_Am_I -- Returns with the RTTI type this object is. * 90 * UnitClass::Write_INI -- Writes all the units out to an INI file. * 91 * UnitClass::delete -- Deletion operator for units. * 92 * UnitClass::new -- Allocate a unit slot and adjust access arrays. * 93 * UnitClass::~UnitClass -- Destructor for unit objects. * 94 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 95 96 #include "function.h" 97 98 /* 99 ** This contains the value of the Virtual Function Table Pointer 100 */ 101 void * UnitClass::VTable; 102 103 104 /*********************************************************************************************** 105 * UnitClass::Validate -- validates unit pointer. * 106 * * 107 * INPUT: * 108 * none. * 109 * * 110 * OUTPUT: * 111 * 1 = ok, 0 = error * 112 * * 113 * WARNINGS: * 114 * none. * 115 * * 116 * HISTORY: * 117 * 08/09/1995 BRR : Created. * 118 *=============================================================================================*/ 119 #ifdef CHEAT_KEYS 120 int UnitClass::Validate(void) const 121 { 122 int num; 123 124 num = Units.ID(this); 125 if (num < 0 || num >= UNIT_MAX) { 126 Validate_Error("UNIT"); 127 return (0); 128 } 129 else 130 return (1); 131 } 132 #else 133 #define Validate() 134 #endif 135 136 137 /*********************************************************************************************** 138 * Recoil_Adjust -- Adjust pixel values in direction specified. * 139 * * 140 * This is a helper routine that modifies the pixel coordinates provided according to the * 141 * direction specified. The effect is the simulate recoil effects by moving an object 'back'* 142 * one pixel. Since the pixels moved depend on facing, this routine handles the pixel * 143 * adjustment quickly. * 144 * * 145 * INPUT: dir -- The direction to base the recoil on. * 146 * * 147 * x,y -- References to the pixel coordinates that will be adjusted. * 148 * * 149 * OUTPUT: none * 150 * * 151 * WARNINGS: none * 152 * * 153 * HISTORY: * 154 * 05/08/1995 JLB : Created. * 155 *=============================================================================================*/ 156 void Recoil_Adjust(DirType dir, int &x, int &y) 157 { 158 static struct { 159 signed char X,Y; 160 } _adjust[32] = { 161 {0,1}, // N 162 {0,1}, 163 {0,1}, 164 {-1,1}, 165 {-1,1}, // NE 166 {-1,1}, 167 {-1,0}, 168 {-1,0}, 169 {-1,0}, // E 170 {-1,0}, 171 {-1,-1}, 172 {-1,-1}, 173 {-1,-1}, // SE 174 {-1,-1}, 175 {-1,-1}, 176 {0,-1}, 177 {0,-1}, // S 178 {0,-1}, 179 {0,-1}, 180 {1,-1}, 181 {1,-1}, // SW 182 {1,-1}, 183 {1,0}, 184 {1,0}, 185 {1,0}, // W 186 {1,0}, 187 {1,1}, 188 {1,1}, 189 {1,1}, // NW 190 {1,1}, 191 {0,1}, 192 {0,1} 193 }; 194 195 int index = Facing_To_32(dir); 196 x += _adjust[index].X; 197 y += _adjust[index].Y; 198 } 199 200 201 /*********************************************************************************************** 202 * Turret_Adjust -- Turret adjustment routine for MLRS and MSAM units. * 203 * * 204 * This routine adjusts the pixel coordinates specified to account for the displacement of * 205 * the turret on the MLRS and MSAM vehicles. * 206 * * 207 * INPUT: dir -- The direction of the body of the vehicle. * 208 * * 209 * x,y -- References to the turret center pixel position. These will be modified as * 210 * necessary. * 211 * * 212 * OUTPUT: none * 213 * * 214 * WARNINGS: none * 215 * * 216 * HISTORY: * 217 * 05/08/1995 JLB : Created. * 218 *=============================================================================================*/ 219 void Turret_Adjust(DirType dir, int &x, int &y) 220 { 221 static struct { 222 signed char X,Y; 223 } _adjust[32] = { 224 {1,2}, // N 225 {-1,1}, 226 {-2,0}, 227 {-3,0}, 228 {-3,1}, // NW 229 {-4,-1}, 230 {-4,-1}, 231 {-5,-2}, 232 {-5,-3}, // W 233 {-5,-3}, 234 {-3,-3}, 235 {-3,-4}, 236 {-3,-4}, // SW 237 {-3,-5}, 238 {-2,-5}, 239 {-1,-5}, 240 {0,-5}, // S 241 {1,-6}, 242 {2,-5}, 243 {3,-5}, 244 {4,-5}, // SE 245 {6,-4}, 246 {6,-3}, 247 {6,-3}, 248 {6,-3}, // E 249 {5,-1}, 250 {5,-1}, 251 {4,0}, 252 {3,0}, // NE 253 {2,0}, 254 {2,1}, 255 {1,2} 256 }; 257 258 int index = Facing_To_32(dir); 259 x += _adjust[index].X; 260 y += _adjust[index].Y; 261 } 262 263 264 /*********************************************************************************************** 265 * UnitClass::As_Target -- Returns the unit as a target value. * 266 * * 267 * This routine will convert the unit into a target value that is then returned. Target * 268 * values are typically used for navigation and other computer uses. * 269 * * 270 * INPUT: none * 271 * * 272 * OUTPUT: Returns with target value of unit. * 273 * * 274 * WARNINGS: none * 275 * * 276 * HISTORY: * 277 * 09/19/1994 JLB : Created. * 278 *=============================================================================================*/ 279 TARGET UnitClass::As_Target(void) const 280 { 281 Validate(); 282 return(Build_Target(KIND_UNIT, Units.ID(this))); 283 } 284 285 286 #ifdef CHEAT_KEYS 287 /*********************************************************************************************** 288 * UnitClass::Debug_Dump -- Displays the status of the unit to the mono monitor. * 289 * * 290 * This displays the current status of the unit class to the mono monitor. By this display * 291 * bugs may be tracked down or prevented. * 292 * * 293 * INPUT: none * 294 * * 295 * OUTPUT: none * 296 * * 297 * WARNINGS: none * 298 * * 299 * HISTORY: * 300 * 06/02/1994 JLB : Created. * 301 *=============================================================================================*/ 302 void UnitClass::Debug_Dump(MonoClass *mono) const 303 { 304 Validate(); 305 mono->Set_Cursor(0, 0); 306 mono->Print( 307 "�Name:���������������Mission:����TarCom:�NavCom:�Radio:�Coord:���HeadTo:��St:Ŀ\n" 308 "� � � � � � � � �\n" 309 "����������������N�Y�Health:��Body:�Turret:�Speed:�Path:��������Cargo:��������Ĵ\n" 310 "�Active........� � � � � � � � �\n" 311 "�Limbo.........� � ����������������������������������������������������������Ĵ\n" 312 "�Owned.........� � �Last Message: �\n" 313 "�Discovered....� � �Timer:�Arm:�Track:�Tiberium:�Flash:�Stage:�Team:�����Arch:�\n" 314 "�Selected......� � � � � � � � � � �\n" 315 "�Teathered.....� � ������������������������������������������������������������\n" 316 "�Locked on Map.� � � \n" 317 "�Turret Locked.� � � \n" 318 "�Is A Loaner...� � � \n" 319 "�Deploying.....� � � \n" 320 "�Rotating......� � � \n" 321 "�Firing........� � � \n" 322 "�Driving.......� � � \n" 323 "�To Look.......� � � \n" 324 "�Recoiling.....� � � \n" 325 "�To Display....� � � \n" 326 "�������������������� \n"); 327 mono->Set_Cursor(1, 1);mono->Printf("%s:%s", House->Class->IniName, Class->IniName); 328 CargoClass::Debug_Dump(mono); 329 MissionClass::Debug_Dump(mono); 330 TarComClass::Debug_Dump(mono); 331 } 332 #endif 333 334 335 /*********************************************************************************************** 336 * UnitClass::Sort_Y -- Give Y coordinate sort value for unit. * 337 * * 338 * This routine is used by the rendering system in order to sort the * 339 * game objects in a back to front order. This is now the correct * 340 * overlap effect is achieved. * 341 * * 342 * INPUT: none * 343 * * 344 * OUTPUT: Returns with a coordinate value that can be used for sorting. * 345 * * 346 * WARNINGS: none * 347 * * 348 * HISTORY: * 349 * 05/17/1994 JLB : Created. * 350 *=============================================================================================*/ 351 COORDINATE UnitClass::Sort_Y(void) const 352 { 353 Validate(); 354 if (IsTethered && *this == UNIT_HOVER) { 355 return(Coord_Add(Coord, 0xFF800000L)); 356 } 357 return(Coord_Add(Coord, 0x00800000L)); 358 } 359 360 361 /*********************************************************************************************** 362 * UnitClass::AI -- AI processing for the unit. * 363 * * 364 * This routine will perform the AI processing necessary for the unit. These are non- * 365 * graphic related operations. * 366 * * 367 * INPUT: none * 368 * * 369 * OUTPUT: none * 370 * * 371 * WARNINGS: none * 372 * * 373 * HISTORY: * 374 * 05/31/1994 JLB : Created. * 375 *=============================================================================================*/ 376 void UnitClass::AI(void) 377 { 378 Validate(); 379 380 /* 381 ** Act on new orders if the unit is at a good position to do so. 382 */ 383 if (!IsDriving && Is_Door_Closed() /*Mission != MISSION_UNLOAD*/) { 384 Commence(); 385 } 386 387 TarComClass::AI(); 388 389 /* 390 ** Delete this unit if it finds itself off the edge of the map and it is in 391 ** guard or other static mission mode. 392 */ 393 if (!Team && Mission == MISSION_GUARD && MissionQueue == MISSION_NONE && !Map.In_Radar(Coord_Cell(Coord))) { 394 Stun(); 395 Delete_This(); 396 return; 397 } 398 399 /* 400 ** Rocket launchers will reload every so often. 401 */ 402 if (*this == UNIT_MSAM && Ammo < Class->MaxAmmo) { 403 if (IsDriving) { 404 Reload = Reload + 1; 405 } else { 406 if (Reload.Expired()) { 407 Ammo++; 408 if (Ammo < Class->MaxAmmo) { 409 Reload = TICKS_PER_SECOND*30; 410 } 411 Mark(MARK_CHANGE); 412 } 413 } 414 } 415 416 /* 417 ** Hover landers always are flagged to redraw since they don't record themselves 418 ** on the map in the normal fashion. 419 */ 420 if (*this == UNIT_HOVER) { 421 // Mark_For_Redraw(); 422 //if (IsDown) Mono_Printf("*"); 423 Mark(MARK_CHANGE); 424 } 425 426 /* 427 ** If this is a vehicle that heals itself (e.g., Mammoth Tank), then it will perform 428 ** the heal logic here. 429 */ 430 if (*this == UNIT_HTANK && (Frame % 16) == 0 && Health_Ratio() < 0x0080) { 431 Strength++; 432 Mark(MARK_CHANGE); 433 } 434 if (*this == UNIT_VICE && Map[Coord_Cell(Coord)].Land_Type() == LAND_TIBERIUM && Health_Ratio() < 0x0100 && (Frame % 16) == 0) { 435 Strength++; 436 Mark(MARK_CHANGE); 437 } 438 439 /* 440 ** Transporters require special logic handled here since there isn't a MISSION_WAIT_FOR_PASSENGERS 441 ** mission that they can follow. Passenger loading is merely a part of their normal operation. 442 */ 443 if (Class->IsTransporter) { 444 445 /* 446 ** Double check that there is a passenger that is trying to load or unload. 447 ** If not, then close the door. 448 */ 449 if (!Is_Door_Closed() && Mission != MISSION_UNLOAD && Transmit_Message(RADIO_TRYING_TO_LOAD) != RADIO_ROGER) { 450 APC_Close_Door(); 451 } 452 } 453 454 /* 455 ** Don't start a new mission unless the vehicle is in the center of 456 ** a cell (not driving) and the door (if any) is closed. 457 */ 458 if (!IsDriving && Is_Door_Closed()/*&& Mission != MISSION_UNLOAD*/) { 459 Commence(); 460 } 461 462 /* 463 ** Handle recoil recovery here. 464 */ 465 if (IsInRecoilState) { 466 IsInRecoilState = false; 467 Mark(MARK_CHANGE); 468 } 469 470 /* 471 ** For animating objects such as Visceroids, max-out their animation 472 ** stages here 473 */ 474 if (Class->IsAnimating) { 475 if (!Fetch_Rate()) Set_Rate(2); 476 StageClass::Graphic_Logic(); 477 if (Fetch_Stage() >= Get_Build_Frame_Count(Class->Get_Image_Data())-1) { 478 Set_Stage(0); 479 } 480 } 481 482 /* 483 ** for Jurassic objects, animate them if they're walking 484 */ 485 // Assume funpark mode might be required. ST - 10/14/2019 11:53AM 486 if (Class->IsPieceOfEight) { // && Special.IsJurassic && AreThingiesEnabled) { 487 // Only animate if they're walking 488 if (IsDriving || IsFiring) { 489 if (!Fetch_Rate()) { 490 Set_Rate(Options.Normalize_Delay(2)); 491 Set_Stage(0); 492 } 493 StageClass::Graphic_Logic(); 494 if (Fetch_Stage() >= ( (IsDriving || *this == UNIT_TREX || *this == UNIT_RAPT) ? 8 : 12) ) { 495 Set_Stage(0); 496 if (IsFiring) { 497 Set_Rate(0); 498 IsFiring = false; 499 } 500 } 501 } 502 } 503 504 /* 505 ** Scatter units off buildings in guard modes. 506 */ 507 if (!IsTethered && !IsFiring && !IsDriving && !IsRotating && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord_Cell(Coord)].Cell_Building() != NULL) { 508 Scatter(0, true, true); 509 } 510 511 /* 512 ** A cloaked object that is carrying the flag will always shimmer. 513 */ 514 if (Cloak == CLOAKED && Flagged != HOUSE_NONE) { 515 Do_Shimmer(); 516 } 517 518 } 519 520 521 /*********************************************************************************************** 522 * UnitClass::Can_Fire -- Determines if this unit can fire. * 523 * * 524 * INPUT: target -- The target that is desired to fire upon. * 525 * * 526 * which -- The weapon (primary=0, secondary=1) to fire. * 527 * * 528 * OUTPUT: Returns with the fire error number if it cannot fire or else FIRE_OK. * 529 * * 530 * WARNINGS: none * 531 * * 532 * HISTORY: * 533 * 05/08/1995 JLB : Created. * 534 *=============================================================================================*/ 535 FireErrorType UnitClass::Can_Fire(TARGET target, int which) const 536 { 537 Validate(); 538 FireErrorType cf; 539 540 cf = TarComClass::Can_Fire(target, which); 541 if (cf == FIRE_OK) { 542 543 /* 544 ** If it's a dinosaur, when it's OK to fire we should start the firing 545 ** animation, but wait for the proper attack stage before starting the 546 ** bullet, so things won't die prematurely. 547 */ 548 if (Class->IsFireAnim) { 549 if (!IsFiring) { 550 UnitClass *nonconst; 551 552 nonconst = (UnitClass *)this; 553 nonconst->Set_Rate(Options.Normalize_Delay(2)); 554 nonconst->Set_Stage(0); 555 // Cast out the const. ST - 12/18/2018 1:03PM 556 ((UnitClass*)this)->IsFiring = true; 557 cf = FIRE_BUSY; 558 } else { 559 if (Fetch_Stage() < 4) cf = FIRE_BUSY; 560 } 561 } 562 } 563 return(cf); 564 } 565 566 567 /*********************************************************************************************** 568 * UnitClass::Receive_Message -- Handles receiving a radio message. * 569 * * 570 * This is the handler function for when a unit receives a radio * 571 * message. Typical use of this is when a unit unloads from a hover * 572 * class so that clearing of the transport is successful. * 573 * * 574 * INPUT: from -- Pointer to the originator of the message. * 575 * * 576 * message -- The radio message received. * 577 * * 578 * param -- Reference to an optional parameter the might be needed to return * 579 * information back to the originator of the message. * 580 * * 581 * OUTPUT: Returns with the radio message response. * 582 * * 583 * WARNINGS: none * 584 * * 585 * HISTORY: * 586 * 05/22/1994 JLB : Created. * 587 *=============================================================================================*/ 588 RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType message, long & param) 589 { 590 Validate(); 591 switch (message) { 592 593 /* 594 ** Checks to see if this object is in need of service depot processing. 595 */ 596 case RADIO_NEED_REPAIR: 597 if (!IsDriving && !Target_Legal(NavCom) && Health_Ratio() >= 0x100) return(RADIO_NEGATIVE); 598 break; 599 600 /* 601 ** Asks if the passenger can load on this transport. 602 */ 603 case RADIO_CAN_LOAD: 604 if (Class->IsTransporter && How_Many() < Class->Max_Passengers() && from && House->Class->House == from->Owner()) { 605 return(RADIO_ROGER); 606 } 607 return(RADIO_NEGATIVE); 608 609 /* 610 ** The refinery has told this harvester that it should begin the backup procedure 611 ** so that proper unloading may take place. 612 */ 613 case RADIO_BACKUP_NOW: 614 TarComClass::Receive_Message(from, message, param); 615 if (!IsRotating && PrimaryFacing != DIR_SW) { 616 Do_Turn(DIR_SW); 617 } else { 618 if (!IsDriving) { 619 Force_Track(BACKUP_INTO_REFINERY, Adjacent_Cell(Center_Coord(), FACING_N)); 620 Set_Speed(128); 621 } 622 } 623 return(RADIO_ROGER); 624 625 /* 626 ** This message is sent by the passenger when it determines that it has 627 ** entered the transport. 628 */ 629 case RADIO_IM_IN: 630 if (How_Many() == Class->Max_Passengers()) { 631 APC_Close_Door(); 632 } 633 return(RADIO_ATTACH); 634 635 /* 636 ** Docking maintenance message received. Check to see if new orders should be given 637 ** to the impatient unit. 638 */ 639 case RADIO_DOCKING: 640 641 /* 642 ** Check for the case of a docking message arriving from a unit that does not 643 ** have formal radio contact established. This might be a unit that is standing 644 ** by. If this transport is free to proceed with normal docking operation, then 645 ** establish formal contact now. If the transport is completely full, then break 646 ** off contact. In all other cases, just tell the pending unit to stand by. 647 */ 648 if (Contact_With_Whom() != from) { 649 650 /* 651 ** Can't ever load up so tell the passenger to bug off. 652 */ 653 if (How_Many() >= Class->Max_Passengers()) { 654 return(RADIO_NEGATIVE); 655 } 656 657 /* 658 ** Establish contact and let the loading process proceed normally. 659 */ 660 if (!In_Radio_Contact()) { 661 Transmit_Message(RADIO_HELLO, from); 662 } else { 663 664 /* 665 ** This causes the potential passenger to think that all is ok and to 666 ** hold on for a bit. 667 */ 668 return(RADIO_ROGER); 669 } 670 } 671 672 if (Class->IsTransporter && *this == UNIT_APC && How_Many() < Class->Max_Passengers()) { 673 TarComClass::Receive_Message(from, message, param); 674 675 /* 676 ** Check if the APC is busy with an order 677 */ 678 bool is_busy = Mission == MISSION_MOVE; 679 680 if ((!IsRotating || Target_Legal(TarCom)) && !IsTethered) { 681 682 /* 683 ** If the potential passenger needs someplace to go, then figure out a good 684 ** spot and tell it to go. 685 */ 686 if (Transmit_Message(RADIO_NEED_TO_MOVE, from) == RADIO_ROGER) { 687 688 /* 689 ** If the unit has a target, don't bother with turning logic 690 */ 691 CELL cell; 692 DirType dir; 693 if (Target_Legal(TarCom)) { 694 dir = PrimaryFacing.Current(); 695 cell = Adjacent_Cell(Coord_Cell(Coord), Direction(from)); 696 } 697 else { 698 dir = Desired_Load_Dir(from, cell); 699 } 700 701 /* 702 ** If no adjacent free cells are detected, then passenger loading 703 ** cannot occur. Break radio contact. 704 */ 705 if (cell == 0) { 706 Transmit_Message(RADIO_OVER_OUT, from); 707 } else { 708 param = (long)::As_Target(cell); 709 710 if (!is_busy && !IsDriving) { 711 Do_Turn(dir); 712 713 /* 714 ** If it is now facing the correct direction, then open the 715 ** transport doors. Close the doors if the transport is or needs 716 ** to rotate. 717 */ 718 if (IsRotating) { 719 if (!Is_Door_Closed()) { 720 APC_Close_Door(); 721 } 722 } else { 723 if (!Is_Door_Open()) { 724 APC_Open_Door(); 725 } 726 } 727 } 728 729 /* 730 ** Tell the potential passenger where it should go. If the passenger is 731 ** already at the staging location, then tell it to move onto the transport 732 ** directly. 733 */ 734 if (Transmit_Message(RADIO_MOVE_HERE, param, from) == RADIO_YEA_NOW_WHAT) { 735 if (Is_Door_Open() || Target_Legal(TarCom)) { 736 param = (long)As_Target(); 737 Transmit_Message(RADIO_TETHER); 738 if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { 739 Transmit_Message(RADIO_OVER_OUT, from); 740 } else { 741 Contact_With_Whom()->Unselect(); 742 } 743 } 744 } 745 } 746 } 747 } 748 return(RADIO_ROGER); 749 } 750 break; 751 752 /* 753 ** Something bad has happened to the object in contact with. Abort any coordinated 754 ** activity with this object. Basically, ... run away! Run away! 755 */ 756 case RADIO_RUN_AWAY: 757 if (Class->IsToHarvest && In_Radio_Contact() && Mission == MISSION_ENTER) { 758 TechnoClass * contact = Contact_With_Whom(); 759 if (contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) == STRUCT_REFINERY) { 760 // Slight hack; set a target so the harvest mission knows to skip to finding home state 761 Assign_Mission(MISSION_HARVEST); 762 TarCom = As_Target(); 763 return(RADIO_ROGER); 764 } 765 } 766 return(DriveClass::Receive_Message(from, message, param)); 767 768 /* 769 ** When this message is received, it means that the other object 770 ** has already turned its radio off. Turn this radio off as well. 771 */ 772 case RADIO_OVER_OUT: 773 if (Mission == MISSION_RETURN) { 774 Assign_Mission(MISSION_GUARD); 775 } 776 TarComClass::Receive_Message(from, message, param); 777 return(RADIO_ROGER); 778 779 } 780 return(TarComClass::Receive_Message(from, message, param)); 781 } 782 783 784 /*********************************************************************************************** 785 * UnitClass::Unlimbo -- Removes unit from stasis. * 786 * * 787 * This routine will place a unit into the game and out of its limbo * 788 * state. This occurs whenever a unit is unloaded from a transport. * 789 * * 790 * INPUT: coord -- The coordinate to make the unit appear. * 791 * * 792 * dir -- The initial facing to impart upon the unit. * 793 * * 794 * OUTPUT: bool; Was the unit unlimboed successfully? If the desired * 795 * coordinate is illegal, then this might very well return * 796 * false. * 797 * * 798 * WARNINGS: none * 799 * * 800 * HISTORY: * 801 * 05/22/1994 JLB : Created. * 802 *=============================================================================================*/ 803 bool UnitClass::Unlimbo(COORDINATE coord, DirType dir) 804 { 805 Validate(); 806 /* 807 ** All units must start out facing one of the 8 major directions. 808 */ 809 dir = Facing_Dir(Dir_Facing(dir)); 810 811 if (TarComClass::Unlimbo(coord, dir)) { 812 813 /* 814 ** Ensure that the owning house knows about the 815 ** new object. 816 */ 817 House->UScan |= (1L << Class->Type); 818 House->ActiveUScan |= (1L << Class->Type); 819 820 /* 821 ** If it starts off the edge of the map, then it already starts cloaked. 822 */ 823 if (IsCloakable && !IsLocked) Cloak = CLOAKED; 824 825 /* 826 ** Units default to no special animation. 827 */ 828 Set_Rate(0); 829 Set_Stage(0); 830 831 /* 832 ** A gun boat and a hover craft are allowed to exit the map thus we 833 ** flag them so they can. This undoes the work of Techno::Unlimbo which 834 ** stole their IsALoaner flag. 835 */ 836 if (*this == UNIT_GUNBOAT || *this == UNIT_HOVER) { 837 IsALoaner = true; 838 } 839 840 /* 841 ** Start the gunboat animating when it is unlimboed. 842 */ 843 if (*this == UNIT_GUNBOAT) { 844 Set_Rate(2); 845 Set_Stage(0); 846 } 847 return(true); 848 } 849 return(false); 850 } 851 852 853 /*********************************************************************************************** 854 * UnitClass::Take_Damage -- Inflicts damage points on a unit. * 855 * * 856 * This routine will inflict the specified number of damage points on * 857 * the given unit. If the unit is destroyed, then this routine will * 858 * remove the unit cleanly from the game. The return value indicates * 859 * whether the unit was destroyed. This will allow appropriate death * 860 * animation or whatever. * 861 * * 862 * INPUT: damage-- The number of damage points to inflict. * 863 * * 864 * distance -- The distance from the damage center point to the object's center point.* 865 * * 866 * warhead--The type of damage to inflict. * 867 * * 868 * source -- Who is responsible for this damage? * 869 * * 870 * OUTPUT: Returns the result of the damage process. This can range from RESULT_NONE up to * 871 * RESULT_DESTROYED. * 872 * * 873 * WARNINGS: none * 874 * * 875 * HISTORY: * 876 * 05/30/1991 JLB : Created. * 877 * 07/12/1991 JLB : Script initiated by unit destruction. * 878 * 04/15/1994 JLB : Converted to member function. * 879 * 04/16/1994 JLB : Warhead modifier. * 880 * 06/03/1994 JLB : Added the source of the damage target value. * 881 * 06/20/1994 JLB : Source is a base class pointer. * 882 * 11/22/1994 JLB : Shares base damage handler for techno objects. * 883 * 06/30/1995 JLB : Lasers do maximum damage against gunboat. * 884 * 08/16/1995 JLB : Harvester crushing doesn't occur on early missions. * 885 *=============================================================================================*/ 886 ResultType UnitClass::Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source) 887 { 888 Validate(); 889 ResultType res = RESULT_NONE; 890 891 /* 892 ** Special case: If this is a laser attacking a gunboat, then the gunboat is ALWAYS toasted. 893 */ 894 if (*this == UNIT_GUNBOAT && warhead == WARHEAD_LASER) { 895 damage = Strength*3; 896 } 897 898 /* 899 ** Remember if this object was selected. If it was and it gets destroyed and it has 900 ** passengers that pop out, then the passengers will inherit the select state. 901 */ 902 bool select = (Is_Selected_By_Player() && Is_Owned_By_Player()); // Fox for GlyphX multiplayer. ST - 4/16/2019 9:46AM 903 904 /* 905 ** In order for a this to be damaged, it must either be a unit 906 ** with a crew or a sandworm. 907 */ 908 res = TarComClass::Take_Damage(damage, distance, warhead, source); 909 910 if (res == RESULT_DESTROYED) { 911 Death_Announcement(source); 912 if (Class->Explosion != ANIM_NONE) { 913 AnimType anim = Class->Explosion; 914 915 /* 916 ** SSM launchers will really explode big if they are carrying 917 ** missiles at the time of the explosion. 918 */ 919 if (*this == UNIT_MSAM && Ammo) { 920 anim = ANIM_NAPALM3; 921 } 922 923 if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { 924 Sound_Effect(VOC_DINODIE1, Coord); 925 } 926 927 new AnimClass(anim, Coord); 928 929 /* 930 ** When the flame tank blows up, it really blows up. It is 931 ** equivalent to a napalm strike. 932 */ 933 if (Class->Explosion == ANIM_NAPALM3) { 934 Explosion_Damage(Coord, 200, source, WARHEAD_FIRE); 935 } 936 937 /* 938 ** Very strong units that have an explosion will also rock the 939 ** screen when they are destroyed. 940 */ 941 if (Class->MaxStrength > 400) { 942 Shake_The_Screen(3, Owner()); 943 if (source && Owner() != source->Owner()) { 944 Shake_The_Screen(3, source->Owner()); 945 } 946 } 947 } 948 949 /* 950 ** Possibly have the crew member run away. 951 */ 952 Mark(MARK_UP); 953 if (Class->IsCrew && !Class->IsTransporter) { 954 if (Random_Pick(0, 1) == 0) { 955 InfantryClass * i = 0; 956 if (Class->Primary == WEAPON_NONE) { 957 i = new InfantryClass(INFANTRY_C1, House->Class->House); 958 i->IsTechnician = true; 959 } else { 960 i = new InfantryClass(INFANTRY_E1, House->Class->House); 961 } 962 if (i) { 963 if (i->Unlimbo(Coord, DIR_N)) { 964 i->Strength = Random_Pick(5, (int)i->Class->MaxStrength/2); 965 i->Scatter(0, true); 966 if (!House->IsHuman) { 967 i->Assign_Mission(MISSION_HUNT); 968 } else { 969 i->Assign_Mission(MISSION_GUARD); 970 } 971 if (select) i->Select(); 972 } else { 973 delete i; 974 } 975 } 976 } 977 } else { 978 if (*this != UNIT_HOVER) { 979 while (Is_Something_Attached()) { 980 FootClass * object = Detach_Object(); 981 982 if (!object) break; // How can this happen? 983 984 /* 985 ** Only infantry can run from a destroyed vehicle. Even then, it is not a sure 986 ** thing. 987 */ 988 if (object->Is_Infantry() && object->Unlimbo(Coord, DIR_N)) { 989 // object->Strength = Random_Pick(5, (int)((InfantryClass*)object)->Class->MaxStrength/2); 990 object->Look(false); 991 object->Scatter(0, true); 992 if (select) object->Select(); 993 } else { 994 object->Record_The_Kill(source); 995 delete object; 996 } 997 } 998 } else { 999 Kill_Cargo(source); 1000 } 1001 } 1002 1003 /* 1004 ** When the mobile head quarters blows up, the entire side blows up. 1005 */ 1006 if (*this == UNIT_MHQ) { 1007 House->Flag_To_Die(); 1008 } 1009 1010 if (*this == UNIT_MCV) { 1011 if (House) { 1012 House->Check_Pertinent_Structures(); 1013 } 1014 } 1015 1016 /* 1017 ** Finally, delete the vehicle. 1018 */ 1019 Delete_This(); 1020 1021 } else { 1022 1023 /* 1024 ** When damaged and below half strength, start smoking if 1025 ** it isn't already smoking. 1026 */ 1027 if (Health_Ratio() < 0x0080 && !IsAnimAttached && *this != UNIT_VICE && *this != UNIT_STEG && *this != UNIT_TREX && *this != UNIT_TRIC && *this != UNIT_RAPT) { 1028 AnimClass * anim = new AnimClass(ANIM_SMOKE_M, Coord_Add(Coord, XYP_Coord(0, -8))); 1029 if (anim) anim->Attach_To(this); 1030 } 1031 1032 /* 1033 ** If at half damage, then start smoking or burning as appropriate. 1034 */ 1035 if (res == RESULT_HALF) { 1036 if (*this == UNIT_GUNBOAT) { 1037 AnimClass * anim = new AnimClass(ANIM_ON_FIRE_MED, Coord_Add(Coord, XYP_Coord(Random_Pick(0, 16)-8, -2))); 1038 if (anim) anim->Attach_To(this); 1039 } 1040 } 1041 1042 /* 1043 ** Try to crush anyone that fires on this unit if possible. The harvester 1044 ** typically is the only one that will qualify here. 1045 */ 1046 if (!Team && source && !IsTethered && !House->Is_Ally(source) && (!House->IsHuman || Special.IsSmartDefense)) { 1047 1048 /* 1049 ** Try to crush the attacker if it can be crushed by this unit and this unit is 1050 ** not equipped with a flame type weapon. If this unit has a weapon and the target 1051 ** is not very close, then fire on it instead. In easy mode, they never run over the 1052 ** player. In hard mode, they always do. In normal mode, they only overrun past 1053 ** mission #8. 1054 */ 1055 if ((Class->Primary == WEAPON_NONE || (Distance(source) < 0x0180 && BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead != WARHEAD_FIRE)) && 1056 (GameToPlay != GAME_NORMAL || 1057 *this != UNIT_HARVESTER || 1058 BuildLevel > 8 || 1059 PlayerPtr->Difficulty == DIFF_HARD) && 1060 !(GameToPlay == GAME_NORMAL && PlayerPtr->Difficulty == DIFF_EASY) && 1061 Class->IsCrusher && source->Is_Techno() && ((TechnoTypeClass const &)source->Class_Of()).IsCrushable) { 1062 1063 Assign_Destination(source->As_Target()); 1064 Assign_Mission(MISSION_MOVE); 1065 } else { 1066 1067 /* 1068 ** Try to return to base if possible. 1069 */ 1070 if (*this == UNIT_HARVESTER && Pip_Count() && Health_Ratio() < 0x0080) { 1071 /* 1072 ** Find nearby refinery and head to it? 1073 */ 1074 BuildingClass * building = Find_Docking_Bay(STRUCT_REFINERY, false); 1075 1076 /* 1077 ** Since the refinery said it was ok to load, establish radio 1078 ** contact with the refinery and then await docking orders. 1079 */ 1080 if (building && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { 1081 Assign_Mission(MISSION_ENTER); 1082 } 1083 } 1084 } 1085 } 1086 1087 /* 1088 ** Computer controlled harvester will radio for help if they are attacked. 1089 */ 1090 if (*this == UNIT_HARVESTER && !House->IsHuman && source) { 1091 Base_Is_Attacked(source); 1092 } 1093 } 1094 return(res); 1095 } 1096 1097 1098 /*********************************************************************************************** 1099 * UnitClass::new -- Allocate a unit slot and adjust access arrays. * 1100 * * 1101 * This routine will allocate a unit from the available unit pool and * 1102 * fixup all the access lists to match. It will allocate a unit slot * 1103 * from within the range allowed for the specified unit type. If no * 1104 * slot was found, then it will fail. * 1105 * * 1106 * INPUT: none * 1107 * * 1108 * OUTPUT: Returns with a pointer to the allocated unit. * 1109 * * 1110 * WARNINGS: none * 1111 * * 1112 * HISTORY: * 1113 * 04/11/1994 JLB : Created. * 1114 * 04/21/1994 JLB : Converted to operator new. * 1115 *=============================================================================================*/ 1116 void * UnitClass::operator new(size_t) 1117 { 1118 void * ptr = (UnitClass *)Units.Allocate(); 1119 if (ptr) { 1120 ((UnitClass *)ptr)->Set_Active(); 1121 } 1122 return(ptr); 1123 } 1124 1125 1126 /*********************************************************************************************** 1127 * UnitClass::delete -- Deletion operator for units. * 1128 * * 1129 * This removes the unit from the local allocation system. Since this * 1130 * is a fixed block of memory, not much has to be done to delete the * 1131 * unit. Merely marking it as inactive is enough. * 1132 * * 1133 * INPUT: ptr -- Pointer to the unit to delete. * 1134 * * 1135 * OUTPUT: none * 1136 * * 1137 * WARNINGS: none * 1138 * * 1139 * HISTORY: * 1140 * 04/21/1994 JLB : Created. * 1141 *=============================================================================================*/ 1142 void UnitClass::operator delete(void *ptr) 1143 { 1144 if (ptr) { 1145 ((UnitClass *)ptr)->IsActive = false; 1146 } 1147 Units.Free((UnitClass *)ptr); 1148 1149 //Map.Validate(); 1150 } 1151 1152 1153 /*********************************************************************************************** 1154 * UnitClass::~UnitClass -- Destructor for unit objects. * 1155 * * 1156 * This destructor will lower the unit count for the owning house as well as inform any * 1157 * other units in communication, that this unit is about to leave reality. * 1158 * * 1159 * INPUT: none * 1160 * * 1161 * OUTPUT: none * 1162 * * 1163 * WARNINGS: none * 1164 * * 1165 * HISTORY: * 1166 * 08/15/1994 JLB : Created. * 1167 *=============================================================================================*/ 1168 UnitClass::~UnitClass(void) 1169 { 1170 if (GameActive && Class) { 1171 1172 #ifdef USE_RA_AI 1173 // 1174 // Added for RA AI in TD. ST - 7/26/2019 9:12AM 1175 // 1176 House->Tracking_Remove(this); 1177 #endif // USE_RA_AI 1178 1179 /* 1180 ** If there are any cargo members, delete them. 1181 */ 1182 while (Is_Something_Attached()) { 1183 delete Detach_Object(); 1184 } 1185 1186 Limbo(); 1187 } 1188 if (GameActive && Team) Team->Remove(this); 1189 } 1190 1191 1192 /*********************************************************************************************** 1193 * UnitClass::UnitClass -- Constructor for units. * 1194 * * 1195 * This constructor for units will initialize the unit into the game * 1196 * system. It will be placed in all necessary tracking lists. The initial condition will * 1197 * be in a state of limbo. * 1198 * * 1199 * INPUT: classid -- The type of unit to create. * 1200 * * 1201 * house -- The house owner of this unit. * 1202 * * 1203 * OUTPUT: none * 1204 * * 1205 * WARNINGS: none * 1206 * * 1207 * HISTORY: * 1208 * 04/21/1994 JLB : Created. * 1209 *=============================================================================================*/ 1210 UnitClass::UnitClass(UnitType classid, HousesType house) : 1211 TarComClass(classid, house) 1212 { 1213 Flagged = HOUSE_NONE; 1214 Reload = 0; 1215 Ammo = Class->MaxAmmo; 1216 IsCloakable = Class->IsCloakable; 1217 TiberiumUnloadRefinery = NULL; 1218 if (Class->IsAnimating) Set_Rate(Options.Normalize_Delay(3)); 1219 1220 /* 1221 ** Keep count of the number of units created. 1222 */ 1223 if (GameToPlay == GAME_INTERNET){ 1224 House->UnitTotals->Increment_Unit_Total((int)classid); 1225 } 1226 1227 #ifdef USE_RA_AI 1228 // 1229 // Added for RA AI in TD. ST - 7/26/2019 9:12AM 1230 // 1231 House->Tracking_Add(this); 1232 #endif // USE_RA_AI 1233 } 1234 1235 1236 /*********************************************************************************************** 1237 * UnitClass::Active_Click_With -- Intercepts the active click to see if deployment is possible* 1238 * * 1239 * This routine intercepts the active click operation. It check to see if this is a self * 1240 * deployment request (MCV's have this ability). If it is, then the object is initiated * 1241 * to self deploy. In the other cases, it passes the operation down to the lower * 1242 * classes for processing. * 1243 * * 1244 * INPUT: action -- The action requested of the unit. * 1245 * * 1246 * object -- The object that the mouse pointer is over. * 1247 * * 1248 * OUTPUT: none * 1249 * * 1250 * WARNINGS: none * 1251 * * 1252 * HISTORY: * 1253 * 03/10/1995 JLB : Created. * 1254 *=============================================================================================*/ 1255 void UnitClass::Active_Click_With(ActionType action, ObjectClass * object) 1256 { 1257 Validate(); 1258 if (action != What_Action(object)) { 1259 switch (action) { 1260 case ACTION_SABOTAGE: 1261 case ACTION_CAPTURE: 1262 action = ACTION_ATTACK; 1263 break; 1264 1265 case ACTION_ENTER: 1266 action = ACTION_MOVE; 1267 break; 1268 1269 default: 1270 action = ACTION_NONE; 1271 break; 1272 } 1273 } 1274 TarComClass::Active_Click_With(action, object); 1275 } 1276 1277 1278 void UnitClass::Active_Click_With(ActionType action, CELL cell) {TarComClass::Active_Click_With(action, cell);}; 1279 1280 1281 void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET destination) 1282 { 1283 Validate(); 1284 if (mission == MISSION_HARVEST) { 1285 ArchiveTarget = TARGET_NONE; 1286 } else if (mission == MISSION_ENTER) { 1287 BuildingClass* building = As_Building(destination); 1288 if (building != NULL && *building == STRUCT_REFINERY && building->In_Radio_Contact()) { 1289 building->Transmit_Message(RADIO_OVER_OUT); 1290 } 1291 } 1292 TarComClass::Player_Assign_Mission(mission, target, destination); 1293 } 1294 1295 1296 /*********************************************************************************************** 1297 * UnitClass::Enter_Idle_Mode -- Unit enters idle mode state. * 1298 * * 1299 * This routine is called when the unit completes one mission but does not have a clear * 1300 * follow up mission to perform. In such a case, the unit should enter a default idle * 1301 * state. This idle state varies depending on what the current internal computer * 1302 * settings of the unit is as well as what kind of unit it is. * 1303 * * 1304 * INPUT: initial -- Is this called when the unit just leaves a factory or is initially * 1305 * or is initially placed on the map? * 1306 * * 1307 * OUTPUT: none * 1308 * * 1309 * WARNINGS: none * 1310 * * 1311 * HISTORY: * 1312 * 05/31/1994 JLB : Created. * 1313 * 06/03/1994 JLB : Fixed to handle non-combat vehicles. * 1314 * 06/18/1995 JLB : Allows a harvester to stop harvesting. * 1315 *=============================================================================================*/ 1316 void UnitClass::Enter_Idle_Mode(bool initial) 1317 { 1318 Validate(); 1319 MissionType order; 1320 1321 /* 1322 ** A movement mission without a NavCom would be pointless to have a radio contact since 1323 ** no radio coordination occurs on a just a simple movement mission. 1324 */ 1325 if (Mission == MISSION_MOVE && !Target_Legal(NavCom)) { 1326 Transmit_Message(RADIO_OVER_OUT); 1327 } 1328 1329 if (Class->Primary == WEAPON_NONE) { 1330 if (Class->IsToHarvest) { 1331 if (!In_Radio_Contact() && Mission != MISSION_HARVEST) { 1332 if (initial || !House->IsHuman || Map[Coord_Cell(Coord)].Land_Type() == LAND_TIBERIUM) { 1333 order = MISSION_HARVEST; 1334 } else { 1335 order = MISSION_GUARD; 1336 } 1337 Assign_Target(TARGET_NONE); 1338 Assign_Destination(TARGET_NONE); 1339 } else { 1340 return; 1341 } 1342 } else { 1343 if (IsALoaner && Class->IsTransporter && Is_Something_Attached() && !Team) { 1344 order = MISSION_UNLOAD; 1345 } else { 1346 order = MISSION_GUARD; 1347 Assign_Target(TARGET_NONE); 1348 Assign_Destination(TARGET_NONE); 1349 } 1350 } 1351 } else { 1352 if (Target_Legal(TarCom)) { 1353 order = MISSION_ATTACK; 1354 } else { 1355 if (Target_Legal(NavCom)) { 1356 order = MISSION_MOVE; 1357 } else { 1358 if (GameToPlay == GAME_NORMAL || House->IsHuman) { 1359 order = MISSION_GUARD; 1360 } else { 1361 //if (GameToPlay != GAME_NORMAL) { 1362 #ifndef USE_RA_AI 1363 // Don't use MISSION_TIMED_HUNT since this can trigger the Blitz behavior 1364 order = MISSION_TIMED_HUNT; 1365 #else 1366 // 1367 // Added for RA AI in TD. ST - 7/26/2019 9:12AM 1368 // 1369 // This applies only to non-human houses in a non-normal game type 1370 // 1371 order = MISSION_GUARD; 1372 1373 if (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) { 1374 return; 1375 } 1376 1377 if (!Team) { 1378 if (House->IQ >= Rule.IQGuardArea) { 1379 if (Is_Weapon_Equipped()) { 1380 order = MISSION_GUARD_AREA; 1381 } 1382 } 1383 } 1384 1385 #endif 1386 //} else { 1387 // order = MISSION_HUNT; 1388 //} 1389 } 1390 } 1391 } 1392 } 1393 Assign_Mission(order); 1394 } 1395 1396 1397 /*********************************************************************************************** 1398 * UnitClass::Find_LZ -- Maintenance function for transport units. * 1399 * * 1400 * This is a maintenance function for transport units. It checks to see if it is loaded * 1401 * with cargo, but it doesn't know where to put it. In such a case, the unit must look * 1402 * for a landing zone (LZ) to unload the cargo. This routine should be called every so * 1403 * often. Typically, calling this routine is controlled by the scripts. * 1404 * * 1405 * INPUT: none * 1406 * * 1407 * OUTPUT: none * 1408 * * 1409 * WARNINGS: none * 1410 * * 1411 * HISTORY: * 1412 * 05/31/1994 JLB : Created. * 1413 *=============================================================================================*/ 1414 void UnitClass::Find_LZ(void) 1415 { 1416 Validate(); 1417 CELL cell; // Map exit cell number. 1418 1419 if (*this == UNIT_HOVER) { 1420 1421 if (!IsRotating && Is_Something_Attached() && !Target_Legal(NavCom)) { 1422 cell = Map.Calculated_Cell(SOURCE_BEACH, House->Class->House); 1423 Assign_Destination(::As_Target(cell)); 1424 } 1425 } 1426 } 1427 1428 1429 /*********************************************************************************************** 1430 * UnitClass::Unload_Hovercraft_Process -- Handles unloading hovercraft transport. * 1431 * * 1432 * This is a maintenance routine to handle the special operations necessary for a * 1433 * hovercraft transport. This routine checks to see if special unloading operations are * 1434 * necessary and performs them. These operations can include unloading the transported * 1435 * object, finding a new beach cell, and rotating to a convenient unloading facing. * 1436 * * 1437 * INPUT: none * 1438 * * 1439 * OUTPUT: bool; Has the transport finished its unloading mission? This is true after the * 1440 * hovercraft has completely dispatched its cargo. * 1441 * * 1442 * WARNINGS: none * 1443 * * 1444 * HISTORY: * 1445 * 05/31/1994 JLB : Created. * 1446 * 07/31/1995 JLB : Second infantry unloaded MUST be the one tethered. * 1447 *=============================================================================================*/ 1448 bool UnitClass::Unload_Hovercraft_Process(void) 1449 { 1450 Validate(); 1451 bool unloaded = false; 1452 FootClass *unit; // The unit to be unloaded. 1453 CELL cell; // Cell to unload to. 1454 1455 /* 1456 ** If the hovercraft is currently waiting for the last unit 1457 ** to completely unload, then don't do anything. 1458 */ 1459 if (IsTethered || IsRotating) { 1460 return(false); 1461 } 1462 1463 if (Is_Something_Attached()) { 1464 1465 /* 1466 ** Only unload if the hovercraft has reached the beach. 1467 */ 1468 if (!Target_Legal(NavCom)) { 1469 1470 cell = Coord_Cell(Adjacent_Cell(Coord, Dir_Facing(PrimaryFacing.Current()))); 1471 1472 unit = (FootClass *)Attached_Object(); 1473 1474 Mark(MARK_UP); 1475 if (Map.In_Radar(cell) && !Map[cell].Cell_Unit()) { 1476 1477 if (unit->Can_Enter_Cell(cell, FACING_NONE) == MOVE_OK) { 1478 1479 /* 1480 ** Place all the transported units onto the map. 1481 */ 1482 int count = 0; 1483 bool first = true; 1484 FootClass * secondary = 0; 1485 while (Attached_Object()) { 1486 FootClass * u = (FootClass *)Detach_Object(); 1487 1488 if (!first && !secondary) secondary = u; 1489 first = false; 1490 1491 /* 1492 ** Place the unit on the map and start it heading in the right direction. 1493 */ 1494 ScenarioInit++; 1495 if (u->Unlimbo(Coord_Add(Coord & 0xFF00FF00L, StoppingCoordAbs[count]), DIR_N)) { 1496 u->Assign_Mission(MISSION_MOVE); 1497 u->NavCom = ::As_Target(cell); 1498 u->Path[0] = Dir_Facing(u->PrimaryFacing.Current()); 1499 u->Path[1] = FACING_NONE; 1500 u->Set_Speed(0x80); 1501 u->IsUnloading = true; 1502 u->Look(false); 1503 } else { 1504 1505 /* 1506 ** Couldn't unlimbo for some strange reason. Kill the unit off. 1507 */ 1508 delete u; 1509 } 1510 ScenarioInit--; 1511 count++; 1512 } 1513 if (secondary) unit = secondary; 1514 1515 /* 1516 ** Establish radio contact bond with the transport 1517 ** hovercraft. This bond tells the hovercraft to 1518 ** not move until the unit has completely unloaded. 1519 */ 1520 if (Transmit_Message(RADIO_HELLO, unit) == RADIO_ROGER) { 1521 Transmit_Message(RADIO_UNLOAD); 1522 } 1523 Mark(MARK_DOWN); 1524 Map.Layer[LAYER_GROUND].Sort(); 1525 Map.Layer[LAYER_GROUND].Sort(); 1526 Map.Layer[LAYER_GROUND].Sort(); 1527 return(false); 1528 } else { 1529 1530 /* 1531 ** If the attached object cannot unload because the desired destination 1532 ** cell is impassable, then abort the landing. This is faked by pretending 1533 ** that the unload was successful. The controlling routine will cause 1534 ** the transport to leave. 1535 */ 1536 Mark(MARK_DOWN); 1537 return(false); 1538 // return(true); 1539 } 1540 } 1541 1542 Mark(MARK_DOWN); 1543 } 1544 } else { 1545 return(true); 1546 } 1547 1548 return(unloaded); 1549 } 1550 1551 1552 /*********************************************************************************************** 1553 * UnitClass::Goto_Clear_Spot -- Finds a clear spot to deploy. * 1554 * * 1555 * This routine is used by the MCV to find a clear spot to deploy. If a clear spot * 1556 * is found, then the MCV will assign that location to its navigation computer. This only * 1557 * occurs if the MCV isn't already heading toward a spot. * 1558 * * 1559 * INPUT: none * 1560 * * 1561 * OUTPUT: bool; Is the located at a spot where it can deploy? * 1562 * * 1563 * WARNINGS: none * 1564 * * 1565 * HISTORY: * 1566 * 06/27/1994 JLB : Created. * 1567 *=============================================================================================*/ 1568 bool UnitClass::Goto_Clear_Spot(void) 1569 { 1570 Validate(); 1571 Mark(MARK_UP); 1572 if (!Target_Legal(NavCom) && BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { 1573 Mark(MARK_DOWN); 1574 return(true); 1575 } 1576 1577 if (!Target_Legal(NavCom)) { 1578 1579 #ifndef USE_RA_AI 1580 /* 1581 ** This scan table is skewed to north scanning only. This should 1582 ** probably be converted to a more flexible method. 1583 */ 1584 static int _offsets[] = { 1585 -MAP_CELL_W*1, 1586 -MAP_CELL_W*2, 1587 -(MAP_CELL_W*2)+1, 1588 -(MAP_CELL_W*2)-1, 1589 -MAP_CELL_W*3, 1590 -(MAP_CELL_W*3)+1, 1591 -(MAP_CELL_W*3)-1, 1592 -(MAP_CELL_W*3)+2, 1593 -(MAP_CELL_W*3)-2, 1594 -MAP_CELL_W*4, 1595 -(MAP_CELL_W*4)+1, 1596 -(MAP_CELL_W*4)-1, 1597 -(MAP_CELL_W*4)+2, 1598 -(MAP_CELL_W*4)-2, 1599 0 1600 }; 1601 int *ptr; 1602 1603 ptr = &_offsets[0]; 1604 while (*ptr) { 1605 CELL cell = Coord_Cell(Coord)+*ptr++; 1606 1607 if (BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(cell)) { 1608 Assign_Destination(::As_Target(cell)); 1609 break; 1610 } 1611 } 1612 1613 #else //USE_RA_AI 1614 1615 // 1616 // Pulled in the extra offsets from RA, and started at a random position in the array. ST - 7/26/2019 2:07PM 1617 // 1618 static int _offset_count = 36; 1619 static int _offsets[] = { 1620 -MAP_CELL_W*1, 1621 -MAP_CELL_W*2, 1622 -(MAP_CELL_W*2)+1, 1623 -(MAP_CELL_W*2)-1, 1624 -MAP_CELL_W*3, 1625 -(MAP_CELL_W*3)+1, 1626 -(MAP_CELL_W*3)-1, 1627 -(MAP_CELL_W*3)+2, 1628 -(MAP_CELL_W*3)-2, 1629 -MAP_CELL_W*4, 1630 -(MAP_CELL_W*4)+1, 1631 -(MAP_CELL_W*4)-1, 1632 -(MAP_CELL_W*4)+2, 1633 -(MAP_CELL_W*4)-2, 1634 //BG: Added south scanning 1635 MAP_CELL_W*1, 1636 MAP_CELL_W*2, 1637 (MAP_CELL_W*2)+1, 1638 (MAP_CELL_W*2)-1, 1639 MAP_CELL_W*3, 1640 (MAP_CELL_W*3)+1, 1641 (MAP_CELL_W*3)-1, 1642 (MAP_CELL_W*3)+2, 1643 (MAP_CELL_W*3)-2, 1644 MAP_CELL_W*4, 1645 (MAP_CELL_W*4)+1, 1646 (MAP_CELL_W*4)-1, 1647 (MAP_CELL_W*4)+2, 1648 (MAP_CELL_W*4)-2, 1649 1650 //BG: Added some token east/west scanning 1651 -1,-2,-3,-4, 1652 1653 1, 2, 3, 4, 1654 0 1655 }; 1656 1657 int offset_index = Random_Pick(0, _offset_count); 1658 1659 for (int i=0 ; i<_offset_count ; i++) { 1660 CELL cell = Coord_Cell(Coord) + _offsets[offset_index++]; 1661 if (offset_index >= _offset_count || _offsets[offset_index] == 0) { 1662 offset_index = 0; 1663 } 1664 1665 if (BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(cell)) { 1666 Assign_Destination(::As_Target(cell)); 1667 break; 1668 } 1669 } 1670 1671 #endif //USE_RA_AI 1672 } 1673 Mark(MARK_DOWN); 1674 1675 return(false); 1676 } 1677 1678 1679 /*********************************************************************************************** 1680 * UnitClass::Try_To_Deploy -- The unit attempts to "deploy" at current location. * 1681 * * 1682 * Certain units have the ability to deploy into a building. When this routine is called * 1683 * for one of those units, it will attempt to deploy at its current location. If the unit * 1684 * is in motion to a destination or it isn't one of the special units that can deploy or * 1685 * it isn't allowed to deploy at this location for some reason it won't deploy. In all * 1686 * other cases, it will begin to deploy and once it begins only a player abort action will * 1687 * stop it. * 1688 * * 1689 * INPUT: none * 1690 * * 1691 * OUTPUT: bool; Was deployment begun? * 1692 * * 1693 * WARNINGS: none * 1694 * * 1695 * HISTORY: * 1696 * 06/18/1994 JLB : Created. * 1697 *=============================================================================================*/ 1698 bool UnitClass::Try_To_Deploy(void) 1699 { 1700 Validate(); 1701 if (!Target_Legal(NavCom) && !IsRotating) { 1702 if (*this == UNIT_MCV) { 1703 1704 /* 1705 ** Determine if it is legal to deploy at this location. If not, tell the 1706 ** player. 1707 */ 1708 Mark(MARK_UP); 1709 if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { 1710 if (PlayerPtr == House) { 1711 Speak(VOX_DEPLOY); 1712 } 1713 Mark(MARK_DOWN); 1714 IsDeploying = false; 1715 return(false); 1716 } 1717 Mark(MARK_DOWN); 1718 1719 /* 1720 ** If the unit is not facing the correct direction, then start it rotating 1721 ** toward the right facing, but still flag it as if it had deployed. This is 1722 ** because it will deploy as soon as it reaches the correct facing. 1723 */ 1724 if (PrimaryFacing.Current() != DIR_SW) { 1725 Do_Turn(DIR_SW); 1726 // PrimaryFacing.Set_Desired(DIR_SW); 1727 IsDeploying = true; 1728 return(true); 1729 } 1730 1731 /* 1732 ** Since the unit is already facing the correct direction, actually do the 1733 ** deploy logic. If for some reason this cannot occur, then don't delete the 1734 ** unit, just mark it as not deploying. 1735 */ 1736 Mark(MARK_UP); 1737 BuildingClass * building = new BuildingClass(STRUCT_CONST, House->Class->House); 1738 if (building) { 1739 if (building->Unlimbo(Adjacent_Cell(Coord, FACING_NW))) { 1740 1741 /* 1742 ** Always reveal the construction yard to the player that owned the 1743 ** mobile construction vehicle. 1744 */ 1745 building->Revealed(House); 1746 1747 /* 1748 ** Force the newly placed construction yard to be in the same strength 1749 ** ratio as the MCV that deployed into it. 1750 */ 1751 int ratio = Health_Ratio(); 1752 building->Strength = Fixed_To_Cardinal(building->Class->MaxStrength, ratio); 1753 /* 1754 ** Force the MCV to drop any flag it was carrying. This will also set 1755 ** the owner house's flag home cell (since the house's FlagHome is 1756 ** presumably 0 at this point). 1757 */ 1758 Stun(); 1759 Delete_This(); 1760 return(true); 1761 } else { 1762 1763 /* 1764 ** Could not deploy the construction yard at this location! Just revert 1765 ** back to normal "just sitting there" mode and await further instructions. 1766 */ 1767 delete building; 1768 } 1769 } 1770 Mark(MARK_DOWN); 1771 IsDeploying = false; 1772 } 1773 } 1774 return(false); 1775 } 1776 1777 1778 /*********************************************************************************************** 1779 * UnitClass::Per_Cell_Process -- Performs operations necessary on a per cell basis. * 1780 * * 1781 * This routine will perform the operations necessary that occur when a unit is at the * 1782 * center of a cell. These operations could entail deploying into a construction yard, * 1783 * radioing a transport unit, and looking around for the enemy. * 1784 * * 1785 * INPUT: center -- Is the unit safely at the center of a cell? If it is merely "close" * 1786 * to the center, then this parameter will be false. * 1787 * * 1788 * OUTPUT: none * 1789 * * 1790 * WARNINGS: none * 1791 * * 1792 * HISTORY: * 1793 * 06/18/1994 JLB : Created. * 1794 * 06/17/1995 JLB : Handles case when building says "NO!" * 1795 * 06/30/1995 JLB : Gunboats head back and forth now. * 1796 *=============================================================================================*/ 1797 void UnitClass::Per_Cell_Process(bool center) 1798 { 1799 Validate(); 1800 CELL cell = Coord_Cell(Coord); 1801 TechnoClass * whom; 1802 HousesType house; 1803 1804 /* 1805 ** If this is a unit that is driving onto a building then the unit must enter 1806 ** the building as the final step. 1807 */ 1808 whom = Contact_With_Whom(); 1809 if (IsTethered && whom && center) { 1810 if (whom->What_Am_I() == RTTI_BUILDING && Mission == MISSION_ENTER) { 1811 if (whom == Map[cell].Cell_Building()) { 1812 switch (Transmit_Message(RADIO_IM_IN, whom)) { 1813 case RADIO_ROGER: 1814 break; 1815 1816 case RADIO_ATTACH: 1817 Mark(MARK_UP); 1818 SpecialFlag = true; 1819 Limbo(); 1820 SpecialFlag = false; 1821 whom->Attach(this); 1822 return; 1823 1824 default: 1825 Scatter(0, true); 1826 break; 1827 } 1828 } 1829 } 1830 } 1831 1832 /* 1833 ** When breaking away from a transport object or building, possibly 1834 ** scatter or otherwise begin normal unit operations. 1835 */ 1836 if (IsTethered && center && 1837 (Mission != MISSION_ENTER || (As_Techno(NavCom) != NULL && Contact_With_Whom() != As_Techno(NavCom))) && 1838 Mission != MISSION_UNLOAD) { 1839 /* 1840 ** Special hack check to make sure that even though it has moved one 1841 ** cell, if it is still on the building (e.g., service depot), have 1842 ** it scatter again. 1843 */ 1844 if (Map[cell].Cell_Building() != NULL && !Target_Legal(NavCom)) { 1845 Scatter(0, true, true); 1846 } else { 1847 TechnoClass * contact = Contact_With_Whom(); 1848 if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) { 1849 if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) != STRUCT_REPAIR) { 1850 Assign_Mission(MISSION_HARVEST); 1851 } else if (!Target_Legal(NavCom)) { 1852 Scatter(0, true); 1853 } 1854 } else { 1855 if (*this == UNIT_HARVESTER) { 1856 if (Target_Legal(ArchiveTarget)) { 1857 Assign_Mission(MISSION_HARVEST); 1858 Assign_Destination(ArchiveTarget); 1859 ArchiveTarget = TARGET_NONE; 1860 } else { 1861 1862 /* 1863 ** Since there is no place to go, move away to clear 1864 ** the pad for another harvester. 1865 */ 1866 if (!Target_Legal(NavCom)) { 1867 Scatter(0, true); 1868 } 1869 } 1870 } 1871 } 1872 } 1873 } 1874 1875 #ifdef OBSOLETE 1876 /* 1877 ** If this unit is on a teather, then cut it at this time so that 1878 ** the "parent" unit is free to proceed. Note that the parent 1879 ** unit might actually be a building. 1880 */ 1881 if (IsTethered && center && !Map[cell].Cell_Building()) { 1882 if (!Tiberium || *this != UNIT_HARVESTER) { 1883 Transmit_Message(RADIO_UNLOADED); 1884 if (*this == UNIT_HARVESTER) { 1885 if (Target_Legal(ArchiveTarget)) { 1886 Assign_Mission(MISSION_HARVEST); 1887 Assign_Destination(ArchiveTarget); 1888 ArchiveTarget = TARGET_NONE; 1889 } else { 1890 1891 /* 1892 ** Since there is no place to go, move away to clear 1893 ** the pad for another harvester. 1894 */ 1895 Scatter(0, true); 1896 } 1897 } 1898 } 1899 } 1900 #endif 1901 1902 #ifdef OBSOLETE 1903 /* 1904 ** If the unit is at the center of the repair facility, and that was his 1905 ** destination, then start him repairing. 1906 */ 1907 if (center && !IsRepairing) { 1908 BuildingClass * b = As_Building(NavCom); 1909 if (b && *b==STRUCT_REPAIR && Coord == b->Center_Coord()) { 1910 NavCom = 0; 1911 IsRepairing = true; 1912 Transmit_Message(RADIO_REPAIR_BEGIN_ANIM); 1913 } 1914 } 1915 #endif 1916 1917 /* 1918 ** Check to see if this is merely the end of a rotation for the MCV as it is 1919 ** preparing to deploy. In this case, it should begin its deploy process. 1920 */ 1921 if (center && IsDeploying) { 1922 Try_To_Deploy(); 1923 if (!IsActive) return; // Unit no longer exists -- bail. 1924 } 1925 1926 /* 1927 ** If this is a loaner unit and is is off the edge of the 1928 ** map, then it gets eliminated. That is, unless it is carrying cargo. This means that 1929 ** it is probably carrying an incoming reinforcement and it should not be eliminated. 1930 */ 1931 if (center && IsALoaner && !Map.In_Radar(cell)) { 1932 if (IsReturning || !Is_Something_Attached()) { 1933 if (*this == UNIT_GUNBOAT) { 1934 CELL cell = Coord_Cell(Coord); 1935 if (Cell_X(cell) <= Map.MapCellX) { 1936 Assign_Mission(MISSION_HUNT); 1937 Assign_Destination(::As_Target(XY_Cell(Map.MapCellX+Map.MapCellWidth, Cell_Y(cell)))); 1938 Set_Speed(255); 1939 PrimaryFacing = DIR_E; 1940 } else { 1941 Assign_Mission(MISSION_HUNT); 1942 Assign_Destination(::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(cell)))); 1943 Set_Speed(255); 1944 PrimaryFacing = DIR_W; 1945 } 1946 Mark(MARK_DOWN); 1947 } else { 1948 Mark(MARK_DOWN); 1949 Stun(); 1950 Delete_This(); 1951 return; 1952 } 1953 } 1954 } 1955 1956 #ifdef OBSOLETE 1957 /* 1958 ** Destroy any crushable wall that is driven over by a tracked vehicle. 1959 */ 1960 CellClass * cellptr = &Map[cell]; 1961 if (center && Class->Speed == SPEED_TRACK && cellptr->Overlay != OVERLAY_NONE) { 1962 OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); 1963 1964 if (optr->IsCrushable) { 1965 cellptr->Reduce_Wall(100); 1966 cellptr->Reduce_Wall(100); 1967 cellptr->Reduce_Wall(100); 1968 cellptr->Reduce_Wall(100); 1969 cellptr->Reduce_Wall(100); 1970 cellptr->Reduce_Wall(100); 1971 } 1972 } 1973 #endif 1974 1975 /* 1976 ** Check to see if crushing of any unfortunate infantry is warranted. 1977 */ 1978 Overrun_Square(Coord_Cell(Coord), false); 1979 1980 /* 1981 ** The unit performs looking around at this time. If the 1982 ** unit moved further than one square during the last track 1983 ** move, don't do an incremental look. Do a full look around 1984 ** instead. 1985 */ 1986 if (IsPlanningToLook) { 1987 IsPlanningToLook = false; 1988 Look(false); 1989 } else { 1990 Look(true); 1991 } 1992 1993 /* 1994 ** Act on new orders if the unit is at a good position to do so. 1995 */ 1996 if (center) { 1997 Commence(); 1998 } 1999 2000 /* 2001 ** Certain units require some setup time after they come to a halt. 2002 */ 2003 if (Special.IsDefenderAdvantage && /*center &&*/ !Target_Legal(NavCom) && Path[0] == FACING_NONE) { 2004 if (*this == UNIT_MLRS || *this == UNIT_ARTY || *this == UNIT_MSAM) { 2005 Arm = Rearm_Delay(false)*2; 2006 } 2007 } 2008 2009 /* 2010 ** If there is a house flag here, then this unit just might pick it up. 2011 */ 2012 if (center && Flagged == HOUSE_NONE) { 2013 2014 if (Map[cell].IsFlagged && !House->Is_Ally(Map[cell].Owner)) { 2015 HouseClass::As_Pointer(Map[cell].Owner)->Flag_Attach(this); 2016 } 2017 } 2018 2019 /* 2020 ** If this is the unit's own flag-home-cell and the unit is carrying 2021 ** a flag, destroy the house of the flag the unit is carrying. 2022 */ 2023 if (Flagged != HOUSE_NONE) { 2024 2025 /* 2026 ** If this vehicle is carrying your flag, then it will reveal the 2027 ** map for you as well as itself. This gives you and opportunity to 2028 ** attack the unit. 2029 */ 2030 if (!IsOwnedByPlayer && Flagged == PlayerPtr->Class->House) { 2031 Map.Sight_From(House, Coord_Cell(Coord), Class->SightRange, true); // Passed our house into Map.Sight_From since it now needs to know who it is performing the action on behalf of. ST - 3/28/2019 2:55PM 2032 } 2033 2034 /* 2035 ** If the flag reaches the home cell for the player, then the flag's 2036 ** owner will be destroyed. 2037 */ 2038 if (cell == HouseClass::As_Pointer(Owner())->FlagHome && center) { 2039 house = Flagged; // Flag_Remove will clear 'Flagged', so save it 2040 HouseClass::As_Pointer(house)->Flag_Remove(As_Target(),true); 2041 HouseClass::As_Pointer(house)->Flag_To_Die(); 2042 } 2043 } 2044 2045 TarComClass::Per_Cell_Process(center); 2046 } 2047 2048 2049 2050 /*********************************************************************************************** 2051 * UnitClass::Draw_It -- Draws a unit object. * 2052 * * 2053 * This routine is the one that actually draws a unit object. It displays the unit * 2054 * according to its current state flags and centered at the location specified. * 2055 * * 2056 * INPUT: x,y -- The X and Y coordinate of where to draw the unit. * 2057 * * 2058 * window -- The clipping window to use. * 2059 * * 2060 * OUTPUT: none * 2061 * * 2062 * WARNINGS: none * 2063 * * 2064 * HISTORY: * 2065 * 06/20/1994 JLB : Created. * 2066 * 06/27/1994 JLB : Takes a window parameter. * 2067 * 08/15/1994 JLB : Removed infantry support. * 2068 * 01/07/1995 JLB : Harvester animation support. * 2069 * 07/08/1995 JLB : Uses general purpose draw routine. * 2070 *=============================================================================================*/ 2071 void UnitClass::Draw_It(int x, int y, WindowNumberType window) 2072 { 2073 Validate(); 2074 int shapenum; // Working shape number. 2075 void const *shapefile; // Working shape file pointer. 2076 int facing = Facing_To_32(PrimaryFacing.Current()); 2077 int tfacing = Facing_To_32(SecondaryFacing.Current()); 2078 int shapestart; 2079 int xx, yy; 2080 2081 /* 2082 ** Verify the legality of the unit class. 2083 */ 2084 shapefile = Class->Get_Image_Data(); 2085 if (!shapefile) return; 2086 2087 /* 2088 ** If drawing of this unit is not explicitly prohibited, then proceed 2089 ** with the render process. 2090 */ 2091 const bool is_hidden = (Visual_Character() == VISUAL_HIDDEN) && (window != WINDOW_VIRTUAL); 2092 if (!is_hidden) { 2093 2094 /* 2095 ** For eight facing units, adjust the facing number accordingly. 2096 */ 2097 if (Class->IsPieceOfEight) { 2098 facing = Dir_Facing(PrimaryFacing.Current()); 2099 } 2100 2101 /* 2102 ** Calculations for special wake drawing. 2103 */ 2104 xx = x; 2105 yy = y; 2106 switch (Dir_Facing(PrimaryFacing.Current())) { 2107 case FACING_NE: 2108 case FACING_E: 2109 case FACING_SE: 2110 shapenum = UnitClass::BodyShape[tfacing] + 96; 2111 shapestart = 0; 2112 //xx -= 4; 2113 break; 2114 2115 case FACING_W: 2116 default: 2117 shapenum = UnitClass::BodyShape[tfacing]; 2118 shapestart = 6; 2119 xx += 4; 2120 break; 2121 } 2122 2123 /* 2124 ** Some units have only four facings, but are equipped with a turret 2125 ** that has 32 facings. 2126 */ 2127 if (Class->IsChunkyShape) { 2128 2129 /* 2130 ** Special wake drawing occurs here for non-virtual rendering 2131 */ 2132 if (*this == UNIT_GUNBOAT) { 2133 2134 if (window != WINDOW_VIRTUAL) { 2135 //Added this and wake name parameters. ST - 8/20/2019 10:54AM 2136 CC_Draw_Shape(this, "WAKE", UnitTypeClass::WakeShapes, shapestart + (Fetch_Stage() % 6), xx-1, yy+3, window, SHAPE_CENTER|SHAPE_WIN_REL); 2137 } 2138 2139 if (Health_Ratio() < 0x0080) shapenum += 32; 2140 if (Health_Ratio() < 0x0040) shapenum += 32; 2141 2142 } else { 2143 2144 /* 2145 ** Special hovercraft shape is ALWAYS N/S. 2146 */ 2147 shapenum = 0; 2148 2149 //shapenum = ((UnitClass::BodyShape[facing] + 4) / 8) & 0x03; 2150 } 2151 2152 } else { 2153 2154 /* 2155 ** Fetch the harvesting animation stage as appropriate. 2156 */ 2157 if (IsHarvesting && !PrimaryFacing.Is_Rotating() && !NavCom && !IsDriving) { 2158 static char _hstage[6] = { 2159 0, 1, 2, 3, 2, 1 2160 }; 2161 shapenum = 32 + (((UnitClass::BodyShape[facing]+2)/4)*4)+_hstage[Fetch_Stage()%sizeof(_hstage)]; 2162 } else { 2163 shapenum = UnitClass::BodyShape[facing]; 2164 if (Class->IsAnimating) { 2165 shapenum = Fetch_Stage(); 2166 } 2167 if (Class->IsPieceOfEight) { 2168 shapenum = 0; 2169 int numshapes = (IsDriving || (*this == UNIT_TREX) || (*this == UNIT_RAPT)) ? 8 : 12; 2170 if (facing) shapenum = UnitClass::BodyShape[24+facing]; 2171 if (IsDriving) shapenum = (Fetch_Stage()%numshapes) + 16 + (shapenum*numshapes); 2172 else if (IsFiring) shapenum = (Fetch_Stage()%numshapes) + 80 + (shapenum*numshapes); 2173 } else { 2174 2175 /* 2176 ** Door opening and closing animation must be handled carefully. There are only 2177 ** certain directions where this door animation will work. 2178 */ 2179 if (!Is_Door_Closed() && (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE)) { 2180 if (PrimaryFacing == DIR_NE) { 2181 shapenum = 32; 2182 } else { 2183 if (PrimaryFacing == DIR_NW) { 2184 shapenum = 35; 2185 } 2186 } 2187 shapenum += Door_Stage(); 2188 } 2189 } 2190 } 2191 } 2192 2193 /* 2194 ** The artillery unit should have its entire body recoil when it fires. 2195 */ 2196 if (*this == UNIT_ARTY && IsInRecoilState) { 2197 Recoil_Adjust(PrimaryFacing.Current(), x, y); 2198 } 2199 2200 /* 2201 ** Actually perform the draw. Overlay an optional shimmer effect as necessary. 2202 */ 2203 //if (*this == UNIT_HOVER) { 2204 // Mono_Printf("Display hover %p %d.\n", shapefile, shapenum); 2205 //} 2206 Techno_Draw_Object(shapefile, shapenum, x, y, window); 2207 2208 /* 2209 ** Special wake drawing occurs here for virtual rendering 2210 */ 2211 if (Class->IsChunkyShape && (*this == UNIT_GUNBOAT) && (window == WINDOW_VIRTUAL)) { 2212 //Added this and wake name parameters. ST - 8/20/2019 10:54AM 2213 CC_Draw_Shape(this, "WAKE", UnitTypeClass::WakeShapes, shapestart + (Fetch_Stage() % 6), xx-1, yy+3, window, SHAPE_CENTER|SHAPE_WIN_REL); 2214 } 2215 2216 /* 2217 ** If there is a rotating radar dish, draw it now. 2218 */ 2219 if (Class->IsRadarEquipped) { 2220 shapenum = 32 + (Frame % 32); 2221 Techno_Draw_Object(shapefile, shapenum, x, y-5, window); 2222 } 2223 2224 /* 2225 ** If there is a turret, then it must be rendered as well. This may include 2226 ** firing animation if required. 2227 */ 2228 if (!Class->IsChunkyShape && Class->IsTurretEquipped) { 2229 int x1 = x; 2230 int y1 = y; 2231 2232 /* 2233 ** Determine which turret shape to use. This depends on if there 2234 ** is any firing animation in progress. 2235 */ 2236 shapenum = TechnoClass::BodyShape[tfacing]+32; 2237 2238 /* 2239 ** The shape to use for the rocket launcher is dependant on the 2240 ** ammo remaining. 2241 */ 2242 if (*this == UNIT_MSAM) { 2243 if (Ammo == 0) shapenum += 64; 2244 if (Ammo == 1) shapenum += 32; 2245 } 2246 2247 /* 2248 ** A recoiling turret moves "backward" one pixel. 2249 */ 2250 if (IsInRecoilState) { 2251 Recoil_Adjust(SecondaryFacing.Current(), x1, y1); 2252 } 2253 2254 /* 2255 ** The Mobile SAM and the Missile Launchers need their turrets moved based 2256 ** on the facing of the vehicle. 2257 */ 2258 if (*this == UNIT_MSAM || *this == UNIT_MLRS) { 2259 Turret_Adjust(PrimaryFacing, x1, y1); 2260 } 2261 if (*this == UNIT_JEEP || *this == UNIT_BUGGY) { 2262 y1 -= 4; 2263 } 2264 2265 /* 2266 ** Actually perform the draw. Overlay an optional shimmer effect as necessary. 2267 */ 2268 Techno_Draw_Object(shapefile, shapenum, x1, y1, window); 2269 } 2270 2271 /* 2272 ** If this unit has "piggy back" unit(s), then render it at the same time. 2273 */ 2274 if (*this == UNIT_HOVER && Is_Something_Attached()) { 2275 TechnoClass * u = (TechnoClass *)Attached_Object(); 2276 2277 int counter = 0; 2278 for (;;) { 2279 int x1,y1; 2280 2281 if (Map.Coord_To_Pixel(Coord_Add(Coord_Add(Coord, 0xFF80FF80L), StoppingCoordAbs[counter++]), x1, y1)) { 2282 // Pass the window through, so that the virtual window will also work. ST - 6/18/2019 12:00PM 2283 //u->Draw_It(x1, y1, WINDOW_TACTICAL); 2284 u->Draw_It(x1, y1, window); 2285 } 2286 if (!u->Next) break; 2287 u = (TechnoClass *)u->Next; 2288 } 2289 } 2290 } 2291 2292 /* 2293 ** If this unit is carrying the flag, then draw that on top of everything else. 2294 */ 2295 if (Flagged != HOUSE_NONE) { 2296 shapefile = MixFileClass::Retrieve("FLAGFLY.SHP"); 2297 int flag_x = x + (ICON_PIXEL_W / 2) - 2; 2298 int flag_y = y + (3 * ICON_PIXEL_H / 4) - Get_Build_Frame_Height(shapefile); 2299 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, false), Map.UnitShadow, Flagged); 2300 } 2301 2302 TarComClass::Draw_It(x, y, window); 2303 } 2304 2305 2306 /*********************************************************************************************** 2307 * UnitClass::Goto_Tiberium -- Search for and head toward nearest available Tiberium patch. * 2308 * * 2309 * This routine is used to move a harvester to a place where it can load up with * 2310 * Tiberium. It will return true only if it can start harvesting. Otherwise, it sets * 2311 * the navigation computer toward the nearest Tiberium and lets the unit head there * 2312 * automatically. * 2313 * * 2314 * INPUT: none * 2315 * * 2316 * OUTPUT: int; Amount of Tiberium at this location. * 2317 * * 2318 * WARNINGS: none * 2319 * * 2320 * HISTORY: * 2321 * 07/18/1994 JLB : Created. * 2322 *=============================================================================================*/ 2323 int UnitClass::Tiberium_Check(CELL ¢er, int x, int y) 2324 { 2325 Validate(); 2326 /* 2327 ** If the specified offset from the origin will cause it 2328 ** to spill past the map edge, then abort this cell check. 2329 */ 2330 if (Cell_X(center)+x < Map.MapCellX) return(0); 2331 if (Cell_X(center)+x >= Map.MapCellX+Map.MapCellWidth) return(0); 2332 if (Cell_Y(center)+y < Map.MapCellY) return(0); 2333 if (Cell_Y(center)+y >= Map.MapCellY+Map.MapCellHeight) return(0); 2334 2335 center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y); 2336 2337 //using function for IsVisible so we have different results for different players - JAS 2019/09/30 2338 if ((GameToPlay != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Visible(PlayerPtr)))) { 2339 if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { 2340 return(Map[center].OverlayData+1); 2341 } 2342 } 2343 return(0); 2344 } 2345 2346 2347 bool UnitClass::Goto_Tiberium(void) 2348 { 2349 Validate(); 2350 if (!Target_Legal(NavCom)) { 2351 CELL center = Coord_Cell(Center_Coord()); 2352 if (Map[center].Land_Type() == LAND_TIBERIUM) { 2353 return(true); 2354 } else { 2355 2356 /* 2357 ** Perform a ring search outward from the center. 2358 */ 2359 for (int radius = 1; radius < 64; radius++) { 2360 CELL cell = center; 2361 CELL bestcell = 0; 2362 int tiberium = 0; 2363 int besttiberium = 0; 2364 for (int x = -radius; x <= radius; x++) { 2365 2366 /* 2367 ** Randomize the corners. 2368 */ 2369 int corner[2]; 2370 int corners[4][2] = { 2371 {x, -radius}, 2372 {x, +radius}, 2373 {-radius, x}, 2374 {+radius, x} 2375 }; 2376 for (int i = 0; i < 3; i++) { 2377 int j = i + rand() / (RAND_MAX / (4 - i) + 1); 2378 memcpy(&corner, &corners[j], sizeof(corner)); 2379 memcpy(&corners[j], &corners[i], sizeof(corner)); 2380 memcpy(&corners[i], corner, sizeof(corner)); 2381 } 2382 2383 cell = center; 2384 tiberium = Tiberium_Check(cell, corners[0][0], corners[0][1]); 2385 if (tiberium > besttiberium) { 2386 bestcell = cell; 2387 besttiberium = tiberium; 2388 } 2389 2390 cell = center; 2391 tiberium = Tiberium_Check(cell, corners[1][0], corners[1][1]); 2392 if (tiberium > besttiberium) { 2393 bestcell = cell; 2394 besttiberium = tiberium; 2395 } 2396 2397 cell = center; 2398 tiberium = Tiberium_Check(cell, corners[2][0], corners[2][1]); 2399 if (tiberium > besttiberium) { 2400 bestcell = cell; 2401 besttiberium = tiberium; 2402 } 2403 2404 cell = center; 2405 tiberium = Tiberium_Check(cell, corners[3][0], corners[3][1]); 2406 if (tiberium > besttiberium) { 2407 bestcell = cell; 2408 besttiberium = tiberium; 2409 } 2410 } 2411 if (bestcell) { 2412 Assign_Destination(::As_Target(bestcell)); 2413 return(false); 2414 } 2415 } 2416 } 2417 } 2418 2419 return(false); 2420 } 2421 2422 2423 BuildingClass* UnitClass::Find_Best_Refinery(void) const 2424 { 2425 /* 2426 ** Remember our last refinery and prefer that one, if still available and valid. 2427 */ 2428 if (TiberiumUnloadRefinery != NULL) { 2429 if (TiberiumUnloadRefinery->House == House && 2430 !TiberiumUnloadRefinery->IsInLimbo && 2431 TiberiumUnloadRefinery->Mission != MISSION_DECONSTRUCTION && 2432 *TiberiumUnloadRefinery == STRUCT_REFINERY) { 2433 return TiberiumUnloadRefinery; 2434 } else { 2435 TiberiumUnloadRefinery = NULL; 2436 } 2437 } 2438 2439 /* 2440 ** Find nearby refinery and head to it? 2441 */ 2442 return Find_Docking_Bay(STRUCT_REFINERY, false); 2443 } 2444 2445 2446 /*********************************************************************************************** 2447 * UnitClass::Harvesting -- Harvests tiberium at the current location. * 2448 * * 2449 * This routine is used to by the harvester to harvest Tiberium at the current location. * 2450 * When harvesting is complete, this routine will return true. * 2451 * * 2452 * INPUT: none * 2453 * * 2454 * OUTPUT: bool; Is harvesting complete? * 2455 * * 2456 * WARNINGS: none * 2457 * * 2458 * HISTORY: * 2459 * 07/18/1994 JLB : Created. * 2460 *=============================================================================================*/ 2461 bool UnitClass::Harvesting(void) 2462 { 2463 Validate(); 2464 CELL cell = Coord_Cell(Coord); 2465 CellClass * ptr = &Map[cell]; 2466 2467 /* 2468 ** Keep waiting if still heading toward a spot to harvest or the harvest timer hasn't expired yet. 2469 */ 2470 if (Target_Legal(NavCom) || !HarvestTimer.Expired() || IsDriving || IsRotating) return(true); 2471 2472 if (Tiberium_Load() < 0x0100 && ptr->Land_Type() == LAND_TIBERIUM) { 2473 2474 /* 2475 ** Lift some Tiberium from the ground. Try to lift a complete 2476 ** "level" of Tiberium. A level happens to be 6 steps. If there 2477 ** is a partial level, then lift that instead. Never lift more 2478 ** than the harvester can carry. 2479 */ 2480 int reducer = (ptr->OverlayData % 6) + 1; 2481 reducer = ptr->Reduce_Tiberium(MIN(reducer, UnitTypeClass::STEP_COUNT-Tiberium)); 2482 Tiberium += reducer; 2483 Set_Stage(0); 2484 Set_Rate(2); 2485 2486 HarvestTimer = TICKS_PER_SECOND; 2487 2488 } else { 2489 2490 /* 2491 ** If the harvester is stopped on a non Tiberium field and the harvester 2492 ** isn't loaded with Tiberium, then no further action can be performed 2493 ** by this logic routine. Bail with a failure and thus cause a branch to 2494 ** a better suited logic processor. 2495 */ 2496 Set_Stage(0); 2497 Set_Rate(0); 2498 return(false); 2499 } 2500 return(true); 2501 } 2502 2503 2504 /*********************************************************************************************** 2505 * UnitClass::Mission_Unload -- Handles unloading cargo. * 2506 * * 2507 * This is the AI control sequence for when a transport desires to unload its cargo and * 2508 * then exit the map. * 2509 * * 2510 * INPUT: none * 2511 * * 2512 * OUTPUT: Returns with the delay before calling this routine again. * 2513 * * 2514 * WARNINGS: none * 2515 * * 2516 * HISTORY: * 2517 * 07/18/1994 JLB : Created. * 2518 *=============================================================================================*/ 2519 int UnitClass::Mission_Unload(void) 2520 { 2521 Validate(); 2522 enum { 2523 INITIAL_CHECK, 2524 MANEUVERING, 2525 OPENING_DOOR, 2526 UNLOADING, 2527 CLOSING_DOOR 2528 }; 2529 DirType dir; 2530 CELL cell; 2531 2532 switch (Class->Type) { 2533 case UNIT_APC: 2534 switch (Status) { 2535 case INITIAL_CHECK: 2536 dir = Desired_Load_Dir(NULL, cell); 2537 if (How_Many() && cell != 0) { 2538 Do_Turn(dir); 2539 Status = MANEUVERING; 2540 return(1); 2541 } else { 2542 Assign_Mission(MISSION_GUARD); 2543 } 2544 break; 2545 2546 case MANEUVERING: 2547 if (!IsRotating) { 2548 APC_Open_Door(); 2549 if (Is_Door_Opening()) { 2550 Status = OPENING_DOOR; 2551 return(1); 2552 } 2553 } 2554 break; 2555 2556 case OPENING_DOOR: 2557 if (Is_Door_Open()) { 2558 Status = UNLOADING; 2559 return(1); 2560 } else { 2561 if (!Is_Door_Opening()) { 2562 Status = INITIAL_CHECK; 2563 } 2564 } 2565 break; 2566 2567 case UNLOADING: 2568 if (How_Many()) { 2569 FootClass * passenger = Detach_Object(); 2570 2571 if (passenger) { 2572 DirType toface = DIR_S + PrimaryFacing; 2573 bool placed = false; 2574 2575 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 2576 DirType newface = toface + face; 2577 CELL newcell = Adjacent_Cell(Coord_Cell(Coord), newface); 2578 2579 if (passenger->Can_Enter_Cell(newcell) == MOVE_OK) { 2580 ScenarioInit++; 2581 passenger->Unlimbo(Coord_Move(Coord, newface, 0x0080), newface); 2582 ScenarioInit--; 2583 passenger->Assign_Mission(MISSION_MOVE); 2584 passenger->Assign_Destination(::As_Target(newcell)); 2585 placed = true; 2586 break; 2587 } 2588 } 2589 2590 /* 2591 ** If the attached unit could NOT be deployed, then re-attach 2592 ** it and then bail out of this deploy process. 2593 */ 2594 if (!placed) { 2595 Attach(passenger); 2596 Status = CLOSING_DOOR; 2597 } 2598 else { 2599 passenger->Look(false); 2600 } 2601 } 2602 } else { 2603 Status = CLOSING_DOOR; 2604 } 2605 break; 2606 2607 /* 2608 ** Close APC door in preparation for normal operation. 2609 */ 2610 case CLOSING_DOOR: 2611 if (Is_Door_Open()) { 2612 APC_Close_Door(); 2613 } 2614 if (Is_Door_Closed()) { 2615 Assign_Mission(MISSION_GUARD); 2616 } 2617 break; 2618 } 2619 break; 2620 2621 case UNIT_MCV: 2622 switch (Status) { 2623 case 0: 2624 Path[0] = FACING_NONE; 2625 Status = 1; 2626 break; 2627 2628 case 1: 2629 if (!IsDriving) { 2630 Try_To_Deploy(); 2631 if (IsDeploying) { 2632 Status = 2; 2633 } else { 2634 /* 2635 ** Functionality from Red Alert for AI. ST - 7/25/2019 3:09PM 2636 */ 2637 #ifndef USE_RA_AI 2638 Assign_Mission(MISSION_GUARD); 2639 #else //USE_RA_AI 2640 if (!House->IsHuman && GameToPlay != GAME_NORMAL) { 2641 Assign_Mission(MISSION_HUNT); 2642 } else { 2643 Assign_Mission(MISSION_GUARD); 2644 } 2645 #endif //USE_RA_AI 2646 } 2647 } 2648 break; 2649 2650 case 2: 2651 #ifdef USE_RA_AI 2652 /* 2653 ** Functionality from Red Alert for AI. ST - 7/25/2019 3:09PM 2654 */ 2655 if (GameToPlay != GAME_NORMAL) { 2656 if (!IsDeploying) { 2657 Assign_Mission(MISSION_GUARD); 2658 } 2659 } 2660 #endif // USE_RA_AI 2661 break; 2662 } 2663 return(1); 2664 2665 case UNIT_HOVER: 2666 switch (Status) { 2667 case 0: 2668 if (Unload_Hovercraft_Process()) { 2669 Status = 1; 2670 } 2671 break; 2672 2673 /* 2674 ** Hovercraft always leave the map when they finish 2675 ** unloading. 2676 */ 2677 case 1: 2678 if (Team) { 2679 Team->Remove(this); 2680 } 2681 Exit_Map(); 2682 break; 2683 } 2684 break; 2685 } 2686 return(TICKS_PER_SECOND); 2687 } 2688 2689 2690 /*********************************************************************************************** 2691 * UnitClass::Mission_Harvest -- Handles the harvesting process used by harvesters. * 2692 * * 2693 * This is the AI process used by harvesters when they are doing their harvesting action. * 2694 * This entails searching for nearby Tiberium, heading there, harvesting, and then * 2695 * returning to a refinery for unloading. * 2696 * * 2697 * INPUT: none * 2698 * * 2699 * OUTPUT: Returns with the delay before calling this routine again. * 2700 * * 2701 * WARNINGS: none * 2702 * * 2703 * HISTORY: * 2704 * 07/18/1994 JLB : Created. * 2705 * 06/21/1995 JLB : Force guard mode if no Tiberium found. * 2706 *=============================================================================================*/ 2707 int UnitClass::Mission_Harvest(void) 2708 { 2709 Validate(); 2710 enum { 2711 LOOKING, 2712 HARVESTING, 2713 FINDHOME, 2714 HEADINGHOME, 2715 GOINGTOIDLE, 2716 }; 2717 2718 /* 2719 ** A non-harvesting type unit will just sit still if it is given the harvest mission. This 2720 ** allows combat units to act "brain dead". 2721 */ 2722 if (!Class->IsToHarvest) return(TICKS_PER_SECOND*30); 2723 2724 switch (Status) { 2725 2726 /* 2727 ** Go and find a Tiberium field to harvest. 2728 */ 2729 case LOOKING: 2730 IsHarvesting = false; 2731 /* 2732 ** Slightly hacky; if TarCom is set then skip to finding home state. 2733 */ 2734 if (Target_Legal(TarCom)) { 2735 Assign_Target(TARGET_NONE); 2736 Status = FINDHOME; 2737 return(1); 2738 } else if (Goto_Tiberium()) { 2739 IsHarvesting = true; 2740 Set_Rate(2); 2741 Set_Stage(0); 2742 Status = HARVESTING; 2743 ArchiveTarget = ::As_Target(Coord_Cell(Coord)); 2744 return(1); 2745 } else { 2746 2747 /* 2748 ** If the harvester isn't on Tiberium and it is not heading toward Tiberium, then 2749 ** force it to go into guard mode. This will prevent the harvester from repeatedly 2750 ** searching for Tiberium. 2751 */ 2752 if (!Target_Legal(NavCom)) { 2753 2754 /* 2755 ** If the archive target is legal, then head there since it is presumed 2756 ** that the archive target points to the last place it harvested at. This might 2757 ** solve the case where the harvester gets stuck and can't find Tiberium just because 2758 ** it is greater than 32 squares away. 2759 */ 2760 if (Target_Legal(ArchiveTarget)) { 2761 Assign_Destination(ArchiveTarget); 2762 } else { 2763 Status = GOINGTOIDLE; 2764 return(TICKS_PER_SECOND*15); 2765 } 2766 } 2767 } 2768 break; 2769 2770 /* 2771 ** Harvest at current location until full or Tiberium exhausted. 2772 */ 2773 case HARVESTING: 2774 if (!Harvesting()) { 2775 IsHarvesting = false; 2776 if (Tiberium_Load() == 0x0100) { 2777 Status = FINDHOME; 2778 } else { 2779 if (!Goto_Tiberium() && !Target_Legal(NavCom)) { 2780 ArchiveTarget = TARGET_NONE; 2781 Status = FINDHOME; 2782 } else { 2783 Status = HARVESTING; 2784 IsHarvesting = true; 2785 } 2786 } 2787 return(1); 2788 } else if (!Target_Legal(NavCom) && ArchiveTarget == TARGET_NONE) { 2789 ArchiveTarget = ::As_Target(Coord_Cell(Coord)); 2790 } 2791 break; 2792 2793 /* 2794 ** Find and head to refinery. 2795 */ 2796 case FINDHOME: 2797 if (!Target_Legal(NavCom)) { 2798 2799 /* 2800 ** Find best refinery. 2801 */ 2802 BuildingClass * nearest = Find_Best_Refinery(); 2803 2804 /* 2805 ** Since the refinery said it was ok to load, establish radio 2806 ** contact with the refinery and then await docking orders. 2807 */ 2808 if (nearest && Transmit_Message(RADIO_HELLO, nearest) == RADIO_ROGER) { 2809 Status = HEADINGHOME; 2810 } else { 2811 ScenarioInit++; 2812 nearest = Find_Best_Refinery(); 2813 ScenarioInit--; 2814 if (nearest) { 2815 Assign_Destination(::As_Target(nearest->Nearby_Location(this))); 2816 } 2817 } 2818 } 2819 break; 2820 2821 /* 2822 ** In communication with refinery so that it will successfully dock and 2823 ** unload. If, for some reason, radio contact was lost, then hunt for 2824 ** another refinery to unload at. 2825 */ 2826 case HEADINGHOME: 2827 Assign_Mission(MISSION_ENTER); 2828 return(1); 2829 2830 case GOINGTOIDLE: 2831 Assign_Mission(MISSION_GUARD); 2832 break; 2833 2834 } 2835 return(TICKS_PER_SECOND); 2836 } 2837 2838 2839 /*********************************************************************************************** 2840 * UnitClass::Mission_Hunt -- This is the AI process for aggressive enemy units. * 2841 * * 2842 * Computer controlled units must be intelligent enough to find enemies as well as to * 2843 * attack them. This AI process will handle both the simple attack process as well as the * 2844 * scanning for enemy units to attack. * 2845 * * 2846 * INPUT: none * 2847 * * 2848 * OUTPUT: Returns with the delay before calling this routine again. * 2849 * * 2850 * WARNINGS: none * 2851 * * 2852 * HISTORY: * 2853 * 07/18/1994 JLB : Created. * 2854 *=============================================================================================*/ 2855 int UnitClass::Mission_Hunt(void) 2856 { 2857 Validate(); 2858 if (*this == UNIT_MCV) { 2859 switch (Status) { 2860 2861 /* 2862 ** This stage handles locating a convenient spot, rotating to face the correct 2863 ** direction and then commencing the deployment operation. 2864 */ 2865 case 0: 2866 if (Goto_Clear_Spot()) { 2867 if (Try_To_Deploy()) { 2868 Status = 1; 2869 } 2870 } 2871 break; 2872 2873 /* 2874 ** This stage watchdogs the deployment operation and if for some reason, the deployment 2875 ** is aborted (the IsDeploying flag becomes false), then it reverts back to hunting for 2876 ** a convenient spot to deploy. 2877 */ 2878 case 1: 2879 if (!IsDeploying) { 2880 Status = 0; 2881 } 2882 break; 2883 } 2884 } else { 2885 2886 if (*this == UNIT_GUNBOAT) { 2887 if (!Target_Legal(NavCom)) { 2888 if (PrimaryFacing == DIR_W) { 2889 Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(Coord)))) ); 2890 } else { 2891 Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX+Map.MapCellWidth, Cell_Y(Coord_Cell(Coord)))) ); 2892 } 2893 Set_Speed(255); 2894 } 2895 if (!Speed) { 2896 Set_Speed(255); 2897 } 2898 if (!Target_Legal(TarCom) || !In_Range(TarCom, 0)) { 2899 Target_Something_Nearby(THREAT_AREA); 2900 } 2901 } else { 2902 return(TarComClass::Mission_Hunt()); 2903 } 2904 } 2905 return(TICKS_PER_SECOND); 2906 } 2907 2908 2909 int UnitClass::Mission_Enter(void) 2910 { 2911 Validate(); 2912 if (Class->IsToHarvest) { 2913 TechnoClass * contact = Contact_With_Whom(); 2914 if (contact == NULL) { 2915 contact = As_Techno(ArchiveTarget); 2916 } 2917 if (contact == NULL) { 2918 contact = As_Techno(NavCom); 2919 } 2920 if (contact != NULL && 2921 contact->What_Am_I() == RTTI_BUILDING && 2922 *((BuildingClass*)contact) == STRUCT_REFINERY) { 2923 TiberiumUnloadRefinery = (BuildingClass*)contact; 2924 } 2925 } 2926 2927 return(TarComClass::Mission_Enter()); 2928 } 2929 2930 2931 /*********************************************************************************************** 2932 * UnitClass::Look -- Perform map revelation from a unit's position. * 2933 * * 2934 * Reveal the map around the specified unit with the sighting range * 2935 * associated with the specified unit. * 2936 * * 2937 * INPUT: incremental -- If looking is to process only the cells in the * 2938 * outer ring of the unit's search radius, then * 2939 * set this parameter to true. This method is * 2940 * quite a bit faster than processing all cells, * 2941 * but must be used with caution. * 2942 * * 2943 * OUTPUT: none * 2944 * * 2945 * WARNINGS: none * 2946 * * 2947 * HISTORY: * 2948 * 06/08/1992 JLB : Created. * 2949 * 03/08/1994 JLB : Added incremental option. * 2950 * 04/15/1994 JLB : Converted to member function. * 2951 *=============================================================================================*/ 2952 void UnitClass::Look(bool incremental) 2953 { 2954 Validate(); 2955 //if (!IsInLimbo && IsOwnedByPlayer) { // Changed for mapping of multiple players 2956 if (!IsInLimbo && House && (House->IsHuman || GameToPlay != GAME_NORMAL)) { 2957 int sight = Class->SightRange; 2958 2959 if (sight) { 2960 Map.Sight_From(House, Coord_Cell(Coord), sight, (*this == UNIT_GUNBOAT) ? false : incremental); // Passed our house into Map.Sight_From since it now needs to know who it is performing the action on behalf of. ST - 3/28/2019 2:55PM 2961 } 2962 } 2963 } 2964 2965 2966 /*********************************************************************************************** 2967 * UnitClass::Overlap_List -- Determines overlap list for units. * 2968 * * 2969 * The unit overlap list is used to keep track of which cells are to * 2970 * be marked for redraw. This is critical in order to keep the units * 2971 * displayed correctly. * 2972 * * 2973 * INPUT: none * 2974 * * 2975 * OUTPUT: Returns with the overlap list pointer for the unit at its * 2976 * present position. * 2977 * * 2978 * WARNINGS: none * 2979 * * 2980 * HISTORY: * 2981 * 05/26/1994 JLB : Created. * 2982 * 06/19/1994 JLB : Uses Coord_Spillable_List function. * 2983 *=============================================================================================*/ 2984 short const * UnitClass::Overlap_List(void) const 2985 { 2986 Validate(); 2987 static short const _gunboat[] = {-3, -2, 2, 3, REFRESH_EOL}; 2988 int size; 2989 2990 /* 2991 ** The gunboat is a special case. 2992 */ 2993 if (*this == UNIT_GUNBOAT) { 2994 return(&_gunboat[0]); 2995 } 2996 2997 size = ICON_PIXEL_W; 2998 if (Is_Selected_By_Player() || IsFiring) { 2999 size += 24; 3000 } 3001 if (Is_Selected_By_Player() || Class->IsGigundo || IsAnimAttached || Flagged != HOUSE_NONE) { 3002 size = ICON_PIXEL_W*2; 3003 } 3004 return(Coord_Spillage_List(Coord, size)+1); 3005 } 3006 3007 3008 #ifdef NEVER 3009 /********************************************************************************************* * 3010 * UnitClass::Blocking_Object -- Determines how a object blocks a unit * 3011 * * 3012 * This routine is used by the Can_Enter_Cell logic when an object is in the desired cell * 3013 * and it needs to know if that causes blockage. If blocked, this routine will return why. * 3014 * * 3015 * INPUT: TechnoClass * pointer to object that is blocking unit * 3016 * * 3017 * CELL the cell the unit is being blocked in * 3018 * * 3019 * OUTPUT: MoveBitType the way that the object is blocking the unit * 3020 * * 3021 * HISTORY: * 3022 * 06/08/1995 PWG : Created. * 3023 *=============================================================================================*/ 3024 MoveBitType UnitClass::Blocking_Object(TechnoClass const *techno, CELL cell) const 3025 { 3026 Validate(); 3027 /* 3028 ** There are some extra checks we need to make if the techno is a unit 3029 */ 3030 bool unit = (techno->What_Am_I() == RTTI_INFANTRY || techno->What_Am_I() == RTTI_UNIT); 3031 CellClass const * cellptr = &Map[cell]; 3032 3033 if (House->Is_Ally(techno)) { 3034 3035 if (techno == Contact_With_Whom() && IsTethered) { 3036 return(MOVE_BIT_OK); 3037 } 3038 3039 if (unit) { 3040 /* 3041 ** If the unit in question has a destination than we should 3042 ** be prepared to wait for the unit to get out of our way. 3043 */ 3044 if (((FootClass *)techno)->NavCom != TARGET_NONE) { 3045 int face = Dir_Facing(PrimaryFacing); 3046 int techface = Dir_Facing(((FootClass const *)techno)->PrimaryFacing) ^4; 3047 if (face != techface && Distance((AbstractClass const *)techno) > 0x1FF) { 3048 return(MOVE_BIT_MOVING_BLOCK); 3049 } else { 3050 // Mono_Printf("Move No!\r"); 3051 return(MOVE_BIT_NO); 3052 } 3053 } 3054 3055 return(MOVE_BIT_TEMP); 3056 } 3057 } else { 3058 3059 /* 3060 ** If its an enemy unit, things are dealt with a little differently 3061 */ 3062 if (unit) { 3063 3064 #ifdef NEVER 3065 /* 3066 ** If this is an enemy unit and we are not doing a find path then 3067 ** we need to tell the unit to uncloak just in case it is a 3068 ** stealth tank. 3069 */ 3070 if (!IsFindPath) { 3071 techno->Do_Uncloak(); 3072 } 3073 #endif 3074 3075 /* 3076 ** Can we just run it over? 3077 */ 3078 if (techno->Class_Of().IsCrushable && (cellptr->Flag.Composite & 0xE0) == 0 && Class->IsCrusher) { 3079 3080 /* 3081 ** Now lets run it over. 3082 */ 3083 return(MOVE_BIT_OK); 3084 } 3085 3086 /* 3087 ** If the object is cloaked, then consider it passable for findpath purposes, 3088 ** but not so for all other cases. 3089 */ 3090 if (techno->Cloak == CLOAKED) { 3091 if (House == techno->House) return(MOVE_BIT_NO); 3092 if (IsFindPath) return(MOVE_BIT_OK); 3093 return(MOVE_BIT_CLOAK); 3094 } 3095 3096 /* 3097 ** If our vehicle is weapon equipped, then report that the cell occupier 3098 ** needs only to be destroyed in order to make the cell passable. 3099 */ 3100 if (Class->Primary != WEAPON_NONE) { 3101 return(MOVE_BIT_DESTROYABLE); 3102 } 3103 } 3104 } 3105 return(MOVE_BIT_NO); 3106 } 3107 #endif 3108 3109 3110 /*********************************************************************************************** 3111 * UnitClass::Can_Enter_Cell -- Determines cell entry legality. * 3112 * * 3113 * Use this routine to determine if the unit can enter the cell * 3114 * specified and given the direction of entry specified. Typically, * 3115 * this is used when determining unit travel path. * 3116 * * 3117 * INPUT: cell -- The cell to examine. * 3118 * * 3119 * facing -- The facing that the unit would enter the specified * 3120 * cell. If this value is -1, then don't consider * 3121 * facing when performing the check. * 3122 * * 3123 * OUTPUT: Returns the reason why it couldn't enter the cell or MOVE_OK if movement is * 3124 * allowed. * 3125 * * 3126 * WARNINGS: none * 3127 * * 3128 * HISTORY: * 3129 * 09/07/1992 JLB : Created. * 3130 * 04/16/1994 JLB : Converted to member function. * 3131 * 07/04/1995 JLB : Allowed to drive on building trying to enter it. * 3132 *=============================================================================================*/ 3133 MoveType UnitClass::Can_Enter_Cell(CELL cell, FacingType ) const 3134 { 3135 Validate(); 3136 CellClass const * cellptr = &Map[cell]; 3137 3138 if ((unsigned)cell >= MAP_CELL_TOTAL) return(MOVE_NO); 3139 3140 /* 3141 ** The gunboat can always move. This prevents it from trying to move around possible hover 3142 ** craft blockage. 3143 */ 3144 if (*this == UNIT_GUNBOAT) return(MOVE_OK); 3145 3146 /* 3147 ** Moving off the edge of the map is not allowed unless 3148 ** this is a loaner vehicle. 3149 */ 3150 if (IsLocked && !IsALoaner && !ScenarioInit && !Map.In_Radar(cell)) { 3151 return(MOVE_NO); 3152 } 3153 3154 MoveType retval = MOVE_OK; 3155 3156 /* 3157 ** Certain vehicles can drive over walls. Check for this case and 3158 ** and return the appropriate flag. Other units treat walls as impassable. 3159 */ 3160 if (cellptr->Overlay != OVERLAY_NONE) { 3161 OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); 3162 3163 if (optr->IsCrate && !House->IsHuman) { 3164 return(MOVE_NO); 3165 } 3166 3167 if (optr->IsWall) { 3168 if (Class->Primary != WEAPON_NONE) { 3169 WarheadTypeClass const * whead = &Warheads[BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead]; 3170 3171 if (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)) { 3172 if (!House->IsHuman && !House->Is_Ally(cellptr->Owner)) { 3173 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3174 } else { 3175 return(MOVE_NO); 3176 } 3177 } else { 3178 return(MOVE_NO); 3179 } 3180 } else { 3181 return(MOVE_NO); 3182 } 3183 } 3184 } 3185 3186 /* 3187 ** If the cell is out and out impassable because of underlying terrain, then 3188 ** return this immutable fact. 3189 */ 3190 #ifdef ADVANCED 3191 if (retval != MOVE_DESTROYABLE && !Ground[cellptr->Land_Type()].Cost[Class->Speed]) { 3192 #else 3193 if (!Ground[cellptr->Land_Type()].Cost[Class->Speed]) { 3194 #endif 3195 return(MOVE_NO); 3196 } 3197 3198 /* 3199 ** Loop through all of the objects in the square setting a bit 3200 ** for how they affect movement. 3201 */ 3202 bool crushable = false; 3203 ObjectClass *obj = cellptr->Cell_Occupier(); 3204 while (obj) { 3205 3206 if (obj != this) { 3207 3208 /* 3209 ** Always allow entry if trying to move on a cell with 3210 ** authorization from the occupier. 3211 */ 3212 if (obj == Contact_With_Whom() && (IsTethered || (obj->What_Am_I() == RTTI_BUILDING && *((BuildingClass *)obj) == STRUCT_REPAIR))) { 3213 return(MOVE_OK); 3214 } 3215 3216 bool is_moving = (obj->What_Am_I() == RTTI_INFANTRY || obj->What_Am_I() == RTTI_UNIT) && Target_Legal(((FootClass *)obj)->NavCom); 3217 3218 if (House->Is_Ally(obj)) { 3219 if (is_moving) { 3220 int face = Dir_Facing(PrimaryFacing); 3221 int techface = Dir_Facing(((FootClass const *)obj)->PrimaryFacing) ^4; 3222 if (face == techface && Distance((AbstractClass const *)obj) <= 0x1FF) { 3223 return(MOVE_NO); 3224 } 3225 if (retval < MOVE_MOVING_BLOCK) retval = MOVE_MOVING_BLOCK; 3226 } else { 3227 if (obj->What_Am_I() == RTTI_BUILDING) return(MOVE_NO); 3228 if (retval < MOVE_TEMP) retval = MOVE_TEMP; 3229 } 3230 } else { 3231 3232 /* 3233 ** Cloaked enemy objects are not considered if this is a Find_Path() 3234 ** call. 3235 */ 3236 if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) { 3237 3238 /* 3239 ** If this unit can crush infantry, and there is an enemy infantry in the 3240 ** cell, don't consider the cell impassible. This is true even if the unit 3241 ** doesn't contain a legitimate weapon. 3242 */ 3243 if (!Class->IsCrusher || !obj->Class_Of().IsCrushable) { 3244 3245 /* 3246 ** Any non-allied blockage is considered impassible if the unit 3247 ** is not equipped with a weapon. 3248 */ 3249 if (Class->Primary == WEAPON_NONE) return(MOVE_NO); 3250 3251 /* 3252 ** Some kinds of terrain are considered destroyable if the unit is equipped 3253 ** with the weapon that can destroy it. Otherwise, the terrain is considered 3254 ** impassable. 3255 */ 3256 switch (obj->What_Am_I()) { 3257 case RTTI_TERRAIN: 3258 if (((TerrainClass *)obj)->Class->IsFlammable && 3259 BulletTypeClass::As_Reference(Weapons[Class->Primary].Fires).Warhead == WARHEAD_FIRE) { 3260 3261 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3262 } else { 3263 return(MOVE_NO); 3264 } 3265 break; 3266 3267 default: 3268 if (retval < MOVE_DESTROYABLE) retval = MOVE_DESTROYABLE; 3269 break; 3270 } 3271 } else { 3272 crushable = true; 3273 } 3274 } else { 3275 if (retval < MOVE_CLOAK) retval = MOVE_CLOAK; 3276 } 3277 } 3278 } 3279 3280 /* 3281 ** Move to next object in chain. 3282 */ 3283 obj = obj->Next; 3284 } 3285 3286 /* 3287 ** If some allied object has reserved the cell, then consider the cell 3288 ** as blocked by a moving object. 3289 */ 3290 if (retval == MOVE_OK && !crushable && cellptr->Flag.Composite) { 3291 3292 /* 3293 ** If reserved by a vehicle, then consider this blocked terrain. 3294 */ 3295 if (cellptr->Flag.Occupy.Vehicle) { 3296 retval = MOVE_MOVING_BLOCK; 3297 } else { 3298 if (cellptr->InfType != HOUSE_NONE && House->Is_Ally(cellptr->InfType)) { 3299 retval = MOVE_MOVING_BLOCK; 3300 } else { 3301 3302 /* 3303 ** Enemy infantry have reserved the cell. If this unit can crush 3304 ** infantry, consider the cell passable. If not, then consider the 3305 ** cell destroyable if it has a weapon. If neither case applies, then 3306 ** this vehicle should avoid the cell altogether. 3307 */ 3308 if (!Class->IsCrusher) { 3309 if (Class->Primary != WEAPON_NONE) { 3310 retval = MOVE_DESTROYABLE; 3311 } else { 3312 return(MOVE_NO); 3313 } 3314 } 3315 } 3316 } 3317 } 3318 3319 /* 3320 ** If its ok to move into the cell because we can crush whats in the cell, then 3321 ** make sure no one else is already moving into the cell to crush something. 3322 */ 3323 if (retval == MOVE_OK && crushable && cellptr->Flag.Occupy.Vehicle) { 3324 3325 #ifdef ADVANCED 3326 /* 3327 ** However, if the cell is occupied by a crushable vehicle, then we can 3328 ** never be sure if some other friendly vehicle is also trying to crush 3329 ** the cell at the same time. In the case of a crushable vehicle in the 3330 ** cell, then allow entry. 3331 */ 3332 if (!cellptr->Cell_Unit() || !cellptr->Cell_Unit()->Class->IsCrushable) { 3333 return(MOVE_MOVING_BLOCK); 3334 } 3335 #else 3336 return(MOVE_MOVING_BLOCK); 3337 #endif 3338 } 3339 3340 /* 3341 ** Return with the most severe reason why this cell would be impassable. 3342 */ 3343 return(retval); 3344 } 3345 3346 3347 /*********************************************************************************************** 3348 * UnitClass::Init -- Clears all units for scenario preparation. * 3349 * * 3350 * This routine will zero out the unit list and unit objects. This routine is typically * 3351 * used in preparation for a new scenario load. All units are guaranteed to be eliminated * 3352 * by this routine. * 3353 * * 3354 * INPUT: none * 3355 * * 3356 * OUTPUT: none * 3357 * * 3358 * WARNINGS: none * 3359 * * 3360 * HISTORY: * 3361 * 08/15/1994 JLB : Created. * 3362 *=============================================================================================*/ 3363 void UnitClass::Init(void) 3364 { 3365 UnitClass * ptr; 3366 3367 Units.Free_All(); 3368 3369 ptr = new UnitClass(); 3370 VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; 3371 delete ptr; 3372 } 3373 3374 3375 /*********************************************************************************************** 3376 * UnitClass::Target_Coord -- The coordinate to use when targeting this unit. * 3377 * * 3378 * Sometimes the coordinate to use when targeting an object is not the same as its center * 3379 * coordinate. This is especially true for boats since leading their movement is needed * 3380 * in order have any chance of hitting. * 3381 * * 3382 * INPUT: none * 3383 * * 3384 * OUTPUT: Returns with the coordinate to fire upon when attacking the unit. * 3385 * * 3386 * WARNINGS: none * 3387 * * 3388 * HISTORY: * 3389 * 09/19/1994 JLB : Created. * 3390 *=============================================================================================*/ 3391 COORDINATE UnitClass::Target_Coord(void) const 3392 { 3393 Validate(); 3394 // if (*this == UNIT_GUNBOAT) { 3395 // return(Coord_Move(Coord, PrimaryFacing.Current(), 0x0080)); 3396 // } 3397 return(TarComClass::Center_Coord()); 3398 } 3399 3400 3401 /*********************************************************************************************** 3402 * UnitClass::Scatter -- Causes the unit to travel to a nearby safe cell. * 3403 * * 3404 * This routine is called when the unit discovers that it should get out of the "hot seat" * 3405 * and move to an adjacent cell. Since the safety of the adjacent cell is not determined * 3406 * before the move begins, it will appear that the unit is just scattering (which it * 3407 * should). * 3408 * * 3409 * INPUT: threat -- The coordinate of the source of the threat. The unit will try to move * 3410 * roughly away from the threat. * 3411 * * 3412 * OUTPUT: none * 3413 * * 3414 * WARNINGS: none * 3415 * * 3416 * HISTORY: * 3417 * 09/25/1994 JLB : Created. * 3418 *=============================================================================================*/ 3419 void UnitClass::Scatter(COORDINATE threat, bool forced, bool nokidding) 3420 { 3421 Validate(); 3422 if (*this != UNIT_GUNBOAT && *this != UNIT_HOVER) { 3423 if (PrimaryFacing.Is_Rotating()) return; 3424 if (Target_Legal(NavCom) && !nokidding) return; 3425 if (threat == 0) { 3426 Assign_Destination(::As_Target(Map.Nearby_Location(Coord_Cell(Coord)))); 3427 } else if (((!Target_Legal(TarCom) && !Target_Legal(NavCom)) || forced || nokidding || Random_Pick(1, 4) == 1)) { 3428 FacingType toface; 3429 FacingType newface; 3430 CELL newcell; 3431 3432 if (threat) { 3433 toface = Dir_Facing(Direction8(threat, Coord)); 3434 toface = toface + (Random_Pick(0, 2)-1); 3435 } else { 3436 toface = Dir_Facing(PrimaryFacing.Current()); 3437 } 3438 3439 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 3440 newface = toface + face; 3441 newcell = Adjacent_Cell(Coord_Cell(Coord), newface); 3442 3443 if (Map.In_Radar(newcell) && Can_Enter_Cell(newcell) == MOVE_OK) { 3444 Assign_Destination(::As_Target(newcell)); 3445 } 3446 } 3447 } 3448 } 3449 } 3450 3451 3452 /*********************************************************************************************** 3453 * UnitClass::Stop_Driver -- Handles removing occupation bits when driving stops. * 3454 * * 3455 * This routine will remove the "reservation" flag (if present) when the vehicle is * 3456 * required to stop movement. * 3457 * * 3458 * INPUT: none * 3459 * * 3460 * OUTPUT: bool; Was the vehicle stopped? * 3461 * * 3462 * WARNINGS: none * 3463 * * 3464 * HISTORY: * 3465 * 12/22/1994 JLB : Created. * 3466 *=============================================================================================*/ 3467 bool UnitClass::Stop_Driver(void) 3468 { 3469 Validate(); 3470 3471 /* 3472 ** We only need to do something if the vehicle is actually going 3473 ** somewhere. 3474 */ 3475 if (Head_To_Coord()) { 3476 3477 /* 3478 ** Safe off whether the vehicle is down or not so we know whether 3479 ** we have to put it back down. 3480 */ 3481 int temp = IsDown; 3482 3483 /* 3484 ** If the vehicle is down, pick it up so it doesnt interfere with 3485 ** our flags. 3486 */ 3487 if (temp) { 3488 Mark(MARK_UP); 3489 } 3490 3491 /* 3492 ** Call the drive class function which will let us release the 3493 ** reserved track. 3494 */ 3495 Mark_Track(Head_To_Coord(), MARK_UP); 3496 3497 /* 3498 ** If it was down it should be down when we are done. 3499 */ 3500 if (temp) { 3501 Mark(MARK_DOWN); 3502 } 3503 } 3504 return(TarComClass::Stop_Driver()); 3505 } 3506 3507 3508 /*********************************************************************************************** 3509 * UnitClass::Start_Driver -- Starts driving and reserves destination cell. * 3510 * * 3511 * This routine will start the vehicle moving by marking the destination cell as * 3512 * reserved. Cells must be reserved in this fashion or else they might become occupied as * 3513 * the vehicle is proceeding toward it. * 3514 * * 3515 * INPUT: headto -- The location where the vehicle will be heading. * 3516 * * 3517 * OUTPUT: bool; Was the vehicle started to move? Failure could be the result of the cell * 3518 * being occupied. * 3519 * * 3520 * WARNINGS: none * 3521 * * 3522 * HISTORY: * 3523 * 12/22/1994 JLB : Created. * 3524 *=============================================================================================*/ 3525 bool UnitClass::Start_Driver(COORDINATE & headto) 3526 { 3527 Validate(); 3528 if (TarComClass::Start_Driver(headto)) { 3529 Mark_Track(headto, MARK_DOWN); 3530 return(true); 3531 } 3532 return(false); 3533 } 3534 3535 3536 /*********************************************************************************************** 3537 * UnitClass::Limbo -- Prepares vehicle and then limbos it. * 3538 * * 3539 * This routine removes the occupation bits for the vehicle and also handles cleaning up * 3540 * any vehicle reservation bits. After this, it then proceeds with limboing the unit. * 3541 * * 3542 * INPUT: none * 3543 * * 3544 * OUTPUT: bool; Was the vehicle limboed? * 3545 * * 3546 * WARNINGS: none * 3547 * * 3548 * HISTORY: * 3549 * 12/22/1994 JLB : Created. * 3550 *=============================================================================================*/ 3551 bool UnitClass::Limbo(void) 3552 { 3553 Validate(); 3554 if (!IsInLimbo) { 3555 Stop_Driver(); 3556 } 3557 if (TarComClass::Limbo()) { 3558 if (Flagged != HOUSE_NONE) { 3559 HouseClass::As_Pointer(Flagged)->Flag_Attach(Coord_Cell(Coord)); 3560 } 3561 return(true); 3562 } 3563 return(false); 3564 } 3565 3566 3567 /*********************************************************************************************** 3568 * UnitClass::Response_Select -- Voice feedback when selecting the unit. * 3569 * * 3570 * This is the voice to play when the unit is selected. * 3571 * * 3572 * INPUT: none * 3573 * * 3574 * OUTPUT: none * 3575 * * 3576 * WARNINGS: none * 3577 * * 3578 * HISTORY: * 3579 * 12/30/1994 JLB : Created. * 3580 *=============================================================================================*/ 3581 void UnitClass::Response_Select(void) 3582 { 3583 Validate(); 3584 static VocType _response[] = { 3585 VOC_VEHIC, 3586 VOC_UNIT, 3587 VOC_YESSIR, 3588 VOC_YESSIR, 3589 VOC_YESSIR, 3590 VOC_AWAIT 3591 }; 3592 VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; 3593 3594 if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { 3595 response = VOC_DINOYES; 3596 } 3597 3598 if (AllowVoice) { 3599 Sound_Effect(response, 0, -(Units.ID(this)+1)); 3600 } 3601 } 3602 3603 3604 /*********************************************************************************************** 3605 * UnitClass::Response_Move -- Voice feedback when ordering the unit to move. * 3606 * * 3607 * This plays the audio feedback when ordering this unit to move to a new destination. * 3608 * * 3609 * INPUT: none * 3610 * * 3611 * OUTPUT: none * 3612 * * 3613 * WARNINGS: none * 3614 * * 3615 * HISTORY: * 3616 * 12/30/1994 JLB : Created. * 3617 *=============================================================================================*/ 3618 void UnitClass::Response_Move(void) 3619 { 3620 Validate(); 3621 static VocType _response[] = { 3622 VOC_MOVEOUT, 3623 VOC_MOVEOUT, 3624 VOC_MOVEOUT, 3625 VOC_ACKNOWL, 3626 VOC_AFFIRM, 3627 VOC_AFFIRM 3628 }; 3629 VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; 3630 3631 if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { 3632 response = VOC_DINOMOUT; 3633 } 3634 3635 if (AllowVoice) { 3636 Sound_Effect(response, 0, -(Units.ID(this)+1)); 3637 } 3638 } 3639 3640 3641 /*********************************************************************************************** 3642 * UnitClass::Response_Attack -- Voice feedback when ordering the unit to attack a target. * 3643 * * 3644 * This plays the audio feedback when ordering this unit to attack. * 3645 * * 3646 * INPUT: none * 3647 * * 3648 * OUTPUT: none * 3649 * * 3650 * WARNINGS: none * 3651 * * 3652 * HISTORY: * 3653 * 12/30/1994 JLB : Created. * 3654 *=============================================================================================*/ 3655 void UnitClass::Response_Attack(void) 3656 { 3657 Validate(); 3658 static VocType _response[] = { 3659 VOC_AFFIRM, 3660 VOC_ACKNOWL, 3661 VOC_YESSIR, 3662 VOC_YESSIR, 3663 VOC_YESSIR 3664 }; 3665 VocType response = _response[Sim_Random_Pick(0, (int)(sizeof(_response) / sizeof(_response[0]))-1)]; 3666 3667 if (*this == UNIT_TRIC || *this == UNIT_TREX || *this == UNIT_RAPT || *this == UNIT_STEG) { 3668 response = VOC_DINOMOUT; 3669 } 3670 3671 if (AllowVoice) { 3672 Sound_Effect(response, 0, -(Units.ID(this)+1)); 3673 } 3674 } 3675 3676 3677 /*********************************************************************************************** 3678 * UnitClass::What_Action -- Determines what action would occur if clicked on object. * 3679 * * 3680 * Use this function to determine what action would likely occur if the specified object * 3681 * were clicked on while this unit was selected as current. This function controls, not * 3682 * only the action to perform, but indirectly controls the cursor shape to use as well. * 3683 * * 3684 * INPUT: object -- The object that to check for against "this" object. * 3685 * * 3686 * OUTPUT: Returns with the default action to perform. If no clear action can be determined, * 3687 * then ACTION_NONE is returned. * 3688 * * 3689 * WARNINGS: none * 3690 * * 3691 * HISTORY: * 3692 * 01/11/1995 JLB : Created. * 3693 *=============================================================================================*/ 3694 ActionType UnitClass::What_Action(ObjectClass * object) const 3695 { 3696 Validate(); 3697 ActionType action = TarComClass::What_Action(object); 3698 3699 /* 3700 ** If the unit doesn't have a weapon, but can crush the object, then consider 3701 ** the object as a movable location. 3702 */ 3703 if (action == ACTION_ATTACK && !Can_Player_Fire()) { 3704 if (Class->IsCrusher && object->Class_Of().IsCrushable) { 3705 action = ACTION_MOVE; 3706 } else { 3707 action = ACTION_SELECT; 3708 } 3709 } 3710 3711 /* 3712 ** Don't allow special deploy action unless there is something to deploy. 3713 */ 3714 if (action == ACTION_SELF) { 3715 if (*this != UNIT_MCV) { 3716 if (!Class->IsTransporter || !How_Many()) { 3717 action = ACTION_NONE; 3718 } 3719 } else { 3720 ((ObjectClass &)(*this)).Mark(MARK_UP); 3721 if (!BuildingTypeClass::As_Reference(STRUCT_CONST).Legal_Placement(Coord_Cell(Adjacent_Cell(Center_Coord(), FACING_NW)))) { 3722 action = ACTION_NO_DEPLOY; 3723 } 3724 ((ObjectClass &)(*this)).Mark(MARK_DOWN); 3725 } 3726 } 3727 3728 /* 3729 ** Special return to friendly refinery action. 3730 */ 3731 if (Is_Owned_By_Player() && House->Class->House == object->Owner() && object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) { 3732 action = ACTION_ENTER; 3733 } 3734 3735 /* 3736 ** Special return to friendly repair factory action. 3737 */ 3738 if (Is_Owned_By_Player() && House->Class->House == object->Owner() && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) { 3739 BuildingClass * building = (BuildingClass *)object; 3740 if (building->Class->Type == STRUCT_REPAIR && !building->In_Radio_Contact() && !building->Is_Something_Attached()) { 3741 action = ACTION_MOVE; 3742 } 3743 } 3744 3745 return(action); 3746 } 3747 3748 3749 ActionType UnitClass::What_Action(CELL cell) const 3750 { 3751 Validate(); 3752 ActionType action = TarComClass::What_Action(cell); 3753 if (action == ACTION_MOVE && Map[cell].Land_Type() == LAND_TIBERIUM && Class->IsToHarvest) { 3754 return(ACTION_HARVEST); 3755 } 3756 return(action); 3757 } 3758 3759 3760 /*********************************************************************************************** 3761 * UnitClass::Can_Player_Move -- Determines if the player is legally allowed to move it. * 3762 * * 3763 * Use this routine to see if the player can move this object. If the player can move the * 3764 * object, even only in theory, then this function returns true. In all other cases, such * 3765 * as for enemy units, gunboats, or hovercraft, it returns false. * 3766 * * 3767 * INPUT: none * 3768 * * 3769 * OUTPUT: bool; Can the player give this object a movement order? * 3770 * * 3771 * WARNINGS: none * 3772 * * 3773 * HISTORY: * 3774 * 01/19/1995 JLB : Created. * 3775 *=============================================================================================*/ 3776 bool UnitClass::Can_Player_Move(void) const 3777 { 3778 Validate(); 3779 return(TarComClass::Can_Player_Move() && *this != UNIT_GUNBOAT && *this != UNIT_HOVER); 3780 } 3781 3782 3783 /*********************************************************************************************** 3784 * UnitClass::Read_INI -- Reads units from scenario INI file. * 3785 * * 3786 * This routine is used to read all the starting units from the * 3787 * scenario control INI file. The units are created and placed on the * 3788 * map by this routine. * 3789 * * 3790 * INI entry format: * 3791 * Housename, Typename, Strength, Coord, Facingnum, Missionname, Triggername * 3792 * * 3793 * INPUT: buffer -- Pointer to the loaded scenario INI file. * 3794 * * 3795 * OUTPUT: none * 3796 * * 3797 * WARNINGS: none * 3798 * * 3799 * HISTORY: * 3800 * 05/24/1994 JLB : Created. * 3801 *=============================================================================================*/ 3802 void UnitClass::Read_INI(char *buffer) 3803 { 3804 UnitClass *unit; // Working unit pointer. 3805 char *tbuffer; // Accumulation buffer of unit IDs. 3806 HousesType inhouse; // Unit house. 3807 UnitType classid; // Unit class. 3808 int len; // Length of data in buffer. 3809 char buf[128]; 3810 3811 len = strlen(buffer) + 2; 3812 tbuffer = buffer + len; 3813 3814 WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-len, buffer); 3815 while (*tbuffer != '\0') { 3816 3817 WWGetPrivateProfileString(INI_Name(), tbuffer, NULL, buf, sizeof(buf)-1, buffer); 3818 inhouse = HouseTypeClass::From_Name(strtok(buf, ",")); 3819 if (inhouse != HOUSE_NONE) { 3820 classid = UnitTypeClass::From_Name(strtok(NULL, ",")); 3821 3822 if (classid != UNIT_NONE) { 3823 3824 if (HouseClass::As_Pointer(inhouse) != NULL) { 3825 unit = new UnitClass(classid, inhouse); 3826 if (unit) { 3827 3828 /* 3829 ** Read the raw data. 3830 */ 3831 int strength = atoi(strtok(NULL, ",\r\n")); 3832 COORDINATE coord = Cell_Coord((CELL)atoi(strtok(NULL, ",\r\n"))); 3833 DirType dir = (DirType)atoi(strtok(NULL, ",\r\n")); 3834 MissionType mission = MissionClass::Mission_From_Name(strtok(NULL, ",\n\r")); 3835 unit->Trigger = TriggerClass::As_Pointer(strtok(NULL,",\r\n")); 3836 if (unit->Trigger) { 3837 unit->Trigger->AttachCount++; 3838 } 3839 3840 if (unit->Unlimbo(coord, dir)) { 3841 unit->Strength = Fixed_To_Cardinal(unit->Class->MaxStrength, strength); 3842 if (GameToPlay == GAME_NORMAL || unit->House->IsHuman) { 3843 unit->Assign_Mission(mission); 3844 unit->Commence(); 3845 } else { 3846 unit->Enter_Idle_Mode(); 3847 } 3848 3849 /* 3850 ** The gunboat is a special case: It must "drive" off the edge of the map. 3851 ** Just pick the map edge that it is facing and set that as the destination 3852 ** of the drive. 3853 */ 3854 if (*unit == UNIT_GUNBOAT) { 3855 unit->PrimaryFacing.Set_Desired(DIR_W); 3856 unit->PrimaryFacing.Set_Current(DIR_W); 3857 unit->Assign_Mission(MISSION_HUNT); 3858 unit->Commence(); 3859 unit->Assign_Destination( ::As_Target(XY_Cell(Map.MapCellX-1, Cell_Y(Coord_Cell(unit->Coord))))); 3860 } 3861 3862 } else { 3863 3864 /* 3865 ** If the unit could not be unlimboed, then this is a catastrophic error 3866 ** condition. Delete the unit. 3867 */ 3868 delete unit; 3869 } 3870 } 3871 } 3872 } 3873 } 3874 tbuffer += strlen(tbuffer)+1; 3875 } 3876 } 3877 3878 3879 /*********************************************************************************************** 3880 * UnitClass::Write_INI -- Writes all the units out to an INI file. * 3881 * * 3882 * This routine writes all of the units in the game out to an INI file. This is used * 3883 * in the scenario editor when the game needs to be saved. * 3884 * * 3885 * INI entry format: * 3886 * Housename, Typename, Strength, Coord, Facingnum, Missionname, Triggername * 3887 * * 3888 * INPUT: buffer -- A pointer to the loaded INI file staging area. * 3889 * * 3890 * OUTPUT: none * 3891 * * 3892 * WARNINGS: none * 3893 * * 3894 * HISTORY: * 3895 * 05/28/1994 JLB : Created. * 3896 *=============================================================================================*/ 3897 void UnitClass::Write_INI(char *buffer) 3898 { 3899 int index; 3900 char uname[10]; 3901 char buf[128]; 3902 char *tbuffer; // Accumulation buffer of unit IDs. 3903 3904 /* 3905 ** First, clear out all existing unit data from the ini file. 3906 */ 3907 tbuffer = buffer + strlen(buffer) + 2; 3908 WWGetPrivateProfileString(INI_Name(), NULL, NULL, tbuffer, ShapeBufferSize-strlen(buffer), buffer); 3909 while (*tbuffer != '\0') { 3910 WWWritePrivateProfileString(INI_Name(), tbuffer, NULL, buffer); 3911 tbuffer += strlen(tbuffer)+1; 3912 } 3913 3914 /* 3915 ** Write the unit data out. 3916 */ 3917 for (index = 0; index < Units.Count(); index++) { 3918 UnitClass * unit; 3919 3920 unit = Units.Ptr(index); 3921 if (!unit->IsInLimbo && unit->IsActive) { 3922 3923 sprintf(uname, "%03d", index); 3924 sprintf(buf, "%s,%s,%d,%u,%d,%s,%s", 3925 unit->House->Class->IniName, 3926 unit->Class->IniName, 3927 unit->Health_Ratio(), 3928 Coord_Cell(unit->Coord), 3929 unit->PrimaryFacing.Current(), 3930 MissionClass::Mission_Name(unit->Mission), 3931 unit->Trigger ? unit->Trigger->Get_Name() : "None" 3932 ); 3933 WWWritePrivateProfileString(INI_Name(), uname, buf, buffer); 3934 } 3935 } 3936 } 3937 3938 3939 /*********************************************************************************************** 3940 * UnitClass::Exit_Repair -- Drive the unit off the repair facility. * 3941 * * 3942 * INPUT: none * 3943 * * 3944 * OUTPUT: none * 3945 * * 3946 * WARNINGS: none * 3947 * * 3948 * HISTORY: * 3949 * 04/03/1995 BWG : Created. * 3950 *=============================================================================================*/ 3951 #define XYCELL(x,y) (y*MAP_CELL_W+x) 3952 void UnitClass::Exit_Repair(void) 3953 { 3954 Validate(); 3955 int i; 3956 CELL cell; 3957 bool found = false; 3958 static short const ExitRepair[] = { 3959 XYCELL(0,-2), 3960 XYCELL(1,-1), 3961 XYCELL(2, 0), 3962 XYCELL(1, 1), 3963 XYCELL(0, 2), 3964 XYCELL(-1,1), 3965 XYCELL(-2,0), 3966 XYCELL(-1,-1) 3967 }; 3968 3969 cell = Coord_Cell(Coord) + ExitRepair[Dir_Facing(PrimaryFacing.Current())]; 3970 if (Can_Enter_Cell(cell) == MOVE_OK) found = true; 3971 3972 if (!found) for (i=0; i<8; i++) { 3973 cell = Coord_Cell(Coord) + ExitRepair[i]; 3974 if (Can_Enter_Cell(cell) == MOVE_OK) { 3975 found = true; 3976 break; 3977 } 3978 } 3979 if (found) { 3980 DirType dir = Direction(cell); 3981 3982 Assign_Mission(MISSION_MOVE); 3983 Assign_Destination(::As_Target(cell)); 3984 } 3985 } 3986 3987 3988 /*********************************************************************************************** 3989 * UnitClass::Mission_Guard -- Special guard mission override processor. * 3990 * * 3991 * This routine will intercept the guard mission and if it is for a hovercraft, assign * 3992 * it the unload mission instead. This prevents the hovercraft from being stuck in the * 3993 * water if something unexpected causes it to drop into guard mode. * 3994 * * 3995 * INPUT: none * 3996 * * 3997 * OUTPUT: Returns the time delay before this command is executed again. * 3998 * * 3999 * WARNINGS: none * 4000 * * 4001 * HISTORY: * 4002 * 05/08/1995 JLB : Created. * 4003 * 05/08/1995 JLB : Fixes gunboat problems. * 4004 *=============================================================================================*/ 4005 int UnitClass::Mission_Guard(void) 4006 { 4007 Validate(); 4008 if (*this == UNIT_HOVER) { 4009 if (Is_Something_Attached()) { 4010 Assign_Mission(MISSION_UNLOAD); 4011 Find_LZ(); 4012 } else { 4013 Exit_Map(); 4014 } 4015 return(TICKS_PER_SECOND); 4016 } 4017 4018 if (*this == UNIT_GUNBOAT) { 4019 Assign_Mission(MISSION_HUNT); 4020 return(TICKS_PER_SECOND); 4021 } 4022 4023 if (*this == UNIT_HARVESTER && !House->IsHuman) { 4024 Assign_Mission(MISSION_HARVEST); 4025 return(TICKS_PER_SECOND); 4026 } 4027 4028 #ifdef USE_RA_AI 4029 /* 4030 ** Copied functionality from RA for AI. ST - 7/25/2019 3:11PM 4031 */ 4032 if (GameToPlay != GAME_NORMAL) { 4033 if (*this == UNIT_MCV && House->IsBaseBuilding && House->IsHuman == false) { 4034 Assign_Mission(MISSION_UNLOAD); 4035 return(TICKS_PER_SECOND + Random_Pick(0, 2)); 4036 } 4037 } 4038 #endif // USE_RA_AI 4039 4040 return(TarComClass::Mission_Guard()); 4041 } 4042 4043 4044 /*********************************************************************************************** 4045 * UnitClass::Mission_Move -- Handles special move mission overrides. * 4046 * * 4047 * This routine intercepts the normal move mission and if a gunboat is being processed, * 4048 * changes its mission to hunt. This is an attempt to keep the gunboat on the hunt mission * 4049 * regardless of what the player did. * 4050 * * 4051 * INPUT: none * 4052 * * 4053 * OUTPUT: Returns the number of ticks before this routine should be called again. * 4054 * * 4055 * WARNINGS: none * 4056 * * 4057 * HISTORY: * 4058 * 05/09/1995 JLB : Created. * 4059 *=============================================================================================*/ 4060 int UnitClass::Mission_Move(void) 4061 { 4062 Validate(); 4063 IsHarvesting = false; 4064 4065 /* 4066 ** Always make sure that that transport door is closed if the vehcile is moving. 4067 */ 4068 if (!Is_Door_Closed()) { 4069 APC_Close_Door(); 4070 } 4071 4072 /* 4073 ** Gunboats must always have the hunt mission. 4074 */ 4075 if (*this == UNIT_GUNBOAT) { 4076 Assign_Mission(MISSION_HUNT); 4077 return(TICKS_PER_SECOND); 4078 } 4079 return(TarComClass::Mission_Move()); 4080 } 4081 4082 4083 /*********************************************************************************************** 4084 * UnitClass::Desired_Load_Dir -- Determines the best cell and facing for loading. * 4085 * * 4086 * This routine examines the unit and adjacent cells in order to find the best facing * 4087 * for the transport and best staging cell for the potential passengers. This location is * 4088 * modified by adjacent cell passability and direction of the potential passenger. * 4089 * * 4090 * INPUT: passenger -- Pointer to the potential passenger. * 4091 * * 4092 * moveto -- Reference to the cell number that specifies where the potential * 4093 * passenger should move to first. * 4094 * * 4095 * OUTPUT: Returns with the direction the transport should face before opening the transport * 4096 * door. * 4097 * * 4098 * WARNINGS: none * 4099 * * 4100 * HISTORY: * 4101 * 05/23/1995 JLB : Created. * 4102 *=============================================================================================*/ 4103 DirType UnitClass::Desired_Load_Dir(ObjectClass * passenger, CELL & moveto) const 4104 { 4105 Validate(); 4106 /* 4107 ** Determine the ideal facing that provides the least resistance. This would be the direction 4108 ** of the potential passenger or the current transport facing if it is going to unload. 4109 */ 4110 DirType faceto; 4111 if (passenger) { 4112 faceto = Direction(passenger); 4113 } else { 4114 faceto = PrimaryFacing.Current() + DIR_S; 4115 } 4116 4117 /* 4118 ** Sweep through the adjacent cells in order to find the best candidate. 4119 */ 4120 FacingType bestdir; 4121 int bestval = -1; 4122 for (FacingType face = FACING_N; face < FACING_COUNT; face++) { 4123 int value = 0; 4124 CELL cellnum = Adjacent_Cell(Coord_Cell(Coord), face); 4125 4126 /* 4127 ** Base the initial value of the potential cell according to whether the passenger is 4128 ** allowed to enter the cell. If it can't, then give such a negative value to the 4129 ** cell so that it is prevented from ever choosing that cell for load/unload. 4130 */ 4131 if (passenger) { 4132 value = (passenger->Can_Enter_Cell(cellnum) == MOVE_OK || Coord_Cell(passenger->Coord) == cellnum) ? 128 : -128; 4133 } else { 4134 CellClass * cell = &Map[cellnum]; 4135 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) { 4136 value = -128; 4137 } else { 4138 if (cell->Cell_Techno() && !House->Is_Ally(cell->Cell_Techno())) { 4139 value = -128; 4140 } else { 4141 value = 128; 4142 } 4143 } 4144 } 4145 4146 /* 4147 ** Give more weight to the cells that require the least rotation of the transport or the 4148 ** least roundabout movement for the potential passenger. 4149 */ 4150 value -= (int)ABS(Dir_Diff(Facing_Dir(face), faceto)); 4151 if (face == FACING_S) { 4152 value -= 100; 4153 } 4154 if (face == FACING_SW || face == FACING_SE) value += 64; 4155 4156 /* 4157 ** If the value for the potiential cell is greater than the last recorded potential 4158 ** value, then record this cell as the best candidate. 4159 */ 4160 if (bestval == -1 || value > bestval) { 4161 bestval = value; 4162 bestdir = face; 4163 } 4164 } 4165 4166 /* 4167 ** If a suitable direction was found, then return with the direction value. 4168 */ 4169 moveto = 0; 4170 if (bestval > 0) { 4171 static DirType _desired_to_actual[FACING_COUNT] = {DIR_S, DIR_SW, DIR_NW, DIR_NW, DIR_NE, DIR_NE, DIR_NE, DIR_SE}; 4172 4173 moveto = Adjacent_Cell(Coord_Cell(Coord), bestdir); 4174 return(_desired_to_actual[bestdir]); 4175 } 4176 return(DIR_S); 4177 } 4178 4179 4180 /*********************************************************************************************** 4181 * UnitClass::Mission_Attack -- Handles the mission attack logic. * 4182 * * 4183 * This routine intercepts the normal mission attack logic. If a gunboat is assigned the * 4184 * attack mission then it must be converted back to a hunt mission. * 4185 * * 4186 * INPUT: none * 4187 * * 4188 * OUTPUT: Returns with the time before calling this routine again. * 4189 * * 4190 * WARNINGS: none * 4191 * * 4192 * HISTORY: * 4193 * 05/23/1995 JLB : Created. * 4194 *=============================================================================================*/ 4195 int UnitClass::Mission_Attack(void) 4196 { 4197 Validate(); 4198 if (*this == UNIT_GUNBOAT) { 4199 Assign_Mission(MISSION_HUNT); 4200 return(TICKS_PER_SECOND); 4201 } 4202 return(TarComClass::Mission_Attack()); 4203 } 4204 4205 4206 /*********************************************************************************************** 4207 * UnitClass::Flag_Attach -- Attaches a house flag to this unit. * 4208 * * 4209 * This routine will attach a house flag to this unit. * 4210 * * 4211 * INPUT: house -- The house that is having its flag attached to it. * 4212 * * 4213 * OUTPUT: Was the house flag successfully attached to this unit? * 4214 * * 4215 * WARNINGS: A unit can only carry one flag at a time. This might be a reason for failure * 4216 * of this routine. * 4217 * * 4218 * HISTORY: * 4219 * 05/23/1995 JLB : Created. * 4220 *=============================================================================================*/ 4221 bool UnitClass::Flag_Attach(HousesType house) 4222 { 4223 Validate(); 4224 if (house != HOUSE_NONE && Flagged == HOUSE_NONE) { 4225 Flagged = house; 4226 Mark(MARK_CHANGE); 4227 return(true); 4228 } 4229 return(false); 4230 } 4231 4232 4233 /*********************************************************************************************** 4234 * UnitClass::Flag_Remove -- Removes the house flag from this unit. * 4235 * * 4236 * This routine will remove the house flag that is attached to this unit. * 4237 * * 4238 * INPUT: none * 4239 * * 4240 * OUTPUT: Was the flag successfully removed? * 4241 * * 4242 * WARNINGS: This routine doesn't put the flag into a new location. That operation must * 4243 * be performed or else the house flag will cease to exist. * 4244 * * 4245 * HISTORY: * 4246 * 05/23/1995 JLB : Created. * 4247 *=============================================================================================*/ 4248 bool UnitClass::Flag_Remove(void) 4249 { 4250 Validate(); 4251 if (Flagged != HOUSE_NONE) { 4252 Flagged = HOUSE_NONE; 4253 Mark(MARK_CHANGE); 4254 return(true); 4255 } 4256 return(false); 4257 } 4258 4259 4260 /*********************************************************************************************** 4261 * UnitClass::Stun -- Stuns the unit in preparation for unit removal. * 4262 * * 4263 * This routine intercepts the stun operation for the unit and if there is a house flag * 4264 * attached, it will drop it to the ground. * 4265 * * 4266 * INPUT: none * 4267 * * 4268 * OUTPUT: none * 4269 * * 4270 * WARNINGS: none * 4271 * * 4272 * HISTORY: * 4273 * 05/23/1995 JLB : Created. * 4274 *=============================================================================================*/ 4275 void UnitClass::Stun(void) 4276 { 4277 Validate(); 4278 if (Flagged != HOUSE_NONE) { 4279 HouseClass::As_Pointer(Flagged)->Flag_Attach(Coord_Cell(Coord)); 4280 } 4281 TarComClass::Stun(); 4282 } 4283 4284 4285 /*********************************************************************************************** 4286 * UnitClass::Pip_Count -- Fetchs the number of pips to display on unit. * 4287 * * 4288 * This routine is used to fetch the number of "fullness" pips to display on the unit. * 4289 * This will either be the number of passengers or the percentage full (in 1/5ths) of * 4290 * a harvester. * 4291 * * 4292 * INPUT: none * 4293 * * 4294 * OUTPUT: Returns with the number of pips to draw on this unit. * 4295 * * 4296 * WARNINGS: none * 4297 * * 4298 * HISTORY: * 4299 * 06/25/1995 JLB : Created. * 4300 *=============================================================================================*/ 4301 int UnitClass::Pip_Count(void) const 4302 { 4303 Validate(); 4304 if (Class->IsTransporter) { 4305 return(How_Many()); 4306 } 4307 if (Class->IsToHarvest) { 4308 return(Fixed_To_Cardinal(UnitTypeClass::FULL_LOAD_CREDITS/100, Tiberium_Load())); 4309 } 4310 return(0); 4311 } 4312 4313 4314 /*********************************************************************************************** 4315 * UnitClass::APC_Close_Door -- Closes an APC door. * 4316 * * 4317 * This routine will initiate closing of the APC door. * 4318 * * 4319 * INPUT: none * 4320 * * 4321 * OUTPUT: none * 4322 * * 4323 * WARNINGS: none * 4324 * * 4325 * HISTORY: * 4326 * 06/25/1995 JLB : Created. * 4327 *=============================================================================================*/ 4328 void UnitClass::APC_Close_Door(void) 4329 { 4330 Validate(); 4331 Close_Door(10, 2); 4332 } 4333 4334 4335 /*********************************************************************************************** 4336 * UnitClass::APC_Open_Door -- Opens an APC door. * 4337 * * 4338 * This routine will initiate opening of the APC door. * 4339 * * 4340 * INPUT: none * 4341 * * 4342 * OUTPUT: none * 4343 * * 4344 * WARNINGS: none * 4345 * * 4346 * HISTORY: * 4347 * 06/25/1995 JLB : Created. * 4348 *=============================================================================================*/ 4349 void UnitClass::APC_Open_Door(void) 4350 { 4351 Validate(); 4352 if (!IsDriving && !IsRotating) { 4353 if (PrimaryFacing == DIR_NW || PrimaryFacing == DIR_NE) { 4354 Open_Door(10, 2); 4355 } else { 4356 Open_Door(1, 2); 4357 } 4358 } 4359 } 4360 4361 4362 /*********************************************************************************************** 4363 * UnitClass::Remap_Table -- Fetches the remap table to use for this object. * 4364 * * 4365 * Use this routine to determine the rendering remap table to use for this object. The * 4366 * remap table is normally the unit remap table, except for the MCV and the Harvestor. * 4367 * These units use the building remap table since these units become part of the building * 4368 * animation. * 4369 * * 4370 * INPUT: none * 4371 * * 4372 * OUTPUT: Returns with a pointer to the remap table to use for this unit. * 4373 * * 4374 * WARNINGS: none * 4375 * * 4376 * HISTORY: * 4377 * 07/08/1995 JLB : Created. * 4378 *=============================================================================================*/ 4379 void const * UnitClass::Remap_Table(void) 4380 { 4381 Validate(); 4382 if (*this == UNIT_MCV || *this == UNIT_HARVESTER) { 4383 return(House->Remap_Table(IsBlushing, false)); 4384 } 4385 return(TarComClass::Remap_Table()); 4386 } 4387 4388 4389 /*********************************************************************************************** 4390 * UnitClass::Crew_Type -- Fetches the kind of crew that this object produces. * 4391 * * 4392 * When a unit is destroyed, a crew member might be generated. This routine will return * 4393 * with the infantry type to produce for this unit. This routine will be called for every * 4394 * survivor that is generated. * 4395 * * 4396 * INPUT: none * 4397 * * 4398 * OUTPUT: Returns with a suggested infantry type to generate as a survivor from this unit. * 4399 * * 4400 * WARNINGS: none * 4401 * * 4402 * HISTORY: * 4403 * 08/13/1995 JLB : Created. * 4404 *=============================================================================================*/ 4405 InfantryType UnitClass::Crew_Type(void) const 4406 { 4407 Validate(); 4408 if (Class->Primary == WEAPON_NONE) { 4409 if (Random_Pick(0, 1) == 0) { 4410 return(INFANTRY_C1); 4411 } else { 4412 return(INFANTRY_C7); 4413 } 4414 } 4415 return(TarComClass::Crew_Type()); 4416 } 4417 4418 4419 /*********************************************************************************************** 4420 * UnitClass::What_Am_I -- Returns with the RTTI type this object is. * 4421 * * 4422 * This will return that this is a normal vehicle unit type. Each object class overrides * 4423 * this function in order to provide run time type identification support. * 4424 * * 4425 * INPUT: none * 4426 * * 4427 * OUTPUT: Returns the RTTI type that this object is (i.e., RTTI_UNIT). * 4428 * * 4429 * WARNINGS: none * 4430 * * 4431 * HISTORY: * 4432 * 08/13/1995 JLB : Created. * 4433 *=============================================================================================*/ 4434 RTTIType UnitClass::What_Am_I(void) const 4435 { 4436 Validate(); 4437 return(RTTI_UNIT); 4438 }