diff --git a/code/redalert/adata.cpp b/code/redalert/adata.cpp index d3ab825..0910798 100644 --- a/code/redalert/adata.cpp +++ b/code/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/code/redalert/aircraft.cpp b/code/redalert/aircraft.cpp index 72a3f37..24af424 100644 --- a/code/redalert/aircraft.cpp +++ b/code/redalert/aircraft.cpp @@ -1955,9 +1955,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) { @@ -2374,7 +2382,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; } @@ -3075,7 +3083,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)) ) ) { @@ -3135,9 +3143,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/code/redalert/anim.cpp b/code/redalert/anim.cpp index 36e211e..6304703 100644 --- a/code/redalert/anim.cpp +++ b/code/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/code/redalert/building.cpp b/code/redalert/building.cpp index 08790f0..6da38a1 100644 --- a/code/redalert/building.cpp +++ b/code/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; @@ -2674,13 +2674,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; } @@ -3829,9 +3837,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/code/redalert/cell.cpp b/code/redalert/cell.cpp index 1385fea..d5d7a68 100644 --- a/code/redalert/cell.cpp +++ b/code/redalert/cell.cpp @@ -2935,9 +2935,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/code/redalert/defines.h b/code/redalert/defines.h index 5595f1a..e37cb06 100644 --- a/code/redalert/defines.h +++ b/code/redalert/defines.h @@ -3495,6 +3495,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/code/redalert/display.cpp b/code/redalert/display.cpp index 910814b..a727daa 100644 --- a/code/redalert/display.cpp +++ b/code/redalert/display.cpp @@ -3024,9 +3024,9 @@ int DisplayClass::TacticalClass::Action(unsigned flags, KeyNumType & key) object = Map.Close_Object(x, y); /* - ** 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/code/redalert/dllinterface.cpp b/code/redalert/dllinterface.cpp index 172e8ea..bff27ef 100644 --- a/code/redalert/dllinterface.cpp +++ b/code/redalert/dllinterface.cpp @@ -384,6 +384,8 @@ bool MPSuperWeaponDisable = false; bool ShareAllyVisibility = true; bool UseGlyphXStartLocations = true; +SpecialClass* SpecialBackup = NULL; + int GetRandSeed() { @@ -670,6 +672,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 */ @@ -714,6 +718,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; } @@ -1527,7 +1538,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 @@ -1538,6 +1549,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; @@ -1730,6 +1748,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); } @@ -1775,6 +1802,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); @@ -1995,6 +2028,11 @@ void DLLExportClass::Init(void) CurrentLocalPlayerIndex = 0; MessagesSent.clear(); + + if (SpecialBackup == NULL) { + SpecialBackup = new SpecialClass; + } + memcpy(SpecialBackup, &Special, sizeof(SpecialClass)); } @@ -2011,6 +2049,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; @@ -4259,6 +4301,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; @@ -4271,7 +4315,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); @@ -4414,6 +4458,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; @@ -4426,7 +4472,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); @@ -8345,10 +8397,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)); @@ -8431,7 +8489,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/code/redalert/dllinterface.h b/code/redalert/dllinterface.h index 7e4525f..a9e643d 100644 --- a/code/redalert/dllinterface.h +++ b/code/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/code/redalert/event.cpp b/code/redalert/event.cpp index 176dd9a..5c96ba3 100644 --- a/code/redalert/event.cpp +++ b/code/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/code/redalert/foot.cpp b/code/redalert/foot.cpp index 3d40c1b..ca42e59 100644 --- a/code/redalert/foot.cpp +++ b/code/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/code/redalert/house.cpp b/code/redalert/house.cpp index b0c77ac..c3d0b7d 100644 --- a/code/redalert/house.cpp +++ b/code/redalert/house.cpp @@ -924,7 +924,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; @@ -960,6 +960,9 @@ void HouseClass::AI(void) if (IsToDie && BorrowedTime == 0) { IsToDie = false; Blowup_All(); + if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { + MPlayer_Defeated(); + } } /* @@ -1158,7 +1161,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 @@ -1825,12 +1828,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 @@ -1845,6 +1854,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 + } + } } @@ -3765,6 +3790,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 @@ -4106,7 +4138,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) { @@ -4125,7 +4164,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--; @@ -4140,7 +4185,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--; @@ -4158,7 +4209,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) { @@ -4181,7 +4239,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); @@ -8050,6 +8115,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++) { @@ -8057,9 +8129,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/code/redalert/infantry.cpp b/code/redalert/infantry.cpp index afa8b8a..9558b74 100644 --- a/code/redalert/infantry.cpp +++ b/code/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 @@ -698,7 +701,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) { @@ -1486,7 +1489,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 @@ -3964,6 +3967,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/code/redalert/inicode.cpp b/code/redalert/inicode.cpp index 705ab26..65bff85 100644 --- a/code/redalert/inicode.cpp +++ b/code/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/code/redalert/init.cpp b/code/redalert/init.cpp index 08ccc72..d2887fd 100644 --- a/code/redalert/init.cpp +++ b/code/redalert/init.cpp @@ -435,6 +435,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/code/redalert/logic.cpp b/code/redalert/logic.cpp index ed4b6fa..6b79c03 100644 --- a/code/redalert/logic.cpp +++ b/code/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/code/redalert/map.cpp b/code/redalert/map.cpp index 1a14c23..686bd7b 100644 --- a/code/redalert/map.cpp +++ b/code/redalert/map.cpp @@ -1027,6 +1027,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++) { @@ -1675,7 +1685,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; @@ -1759,7 +1769,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/code/redalert/map.h b/code/redalert/map.h index ba164ea..8cf32c1 100644 --- a/code/redalert/map.h +++ b/code/redalert/map.h @@ -61,7 +61,8 @@ 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(int screenx, int screeny) const; virtual void Detach(ObjectClass * ) {}; int Cell_Region(CELL cell); diff --git a/code/redalert/object.cpp b/code/redalert/object.cpp index 301f3d4..57717f4 100644 --- a/code/redalert/object.cpp +++ b/code/redalert/object.cpp @@ -635,6 +635,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/code/redalert/object.h b/code/redalert/object.h index e9cc4b3..438c4cd 100644 --- a/code/redalert/object.h +++ b/code/redalert/object.h @@ -180,6 +180,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/code/redalert/radar.cpp b/code/redalert/radar.cpp index aed871a..ba57181 100644 --- a/code/redalert/radar.cpp +++ b/code/redalert/radar.cpp @@ -255,7 +255,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) { @@ -272,7 +272,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/code/redalert/scenario.cpp b/code/redalert/scenario.cpp index 446aadc..5838428 100644 --- a/code/redalert/scenario.cpp +++ b/code/redalert/scenario.cpp @@ -2348,7 +2348,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/code/redalert/special.cpp b/code/redalert/special.cpp index 7fb9ad4..b4e5073 100644 --- a/code/redalert/special.cpp +++ b/code/redalert/special.cpp @@ -82,6 +82,7 @@ void SpecialClass::Init(void) UseMCVDeploy = false; IsMCVDeploy = false; IsEarlyWin = false; + ModernBalance = false; } diff --git a/code/redalert/special.h b/code/redalert/special.h index f080f76..3a65d23 100644 --- a/code/redalert/special.h +++ b/code/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/code/redalert/startup.cpp b/code/redalert/startup.cpp index e96d508..511446f 100644 --- a/code/redalert/startup.cpp +++ b/code/redalert/startup.cpp @@ -752,6 +752,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/code/redalert/super.cpp b/code/redalert/super.cpp index 2e9f8b2..7e2fb62 100644 --- a/code/redalert/super.cpp +++ b/code/redalert/super.cpp @@ -170,9 +170,9 @@ bool SuperClass::Enable(bool onetime, bool player, bool quiet, bool skipPreReq) * HISTORY: * * 07/28/1995 JLB : Created. * *=============================================================================================*/ -bool SuperClass::Remove(void) +bool SuperClass::Remove(bool forced) { - if (IsPresent && !IsOneTime && !IsNoPreq) { + if (IsPresent && (!IsOneTime || forced) && !IsNoPreq) { IsReady = false; IsPresent = false; return(true); diff --git a/code/redalert/super.h b/code/redalert/super.h index 4ef84ca..2912365 100644 --- a/code/redalert/super.h +++ b/code/redalert/super.h @@ -47,7 +47,7 @@ class SuperClass { bool Enable(bool onetime = false, bool player=false, bool quiet=false, bool skipPreReq = 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/code/redalert/techno.cpp b/code/redalert/techno.cpp index 4f31ce7..0c99e0e 100644 --- a/code/redalert/techno.cpp +++ b/code/redalert/techno.cpp @@ -484,6 +484,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); @@ -662,7 +681,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); @@ -968,9 +987,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; /* @@ -1083,7 +1107,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)); @@ -1248,8 +1272,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); } @@ -1304,8 +1328,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); } @@ -1501,7 +1525,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); } @@ -1530,16 +1554,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); } /* @@ -2011,6 +2031,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. * * * @@ -2443,7 +2484,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 @@ -2768,9 +2809,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); } @@ -4185,7 +4226,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); @@ -4201,7 +4242,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/code/redalert/techno.h b/code/redalert/techno.h index 7586355..f4d041f 100644 --- a/code/redalert/techno.h +++ b/code/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/code/redalert/unit.cpp b/code/redalert/unit.cpp index dda4895..9714ca5 100644 --- a/code/redalert/unit.cpp +++ b/code/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); } @@ -1707,7 +1719,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); @@ -2231,10 +2243,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); @@ -2278,6 +2305,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; @@ -2862,10 +2890,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); } @@ -3286,7 +3314,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 @@ -3564,14 +3592,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/code/redalert/version.cpp b/code/redalert/version.cpp index 5f17a54..3e322d7 100644 --- a/code/redalert/version.cpp +++ b/code/redalert/version.cpp @@ -728,7 +728,7 @@ unsigned long VersionClass::Max_Version(void) char const * Version_Name(void) { // jmarshall #define AF_BUILDNUMBER 0 // Patch number for allied force. - #define EA_REMASTER_PATCH 2 // What version of the remaster source we are integrated with. + #define EA_REMASTER_PATCH 3 // What version of the remaster source we are integrated with. static char tmp[512]; #ifdef _DEBUG diff --git a/code/redalert/vessel.cpp b/code/redalert/vessel.cpp index bb5a316..e4e621f 100644 --- a/code/redalert/vessel.cpp +++ b/code/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/code/redalert/vessel.h b/code/redalert/vessel.h index dc383d2..a57ffff 100644 --- a/code/redalert/vessel.h +++ b/code/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;