From ae72fce5ddd6a12bf48d00e0db8a400b821c2529 Mon Sep 17 00:00:00 2001 From: PG-SteveT <63470275+PG-SteveT@users.noreply.github.com> Date: Thu, 6 Aug 2020 09:44:54 -0700 Subject: [PATCH] August 6th Patch Update Accumulated DLL source code changes since June 22nd patch --- CnCRemastered.sln | 2 +- REDALERT/ADATA.CPP | 4 +- REDALERT/AIRCRAFT.CPP | 19 +++-- REDALERT/ANIM.CPP | 10 ++- REDALERT/BUILDING.CPP | 27 +++++-- REDALERT/CELL.CPP | 13 ++- REDALERT/DEFINES.H | 9 +++ REDALERT/DISPLAY.CPP | 4 +- REDALERT/DLLInterface.cpp | 84 +++++++++++++++++-- REDALERT/DLLInterface.h | 3 +- REDALERT/EVENT.CPP | 8 +- REDALERT/FOOT.CPP | 14 +++- REDALERT/HOUSE.CPP | 98 ++++++++++++++++++++--- REDALERT/INFANTRY.CPP | 14 +++- REDALERT/INICODE.CPP | 3 + REDALERT/INIT.CPP | 10 +++ REDALERT/LOGIC.CPP | 6 +- REDALERT/MAP.CPP | 20 +++-- REDALERT/MAP.H | 2 +- REDALERT/MiscAsm.cpp | 2 +- REDALERT/OBJECT.CPP | 8 ++ REDALERT/OBJECT.H | 1 + REDALERT/RADAR.CPP | 4 +- REDALERT/RedAlert.vcxproj.filters | 2 +- REDALERT/SCENARIO.CPP | 5 +- REDALERT/SPECIAL.CPP | 1 + REDALERT/SPECIAL.H | 14 +++- REDALERT/STARTUP.CPP | 4 + REDALERT/SUPER.CPP | 4 +- REDALERT/SUPER.H | 2 +- REDALERT/TECHNO.CPP | 89 ++++++++++++++------ REDALERT/TECHNO.H | 6 +- REDALERT/UNIT.CPP | 49 +++++++++--- REDALERT/VESSEL.CPP | 28 ++++++- REDALERT/VESSEL.H | 1 + REDALERT/WIN32LIB/DrawMisc.cpp | 2 +- TIBERIANDAWN/ADATA.CPP | 4 +- TIBERIANDAWN/AIRCRAFT.CPP | 43 ++++++++-- TIBERIANDAWN/AIRCRAFT.H | 10 ++- TIBERIANDAWN/ANIM.CPP | 17 +++- TIBERIANDAWN/BDATA.CPP | 2 +- TIBERIANDAWN/BUILDING.CPP | 83 +++++++++++++++---- TIBERIANDAWN/BUILDING.H | 1 + TIBERIANDAWN/CDATA.CPP | 2 +- TIBERIANDAWN/CELL.CPP | 42 +++++++--- TIBERIANDAWN/CELL.H | 2 +- TIBERIANDAWN/DEFINES.H | 9 +++ TIBERIANDAWN/DISPLAY.CPP | 16 ++-- TIBERIANDAWN/DLLInterface.cpp | 45 +++++++++-- TIBERIANDAWN/DLLInterface.h | 3 +- TIBERIANDAWN/DRIVE.CPP | 12 ++- TIBERIANDAWN/EVENT.CPP | 8 +- TIBERIANDAWN/FOOT.CPP | 18 ++++- TIBERIANDAWN/HOUSE.CPP | 62 ++++++++++++-- TIBERIANDAWN/IDATA.CPP | 2 +- TIBERIANDAWN/INFANTRY.CPP | 16 +++- TIBERIANDAWN/INI.CPP | 40 +++++++++ TIBERIANDAWN/LOGIC.CPP | 7 +- TIBERIANDAWN/MAP.CPP | 39 ++++++--- TIBERIANDAWN/MiscAsm.cpp | 2 +- TIBERIANDAWN/OBJECT.CPP | 1 + TIBERIANDAWN/OBJECT.H | 1 + TIBERIANDAWN/RADAR.CPP | 4 +- TIBERIANDAWN/REINF.CPP | 41 +++++++++- TIBERIANDAWN/SCENARIO.CPP | 18 +++++ TIBERIANDAWN/SPECIAL.H | 13 ++- TIBERIANDAWN/TEAM.CPP | 9 ++- TIBERIANDAWN/TECHNO.CPP | 33 ++++++-- TIBERIANDAWN/TECHNO.H | 7 +- TIBERIANDAWN/TURRET.CPP | 58 ++++++++++++++ TIBERIANDAWN/TURRET.H | 1 + TIBERIANDAWN/TiberianDawn.vcxproj | 2 +- TIBERIANDAWN/TiberianDawn.vcxproj.filters | 2 +- TIBERIANDAWN/UNIT.CPP | 30 +++++-- TIBERIANDAWN/WIN32LIB/DrawMisc.cpp | 2 +- TIBERIANDAWN/WIN32LIB/FACINGFF.h | 2 +- 76 files changed, 1071 insertions(+), 210 deletions(-) diff --git a/CnCRemastered.sln b/CnCRemastered.sln index 52d2e12..5a9f093 100644 --- a/CnCRemastered.sln +++ b/CnCRemastered.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.1022 diff --git a/REDALERT/ADATA.CPP b/REDALERT/ADATA.CPP index d3ab825..0910798 100644 --- a/REDALERT/ADATA.CPP +++ b/REDALERT/ADATA.CPP @@ -359,7 +359,7 @@ static AnimTypeClass const LZSmoke( 72, // Loop start frame number. 91, // Ending frame of loop back. -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. ANIM_NONE ); @@ -1127,7 +1127,7 @@ static AnimTypeClass const OilFieldBurn( 33, // Loop start frame number. 99, // Ending frame of loop back. 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. ANIM_NONE ); diff --git a/REDALERT/AIRCRAFT.CPP b/REDALERT/AIRCRAFT.CPP index 16e1a66..729ecd1 100644 --- a/REDALERT/AIRCRAFT.CPP +++ b/REDALERT/AIRCRAFT.CPP @@ -1948,9 +1948,17 @@ void AircraftClass::Enter_Idle_Mode(bool ) /* ** 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); - if (building != NULL && Transmit_Message(RADIO_HELLO, building) == RADIO_ROGER) { + if (building != NULL) { if (Class->IsFixedWing) { Status = 0; //BG - reset the mission status to avoid landing on the ground next to the airstrip if (IsLanding) { @@ -2367,7 +2375,7 @@ ActionType AircraftClass::What_Action(CELL cell) const ActionType action = FootClass::What_Action(cell); //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; } @@ -3068,7 +3076,7 @@ MoveType AircraftClass::Can_Enter_Cell(CELL cell, FacingType ) const if (occupier == NULL || !occupier->Is_Techno() || ((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)) ) ) { @@ -3128,9 +3136,10 @@ TARGET AircraftClass::Good_Fire_Location(TARGET target) const for (int face = 0; face < 255; face += 16) { COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r); 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 - 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; if (altcoord != 0) { dist = ::Distance(newcoord, altcoord); diff --git a/REDALERT/ANIM.CPP b/REDALERT/ANIM.CPP index 09796df..4c96011 100644 --- a/REDALERT/ANIM.CPP +++ b/REDALERT/ANIM.CPP @@ -578,7 +578,7 @@ IsTheaterShape = false; AnimClass::Unlimbo(coord); - VisibleFlags = 0xffff; + VisibleFlags = static_cast(-1); /* ** Drop zone smoke always reveals the map around itself. @@ -817,10 +817,10 @@ void AnimClass::AI(void) int damage = Accum; Accum -= damage; if (As_Object(xObject)->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) { - delete this; if (Target_Legal(VirtualAnimTarget)) { delete As_Animation(VirtualAnimTarget); } + delete this; return; } } @@ -1177,8 +1177,12 @@ void AnimClass::Detach(TARGET target, bool all) assert(IsActive); if (all) { - if (VirtualAnimTarget && VirtualAnimTarget == target) { + if (Target_Legal(VirtualAnimTarget) && VirtualAnimTarget == target) { VirtualAnimTarget = TARGET_NONE; + if (IsInvisible) { + IsToDelete = true; + Mark(MARK_UP); + } } if (xObject == target) { Map.Remove(this, In_Which_Layer()); diff --git a/REDALERT/BUILDING.CPP b/REDALERT/BUILDING.CPP index ac2b939..7f0481e 100644 --- a/REDALERT/BUILDING.CPP +++ b/REDALERT/BUILDING.CPP @@ -173,7 +173,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT case RADIO_CAN_LOAD: TechnoClass::Receive_Message(from, message, param); 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) { case STRUCT_AIRSTRIP: 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 && (ScenarioInit || !Is_Something_Attached())) { - return(RADIO_ROGER); + return((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE); } break; @@ -2572,13 +2572,21 @@ void BuildingClass::Grand_Opening(bool captured) ** to place it in a nearby location. */ 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 ** to the owner and then bail. */ - if (!unit->Unlimbo(Cell_Coord(cell), DIR_SW)) { + if (unit->IsInLimbo) { House->Refund_Money(unit->Class->Cost_Of()); 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); - } + #endif /* ** Destroy all attached objects. ST - 4/24/2020 9:38PM diff --git a/REDALERT/CELL.CPP b/REDALERT/CELL.CPP index e27b5cd..1060d7c 100644 --- a/REDALERT/CELL.CPP +++ b/REDALERT/CELL.CPP @@ -2807,9 +2807,18 @@ void CellClass::Flag_Create(void) { if (!CTFFlag) { 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; } } diff --git a/REDALERT/DEFINES.H b/REDALERT/DEFINES.H index 6dcc9ba..5e3f9cb 100644 --- a/REDALERT/DEFINES.H +++ b/REDALERT/DEFINES.H @@ -3615,6 +3615,15 @@ typedef enum OptionControlType : char { } 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) diff --git a/REDALERT/DISPLAY.CPP b/REDALERT/DISPLAY.CPP index a42e8d1..95df5e4 100644 --- a/REDALERT/DISPLAY.CPP +++ b/REDALERT/DISPLAY.CPP @@ -3146,9 +3146,9 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType & key) 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; } } diff --git a/REDALERT/DLLInterface.cpp b/REDALERT/DLLInterface.cpp index 4cfa6b6..87012a2 100644 --- a/REDALERT/DLLInterface.cpp +++ b/REDALERT/DLLInterface.cpp @@ -384,6 +384,8 @@ bool MPSuperWeaponDisable = false; bool ShareAllyVisibility = true; bool UseGlyphXStartLocations = true; +SpecialClass* SpecialBackup = NULL; + int GetRandSeed() { @@ -800,6 +802,8 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scena Special.IsEarlyWin = game_options.DestroyStructures; + Special.ModernBalance = game_options.ModernBalance; + /* ** Enable Counterstrike/Aftermath units */ @@ -844,6 +848,13 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Set_Multiplayer_Data(int scena */ Rule.IsSmartDefense = true; + /* + ** Backup special + */ + if (SpecialBackup != NULL) { + memcpy(SpecialBackup, &Special, sizeof(SpecialClass)); + } + 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 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. ** 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]); } + /* + ** Restore special from backup + */ + if (SpecialBackup != NULL) { + memcpy(&Special, SpecialBackup, sizeof(SpecialClass)); + } + #ifdef FIXIT_CSII // checked - ajw 9/28/98 TimeQuake = PendingTimeQuake; PendingTimeQuake = false; @@ -1865,6 +1883,15 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Advance_Instance(uint64 player //Sync_Delay(); //DLLExportClass::Set_Event_Callback(NULL); 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); } @@ -1910,6 +1937,12 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, const cha } result = Load_Game(file_path_and_name); + + // MBL 07.21.2020 + if (result == false) + { + return false; + } DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true); Set_Logic_Page(SeenBuff); @@ -2130,6 +2163,11 @@ void DLLExportClass::Init(void) CurrentLocalPlayerIndex = 0; 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) { + delete SpecialBackup; + SpecialBackup = NULL; + for (int i=0 ; iObjects[TotalObjectCount + CurrentDrawCount]; + memset(&new_object, 0, sizeof(new_object)); Convert_Type(object, new_object); if (new_object.Type == UNKNOWN) { return; @@ -4394,6 +4436,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i return false; } + memset(&sidebar_entry, 0, sizeof(sidebar_entry)); + sidebar_entry.AssetName[0] = 0; sidebar_entry.Type = UNKNOWN; 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; 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.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); @@ -4549,6 +4593,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i return false; } + memset(&sidebar_entry, 0, sizeof(sidebar_entry)); + sidebar_entry.AssetName[0] = 0; sidebar_entry.Type = UNKNOWN; 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; 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.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); @@ -8480,10 +8532,16 @@ bool DLLExportClass::Save(Pipe & pipe) 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(¬_allow_super_weapons, sizeof(not_allow_super_weapons)); + /* ** Room for save game expansion */ - unsigned char padding[4096]; + unsigned char padding[4095]; memset(padding, 0, sizeof(padding)); pipe.Put(padding, sizeof(padding)); @@ -8566,7 +8624,23 @@ bool DLLExportClass::Load(Straw & file) 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(¬_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)) { return false; diff --git a/REDALERT/DLLInterface.h b/REDALERT/DLLInterface.h index 7e4525f..a9e643d 100644 --- a/REDALERT/DLLInterface.h +++ b/REDALERT/DLLInterface.h @@ -732,7 +732,7 @@ struct CNCMultiplayerOptionsStruct { int MPlayerCount; // # of human players in this game int MPlayerBases; // 1 = bases are on for this scenario 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 MPlayerGhosts; // 1 = houses with no players will still play int MPlayerSolo; // 1 = allows a single-player net game @@ -744,6 +744,7 @@ struct CNCMultiplayerOptionsStruct { bool MPlayerAftermathUnits; bool CaptureTheFlag; bool DestroyStructures; // New early win condition via destroying all a player's structures + bool ModernBalance; }; diff --git a/REDALERT/EVENT.CPP b/REDALERT/EVENT.CPP index 19eca6f..9e84f7d 100644 --- a/REDALERT/EVENT.CPP +++ b/REDALERT/EVENT.CPP @@ -603,7 +603,7 @@ void EventClass::Execute(void) //2019/09/19 JAS - Visibility needs to be determined per player if (Data.Anim.Owner == HOUSE_NONE || Data.Anim.What != ANIM_MOVE_FLASH) { - anim->Set_Visible_Flags(0xffff); + anim->Set_Visible_Flags(static_cast(-1)); } else { @@ -761,6 +761,12 @@ void EventClass::Execute(void) techno->Assign_Target(TARGET_NONE); techno->Assign_Destination(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 { if (q && techno->Is_Foot()) { ((FootClass *)techno)->Queue_Navigation_List(Data.MegaMission.Destination.As_TARGET()); diff --git a/REDALERT/FOOT.CPP b/REDALERT/FOOT.CPP index 3ef0bdb..e5c005d 100644 --- a/REDALERT/FOOT.CPP +++ b/REDALERT/FOOT.CPP @@ -1741,7 +1741,19 @@ int FootClass::Mission_Enter(void) ** Since there is no potential object to enter, then abort this ** 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)); diff --git a/REDALERT/HOUSE.CPP b/REDALERT/HOUSE.CPP index 63d6230..7207719 100644 --- a/REDALERT/HOUSE.CPP +++ b/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 ** being base building. */ - if (IsBaseBuilding || IQ >= Rule.IQProduction) { + if (!IsHuman && (IsBaseBuilding || IQ >= Rule.IQProduction)) { IsBaseBuilding = true; IsStarted = true; IsAlerted = true; @@ -957,6 +957,9 @@ void HouseClass::AI(void) if (IsToDie && BorrowedTime == 0) { IsToDie = false; 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. - 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( VQuantity[ VESSEL_SS ] > 0 ) // Includes count of VESSEL_MISSILESUBs. ajw @@ -1818,12 +1821,18 @@ void HouseClass::Attacked(BuildingClass* source) { assert(Houses.ID(this) == ID); + bool expired = SpeakAttackDelay == 0; + bool spoke = false; + #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 - if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { + // if (SpeakAttackDelay == 0 && PlayerPtr->Class->House == Class->House) { + if (expired && PlayerPtr->Class->House == Class->House) { #endif 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 // 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); } } + + // 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: ** - Set MPlayerObiWan, so I can only send messages to all players, and @@ -4099,7 +4131,14 @@ void HouseClass::Blowup_All(void) */ count = 0; 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); count++; if (count > 5 && uptr->IsActive) { @@ -4118,7 +4157,13 @@ void HouseClass::Blowup_All(void) if (::Aircraft.Ptr(i)->House == this && !::Aircraft.Ptr(i)->IsInLimbo) { 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); if (!aptr->IsActive) { i--; @@ -4133,7 +4178,13 @@ void HouseClass::Blowup_All(void) if (::Vessels.Ptr(i)->House == this && !::Vessels.Ptr(i)->IsInLimbo) { 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); if (!vptr->IsActive) { i--; @@ -4151,7 +4202,14 @@ void HouseClass::Blowup_All(void) count = 0; 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); count++; if (count > 5) { @@ -4174,7 +4232,14 @@ void HouseClass::Blowup_All(void) count = 0; 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); iptr->Take_Damage(damage, 0, warhead, NULL, true); @@ -8013,6 +8078,13 @@ void HouseClass::Check_Pertinent_Structures(void) 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; 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->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; + } } } } diff --git a/REDALERT/INFANTRY.CPP b/REDALERT/INFANTRY.CPP index edcbf75..60252b5 100644 --- a/REDALERT/INFANTRY.CPP +++ b/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 */ 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 @@ -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 (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 if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { 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() ** 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 @@ -3958,6 +3961,13 @@ void InfantryClass::Movement_AI(void) // 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 ** that it can't travel to. In such a case, abort the movement process by clearing diff --git a/REDALERT/INICODE.CPP b/REDALERT/INICODE.CPP index 705ab26..65bff85 100644 --- a/REDALERT/INICODE.CPP +++ b/REDALERT/INICODE.CPP @@ -286,6 +286,9 @@ bool Read_Scenario_INI_Write_INB( char *root, bool fresh) UnitClass::Read_INI(buffer); Call_Back(); + AircraftClass::Read_INI(buffer); + Call_Back(); + VesselClass::Read_INI(buffer); Call_Back(); diff --git a/REDALERT/INIT.CPP b/REDALERT/INIT.CPP index b1ed212..650a69b 100644 --- a/REDALERT/INIT.CPP +++ b/REDALERT/INIT.CPP @@ -434,6 +434,16 @@ bool Init_Game(int , char * []) ChronalVortex.Stop(); 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); } diff --git a/REDALERT/LOGIC.CPP b/REDALERT/LOGIC.CPP index ed4b6fa..6b79c03 100644 --- a/REDALERT/LOGIC.CPP +++ b/REDALERT/LOGIC.CPP @@ -316,6 +316,7 @@ void LogicClass::AI(void) */ for (index = 0; index < Count(); index++) { ObjectClass * obj = (*this)[index]; + int count = Count(); BStart(BENCH_AI); obj->AI(); @@ -354,8 +355,9 @@ void LogicClass::AI(void) ** If the object was destroyed in the process of performing its AI, then ** 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(); diff --git a/REDALERT/MAP.CPP b/REDALERT/MAP.CPP index 43f1f5f..193f6db 100644 --- a/REDALERT/MAP.CPP +++ b/REDALERT/MAP.CPP @@ -1026,6 +1026,16 @@ void MapClass::Logic(void) ** Tiberium cells that can grow or spread. */ 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); int index; for (index = TiberiumScan; index < MAP_CELL_TOTAL; index++) { @@ -1416,11 +1426,11 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const 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 //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; if (o->What_Am_I() == RTTI_BUILDING) { d = Distance(coord, Cell_Coord(newcell)); @@ -1445,7 +1455,7 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const AircraftClass * aircraft = Aircraft.Ptr(index); 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))); if (d >= 0 && (!object || d < distance)) { distance = d; @@ -1683,7 +1693,7 @@ int MapClass::Zone_Span(CELL cell, int zone, MZoneType check) * HISTORY: * * 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]; int count = 0; @@ -1767,7 +1777,7 @@ CELL MapClass::Nearby_Location(CELL cell, SpeedType speed, int zone, MZoneType c } if (count > 0) { - return(topten[Frame % count]); + return(topten[(Frame+locationmod) % count]); } return(0); } diff --git a/REDALERT/MAP.H b/REDALERT/MAP.H index 5820187..2ca2661 100644 --- a/REDALERT/MAP.H +++ b/REDALERT/MAP.H @@ -61,7 +61,7 @@ class MapClass: public GScreenClass CELL Pick_Random_Location(void) const; int Intact_Bridge_Count(void) 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; virtual void Detach(ObjectClass * ) {}; int Cell_Region(CELL cell); diff --git a/REDALERT/MiscAsm.cpp b/REDALERT/MiscAsm.cpp index 0373f1b..adcf35d 100644 --- a/REDALERT/MiscAsm.cpp +++ b/REDALERT/MiscAsm.cpp @@ -428,7 +428,7 @@ dxisbig: #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 ** ;*************************************************************************** diff --git a/REDALERT/OBJECT.CPP b/REDALERT/OBJECT.CPP index 0ee931a..335ed41 100644 --- a/REDALERT/OBJECT.CPP +++ b/REDALERT/OBJECT.CPP @@ -627,6 +627,14 @@ COORDINATE ObjectClass::Sort_Y(void) const * HISTORY: * * 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 { assert(this != 0); diff --git a/REDALERT/OBJECT.H b/REDALERT/OBJECT.H index 289f04c..c553016 100644 --- a/REDALERT/OBJECT.H +++ b/REDALERT/OBJECT.H @@ -177,6 +177,7 @@ class ObjectClass : public AbstractClass virtual COORDINATE Center_Coord(void) const; virtual COORDINATE Render_Coord(void) const; virtual COORDINATE Sort_Y(void) const; + virtual FireDataType Fire_Data(int which) const; virtual COORDINATE Fire_Coord(int which) const; virtual COORDINATE Exit_Coord(void) const; diff --git a/REDALERT/RADAR.CPP b/REDALERT/RADAR.CPP index f540bc7..78bb77f 100644 --- a/REDALERT/RADAR.CPP +++ b/REDALERT/RADAR.CPP @@ -267,7 +267,7 @@ bool RadarClass::Radar_Activate(int control) case 0: if (Map.IsSidebarActive) { 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; IsRadarActive = false; if (IsRadarActivating == true) { @@ -284,7 +284,7 @@ bool RadarClass::Radar_Activate(int control) case 1: if (Map.IsSidebarActive) { 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; if (IsRadarDeactivating == true) { IsRadarDeactivating = false; diff --git a/REDALERT/RedAlert.vcxproj.filters b/REDALERT/RedAlert.vcxproj.filters index 39e5841..1f02003 100644 --- a/REDALERT/RedAlert.vcxproj.filters +++ b/REDALERT/RedAlert.vcxproj.filters @@ -1,4 +1,4 @@ - + diff --git a/REDALERT/SCENARIO.CPP b/REDALERT/SCENARIO.CPP index 8215a02..869e2f8 100644 --- a/REDALERT/SCENARIO.CPP +++ b/REDALERT/SCENARIO.CPP @@ -2411,7 +2411,10 @@ bool Read_Scenario_INI(char * fname, bool ) UnitClass::Read_INI(ini); Call_Back(); - VesselClass::Read_INI(ini); + AircraftClass::Read_INI(ini); + Call_Back(); + + VesselClass::Read_INI(ini); Call_Back(); /* diff --git a/REDALERT/SPECIAL.CPP b/REDALERT/SPECIAL.CPP index 42163b6..60910b2 100644 --- a/REDALERT/SPECIAL.CPP +++ b/REDALERT/SPECIAL.CPP @@ -82,6 +82,7 @@ void SpecialClass::Init(void) UseMCVDeploy = false; IsMCVDeploy = false; IsEarlyWin = false; + ModernBalance = false; } diff --git a/REDALERT/SPECIAL.H b/REDALERT/SPECIAL.H index f080f76..3a65d23 100644 --- a/REDALERT/SPECIAL.H +++ b/REDALERT/SPECIAL.H @@ -105,10 +105,22 @@ class SpecialClass */ 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 */ - 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. }; diff --git a/REDALERT/STARTUP.CPP b/REDALERT/STARTUP.CPP index 3fdf516..15c0ba9 100644 --- a/REDALERT/STARTUP.CPP +++ b/REDALERT/STARTUP.CPP @@ -977,6 +977,10 @@ void __cdecl Prog_End(const char *why, bool fatal) *((int*)0) = 0; } + if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { + return; + } + Sound_End(); if (WWMouse) { delete WWMouse; diff --git a/REDALERT/SUPER.CPP b/REDALERT/SUPER.CPP index f3ac697..1c98622 100644 --- a/REDALERT/SUPER.CPP +++ b/REDALERT/SUPER.CPP @@ -168,9 +168,9 @@ bool SuperClass::Enable(bool onetime, bool player, bool quiet) * HISTORY: * * 07/28/1995 JLB : Created. * *=============================================================================================*/ -bool SuperClass::Remove(void) +bool SuperClass::Remove(bool forced) { - if (IsPresent && !IsOneTime) { + if (IsPresent && (!IsOneTime || forced)) { IsReady = false; IsPresent = false; return(true); diff --git a/REDALERT/SUPER.H b/REDALERT/SUPER.H index b9838bc..a5d2605 100644 --- a/REDALERT/SUPER.H +++ b/REDALERT/SUPER.H @@ -47,7 +47,7 @@ class SuperClass { bool Enable(bool onetime = false, bool player=false, bool quiet=false); void Forced_Charge(bool player=false); bool AI(bool player=false); - bool Remove(void); + bool Remove(bool forced=false); void Impatient_Click(void) const; int Anim_Stage(void) const; bool Discharged(bool player); diff --git a/REDALERT/TECHNO.CPP b/REDALERT/TECHNO.CPP index 8e45c10..2c14639 100644 --- a/REDALERT/TECHNO.CPP +++ b/REDALERT/TECHNO.CPP @@ -489,6 +489,25 @@ bool TechnoClass::Is_Allowed_To_Recloak(void) const * HISTORY: * * 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 { 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(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. ** 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; /* @@ -1088,7 +1112,7 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) const int 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_ALWAYS)); @@ -1253,8 +1277,8 @@ bool TechnoClass::In_Range(TARGET target, int which, bool reciprocal_check) cons if (building != NULL) { 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); } @@ -1309,8 +1333,8 @@ bool TechnoClass::In_Range(ObjectClass const * target, int which, bool reciproca BuildingClass const * building = (BuildingClass const *)target; 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); } @@ -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 (object->Cloak == CLOAKED) { + if (object->Is_Cloaked(this)) { BEnd(BENCH_EVAL_OBJECT); 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, ** 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. * * * @@ -2448,7 +2489,7 @@ void TechnoClass::AI(void) ** If for some strange reason, the computer is firing upon itself, then ** 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) //if(What_Am_I() == RTTI_INFANTRY && *(InfantryClass *)this==INFANTRY_GENERAL && Session.Type==GAME_NORMAL && House->Class->House==HOUSE_UKRAINE) { //} else @@ -2773,9 +2814,9 @@ FireErrorType TechnoClass::Can_Fire(TARGET target, int which) const 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); } @@ -4187,7 +4228,7 @@ void TechnoClass::Record_The_Kill(TechnoClass * source) * 07/06/1995 JLB : Created. * * 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); @@ -4203,7 +4244,7 @@ CELL TechnoClass::Nearby_Location(TechnoClass const * techno) const 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)); } diff --git a/REDALERT/TECHNO.H b/REDALERT/TECHNO.H index 689fe2a..f33b8ef 100644 --- a/REDALERT/TECHNO.H +++ b/REDALERT/TECHNO.H @@ -271,7 +271,7 @@ class TechnoClass : public RadioClass, bool Is_Ready_To_Cloak(void) const; virtual int How_Many_Survivors(void) const; 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());}; bool Is_Visible_On_Radar(void) const; int Anti_Air(void) const; @@ -282,6 +282,7 @@ class TechnoClass : public RadioClass, virtual ActionType What_Action(ObjectClass const * target) const; virtual BuildingClass * Find_Docking_Bay(StructType b, bool friendly) const; virtual CELL Find_Exit_Cell(TechnoClass const * techno) const; + virtual FireDataType Fire_Data(int) const; virtual COORDINATE Fire_Coord(int which) const; virtual DirType Desired_Load_Dir(ObjectClass * , CELL & moveto) 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; 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; + 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. diff --git a/REDALERT/UNIT.CPP b/REDALERT/UNIT.CPP index 712f0b1..04e9a34 100644 --- a/REDALERT/UNIT.CPP +++ b/REDALERT/UNIT.CPP @@ -433,6 +433,13 @@ void UnitClass::AI(void) */ 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 ** guard or other static mission mode. @@ -1283,6 +1290,11 @@ void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET if (mission == MISSION_HARVEST) { 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); } @@ -1704,7 +1716,7 @@ void UnitClass::Per_Cell_Process(PCPType why) } else { TechnoClass * contact = Contact_With_Whom(); 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); } else if (!Target_Legal(NavCom)) { 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); - 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[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); @@ -2248,6 +2275,7 @@ bool UnitClass::Goto_Tiberium(int rad) int tiberium = 0; int besttiberium = 0; for (int x = -radius; x <= radius; x++) { + cell = center; tiberium = Tiberium_Check(cell, x, -radius); if (tiberium > besttiberium) { bestcell = cell; @@ -2832,10 +2860,10 @@ int UnitClass::Mission_Harvest(void) */ 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; 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() ** 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 @@ -3530,14 +3558,17 @@ ActionType UnitClass::What_Action(ObjectClass const * object) const /* ** 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; } /* ** 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; 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; diff --git a/REDALERT/VESSEL.CPP b/REDALERT/VESSEL.CPP index bb5a316..e4e621f 100644 --- a/REDALERT/VESSEL.CPP +++ b/REDALERT/VESSEL.CPP @@ -299,7 +299,7 @@ MoveType VesselClass::Can_Enter_Cell(CELL cell, FacingType ) const } 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); } @@ -1183,6 +1183,32 @@ Mono_Set_Cursor(0,0); * HISTORY: * * 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 { assert(Vessels.ID(this) == ID); diff --git a/REDALERT/VESSEL.H b/REDALERT/VESSEL.H index dc383d2..a57ffff 100644 --- a/REDALERT/VESSEL.H +++ b/REDALERT/VESSEL.H @@ -97,6 +97,7 @@ class VesselClass : public DriveClass virtual int Mission_Unload(void); void LST_Open_Door(void); void LST_Close_Door(void); + virtual FireDataType Fire_Data(int) const; virtual COORDINATE Fire_Coord(int which) const; virtual MoveType Can_Enter_Cell(CELL cell, FacingType from=FACING_NONE) const; virtual void Draw_It(int x, int y, WindowNumberType window) const; diff --git a/REDALERT/WIN32LIB/DrawMisc.cpp b/REDALERT/WIN32LIB/DrawMisc.cpp index 70a9ba1..1704bfa 100644 --- a/REDALERT/WIN32LIB/DrawMisc.cpp +++ b/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 ** ;*************************************************************************** diff --git a/TIBERIANDAWN/ADATA.CPP b/TIBERIANDAWN/ADATA.CPP index 3958f21..0eeb732 100644 --- a/TIBERIANDAWN/ADATA.CPP +++ b/TIBERIANDAWN/ADATA.CPP @@ -337,7 +337,7 @@ static AnimTypeClass const LZSmoke( 72, // Loop start frame number. 91, // Ending frame of loop back. -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. ANIM_NONE ); @@ -1427,7 +1427,7 @@ static AnimTypeClass const OilFieldBurn( 33, // Loop start frame number. 99, // Ending frame of loop back. 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. ANIM_NONE ); diff --git a/TIBERIANDAWN/AIRCRAFT.CPP b/TIBERIANDAWN/AIRCRAFT.CPP index f0ffded..c444d94 100644 --- a/TIBERIANDAWN/AIRCRAFT.CPP +++ b/TIBERIANDAWN/AIRCRAFT.CPP @@ -245,6 +245,7 @@ AircraftClass::AircraftClass(AircraftType classid, HousesType house) : NavCom = TARGET_NONE; SecondaryFacing = PrimaryFacing; Jitter = 0; + ReinforcementStart = -1; /* ** 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 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. @@ -743,7 +752,17 @@ void AircraftClass::AI(void) 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(); } @@ -1399,9 +1418,13 @@ int AircraftClass::Mission_Retreat(void) { Validate(); 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++; - return(3); + return(1); } return(TICKS_PER_SECOND*10); } @@ -2175,7 +2198,7 @@ ActionType AircraftClass::What_Action(CELL cell) const ActionType action = FootClass::What_Action(cell); //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; } @@ -2438,7 +2461,7 @@ int AircraftClass::Mission_Attack(void) * HISTORY: * * 06/19/1995 JLB : Created. * *=============================================================================================*/ -TARGET AircraftClass::New_LZ(TARGET oldlz) const +TARGET AircraftClass::New_LZ(TARGET oldlz, bool stable) const { Validate(); 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. */ 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; /* @@ -2838,8 +2861,9 @@ TARGET AircraftClass::Good_Fire_Location(TARGET target) const for (int face = 0; face < 255; face += 16) { COORDINATE newcoord = Coord_Move(tcoord, (DirType)face, r); 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); if (bestval == -1 || dist < bestval) { best2val = bestval; @@ -3545,4 +3569,9 @@ void AircraftClass::Response_Select(void) if (AllowVoice) { Sound_Effect(response, 0, -(Aircraft.ID(this)+1)); } +} + +void AircraftClass::Set_Reinforcement_Delay(long delay) +{ + ReinforcementStart = Frame + delay; } \ No newline at end of file diff --git a/TIBERIANDAWN/AIRCRAFT.H b/TIBERIANDAWN/AIRCRAFT.H index 2f2124d..0abf343 100644 --- a/TIBERIANDAWN/AIRCRAFT.H +++ b/TIBERIANDAWN/AIRCRAFT.H @@ -98,7 +98,7 @@ class AircraftClass : public FootClass, public FlyClass ** Landing zone support functionality. */ 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 @@ -148,6 +148,7 @@ class AircraftClass : public FootClass, public FlyClass virtual void Enter_Idle_Mode(bool initial = false); virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, long & param); virtual void Scatter(COORDINATE threat, bool forced=false, bool nokidding=false); + void Set_Reinforcement_Delay(long delay); /* ** Scenario and debug support. @@ -242,10 +243,15 @@ class AircraftClass : public FootClass, public FlyClass */ 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 */ - unsigned char SaveLoadPadding[32]; + unsigned char SaveLoadPadding[28]; /* ** This contains the value of the Virtual Function Table Pointer diff --git a/TIBERIANDAWN/ANIM.CPP b/TIBERIANDAWN/ANIM.CPP index 12644a1..e388218 100644 --- a/TIBERIANDAWN/ANIM.CPP +++ b/TIBERIANDAWN/ANIM.CPP @@ -589,7 +589,7 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay coord = Adjust_Coord(coord); Unlimbo(coord); - VisibleFlags = 0xffff; + VisibleFlags = static_cast(-1); /* ** Drop zone smoke always reveals the map around itself. @@ -751,6 +751,16 @@ void AnimClass::AI(void) 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) { Delay--; if (!Delay) { @@ -788,10 +798,10 @@ void AnimClass::AI(void) int damage = accum >> 8; if (Object->Take_Damage(damage, 0, WARHEAD_FIRE) == RESULT_DESTROYED) { //Object = 0; - Delete_This(); if (VirtualAnim != NULL) { VirtualAnim->Delete_This(); } + Delete_This(); return; } } @@ -1272,6 +1282,9 @@ void AnimClass::Detach(TARGET target, bool all) if (all) { if (VirtualAnim && VirtualAnim->As_Target() == target) { VirtualAnim = NULL; + if (IsInvisible) { + IsToDelete = true; + } } if (Object && Object->As_Target() == target) { Map.Remove(this, In_Which_Layer()); diff --git a/TIBERIANDAWN/BDATA.CPP b/TIBERIANDAWN/BDATA.CPP index 635e506..c062429 100644 --- a/TIBERIANDAWN/BDATA.CPP +++ b/TIBERIANDAWN/BDATA.CPP @@ -234,7 +234,7 @@ static BuildingTypeClass const ClassEye( false, // Always use the given name for the building? false, // Is this a wall type structure? 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, // Simple (one frame) damage imagery? false, // Is it invisible to radar? diff --git a/TIBERIANDAWN/BUILDING.CPP b/TIBERIANDAWN/BUILDING.CPP index 8a19967..441f8ee 100644 --- a/TIBERIANDAWN/BUILDING.CPP +++ b/TIBERIANDAWN/BUILDING.CPP @@ -213,7 +213,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT case RADIO_CAN_LOAD: 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) { case STRUCT_AIRSTRIP: 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 && (ScenarioInit || !Is_Something_Attached())) { - return(RADIO_ROGER); + return((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE); } break; @@ -1166,7 +1166,9 @@ void BuildingClass::AI(void) Repair(1); } else { 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: * * 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 { Validate(); @@ -3519,8 +3554,14 @@ bool BuildingClass::Toggle_Primary(void) } } 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); @@ -3832,14 +3873,14 @@ bool BuildingClass::Can_Demolish_Unit(void) const bool BuildingClass::Can_Capture(void) const { 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); } @@ -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); - } + #endif /* ** 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 (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; Begin_Mode(BSTATE_ACTIVE); IsReadyToCommence = false; diff --git a/TIBERIANDAWN/BUILDING.H b/TIBERIANDAWN/BUILDING.H index 16c28ac..ed0eb1a 100644 --- a/TIBERIANDAWN/BUILDING.H +++ b/TIBERIANDAWN/BUILDING.H @@ -196,6 +196,7 @@ class BuildingClass : public TechnoClass ** combat purposes. */ virtual COORDINATE Docking_Coord(void) const; + virtual FireDataType Fire_Data(int which) const; virtual COORDINATE Fire_Coord(int which) const; virtual COORDINATE Center_Coord(void) const; virtual COORDINATE Sort_Y(void) const; diff --git a/TIBERIANDAWN/CDATA.CPP b/TIBERIANDAWN/CDATA.CPP index 26175ed..7a756ae 100644 --- a/TIBERIANDAWN/CDATA.CPP +++ b/TIBERIANDAWN/CDATA.CPP @@ -1824,7 +1824,7 @@ static TemplateTypeClass const Patch15( "P15", TXT_PATCH, LAND_CLEAR, - 1,1, + 4,2, LAND_CLEAR, NULL ); diff --git a/TIBERIANDAWN/CELL.CPP b/TIBERIANDAWN/CELL.CPP index 22274fa..184001f 100644 --- a/TIBERIANDAWN/CELL.CPP +++ b/TIBERIANDAWN/CELL.CPP @@ -1936,7 +1936,7 @@ long CellClass::Tiberium_Adjust(bool pregame) * 05/22/1995 JLB : Created. * * 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(); enum { @@ -1990,21 +1990,30 @@ bool CellClass::Goodie_Check(FootClass * object) bool steel = (Overlay == OVERLAY_STEEL_CRATE); 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) { object->House->Add_Nuke_Piece(); 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; UnitClass * unit = 0; @@ -2428,9 +2437,18 @@ void CellClass::Flag_Create(void) { if (!CTFFlag) { 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; } } diff --git a/TIBERIANDAWN/CELL.H b/TIBERIANDAWN/CELL.H index 963897a..a059599 100644 --- a/TIBERIANDAWN/CELL.H +++ b/TIBERIANDAWN/CELL.H @@ -208,7 +208,7 @@ class CellClass InfantryClass * Cell_Infantry(void) const; TriggerClass * Get_Trigger(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; bool Get_Template_Info(char *template_name, int &icon, void *&image_data); diff --git a/TIBERIANDAWN/DEFINES.H b/TIBERIANDAWN/DEFINES.H index a5c3179..141699c 100644 --- a/TIBERIANDAWN/DEFINES.H +++ b/TIBERIANDAWN/DEFINES.H @@ -2847,6 +2847,15 @@ typedef enum OptionControlType : char { } OptionControlType; +/**************************************************************************** +** Used to store firing data for a unit. +*/ +typedef struct { + COORDINATE Center; + int Distance; +} FireDataType; + + #define TOTAL_CRATE_TYPES 15 #define size_of(typ,id) sizeof(((typ*)0)->id) diff --git a/TIBERIANDAWN/DISPLAY.CPP b/TIBERIANDAWN/DISPLAY.CPP index 188c55a..f24628e 100644 --- a/TIBERIANDAWN/DISPLAY.CPP +++ b/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 */ - 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); if (hptr && hptr->IsActive) { 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; for (int index = 0; index < CurrentObject.Count(); index++) { ObjectClass * tobject = CurrentObject[index]; + ActionType action = ACTION_NONE; if (object) { - tobject->Active_Click_With(tobject->What_Action(object), object); + action = tobject->What_Action(object); + tobject->Active_Click_With(action, object); } 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; diff --git a/TIBERIANDAWN/DLLInterface.cpp b/TIBERIANDAWN/DLLInterface.cpp index abbfabb..f2de68d 100644 --- a/TIBERIANDAWN/DLLInterface.cpp +++ b/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.IsCaptureTheFlag = game_options.CaptureTheFlag; Special.IsEarlyWin = game_options.DestroyStructures; + Special.ModernBalance = game_options.ModernBalance; 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(); - 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(); 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); + // MBL 07.21.2020 + if (result == false) + { + return false; + } + DLLExportClass::Set_Player_Context(DLLExportClass::GlyphxPlayerIDs[0], true); Set_Logic_Page(SeenBuff); 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) { CNCObjectStruct& new_object = ObjectList->Objects[TotalObjectCount + CurrentDrawCount]; + memset(&new_object, 0, sizeof(new_object)); Convert_Type(object, new_object); if (new_object.Type == UNKNOWN) { 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) { return false; } + + memset(&sidebar_entry, 0, sizeof(sidebar_entry)); sidebar_entry.AssetName[0] = 0; sidebar_entry.Type = UNKNOWN; @@ -4191,6 +4203,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char *buffer_i return false; } + memset(&sidebar_entry, 0, sizeof(sidebar_entry)); + sidebar_entry.AssetName[0] = 0; sidebar_entry.Type = UNKNOWN; 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) { int map_cell_x = Map.MapCellX; @@ -4380,7 +4396,7 @@ void DLLExportClass::Calculate_Placement_Distances(BuildingTypeClass* placement_ memset(placement_distance, 255U, MAP_CELL_TOTAL); for (int y = 0; y < map_cell_height; y++) { 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); if ((base && base->House->Class->House == PlayerPtr->Class->House) || (Map[cell].Owner == PlayerPtr->Class->House)) { 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 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]); @@ -5132,7 +5148,7 @@ Map.Passes_Proximity_Check 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. @@ -7503,10 +7519,18 @@ bool DLLExportClass::Save(FileClass & file) 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(¬_allow_super_weapons, sizeof(not_allow_super_weapons)) != sizeof(not_allow_super_weapons)) { + return false; + } + /* ** Room for save game expansion */ - unsigned char padding[4096]; + unsigned char padding[4095]; memset(padding, 0, sizeof(padding)); if (file.Write(padding, sizeof(padding)) != sizeof(padding)) { @@ -7621,7 +7645,16 @@ bool DLLExportClass::Load(FileClass & file) 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(¬_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)) { return false; diff --git a/TIBERIANDAWN/DLLInterface.h b/TIBERIANDAWN/DLLInterface.h index a52c00c..2e1c770 100644 --- a/TIBERIANDAWN/DLLInterface.h +++ b/TIBERIANDAWN/DLLInterface.h @@ -728,7 +728,7 @@ struct CNCMultiplayerOptionsStruct { int MPlayerCount; // # of human players in this game int MPlayerBases; // 1 = bases are on for this scenario 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 MPlayerGhosts; // 1 = houses with no players will still play int MPlayerSolo; // 1 = allows a single-player net game @@ -740,6 +740,7 @@ struct CNCMultiplayerOptionsStruct { bool MPlayerAftermathUnits; bool CaptureTheFlag; bool DestroyStructures; // New early win condition via destroying all a player's structures + bool ModernBalance; }; diff --git a/TIBERIANDAWN/DRIVE.CPP b/TIBERIANDAWN/DRIVE.CPP index dfd1cda..6200ec6 100644 --- a/TIBERIANDAWN/DRIVE.CPP +++ b/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 ** 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 (Can_Enter_Cell(cell) == MOVE_TEMP) { CellClass * cellptr = &Map[cell]; diff --git a/TIBERIANDAWN/EVENT.CPP b/TIBERIANDAWN/EVENT.CPP index fe3dd46..9f3380a 100644 --- a/TIBERIANDAWN/EVENT.CPP +++ b/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 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(-1)); } else { @@ -626,6 +626,12 @@ CCDebugString ("C&C95 - Primary building packet received\n"); techno->ArchiveTarget = Data.MegaMission.Target; techno->Assign_Target(TARGET_NONE); 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 { techno->Assign_Target(Data.MegaMission.Target); techno->Assign_Destination(Data.MegaMission.Destination); diff --git a/TIBERIANDAWN/FOOT.CPP b/TIBERIANDAWN/FOOT.CPP index c71453f..942c233 100644 --- a/TIBERIANDAWN/FOOT.CPP +++ b/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); } @@ -1635,8 +1637,20 @@ int FootClass::Mission_Enter(void) ** Since there is no potential object to enter, then abort this ** 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); } diff --git a/TIBERIANDAWN/HOUSE.CPP b/TIBERIANDAWN/HOUSE.CPP index c96650c..62bddbf 100644 --- a/TIBERIANDAWN/HOUSE.CPP +++ b/TIBERIANDAWN/HOUSE.CPP @@ -569,16 +569,18 @@ bool HouseClass::Can_Build(TechnoTypeClass const * type, HousesType house) const */ 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) { - flags = OldBScan; + flags = 0; + for (int i = 0; i < 32; i++) { + if (BQuantity[i] > 0) { + flags |= (1 << i); + } + } } -#endif int pre = type->Pre; if (flags & STRUCTF_ADVANCED_POWER) flags |= STRUCTF_POWER; @@ -972,6 +974,12 @@ void HouseClass::AI(void) if (IsToDie && BorrowedTime.Expired()) { IsToDie = false; 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 - if (GameToPlay != GAME_NORMAL) { + if (GameToPlay != GAME_NORMAL && Class->House != HOUSE_JP) { Check_Pertinent_Structures(); } @@ -1664,8 +1672,15 @@ void HouseClass::AI(void) void HouseClass::Attacked(BuildingClass* source) { 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); + spoke = true; // 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 @@ -1680,6 +1695,23 @@ void HouseClass::Attacked(BuildingClass* source) 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: - Set MPlayerObiWan, so I can only send messages to all players, and @@ -4784,6 +4823,13 @@ void HouseClass::Check_Pertinent_Structures(void) 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; for (int index = 0; index < Buildings.Count(); index++) { diff --git a/TIBERIANDAWN/IDATA.CPP b/TIBERIANDAWN/IDATA.CPP index 7410c6b..1be9946 100644 --- a/TIBERIANDAWN/IDATA.CPP +++ b/TIBERIANDAWN/IDATA.CPP @@ -430,7 +430,7 @@ static InfantryTypeClass const E5( 70, // Strength of infantry (in damage points). 1, // Sight range. 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. HOUSEF_MULTI1| HOUSEF_MULTI2| diff --git a/TIBERIANDAWN/INFANTRY.CPP b/TIBERIANDAWN/INFANTRY.CPP index 585fce8..308c332 100644 --- a/TIBERIANDAWN/INFANTRY.CPP +++ b/TIBERIANDAWN/INFANTRY.CPP @@ -882,7 +882,7 @@ void InfantryClass::Look(bool incremental) if (!IsInLimbo) { //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; if (sight) { @@ -1098,7 +1098,14 @@ void InfantryClass::AI(void) ** run in circles, scream, and shout. */ 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() ** 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 @@ -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 ** 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; } diff --git a/TIBERIANDAWN/INI.CPP b/TIBERIANDAWN/INI.CPP index 1bfcad7..923768f 100644 --- a/TIBERIANDAWN/INI.CPP +++ b/TIBERIANDAWN/INI.CPP @@ -321,6 +321,9 @@ bool Read_Scenario_Ini(char *root, bool fresh) #ifdef NEWMENU if (Scenario <= 15) { 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 { BuildLevel = WWGetPrivateProfileInt("Basic", "BuildLevel", Scenario, buffer); } @@ -444,6 +447,9 @@ bool Read_Scenario_Ini(char *root, bool fresh) UnitClass::Read_INI(buffer); Call_Back(); + AircraftClass::Read_INI(buffer); + Call_Back(); + /* ** 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 ** NOD09A - delete airstrike trigger when radar destroyed ** 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) { 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) { 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) @@ -844,6 +876,9 @@ bool Read_Scenario_Ini_File(char *scenario_file_name, char* bin_file_name, const UnitClass::Read_INI(buffer); Call_Back(); + AircraftClass::Read_INI(buffer); + Call_Back(); + /* ** 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(); Call_Back(); + /* + ** Scenario fix-up (applied on loaded games as well) + */ + Fixup_Scenario(); + /* ** Multi-player last-minute fixups: ** - If computer players are disabled, remove all computer-owned houses diff --git a/TIBERIANDAWN/LOGIC.CPP b/TIBERIANDAWN/LOGIC.CPP index f6298f6..036d4ed 100644 --- a/TIBERIANDAWN/LOGIC.CPP +++ b/TIBERIANDAWN/LOGIC.CPP @@ -190,6 +190,7 @@ void LogicClass::AI(void) */ for (index = 0; index < Count(); index++) { ObjectClass * obj = (*this)[index]; + int count = Count(); obj->AI(); @@ -197,9 +198,9 @@ void LogicClass::AI(void) ** If the object was destroyed in the process of performing its AI, then ** 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; } } diff --git a/TIBERIANDAWN/MAP.CPP b/TIBERIANDAWN/MAP.CPP index bb789a5..7a34830 100644 --- a/TIBERIANDAWN/MAP.CPP +++ b/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 ** 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) { int tries = 1; 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; IsForwardScan = (IsForwardScan == false); @@ -958,11 +965,17 @@ void MapClass::Logic(void) */ if (TiberiumGrowthCount) { 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]; if (newcell->Land_Type() == LAND_TIBERIUM && newcell->OverlayData < 12-1) { newcell->OverlayData++; } + TiberiumGrowth[pick] = TiberiumGrowth[TiberiumGrowthCount - 1]; + TiberiumGrowthCount--; + if (TiberiumGrowthCount <= 0) { + break; + } } } TiberiumGrowthCount = 0; @@ -972,7 +985,8 @@ void MapClass::Logic(void) */ if (TiberiumSpreadCount) { 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. @@ -1003,6 +1017,11 @@ void MapClass::Logic(void) } } } + TiberiumSpread[pick] = TiberiumSpread[TiberiumSpreadCount - 1]; + TiberiumSpreadCount--; + if (TiberiumSpreadCount <= 0) { + break; + } } } TiberiumSpreadCount = 0; @@ -1400,10 +1419,10 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const 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 - 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) { int d=-1; if (o->What_Am_I() == RTTI_BUILDING) { @@ -1429,7 +1448,7 @@ ObjectClass * MapClass::Close_Object(COORDINATE coord) const AircraftClass * aircraft = Aircraft.Ptr(index); 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)))); if (d >= 0 && (!object || d < distance)) { distance = d; diff --git a/TIBERIANDAWN/MiscAsm.cpp b/TIBERIANDAWN/MiscAsm.cpp index 2aea545..4f03422 100644 --- a/TIBERIANDAWN/MiscAsm.cpp +++ b/TIBERIANDAWN/MiscAsm.cpp @@ -428,7 +428,7 @@ dxisbig: #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 ** ;*************************************************************************** diff --git a/TIBERIANDAWN/OBJECT.CPP b/TIBERIANDAWN/OBJECT.CPP index ce137f0..3621787 100644 --- a/TIBERIANDAWN/OBJECT.CPP +++ b/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::Docking_Coord(void) const {return(Center_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;}; void ObjectClass::Record_The_Kill(TechnoClass * ) {}; void ObjectClass::Do_Shimmer(void) {}; diff --git a/TIBERIANDAWN/OBJECT.H b/TIBERIANDAWN/OBJECT.H index 2bf51cb..6c9a951 100644 --- a/TIBERIANDAWN/OBJECT.H +++ b/TIBERIANDAWN/OBJECT.H @@ -172,6 +172,7 @@ class ObjectClass : public AbstractClass virtual COORDINATE Center_Coord(void) const; virtual COORDINATE Render_Coord(void) const; virtual COORDINATE Sort_Y(void) const; + virtual FireDataType Fire_Data(int) const; virtual COORDINATE Fire_Coord(int ) const; /* diff --git a/TIBERIANDAWN/RADAR.CPP b/TIBERIANDAWN/RADAR.CPP index ff090ee..7fe6c69 100644 --- a/TIBERIANDAWN/RADAR.CPP +++ b/TIBERIANDAWN/RADAR.CPP @@ -235,7 +235,7 @@ bool RadarClass::Radar_Activate(int control) case 0: if (Map.IsSidebarActive) { 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; IsRadarActive = false; if (IsRadarActivating == true) { @@ -252,7 +252,7 @@ bool RadarClass::Radar_Activate(int control) case 1: if (Map.IsSidebarActive) { 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; if (IsRadarDeactivating == true) { IsRadarDeactivating = false; diff --git a/TIBERIANDAWN/REINF.CPP b/TIBERIANDAWN/REINF.CPP index 2731582..ef5486b 100644 --- a/TIBERIANDAWN/REINF.CPP +++ b/TIBERIANDAWN/REINF.CPP @@ -338,6 +338,7 @@ bool Do_Reinforcements(TeamTypeClass *teamtype) */ case SOURCE_AIR: { AircraftClass * thisone = (AircraftClass *)object; + TARGET target = TARGET_NONE; while (thisone) { 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 ** exactly lines up with the airfield they will unload at. */ - CELL newcell; + COORDINATE newcoord; + long reinforcement_delay = -1; 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--; if (*thisone == AIRCRAFT_CARGO) { BuildingClass const * building = thisone->Find_Docking_Bay(STRUCT_AIRSTRIP, false); 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; 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--; if (placed) { if (!team) { if (thisone->Class->IsFixedWing) { 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 { if (thisone->Class->IsTransporter && thisone->Is_Something_Attached()) { thisone->Assign_Mission(MISSION_UNLOAD); diff --git a/TIBERIANDAWN/SCENARIO.CPP b/TIBERIANDAWN/SCENARIO.CPP index f4742fa..abc406a 100644 --- a/TIBERIANDAWN/SCENARIO.CPP +++ b/TIBERIANDAWN/SCENARIO.CPP @@ -799,4 +799,22 @@ void Fixup_Scenario(void) } else { ((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; + } } \ No newline at end of file diff --git a/TIBERIANDAWN/SPECIAL.H b/TIBERIANDAWN/SPECIAL.H index 80db3cd..7561a83 100644 --- a/TIBERIANDAWN/SPECIAL.H +++ b/TIBERIANDAWN/SPECIAL.H @@ -70,6 +70,7 @@ class SpecialClass IsEarlyWin = false; HealthBarDisplayMode = HB_SELECTED; ResourceBarDisplayMode = RB_SELECTED; + ModernBalance = false; } /* @@ -254,11 +255,21 @@ class SpecialClass RB_ALWAYS, } 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 */ - 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]; }; diff --git a/TIBERIANDAWN/TEAM.CPP b/TIBERIANDAWN/TEAM.CPP index f14214f..b3dad15 100644 --- a/TIBERIANDAWN/TEAM.CPP +++ b/TIBERIANDAWN/TEAM.CPP @@ -1266,7 +1266,14 @@ void TeamClass::Coordinate_Move(void) unit->Assign_Mission(MISSION_MOVE); } 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; } else { diff --git a/TIBERIANDAWN/TECHNO.CPP b/TIBERIANDAWN/TECHNO.CPP index 87bf3cc..ae97d9e 100644 --- a/TIBERIANDAWN/TECHNO.CPP +++ b/TIBERIANDAWN/TECHNO.CPP @@ -1021,7 +1021,7 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) Clear_Redraw_Flag(); const bool show_health_bar = - (Strength > 0) && ((Cloak != CLOAKED) || Is_Owned_By_Player()) && + (Strength > 0) && !Is_Cloaked(PlayerPtr) && (Is_Selected_By_Player() || ((Special.HealthBarDisplayMode == SpecialClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) || (Special.HealthBarDisplayMode == SpecialClass::HB_ALWAYS)); @@ -1191,7 +1191,8 @@ bool TechnoClass::In_Range(TARGET target, int which, bool reciprocal_check) cons if (building) { 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); } @@ -1244,8 +1245,8 @@ bool TechnoClass::In_Range(ObjectClass const * target, int which, bool reciproca BuildingClass const * building = (BuildingClass const *)target; 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); } @@ -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 (object->Cloak == CLOAKED) return(false); + if (object->Is_Cloaked(this)) return(false); /* ** 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("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(); - 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); } @@ -2425,7 +2426,7 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which) } #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); @@ -2433,7 +2434,7 @@ BulletClass * TechnoClass::Fire_At(TARGET target, int which) HousesType tgt_owner = obj->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)) { 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) { best = building; bestval = Distance(building); + if (building->IsLeader) break; } } } @@ -4616,8 +4618,23 @@ 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); +} + diff --git a/TIBERIANDAWN/TECHNO.H b/TIBERIANDAWN/TECHNO.H index 6690db6..ad8f36a 100644 --- a/TIBERIANDAWN/TECHNO.H +++ b/TIBERIANDAWN/TECHNO.H @@ -280,8 +280,11 @@ class TechnoClass : public RadioClass, virtual int Weapon_Range(int which) const; virtual bool Captured(HouseClass * newowner); 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. diff --git a/TIBERIANDAWN/TURRET.CPP b/TIBERIANDAWN/TURRET.CPP index 409fe1e..cf4e31f 100644 --- a/TIBERIANDAWN/TURRET.CPP +++ b/TIBERIANDAWN/TURRET.CPP @@ -361,6 +361,64 @@ FireErrorType TurretClass::Can_Fire(TARGET target, int which) const * HISTORY: * * 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 coord = Center_Coord(); diff --git a/TIBERIANDAWN/TURRET.H b/TIBERIANDAWN/TURRET.H index 35b478e..53c3b80 100644 --- a/TIBERIANDAWN/TURRET.H +++ b/TIBERIANDAWN/TURRET.H @@ -75,6 +75,7 @@ class TurretClass : public DriveClass virtual FireErrorType Can_Fire(TARGET target, int which) const; virtual bool Ok_To_Move(DirType facing); virtual void AI(void); + virtual FireDataType Fire_Data(int which) const; virtual COORDINATE Fire_Coord(int which) const; }; diff --git a/TIBERIANDAWN/TiberianDawn.vcxproj b/TIBERIANDAWN/TiberianDawn.vcxproj index 0896ad2..5c53ff1 100644 --- a/TIBERIANDAWN/TiberianDawn.vcxproj +++ b/TIBERIANDAWN/TiberianDawn.vcxproj @@ -1,4 +1,4 @@ - + diff --git a/TIBERIANDAWN/TiberianDawn.vcxproj.filters b/TIBERIANDAWN/TiberianDawn.vcxproj.filters index fa54c35..72cfbe6 100644 --- a/TIBERIANDAWN/TiberianDawn.vcxproj.filters +++ b/TIBERIANDAWN/TiberianDawn.vcxproj.filters @@ -1,4 +1,4 @@ - + diff --git a/TIBERIANDAWN/UNIT.CPP b/TIBERIANDAWN/UNIT.CPP index 8cf678d..039577b 100644 --- a/TIBERIANDAWN/UNIT.CPP +++ b/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 ** 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(); Delete_This(); 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. */ @@ -1259,6 +1266,11 @@ void UnitClass::Player_Assign_Mission(MissionType mission, TARGET target, TARGET Validate(); if (mission == MISSION_HARVEST) { 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); } @@ -1817,7 +1829,7 @@ void UnitClass::Per_Cell_Process(bool center) } else { TechnoClass * contact = Contact_With_Whom(); 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); } else if (!Target_Legal(NavCom)) { Scatter(0, true); @@ -2308,7 +2320,7 @@ int UnitClass::Tiberium_Check(CELL ¢er, int x, int y) //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 (!Map[center].Cell_Techno() && Map[center].Land_Type() == LAND_TIBERIUM) { - return(Map[center].OverlayData); + return(Map[center].OverlayData+1); } } return(0); @@ -2333,6 +2345,7 @@ bool UnitClass::Goto_Tiberium(void) int tiberium = 0; int besttiberium = 0; for (int x = -radius; x <= radius; x++) { + cell = center; tiberium = Tiberium_Check(cell, x, -radius); if (tiberium > besttiberium) { bestcell = cell; @@ -2657,7 +2670,14 @@ int UnitClass::Mission_Harvest(void) */ case LOOKING: 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; Set_Rate(2); Set_Stage(0); @@ -2853,7 +2873,7 @@ void UnitClass::Look(bool incremental) { Validate(); //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; if (sight) { diff --git a/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp b/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp index 05f9a52..27f9940 100644 --- a/TIBERIANDAWN/WIN32LIB/DrawMisc.cpp +++ b/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 ** ;*************************************************************************** diff --git a/TIBERIANDAWN/WIN32LIB/FACINGFF.h b/TIBERIANDAWN/WIN32LIB/FACINGFF.h index 9af5356..7183104 100644 --- a/TIBERIANDAWN/WIN32LIB/FACINGFF.h +++ b/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 ** ;***************************************************************************