Browse Source

August 6th Patch Update

Accumulated DLL source code changes since June 22nd patch
PG-SteveT 5 years ago
parent
commit
ae72fce5dd
76 changed files with 1071 additions and 210 deletions
  1. 1 1
      CnCRemastered.sln
  2. 2 2
      REDALERT/ADATA.CPP
  3. 14 5
      REDALERT/AIRCRAFT.CPP
  4. 7 3
      REDALERT/ANIM.CPP
  5. 21 6
      REDALERT/BUILDING.CPP
  6. 11 2
      REDALERT/CELL.CPP
  7. 9 0
      REDALERT/DEFINES.H
  8. 2 2
      REDALERT/DISPLAY.CPP
  9. 79 5
      REDALERT/DLLInterface.cpp
  10. 2 1
      REDALERT/DLLInterface.h
  11. 7 1
      REDALERT/EVENT.CPP
  12. 13 1
      REDALERT/FOOT.CPP
  13. 86 12
      REDALERT/HOUSE.CPP
  14. 12 2
      REDALERT/INFANTRY.CPP
  15. 3 0
      REDALERT/INICODE.CPP
  16. 10 0
      REDALERT/INIT.CPP
  17. 4 2
      REDALERT/LOGIC.CPP
  18. 15 5
      REDALERT/MAP.CPP
  19. 1 1
      REDALERT/MAP.H
  20. 1 1
      REDALERT/MiscAsm.cpp
  21. 8 0
      REDALERT/OBJECT.CPP
  22. 1 0
      REDALERT/OBJECT.H
  23. 2 2
      REDALERT/RADAR.CPP
  24. 1 1
      REDALERT/RedAlert.vcxproj.filters
  25. 4 1
      REDALERT/SCENARIO.CPP
  26. 1 0
      REDALERT/SPECIAL.CPP
  27. 13 1
      REDALERT/SPECIAL.H
  28. 4 0
      REDALERT/STARTUP.CPP
  29. 2 2
      REDALERT/SUPER.CPP
  30. 1 1
      REDALERT/SUPER.H
  31. 65 24
      REDALERT/TECHNO.CPP
  32. 5 1
      REDALERT/TECHNO.H
  33. 40 9
      REDALERT/UNIT.CPP
  34. 27 1
      REDALERT/VESSEL.CPP
  35. 1 0
      REDALERT/VESSEL.H
  36. 1 1
      REDALERT/WIN32LIB/DrawMisc.cpp
  37. 2 2
      TIBERIANDAWN/ADATA.CPP
  38. 36 7
      TIBERIANDAWN/AIRCRAFT.CPP
  39. 8 2
      TIBERIANDAWN/AIRCRAFT.H
  40. 15 2
      TIBERIANDAWN/ANIM.CPP
  41. 1 1
      TIBERIANDAWN/BDATA.CPP
  42. 68 15
      TIBERIANDAWN/BUILDING.CPP
  43. 1 0
      TIBERIANDAWN/BUILDING.H
  44. 1 1
      TIBERIANDAWN/CDATA.CPP
  45. 30 12
      TIBERIANDAWN/CELL.CPP
  46. 1 1
      TIBERIANDAWN/CELL.H
  47. 9 0
      TIBERIANDAWN/DEFINES.H
  48. 11 5
      TIBERIANDAWN/DISPLAY.CPP
  49. 39 6
      TIBERIANDAWN/DLLInterface.cpp
  50. 2 1
      TIBERIANDAWN/DLLInterface.h
  51. 10 2
      TIBERIANDAWN/DRIVE.CPP
  52. 7 1
      TIBERIANDAWN/EVENT.CPP
  53. 16 2
      TIBERIANDAWN/FOOT.CPP
  54. 54 8
      TIBERIANDAWN/HOUSE.CPP
  55. 1 1
      TIBERIANDAWN/IDATA.CPP
  56. 12 4
      TIBERIANDAWN/INFANTRY.CPP
  57. 40 0
      TIBERIANDAWN/INI.CPP
  58. 4 3
      TIBERIANDAWN/LOGIC.CPP
  59. 29 10
      TIBERIANDAWN/MAP.CPP
  60. 1 1
      TIBERIANDAWN/MiscAsm.cpp
  61. 1 0
      TIBERIANDAWN/OBJECT.CPP
  62. 1 0
      TIBERIANDAWN/OBJECT.H
  63. 2 2
      TIBERIANDAWN/RADAR.CPP
  64. 37 4
      TIBERIANDAWN/REINF.CPP
  65. 18 0
      TIBERIANDAWN/SCENARIO.CPP
  66. 12 1
      TIBERIANDAWN/SPECIAL.H
  67. 8 1
      TIBERIANDAWN/TEAM.CPP
  68. 25 8
      TIBERIANDAWN/TECHNO.CPP
  69. 5 2
      TIBERIANDAWN/TECHNO.H
  70. 58 0
      TIBERIANDAWN/TURRET.CPP
  71. 1 0
      TIBERIANDAWN/TURRET.H
  72. 1 1
      TIBERIANDAWN/TiberianDawn.vcxproj
  73. 1 1
      TIBERIANDAWN/TiberianDawn.vcxproj.filters
  74. 25 5
      TIBERIANDAWN/UNIT.CPP
  75. 1 1
      TIBERIANDAWN/WIN32LIB/DrawMisc.cpp
  76. 1 1
      TIBERIANDAWN/WIN32LIB/FACINGFF.h

+ 1 - 1
CnCRemastered.sln

@@ -1,4 +1,4 @@
-
+
 Microsoft Visual Studio Solution File, Format Version 12.00
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 # Visual Studio 15
 VisualStudioVersion = 15.0.28307.1022
 VisualStudioVersion = 15.0.28307.1022

+ 2 - 2
REDALERT/ADATA.CPP

@@ -359,7 +359,7 @@ static AnimTypeClass const LZSmoke(
 	72,										// Loop start frame number.
 	72,										// Loop start frame number.
 	91,										// Ending frame of loop back.
 	91,										// Ending frame of loop back.
 	-1,										// Number of animation stages.
 	-1,										// Number of animation stages.
-	255,										// Number of times the animation loops.
+	127,										// Number of times the animation loops.
 	VOC_NONE,								// Sound effect to play.
 	VOC_NONE,								// Sound effect to play.
 	ANIM_NONE
 	ANIM_NONE
 );
 );
@@ -1127,7 +1127,7 @@ static AnimTypeClass const OilFieldBurn(
 	33,										// Loop start frame number.
 	33,										// Loop start frame number.
 	99,										// Ending frame of loop back.
 	99,										// Ending frame of loop back.
 	66,										// Number of animation stages.
 	66,										// Number of animation stages.
-	65535,									// Number of times the animation loops.
+	127,									// Number of times the animation loops.
 	VOC_NONE,								// Sound effect to play.
 	VOC_NONE,								// Sound effect to play.
 	ANIM_NONE
 	ANIM_NONE
 );
 );

+ 14 - 5
REDALERT/AIRCRAFT.CPP

@@ -1948,9 +1948,17 @@ void AircraftClass::Enter_Idle_Mode(bool )
 					/*
 					/*
 					**	Normal aircraft try to find a good landing spot to rest.
 					**	Normal aircraft try to find a good landing spot to rest.
 					*/
 					*/
-					BuildingClass * building = Find_Docking_Bay(Class->Building, false);
+					BuildingClass * building = NULL;
+					if (In_Radio_Contact() && Contact_With_Whom()->What_Am_I() == RTTI_BUILDING) {
+						building = (BuildingClass *)Contact_With_Whom();
+					} else {
+						building = Find_Docking_Bay(Class->Building, false);
+						if (Transmit_Message(RADIO_HELLO, building) != RADIO_ROGER) {
+							building = NULL;
+						}
+					}
 					Assign_Destination(TARGET_NONE);
 					Assign_Destination(TARGET_NONE);
-					if (building != NULL && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) {
+					if (building != NULL) {
 						if (Class->IsFixedWing) {
 						if (Class->IsFixedWing) {
 							Status = 0;	//BG - reset the mission status to avoid landing on the ground next to the airstrip
 							Status = 0;	//BG - reset the mission status to avoid landing on the ground next to the airstrip
 							if (IsLanding) {
 							if (IsLanding) {
@@ -2367,7 +2375,7 @@ ActionType AircraftClass::What_Action(CELL cell) const
 	ActionType action = FootClass::What_Action(cell);
 	ActionType action = FootClass::What_Action(cell);
 
 
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
-	if (action == ACTION_MOVE && !Map[cell].Is_Visible(PlayerPtr)) {
+	if ((action == ACTION_MOVE || action == ACTION_ATTACK) && !Map[cell].Is_Visible(PlayerPtr)) {
 		action = ACTION_NOMOVE;
 		action = ACTION_NOMOVE;
 	}
 	}
 
 
@@ -3068,7 +3076,7 @@ MoveType AircraftClass::Can_Enter_Cell(CELL cell, FacingType ) const
 	if (occupier == NULL ||
 	if (occupier == NULL ||
 		!occupier->Is_Techno() ||
 		!occupier->Is_Techno() ||
 		((TechnoClass *)occupier)->House->Is_Ally(House) ||
 		((TechnoClass *)occupier)->House->Is_Ally(House) ||
-		(((TechnoClass *)occupier)->Cloak != CLOAKED &&
+		(!((TechnoClass *)occupier)->Is_Cloaked(this) &&
 			(ScenarioInit == 0 && (occupier->What_Am_I() != RTTI_BUILDING || !((BuildingClass*)occupier)->Class->IsInvisible)) )
 			(ScenarioInit == 0 && (occupier->What_Am_I() != RTTI_BUILDING || !((BuildingClass*)occupier)->Class->IsInvisible)) )
 		) {
 		) {
 
 
@@ -3128,9 +3136,10 @@ TARGET AircraftClass::Good_Fire_Location(TARGET target) const
 			for (int face = 0; face < 255; face += 16) {
 			for (int face = 0; face < 255; face += 16) {
 				COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r);
 				COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r);
 				CELL newcell = Coord_Cell(newcoord);
 				CELL newcell = Coord_Cell(newcoord);
+				CELL actualcell = Coord_Cell(Coord_Sub(newcoord, XY_Coord(0, FLIGHT_LEVEL)));
 
 
 				//using function for IsVisible so we have different results for different players - JAS 2019/09/30
 				//using function for IsVisible so we have different results for different players - JAS 2019/09/30
-				if (Map.In_Radar(newcell) && (Session.Type != GAME_NORMAL || Map[newcell].Is_Visible(PlayerPtr)) && Cell_Seems_Ok(newcell, true)) {
+				if (Map.In_Radar(actualcell) && (Session.Type != GAME_NORMAL || Map[newcell].Is_Visible(PlayerPtr)) && Cell_Seems_Ok(newcell, true)) {
 					int dist;
 					int dist;
 					if (altcoord != 0) {
 					if (altcoord != 0) {
 						dist = ::Distance(newcoord, altcoord);
 						dist = ::Distance(newcoord, altcoord);

+ 7 - 3
REDALERT/ANIM.CPP

@@ -578,7 +578,7 @@ IsTheaterShape = false;
 
 
 	AnimClass::Unlimbo(coord);
 	AnimClass::Unlimbo(coord);
 
 
-	VisibleFlags = 0xffff;
+	VisibleFlags = static_cast<unsigned int>(-1);
 
 
 	/*
 	/*
 	**	Drop zone smoke always reveals the map around itself.
 	**	Drop zone smoke always reveals the map around itself.
@@ -817,10 +817,10 @@ void AnimClass::AI(void)
 					int damage = Accum;
 					int damage = Accum;
 					Accum -= damage;
 					Accum -= damage;
 					if (As_Object(xObject)->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) {
 					if (As_Object(xObject)->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) {
-						delete this;
 						if (Target_Legal(VirtualAnimTarget)) {
 						if (Target_Legal(VirtualAnimTarget)) {
 							delete As_Animation(VirtualAnimTarget);
 							delete As_Animation(VirtualAnimTarget);
 						}
 						}
+						delete this;
 						return;
 						return;
 					}
 					}
 				}
 				}
@@ -1177,8 +1177,12 @@ void AnimClass::Detach(TARGET target, bool all)
 	assert(IsActive);
 	assert(IsActive);
 
 
 	if (all) {
 	if (all) {
-		if (VirtualAnimTarget && VirtualAnimTarget == target) {
+		if (Target_Legal(VirtualAnimTarget) && VirtualAnimTarget == target) {
 			VirtualAnimTarget = TARGET_NONE;
 			VirtualAnimTarget = TARGET_NONE;
+			if (IsInvisible) {
+				IsToDelete = true;
+				Mark(MARK_UP);
+			}
 		}
 		}
 		if (xObject == target) {
 		if (xObject == target) {
 			Map.Remove(this, In_Which_Layer());
 			Map.Remove(this, In_Which_Layer());

+ 21 - 6
REDALERT/BUILDING.CPP

@@ -173,7 +173,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT
 		case RADIO_CAN_LOAD:
 		case RADIO_CAN_LOAD:
 			TechnoClass::Receive_Message(from, message, param);
 			TechnoClass::Receive_Message(from, message, param);
 			if (!House->Is_Ally(from)) return(RADIO_STATIC);
 			if (!House->Is_Ally(from)) return(RADIO_STATIC);
-			if (Mission == MISSION_CONSTRUCTION || Mission == MISSION_DECONSTRUCTION || BState == BSTATE_CONSTRUCTION || (!ScenarioInit && In_Radio_Contact() && Contact_With_Whom() != from)) return(RADIO_NEGATIVE);
+			if (Mission == MISSION_CONSTRUCTION || Mission == MISSION_DECONSTRUCTION || BState == BSTATE_CONSTRUCTION || (!ScenarioInit && Class->Type != STRUCT_REFINERY && In_Radio_Contact())) return(RADIO_NEGATIVE);
 			switch (Class->Type) {
 			switch (Class->Type) {
 				case STRUCT_AIRSTRIP:
 				case STRUCT_AIRSTRIP:
 					if (from->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass const *)from)->Class->IsFixedWing) {
 					if (from->What_Am_I() == RTTI_AIRCRAFT && ((AircraftClass const *)from)->Class->IsFixedWing) {
@@ -200,7 +200,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT
 						*((UnitClass *)from) == UNIT_HARVESTER &&
 						*((UnitClass *)from) == UNIT_HARVESTER &&
 						(ScenarioInit || !Is_Something_Attached())) {
 						(ScenarioInit || !Is_Something_Attached())) {
 
 
-						return(RADIO_ROGER);
+						return((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE);
 					}
 					}
 					break;
 					break;
 
 
@@ -2572,13 +2572,21 @@ void BuildingClass::Grand_Opening(bool captured)
 				**	to place it in a nearby location.
 				**	to place it in a nearby location.
 				*/
 				*/
 				if (!unit->Unlimbo(Cell_Coord(cell), DIR_W)) {
 				if (!unit->Unlimbo(Cell_Coord(cell), DIR_W)) {
-					cell = unit->Nearby_Location(this);
+					/*
+					**	Check multiple times for clear locations.
+					*/
+					for (int i = 0; i < 10; i++) {
+						cell = unit->Nearby_Location(this, i);
+						if (unit->Unlimbo(Cell_Coord(cell), DIR_SW)) {
+							break;
+						}
+					}
 
 
 					/*
 					/*
 					**	If the harvester could still not be placed, then refund the money
 					**	If the harvester could still not be placed, then refund the money
 					**	to the owner and then bail.
 					**	to the owner and then bail.
 					*/
 					*/
-					if (!unit->Unlimbo(Cell_Coord(cell), DIR_SW)) {
+					if (unit->IsInLimbo) {
 						House->Refund_Money(unit->Class->Cost_Of());
 						House->Refund_Money(unit->Class->Cost_Of());
 						delete unit;
 						delete unit;
 					}
 					}
@@ -3726,9 +3734,16 @@ int BuildingClass::Mission_Deconstruction(void)
 					}
 					}
 				}
 				}
 
 
-				if (House->IsPlayerControl) {
+				// MBL 07.10.2020 - In 1v1, sometimes both players will hear this SFX, or neither player will hear it
+				// Making it so all players hear it positionally in the map; Per thread discussion in https://jaas.ea.com/browse/TDRA-7245
+				//
+				#if 0
+					if (House->IsPlayerControl) {
+						Sound_Effect(VOC_CASHTURN, Coord);
+					}
+				#else
 					Sound_Effect(VOC_CASHTURN, Coord);
 					Sound_Effect(VOC_CASHTURN, Coord);
-				}
+				#endif
 				
 				
 				/*
 				/*
 				**	Destroy all attached objects. ST - 4/24/2020 9:38PM
 				**	Destroy all attached objects. ST - 4/24/2020 9:38PM

+ 11 - 2
REDALERT/CELL.CPP

@@ -2807,9 +2807,18 @@ void CellClass::Flag_Create(void)
 {
 {
 	if (!CTFFlag) {
 	if (!CTFFlag) {
 		CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord());
 		CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord());
-		if (CTFFlag) {
-			CTFFlag->OwnerHouse = Owner;
+		if (CTFFlag == NULL) {
+			for (int i = 0; i < Anims.Count(); ++i) {
+				AnimClass* anim = Anims.Ptr(i);
+				if (*anim != ANIM_FLAG) {
+					delete anim;
+					break;
+				}
+			}
+			CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord());
 		}
 		}
+		assert(CTFFlag != NULL);
+		CTFFlag->OwnerHouse = Owner;
 	}
 	}
 }
 }
 
 

+ 9 - 0
REDALERT/DEFINES.H

@@ -3615,6 +3615,15 @@ typedef enum OptionControlType : char {
 } OptionControlType;
 } OptionControlType;
 
 
 
 
+/****************************************************************************
+**	Used to store firing data for a unit.
+*/
+typedef struct {
+	COORDINATE Center;
+	int Distance;
+} FireDataType;
+
+
 #define size_of(typ,id) sizeof(((typ*)0)->id)
 #define size_of(typ,id) sizeof(((typ*)0)->id)
 
 
 
 

+ 2 - 2
REDALERT/DISPLAY.CPP

@@ -3146,9 +3146,9 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType & key)
 			object = Map.Close_Object(coord);
 			object = Map.Close_Object(coord);
 
 
 			/*
 			/*
-			**	Special case check to ignore cloaked object if not owned by the player.
+			**	Special case check to ignore cloaked object if not allied with the player.
 			*/
 			*/
-			if (object != NULL && object->Is_Techno() && !((TechnoClass *)object)->IsOwnedByPlayer && (((TechnoClass *)object)->Cloak == CLOAKED || ((TechnoClass *)object)->Techno_Type_Class()->IsInvisible)) {
+			if (object != NULL && object->Is_Techno() && ((TechnoClass *)object)->Is_Cloaked(PlayerPtr, true)) {
 				object = NULL;
 				object = NULL;
 			}
 			}
 		}
 		}

+ 79 - 5
REDALERT/DLLInterface.cpp

@@ -384,6 +384,8 @@ bool MPSuperWeaponDisable = false;
 bool ShareAllyVisibility = true;
 bool ShareAllyVisibility = true;
 bool UseGlyphXStartLocations = true;
 bool UseGlyphXStartLocations = true;
 
 
+SpecialClass* SpecialBackup = NULL;
+
 
 
 int GetRandSeed()
 int GetRandSeed()
 {
 {
@@ -800,6 +802,8 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scena
 
 
 	Special.IsEarlyWin = game_options.DestroyStructures;
 	Special.IsEarlyWin = game_options.DestroyStructures;
 
 
+	Special.ModernBalance = game_options.ModernBalance;
+
 	/*
 	/*
 	** Enable Counterstrike/Aftermath units
 	** Enable Counterstrike/Aftermath units
 	*/
 	*/
@@ -844,6 +848,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scena
 	*/
 	*/
 	Rule.IsSmartDefense = true;
 	Rule.IsSmartDefense = true;
 
 
+	/*
+	** Backup special
+	*/
+	if (SpecialBackup != NULL) {
+		memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
+	}
+
 	return true;
 	return true;
 }
 }
 
 
@@ -1662,7 +1673,7 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player
 	if (Frame <= 10) {		// Don't spam forever, but useful to know that we actually started advancing
 	if (Frame <= 10) {		// Don't spam forever, but useful to know that we actually started advancing
 		GlyphX_Debug_Print("CNC_Advance_Instance - RA");
 		GlyphX_Debug_Print("CNC_Advance_Instance - RA");
 	}
 	}
-	
+
 	/*
 	/*
 	** Shouldn't really need to do this, but I like the idea of always running the main loop in the context of the same player.
 	** Shouldn't really need to do this, but I like the idea of always running the main loop in the context of the same player.
 	** Might make tbe bugs more repeatable and consistent. ST - 3/15/2019 11:58AM
 	** Might make tbe bugs more repeatable and consistent. ST - 3/15/2019 11:58AM
@@ -1673,6 +1684,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0]);
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0]);
 	}
 	}
 
 
+	/*
+	** Restore special from backup
+	*/
+	if (SpecialBackup != NULL) {
+		memcpy(&Special, SpecialBackup, sizeof(SpecialClass));
+	}
+
 #ifdef FIXIT_CSII	//	checked - ajw 9/28/98
 #ifdef FIXIT_CSII	//	checked - ajw 9/28/98
 	TimeQuake = PendingTimeQuake;
 	TimeQuake = PendingTimeQuake;
 	PendingTimeQuake = false;
 	PendingTimeQuake = false;
@@ -1865,6 +1883,15 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player
 	//Sync_Delay();
 	//Sync_Delay();
 	//DLLExportClass::Set_Event_Callback(NULL);
 	//DLLExportClass::Set_Event_Callback(NULL);
 	Color_Cycle();
 	Color_Cycle();
+	
+	
+	/*
+	** Don't respect GameActive. Game will end in multiplayer on win/loss
+	*/
+	if (GAME_TO_PLAY == GAME_GLYPHX_MULTIPLAYER) {
+		return true;
+	}
+
 	return(GameActive);
 	return(GameActive);
 }
 }
 
 
@@ -1910,6 +1937,12 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha
 		}
 		}
 		
 		
 		result = Load_Game(file_path_and_name);
 		result = Load_Game(file_path_and_name);
+
+		// MBL 07.21.2020
+		if (result == false)
+		{
+			return false;
+		}
 		
 		
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
 		Set_Logic_Page(SeenBuff);
 		Set_Logic_Page(SeenBuff);
@@ -2130,6 +2163,11 @@ void DLLExportClass::Init(void)
 	CurrentLocalPlayerIndex = 0;
 	CurrentLocalPlayerIndex = 0;
 
 
 	MessagesSent.clear();
 	MessagesSent.clear();
+
+	if (SpecialBackup == NULL) {
+		SpecialBackup = new SpecialClass;
+	}
+	memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
 }
 }
 
 
 
 
@@ -2146,6 +2184,9 @@ void DLLExportClass::Init(void)
 **************************************************************************************************/
 **************************************************************************************************/
 void DLLExportClass::Shutdown(void)
 void DLLExportClass::Shutdown(void)
 {
 {
+	delete SpecialBackup;
+	SpecialBackup = NULL;
+
 	for (int i=0 ; i<ModSearchPaths.Count() ; i++) {
 	for (int i=0 ; i<ModSearchPaths.Count() ; i++) {
 		delete [] ModSearchPaths[i];
 		delete [] ModSearchPaths[i];
 	}
 	}
@@ -3252,6 +3293,7 @@ void DLL_Draw_Pip_Intercept(const ObjectClass* object, int pip)
 void DLLExportClass::DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, const ObjectClass *object, DirType rotation, long scale, const char *shape_file_name, char override_owner)
 void DLLExportClass::DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, const ObjectClass *object, DirType rotation, long scale, const char *shape_file_name, char override_owner)
 {
 {
 	CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount];
 	CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount];
+	memset(&new_object, 0, sizeof(new_object));
 	Convert_Type(object, new_object);
 	Convert_Type(object, new_object);
 	if (new_object.Type == UNKNOWN) {
 	if (new_object.Type == UNKNOWN) {
 		return;
 		return;
@@ -4394,6 +4436,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 					return false;
 					return false;
 				}
 				}
 
 
+				memset(&sidebar_entry, 0, sizeof(sidebar_entry));
+
 				sidebar_entry.AssetName[0] = 0;
 				sidebar_entry.AssetName[0] = 0;
 				sidebar_entry.Type = UNKNOWN;
 				sidebar_entry.Type = UNKNOWN;
 				sidebar_entry.BuildableID = Map.Column[c].Buildables[b].BuildableID;
 				sidebar_entry.BuildableID = Map.Column[c].Buildables[b].BuildableID;
@@ -4406,7 +4450,7 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 				sidebar_entry.SuperWeaponType = SW_NONE;
 				sidebar_entry.SuperWeaponType = SW_NONE;
 
 
 				if (tech) {
 				if (tech) {
-					sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias;
+					sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias; // MBL: If this gets modified, also modify below for skirmish and multiplayer
 					sidebar_entry.PowerProvided = 0;
 					sidebar_entry.PowerProvided = 0;
 					sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
 					sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
 					strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
 					strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
@@ -4549,6 +4593,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 						return false;
 						return false;
 					}
 					}
 
 
+					memset(&sidebar_entry, 0, sizeof(sidebar_entry));
+
 					sidebar_entry.AssetName[0] = 0;
 					sidebar_entry.AssetName[0] = 0;
 					sidebar_entry.Type = UNKNOWN;
 					sidebar_entry.Type = UNKNOWN;
 					sidebar_entry.BuildableID = context_sidebar->Column[c].Buildables[b].BuildableID;
 					sidebar_entry.BuildableID = context_sidebar->Column[c].Buildables[b].BuildableID;
@@ -4561,7 +4607,13 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 					sidebar_entry.SuperWeaponType = SW_NONE;
 					sidebar_entry.SuperWeaponType = SW_NONE;
 
 
 					if (tech) {
 					if (tech) {
-						sidebar_entry.Cost = tech->Cost;
+
+						// MBL 06.22.2020	- Updated to apply and difficulty abd/or faction price modifier; See https://jaas.ea.com/browse/TDRA-6864
+						// If this gets modified, also modify above for non-skirmish / non-multiplayer
+						//
+						// sidebar_entry.Cost = tech->Cost;
+						sidebar_entry.Cost = tech->Cost * PlayerPtr->CostBias;
+
 						sidebar_entry.PowerProvided = 0;
 						sidebar_entry.PowerProvided = 0;
 						sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
 						sidebar_entry.BuildTime = tech->Time_To_Build(PlayerPtr->Class->House); // sidebar_entry.BuildTime = tech->Time_To_Build() / 60;
 						strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
 						strncpy(sidebar_entry.AssetName, tech->IniName, CNC_OBJECT_ASSET_NAME_LENGTH);
@@ -8480,10 +8532,16 @@ bool DLLExportClass::Save(Pipe & pipe)
 
 
 	pipe.Put(&Special, sizeof(Special));
 	pipe.Put(&Special, sizeof(Special));
 
 
+	/*
+	** Special case for MPSuperWeaponDisable - store negated value so it defaults to enabled
+	*/
+	bool not_allow_super_weapons = !MPSuperWeaponDisable;
+	pipe.Put(&not_allow_super_weapons, sizeof(not_allow_super_weapons));
+
 	/*
 	/*
 	** Room for save game expansion
 	** Room for save game expansion
 	*/
 	*/
-	unsigned char padding[4096];
+	unsigned char padding[4095];
 	memset(padding, 0, sizeof(padding));
 	memset(padding, 0, sizeof(padding));
 	
 	
 	pipe.Put(padding, sizeof(padding));
 	pipe.Put(padding, sizeof(padding));
@@ -8566,7 +8624,23 @@ bool DLLExportClass::Load(Straw & file)
 		return false;
 		return false;
 	}
 	}
 
 
-	unsigned char padding[4096];
+	/*
+	** Restore backup
+	*/
+	if (SpecialBackup != NULL) {
+		memcpy(SpecialBackup, &Special, sizeof(SpecialClass));
+	}
+
+	/*
+	** Special case for MPSuperWeaponDisable - store negated value so it defaults to enabled
+	*/
+	bool not_allow_super_weapons = false;
+	if (file.Get(&not_allow_super_weapons, sizeof(not_allow_super_weapons)) != sizeof(not_allow_super_weapons)) {
+		return false;
+	}
+	MPSuperWeaponDisable = !not_allow_super_weapons;
+
+	unsigned char padding[4095];
 	
 	
 	if (file.Get(padding, sizeof(padding)) != sizeof(padding)) {
 	if (file.Get(padding, sizeof(padding)) != sizeof(padding)) {
 		return false;
 		return false;

+ 2 - 1
REDALERT/DLLInterface.h

@@ -732,7 +732,7 @@ struct CNCMultiplayerOptionsStruct {
 	int MPlayerCount;						// # of human players in this game
 	int MPlayerCount;						// # of human players in this game
 	int MPlayerBases;						// 1 = bases are on for this scenario
 	int MPlayerBases;						// 1 = bases are on for this scenario
 	int MPlayerCredits;					// # credits everyone gets
 	int MPlayerCredits;					// # credits everyone gets
-	int MPlayerTiberium;					// 1 = tiberium enabled for this scenario
+	int MPlayerTiberium;					// >0 = tiberium enabled for this scenario
 	int MPlayerGoodies;					// 1 = goodies enabled for this scenario
 	int MPlayerGoodies;					// 1 = goodies enabled for this scenario
 	int MPlayerGhosts;					// 1 = houses with no players will still play
 	int MPlayerGhosts;					// 1 = houses with no players will still play
 	int MPlayerSolo;						// 1 = allows a single-player net game
 	int MPlayerSolo;						// 1 = allows a single-player net game
@@ -744,6 +744,7 @@ struct CNCMultiplayerOptionsStruct {
 	bool MPlayerAftermathUnits;
 	bool MPlayerAftermathUnits;
 	bool CaptureTheFlag;
 	bool CaptureTheFlag;
 	bool DestroyStructures;				// New early win condition via destroying all a player's structures
 	bool DestroyStructures;				// New early win condition via destroying all a player's structures
+	bool ModernBalance;
 };
 };
 
 
 
 

+ 7 - 1
REDALERT/EVENT.CPP

@@ -603,7 +603,7 @@ void EventClass::Execute(void)
 				//2019/09/19 JAS - Visibility needs to be determined per player
 				//2019/09/19 JAS - Visibility needs to be determined per player
 				if (Data.Anim.Owner == HOUSE_NONE || Data.Anim.What != ANIM_MOVE_FLASH)
 				if (Data.Anim.Owner == HOUSE_NONE || Data.Anim.What != ANIM_MOVE_FLASH)
 				{
 				{
-					anim->Set_Visible_Flags(0xffff);
+					anim->Set_Visible_Flags(static_cast<unsigned int>(-1));
 				}
 				}
 				else
 				else
 				{
 				{
@@ -761,6 +761,12 @@ void EventClass::Execute(void)
 					techno->Assign_Target(TARGET_NONE);
 					techno->Assign_Target(TARGET_NONE);
 					techno->Assign_Destination(Data.MegaMission.Target.As_TARGET());
 					techno->Assign_Destination(Data.MegaMission.Target.As_TARGET());
 					techno->ArchiveTarget = Data.MegaMission.Target.As_TARGET();
 					techno->ArchiveTarget = Data.MegaMission.Target.As_TARGET();
+				} else if (Data.MegaMission.Mission == MISSION_ENTER &&
+							object != NULL &&
+							object->What_Am_I() == RTTI_BUILDING &&
+							*((BuildingClass*)object) == STRUCT_REFINERY) {
+					techno->Transmit_Message(RADIO_HELLO, (BuildingClass*)object);
+					techno->Assign_Destination(TARGET_NONE);
 				} else {
 				} else {
 					if (q && techno->Is_Foot()) {
 					if (q && techno->Is_Foot()) {
 						((FootClass *)techno)->Queue_Navigation_List(Data.MegaMission.Destination.As_TARGET());
 						((FootClass *)techno)->Queue_Navigation_List(Data.MegaMission.Destination.As_TARGET());

+ 13 - 1
REDALERT/FOOT.CPP

@@ -1741,7 +1741,19 @@ int FootClass::Mission_Enter(void)
 		**	Since there is no potential object to enter, then abort this
 		**	Since there is no potential object to enter, then abort this
 		**	mission with some default standby mission.
 		**	mission with some default standby mission.
 		*/
 		*/
-		Enter_Idle_Mode();
+		if (MissionQueue == MISSION_NONE) {
+			/*
+			**	If this is a harvester, then return to harvesting.
+			**	Set a hacky target so we know to skip to the proper state.
+			*/
+			if (What_Am_I() == RTTI_UNIT && ((UnitClass*)this)->Class->IsToHarvest) {
+				Assign_Mission(MISSION_HARVEST);
+				Assign_Target(As_Target());
+				Assign_Destination(TARGET_NONE);
+			} else {
+				Enter_Idle_Mode();
+			}
+		}
 	}
 	}
 
 
 	return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2));
 	return(MissionControl[Mission].Normal_Delay() + Random_Pick(0, 2));

+ 86 - 12
REDALERT/HOUSE.CPP

@@ -921,7 +921,7 @@ void HouseClass::AI(void)
 	**	production and team creation as well. This is also true if the IQ is high enough to
 	**	production and team creation as well. This is also true if the IQ is high enough to
 	**	being base building.
 	**	being base building.
 	*/
 	*/
-	if (IsBaseBuilding || IQ >= Rule.IQProduction) {
+	if (!IsHuman && (IsBaseBuilding || IQ >= Rule.IQProduction)) {
 		IsBaseBuilding = true;
 		IsBaseBuilding = true;
 		IsStarted = true;
 		IsStarted = true;
 		IsAlerted = true;
 		IsAlerted = true;
@@ -957,6 +957,9 @@ void HouseClass::AI(void)
 	if (IsToDie && BorrowedTime == 0) {
 	if (IsToDie && BorrowedTime == 0) {
 		IsToDie = false;
 		IsToDie = false;
 		Blowup_All();
 		Blowup_All();
+		if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
+			MPlayer_Defeated();
+		}
 	}
 	}
 
 
 	/*
 	/*
@@ -1151,7 +1154,7 @@ void HouseClass::AI(void)
 	}
 	}
 
 
 #ifdef FIXIT_VERSION_3			//	For endgame auto-sonar pulse.
 #ifdef FIXIT_VERSION_3			//	For endgame auto-sonar pulse.
-	if( Scen.AutoSonarTimer == 0 )
+	if( (Session.Type != GAME_NORMAL || !IsHuman) && Scen.AutoSonarTimer == 0 )
 	{
 	{
 		//	If house has nothing but subs left, do an automatic sonar pulse to reveal them.
 		//	If house has nothing but subs left, do an automatic sonar pulse to reveal them.
 		if( VQuantity[ VESSEL_SS ] > 0 )		//	Includes count of VESSEL_MISSILESUBs. ajw
 		if( VQuantity[ VESSEL_SS ] > 0 )		//	Includes count of VESSEL_MISSILESUBs. ajw
@@ -1818,12 +1821,18 @@ void HouseClass::Attacked(BuildingClass* source)
 {
 {
 	assert(Houses.ID(this) == ID);
 	assert(Houses.ID(this) == ID);
 
 
+	bool expired = SpeakAttackDelay == 0;
+	bool spoke = false;
+
 #ifdef FIXIT_BASE_ANNOUNCE
 #ifdef FIXIT_BASE_ANNOUNCE
-	if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
+	// if (SpeakAttackDelay == 0 && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
+	if (expired && ((Session.Type == GAME_NORMAL && IsPlayerControl) || PlayerPtr->Class->House == Class->House)) {
 #else
 #else
-	if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) {
+	// if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) {
+	if (expired && PlayerPtr->Class->House == Class->House) {
 #endif
 #endif
 		Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
 		Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
+		spoke = true;
 
 
 		// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
 		// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
 		// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes
 		// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes
@@ -1838,6 +1847,22 @@ void HouseClass::Attacked(BuildingClass* source)
 			HouseTriggers[Class->House][index]->Spring(TEVENT_ATTACKED);
 			HouseTriggers[Class->House][index]->Spring(TEVENT_ATTACKED);
 		}
 		}
 	}
 	}
+
+	// MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249
+	// Separated to here as did not want to change any logic around the HouseTriggers[] Spring events
+	//
+	if (expired == true && spoke == false) 
+	{
+		if (Session.Type != GAME_NORMAL) // Multiplayer
+		{
+			Speak(VOX_BASE_UNDER_ATTACK, this);
+			spoke = true;
+	
+			// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE * Rule.SpeakDelay); // 2 minutes
+			// SpeakAttackDelay = Options.Normalize_Delay(TICKS_PER_MINUTE/2); // 30 seconds as requested
+			SpeakAttackDelay = Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) ); // Tweaked for accuracy
+		}
+	}
 }
 }
 
 
 
 
@@ -3758,6 +3783,13 @@ void HouseClass::MPlayer_Defeated(void)
 		}
 		}
 	}
 	}
 
 
+	/*
+	**	Remove any one-time superweapons the player might have.
+	*/
+	for (i = SPC_FIRST; i < SPC_COUNT; i++) {
+		SuperWeapon[i].Remove(true);
+	}
+
 	/*
 	/*
 	**	If this is me:
 	**	If this is me:
 	**	- Set MPlayerObiWan, so I can only send messages to all players, and
 	**	- Set MPlayerObiWan, so I can only send messages to all players, and
@@ -4099,7 +4131,14 @@ void HouseClass::Blowup_All(void)
 			*/
 			*/
 			count = 0;
 			count = 0;
 			while (::Units.Ptr(i)==uptr && uptr->Strength) {
 			while (::Units.Ptr(i)==uptr && uptr->Strength) {
-				damage = uptr->Strength;
+
+				// MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840
+				// Likely due to damage biasing based on RA factions and/or difficulty settings
+				// Applying this to units (vehicles), ships, buildings, and infantry, too
+				//
+				// damage = uptr->Strength; // Original
+				damage = 0x7fff; // Copied from TD
+
 				uptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 				uptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 				count++;
 				count++;
 				if (count > 5 && uptr->IsActive) {
 				if (count > 5 && uptr->IsActive) {
@@ -4118,7 +4157,13 @@ void HouseClass::Blowup_All(void)
 		if (::Aircraft.Ptr(i)->House == this && !::Aircraft.Ptr(i)->IsInLimbo) {
 		if (::Aircraft.Ptr(i)->House == this && !::Aircraft.Ptr(i)->IsInLimbo) {
 			AircraftClass * aptr = ::Aircraft.Ptr(i);
 			AircraftClass * aptr = ::Aircraft.Ptr(i);
 
 
-			damage = aptr->Strength;
+			// MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840
+			// Likely due to damage biasing based on RA factions and/or difficulty settings
+			// Applying this to units (vehicles), ships, buildings, and infantry, too
+			//
+			// damage = aptr->Strength; // Original
+			damage = 0x7fff; // Copied from TD
+
 			aptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 			aptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 			if (!aptr->IsActive) {
 			if (!aptr->IsActive) {
 				i--;
 				i--;
@@ -4133,7 +4178,13 @@ void HouseClass::Blowup_All(void)
 		if (::Vessels.Ptr(i)->House == this && !::Vessels.Ptr(i)->IsInLimbo) {
 		if (::Vessels.Ptr(i)->House == this && !::Vessels.Ptr(i)->IsInLimbo) {
 			VesselClass * vptr = ::Vessels.Ptr(i);
 			VesselClass * vptr = ::Vessels.Ptr(i);
 
 
-			damage = vptr->Strength;
+			// MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840
+			// Likely due to damage biasing based on RA factions and/or difficulty settings
+			// Applying this to units (vehicles), ships, buildings, and infantry, too
+			//
+			// damage = vptr->Strength; // Original
+			damage = 0x7fff; // Copied from TD 
+
 			vptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 			vptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 			if (!vptr->IsActive) {
 			if (!vptr->IsActive) {
 				i--;
 				i--;
@@ -4151,7 +4202,14 @@ void HouseClass::Blowup_All(void)
 
 
 			count = 0;
 			count = 0;
 			while (Buildings.Ptr(i)==bptr && bptr->Strength) {
 			while (Buildings.Ptr(i)==bptr && bptr->Strength) {
-				damage = bptr->Strength;
+
+				// MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840
+				// Likely due to damage biasing based on RA factions and/or difficulty settings
+				// Applying this to units (vehicles), ships, buildings, and infantry, too
+				//
+				// damage = bptr->Strength; // Original
+				damage = 0x7fff; // Copied from TD 
+
 				bptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 				bptr->Take_Damage(damage, 0, WARHEAD_HE, NULL, true);
 				count++;
 				count++;
 				if (count > 5) {
 				if (count > 5) {
@@ -4174,7 +4232,14 @@ void HouseClass::Blowup_All(void)
 
 
 			count = 0;
 			count = 0;
 			while (Infantry.Ptr(i)==iptr && iptr->Strength) {
 			while (Infantry.Ptr(i)==iptr && iptr->Strength) {
-				damage = iptr->Strength;
+
+				// MBL 06.22.2020 RA: Not all aircraft die in this case; See https://jaas.ea.com/browse/TDRA-6840
+				// Likely due to damage biasing based on RA factions and/or difficulty settings
+				// Applying this to units (vehicles), ships, buildings, and infantry, too
+				//
+				// damage = iptr->Strength; // Original
+				damage = 0x7fff; // Copied from TD 
+
 				warhead = Random_Pick(WARHEAD_SA, WARHEAD_FIRE);
 				warhead = Random_Pick(WARHEAD_SA, WARHEAD_FIRE);
 				iptr->Take_Damage(damage, 0, warhead, NULL, true);
 				iptr->Take_Damage(damage, 0, warhead, NULL, true);
 
 
@@ -8013,6 +8078,13 @@ void HouseClass::Check_Pertinent_Structures(void)
 		return;
 		return;
 	}
 	}
 
 
+	// MBL 07.15.2020 - Prevention of recent issue with constant "player defeated logic" and message to client spamming
+	// Per https://jaas.ea.com/browse/TDRA-7433
+	//
+	if (IsDefeated) {
+		return;
+	}
+
 	bool any_good_buildings = false;
 	bool any_good_buildings = false;
 	
 	
 	for (int index = 0; index < Buildings.Count(); index++) {
 	for (int index = 0; index < Buildings.Count(); index++) {
@@ -8020,9 +8092,11 @@ void HouseClass::Check_Pertinent_Structures(void)
 
 
 		if (b && b->IsActive && b->House == this) {
 		if (b && b->IsActive && b->House == this) {
 			if (!b->Class->IsWall && *b != STRUCT_APMINE && *b != STRUCT_AVMINE) {
 			if (!b->Class->IsWall && *b != STRUCT_APMINE && *b != STRUCT_AVMINE) {
-				if (!b->IsInLimbo && b->Strength > 0) {
-					any_good_buildings = true;
-					break;
+				if (!Special.ModernBalance || (*b != STRUCT_SHIP_YARD && *b != STRUCT_FAKE_YARD && *b != STRUCT_SUB_PEN && *b != STRUCT_FAKE_PEN)) {
+					if (!b->IsInLimbo && b->Strength > 0) {
+						any_good_buildings = true;
+						break;
+					}
 				}
 				}
 			}
 			}
 		}
 		}

+ 12 - 2
REDALERT/INFANTRY.CPP

@@ -511,6 +511,9 @@ int InfantryClass::Shape_Number(WindowNumberType window) const
 	** The animation frame numbers may be different when rendering in legacy mode vs. exporting for render in GlyphX. ST - 9/5/2019 12:34PM
 	** The animation frame numbers may be different when rendering in legacy mode vs. exporting for render in GlyphX. ST - 9/5/2019 12:34PM
 	*/
 	*/
 	const DoInfoStruct *do_controls = (window == WINDOW_VIRTUAL) ? Class->DoControlsVirtual : Class->DoControls;
 	const DoInfoStruct *do_controls = (window == WINDOW_VIRTUAL) ? Class->DoControlsVirtual : Class->DoControls;
+	if (window != WINDOW_VIRTUAL && !IsOwnedByPlayer && *this == INFANTRY_SPY) {
+		do_controls = InfantryTypeClass::As_Reference(INFANTRY_E1).DoControls;
+	}
 			
 			
 	/*
 	/*
 	**	The infantry shape is always modulo the number of animation frames
 	**	The infantry shape is always modulo the number of animation frames
@@ -692,7 +695,7 @@ void InfantryClass::Per_Cell_Process(PCPType why)
 							
 							
 								// If they're spying on a sub pen, give 'em a sonar pulse
 								// If they're spying on a sub pen, give 'em a sonar pulse
 								if (build == STRUCT_SUB_PEN) {
 								if (build == STRUCT_SUB_PEN) {
-									House->SuperWeapon[SPC_SONAR_PULSE].Enable(true, true, false);
+									House->SuperWeapon[SPC_SONAR_PULSE].Enable(false, true, false);
 									// Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM
 									// Add to Glyphx multiplayer sidebar. ST - 8/7/2019 10:13AM
 									if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
 									if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
 										if (House->IsHuman) {
 										if (House->IsHuman) {
@@ -1480,7 +1483,7 @@ MoveType InfantryClass::Can_Enter_Cell(CELL cell, FacingType ) const
 					**	Cloaked enemy objects are not considered if this is a Find_Path()
 					**	Cloaked enemy objects are not considered if this is a Find_Path()
 					**	call.
 					**	call.
 					*/
 					*/
-					if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) {
+					if (!obj->Is_Techno() || !((TechnoClass *)obj)->Is_Cloaked(this)) {
 
 
 						/*
 						/*
 						**	Any non-allied blockage is considered impassible if the infantry
 						**	Any non-allied blockage is considered impassible if the infantry
@@ -3958,6 +3961,13 @@ void InfantryClass::Movement_AI(void)
 //				if (IsTethered) Scatter(0, true);
 //				if (IsTethered) Scatter(0, true);
 			}
 			}
 
 
+			/*
+			**	Scatter infantry off buildings in guard modes.
+			*/
+			if (!IsTethered && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord].Cell_Building() != NULL) {
+				Scatter(0, true, true);
+			}
+
 			/*
 			/*
 			**	Double check to make sure it doesn't have a movement destination into a zone
 			**	Double check to make sure it doesn't have a movement destination into a zone
 			**	that it can't travel to. In such a case, abort the movement process by clearing
 			**	that it can't travel to. In such a case, abort the movement process by clearing

+ 3 - 0
REDALERT/INICODE.CPP

@@ -286,6 +286,9 @@ bool Read_Scenario_INI_Write_INB( char *root, bool fresh)
 	UnitClass::Read_INI(buffer);
 	UnitClass::Read_INI(buffer);
 	Call_Back();
 	Call_Back();
 
 
+	AircraftClass::Read_INI(buffer);
+	Call_Back();
+
 	VesselClass::Read_INI(buffer);
 	VesselClass::Read_INI(buffer);
 	Call_Back();
 	Call_Back();
 
 

+ 10 - 0
REDALERT/INIT.CPP

@@ -434,6 +434,16 @@ bool Init_Game(int , char * [])
 	ChronalVortex.Stop();
 	ChronalVortex.Stop();
 	ChronalVortex.Setup_Remap_Tables(Scen.Theater);
 	ChronalVortex.Setup_Remap_Tables(Scen.Theater);
 
 
+	/*
+	**	Clear out name overrides array
+	*/
+#ifdef FIXIT_NAME_OVERRIDE
+	for (int index = 0; index < ARRAY_SIZE(NameOverride); index++) {
+		NameOverride[index] = NULL;
+		NameIDOverride[index] = 0;
+	}
+#endif //FIXIT_NAME_OVERRIDE
+
 	return(true);
 	return(true);
 }
 }
 
 

+ 4 - 2
REDALERT/LOGIC.CPP

@@ -316,6 +316,7 @@ void LogicClass::AI(void)
 	*/
 	*/
 	for (index = 0; index < Count(); index++) {
 	for (index = 0; index < Count(); index++) {
 		ObjectClass * obj = (*this)[index];
 		ObjectClass * obj = (*this)[index];
+		int count = Count();
 
 
 		BStart(BENCH_AI);
 		BStart(BENCH_AI);
 		obj->AI();
 		obj->AI();
@@ -354,8 +355,9 @@ void LogicClass::AI(void)
 		**	If the object was destroyed in the process of performing its AI, then
 		**	If the object was destroyed in the process of performing its AI, then
 		**	adjust the index so that no object gets skipped.
 		**	adjust the index so that no object gets skipped.
 		*/
 		*/
-		if (obj != (*this)[index]) {
-			index--;
+		int count_diff = Count() - count;
+		if (count_diff < 0) {
+			index += count_diff;
 		}
 		}
 	}
 	}
 	HouseClass::Recalc_Attributes();
 	HouseClass::Recalc_Attributes();

+ 15 - 5
REDALERT/MAP.CPP

@@ -1026,6 +1026,16 @@ void MapClass::Logic(void)
 	**	Tiberium cells that can grow or spread.
 	**	Tiberium cells that can grow or spread.
 	*/
 	*/
 	int subcount = MAP_CELL_TOTAL / (Rule.GrowthRate * TICKS_PER_MINUTE);
 	int subcount = MAP_CELL_TOTAL / (Rule.GrowthRate * TICKS_PER_MINUTE);
+
+	/*
+	** Use the Tiberium setting as a multiplier on growth rate. ST - 7/1/2020 3:05PM
+	*/
+	if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
+		if (Session.Options.Tiberium > 1) {
+			subcount *= Session.Options.Tiberium;
+		}
+	}		 
+
 	subcount = max(subcount, 1);
 	subcount = max(subcount, 1);
 	int index;
 	int index;
 	for (index = TiberiumScan; index < MAP_CELL_TOTAL; index++) {
 	for (index = TiberiumScan; index < MAP_CELL_TOTAL; index++) {
@@ -1416,11 +1426,11 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const
 			while (o != NULL) {
 			while (o != NULL) {
 
 
 				/*
 				/*
-				**	Special case check to ignore cloaked object if not owned by the player.
+				**	Special case check to ignore cloaked object if not allied with the player.
 				*/
 				*/
 				// Change for client/server multiplayer. ST - 8/7/2019 10:35AM
 				// Change for client/server multiplayer. ST - 8/7/2019 10:35AM
 				//if (!o->Is_Techno() || ((TechnoClass *)o)->IsOwnedByPlayer || ((TechnoClass *)o)->Cloak != CLOAKED) {
 				//if (!o->Is_Techno() || ((TechnoClass *)o)->IsOwnedByPlayer || ((TechnoClass *)o)->Cloak != CLOAKED) {
-				if (!o->Is_Techno() || ((TechnoClass *)o)->Is_Owned_By_Player() || ((TechnoClass *)o)->Cloak != CLOAKED) {
+				if (!o->Is_Techno() || !((TechnoClass *)o)->Is_Cloaked(PlayerPtr)) {
 					int d=-1;
 					int d=-1;
 					if (o->What_Am_I() == RTTI_BUILDING) {
 					if (o->What_Am_I() == RTTI_BUILDING) {
 						d = Distance(coord, Cell_Coord(newcell));
 						d = Distance(coord, Cell_Coord(newcell));
@@ -1445,7 +1455,7 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const
 		AircraftClass * aircraft = Aircraft.Ptr(index);
 		AircraftClass * aircraft = Aircraft.Ptr(index);
 
 
 		if (aircraft->In_Which_Layer() != LAYER_GROUND) {
 		if (aircraft->In_Which_Layer() != LAYER_GROUND) {
-			if (aircraft->Is_Owned_By_Player() || (aircraft->Cloak != CLOAKED)) {
+			if (!aircraft->Is_Cloaked(PlayerPtr)) {
 				int d = Distance(coord, Coord_Add(aircraft->Center_Coord(), XY_Coord(0, -aircraft->Height)));
 				int d = Distance(coord, Coord_Add(aircraft->Center_Coord(), XY_Coord(0, -aircraft->Height)));
 				if (d >= 0 && (!object || d < distance)) {
 				if (d >= 0 && (!object || d < distance)) {
 					distance = d;
 					distance = d;
@@ -1683,7 +1693,7 @@ int MapClass::Zone_Span(CELL cell, int zone, MZoneType check)
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   10/05/1995 JLB : Created.                                                                 *
  *   10/05/1995 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
-CELL MapClass::Nearby_Location(CELL cell, SpeedType speed, int zone, MZoneType check, bool checkflagged) const
+CELL MapClass::Nearby_Location(CELL cell, SpeedType speed, int zone, MZoneType check, bool checkflagged, int locationmod) const
 {
 {
 	CELL topten[10];
 	CELL topten[10];
 	int count = 0;
 	int count = 0;
@@ -1767,7 +1777,7 @@ CELL MapClass::Nearby_Location(CELL cell, SpeedType speed, int zone, MZoneType c
 	}
 	}
 
 
 	if (count > 0) {
 	if (count > 0) {
-		return(topten[Frame % count]);
+		return(topten[(Frame+locationmod) % count]);
 	}
 	}
 	return(0);
 	return(0);
 }
 }

+ 1 - 1
REDALERT/MAP.H

@@ -61,7 +61,7 @@ class MapClass: public GScreenClass
 		CELL Pick_Random_Location(void) const;
 		CELL Pick_Random_Location(void) const;
 		int Intact_Bridge_Count(void) const;
 		int Intact_Bridge_Count(void) const;
 		bool Base_Region(CELL cell, HousesType & house, ZoneType & zone) const;
 		bool Base_Region(CELL cell, HousesType & house, ZoneType & zone) const;
-		CELL Nearby_Location(CELL cell, SpeedType speed, int zone=-1, MZoneType check=MZONE_NORMAL, bool checkflagged=false) const;
+		CELL Nearby_Location(CELL cell, SpeedType speed, int zone=-1, MZoneType check=MZONE_NORMAL, bool checkflagged=false, int locationmod=0) const;
 		ObjectClass * Close_Object(COORDINATE coord) const;
 		ObjectClass * Close_Object(COORDINATE coord) const;
 		virtual void Detach(ObjectClass * ) {};
 		virtual void Detach(ObjectClass * ) {};
 		int Cell_Region(CELL cell);
 		int Cell_Region(CELL cell);

+ 1 - 1
REDALERT/MiscAsm.cpp

@@ -428,7 +428,7 @@ dxisbig:
 #if (0)
 #if (0)
 
 
 /*
 /*
-	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#57 $
+	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/MiscAsm.cpp#95 $
 ;***************************************************************************
 ;***************************************************************************
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;***************************************************************************
 ;***************************************************************************

+ 8 - 0
REDALERT/OBJECT.CPP

@@ -627,6 +627,14 @@ COORDINATE ObjectClass::Sort_Y(void) const
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   09/21/1995 JLB : Created.                                                                 *
  *   09/21/1995 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
+FireDataType ObjectClass::Fire_Data(int which) const
+{
+	assert(this != 0);
+	assert(IsActive);
+
+	return{Fire_Coord(which),0};
+}
+
 COORDINATE ObjectClass::Fire_Coord(int ) const
 COORDINATE ObjectClass::Fire_Coord(int ) const
 {
 {
 	assert(this != 0);
 	assert(this != 0);

+ 1 - 0
REDALERT/OBJECT.H

@@ -177,6 +177,7 @@ class ObjectClass : public AbstractClass
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Render_Coord(void) const;
 		virtual COORDINATE Render_Coord(void) const;
 		virtual COORDINATE Sort_Y(void) const;
 		virtual COORDINATE Sort_Y(void) const;
+		virtual FireDataType Fire_Data(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Exit_Coord(void) const;
 		virtual COORDINATE Exit_Coord(void) const;
 
 

+ 2 - 2
REDALERT/RADAR.CPP

@@ -267,7 +267,7 @@ bool RadarClass::Radar_Activate(int control)
 		case 0:
 		case 0:
 			if (Map.IsSidebarActive) {
 			if (Map.IsSidebarActive) {
 				if (IsRadarActive && !IsRadarDeactivating) {
 				if (IsRadarActive && !IsRadarDeactivating) {
-					Sound_Effect(VOC_RADAR_OFF);
+					// Sound_Effect(VOC_RADAR_OFF); // MBL 07.20.2020: These are never being sent to the client, so handled there; Disabling here for good measure.
 					IsRadarDeactivating = true;
 					IsRadarDeactivating = true;
 					IsRadarActive = false;
 					IsRadarActive = false;
 					if (IsRadarActivating == true) {
 					if (IsRadarActivating == true) {
@@ -284,7 +284,7 @@ bool RadarClass::Radar_Activate(int control)
 		case 1:
 		case 1:
 			if (Map.IsSidebarActive) {
 			if (Map.IsSidebarActive) {
 				if (!IsRadarActivating && !IsRadarActive) {
 				if (!IsRadarActivating && !IsRadarActive) {
-					Sound_Effect(VOC_RADAR_ON);
+					// Sound_Effect(VOC_RADAR_ON); // MBL 07.20.2020: These are never being sent to the client, so handled there; Disabling here for good measure.
 					IsRadarActivating = true;
 					IsRadarActivating = true;
 					if (IsRadarDeactivating == true) {
 					if (IsRadarDeactivating == true) {
 						IsRadarDeactivating = false;
 						IsRadarDeactivating = false;

+ 1 - 1
REDALERT/RedAlert.vcxproj.filters

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
   <ItemGroup>
     <Filter Include="Source Files">
     <Filter Include="Source Files">

+ 4 - 1
REDALERT/SCENARIO.CPP

@@ -2411,7 +2411,10 @@ bool Read_Scenario_INI(char * fname, bool )
 	UnitClass::Read_INI(ini);
 	UnitClass::Read_INI(ini);
 	Call_Back();
 	Call_Back();
 
 
-    	VesselClass::Read_INI(ini);
+	AircraftClass::Read_INI(ini);
+	Call_Back();
+
+	VesselClass::Read_INI(ini);
 	Call_Back();
 	Call_Back();
 
 
 	/*
 	/*

+ 1 - 0
REDALERT/SPECIAL.CPP

@@ -82,6 +82,7 @@ void SpecialClass::Init(void)
 	UseMCVDeploy = false;
 	UseMCVDeploy = false;
 	IsMCVDeploy = false;
 	IsMCVDeploy = false;
 	IsEarlyWin = false;
 	IsEarlyWin = false;
+	ModernBalance = false;
 }
 }
 
 
 
 

+ 13 - 1
REDALERT/SPECIAL.H

@@ -105,10 +105,22 @@ class SpecialClass
 		*/
 		*/
 		unsigned IsEarlyWin:1;
 		unsigned IsEarlyWin:1;
 
 
+		/*
+		** New modern balance setting.
+		*/
+		unsigned ModernBalance:1;
+
 		/*
 		/*
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		*/
 		*/
-		unsigned char SaveLoadPadding[128];
+		// MBL 07.21.2020 - https://jaas.ea.com/browse/TDRA-7537
+		// Loading save files from Live and July Patch 3 Beta versions results in a crash
+		// Fixes issue from Change 738397 2020/07/17 14:06:03
+		// 
+		// unsigned char SaveLoadPadding[128]; // Note: Never changed to 127 like TD did
+		//
+		// unsigned char SaveLoadPadding[124]; // Trying 124 like we did with TD - Failed
+		unsigned char SaveLoadPadding[128]; 	// Works with With last weeks saves (7/16/2020) and newest saves; Skyler says go with this.
 };
 };
 
 
 
 

+ 4 - 0
REDALERT/STARTUP.CPP

@@ -977,6 +977,10 @@ void __cdecl Prog_End(const char *why, bool fatal)
 		*((int*)0) = 0;
 		*((int*)0) = 0;
 	}
 	}
 
 
+	if (Session.Type == GAME_GLYPHX_MULTIPLAYER) {
+		return;
+	}
+
 	Sound_End();
 	Sound_End();
 	if (WWMouse) {
 	if (WWMouse) {
 		delete WWMouse;
 		delete WWMouse;

+ 2 - 2
REDALERT/SUPER.CPP

@@ -168,9 +168,9 @@ bool SuperClass::Enable(bool onetime, bool player, bool quiet)
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   07/28/1995 JLB : Created.                                                                 *
  *   07/28/1995 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
-bool SuperClass::Remove(void)
+bool SuperClass::Remove(bool forced)
 {
 {
-	if (IsPresent && !IsOneTime) {
+	if (IsPresent && (!IsOneTime || forced)) {
 		IsReady = false;
 		IsReady = false;
 		IsPresent = false;
 		IsPresent = false;
 		return(true);
 		return(true);

+ 1 - 1
REDALERT/SUPER.H

@@ -47,7 +47,7 @@ class SuperClass {
 		bool Enable(bool onetime = false, bool player=false, bool quiet=false);
 		bool Enable(bool onetime = false, bool player=false, bool quiet=false);
 		void Forced_Charge(bool player=false);
 		void Forced_Charge(bool player=false);
 		bool AI(bool player=false);
 		bool AI(bool player=false);
-		bool Remove(void);
+		bool Remove(bool forced=false);
 		void Impatient_Click(void) const;
 		void Impatient_Click(void) const;
 		int Anim_Stage(void) const;
 		int Anim_Stage(void) const;
 		bool Discharged(bool player);
 		bool Discharged(bool player);

+ 65 - 24
REDALERT/TECHNO.CPP

@@ -489,6 +489,25 @@ bool TechnoClass::Is_Allowed_To_Recloak(void) const
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   07/29/1996 JLB : Created.                                                                 *
  *   07/29/1996 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
+FireDataType TechnoClass::Fire_Data(int which) const
+{
+	assert(IsActive);
+
+	TechnoTypeClass const * tclass = Techno_Type_Class();
+
+	int dist = 0;
+	if (which == 0) {
+		dist = tclass->PrimaryOffset;
+	} else {
+		dist = tclass->SecondaryOffset;
+	}
+
+	COORDINATE coord = Coord_Move(Center_Coord(), DIR_N, tclass->VerticalOffset + Height);
+	coord = Coord_Move(coord, DIR_E, tclass->HorizontalOffset);
+
+	return{coord,dist};
+}
+
 COORDINATE TechnoClass::Fire_Coord(int which) const
 COORDINATE TechnoClass::Fire_Coord(int which) const
 {
 {
 	assert(IsActive);
 	assert(IsActive);
@@ -667,7 +686,7 @@ bool TechnoClass::Is_Visible_On_Radar(void) const
 			}
 			}
 		}
 		}
 	}
 	}
-	if (!Techno_Type_Class()->IsInvisible && (Cloak != CLOAKED || House->Is_Ally(PlayerPtr))) {
+	if (!Is_Cloaked(PlayerPtr, true)) {
 		return(true);
 		return(true);
 	}
 	}
 	return(false);
 	return(false);
@@ -973,9 +992,14 @@ RadioMessageType TechnoClass::Receive_Message(RadioClass * from, RadioMessageTyp
 				/*
 				/*
 				**	If there is sufficient money to repair the unit one step, then do so.
 				**	If there is sufficient money to repair the unit one step, then do so.
 				**	Otherwise return with a "can't complete" radio response.
 				**	Otherwise return with a "can't complete" radio response.
+				**	Special case: in single-player campaigns, also try to use the repair pad house's money.
 				*/
 				*/
-				if (House->Available_Money() >= cost) {
-					House->Spend_Money(cost);
+				HouseClass* house = House;
+				if (Session.Type == GAME_NORMAL && house->Available_Money() < cost) {
+					house = HouseClass::As_Pointer(from->Owner());
+				}
+				if (house != NULL && house->Available_Money() >= cost) {
+					house->Spend_Money(cost);
 					Strength += step;
 					Strength += step;
 
 
 					/*
 					/*
@@ -1088,7 +1112,7 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) const
 	int width, height;
 	int width, height;
 	Class_Of().Dimensions(width, height);
 	Class_Of().Dimensions(width, height);
 
 
-	const bool show_health_bar = (Strength > 0) && (Is_Selected_By_Player() ||
+	const bool show_health_bar = (Strength > 0) && !Is_Cloaked(PlayerPtr) && (Is_Selected_By_Player() ||
 		((Rule.HealthBarDisplayMode == RulesClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) ||
 		((Rule.HealthBarDisplayMode == RulesClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) ||
 		(Rule.HealthBarDisplayMode == RulesClass::HB_ALWAYS));
 		(Rule.HealthBarDisplayMode == RulesClass::HB_ALWAYS));
 
 
@@ -1253,8 +1277,8 @@ bool TechnoClass::In_Range(TARGET target, int which, bool reciprocal_check) cons
 		if (building != NULL) {
 		if (building != NULL) {
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 		}
 		}
-
-		if (::Distance(Fire_Coord(which), As_Coord(target)) <= range) {
+		FireDataType data = Fire_Data(which);
+		if (MAX(0, ::Distance(data.Center, As_Coord(target)) - data.Distance) <= range) {
 			return(true);
 			return(true);
 		}
 		}
 
 
@@ -1309,8 +1333,8 @@ bool TechnoClass::In_Range(ObjectClass const * target, int which, bool reciproca
 			BuildingClass const * building = (BuildingClass const *)target;
 			BuildingClass const * building = (BuildingClass const *)target;
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 		}
 		}
-
-		if (::Distance(Fire_Coord(which), target->Center_Coord()) <= range) {
+		FireDataType data = Fire_Data(which);
+		if (MAX(0, ::Distance(data.Center, target->Center_Coord()) - data.Distance) <= range) {
 			return(true);
 			return(true);
 		}
 		}
 
 
@@ -1506,7 +1530,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno
 	/*
 	/*
 	**	If the object is cloaked, then it isn't a legal target.
 	**	If the object is cloaked, then it isn't a legal target.
 	*/
 	*/
-	if (object->Cloak == CLOAKED) {
+	if (object->Is_Cloaked(this)) {
 		BEnd(BENCH_EVAL_OBJECT);
 		BEnd(BENCH_EVAL_OBJECT);
 		return(false);
 		return(false);
 	}
 	}
@@ -1535,16 +1559,12 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno
 	**	object is a friend.  Unless we're a medic, of course.  But then,
 	**	object is a friend.  Unless we're a medic, of course.  But then,
 	** only consider it a target if it's injured.
 	** only consider it a target if it's injured.
 	*/
 	*/
-	if (House->Is_Ally(object)) {
-		if (Combat_Damage() < 0) {
-			if (object->Health_Ratio() == Rule.ConditionGreen) {
-				BEnd(BENCH_EVAL_OBJECT);
-				return(false);
-			}
-		} else {
-			BEnd(BENCH_EVAL_OBJECT);
-			return(false);
-		}
+	bool is_ally = House->Is_Ally(object);
+	bool is_medic = Combat_Damage() < 0;
+	bool green_health = object->Health_Ratio() == Rule.ConditionGreen;
+	if ((is_ally && (!is_medic || green_health)) || (!is_ally && is_medic)) {
+		BEnd(BENCH_EVAL_OBJECT);
+		return(false);
 	}
 	}
 
 
 	/*
 	/*
@@ -2016,6 +2036,27 @@ int TechnoClass::Evaluate_Just_Cell(CELL cell) const
 }
 }
 
 
 
 
+bool TechnoClass::Is_Cloaked(HousesType house, bool check_invisible) const
+{
+	const bool is_invisible = check_invisible && Techno_Type_Class()->IsInvisible;
+	return !House->Is_Ally(house) && ((Cloak == CLOAKED) || is_invisible);
+}
+
+
+bool TechnoClass::Is_Cloaked(HouseClass const * house, bool check_invisible) const
+{
+	const bool is_invisible = check_invisible && Techno_Type_Class()->IsInvisible;
+	return !House->Is_Ally(house) && ((Cloak == CLOAKED) || is_invisible);
+}
+
+
+bool TechnoClass::Is_Cloaked(ObjectClass const * object, bool check_invisible) const
+{
+	const bool is_invisible = check_invisible && Techno_Type_Class()->IsInvisible;
+	return !House->Is_Ally(object) && ((Cloak == CLOAKED) || is_invisible);
+}
+
+
 /***********************************************************************************************
 /***********************************************************************************************
  * TechnoClass::Greatest_Threat -- Determines best target given search criteria.               *
  * TechnoClass::Greatest_Threat -- Determines best target given search criteria.               *
  *                                                                                             *
  *                                                                                             *
@@ -2448,7 +2489,7 @@ void TechnoClass::AI(void)
 	**	If for some strange reason, the computer is firing upon itself, then
 	**	If for some strange reason, the computer is firing upon itself, then
 	**	tell it not to.
 	**	tell it not to.
 	*/
 	*/
-	if (!House->IsHuman && As_Techno(TarCom) && As_Techno(TarCom)->House->Is_Ally(this)) {
+	if (!House->IsHuman && As_Techno(TarCom) && As_Techno(TarCom)->House->Is_Ally(this) && Combat_Damage() >= 0) {
 //#ifdef FIXIT_CSII	//	checked - ajw 9/28/98 (commented out)
 //#ifdef FIXIT_CSII	//	checked - ajw 9/28/98 (commented out)
 //if(What_Am_I() == RTTI_INFANTRY && *(InfantryClass *)this==INFANTRY_GENERAL && Session.Type==GAME_NORMAL && House->Class->House==HOUSE_UKRAINE) {
 //if(What_Am_I() == RTTI_INFANTRY && *(InfantryClass *)this==INFANTRY_GENERAL && Session.Type==GAME_NORMAL && House->Class->House==HOUSE_UKRAINE) {
 //} else
 //} else
@@ -2773,9 +2814,9 @@ FireErrorType TechnoClass::Can_Fire(TARGET target, int which) const
 	ObjectClass * object = As_Object(target);
 	ObjectClass * object = As_Object(target);
 
 
 	/*
 	/*
-	**	If the object is completely cloaked, then you can't fire on it.
+	**	If an enemy object is completely cloaked, then you can't fire on it.
 	*/
 	*/
-	if (object != NULL && object->Is_Techno() && ((TechnoClass *)object)->Cloak == CLOAKED) {
+	if (object != NULL && object->Is_Techno() && ((TechnoClass *)object)->Is_Cloaked(this)) {
 		return(FIRE_CANT);
 		return(FIRE_CANT);
 	}
 	}
 
 
@@ -4187,7 +4228,7 @@ void TechnoClass::Record_The_Kill(TechnoClass * source)
  *   07/06/1995 JLB : Created.                                                                 *
  *   07/06/1995 JLB : Created.                                                                 *
  *   09/28/1995 JLB : Uses map scan function.                                                  *
  *   09/28/1995 JLB : Uses map scan function.                                                  *
  *=============================================================================================*/
  *=============================================================================================*/
-CELL TechnoClass::Nearby_Location(TechnoClass const * techno) const
+CELL TechnoClass::Nearby_Location(TechnoClass const * techno, int locationmod) const
 {
 {
 	assert(IsActive);
 	assert(IsActive);
 
 
@@ -4203,7 +4244,7 @@ CELL TechnoClass::Nearby_Location(TechnoClass const * techno) const
 		cell = Coord_Cell(Center_Coord());
 		cell = Coord_Cell(Center_Coord());
 	}
 	}
 
 
-	return(Map.Nearby_Location(cell, speed, Map[cell].Zones[Techno_Type_Class()->MZone], Techno_Type_Class()->MZone));
+	return(Map.Nearby_Location(cell, speed, Map[cell].Zones[Techno_Type_Class()->MZone], Techno_Type_Class()->MZone, false, locationmod));
 }
 }
 
 
 
 

+ 5 - 1
REDALERT/TECHNO.H

@@ -271,7 +271,7 @@ class TechnoClass :	public RadioClass,
 		bool Is_Ready_To_Cloak(void) const;
 		bool Is_Ready_To_Cloak(void) const;
 		virtual int How_Many_Survivors(void) const;
 		virtual int How_Many_Survivors(void) const;
 		virtual DirType Turret_Facing(void) const {return(PrimaryFacing.Current());}
 		virtual DirType Turret_Facing(void) const {return(PrimaryFacing.Current());}
-		CELL Nearby_Location(TechnoClass const * from=NULL) const;
+		CELL Nearby_Location(TechnoClass const * from=NULL, int locationmod=0) const;
 		TechnoTypeClass * Techno_Type_Class(void) const {return((TechnoTypeClass *)&Class_Of());};
 		TechnoTypeClass * Techno_Type_Class(void) const {return((TechnoTypeClass *)&Class_Of());};
 		bool Is_Visible_On_Radar(void) const;
 		bool Is_Visible_On_Radar(void) const;
 		int Anti_Air(void) const;
 		int Anti_Air(void) const;
@@ -282,6 +282,7 @@ class TechnoClass :	public RadioClass,
 		virtual ActionType What_Action(ObjectClass const * target) const;
 		virtual ActionType What_Action(ObjectClass const * target) const;
 		virtual BuildingClass * Find_Docking_Bay(StructType b, bool friendly) const;
 		virtual BuildingClass * Find_Docking_Bay(StructType b, bool friendly) const;
 		virtual CELL Find_Exit_Cell(TechnoClass const * techno) const;
 		virtual CELL Find_Exit_Cell(TechnoClass const * techno) const;
+		virtual FireDataType Fire_Data(int) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual DirType Desired_Load_Dir(ObjectClass * , CELL & moveto) const;
 		virtual DirType Desired_Load_Dir(ObjectClass * , CELL & moveto) const;
 		virtual DirType Fire_Direction(void) const;
 		virtual DirType Fire_Direction(void) const;
@@ -338,6 +339,9 @@ class TechnoClass :	public RadioClass,
 		bool Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value, int zone=-1) const;
 		bool Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value, int zone=-1) const;
 		int Evaluate_Just_Cell(CELL cell) const;
 		int Evaluate_Just_Cell(CELL cell) const;
 		virtual bool Electric_Zap (COORDINATE target_coord, int which, WindowNumberType window, COORDINATE source_coord=0L, unsigned char * remap=NULL) const;
 		virtual bool Electric_Zap (COORDINATE target_coord, int which, WindowNumberType window, COORDINATE source_coord=0L, unsigned char * remap=NULL) const;
+		bool Is_Cloaked(HousesType house, bool check_invisible=false) const;
+		bool Is_Cloaked(HouseClass const * house, bool check_invisible=false) const;
+		bool Is_Cloaked(ObjectClass const * object, bool check_invisible=false) const;
 
 
 		/*
 		/*
 		**	AI.
 		**	AI.

+ 40 - 9
REDALERT/UNIT.CPP

@@ -433,6 +433,13 @@ void UnitClass::AI(void)
 	*/
 	*/
 	Rotation_AI();
 	Rotation_AI();
 
 
+	/*
+	**	Scatter units off buildings in guard modes.
+	*/
+	if (!IsTethered && !IsFiring && !IsDriving && !IsRotating && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord].Cell_Building() != NULL) {
+		Scatter(0, true, true);
+	}
+
 	/*
 	/*
 	**	Delete this unit if it finds itself off the edge of the map and it is in
 	**	Delete this unit if it finds itself off the edge of the map and it is in
 	**	guard or other static mission mode.
 	**	guard or other static mission mode.
@@ -1283,6 +1290,11 @@ void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET
 
 
 	if (mission == MISSION_HARVEST) {
 	if (mission == MISSION_HARVEST) {
 		ArchiveTarget = TARGET_NONE;
 		ArchiveTarget = TARGET_NONE;
+	} else if (mission == MISSION_ENTER) {
+		BuildingClass* building = As_Building(destination);
+		if (building != NULL && *building == STRUCT_REFINERY && building->In_Radio_Contact()) {
+			building->Transmit_Message(RADIO_OVER_OUT);
+		}
 	}
 	}
 	DriveClass::Player_Assign_Mission(mission, target, destination);
 	DriveClass::Player_Assign_Mission(mission, target, destination);
 }
 }
@@ -1704,7 +1716,7 @@ void UnitClass::Per_Cell_Process(PCPType why)
 			} else {
 			} else {
 				TechnoClass * contact = Contact_With_Whom();
 				TechnoClass * contact = Contact_With_Whom();
 				if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) {
 				if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) {
-					if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING) {
+					if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) != STRUCT_REPAIR) {
 						Assign_Mission(MISSION_HARVEST);
 						Assign_Mission(MISSION_HARVEST);
 					} else if (!Target_Legal(NavCom)) {
 					} else if (!Target_Legal(NavCom)) {
 						Scatter(0, true);
 						Scatter(0, true);
@@ -2201,10 +2213,25 @@ int UnitClass::Tiberium_Check(CELL & center, int x, int y)
 
 
 	center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y);
 	center = XY_Cell(Cell_X(center)+x, Cell_Y(center)+y);
 
 
-	if ((Session.Type != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].IsMapped))) {
+	if ((Session.Type != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Mapped(PlayerPtr)))) {
 		if (Map[Coord].Zones[Class->MZone] != Map[center].Zones[Class->MZone]) return(0);
 		if (Map[Coord].Zones[Class->MZone] != Map[center].Zones[Class->MZone]) return(0);
 		if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) {
 		if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) {
-			return(Map[center].OverlayData);
+			int value = 0;
+			switch (Map[center].Overlay) {
+				case OVERLAY_GOLD1:
+				case OVERLAY_GOLD2:
+				case OVERLAY_GOLD3:
+				case OVERLAY_GOLD4:
+					value = Rule.GoldValue;
+					break;
+				case OVERLAY_GEMS1:
+				case OVERLAY_GEMS2:
+				case OVERLAY_GEMS3:
+				case OVERLAY_GEMS4:
+					value = Rule.GemValue*4;
+					break;
+			}
+			return((Map[center].OverlayData+1)*value);
 		}
 		}
 	}
 	}
 	return(0);
 	return(0);
@@ -2248,6 +2275,7 @@ bool UnitClass::Goto_Tiberium(int rad)
 				int tiberium = 0;
 				int tiberium = 0;
 				int besttiberium = 0;
 				int besttiberium = 0;
 				for (int x = -radius; x <= radius; x++) {
 				for (int x = -radius; x <= radius; x++) {
+					cell = center;
 					tiberium = Tiberium_Check(cell, x, -radius);
 					tiberium = Tiberium_Check(cell, x, -radius);
 					if (tiberium > besttiberium) {
 					if (tiberium > besttiberium) {
 						bestcell = cell;
 						bestcell = cell;
@@ -2832,10 +2860,10 @@ int UnitClass::Mission_Harvest(void)
 		*/
 		*/
 		case LOOKING:
 		case LOOKING:
 			/*
 			/*
-			**	When full of tiberium, just skip to finding a free refinery
-			**	to unload at.
+			**	Slightly hacky; if TarCom is set then skip to finding home state.
 			*/
 			*/
-			if (Tiberium_Load() == 1) {
+			if (Target_Legal(TarCom)) {
+				Assign_Target(TARGET_NONE);
 				Status = FINDHOME;
 				Status = FINDHOME;
 				return(1);
 				return(1);
 			}
 			}
@@ -3252,7 +3280,7 @@ MoveType UnitClass::Can_Enter_Cell(CELL cell, FacingType ) const
 				**	Cloaked enemy objects are not considered if this is a Find_Path()
 				**	Cloaked enemy objects are not considered if this is a Find_Path()
 				**	call.
 				**	call.
 				*/
 				*/
-				if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) {
+				if (!obj->Is_Techno() || !((TechnoClass *)obj)->Is_Cloaked(this)) {
 
 
 					/*
 					/*
 					**	If this unit can crush infantry, and there is an enemy infantry in the
 					**	If this unit can crush infantry, and there is an enemy infantry in the
@@ -3530,14 +3558,17 @@ ActionType UnitClass::What_Action(ObjectClass const * object) const
 	/*
 	/*
 	**	Special return to friendly refinery action.
 	**	Special return to friendly refinery action.
 	*/
 	*/
-	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) {
+	bool is_player_controlled = (Session.Type == GAME_NORMAL)
+		? (House->IsPlayerControl && object->Owner() != HOUSE_NONE && HouseClass::As_Pointer(object->Owner())->IsPlayerControl)
+		: (Is_Owned_By_Player() && House->Class->House == object->Owner());
+	if (is_player_controlled && object->What_Am_I() == RTTI_BUILDING && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, (TechnoClass*)object) == RADIO_ROGER) {
 		action = ACTION_ENTER;
 		action = ACTION_ENTER;
 	}
 	}
 
 
 	/*
 	/*
 	**	Special return to friendly repair factory action.
 	**	Special return to friendly repair factory action.
 	*/
 	*/
-	if (Is_Owned_By_Player() && House->Class->House == object->Owner() && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) {
+	if (is_player_controlled && action == ACTION_SELECT && object->What_Am_I() == RTTI_BUILDING) {
 		BuildingClass * building = (BuildingClass *)object;
 		BuildingClass * building = (BuildingClass *)object;
 		if (building->Class->Type == STRUCT_REPAIR && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, building) == RADIO_ROGER && !building->In_Radio_Contact() && !building->Is_Something_Attached()) {
 		if (building->Class->Type == STRUCT_REPAIR && ((UnitClass *)this)->Transmit_Message(RADIO_CAN_LOAD, building) == RADIO_ROGER && !building->In_Radio_Contact() && !building->Is_Something_Attached()) {
 			action = ACTION_MOVE;
 			action = ACTION_MOVE;

+ 27 - 1
REDALERT/VESSEL.CPP

@@ -299,7 +299,7 @@ MoveType VesselClass::Can_Enter_Cell(CELL cell, FacingType ) const
 		}
 		}
 
 
 		TechnoClass * techno = cellptr->Cell_Techno();
 		TechnoClass * techno = cellptr->Cell_Techno();
-		if (techno != NULL && techno->Cloak == CLOAKED && !House->Is_Ally(techno)) {
+		if (techno != NULL && techno->Is_Cloaked(this)) {
 			return(MOVE_CLOAK);
 			return(MOVE_CLOAK);
 		}
 		}
 
 
@@ -1183,6 +1183,32 @@ Mono_Set_Cursor(0,0);
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   05/13/1996 JLB : Created.                                                                 *
  *   05/13/1996 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
+FireDataType VesselClass::Fire_Data(int which) const
+{
+	assert(Vessels.ID(this) == ID);
+	assert(IsActive);
+
+	COORDINATE coord = Center_Coord();
+
+	if (*this == VESSEL_CA) {
+		if (IsSecondShot) {
+			coord = Coord_Move(coord, PrimaryFacing + DIR_S, 0x0100);
+		} else {
+			coord = Coord_Move(coord, PrimaryFacing, 0x0100);
+		}
+		coord = Coord_Move(coord, DIR_N, 0x0030);
+		return{coord,0x0040};
+	}
+
+	if (*this == VESSEL_PT) {
+		coord = Coord_Move(coord, PrimaryFacing, 0x0080);
+		coord = Coord_Move(coord, DIR_N, 0x0020);
+		return{coord,0x0010};
+	}
+
+	return(DriveClass::Fire_Data(which));
+}
+
 COORDINATE VesselClass::Fire_Coord(int which) const
 COORDINATE VesselClass::Fire_Coord(int which) const
 {
 {
 	assert(Vessels.ID(this) == ID);
 	assert(Vessels.ID(this) == ID);

+ 1 - 0
REDALERT/VESSEL.H

@@ -97,6 +97,7 @@ class VesselClass : public DriveClass
 		virtual int Mission_Unload(void);
 		virtual int Mission_Unload(void);
 		void LST_Open_Door(void);
 		void LST_Open_Door(void);
 		void LST_Close_Door(void);
 		void LST_Close_Door(void);
+		virtual FireDataType Fire_Data(int) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual MoveType Can_Enter_Cell(CELL cell, FacingType from=FACING_NONE) const;
 		virtual MoveType Can_Enter_Cell(CELL cell, FacingType from=FACING_NONE) const;
 		virtual void Draw_It(int x, int y, WindowNumberType window) const;
 		virtual void Draw_It(int x, int y, WindowNumberType window) const;

+ 1 - 1
REDALERT/WIN32LIB/DrawMisc.cpp

@@ -4842,7 +4842,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi
 
 
 
 
 /*
 /*
-; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#57 $
+; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/REDALERT/WIN32LIB/DrawMisc.cpp#95 $
 ;***************************************************************************
 ;***************************************************************************
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;***************************************************************************
 ;***************************************************************************

+ 2 - 2
TIBERIANDAWN/ADATA.CPP

@@ -337,7 +337,7 @@ static AnimTypeClass const LZSmoke(
 	72,										// Loop start frame number.
 	72,										// Loop start frame number.
 	91,										// Ending frame of loop back.
 	91,										// Ending frame of loop back.
 	-1,										// Number of animation stages.
 	-1,										// Number of animation stages.
-	255,										// Number of times the animation loops.
+	127,										// Number of times the animation loops.
 	VOC_NONE,								// Sound effect to play.
 	VOC_NONE,								// Sound effect to play.
 	ANIM_NONE
 	ANIM_NONE
 );
 );
@@ -1427,7 +1427,7 @@ static AnimTypeClass const OilFieldBurn(
 	33,										// Loop start frame number.
 	33,										// Loop start frame number.
 	99,										// Ending frame of loop back.
 	99,										// Ending frame of loop back.
 	66,										// Number of animation stages.
 	66,										// Number of animation stages.
-	65535,									// Number of times the animation loops.
+	127,									// Number of times the animation loops.
 	VOC_NONE,								// Sound effect to play.
 	VOC_NONE,								// Sound effect to play.
 	ANIM_NONE
 	ANIM_NONE
 );
 );

+ 36 - 7
TIBERIANDAWN/AIRCRAFT.CPP

@@ -245,6 +245,7 @@ AircraftClass::AircraftClass(AircraftType classid, HousesType house) :
 	NavCom = TARGET_NONE;
 	NavCom = TARGET_NONE;
 	SecondaryFacing = PrimaryFacing;
 	SecondaryFacing = PrimaryFacing;
 	Jitter = 0;
 	Jitter = 0;
+	ReinforcementStart = -1;
 
 
 	/*
 	/*
 	** Keep count of the number of units created. Dont track cargo planes as they are created
 	** Keep count of the number of units created. Dont track cargo planes as they are created
@@ -346,6 +347,14 @@ void AircraftClass::Draw_It(int x, int y, WindowNumberType window)
 	int shapenum = 0;
 	int shapenum = 0;
 	int facing = Facing_To_32(SecondaryFacing);
 	int facing = Facing_To_32(SecondaryFacing);
 
 
+	/*
+	**	Don't draw Cargo aircraft that are delayed.
+	*/
+	if (Special.ModernBalance) {
+		if (*this == AIRCRAFT_CARGO && !Map.In_Radar(Coord_Cell(Coord)) && ReinforcementStart > Frame) {
+			return;
+		}
+	}
 
 
 	/*
 	/*
 	**	Verify the legality of the unit class.
 	**	Verify the legality of the unit class.
@@ -743,7 +752,17 @@ void AircraftClass::AI(void)
 			Mark();
 			Mark();
 		}
 		}
 	}
 	}
-	if (Physics(Coord, PrimaryFacing) != RESULT_NONE) {
+
+	/*
+	**	Handle reinforcement delay.
+	*/
+	bool do_physics = true;
+	if (Special.ModernBalance) {
+		if (*this == AIRCRAFT_CARGO && !Map.In_Radar(Coord_Cell(Coord)) && ReinforcementStart > Frame) {
+			do_physics = false;
+		}
+	}
+	if (do_physics && Physics(Coord, PrimaryFacing) != RESULT_NONE) {
 		Mark();
 		Mark();
 	}
 	}
 
 
@@ -1399,9 +1418,13 @@ int AircraftClass::Mission_Retreat(void)
 {
 {
 	Validate();
 	Validate();
 	if (Class->IsFixedWing) {
 	if (Class->IsFixedWing) {
-		if (Class->IsFixedWing && Altitude < FLIGHT_LEVEL) {
+		if (*this == AIRCRAFT_CARGO) {
+			PrimaryFacing.Set_Desired(DIR_W);
+			SecondaryFacing.Set_Desired(PrimaryFacing.Desired());
+		}
+		if (Altitude < FLIGHT_LEVEL) {
 			Altitude++;
 			Altitude++;
-			return(3);
+			return(1);
 		}
 		}
 		return(TICKS_PER_SECOND*10);
 		return(TICKS_PER_SECOND*10);
 	}
 	}
@@ -2175,7 +2198,7 @@ ActionType AircraftClass::What_Action(CELL cell) const
 	ActionType action = FootClass::What_Action(cell);
 	ActionType action = FootClass::What_Action(cell);
 
 
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
-	if (action == ACTION_MOVE && !Map[cell].Is_Visible(PlayerPtr)) {
+	if ((action == ACTION_MOVE || action == ACTION_ATTACK) && !Map[cell].Is_Visible(PlayerPtr)) {
 		action = ACTION_NOMOVE;
 		action = ACTION_NOMOVE;
 	}
 	}
 
 
@@ -2438,7 +2461,7 @@ int AircraftClass::Mission_Attack(void)
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   06/19/1995 JLB : Created.                                                                 *
  *   06/19/1995 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
-TARGET AircraftClass::New_LZ(TARGET oldlz) const
+TARGET AircraftClass::New_LZ(TARGET oldlz, bool stable) const
 {
 {
 	Validate();
 	Validate();
 	if (Target_Legal(oldlz) && (!Is_LZ_Clear(oldlz) || !Cell_Seems_Ok(As_Cell(oldlz)))) {
 	if (Target_Legal(oldlz) && (!Is_LZ_Clear(oldlz) || !Cell_Seems_Ok(As_Cell(oldlz)))) {
@@ -2449,7 +2472,7 @@ TARGET AircraftClass::New_LZ(TARGET oldlz) const
 		**	in cells.
 		**	in cells.
 		*/
 		*/
 		for (int radius = 0; radius < 16; radius++) {
 		for (int radius = 0; radius < 16; radius++) {
-			FacingType modifier = Random_Pick(FACING_N, FACING_NW);
+			FacingType modifier = stable ? FACING_N : Random_Pick(FACING_N, FACING_NW);
 			CELL lastcell = -1;
 			CELL lastcell = -1;
 
 
 			/*
 			/*
@@ -2838,8 +2861,9 @@ TARGET AircraftClass::Good_Fire_Location(TARGET target) const
 			for (int face = 0; face < 255; face += 16) {
 			for (int face = 0; face < 255; face += 16) {
 				COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r);
 				COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r);
 				CELL newcell = Coord_Cell(newcoord);
 				CELL newcell = Coord_Cell(newcoord);
+				CELL actualcell = Coord_Cell(Coord_Sub(newcoord, XYPixel_Coord(0, FLIGHT_LEVEL)));
 
 
-				if (Map.In_Radar(newcell) && (GameToPlay != GAME_NORMAL || Map[newcell].Is_Visible(PlayerPtr)) && Cell_Seems_Ok(newcell, true)) {
+				if (Map.In_Radar(actualcell) && (GameToPlay != GAME_NORMAL || Map[newcell].Is_Visible(PlayerPtr)) && Cell_Seems_Ok(newcell, true)) {
 					int dist = Distance(newcoord);
 					int dist = Distance(newcoord);
 					if (bestval == -1 || dist < bestval) {
 					if (bestval == -1 || dist < bestval) {
 						best2val = bestval;
 						best2val = bestval;
@@ -3545,4 +3569,9 @@ void AircraftClass::Response_Select(void)
 	if (AllowVoice) {
 	if (AllowVoice) {
 		Sound_Effect(response, 0, -(Aircraft.ID(this)+1));
 		Sound_Effect(response, 0, -(Aircraft.ID(this)+1));
 	}
 	}
+}
+
+void AircraftClass::Set_Reinforcement_Delay(long delay)
+{
+	ReinforcementStart = Frame + delay;
 }
 }

+ 8 - 2
TIBERIANDAWN/AIRCRAFT.H

@@ -98,7 +98,7 @@ class AircraftClass : public FootClass, public FlyClass
 		**	Landing zone support functionality.
 		**	Landing zone support functionality.
 		*/
 		*/
 		bool  Is_LZ_Clear(TARGET target) const;
 		bool  Is_LZ_Clear(TARGET target) const;
-		TARGET  New_LZ(TARGET oldlz) const;
+		TARGET  New_LZ(TARGET oldlz, bool stable = false) const;
 
 
 		/*
 		/*
 		**	Coordinate inquiry functions. These are used for both display and
 		**	Coordinate inquiry functions. These are used for both display and
@@ -148,6 +148,7 @@ class AircraftClass : public FootClass, public FlyClass
 		virtual void Enter_Idle_Mode(bool initial = false);
 		virtual void Enter_Idle_Mode(bool initial = false);
 		virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param);
 		virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param);
 		virtual void Scatter(COORDINATE threat, bool forced=false, bool nokidding=false);
 		virtual void Scatter(COORDINATE threat, bool forced=false, bool nokidding=false);
+		void Set_Reinforcement_Delay(long delay);
 
 
 		/*
 		/*
 		**	Scenario and debug support.
 		**	Scenario and debug support.
@@ -242,10 +243,15 @@ class AircraftClass : public FootClass, public FlyClass
 		*/
 		*/
 		char AttacksRemaining;
 		char AttacksRemaining;
 
 
+		/*
+		**	Cargo planes will wait a certain number of ticks before flying in.
+		*/
+		long ReinforcementStart;
+
 		/*
 		/*
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		*/
 		*/
-		unsigned char SaveLoadPadding[32];
+		unsigned char SaveLoadPadding[28];
 
 
 		/*
 		/*
 		** This contains the value of the Virtual Function Table Pointer
 		** This contains the value of the Virtual Function Table Pointer

+ 15 - 2
TIBERIANDAWN/ANIM.CPP

@@ -589,7 +589,7 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay
 	coord = Adjust_Coord(coord);
 	coord = Adjust_Coord(coord);
 	Unlimbo(coord);
 	Unlimbo(coord);
 
 
-	VisibleFlags = 0xffff;
+	VisibleFlags = static_cast<unsigned int>(-1);
 
 
 	/*
 	/*
 	**	Drop zone smoke always reveals the map around itself.
 	**	Drop zone smoke always reveals the map around itself.
@@ -751,6 +751,16 @@ void AnimClass::AI(void)
 		return;
 		return;
 	}
 	}
 
 
+	/*
+	**	Lazy-initialize animation data (for loaded saves).
+	*/
+	if (Class->Stages == -1) {
+		((int&)Class->Stages) = Get_Build_Frame_Count(Class->Get_Image_Data());
+	}
+	if (Class->LoopEnd == -1) {
+		((int&)Class->LoopEnd) = Class->Stages;
+	}
+
 	if (Delay) {
 	if (Delay) {
 		Delay--;
 		Delay--;
 		if (!Delay) {
 		if (!Delay) {
@@ -788,10 +798,10 @@ void AnimClass::AI(void)
 					int damage = accum >> 8;
 					int damage = accum >> 8;
 					if (Object->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) {
 					if (Object->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) {
 						//Object = 0;
 						//Object = 0;
-						Delete_This();
 						if (VirtualAnim != NULL) {
 						if (VirtualAnim != NULL) {
 							VirtualAnim->Delete_This();
 							VirtualAnim->Delete_This();
 						}
 						}
+						Delete_This();
 						return;
 						return;
 					}
 					}
 				}
 				}
@@ -1272,6 +1282,9 @@ void AnimClass::Detach(TARGET target, bool all)
 	if (all) {
 	if (all) {
 		if (VirtualAnim && VirtualAnim->As_Target() == target) {
 		if (VirtualAnim && VirtualAnim->As_Target() == target) {
 			VirtualAnim = NULL;
 			VirtualAnim = NULL;
+			if (IsInvisible) {
+				IsToDelete = true;
+			}
 		}
 		}
 		if (Object && Object->As_Target() == target) {
 		if (Object && Object->As_Target() == target) {
 			Map.Remove(this, In_Which_Layer());
 			Map.Remove(this, In_Which_Layer());

+ 1 - 1
TIBERIANDAWN/BDATA.CPP

@@ -234,7 +234,7 @@ static BuildingTypeClass const ClassEye(
 		false,		// Always use the given name for the building?
 		false,		// Always use the given name for the building?
 		false,		// Is this a wall type structure?
 		false,		// Is this a wall type structure?
 		false,		// Is it a factory type building?
 		false,		// Is it a factory type building?
-		true,			// Can this building be captured?
+		false,			// Can this building be captured?
 		false,		// Does it catch fire?
 		false,		// Does it catch fire?
 		false,		// Simple (one frame) damage imagery?
 		false,		// Simple (one frame) damage imagery?
 		false,		// Is it invisible to radar?
 		false,		// Is it invisible to radar?

+ 68 - 15
TIBERIANDAWN/BUILDING.CPP

@@ -213,7 +213,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT
 
 
 		case RADIO_CAN_LOAD:
 		case RADIO_CAN_LOAD:
 			TechnoClass::Receive_Message(from, message, param);
 			TechnoClass::Receive_Message(from, message, param);
-			if (BState == BSTATE_CONSTRUCTION || (!ScenarioInit && In_Radio_Contact())) return(RADIO_NEGATIVE);
+			if (BState == BSTATE_CONSTRUCTION || (!ScenarioInit && Class->Type != STRUCT_REFINERY && In_Radio_Contact())) return(RADIO_NEGATIVE);
 			switch (Class->Type) {
 			switch (Class->Type) {
 				case STRUCT_AIRSTRIP:
 				case STRUCT_AIRSTRIP:
 					if (from->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass const *)from) == AIRCRAFT_CARGO) {
 					if (from->What_Am_I() == RTTI_AIRCRAFT && *((AircraftClass const *)from) == AIRCRAFT_CARGO) {
@@ -238,7 +238,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT
 						*((UnitClass *)from) == UNIT_HARVESTER &&
 						*((UnitClass *)from) == UNIT_HARVESTER &&
 						(ScenarioInit || !Is_Something_Attached())) {
 						(ScenarioInit || !Is_Something_Attached())) {
 
 
-						return(RADIO_ROGER);
+						return((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE);
 					}
 					}
 					break;
 					break;
 
 
@@ -1166,7 +1166,9 @@ void BuildingClass::AI(void)
 				Repair(1);
 				Repair(1);
 			} else {
 			} else {
 				if (IsTickedOff && (int)Scenario > 2 && Random_Pick(0, 50) < (int)Scenario && !Trigger) {
 				if (IsTickedOff && (int)Scenario > 2 && Random_Pick(0, 50) < (int)Scenario && !Trigger) {
-					Sell_Back(1);
+					if (GameToPlay != GAME_NORMAL || Scenario != 15 || PlayerPtr->ActLike != HOUSE_GOOD || *this != STRUCT_TEMPLE) {
+						Sell_Back(1);
+					}
 				}
 				}
 			}
 			}
 		}
 		}
@@ -2734,6 +2736,39 @@ bool BuildingClass::Limbo(void)
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   12/24/1994 JLB : Created.                                                                 *
  *   12/24/1994 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
+FireDataType BuildingClass::Fire_Data(int ) const
+{
+	Validate();
+	COORDINATE coord = Center_Coord();
+	int dist = 0;
+
+	/*
+	**	Make adjustments to the firing coordinate to account for turret
+	**	position. This depends on building type and turret facing.
+	*/
+	switch (Class->Type) {
+		default:
+		case STRUCT_GTOWER:
+		case STRUCT_ATOWER:
+			coord = Coord_Move(coord, DIR_N, 0x0030);
+			dist = 0x0040;
+			break;
+		case STRUCT_OBELISK:
+			coord = Coord_Move(coord, DIR_N, 0x00A8);
+			coord = Coord_Move(coord, DIR_W, 0x0018);
+			break;
+
+		case STRUCT_SAM:
+		case STRUCT_TURRET:
+			coord = Coord_Move(coord, DIR_N, 0x0030);
+			dist = 0x0080;
+			break;
+	}
+
+	return{coord,dist};
+}
+
+
 COORDINATE BuildingClass::Fire_Coord(int ) const
 COORDINATE BuildingClass::Fire_Coord(int ) const
 {
 {
 	Validate();
 	Validate();
@@ -3519,8 +3554,14 @@ bool BuildingClass::Toggle_Primary(void)
 			}
 			}
 		}
 		}
 		IsLeader = true;
 		IsLeader = true;
-		if (House == PlayerPtr) {
-			Speak(VOX_PRIMARY_SELECTED);
+		//
+		// MBL 07.22.2020 - Update so that each player in multiplayer will properly hear this when it applies to them
+		//
+		// if (House == PlayerPtr) {
+		// 	Speak(VOX_PRIMARY_SELECTED);
+		// }
+		if ((HouseClass *)House->IsHuman) {
+			Speak(VOX_PRIMARY_SELECTED, House);
 		}
 		}
 	}
 	}
 	Mark(MARK_CHANGE);
 	Mark(MARK_CHANGE);
@@ -3832,14 +3873,14 @@ bool BuildingClass::Can_Demolish_Unit(void) const
 bool BuildingClass::Can_Capture(void) const
 bool BuildingClass::Can_Capture(void) const
 {
 {
 	bool can_capture = Class->IsCaptureable;
 	bool can_capture = Class->IsCaptureable;
-	if (*this == STRUCT_EYE) {
-		// Don't allow the Advanced Comm Center to be capturable in skirmish, MP, or beyond scenario 13 in SP
-		if (GameToPlay == GAME_NORMAL) {
-			can_capture &= Scenario < 13;
-		} else {
-			can_capture = false;
+
+	// Override capturable state if this building has a capture win trigger
+	if (GameToPlay == GAME_NORMAL) {
+		if (Trigger != NULL && Trigger->Action == TriggerClass::ACTION_WINLOSE) {
+			can_capture = true;
 		}
 		}
 	}
 	}
+
 	return(can_capture);
 	return(can_capture);
 }
 }
 
 
@@ -4093,11 +4134,18 @@ int BuildingClass::Mission_Deconstruction(void)
 					}
 					}
 				}
 				}
 
 
-				//Changed for multiplayer ST - 3/13/2019 5:31PM
-				if (Is_Owned_By_Player()) {
-				//if (IsOwnedByPlayer) {
+				// MBL 07.10.2020 - In 1v1, sometimes both players will hear this SFX, or neither player will hear it
+				// Making it so all players hear it positionally in the map; Per thread discussion in https://jaas.ea.com/browse/TDRA-7245
+				//
+				#if 0
+					//Changed for multiplayer ST - 3/13/2019 5:31PM
+					if (Is_Owned_By_Player()) {
+					//if (IsOwnedByPlayer) {
+						Sound_Effect(VOC_CASHTURN, Coord);
+					}
+				#else
 					Sound_Effect(VOC_CASHTURN, Coord);
 					Sound_Effect(VOC_CASHTURN, Coord);
-				}
+				#endif
 				
 				
 				/*
 				/*
 				**	Destroy all attached objects. ST - 4/24/2020 9:38PM
 				**	Destroy all attached objects. ST - 4/24/2020 9:38PM
@@ -4557,6 +4605,11 @@ int BuildingClass::Mission_Repair(void)
 
 
 				if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
 				if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER) {
 					if (Contact_With_Whom()->Health_Ratio() < 0x0100 && Transmit_Message(RADIO_REPAIR) == RADIO_ROGER) {
 					if (Contact_With_Whom()->Health_Ratio() < 0x0100 && Transmit_Message(RADIO_REPAIR) == RADIO_ROGER) {
+
+						// MBL 07.06.2020 - Patch 3: Change to TD Legacy: Adding "Repairing" VO for units on repair bay
+						// Per https://jaas.ea.com/browse/TDRA-7271
+						if (IsOwnedByPlayer && House) Speak(VOX_REPAIRING, House);
+
 						Status = DURING;
 						Status = DURING;
 						Begin_Mode(BSTATE_ACTIVE);
 						Begin_Mode(BSTATE_ACTIVE);
 						IsReadyToCommence = false;
 						IsReadyToCommence = false;

+ 1 - 0
TIBERIANDAWN/BUILDING.H

@@ -196,6 +196,7 @@ class BuildingClass : public TechnoClass
 		**	combat purposes.
 		**	combat purposes.
 		*/
 		*/
 		virtual COORDINATE Docking_Coord(void) const;
 		virtual COORDINATE Docking_Coord(void) const;
+		virtual FireDataType Fire_Data(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Sort_Y(void) const;
 		virtual COORDINATE Sort_Y(void) const;

+ 1 - 1
TIBERIANDAWN/CDATA.CPP

@@ -1824,7 +1824,7 @@ static TemplateTypeClass const Patch15(
 	"P15",
 	"P15",
 	TXT_PATCH,
 	TXT_PATCH,
 	LAND_CLEAR,
 	LAND_CLEAR,
-	1,1,
+	4,2,
 	LAND_CLEAR,
 	LAND_CLEAR,
 	NULL
 	NULL
 );
 );

+ 30 - 12
TIBERIANDAWN/CELL.CPP

@@ -1936,7 +1936,7 @@ long CellClass::Tiberium_Adjust(bool pregame)
  *   05/22/1995 JLB : Created.                                                                 *
  *   05/22/1995 JLB : Created.                                                                 *
  *   07/08/1995 JLB : Added a bunch of goodies to the crates.                                  *
  *   07/08/1995 JLB : Added a bunch of goodies to the crates.                                  *
  *=============================================================================================*/
  *=============================================================================================*/
-bool CellClass::Goodie_Check(FootClass * object)
+bool CellClass::Goodie_Check(FootClass * object, bool check_steel)
 {
 {
 	Validate();
 	Validate();
 	enum {
 	enum {
@@ -1990,21 +1990,30 @@ bool CellClass::Goodie_Check(FootClass * object)
 		bool steel = (Overlay == OVERLAY_STEEL_CRATE);
 		bool steel = (Overlay == OVERLAY_STEEL_CRATE);
 		COORDINATE coord;			// Temporary working coordinate value.
 		COORDINATE coord;			// Temporary working coordinate value.
 
 
-		/*
-		**	A triggered crate is automatically destroyed regardless of who or how
-		**	it was triggered.
-		*/
-		Redraw_Objects();
-		Overlay = OVERLAY_NONE;
-		OverlayData = 0;
+		if (check_steel && steel) {
+
+			/*
+			**	A triggered crate is automatically destroyed regardless of who or how
+			**	it was triggered.
+			*/
+			Redraw_Objects();
+			Overlay = OVERLAY_NONE;
+			OverlayData = 0;
 
 
-		if (steel) {
 			if (object->Owner() == HOUSE_BAD) {
 			if (object->Owner() == HOUSE_BAD) {
 				object->House->Add_Nuke_Piece();
 				object->House->Add_Nuke_Piece();
 				new AnimClass(ANIM_CRATE_EMPULSE, Cell_Coord());
 				new AnimClass(ANIM_CRATE_EMPULSE, Cell_Coord());
 			}
 			}
 
 
-		} else {
+		} else if(!check_steel && !steel) {
+
+			/*
+			**	A triggered crate is automatically destroyed regardless of who or how
+			**	it was triggered.
+			*/
+			Redraw_Objects();
+			Overlay = OVERLAY_NONE;
+			OverlayData = 0;
 
 
 			int index;
 			int index;
 			UnitClass * unit = 0;
 			UnitClass * unit = 0;
@@ -2428,9 +2437,18 @@ void CellClass::Flag_Create(void)
 {
 {
 	if (!CTFFlag) {
 	if (!CTFFlag) {
 		CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true);
 		CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true);
-		if (CTFFlag) {
-			CTFFlag->OwnerHouse = Owner;
+		if (CTFFlag == NULL) {
+			for (int i = 0; i < Anims.Count(); ++i) {
+				AnimClass* anim = Anims.Ptr(i);
+				if (*anim != ANIM_FLAG) {
+					anim->Delete_This();
+					break;
+				}
+			}
+			CTFFlag = new AnimClass(ANIM_FLAG, Cell_Coord(), 0, 1, true);
 		}
 		}
+		assert(CTFFlag != NULL);
+		CTFFlag->OwnerHouse = Owner;
 	}
 	}
 }
 }
 
 

+ 1 - 1
TIBERIANDAWN/CELL.H

@@ -208,7 +208,7 @@ class CellClass
 		InfantryClass *  Cell_Infantry(void) const;
 		InfantryClass *  Cell_Infantry(void) const;
 		TriggerClass *  Get_Trigger(void) const;
 		TriggerClass *  Get_Trigger(void) const;
 		int  Clear_Icon(void) const;
 		int  Clear_Icon(void) const;
-		bool  Goodie_Check(FootClass * object);
+		bool  Goodie_Check(FootClass * object, bool check_steel = false);
 		ObjectClass *  Fetch_Occupier(void) const;
 		ObjectClass *  Fetch_Occupier(void) const;
 		bool Get_Template_Info(char *template_name, int &icon, void *&image_data);
 		bool Get_Template_Info(char *template_name, int &icon, void *&image_data);
 
 

+ 9 - 0
TIBERIANDAWN/DEFINES.H

@@ -2847,6 +2847,15 @@ typedef enum OptionControlType : char {
 } OptionControlType;
 } OptionControlType;
 
 
 
 
+/****************************************************************************
+**	Used to store firing data for a unit.
+*/
+typedef struct {
+	COORDINATE Center;
+	int Distance;
+} FireDataType;
+
+
 #define TOTAL_CRATE_TYPES 15
 #define TOTAL_CRATE_TYPES 15
 
 
 #define size_of(typ,id) sizeof(((typ*)0)->id)
 #define size_of(typ,id) sizeof(((typ*)0)->id)

+ 11 - 5
TIBERIANDAWN/DISPLAY.CPP

@@ -1810,8 +1810,9 @@ bool DisplayClass::Map_Cell(CELL cell, HouseClass * house, bool and_for_allies)
 	/*
 	/*
 	** Maybe also recurse to map for allies
 	** Maybe also recurse to map for allies
 	*/
 	*/
-	if (ShareAllyVisibility && and_for_allies && GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
-		for (HousesType house_type = HOUSE_MULTI1; house_type < HOUSE_COUNT; house_type++) {
+	if (ShareAllyVisibility && and_for_allies) {
+		HousesType first_house = (GameToPlay == GAME_NORMAL) ? HOUSE_FIRST : HOUSE_MULTI1;
+		for (HousesType house_type = first_house; house_type < HOUSE_COUNT; house_type++) {
 			HouseClass *hptr = HouseClass::As_Pointer(house_type);
 			HouseClass *hptr = HouseClass::As_Pointer(house_type);
 			if (hptr && hptr->IsActive) {
 			if (hptr && hptr->IsActive) {
 				if (hptr != house && house->Is_Ally(hptr)) {
 				if (hptr != house && house->Is_Ally(hptr)) {
@@ -3870,12 +3871,17 @@ void DisplayClass::Mouse_Left_Release(CELL cell, int x, int y, ObjectClass * obj
 				AllowVoice = true;
 				AllowVoice = true;
 				for (int index = 0; index < CurrentObject.Count(); index++) {
 				for (int index = 0; index < CurrentObject.Count(); index++) {
 					ObjectClass * tobject = CurrentObject[index];
 					ObjectClass * tobject = CurrentObject[index];
+					ActionType action = ACTION_NONE;
 					if (object) {
 					if (object) {
-						tobject->Active_Click_With(tobject->What_Action(object), object);
+						action = tobject->What_Action(object);
+						tobject->Active_Click_With(action, object);
 					} else {
 					} else {
-						tobject->Active_Click_With(tobject->What_Action(cell), cell);
+						action = tobject->What_Action(cell);
+						tobject->Active_Click_With(action, cell);
+					}
+					if (action != ACTION_NONE) {
+						AllowVoice = false;
 					}
 					}
-					AllowVoice = false;
 				}
 				}
 				AllowVoice = true;
 				AllowVoice = true;
 
 

+ 39 - 6
TIBERIANDAWN/DLLInterface.cpp

@@ -672,6 +672,7 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scena
 	Special.IsVisceroids = game_options.SpawnVisceroids;
 	Special.IsVisceroids = game_options.SpawnVisceroids;
 	Special.IsCaptureTheFlag = game_options.CaptureTheFlag;
 	Special.IsCaptureTheFlag = game_options.CaptureTheFlag;
 	Special.IsEarlyWin = game_options.DestroyStructures;
 	Special.IsEarlyWin = game_options.DestroyStructures;
+	Special.ModernBalance = game_options.ModernBalance;
 
 
 	Rule.AllowSuperWeapons = game_options.EnableSuperweapons;	// Are superweapons available
 	Rule.AllowSuperWeapons = game_options.EnableSuperweapons;	// Are superweapons available
 
 
@@ -1314,7 +1315,9 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Start_Custom_Instance(const ch
 
 
 	Clear_Scenario();
 	Clear_Scenario();
 
 
-	Read_Scenario_Ini_File(scenario_file_name, bin_file_name, scenario_name, true);
+	if (!Read_Scenario_Ini_File(scenario_file_name, bin_file_name, scenario_name, true)) {
+		return false;
+	}
 
 
 	HiddenPage.Clear();
 	HiddenPage.Clear();
 	VisiblePage.Clear();
 	VisiblePage.Clear();
@@ -1696,6 +1699,12 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha
 		
 		
 		result = Load_Game(file_path_and_name);
 		result = Load_Game(file_path_and_name);
 
 
+		// MBL 07.21.2020
+		if (result == false)
+		{
+			return false;
+		}
+
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
 		DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true);
 		Set_Logic_Page(SeenBuff);
 		Set_Logic_Page(SeenBuff);
 		VisiblePage.Clear();
 		VisiblePage.Clear();
@@ -2938,6 +2947,7 @@ void DLL_Draw_Line_Intercept(int x, int y, int x1, int y1, unsigned char color,
 void DLLExportClass::DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, ObjectClass *object, const char *shape_file_name, char override_owner, int scale)
 void DLLExportClass::DLL_Draw_Intercept(int shape_number, int x, int y, int width, int height, int flags, ObjectClass *object, const char *shape_file_name, char override_owner, int scale)
 {
 {
 	CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount];
 	CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount];
+	memset(&new_object, 0, sizeof(new_object));
 	Convert_Type(object, new_object);
 	Convert_Type(object, new_object);
 	if (new_object.Type == UNKNOWN) {
 	if (new_object.Type == UNKNOWN) {
 		return;
 		return;
@@ -4012,6 +4022,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 				if ((entry_index + 1) * sizeof(CNCSidebarEntryStruct) + memory_needed > buffer_size) {
 				if ((entry_index + 1) * sizeof(CNCSidebarEntryStruct) + memory_needed > buffer_size) {
 					return false;
 					return false;
 				}
 				}
+				
+				memset(&sidebar_entry, 0, sizeof(sidebar_entry));
 
 
 				sidebar_entry.AssetName[0] = 0;
 				sidebar_entry.AssetName[0] = 0;
 				sidebar_entry.Type = UNKNOWN;
 				sidebar_entry.Type = UNKNOWN;
@@ -4191,6 +4203,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 						return false;
 						return false;
 					}
 					}
 
 
+					memset(&sidebar_entry, 0, sizeof(sidebar_entry));
+
 					sidebar_entry.AssetName[0] = 0;
 					sidebar_entry.AssetName[0] = 0;
 					sidebar_entry.Type = UNKNOWN;
 					sidebar_entry.Type = UNKNOWN;
 					sidebar_entry.BuildableID = context_sidebar->Column[c].Buildables[b].BuildableID;
 					sidebar_entry.BuildableID = context_sidebar->Column[c].Buildables[b].BuildableID;
@@ -4352,6 +4366,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i
 }
 }
 
 
 
 
+static const int _map_width_shift_bits = 6;
+
 void DLLExportClass::Calculate_Placement_Distances(BuildingTypeClass* placement_type, unsigned char* placement_distance)
 void DLLExportClass::Calculate_Placement_Distances(BuildingTypeClass* placement_type, unsigned char* placement_distance)
 {
 {
 	int map_cell_x = Map.MapCellX;
 	int map_cell_x = Map.MapCellX;
@@ -4380,7 +4396,7 @@ void DLLExportClass::Calculate_Placement_Distances(BuildingTypeClass* placement_
 	memset(placement_distance, 255U, MAP_CELL_TOTAL);
 	memset(placement_distance, 255U, MAP_CELL_TOTAL);
 	for (int y = 0; y < map_cell_height; y++) {
 	for (int y = 0; y < map_cell_height; y++) {
 		for (int x = 0; x < map_cell_width; x++) {
 		for (int x = 0; x < map_cell_width; x++) {
-			CELL cell = (CELL)map_cell_x + x + ((map_cell_y + y) << 6);
+			CELL cell = (CELL)map_cell_x + x + ((map_cell_y + y) << _map_width_shift_bits);
 			BuildingClass* base = (BuildingClass*)Map[cell].Cell_Find_Object(RTTI_BUILDING);
 			BuildingClass* base = (BuildingClass*)Map[cell].Cell_Find_Object(RTTI_BUILDING);
 			if ((base && base->House->Class->House == PlayerPtr->Class->House) || (Map[cell].Owner == PlayerPtr->Class->House)) {
 			if ((base && base->House->Class->House == PlayerPtr->Class->House) || (Map[cell].Owner == PlayerPtr->Class->House)) {
 				placement_distance[cell] = 0U;
 				placement_distance[cell] = 0U;
@@ -4471,7 +4487,7 @@ bool DLLExportClass::Get_Placement_State(uint64 player_id, unsigned char *buffer
 	for (int y=0 ; y < map_cell_height ; y++) {
 	for (int y=0 ; y < map_cell_height ; y++) {
 		for (int x=0 ; x < map_cell_width ; x++) {
 		for (int x=0 ; x < map_cell_width ; x++) {
 
 
-			CELL cell = (CELL) map_cell_x + x + ((map_cell_y + y) << 6);
+			CELL cell = (CELL) map_cell_x + x + ((map_cell_y + y) << _map_width_shift_bits);
 
 
 			bool pass = Passes_Proximity_Check(cell, PlacementType[CurrentLocalPlayerIndex], PlacementDistance[CurrentLocalPlayerIndex]);
 			bool pass = Passes_Proximity_Check(cell, PlacementType[CurrentLocalPlayerIndex], PlacementDistance[CurrentLocalPlayerIndex]);
 
 
@@ -5132,7 +5148,7 @@ Map.Passes_Proximity_Check
 				map_cell_height++;
 				map_cell_height++;
 			}
 			}
 
 
-			CELL cell = (CELL) (map_cell_x + cell_x) + ( (map_cell_y + cell_y) << 6 );
+			CELL cell = (CELL) (map_cell_x + cell_x) + ( (map_cell_y + cell_y) << _map_width_shift_bits );
 
 
 			/*
 			/*
 			** Call the place directly instead of queueing it, so we can evaluate the return code.
 			** Call the place directly instead of queueing it, so we can evaluate the return code.
@@ -7503,10 +7519,18 @@ bool DLLExportClass::Save(FileClass & file)
 		return false;
 		return false;
 	}
 	}
 
 
+	/*
+	** Special case for Rule.AllowSuperWeapons - store negated value so it defaults to enabled
+	*/
+	bool not_allow_super_weapons = !Rule.AllowSuperWeapons;
+	if (file.Write(&not_allow_super_weapons, sizeof(not_allow_super_weapons)) != sizeof(not_allow_super_weapons)) {
+		return false;
+	}
+
 	/*
 	/*
 	** Room for save game expansion
 	** Room for save game expansion
 	*/
 	*/
-	unsigned char padding[4096];
+	unsigned char padding[4095];
 	memset(padding, 0, sizeof(padding));
 	memset(padding, 0, sizeof(padding));
 
 
 	if (file.Write(padding, sizeof(padding)) != sizeof(padding)) {
 	if (file.Write(padding, sizeof(padding)) != sizeof(padding)) {
@@ -7621,7 +7645,16 @@ bool DLLExportClass::Load(FileClass & file)
 		return false;
 		return false;
 	}
 	}
 
 
-	unsigned char padding[4096];
+	/*
+	** Special case for Rule.AllowSuperWeapons - store negated value so it defaults to enabled
+	*/
+	bool not_allow_super_weapons = false;
+	if (file.Read(&not_allow_super_weapons, sizeof(not_allow_super_weapons)) != sizeof(not_allow_super_weapons)) {
+		return false;
+	}
+	Rule.AllowSuperWeapons = !not_allow_super_weapons;
+
+	unsigned char padding[4095];
 
 
 	if (file.Read(padding, sizeof(padding)) != sizeof(padding)) {
 	if (file.Read(padding, sizeof(padding)) != sizeof(padding)) {
 		return false;
 		return false;

+ 2 - 1
TIBERIANDAWN/DLLInterface.h

@@ -728,7 +728,7 @@ struct CNCMultiplayerOptionsStruct {
 	int MPlayerCount;						// # of human players in this game
 	int MPlayerCount;						// # of human players in this game
 	int MPlayerBases;						// 1 = bases are on for this scenario
 	int MPlayerBases;						// 1 = bases are on for this scenario
 	int MPlayerCredits;					// # credits everyone gets
 	int MPlayerCredits;					// # credits everyone gets
-	int MPlayerTiberium;					// 1 = tiberium enabled for this scenario
+	int MPlayerTiberium;					// >0 = tiberium enabled for this scenario
 	int MPlayerGoodies;					// 1 = goodies enabled for this scenario
 	int MPlayerGoodies;					// 1 = goodies enabled for this scenario
 	int MPlayerGhosts;					// 1 = houses with no players will still play
 	int MPlayerGhosts;					// 1 = houses with no players will still play
 	int MPlayerSolo;						// 1 = allows a single-player net game
 	int MPlayerSolo;						// 1 = allows a single-player net game
@@ -740,6 +740,7 @@ struct CNCMultiplayerOptionsStruct {
 	bool MPlayerAftermathUnits;
 	bool MPlayerAftermathUnits;
 	bool CaptureTheFlag;
 	bool CaptureTheFlag;
 	bool DestroyStructures;				// New early win condition via destroying all a player's structures
 	bool DestroyStructures;				// New early win condition via destroying all a player's structures
+	bool ModernBalance;
 };
 };
 
 
 
 

+ 10 - 2
TIBERIANDAWN/DRIVE.CPP

@@ -962,9 +962,17 @@ bool DriveClass::Start_Of_Move(void)
 				/*
 				/*
 				**	If a basic path could be found, but the immediate move destination is
 				**	If a basic path could be found, but the immediate move destination is
 				**	blocked by a friendly temporary blockage, then cause that blockage
 				**	blocked by a friendly temporary blockage, then cause that blockage
-				**	to scatter.
+				**	to scatter. If the destination is also one cell away, then scatter
+				**	regardless of direction.
 				*/
 				*/
-				CELL cell = Adjacent_Cell(Coord_Cell(Center_Coord()), PrimaryFacing.Current());
+				CELL ourcell = Coord_Cell(Center_Coord());
+				CELL navcell = As_Cell(NavCom);
+				CELL cell = -1;
+				if (::Distance(ourcell, navcell) < 2) {
+					cell = navcell;
+				} else {
+					cell = Adjacent_Cell(ourcell, PrimaryFacing.Current());
+				}
 				if (Map.In_Radar(cell)) {
 				if (Map.In_Radar(cell)) {
 					if (Can_Enter_Cell(cell) == MOVE_TEMP) {
 					if (Can_Enter_Cell(cell) == MOVE_TEMP) {
 						CellClass * cellptr = &Map[cell];
 						CellClass * cellptr = &Map[cell];

+ 7 - 1
TIBERIANDAWN/EVENT.CPP

@@ -509,7 +509,7 @@ CCDebugString ("C&C95 - Sell packet received\n");
 				//2019/09/19 JAS - Visibility needs to be determined per player
 				//2019/09/19 JAS - Visibility needs to be determined per player
 				if (Data.Anim.What != ANIM_MOVE_FLASH || Data.Anim.Owner == HOUSE_NONE || Special.IsVisibleTarget)
 				if (Data.Anim.What != ANIM_MOVE_FLASH || Data.Anim.Owner == HOUSE_NONE || Special.IsVisibleTarget)
 				{
 				{
-					anim->Set_Visible_Flags(0xffff);
+					anim->Set_Visible_Flags(static_cast<unsigned int>(-1));
 				}
 				}
 				else
 				else
 				{
 				{
@@ -626,6 +626,12 @@ CCDebugString ("C&C95 - Primary building packet received\n");
 					techno->ArchiveTarget = Data.MegaMission.Target;
 					techno->ArchiveTarget = Data.MegaMission.Target;
 					techno->Assign_Target(TARGET_NONE);
 					techno->Assign_Target(TARGET_NONE);
 					techno->Assign_Destination(Data.MegaMission.Target);
 					techno->Assign_Destination(Data.MegaMission.Target);
+				} else if (Data.MegaMission.Mission == MISSION_ENTER &&
+							object != NULL &&
+							object->What_Am_I() == RTTI_BUILDING &&
+							*((BuildingClass*)object) == STRUCT_REFINERY) {
+					techno->Transmit_Message(RADIO_HELLO, (BuildingClass*)object);
+					techno->Assign_Destination(TARGET_NONE);
 				} else {
 				} else {
 					techno->Assign_Target(Data.MegaMission.Target);
 					techno->Assign_Target(Data.MegaMission.Target);
 					techno->Assign_Destination(Data.MegaMission.Destination);
 					techno->Assign_Destination(Data.MegaMission.Destination);

+ 16 - 2
TIBERIANDAWN/FOOT.CPP

@@ -1423,6 +1423,8 @@ void FootClass::Per_Cell_Process(bool center)
 		}
 		}
 //	}
 //	}
 
 
+	Map[Coord_Cell(Coord)].Goodie_Check(this, true);
+
 	TechnoClass::Per_Cell_Process(center);
 	TechnoClass::Per_Cell_Process(center);
 }
 }
 
 
@@ -1635,8 +1637,20 @@ int FootClass::Mission_Enter(void)
 		**	Since there is no potential object to enter, then abort this
 		**	Since there is no potential object to enter, then abort this
 		**	mission with some default standby mission.
 		**	mission with some default standby mission.
 		*/
 		*/
-		ArchiveTarget = TARGET_NONE;
-		Enter_Idle_Mode();
+		if (MissionQueue == MISSION_NONE) {
+			/*
+			**	If this is a harvester, then return to harvesting.
+			**	Set a hacky target so we know to skip to the proper state.
+			*/
+			if (What_Am_I() == RTTI_UNIT && ((UnitClass*)this)->Class->IsToHarvest) {
+				Assign_Mission(MISSION_HARVEST);
+				Assign_Target(As_Target());
+				Assign_Destination(TARGET_NONE);
+			} else {
+				ArchiveTarget = TARGET_NONE;
+				Enter_Idle_Mode();
+			}
+		}
 	}
 	}
 	return(TICKS_PER_SECOND/2);
 	return(TICKS_PER_SECOND/2);
 }
 }

+ 54 - 8
TIBERIANDAWN/HOUSE.CPP

@@ -569,16 +569,18 @@ bool HouseClass::Can_Build(TechnoTypeClass const * type, HousesType house) const
 	*/
 	*/
 	long flags = ActiveBScan;
 	long flags = ActiveBScan;
 
 
-#ifdef USE_RA_AI
-	// OldBScan Copied from RA for AI. ST - 7/25/2019 3:27PM
 	/*
 	/*
-	**	The computer records prerequisite buildings because it can't relay on the
-	**	sidebar to keep track of this information.
+	**	AI players update flags using building quantity tracker.
+	**	Ensures consistent logic when determining building choices.
 	*/
 	*/
 	if (!IsHuman) {
 	if (!IsHuman) {
-		flags = OldBScan;
+		flags = 0;
+		for (int i = 0; i < 32; i++) {
+			if (BQuantity[i] > 0) {
+				flags |= (1 << i);
+			}
+		}
 	}
 	}
-#endif
 
 
 	int pre = type->Pre;
 	int pre = type->Pre;
 	if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER;
 	if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER;
@@ -972,6 +974,12 @@ void HouseClass::AI(void)
 	if (IsToDie && BorrowedTime.Expired()) {
 	if (IsToDie && BorrowedTime.Expired()) {
 		IsToDie = false;
 		IsToDie = false;
 		Blowup_All();
 		Blowup_All();
+
+		// MBL 07.15.2020 - Steve made this change for RA, so also applying here to TD
+		// See Change 737595 by Steve_Tall@STEVET3-VICTORY-H on 2020/07/10 13:40:02
+		if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
+			MPlayer_Defeated();
+		}
 	}
 	}
 
 
 	/*
 	/*
@@ -1424,7 +1432,7 @@ void HouseClass::AI(void)
 	}
 	}
 #endif
 #endif
 
 
-	if (GameToPlay != GAME_NORMAL) {
+	if (GameToPlay != GAME_NORMAL && Class->House != HOUSE_JP) {
 		Check_Pertinent_Structures();
 		Check_Pertinent_Structures();
 	}
 	}
 
 
@@ -1664,8 +1672,15 @@ void HouseClass::AI(void)
 void HouseClass::Attacked(BuildingClass* source)
 void HouseClass::Attacked(BuildingClass* source)
 {
 {
 	Validate();
 	Validate();
-	if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
+
+	bool expired = SpeakAttackDelay.Expired();
+	bool spoke = false;
+
+	// if (SpeakAttackDelay.Expired() && PlayerPtr->Class->House == Class->House) {
+	if (expired && PlayerPtr->Class->House == Class->House) {
+
 		Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
 		Speak(VOX_BASE_UNDER_ATTACK, NULL, source ? source->Center_Coord() : 0);
+		spoke = true;
 
 
 		// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
 		// MBL 06.13.2020 - Timing change from 2 minute cooldown, per https://jaas.ea.com/browse/TDRA-6784
 		// SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
 		// SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
@@ -1680,6 +1695,23 @@ void HouseClass::Attacked(BuildingClass* source)
 			HouseTriggers[Class->House][index]->Spring(EVENT_ATTACKED, Class->House);
 			HouseTriggers[Class->House][index]->Spring(EVENT_ATTACKED, Class->House);
 		}
 		}
 	}
 	}
+
+	// MBL 07.07.2020 - CNC Patch 3, fix for not working for all players in MP, per https://jaas.ea.com/browse/TDRA-7249
+	// Separated to here as did not want to change any logic around the HouseTriggers[] Spring events
+	//
+	if (expired == true && spoke == false) 
+	{
+		if (GameToPlay != GAME_NORMAL) // Multiplayer
+		{
+			Speak(VOX_BASE_UNDER_ATTACK, this);
+			spoke = true;
+	
+			// SpeakAttackDelay.Set(Options.Normalize_Delay(SPEAK_DELAY)); // 2 minutes
+			// SpeakAttackDelay.Set(Options.Normalize_Delay(TICKS_PER_MINUTE/2)); // 30 seconds as requested
+			SpeakAttackDelay.Set(Options.Normalize_Delay( (TICKS_PER_MINUTE/2)+(TICKS_PER_SECOND*5) )); // Tweaked for accuracy
+		}
+	}
+	// END MBL 07.07.2020
 }
 }
 
 
 
 
@@ -4051,6 +4083,13 @@ void HouseClass::MPlayer_Defeated(void)
 		}
 		}
 	}
 	}
 
 
+	/*
+	**	Remove any one-time superweapons the player might have.
+	*/
+	IonCannon.Remove(true);
+	AirStrike.Remove(true);
+	NukeStrike.Remove(true);
+
 	/*------------------------------------------------------------------------
 	/*------------------------------------------------------------------------
 	If this is me:
 	If this is me:
 	- Set MPlayerObiWan, so I can only send messages to all players, and
 	- Set MPlayerObiWan, so I can only send messages to all players, and
@@ -4784,6 +4823,13 @@ void HouseClass::Check_Pertinent_Structures(void)
 		return;
 		return;
 	}
 	}
 
 
+	// MBL 07.15.2020 - Prevention of recent issue with constant "player defeated logic" and message to client spamming
+	// Per https://jaas.ea.com/browse/TDRA-7433
+	//
+	if (IsDefeated) {
+		return;
+	}
+
 	bool any_good_buildings = false;
 	bool any_good_buildings = false;
 	
 	
 	for (int index = 0; index < Buildings.Count(); index++) {
 	for (int index = 0; index < Buildings.Count(); index++) {

+ 1 - 1
TIBERIANDAWN/IDATA.CPP

@@ -430,7 +430,7 @@ static InfantryTypeClass const E5(
 	70,							// Strength of infantry (in damage points).
 	70,							// Strength of infantry (in damage points).
 	1,								// Sight range.
 	1,								// Sight range.
 	300,							// Cost of infantry (in credits).
 	300,							// Cost of infantry (in credits).
-	99,							// Scenario when they first appear.
+	98,							// Scenario when they first appear.
 	80,10,						// Risk/Reward of this infantry unit.
 	80,10,						// Risk/Reward of this infantry unit.
 	HOUSEF_MULTI1|
 	HOUSEF_MULTI1|
 	HOUSEF_MULTI2|
 	HOUSEF_MULTI2|

+ 12 - 4
TIBERIANDAWN/INFANTRY.CPP

@@ -882,7 +882,7 @@ void InfantryClass::Look(bool incremental)
 
 
 	if (!IsInLimbo) {
 	if (!IsInLimbo) {
 		//if (IsOwnedByPlayer) {			// Changed for multiple player mapping. ST - 3/6/2019 1:27PM
 		//if (IsOwnedByPlayer) {			// Changed for multiple player mapping. ST - 3/6/2019 1:27PM
-		if (House->IsHuman) {
+		if (House->IsHuman || GameToPlay != GAME_NORMAL) {
 			sight = Class->SightRange;
 			sight = Class->SightRange;
 
 
 			if (sight) {
 			if (sight) {
@@ -1098,7 +1098,14 @@ void InfantryClass::AI(void)
 	**		run in circles, scream, and shout.
 	**		run in circles, scream, and shout.
 	*/
 	*/
 	if (Class->IsFraidyCat && Fear > FEAR_ANXIOUS && !IsDriving && !Target_Legal(NavCom)) {
 	if (Class->IsFraidyCat && Fear > FEAR_ANXIOUS && !IsDriving && !Target_Legal(NavCom)) {
-		Scatter(true);
+		Scatter(0);
+	}
+
+	/*
+	**	Scatter infantry off buildings in guard modes.
+	*/
+	if (!IsTethered && !IsFiring && !IsDriving && !IsRotating && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord_Cell(Coord)].Cell_Building() != NULL) {
+		Scatter(0, true, true);
 	}
 	}
 
 
 	/*
 	/*
@@ -1659,7 +1666,7 @@ MoveType InfantryClass::Can_Enter_Cell(CELL cell, FacingType ) const
 					**	Cloaked enemy objects are not considered if this is a Find_Path()
 					**	Cloaked enemy objects are not considered if this is a Find_Path()
 					**	call.
 					**	call.
 					*/
 					*/
-					if (!obj->Is_Techno() || ((TechnoClass *)obj)->Cloak != CLOAKED) {
+					if (!obj->Is_Techno() || !((TechnoClass *)obj)->Is_Cloaked(this)) {
 
 
 						/*
 						/*
 						**	Any non-allied blockage is considered impassible if the infantry
 						**	Any non-allied blockage is considered impassible if the infantry
@@ -2447,7 +2454,8 @@ bool InfantryClass::Unlimbo(COORDINATE coord, DirType facing)
 		**	If there is no sight range, then this object isn't discovered by the player unless
 		**	If there is no sight range, then this object isn't discovered by the player unless
 		**	it actually appears in a cell mapped by the player.
 		**	it actually appears in a cell mapped by the player.
 		*/
 		*/
-		if (Class->SightRange == 0) {
+		if (Class->SightRange == 0 && GameToPlay == GAME_NORMAL && !House->IsHuman && !Map[Coord_Cell(coord)].Is_Visible(PlayerPtr)) {
+			IsDiscoveredByPlayerMask &= ~(1 << (int)PlayerPtr->Class->House);
 			IsDiscoveredByPlayer = false;
 			IsDiscoveredByPlayer = false;
 		}
 		}
 
 

+ 40 - 0
TIBERIANDAWN/INI.CPP

@@ -321,6 +321,9 @@ bool Read_Scenario_Ini(char *root, bool fresh)
 #ifdef NEWMENU
 #ifdef NEWMENU
 		if (Scenario <= 15) {
 		if (Scenario <= 15) {
 			BuildLevel = Scenario;
 			BuildLevel = Scenario;
+		} else if (_stricmp(ScenarioName, "scg30ea") == 0 || _stricmp(ScenarioName, "scg90ea") == 0 || _stricmp(ScenarioName, "scb22ea") == 0) {
+			// N64 missions require build level 15
+			BuildLevel = 15;
 		} else {
 		} else {
 			BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", Scenario, buffer);
 			BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", Scenario, buffer);
 		}
 		}
@@ -444,6 +447,9 @@ bool Read_Scenario_Ini(char *root, bool fresh)
 	UnitClass::Read_INI(buffer);
 	UnitClass::Read_INI(buffer);
 	Call_Back();
 	Call_Back();
 
 
+	AircraftClass::Read_INI(buffer);
+	Call_Back();
+
 	/*
 	/*
 	**	Read in and place the infantry units (all sides).
 	**	Read in and place the infantry units (all sides).
 	*/
 	*/
@@ -534,6 +540,8 @@ bool Read_Scenario_Ini(char *root, bool fresh)
 	**		NOD7A cell 2795 - LAND_ROCK
 	**		NOD7A cell 2795 - LAND_ROCK
 	**		NOD09A - delete airstrike trigger when radar destroyed
 	**		NOD09A - delete airstrike trigger when radar destroyed
 	**		NOD10B cell 2015 - LAND_ROCK
 	**		NOD10B cell 2015 - LAND_ROCK
+	**		NOD13B - trigger AI production when the player reaches the transports
+	**		NOD13C - delete airstrike trigger when radar destroyed
 	*/
 	*/
 	if (_stricmp(ScenarioName, "scb07ea") == 0) {
 	if (_stricmp(ScenarioName, "scb07ea") == 0) {
 		Map[(CELL)2795].Override_Land_Type(LAND_ROCK);
 		Map[(CELL)2795].Override_Land_Type(LAND_ROCK);
@@ -553,6 +561,30 @@ bool Read_Scenario_Ini(char *root, bool fresh)
 	if (_stricmp(ScenarioName, "scb10eb") == 0) {
 	if (_stricmp(ScenarioName, "scb10eb") == 0) {
 		Map[(CELL)2015].Override_Land_Type(LAND_ROCK);
 		Map[(CELL)2015].Override_Land_Type(LAND_ROCK);
 	}
 	}
+	if (_stricmp(ScenarioName, "scb13eb") == 0) {
+		TriggerClass* prod = new TriggerClass();
+		prod->Set_Name("prod");
+		prod->Event = EVENT_PLAYER_ENTERED;
+		prod->Action = TriggerClass::ACTION_BEGIN_PRODUCTION;
+		prod->House = HOUSE_BAD;
+
+		CellTriggers[276] = prod; prod->AttachCount++;
+		CellTriggers[340] = prod; prod->AttachCount++;
+		CellTriggers[404] = prod; prod->AttachCount++;
+		CellTriggers[468] = prod; prod->AttachCount++;
+	}
+	if (_stricmp(ScenarioName, "scb13ec") == 0) {
+		for (int index = 0; index < Buildings.Count(); ++index) {
+			BuildingClass* building = Buildings.Ptr(index);
+			if (building != NULL && building->Owner() == HOUSE_GOOD && *building == STRUCT_RADAR && building->Trigger == NULL) {
+				building->Trigger = TriggerClass::As_Pointer("delx");
+				if (building->Trigger) {
+					building->Trigger->AttachCount++;
+				}
+				break;
+			}
+		}
+	}
 
 
 	/*
 	/*
 	**	Scenario fix-up (applied on loaded games as well)
 	**	Scenario fix-up (applied on loaded games as well)
@@ -844,6 +876,9 @@ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const
 	UnitClass::Read_INI(buffer);
 	UnitClass::Read_INI(buffer);
 	Call_Back();
 	Call_Back();
 
 
+	AircraftClass::Read_INI(buffer);
+	Call_Back();
+
 	/*
 	/*
 	**	Read in and place the infantry units (all sides).
 	**	Read in and place the infantry units (all sides).
 	*/
 	*/
@@ -929,6 +964,11 @@ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const
 	Map.Overpass();
 	Map.Overpass();
 	Call_Back();
 	Call_Back();
 
 
+	/*
+	**	Scenario fix-up (applied on loaded games as well)
+	*/
+	Fixup_Scenario();
+
 	/*
 	/*
 	**	Multi-player last-minute fixups:
 	**	Multi-player last-minute fixups:
 	**	- If computer players are disabled, remove all computer-owned houses
 	**	- If computer players are disabled, remove all computer-owned houses

+ 4 - 3
TIBERIANDAWN/LOGIC.CPP

@@ -190,6 +190,7 @@ void LogicClass::AI(void)
 	*/
 	*/
 	for (index = 0; index < Count(); index++) {
 	for (index = 0; index < Count(); index++) {
 		ObjectClass * obj = (*this)[index];
 		ObjectClass * obj = (*this)[index];
+		int count = Count();
 
 
 		obj->AI();
 		obj->AI();
 
 
@@ -197,9 +198,9 @@ void LogicClass::AI(void)
 		**	If the object was destroyed in the process of performing its AI, then
 		**	If the object was destroyed in the process of performing its AI, then
 		**	adjust the index so that no object gets skipped.
 		**	adjust the index so that no object gets skipped.
 		*/
 		*/
-		if (obj != (*this)[index]) {
-//		if (!obj->IsActive) {
-			index--;
+		int count_diff = Count() - count;
+		if (count_diff < 0) {
+			index += count_diff;
 		}
 		}
 	}
 	}
 
 

+ 29 - 10
TIBERIANDAWN/MAP.CPP

@@ -362,11 +362,9 @@ void MapClass::Sight_From(HouseClass *house, CELL cell, int sightrange, bool inc
 		**	adjacent cells as well. For full scans, just update
 		**	adjacent cells as well. For full scans, just update
 		**	the cell itself.
 		**	the cell itself.
 		*/
 		*/
-		if (!(*this)[newcell].Is_Mapped(house)) {
-			// Pass the house through, instead of assuming it's the local player. ST - 3/6/2019 10:26AM
-			//Map.Map_Cell(newcell, PlayerPtr);
-			Map.Map_Cell(newcell, house, true);
-		}
+		// Pass the house through, instead of assuming it's the local player. ST - 3/6/2019 10:26AM
+		//Map.Map_Cell(newcell, PlayerPtr);
+		Map.Map_Cell(newcell, house, true);
 	}
 	}
 }
 }
 
 
@@ -950,6 +948,15 @@ void MapClass::Logic(void)
 	if (TiberiumScan >= MAP_CELL_TOTAL) {
 	if (TiberiumScan >= MAP_CELL_TOTAL) {
 		int tries = 1;
 		int tries = 1;
 		if (Special.IsTFast || GameToPlay != GAME_NORMAL) tries = 2;
 		if (Special.IsTFast || GameToPlay != GAME_NORMAL) tries = 2;
+		
+		/*
+		** Use the Tiberium setting as a multiplier on growth rate. ST - 7/1/2020 3:05PM
+		*/
+		if (GameToPlay == GAME_GLYPHX_MULTIPLAYER) {
+			if (MPlayerTiberium > 1) {
+				tries += (MPlayerTiberium - 1) << 1;
+			}
+		}
 		TiberiumScan = 0;
 		TiberiumScan = 0;
 		IsForwardScan = (IsForwardScan == false);
 		IsForwardScan = (IsForwardScan == false);
 
 
@@ -958,11 +965,17 @@ void MapClass::Logic(void)
 		*/
 		*/
 		if (TiberiumGrowthCount) {
 		if (TiberiumGrowthCount) {
 			for (int i = 0; i < tries; i++) {
 			for (int i = 0; i < tries; i++) {
-				CELL cell = TiberiumGrowth[Random_Pick(0, TiberiumGrowthCount-1)];
+				int pick = Random_Pick(0, TiberiumGrowthCount-1);
+				CELL cell = TiberiumGrowth[pick];
 				CellClass * newcell = &(*this)[cell];
 				CellClass * newcell = &(*this)[cell];
 				if (newcell->Land_Type() == LAND_TIBERIUM && newcell->OverlayData < 12-1) {
 				if (newcell->Land_Type() == LAND_TIBERIUM && newcell->OverlayData < 12-1) {
 					newcell->OverlayData++;
 					newcell->OverlayData++;
 				}
 				}
+				TiberiumGrowth[pick] = TiberiumGrowth[TiberiumGrowthCount - 1];
+				TiberiumGrowthCount--;
+				if (TiberiumGrowthCount <= 0) {
+					break;
+				}
 			}
 			}
 		}
 		}
 		TiberiumGrowthCount = 0;
 		TiberiumGrowthCount = 0;
@@ -972,7 +985,8 @@ void MapClass::Logic(void)
 		*/
 		*/
 		if (TiberiumSpreadCount) {
 		if (TiberiumSpreadCount) {
 			for (int i = 0; i < tries; i++) {
 			for (int i = 0; i < tries; i++) {
-				CELL cell = TiberiumSpread[Random_Pick(0, TiberiumSpreadCount-1)];
+				int pick = Random_Pick(0, TiberiumSpreadCount-1);
+				CELL cell = TiberiumSpread[pick];
 
 
 				/*
 				/*
 				**	Find a pseudo-random adjacent cell that doesn't contain any tiberium.
 				**	Find a pseudo-random adjacent cell that doesn't contain any tiberium.
@@ -1003,6 +1017,11 @@ void MapClass::Logic(void)
 						}
 						}
 					}
 					}
 				}
 				}
+				TiberiumSpread[pick] = TiberiumSpread[TiberiumSpreadCount - 1];
+				TiberiumSpreadCount--;
+				if (TiberiumSpreadCount <= 0) {
+					break;
+				}
 			}
 			}
 		}
 		}
 		TiberiumSpreadCount = 0;
 		TiberiumSpreadCount = 0;
@@ -1400,10 +1419,10 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const
 			while (o) {
 			while (o) {
 
 
 				/*
 				/*
-				**	Special case check to ignore cloaked object if not owned by the player.
+				**	Special case check to ignore cloaked object if not allied with the player.
 				*/
 				*/
 				// Changed for multiplayer. ST - 3/13/2019 5:38PM
 				// Changed for multiplayer. ST - 3/13/2019 5:38PM
-				if (!o->Is_Techno() || ((TechnoClass *)o)->Is_Owned_By_Player() || ((TechnoClass *)o)->Cloak != CLOAKED) {
+				if (!o->Is_Techno() || !((TechnoClass *)o)->Is_Cloaked(PlayerPtr)) {
 				//if (!o->Is_Techno() || ((TechnoClass *)o)->IsOwnedByPlayer || ((TechnoClass *)o)->Cloak != CLOAKED) {
 				//if (!o->Is_Techno() || ((TechnoClass *)o)->IsOwnedByPlayer || ((TechnoClass *)o)->Cloak != CLOAKED) {
 					int d=-1;
 					int d=-1;
 					if (o->What_Am_I() == RTTI_BUILDING) {
 					if (o->What_Am_I() == RTTI_BUILDING) {
@@ -1429,7 +1448,7 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const
 		AircraftClass * aircraft = Aircraft.Ptr(index);
 		AircraftClass * aircraft = Aircraft.Ptr(index);
 
 
 		if (aircraft->In_Which_Layer() != LAYER_GROUND) {
 		if (aircraft->In_Which_Layer() != LAYER_GROUND) {
-			if (aircraft->Is_Owned_By_Player() || (aircraft->Cloak != CLOAKED)) {
+			if (!aircraft->Is_Cloaked(PlayerPtr)) {
 				int d = Distance(coord, Coord_Add(aircraft->Center_Coord(), XY_Coord(0, -Pixel_To_Lepton(aircraft->Altitude))));
 				int d = Distance(coord, Coord_Add(aircraft->Center_Coord(), XY_Coord(0, -Pixel_To_Lepton(aircraft->Altitude))));
 				if (d >= 0 && (!object || d < distance)) {
 				if (d >= 0 && (!object || d < distance)) {
 					distance = d;
 					distance = d;

+ 1 - 1
TIBERIANDAWN/MiscAsm.cpp

@@ -428,7 +428,7 @@ dxisbig:
 #if (0)
 #if (0)
 
 
 /*
 /*
-	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#57 $
+	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/MiscAsm.cpp#95 $
 ;***************************************************************************
 ;***************************************************************************
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;***************************************************************************
 ;***************************************************************************

+ 1 - 0
TIBERIANDAWN/OBJECT.CPP

@@ -564,6 +564,7 @@ COORDINATE ObjectClass::Center_Coord(void) const {return Coord;};
 COORDINATE ObjectClass::Render_Coord(void) const {return(Center_Coord());}
 COORDINATE ObjectClass::Render_Coord(void) const {return(Center_Coord());}
 COORDINATE ObjectClass::Docking_Coord(void) const {return(Center_Coord());}
 COORDINATE ObjectClass::Docking_Coord(void) const {return(Center_Coord());}
 COORDINATE ObjectClass::Sort_Y(void) const {return Coord;};
 COORDINATE ObjectClass::Sort_Y(void) const {return Coord;};
+FireDataType ObjectClass::Fire_Data(int which) const {return{Fire_Coord(which),0};}
 COORDINATE ObjectClass::Fire_Coord(int ) const {return Coord;};
 COORDINATE ObjectClass::Fire_Coord(int ) const {return Coord;};
 void ObjectClass::Record_The_Kill(TechnoClass * ) {};
 void ObjectClass::Record_The_Kill(TechnoClass * ) {};
 void ObjectClass::Do_Shimmer(void) {};
 void ObjectClass::Do_Shimmer(void) {};

+ 1 - 0
TIBERIANDAWN/OBJECT.H

@@ -172,6 +172,7 @@ class ObjectClass : public AbstractClass
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Center_Coord(void) const;
 		virtual COORDINATE Render_Coord(void) const;
 		virtual COORDINATE Render_Coord(void) const;
 		virtual COORDINATE Sort_Y(void) const;
 		virtual COORDINATE Sort_Y(void) const;
+		virtual FireDataType Fire_Data(int) const;
 		virtual COORDINATE Fire_Coord(int ) const;
 		virtual COORDINATE Fire_Coord(int ) const;
 
 
 		/*
 		/*

+ 2 - 2
TIBERIANDAWN/RADAR.CPP

@@ -235,7 +235,7 @@ bool RadarClass::Radar_Activate(int control)
 		case 0:
 		case 0:
 			if (Map.IsSidebarActive) {
 			if (Map.IsSidebarActive) {
 				if (IsRadarActive && !IsRadarDeactivating) {
 				if (IsRadarActive && !IsRadarDeactivating) {
-					Sound_Effect(VOC_RADAR_OFF);
+					// Sound_Effect(VOC_RADAR_OFF); // MBL 07.20.2020: These are never being sent to the client, so handled there; Disabling here for good measure.
 					IsRadarDeactivating = true;
 					IsRadarDeactivating = true;
 					IsRadarActive = false;
 					IsRadarActive = false;
 					if (IsRadarActivating == true) {
 					if (IsRadarActivating == true) {
@@ -252,7 +252,7 @@ bool RadarClass::Radar_Activate(int control)
 		case 1:
 		case 1:
 			if (Map.IsSidebarActive) {
 			if (Map.IsSidebarActive) {
 				if (!IsRadarActivating && !IsRadarActive) {
 				if (!IsRadarActivating && !IsRadarActive) {
-					Sound_Effect(VOC_RADAR_ON);
+					// Sound_Effect(VOC_RADAR_ON); // MBL 07.20.2020: These are never being sent to the client, so handled there; Disabling here for good measure.
 					IsRadarActivating = true;
 					IsRadarActivating = true;
 					if (IsRadarDeactivating == true) {
 					if (IsRadarDeactivating == true) {
 						IsRadarDeactivating = false;
 						IsRadarDeactivating = false;

+ 37 - 4
TIBERIANDAWN/REINF.CPP

@@ -338,6 +338,7 @@ bool Do_Reinforcements(TeamTypeClass *teamtype)
 		*/
 		*/
 		case SOURCE_AIR: {
 		case SOURCE_AIR: {
 			AircraftClass * thisone = (AircraftClass *)object;
 			AircraftClass * thisone = (AircraftClass *)object;
+			TARGET target = TARGET_NONE;
 			while (thisone) {
 			while (thisone) {
 				AircraftClass * next = (AircraftClass *)thisone->Next;
 				AircraftClass * next = (AircraftClass *)thisone->Next;
 
 
@@ -345,25 +346,57 @@ bool Do_Reinforcements(TeamTypeClass *teamtype)
 				**	Find a suitable map entry location. Cargo planes will try to find a cell that
 				**	Find a suitable map entry location. Cargo planes will try to find a cell that
 				**	exactly lines up with the airfield they will unload at.
 				**	exactly lines up with the airfield they will unload at.
 				*/
 				*/
-				CELL newcell;
+				COORDINATE newcoord;
+				long reinforcement_delay = -1;
 				ScenarioInit++;
 				ScenarioInit++;
-				newcell = Map.Calculated_Cell(HouseClass::As_Pointer(teamtype->House)->Edge, teamtype->House);
+				newcoord = Cell_Coord(Map.Calculated_Cell(HouseClass::As_Pointer(teamtype->House)->Edge, teamtype->House));
 				ScenarioInit--;
 				ScenarioInit--;
 				if (*thisone == AIRCRAFT_CARGO) {
 				if (*thisone == AIRCRAFT_CARGO) {
 					BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false);
 					BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false);
 					if (building) {
 					if (building) {
-						newcell = XY_Cell(Map.MapCellX+Map.MapCellWidth, Coord_YCell(building->Docking_Coord()+2));
+						COORDINATE docking_coord = building->Docking_Coord();
+						const int border_x = Cell_To_Lepton(Map.MapCellX + Map.MapCellWidth) | 0x80;
+						if (Special.ModernBalance) {
+							/*
+							** Cargo plane takes 5 seconds to reach the airstrip on Normal (1.5x legacy), or (75 / 10) seconds at speed.
+							** Assumes a 45ms (1000 / 45 ticks per second) service rate.
+							*/
+							const int speed = AircraftTypeClass::As_Reference(AIRCRAFT_CARGO).MaxSpeed;
+							int spawn_x = Coord_X(docking_coord) + ((speed * 1000 * 75) / (45 * 10));
+							if (spawn_x > border_x) {
+								reinforcement_delay = (spawn_x - border_x) / speed;
+								spawn_x = border_x;
+							}
+							newcoord = XY_Coord(spawn_x, Coord_Y(docking_coord));
+						} else {
+							newcoord = XY_Coord(border_x, Coord_Y(docking_coord));
+						}
+						if (teamtype->MissionCount) {
+							teamtype->MissionList[0].Argument = building->As_Target();
+						}
 					} 
 					} 
 				}
 				}
 				thisone->Next = 0;
 				thisone->Next = 0;
 
 
 				ScenarioInit++;
 				ScenarioInit++;
-				placed = thisone->Unlimbo(Cell_Coord(newcell), DIR_W);
+				placed = thisone->Unlimbo(newcoord, DIR_W);
+				if (Special.ModernBalance && reinforcement_delay >= 0) {
+					thisone->Set_Reinforcement_Delay(reinforcement_delay);
+				}
 				ScenarioInit--;
 				ScenarioInit--;
 				if (placed) {
 				if (placed) {
 					if (!team) {
 					if (!team) {
 						if (thisone->Class->IsFixedWing) {
 						if (thisone->Class->IsFixedWing) {
 							thisone->Assign_Mission(MISSION_HUNT);
 							thisone->Assign_Mission(MISSION_HUNT);
+							if (*thisone == AIRCRAFT_A10) {
+								/*
+								**	Groups of A10s always go after the same target initally.
+								*/
+								if (target == TARGET_NONE) {
+									target = thisone->Greatest_Threat(THREAT_NORMAL);
+								}
+								thisone->Assign_Target(target);
+							}
 						} else {
 						} else {
 							if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) {
 							if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) {
 								thisone->Assign_Mission(MISSION_UNLOAD);
 								thisone->Assign_Mission(MISSION_UNLOAD);

+ 18 - 0
TIBERIANDAWN/SCENARIO.CPP

@@ -799,4 +799,22 @@ void Fixup_Scenario(void)
 	} else {
 	} else {
 		((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_DRAGON;
 		((AircraftTypeClass&)AircraftTypeClass::As_Reference(AIRCRAFT_ORCA)).Primary = WEAPON_DRAGON;
 	}
 	}
+
+	/*
+	** Modern Balance
+	*/
+	if (Special.ModernBalance) {
+		/*
+		** GDI Weapons Factory has 30% more health.
+		*/
+		((BuildingTypeClass&)BuildingTypeClass::As_Reference(STRUCT_WEAP)).MaxStrength = 520;
+
+		/*
+		** Repair Pad is a pre-requisite for the APC.
+		*/
+		((UnitTypeClass&)UnitTypeClass::As_Reference(UNIT_APC)).Pre |= STRUCTF_REPAIR;
+	} else {
+		((BuildingTypeClass&)BuildingTypeClass::As_Reference(STRUCT_WEAP)).MaxStrength = 400;
+		((UnitTypeClass&)UnitTypeClass::As_Reference(UNIT_APC)).Pre &= ~STRUCTF_REPAIR;
+	}
 }
 }

+ 12 - 1
TIBERIANDAWN/SPECIAL.H

@@ -70,6 +70,7 @@ class SpecialClass
 			IsEarlyWin = false;
 			IsEarlyWin = false;
 			HealthBarDisplayMode = HB_SELECTED;
 			HealthBarDisplayMode = HB_SELECTED;
 			ResourceBarDisplayMode = RB_SELECTED;
 			ResourceBarDisplayMode = RB_SELECTED;
+			ModernBalance = false;
 		}
 		}
 
 
 		/*
 		/*
@@ -254,11 +255,21 @@ class SpecialClass
 			RB_ALWAYS,
 			RB_ALWAYS,
 		} ResourceBarDisplayMode;
 		} ResourceBarDisplayMode;
 
 
+		/*
+		** New modern balance setting.
+		*/
+		unsigned ModernBalance:1;
 
 
 		/*
 		/*
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		** Some additional padding in case we need to add data to the class and maintain backwards compatibility for save/load
 		*/
 		*/
-		unsigned char SaveLoadPadding[128];
+		// MBL 07.21.2020 - https://jaas.ea.com/browse/TDRA-7537
+		// Loading save files from Live and July Patch 3 Beta versions results in a crash
+		// Fixes issue from Change 738397 2020/07/17 14:06:03
+		// 
+		// unsigned char SaveLoadPadding[127];
+		//
+		unsigned char SaveLoadPadding[124]; 
 };
 };
 
 
 
 

+ 8 - 1
TIBERIANDAWN/TEAM.CPP

@@ -1266,7 +1266,14 @@ void TeamClass::Coordinate_Move(void)
 								unit->Assign_Mission(MISSION_MOVE);
 								unit->Assign_Mission(MISSION_MOVE);
 							}
 							}
 							if (unit->NavCom != Target) {
 							if (unit->NavCom != Target) {
-								unit->Assign_Destination(Target);
+								TARGET target = Target;
+								if (unit->What_Am_I() == RTTI_AIRCRAFT) {
+									AircraftClass* aircraft = (AircraftClass *)unit;
+									if (!aircraft->Class->IsFixedWing && !aircraft->Is_LZ_Clear(target)) {
+										target = aircraft->New_LZ(target, true);
+									}
+								}
+								unit->Assign_Destination(target);
 							}
 							}
 							finished = false;
 							finished = false;
 						} else {
 						} else {

+ 25 - 8
TIBERIANDAWN/TECHNO.CPP

@@ -1021,7 +1021,7 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window)
 	Clear_Redraw_Flag();
 	Clear_Redraw_Flag();
 
 
 	const bool show_health_bar =
 	const bool show_health_bar =
-		(Strength > 0) && ((Cloak != CLOAKED) || Is_Owned_By_Player()) &&
+		(Strength > 0) && !Is_Cloaked(PlayerPtr) &&
 		(Is_Selected_By_Player() ||
 		(Is_Selected_By_Player() ||
 		((Special.HealthBarDisplayMode == SpecialClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) ||
 		((Special.HealthBarDisplayMode == SpecialClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) ||
 		(Special.HealthBarDisplayMode == SpecialClass::HB_ALWAYS));
 		(Special.HealthBarDisplayMode == SpecialClass::HB_ALWAYS));
@@ -1191,7 +1191,8 @@ bool TechnoClass::In_Range(TARGET target, int which, bool reciprocal_check) cons
 		if (building) {
 		if (building) {
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 		}
 		}
-		if (::Distance(Fire_Coord(which), As_Coord(target)) <= range) {
+		FireDataType data = Fire_Data(which);
+		if (MAX(0, ::Distance(data.Center, As_Coord(target)) - data.Distance) <= range) {
 			return(true);
 			return(true);
 		}
 		}
 		
 		
@@ -1244,8 +1245,8 @@ bool TechnoClass::In_Range(ObjectClass const * target, int which, bool reciproca
 			BuildingClass const * building = (BuildingClass const *)target;
 			BuildingClass const * building = (BuildingClass const *)target;
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 			range += ((building->Class->Width() + building->Class->Height()) * (ICON_LEPTON_W / 4));
 		}
 		}
-
-		if (::Distance(Fire_Coord(which), target->Center_Coord()) <= range) {
+		FireDataType data = Fire_Data(which);
+		if (MAX(0, ::Distance(data.Center, target->Center_Coord()) - data.Distance) <= range) {
 			return(true);
 			return(true);
 		}
 		}
 		
 		
@@ -1367,7 +1368,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, int mask, int range, Techno
 	/*
 	/*
 	**	If the object is cloaked, then it isn't a legal target.
 	**	If the object is cloaked, then it isn't a legal target.
 	*/
 	*/
-	if (object->Cloak == CLOAKED) return(false);
+	if (object->Is_Cloaked(this)) return(false);
 
 
 	/*
 	/*
 	**	Determine if the target is theoretically allowed to be a target. If
 	**	Determine if the target is theoretically allowed to be a target. If
@@ -2103,7 +2104,7 @@ FireErrorType TechnoClass::Can_Fire(TARGET target, int which) const
 //Mono_Printf("Buildings[0]=%p.\n", Buildings.Raw_Ptr(0));
 //Mono_Printf("Buildings[0]=%p.\n", Buildings.Raw_Ptr(0));
 //Mono_Printf("Aircraft[0]=%p.\n", Aircraft.Raw_Ptr(0));
 //Mono_Printf("Aircraft[0]=%p.\n", Aircraft.Raw_Ptr(0));
 //Mono_Printf("object=%p, Strength=%d, IsActive=%d, IsInLimbo=%d.\n", object, (long)object->Strength, object->IsActive, object->IsInLimbo);Get_Key();
 //Mono_Printf("object=%p, Strength=%d, IsActive=%d, IsInLimbo=%d.\n", object, (long)object->Strength, object->IsActive, object->IsInLimbo);Get_Key();
-	if (object && /*(object->IsActive || GameToPlay != GAME_NORMAL) &&*/ object->Is_Techno() && ((TechnoClass *)object)->Cloak == CLOAKED) {
+	if (object && /*(object->IsActive || GameToPlay != GAME_NORMAL) &&*/ object->Is_Techno() && ((TechnoClass *)object)->Is_Cloaked(this)) {
 		return(FIRE_CANT);
 		return(FIRE_CANT);
 	}
 	}
 
 
@@ -2425,7 +2426,7 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which)
 		}
 		}
 #else
 #else
 		/*
 		/*
-		** Now need to reveal for any human player that is the target. ST - 3/13/2019 5:43PM
+		** Now need to reveal for any player that is the target. ST - 3/13/2019 5:43PM
 		*/
 		*/
 
 
 		ObjectClass *obj = As_Object(target);
 		ObjectClass *obj = As_Object(target);
@@ -2433,7 +2434,7 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which)
 			HousesType tgt_owner = obj->Owner();
 			HousesType tgt_owner = obj->Owner();
 
 
 			HouseClass *player = HouseClass::As_Pointer(tgt_owner);
 			HouseClass *player = HouseClass::As_Pointer(tgt_owner);
-			if (player != nullptr && player->IsHuman) {
+			if (player != nullptr) {
 				if ((!Is_Owned_By_Player(player) && !Is_Discovered_By_Player(player)) || !Map[Coord_Cell(Center_Coord())].Is_Mapped(House)) {
 				if ((!Is_Owned_By_Player(player) && !Is_Discovered_By_Player(player)) || !Map[Coord_Cell(Center_Coord())].Is_Mapped(House)) {
 					Map.Sight_From(player, Coord_Cell(Center_Coord()), 1, false);
 					Map.Sight_From(player, Coord_Cell(Center_Coord()), 1, false);
 				}
 				}
@@ -4442,6 +4443,7 @@ BuildingClass * TechnoClass::Find_Docking_Bay(StructType b, bool friendly) const
 				if (bestval == -1 || Distance(building) < bestval || building->IsLeader) {
 				if (bestval == -1 || Distance(building) < bestval || building->IsLeader) {
 					best = building;
 					best = building;
 					bestval = Distance(building);
 					bestval = Distance(building);
+					if (building->IsLeader) break;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -4616,7 +4618,22 @@ bool TechnoClass::Is_Owned_By_Player(HouseClass *player) const
 }			  
 }			  
 
 
 
 
+bool TechnoClass::Is_Cloaked(HousesType house) const
+{
+	return !House->Is_Ally(house) && (Cloak == CLOAKED);
+}
+
+
+bool TechnoClass::Is_Cloaked(HouseClass const * house) const
+{
+	return !House->Is_Ally(house) && (Cloak == CLOAKED);
+}
+
 
 
+bool TechnoClass::Is_Cloaked(ObjectClass const * object) const
+{
+	return !House->Is_Ally(object) && (Cloak == CLOAKED);
+}
 
 
 
 
 
 

+ 5 - 2
TIBERIANDAWN/TECHNO.H

@@ -280,8 +280,11 @@ class TechnoClass :	public RadioClass,
 		virtual int Weapon_Range(int which) const;
 		virtual int Weapon_Range(int which) const;
 		virtual bool Captured(HouseClass * newowner);
 		virtual bool Captured(HouseClass * newowner);
 		virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source);
 		virtual ResultType Take_Damage(int & damage, int distance, WarheadType warhead, TechnoClass * source);
-		bool  Evaluate_Cell(ThreatType method, int mask, CELL cell, int range, TechnoClass const ** object, int & value) const;
-		bool  Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value) const;
+		bool Evaluate_Cell(ThreatType method, int mask, CELL cell, int range, TechnoClass const ** object, int & value) const;
+		bool Evaluate_Object(ThreatType method, int mask, int range, TechnoClass const * object, int & value) const;
+		bool Is_Cloaked(HousesType house) const;
+		bool Is_Cloaked(HouseClass const * house) const;
+		bool Is_Cloaked(ObjectClass const * object) const;
 
 
 		/*
 		/*
 		**	AI.
 		**	AI.

+ 58 - 0
TIBERIANDAWN/TURRET.CPP

@@ -361,6 +361,64 @@ FireErrorType TurretClass::Can_Fire(TARGET target, int which) const
  * HISTORY:                                                                                    *
  * HISTORY:                                                                                    *
  *   12/28/1994 JLB : Created.                                                                 *
  *   12/28/1994 JLB : Created.                                                                 *
  *=============================================================================================*/
  *=============================================================================================*/
+FireDataType TurretClass::Fire_Data(int which) const
+{
+	COORDINATE coord = Center_Coord();
+	int dist = 0;
+
+	switch (Class->Type) {
+		case UNIT_GUNBOAT:
+			coord = Coord_Move(coord, PrimaryFacing.Current(), Pixel2Lepton[Class->TurretOffset]);
+			dist = 0x0060;
+			break;
+
+		case UNIT_ARTY:
+			coord = Coord_Move(coord, DIR_N, 0x0040);
+			dist = 0x0060;
+			break;
+
+		case UNIT_FTANK:
+			dist = 0x30;
+			break;
+
+		case UNIT_HTANK:
+			coord = Coord_Move(coord, DIR_N, 0x0040);
+			if (which == 0) {
+				dist = 0x00C0;
+			} else {
+				dist = 0x0008;
+			}
+			break;
+
+		case UNIT_LTANK:
+			coord = Coord_Move(coord, DIR_N, 0x0020);
+			dist = 0x00C0;
+			break;
+
+		case UNIT_MTANK:
+			coord = Coord_Move(coord, DIR_N, 0x0030);
+			dist = 0x00C0;
+			break;
+
+		case UNIT_APC:
+		case UNIT_JEEP:
+		case UNIT_BUGGY:
+			coord = Coord_Move(coord, DIR_N, 0x0030);
+			dist = 0x0030;
+			break;
+
+#ifdef PETROGLYPH_EXAMPLE_MOD
+		case UNIT_NUKE_TANK:
+			coord = Coord_Move(coord, DIR_N, 0x00A0);
+			dist = 0x00A0;
+			break;
+#endif //PETROGLYPH_EXAMPLE_MOD
+	}
+
+	return {coord,dist};
+}
+
+
 COORDINATE TurretClass::Fire_Coord(int which) const
 COORDINATE TurretClass::Fire_Coord(int which) const
 {
 {
 	COORDINATE coord = Center_Coord();
 	COORDINATE coord = Center_Coord();

+ 1 - 0
TIBERIANDAWN/TURRET.H

@@ -75,6 +75,7 @@ class TurretClass : public DriveClass
 		virtual FireErrorType Can_Fire(TARGET target, int which) const;
 		virtual FireErrorType Can_Fire(TARGET target, int which) const;
 		virtual bool Ok_To_Move(DirType facing);
 		virtual bool Ok_To_Move(DirType facing);
 		virtual void AI(void);
 		virtual void AI(void);
+		virtual FireDataType Fire_Data(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 		virtual COORDINATE Fire_Coord(int which) const;
 };
 };
 
 

+ 1 - 1
TIBERIANDAWN/TiberianDawn.vcxproj

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
   <ItemGroup Label="ProjectConfigurations">
     <ProjectConfiguration Include="Debug|Win32">
     <ProjectConfiguration Include="Debug|Win32">

+ 1 - 1
TIBERIANDAWN/TiberianDawn.vcxproj.filters

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
   <ItemGroup>
     <Filter Include="Source Files">
     <Filter Include="Source Files">

+ 25 - 5
TIBERIANDAWN/UNIT.CPP

@@ -390,7 +390,7 @@ void UnitClass::AI(void)
 	**	Delete this unit if it finds itself off the edge of the map and it is in
 	**	Delete this unit if it finds itself off the edge of the map and it is in
 	**	guard or other static mission mode.
 	**	guard or other static mission mode.
 	*/
 	*/
-	if (!Team && Mission == MISSION_GUARD && !Map.In_Radar(Coord_Cell(Coord))) {
+	if (!Team && Mission == MISSION_GUARD && MissionQueue == MISSION_NONE && !Map.In_Radar(Coord_Cell(Coord))) {
 		Stun();
 		Stun();
 		Delete_This();
 		Delete_This();
 		return;
 		return;
@@ -501,6 +501,13 @@ void UnitClass::AI(void)
 		}
 		}
 	}
 	}
 
 
+	/*
+	**	Scatter units off buildings in guard modes.
+	*/
+	if (!IsTethered && !IsFiring && !IsDriving && !IsRotating && (Mission == MISSION_GUARD || Mission == MISSION_GUARD_AREA) && MissionQueue == MISSION_NONE && Map[Coord_Cell(Coord)].Cell_Building() != NULL) {
+		Scatter(0, true, true);
+	}
+
 	/*
 	/*
 	**	A cloaked object that is carrying the flag will always shimmer.
 	**	A cloaked object that is carrying the flag will always shimmer.
 	*/
 	*/
@@ -1259,6 +1266,11 @@ void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET
 	Validate();
 	Validate();
 	if (mission == MISSION_HARVEST) {
 	if (mission == MISSION_HARVEST) {
 		ArchiveTarget = TARGET_NONE;
 		ArchiveTarget = TARGET_NONE;
+	} else if (mission == MISSION_ENTER) {
+		BuildingClass* building = As_Building(destination);
+		if (building != NULL && *building == STRUCT_REFINERY && building->In_Radio_Contact()) {
+			building->Transmit_Message(RADIO_OVER_OUT);
+		}
 	}
 	}
 	TarComClass::Player_Assign_Mission(mission, target, destination);
 	TarComClass::Player_Assign_Mission(mission, target, destination);
 }
 }
@@ -1817,7 +1829,7 @@ void UnitClass::Per_Cell_Process(bool center)
 		} else {
 		} else {
 			TechnoClass * contact = Contact_With_Whom();
 			TechnoClass * contact = Contact_With_Whom();
 			if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) {
 			if (Transmit_Message(RADIO_UNLOADED) == RADIO_RUN_AWAY) {
-				if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING) {
+				if (*this == UNIT_HARVESTER && contact && contact->What_Am_I() == RTTI_BUILDING && *((BuildingClass*)contact) != STRUCT_REPAIR) {
 					Assign_Mission(MISSION_HARVEST);
 					Assign_Mission(MISSION_HARVEST);
 				} else if (!Target_Legal(NavCom)) {
 				} else if (!Target_Legal(NavCom)) {
 					Scatter(0, true);
 					Scatter(0, true);
@@ -2308,7 +2320,7 @@ int UnitClass::Tiberium_Check(CELL &center, int x, int y)
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
 	//using function for IsVisible so we have different results for different players - JAS 2019/09/30
 	if ((GameToPlay != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Visible(PlayerPtr)))) {
 	if ((GameToPlay != GAME_NORMAL || (!IsOwnedByPlayer || Map[center].Is_Visible(PlayerPtr)))) {
 		if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) {
 		if (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) {
-			return(Map[center].OverlayData);
+			return(Map[center].OverlayData+1);
 		}
 		}
 	}
 	}
 	return(0);
 	return(0);
@@ -2333,6 +2345,7 @@ bool UnitClass::Goto_Tiberium(void)
 				int tiberium = 0;
 				int tiberium = 0;
 				int besttiberium = 0;
 				int besttiberium = 0;
 				for (int x = -radius; x <= radius; x++) {
 				for (int x = -radius; x <= radius; x++) {
+					cell = center;
 					tiberium = Tiberium_Check(cell, x, -radius);
 					tiberium = Tiberium_Check(cell, x, -radius);
 					if (tiberium > besttiberium) {
 					if (tiberium > besttiberium) {
 						bestcell = cell;
 						bestcell = cell;
@@ -2657,7 +2670,14 @@ int UnitClass::Mission_Harvest(void)
 		*/
 		*/
 		case LOOKING:
 		case LOOKING:
 			IsHarvesting = false;
 			IsHarvesting = false;
-			if (Goto_Tiberium()) {
+			/*
+			**	Slightly hacky; if TarCom is set then skip to finding home state.
+			*/
+			if (Target_Legal(TarCom)) {
+				Assign_Target(TARGET_NONE);
+				Status = FINDHOME;
+				return(1);
+			} else if (Goto_Tiberium()) {
 				IsHarvesting = true;
 				IsHarvesting = true;
 				Set_Rate(2);
 				Set_Rate(2);
 				Set_Stage(0);
 				Set_Stage(0);
@@ -2853,7 +2873,7 @@ void UnitClass::Look(bool incremental)
 {
 {
 	Validate();
 	Validate();
 	//if (!IsInLimbo && IsOwnedByPlayer) {				// Changed for mapping of multiple players
 	//if (!IsInLimbo && IsOwnedByPlayer) {				// Changed for mapping of multiple players
-	if (!IsInLimbo && House && House->IsHuman) {
+	if (!IsInLimbo && House && (House->IsHuman || GameToPlay != GAME_NORMAL)) {
 		int sight = Class->SightRange;
 		int sight = Class->SightRange;
 
 
 		if (sight) {
 		if (sight) {

+ 1 - 1
TIBERIANDAWN/WIN32LIB/DrawMisc.cpp

@@ -4841,7 +4841,7 @@ extern "C" int __cdecl Confine_Rect ( int * x , int * y , int w , int h , int wi
 
 
 
 
 /*
 /*
-; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#57 $
+; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp#95 $
 ;***************************************************************************
 ;***************************************************************************
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;***************************************************************************
 ;***************************************************************************

+ 1 - 1
TIBERIANDAWN/WIN32LIB/FACINGFF.h

@@ -321,7 +321,7 @@ int __cdecl Desired_Facing8(long x1, long y1, long x2, long y2);
 
 
 
 
 /*
 /*
-	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#57 $
+	; $Header: //depot/Projects/Mobius/QA/Project/Run/SOURCECODE/TIBERIANDAWN/WIN32LIB/FACINGFF.h#95 $
 ;***************************************************************************
 ;***************************************************************************
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;**   C O N F I D E N T I A L --- W E S T W O O D   A S S O C I A T E S   **
 ;***************************************************************************
 ;***************************************************************************