TEAM.CPP (60382B)
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\team.cpv 2.18 16 Oct 1995 16:48:34 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 : TEAM.CPP * 24 * * 25 * Programmer : Joe L. Bostic * 26 * * 27 * Start Date : 12/11/94 * 28 * * 29 * Last Update : August 6, 1995 [JLB] * 30 * * 31 *---------------------------------------------------------------------------------------------* 32 * Functions: * 33 * Assign_Mission_Target -- Sets teams mission target and clears old target * 34 * TeamClass::Add -- Adds specified object to team. * 35 * TeamClass::AI -- Process team logic. * 36 * TeamClass::As_Target -- Converts this team object into a target number. * 37 * TeamClass::Calc_Center -- Determines average location of team members. * 38 * TeamClass::Control -- Updates control on a member unit. * 39 * TeamClass::Coordinate_Attack -- Handles coordinating a team attack. * 40 * TeamClass::Coordinate_Conscript -- Gives orders to new recruit. * 41 * TeamClass::Coordinate_Move -- Handles team movement coordination. * 42 * TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). * 43 * TeamClass::Coordinate_Unload -- Tells the team to unload passengers now. * 44 * TeamClass::Detach -- Removes specified target from team tracking. * 45 * TeamClass::Init -- Initializes the team objects for scenario preparation. * 46 * TeamClass::Is_A_Member -- Tests if a unit is a member of a team * 47 * TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. * 48 * TeamClass::Remove -- Removes the specified object from the team. * 49 * TeamClass::Suspend_Teams -- Suspends activity for low priority teams * 50 * TeamClass::Took_Damage -- Informs the team when the team member takes damage. * 51 * TeamClass::Validate -- validates team pointer * 52 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 53 54 #include "function.h" 55 #include "mission.h" 56 57 /* 58 ** This array records the number of teams in existance of each type. 59 */ 60 unsigned char TeamClass::Number[TEAMTYPE_MAX]; 61 62 /* 63 ** This array records the success rating of each of the team types. 64 */ 65 unsigned char TeamClass::Success[TEAMTYPE_MAX]; 66 67 /* 68 ** This contains the value of the Virtual Function Table Pointer 69 */ 70 void * TeamClass::VTable; 71 72 73 /*********************************************************************************************** 74 * TeamClass::Validate -- validates team pointer * 75 * * 76 * INPUT: * 77 * none. * 78 * * 79 * OUTPUT: * 80 * 1 = ok, 0 = error * 81 * * 82 * WARNINGS: * 83 * none. * 84 * * 85 * HISTORY: * 86 * 08/09/1995 BRR : Created. * 87 *=============================================================================================*/ 88 #ifdef CHEAT_KEYS 89 int TeamClass::Validate(void) const 90 { 91 int num; 92 93 num = Teams.ID(this); 94 if (num < 0 || num >= TEAM_MAX) { 95 Validate_Error("TEAM"); 96 return (0); 97 } 98 else 99 return (1); 100 } 101 #else 102 #define Validate() 103 #endif 104 105 106 /*********************************************************************************************** 107 * TeamClass::Init -- Initializes the team objects for scenario preparation. * 108 * * 109 * This routine clears out the team object array in preparation for starting a new * 110 * scenario. * 111 * * 112 * INPUT: none * 113 * * 114 * OUTPUT: none * 115 * * 116 * WARNINGS: none * 117 * * 118 * HISTORY: * 119 * 12/29/1994 JLB : Created. * 120 *=============================================================================================*/ 121 void TeamClass::Init(void) 122 { 123 TeamClass *ptr; 124 125 Teams.Free_All(); 126 memset(Number, 0, sizeof(Number)); 127 memset(Success, 0, sizeof(Success)); 128 129 ptr = new TeamClass(); 130 VTable = ((void **)(((char *)ptr) + sizeof(AbstractClass) - 4))[0]; 131 delete ptr; 132 } 133 134 135 void * TeamClass::operator new (size_t) 136 { 137 void * ptr = Teams.Allocate(); 138 if (ptr) { 139 ((TeamClass *)ptr)->Set_Active(); 140 } 141 return(ptr); 142 } 143 144 145 void TeamClass::operator delete(void * ptr) 146 { 147 if (ptr) { 148 ((TeamClass *)ptr)->IsActive = false; 149 } 150 Teams.Free((TeamClass *)ptr); 151 } 152 153 154 TeamClass::~TeamClass(void) 155 { 156 if (GameActive && Class) { 157 Number[TeamTypes.ID(Class)]--; 158 while (Member) { 159 Remove(Member); 160 } 161 162 if (Class->IsTransient && !Number[TeamTypes.ID(Class)]) { 163 delete (TeamTypeClass *)Class; 164 } 165 } 166 } 167 168 169 TeamClass::TeamClass(TeamTypeClass const * type, HouseClass * owner) : 170 Class(type), 171 House(owner) 172 { 173 memset(Quantity, 0, sizeof(Quantity)); 174 IsAltered = true; 175 IsForcedActive = false; 176 IsFullStrength = false; 177 IsUnderStrength = true; 178 IsReforming = false; 179 IsLagging = false; 180 IsMoving = false; 181 IsHasBeen = false; 182 Center = 0; 183 Target = TARGET_NONE; 184 ObjectiveCenter = 0; 185 MissionTarget = TARGET_NONE; 186 Member = 0; 187 Total = 0; 188 Risk = 0; 189 CurrentMission = -1; 190 IsNextMission = true; 191 TimeOut = 0; 192 SuspendTimer.Clear(); 193 Suspended = false; 194 Number[TeamTypes.ID(Class)]++; 195 } 196 197 198 /*************************************************************************** 199 * TeamClass::Assign_Mission_Target -- Sets mission target and clears old * 200 * * 201 * INPUT: * 202 * * 203 * OUTPUT: * 204 * * 205 * WARNINGS: * 206 * * 207 * HISTORY: * 208 * 05/16/1995 PWG : Created. * 209 *=========================================================================*/ 210 void TeamClass::Assign_Mission_Target(TARGET new_target) 211 { 212 Validate(); 213 /* 214 ** First go through and find anyone who is currently targetting 215 ** the old mission target and clear their Tarcom. 216 */ 217 FootClass * unit = Member; 218 while (unit) { 219 bool tar = (unit->TarCom == MissionTarget); 220 bool nav = (unit->NavCom == MissionTarget); 221 if (tar || nav) { 222 223 /* 224 ** If the unit was doing something related to the team mission 225 ** then we kick him into guard mode so that he is easy to change 226 ** missions for. 227 */ 228 unit->Assign_Mission(MISSION_GUARD); 229 230 /* 231 ** If the unit's tarcom is set to the old mission target, then 232 ** clear it, so that it will be reset by whatever happens next. 233 */ 234 if (nav) { 235 unit->Assign_Destination(TARGET_NONE); 236 } 237 238 /* 239 ** If the unit's navcom is set to the old mission target, then 240 ** clear it, so that it will be reset by whatever happens next. 241 */ 242 if (tar) { 243 unit->Assign_Target(TARGET_NONE); 244 } 245 } 246 unit = (FootClass *)unit->Member; 247 } 248 249 /* 250 ** If there is not currently an override on the current mission target 251 ** then assign both MissionTarget and Target to the new target. If 252 ** there is an overide, allow the team to keep fighting the overide but 253 ** make sure they pick up on the new mission when they are ready. 254 */ 255 if (Target == MissionTarget || !Target_Legal(Target)) { 256 MissionTarget = Target = new_target; 257 } else { 258 MissionTarget = new_target; 259 } 260 } 261 262 263 /*********************************************************************************************** 264 * TeamClass::AI -- Process team logic. * 265 * * 266 * General purpose team logic is handled by this routine. It should be called once per * 267 * active team per game tick. This routine handles recruitment and assigning orders to * 268 * member units. * 269 * * 270 * INPUT: none * 271 * * 272 * OUTPUT: none * 273 * * 274 * WARNINGS: none * 275 * * 276 * HISTORY: * 277 * 12/29/1994 JLB : Created. * 278 * 01/06/1995 JLB : Choreographed gesture. * 279 *=============================================================================================*/ 280 void TeamClass::AI(void) 281 { 282 Validate(); 283 int desired = 0; 284 int old_under = IsUnderStrength; 285 int old_full = IsFullStrength; 286 287 /* 288 ** If the team has been suspended then we need to check if its time for 289 ** us to reactivate the team. If not, no team logic will be processed 290 ** for this team. 291 */ 292 if (Suspended) { 293 if (!SuspendTimer.Expired()) { 294 return; 295 } 296 Suspended = false; 297 } 298 299 /* 300 ** If this team senses that its composition has been altered, then it should 301 ** recalculate the under strength and full strength flags. 302 */ 303 if (IsAltered) { 304 305 for (int index = 0; index < Class->ClassCount; index++) { 306 desired += Class->DesiredNum[index]; 307 } 308 309 if (Total) { 310 IsFullStrength = (Total == desired); 311 312 /* 313 ** Human controlled teams are always considered full strength. This ensures 314 ** that no new team members will be recruited and the team won't go into 315 ** regroup logic. 316 */ 317 if (House->IsHuman) { 318 IsUnderStrength = false; 319 } else { 320 321 /* 322 ** Reinforcable teams will revert (or snap out of) the under strength 323 ** mode when the members transition the magic 1/3 strength threshhold. 324 */ 325 if (Class->IsReinforcable) { 326 IsUnderStrength = (Total <= desired / 3); 327 } else { 328 329 /* 330 ** Teams that are not flagged as reinforcable are never considered under 331 ** strength if the team has already started its main mission. This 332 ** ensures that once the team has started, it won't dally to pick up 333 ** new members. 334 */ 335 IsUnderStrength = !IsHasBeen; 336 } 337 } 338 IsAltered = false; 339 } else { 340 IsUnderStrength = true; 341 IsFullStrength = false; 342 Center = 0; 343 344 /* 345 ** A team that exists on the player's side is automatically destroyed 346 ** when there are no team members left. This team was created as a 347 ** result of reinforcement logic and no longer needs to exist when there 348 ** are no more team members. 349 */ 350 if (House->IsHuman || IsHasBeen) { 351 Delete_This(); 352 return; 353 } 354 } 355 356 /* 357 ** If the team has gone from under strength to no longer under 358 ** strength than the team needs to reform. 359 */ 360 if (old_under != IsUnderStrength) { 361 IsReforming = true; 362 } 363 } 364 365 /* 366 ** If the team is under strength, then flag it to regroup. 367 */ 368 if (IsMoving && IsUnderStrength) { 369 IsMoving = false; 370 CurrentMission = -1; 371 if (Total) { 372 Calc_Center(Center, ObjectiveCenter); 373 374 /* 375 ** When a team is badly damaged and needs to regroup it should 376 ** pick a friendly building to go and regroup at. Its first preference 377 ** should be somewhere near repair factory. If it cannot find a repair 378 ** factory then it should pick another structure that is friendly to 379 ** its side. 380 */ 381 CELL dest = Center; 382 int max = 0x7FFFFFFF; 383 384 for (int index = 0; index < Buildings.Count(); index++) { 385 BuildingClass * b = Buildings.Ptr(index); 386 387 if (b && !b->IsInLimbo && b->House == House && b->Class->Primary == WEAPON_NONE) { 388 CELL cell = Coord_Cell(b->Center_Coord()); 389 int dist = Map.Cell_Distance(cell, Center) * (Map.Cell_Threat(cell, House->Class->House) + 1); 390 391 if (*b == STRUCT_REPAIR) { 392 dist >>= 1; 393 } 394 if (dist < max) { 395 cell = Member->Safety_Point(Center, cell, 2, 4); 396 if (cell != -1) { 397 max = dist; 398 dest = cell; 399 } 400 } 401 } 402 } 403 404 // Should calculate a regroup location. 405 Target = ::As_Target(dest); 406 Coordinate_Move(); 407 return; 408 } else { 409 Center = 0; 410 } 411 } 412 413 /* 414 ** Flag this team into action when it gets to full strength. Human owned teams are only 415 ** used for reinforcement purposes -- always consider them at full strength. 416 */ 417 if (!IsMoving && (IsFullStrength || IsForcedActive)) { 418 IsMoving = true; 419 IsHasBeen = true; 420 IsUnderStrength = false; 421 422 /* 423 ** Infantry can do a gesture when they start their mission. Pick 424 ** a gesture at random. 425 */ 426 FootClass * techno = Member; 427 DoType doaction = (Random_Pick(1, 2) == 1) ? DO_GESTURE1 : DO_GESTURE2; 428 while (techno) { 429 if (!techno->IsInLimbo && techno->What_Am_I() == RTTI_INFANTRY) { 430 ((InfantryClass *)techno)->Do_Action(doaction); 431 } 432 433 if (IsReforming || IsForcedActive) { 434 techno->IsInitiated = true; 435 } 436 437 techno = techno->Member; 438 } 439 CurrentMission = -1; 440 IsNextMission = true; 441 IsForcedActive = false; 442 } 443 444 /* 445 ** If the team is moving or if there is no center position for 446 ** the team, then the center position must be recalculated. 447 */ 448 if (IsReforming || IsMoving || Center == 0) { 449 Calc_Center(Center, ObjectiveCenter); 450 } 451 452 /* 453 ** Try to recruit members if there is room to do so for this team. 454 ** Only try to recruit members for a non player controlled team. 455 */ 456 if (!IsMoving || (!IsFullStrength && Class->IsReinforcable) && !House->IsHuman) { 457 for (int index = 0; index < Class->ClassCount; index++) { 458 if (Quantity[index] < Class->DesiredNum[index]) { 459 Recruit(index); 460 } 461 } 462 } 463 464 /* 465 ** If there are no members of the team and the team has reached 466 ** full strength at one time, then delete the team. 467 */ 468 if (!Member && IsHasBeen) { 469 Delete_This(); 470 return; 471 } 472 473 /* 474 ** If the mission should be advanced to the next entry, then do so at 475 ** this time. Various events may cause the mission to advance, but it 476 ** all boils down to the following change-mission code. 477 */ 478 if (IsMoving && !IsReforming && IsNextMission) { 479 IsNextMission = false; 480 CurrentMission++; 481 if (CurrentMission < Class->MissionCount) { 482 TeamMissionStruct const * mission = &Class->MissionList[CurrentMission]; 483 484 TimeOut = mission->Argument * (TICKS_PER_MINUTE/10); 485 Target = TARGET_NONE; 486 switch (mission->Mission) { 487 case TMISSION_MOVECELL: 488 Assign_Mission_Target(::As_Target((CELL)mission->Argument)); 489 break; 490 491 case TMISSION_MOVE: 492 case TMISSION_UNLOAD: 493 /* 494 ** Argument can be a waypoint index or a direct target. 495 */ 496 if (mission->Argument < WAYPT_COUNT) { 497 Assign_Mission_Target(::As_Target((CELL)Waypoint[mission->Argument])); 498 } else { 499 Assign_Mission_Target((TARGET)mission->Argument); 500 } 501 break; 502 503 case TMISSION_ATTACKTARCOM: 504 Assign_Mission_Target(mission->Argument); 505 break; 506 507 default: 508 Assign_Mission_Target(TARGET_NONE); 509 break; 510 } 511 } else { 512 Delete_This(); 513 return; 514 } 515 } 516 517 /* 518 ** Perform mission of the team. This depends on the mission list. 519 */ 520 if (Member && IsMoving && !IsReforming && !IsUnderStrength) { 521 /* 522 ** If the current Target has been dealt with but the mission target 523 ** has not, then the current target needs to be reset to the mission 524 ** target. 525 */ 526 if (!Target_Legal(Target)) { 527 Target = MissionTarget; 528 } 529 530 /* 531 ** If the current mission is one that times out, then check for 532 ** this case. If it has timed out then advance to the next 533 ** mission in the list or disband the team. 534 */ 535 TeamMissionStruct const * mission = &Class->MissionList[CurrentMission]; 536 switch (mission->Mission) { 537 case TMISSION_ATTACKBASE: 538 if (!Target_Legal(MissionTarget)) { 539 Assign_Mission_Target(Member->Greatest_Threat(THREAT_BUILDINGS)); 540 if (!Target_Legal(MissionTarget)) IsNextMission = true; 541 } 542 Coordinate_Attack(); 543 break; 544 545 case TMISSION_ATTACKUNITS: 546 if (!Target_Legal(MissionTarget)) { 547 Assign_Mission_Target(Member->Greatest_Threat(THREAT_VEHICLES|THREAT_INFANTRY)); 548 if (!Target_Legal(MissionTarget)) IsNextMission = true; 549 } 550 Coordinate_Attack(); 551 break; 552 553 case TMISSION_ATTACKCIVILIANS: 554 if (!Target_Legal(MissionTarget)) { 555 Assign_Mission_Target(Member->Greatest_Threat(THREAT_CIVILIANS)); 556 if (!Target_Legal(MissionTarget)) IsNextMission = true; 557 } 558 Coordinate_Attack(); 559 break; 560 561 case TMISSION_ATTACKTARCOM: 562 case TMISSION_RAMPAGE: 563 if (!Target_Legal(MissionTarget)) { 564 Assign_Mission_Target(Member->Greatest_Threat(THREAT_NORMAL)); 565 if (!Target_Legal(MissionTarget)) IsNextMission = true; 566 } 567 Coordinate_Attack(); 568 break; 569 570 case TMISSION_DEFENDBASE: 571 Coordinate_Move(); 572 break; 573 574 // case TMISSION_HARVEST: 575 // Coordinate_Move(); 576 // break; 577 578 case TMISSION_UNLOAD: 579 Coordinate_Unload(); 580 break; 581 582 case TMISSION_MOVE: 583 Coordinate_Move(); 584 break; 585 586 case TMISSION_RETREAT: 587 Coordinate_Move(); 588 break; 589 590 case TMISSION_GUARD: 591 Coordinate_Regroup(); 592 break; 593 594 case TMISSION_LOOP: 595 CurrentMission = mission->Argument-1; 596 IsNextMission = true; 597 break; 598 } 599 600 /* 601 ** Check for mission time out condition. If the mission does in fact time out, then 602 ** flag it so that the team mission list will advance. 603 */ 604 switch (mission->Mission) { 605 case TMISSION_ATTACKBASE: 606 case TMISSION_ATTACKUNITS: 607 case TMISSION_ATTACKCIVILIANS: 608 case TMISSION_RAMPAGE: 609 case TMISSION_DEFENDBASE: 610 case TMISSION_UNLOAD: 611 case TMISSION_RETREAT: 612 case TMISSION_GUARD: 613 if (TimeOut.Expired()) { 614 IsNextMission = true; 615 } 616 break; 617 } 618 619 } else { 620 if (IsMoving) { 621 IsReforming = !Coordinate_Regroup(); 622 } else { 623 Coordinate_Move(); 624 } 625 } 626 } 627 628 629 /*********************************************************************************************** 630 * TeamClass::Add -- Adds specified object to team. * 631 * * 632 * Use this routine to add the specified object to the team. The object is checked to make * 633 * sure that it can be assigned to the team. If it can't, then the object will be left * 634 * alone and false will be returned. * 635 * * 636 * INPUT: obj -- Pointer to the object that is to be assigned to this team. * 637 * * 638 * typeindex-- Optional value that specifies the index in the team type class array * 639 * that this object belongs. * 640 * * 641 * OUTPUT: bool; Was the unit added to the team? * 642 * * 643 * WARNINGS: none * 644 * * 645 * HISTORY: * 646 * 12/29/1994 JLB : Created. * 647 * 01/02/1995 JLB : Initiation flag setup. * 648 * 08/06/1995 JLB : Allows member stealing from lesser priority teams. * 649 *=============================================================================================*/ 650 bool TeamClass::Add(FootClass * obj, int typeindex) 651 { 652 Validate(); 653 /* 654 ** If this team doesn't accept new members, then don't accept this one either. 655 */ 656 // if (!Class->IsReinforcable && IsMoving) { 657 // return(false); 658 // } 659 660 if (!obj || !obj->Strength || (obj->IsInLimbo && !ScenarioInit) || obj->In_Radio_Contact() || obj->House != House) { 661 return(false); 662 } 663 664 TeamClass * team = obj->Team; 665 666 /* 667 ** Trying to add the team member to itself is an error condition. Just return 668 ** with success, since the end result is the same. 669 */ 670 if (team == this) { 671 return(true); 672 } 673 674 /* 675 ** If the object is doing some mission that precludes it from joining 676 ** a team then don't add it. 677 */ 678 if (obj->Mission == MISSION_STICKY || obj->Mission == MISSION_SLEEP || obj->Mission == MISSION_GUARD_AREA || obj->Mission == MISSION_HUNT || obj->Mission == MISSION_HARVEST) { 679 return(false); 680 } 681 682 /* 683 ** If this object is part of another team, then check to make sure that it 684 ** is permitted to leave the other team in order to join this one. If not, 685 ** then no further processing is allowed -- bail. 686 */ 687 if (team && 688 (/*team->Total >= Total || team->IsMoving ||*/ 689 team->Class->RecruitPriority >= Class->RecruitPriority)) { 690 return(false); 691 } 692 693 /* 694 ** If the proper team index was not provided, then find it in the type type class. 695 ** On the chance that a match could not be found, then it is illegal to add this 696 ** object to this team -- return with failure flag. 697 */ 698 if (typeindex == -1) { 699 for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) { 700 if (Class->Class[typeindex] == &obj->Class_Of()) { 701 break; 702 } 703 } 704 } 705 706 /* 707 ** If the team is already full of this type, then adding the object is not allowed. 708 ** Return with a failure flag in this case. 709 */ 710 if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { 711 return(false); 712 } 713 714 /* 715 ** All is ok to add the object to the team, but if the object is already part of 716 ** another team, then it must be removed from that team first. 717 */ 718 if (team) { 719 team->Remove(obj); 720 } 721 722 /* 723 ** Actually add the object to the team. 724 */ 725 Quantity[typeindex]++; 726 obj->IsInitiated = (Member == NULL); 727 obj->Member = Member; 728 Member = obj; 729 obj->Team = this; 730 Total++; 731 Risk += obj->Risk(); 732 if (!Center) { 733 Calc_Center(Center, ObjectiveCenter); 734 } 735 736 /* 737 ** Return with success, since the object was added to the team. 738 */ 739 IsAltered = true; 740 return(true); 741 } 742 743 744 /*********************************************************************************************** 745 * TeamClass::Remove -- Removes the specified object from the team. * 746 * * 747 * Use this routine to remove an object from a team. Objects removed from the team are * 748 * then available to be recruited by other teams, or even by the same team at a later time. * 749 * * 750 * INPUT: obj -- Pointer to the object that is to be removed from this team. * 751 * * 752 * typeindex-- Optional index of where this object type is specified in the type * 753 * type class. This parameter can be omitted. It only serves to make * 754 * the removal process faster. * 755 * * 756 * OUTPUT: bool; Was the object removed from this team? * 757 * * 758 * WARNINGS: none * 759 * * 760 * HISTORY: * 761 * 12/29/1994 JLB : Created. * 762 * 01/02/1995 JLB : Initiation tracking and team captain selection. * 763 *=============================================================================================*/ 764 bool TeamClass::Remove(FootClass * obj, int typeindex) 765 { 766 Validate(); 767 /* 768 ** Make sure that the object is in fact a member of this team. If not, then it can't 769 ** be removed. Return success because the end result is the same. 770 */ 771 if (obj->Team != this) { 772 return(true); 773 } 774 775 /* 776 ** If the proper team index was not provided, then find it in the type type class. The 777 ** team type class will not be set if the appropriate type could not be found 778 ** for this object. This indicates that the object was illegally added. Continue to 779 ** process however, since removing this object from the team is a good idea. 780 */ 781 if (typeindex == -1) { 782 for (typeindex = 0; typeindex < Class->ClassCount; typeindex++) { 783 if (Class->Class[typeindex] == &obj->Class_Of()) { 784 break; 785 } 786 } 787 } 788 789 /* 790 ** Decrement the counter for the team class. There is now one less of this object type. 791 */ 792 if ((unsigned)typeindex < Class->ClassCount) { 793 Quantity[typeindex]--; 794 } 795 796 /* 797 ** Actually remove the object from the team. Scan through the team members 798 ** looking for the one that matches the one specified. If it is found, it 799 ** is unlinked from the member chain. During this scan, a check is made to 800 ** ensure that at least one remaining member is still initiated. If not, then 801 ** a new team captain must be chosen. 802 */ 803 bool initiated = false; 804 FootClass * prev = 0; 805 FootClass * curr = Member; 806 bool found = false; 807 while (curr && (!found || !initiated)) { 808 if (curr == obj) { 809 if (prev) { 810 prev->Member = curr->Member; 811 } else { 812 Member = curr->Member; 813 } 814 FootClass * temp = curr->Member; 815 curr->Member = 0; 816 curr->Team = 0; 817 curr = temp; 818 Total--; 819 found = true; 820 Risk -= obj->Risk(); 821 continue; 822 } 823 824 /* 825 ** If this (remaining) member is initiated, then keep a record of this. 826 */ 827 initiated |= curr->IsInitiated; 828 829 prev = curr; 830 curr = curr->Member; 831 } 832 833 /* 834 ** If, after removing the team member, there are no initiated members left 835 ** in the team, then just make the first remaining member of the team the 836 ** team captain. Mark the center location of the team as invalid so that 837 ** it will be centered around the captain. 838 */ 839 if (!initiated && Member) { 840 Member->IsInitiated = true; 841 Center = 0; 842 } 843 844 /* 845 ** Must record that the team composition has changed. At the next opportunity, 846 ** the team members will be counted and appropriate AI adjustments made. 847 */ 848 IsAltered = true; 849 return(true); 850 } 851 852 853 /*********************************************************************************************** 854 * TeamClass::Recruit -- Attempts to recruit members to the team for the given index ID. * 855 * * 856 * This routine will take the given index ID and scan for available objects of that type * 857 * to recruit to the team. Recruiting will continue until that object type has either * 858 * been exhausted or if the team's requirment for that type has been filled. * 859 * * 860 * INPUT: typeindex -- The index for the object type to recruit. The index is used to * 861 * look into the type type's array of object types that make up this * 862 * team. * 863 * * 864 * OUTPUT: Returns with the number of objects added to this team. * 865 * * 866 * WARNINGS: none * 867 * * 868 * HISTORY: * 869 * 12/29/1994 JLB : Created. * 870 * 04/10/1995 JLB : Scans for units too. * 871 *=============================================================================================*/ 872 int TeamClass::Recruit(int typeindex) 873 { 874 Validate(); 875 int added = 0; // Total number added to team. 876 877 /* 878 ** Quick check to see if recruiting is really allowed for this index or not. 879 */ 880 if (Class->DesiredNum[typeindex] > Quantity[typeindex]) { 881 882 /* 883 ** For infantry objects, sweep through the infantry in the game looking for 884 ** ones owned by the house that owns the team. When found, try to add. 885 */ 886 if (Class->Class[typeindex]->What_Am_I() == RTTI_INFANTRYTYPE) { 887 888 for (int index = 0; index < Infantry.Count(); index++) { 889 InfantryClass * infantry = Infantry.Ptr(index); 890 891 if (infantry->House == House && infantry->Class == Class->Class[typeindex]) { 892 if (Add(infantry, typeindex)) { 893 added++; 894 } 895 } 896 897 /* 898 ** If there is sufficient quantity of this type of object recruited to the 899 ** team, then abort further scanning for members. 900 */ 901 if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { 902 break; 903 } 904 } 905 } 906 907 if (Class->Class[typeindex]->What_Am_I() == RTTI_UNITTYPE) { 908 909 for (int index = 0; index < Units.Count(); index++) { 910 UnitClass * unit = Units.Ptr(index); 911 912 if (unit->House == House && unit->Class == Class->Class[typeindex]) { 913 if (Add(unit, typeindex)) { 914 added++; 915 916 /* 917 ** If a transport is added to the team, the occupants 918 ** are added by default. 919 */ 920 FootClass * f = unit->Attached_Object(); 921 while (f) { 922 Add(f); 923 f = (FootClass *)f->Next; 924 } 925 } 926 } 927 928 /* 929 ** If there is sufficient quantity of this type of object recruited to the 930 ** team, then abort further scanning for members. 931 */ 932 if (Quantity[typeindex] >= Class->DesiredNum[typeindex]) { 933 break; 934 } 935 } 936 } 937 } 938 939 return(added); 940 } 941 942 943 /*********************************************************************************************** 944 * TeamClass::Detach -- Removes specified target from team tracking. * 945 * * 946 * When a target object is about to be removed from the game (e.g., it was killed), then * 947 * any team that is looking at that target must abort from that target. * 948 * * 949 * INPUT: target -- The target object that is going to be removed from the game. * 950 * * 951 * all -- Is the target going away for good as opposed to just cloaking/hiding? * 952 * * 953 * OUTPUT: none * 954 * * 955 * WARNINGS: none * 956 * * 957 * HISTORY: * 958 * 12/29/1994 JLB : Created. * 959 *=============================================================================================*/ 960 void TeamClass::Detach(TARGET target, bool ) 961 { 962 Validate(); 963 964 /* 965 ** If the target to detatch matches the target of this team, then remove 966 ** the target from this team's Tar/Nav com and let the chips fall 967 ** where they may. 968 */ 969 if (Target == target) { 970 Target = TARGET_NONE; 971 } 972 if (MissionTarget == target) { 973 MissionTarget = TARGET_NONE; 974 } 975 976 } 977 978 979 /*********************************************************************************************** 980 * TeamClass::As_Target -- Converts this team object into a target number. * 981 * * 982 * This routine is used by the save/load code to produce a persistant identifier for this * 983 * team object. * 984 * * 985 * INPUT: none * 986 * * 987 * OUTPUT: Returns the team represented as a target number. * 988 * * 989 * WARNINGS: none * 990 * * 991 * HISTORY: * 992 * 06/14/1995 JLB : Created. * 993 *=============================================================================================*/ 994 TARGET TeamClass::As_Target(void) const 995 { 996 Validate(); 997 return(Build_Target(KIND_TEAM, Teams.ID(this))); 998 } 999 1000 1001 /*********************************************************************************************** 1002 * TeamClass::Calc_Center -- Determines average location of team members. * 1003 * * 1004 * Use this routine to calculate the "center" location of the team. This is the average * 1005 * position of all members of the team. Using this center value it is possible to tell * 1006 * if a team member is too far away and where to head to in order to group up. * 1007 * * 1008 * INPUT: none * 1009 * * 1010 * OUTPUT: Returns with the cell number of the team's center point. If the team contains * 1011 * no members, then the return value will be zero. * 1012 * * 1013 * WARNINGS: none * 1014 * * 1015 * HISTORY: * 1016 * 12/29/1994 JLB : Created. * 1017 *=============================================================================================*/ 1018 void TeamClass::Calc_Center(CELL ¢er, CELL &obj_center) const 1019 { 1020 Validate(); 1021 long x = 0; 1022 long y = 0; 1023 int dist = 0x7FFFFFFF; 1024 int quantity = 0; 1025 FootClass * unit; 1026 1027 obj_center = 0; 1028 center = 0; 1029 1030 unit = Member; 1031 while (unit) { 1032 if (unit->IsInitiated && !unit->IsInLimbo) { 1033 CELL c = Coord_Cell(unit->Center_Coord()); 1034 if (unit->Distance(Target) < dist) { 1035 dist = unit->Distance(Target); 1036 obj_center = c; 1037 } 1038 x += Cell_X(c); 1039 y += Cell_Y(c); 1040 quantity++; 1041 } 1042 unit = unit->Member; 1043 } 1044 if (quantity) { 1045 x /= quantity; 1046 y /= quantity; 1047 CELL cell = XY_Cell((int)x, (int)y); 1048 center = cell; 1049 } 1050 } 1051 1052 1053 /*********************************************************************************************** 1054 * TeamClass::Took_Damage -- Informs the team when the team member takes damage. * 1055 * * 1056 * This routine is used when a team member takes damage. Usually the team will react in * 1057 * some fashion to the attack. This reaction can range from running away to assigning this * 1058 * new target as the team's target. * 1059 * * 1060 * INPUT: obj -- The team member that was damaged. * 1061 * * 1062 * result -- The severity of the damage taken. * 1063 * * 1064 * source -- The purpetrator of the damage. * 1065 * * 1066 * OUTPUT: none * 1067 * * 1068 * WARNINGS: none * 1069 * * 1070 * HISTORY: * 1071 * 12/29/1994 JLB : Created. * 1072 *=============================================================================================*/ 1073 void TeamClass::Took_Damage(FootClass * , ResultType result, TechnoClass * source) 1074 { 1075 Validate(); 1076 if ((result != RESULT_NONE) && (!Class->IsSuicide)) { 1077 if (!IsMoving) { 1078 // Should run to a better hiding place or disband into a group of hunting units. 1079 } else { 1080 1081 if (source && !Is_A_Member(source) && Member && Member->What_Am_I() != RTTI_AIRCRAFT) { 1082 if (Target != source->As_Target()) { 1083 1084 /* 1085 ** Don't change target if the team's target is one that can fire as well. There is 1086 ** no point in endlessly shuffling between targets that have firepower. 1087 */ 1088 if (Target_Legal(Target)) { 1089 TechnoClass * techno = As_Techno(Target); 1090 1091 if (techno && ((TechnoTypeClass const &)techno->Class_Of()).Primary != WEAPON_NONE) { 1092 if (techno->In_Range(Cell_Coord(Center), 0)) { 1093 return; 1094 } 1095 } 1096 } 1097 Target = source->As_Target(); 1098 } 1099 } 1100 } 1101 } 1102 } 1103 1104 1105 /*********************************************************************************************** 1106 * TeamClass::Coordinate_Attack -- Handles coordinating a team attack. * 1107 * * 1108 * This function is called when the team knows what it should attack. This routine will * 1109 * give the necessary orders to the members of the team. * 1110 * * 1111 * INPUT: none * 1112 * * 1113 * OUTPUT: none * 1114 * * 1115 * WARNINGS: none * 1116 * * 1117 * HISTORY: * 1118 * 04/06/1995 JLB : Created. * 1119 *=============================================================================================*/ 1120 void TeamClass::Coordinate_Attack(void) 1121 { 1122 Validate(); 1123 if (!Target_Legal(Target)) { 1124 Target = MissionTarget; 1125 } 1126 1127 if (!Target_Legal(Target)) { 1128 IsNextMission = true; 1129 1130 } else { 1131 1132 FootClass * unit = Member; 1133 while (unit) { 1134 1135 Coordinate_Conscript(unit); 1136 1137 if (unit->IsInitiated && !unit->IsInLimbo) { 1138 1139 if (unit->Mission != MISSION_ATTACK && unit->Mission != MISSION_ENTER && unit->Mission != MISSION_CAPTURE) { 1140 unit->Assign_Mission(MISSION_ATTACK); 1141 unit->Assign_Target(TARGET_NONE); 1142 unit->Assign_Destination(TARGET_NONE); 1143 } 1144 1145 if (unit->TarCom != Target) { 1146 unit->Assign_Target(Target); 1147 } 1148 } 1149 1150 unit = unit->Member; 1151 } 1152 } 1153 } 1154 1155 1156 /*********************************************************************************************** 1157 * TeamClass::Coordinate_Regroup -- Handles team idling (regrouping). * 1158 * * 1159 * This routine is called when the team must delay at its current location. Team members * 1160 * are grouped together by this function. It is called when the team needs to sit and * 1161 * wait. * 1162 * * 1163 * INPUT: none * 1164 * * 1165 * OUTPUT: none * 1166 * * 1167 * WARNINGS: none * 1168 * * 1169 * HISTORY: * 1170 * 04/06/1995 JLB : Created. * 1171 *=============================================================================================*/ 1172 bool TeamClass::Coordinate_Regroup(void) 1173 { 1174 Validate(); 1175 FootClass * unit = Member; 1176 bool retval = true; 1177 1178 /* 1179 ** Regroup default logic. 1180 */ 1181 while (unit) { 1182 1183 Coordinate_Conscript(unit); 1184 1185 if (unit->IsInitiated && !unit->IsInLimbo) { 1186 1187 if (unit->Distance(Center) > STRAY_DISTANCE && (unit->Mission != MISSION_GUARD_AREA || !Target_Legal(unit->TarCom))) { 1188 if (unit->Mission != MISSION_MOVE || !Target_Legal(unit->NavCom) || ::Distance(As_Cell(unit->NavCom), Center) > STRAY_DISTANCE) { 1189 unit->Assign_Mission(MISSION_MOVE); 1190 unit->Assign_Destination(::As_Target(Center)); 1191 } 1192 retval = false; 1193 } else { 1194 1195 /* 1196 ** This unit has gotten close enough to the team center so that it is 1197 ** now considered intiated. An initiated unit is considered when calculating 1198 ** the center of the team. 1199 */ 1200 unit->IsInitiated = true; 1201 1202 /* 1203 ** The team is regrouping, so just sit here and wait. 1204 */ 1205 if (unit->Mission != MISSION_GUARD_AREA) { 1206 unit->Assign_Mission(MISSION_GUARD); 1207 unit->Assign_Destination(TARGET_NONE); 1208 } 1209 } 1210 1211 } 1212 1213 unit = unit->Member; 1214 } 1215 return(retval); 1216 } 1217 1218 1219 /*********************************************************************************************** 1220 * TeamClass::Coordinate_Move -- Handles team movement coordination. * 1221 * * 1222 * This routine is called when the team must move to a new location. Movement and grouping * 1223 * commands associated with this task are initiated here. * 1224 * * 1225 * INPUT: none * 1226 * * 1227 * OUTPUT: none * 1228 * * 1229 * WARNINGS: none * 1230 * * 1231 * HISTORY: * 1232 * 04/06/1995 JLB : Created. * 1233 *=============================================================================================*/ 1234 void TeamClass::Coordinate_Move(void) 1235 { 1236 Validate(); 1237 FootClass * unit = Member; 1238 bool finished = true; 1239 1240 if (!Target_Legal(Target)) { 1241 Target = MissionTarget; 1242 } 1243 1244 if (Target_Legal(Target)) { 1245 1246 if (!Lagging_Units()) { 1247 1248 1249 while (unit) { 1250 1251 Coordinate_Conscript(unit); 1252 1253 if (unit->IsInitiated && !unit->IsInLimbo) { 1254 1255 if (unit->What_Am_I() != RTTI_AIRCRAFT && unit->Distance(Center) > STRAY_DISTANCE) { 1256 IsLagging = true; 1257 finished = false; 1258 } else { 1259 1260 if ((unit->Distance(Target)/ICON_LEPTON_W) > STRAY_DISTANCE || 1261 (unit->What_Am_I() == RTTI_AIRCRAFT && 1262 ((AircraftClass *)unit)->Altitude > 0 && 1263 Class->MissionList[CurrentMission+1].Mission != TMISSION_MOVE)) { 1264 1265 if (unit->Mission != MISSION_MOVE) { 1266 unit->Assign_Mission(MISSION_MOVE); 1267 } 1268 if (unit->NavCom != Target) { 1269 TARGET target = Target; 1270 if (unit->What_Am_I() == RTTI_AIRCRAFT) { 1271 AircraftClass* aircraft = (AircraftClass *)unit; 1272 if (!aircraft->Class->IsFixedWing && !aircraft->Is_LZ_Clear(target)) { 1273 target = aircraft->New_LZ(target, true); 1274 } 1275 } 1276 unit->Assign_Destination(target); 1277 } 1278 finished = false; 1279 } else { 1280 if (unit->Mission == MISSION_MOVE && !Target_Legal(unit->NavCom)) { 1281 unit->Enter_Idle_Mode(); 1282 } 1283 } 1284 } 1285 } 1286 1287 unit = unit->Member; 1288 } 1289 } else { 1290 finished = false; 1291 } 1292 } 1293 1294 /* 1295 ** If all the team members are close enough to the desired destination, then 1296 ** move to the next mission. 1297 */ 1298 if (finished && IsMoving) { 1299 IsNextMission = true; 1300 } 1301 } 1302 1303 1304 /*************************************************************************** 1305 * Lagging_Units -- processes units that cant keep up with the pack * 1306 * * 1307 * INPUT: none * 1308 * * 1309 * OUTPUT: none * 1310 * * 1311 * HISTORY: * 1312 * 08/01/1995 PWG : Created. * 1313 *=========================================================================*/ 1314 bool TeamClass::Lagging_Units(void) 1315 { 1316 Validate(); 1317 FootClass * unit = Member; 1318 bool lag = false; 1319 1320 /* 1321 ** If the IsLagging bit is not set, then obviously there are no lagging 1322 ** units. 1323 */ 1324 if (!IsLagging) return(false); 1325 1326 /* 1327 ** Scan through all of the units, searching for units who are having 1328 ** trouble keeping up with the pack. 1329 */ 1330 while (unit) { 1331 1332 if (!unit->IsInLimbo) { 1333 /* 1334 ** If we find a unit who has fallen to far away from the center of 1335 ** the pack, then we need to order that unit to catch up with the 1336 ** first unit. 1337 */ 1338 if (unit->Distance(ObjectiveCenter) > STRAY_DISTANCE) { 1339 if (unit->Mission != MISSION_MOVE || !Target_Legal(unit->NavCom) || ::Distance(As_Cell(unit->NavCom), ObjectiveCenter) > STRAY_DISTANCE) { 1340 unit->Assign_Mission(MISSION_MOVE); 1341 unit->Assign_Destination(::As_Target(ObjectiveCenter)); 1342 } 1343 lag = true; 1344 } else { 1345 /* 1346 ** We need to order all of the other units to hold there 1347 ** position until all lagging units catch up. 1348 */ 1349 unit->Assign_Mission(MISSION_GUARD); 1350 unit->Assign_Destination(TARGET_NONE); 1351 } 1352 } 1353 unit = unit->Member; 1354 } 1355 1356 /* 1357 ** Once we have handled the loop we know whether there are any lagging 1358 ** units or not. 1359 */ 1360 IsLagging = lag; 1361 return(lag); 1362 } 1363 1364 1365 /*********************************************************************************************** 1366 * TeamClass::Coordinate_Unload -- Tells the team to unload passengers now. * 1367 * * 1368 * This routine tells all transport vehicles to unload passengers now. * 1369 * * 1370 * INPUT: none * 1371 * * 1372 * OUTPUT: none * 1373 * * 1374 * WARNINGS: none * 1375 * * 1376 * HISTORY: * 1377 * 06/14/1995 JLB : Created. * 1378 *=============================================================================================*/ 1379 void TeamClass::Coordinate_Unload(void) 1380 { 1381 Validate(); 1382 FootClass * unit = Member; 1383 bool finished = true; 1384 1385 while (unit) { 1386 1387 Coordinate_Conscript(unit); 1388 1389 if (unit->IsInitiated && !unit->IsInLimbo) { 1390 if (unit->Is_Something_Attached()) { 1391 1392 /* 1393 ** Loaner transports will break off of the team at this time. The normal 1394 ** unload logic for the transport will proceed normally. The rest of the team 1395 ** members will be in a dormant state until they are unloaded. 1396 */ 1397 if (unit->IsALoaner) { 1398 Remove(unit); 1399 unit->Commence(); 1400 unit->Assign_Mission(MISSION_UNLOAD); 1401 unit->Assign_Destination(Target); 1402 } else { 1403 if (unit->Mission != MISSION_UNLOAD) { 1404 unit->Assign_Mission(MISSION_UNLOAD); 1405 unit->Assign_Destination(Target); 1406 } 1407 } 1408 finished = false; 1409 } 1410 } 1411 1412 unit = unit->Member; 1413 } 1414 1415 if (finished) { 1416 IsNextMission = true; 1417 } 1418 } 1419 1420 1421 /*********************************************************************************************** 1422 * TeamClass::Coordinate_Conscript -- Gives orders to new recruit. * 1423 * * 1424 * This routine will give the movement orders to the conscript so that it will group * 1425 * with the other members of the team. * 1426 * * 1427 * INPUT: unit -- Pointer to the conscript unit. * 1428 * * 1429 * OUTPUT: none * 1430 * * 1431 * WARNINGS: none * 1432 * * 1433 * HISTORY: * 1434 * 04/06/1995 JLB : Created. * 1435 *=============================================================================================*/ 1436 void TeamClass::Coordinate_Conscript(FootClass * unit) 1437 { 1438 Validate(); 1439 if (unit && !unit->IsInitiated && !unit->IsInLimbo) { 1440 if (unit->Distance(Center) > STRAY_DISTANCE) { 1441 if (!Target_Legal(unit->NavCom)) { 1442 unit->Assign_Mission(MISSION_MOVE); 1443 unit->Assign_Target(TARGET_NONE); 1444 unit->Assign_Destination(::As_Target(Center)); 1445 } 1446 } else { 1447 1448 /* 1449 ** This unit has gotten close enough to the team center so that it is 1450 ** now considered intiated. An initiated unit is considered when calculating 1451 ** the center of the team. 1452 */ 1453 unit->IsInitiated = true; 1454 } 1455 } 1456 } 1457 1458 1459 /*************************************************************************** 1460 * TeamClass::Is_A_Member -- Tests if a unit is a member of a team * 1461 * * 1462 * INPUT: none * 1463 * * 1464 * OUTPUT: none * 1465 * * 1466 * WARNINGS: * 1467 * * 1468 * HISTORY: * 1469 * 05/16/1995 PWG : Created. * 1470 *=========================================================================*/ 1471 bool TeamClass::Is_A_Member(void const * who) const 1472 { 1473 Validate(); 1474 FootClass * unit = Member; 1475 while (unit) { 1476 if (unit == who) { 1477 return(true); 1478 } 1479 unit = unit->Member; 1480 } 1481 return(false); 1482 } 1483 1484 1485 /*************************************************************************** 1486 * TeamClass::Suspend_Teams -- Suspends activity for low priority teams * 1487 * * 1488 * INPUT: int priority - determines what is considered low priority. * 1489 * * 1490 * OUTPUT: * 1491 * * 1492 * WARNINGS: * 1493 * * 1494 * HISTORY: * 1495 * 06/19/1995 PWG : Created. * 1496 *=========================================================================*/ 1497 void TeamClass::Suspend_Teams(int priority) 1498 { 1499 for (int index = 0; index < Teams.Count(); index++) { 1500 TeamClass *team = Teams.Ptr(index); 1501 1502 /* 1503 ** If a team is below the "survival priority level", then it gets 1504 ** destroyed. The team members are then free to be reassigned. 1505 */ 1506 if (team && team->Class->RecruitPriority < priority) { 1507 FootClass * unit = team->Member; 1508 while (team->Member) { 1509 team->Remove(team->Member); 1510 } 1511 team->IsAltered = true; 1512 team->SuspendTimer = TICKS_PER_MINUTE*2; 1513 team->Suspended = true; 1514 } 1515 } 1516 }