From 555bafa187b4b93c64689225946a685f8802fd82 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 --- redalert/adata.cpp | 4 +- redalert/aircraft.cpp | 19 +++++-- redalert/anim.cpp | 10 +++- redalert/building.cpp | 30 +++++++--- redalert/cell.cpp | 13 ++++- redalert/defines.h | 9 +++ redalert/display.cpp | 6 +- redalert/dllinterface.cpp | 84 ++++++++++++++++++++++++++-- redalert/dllinterface.h | 3 +- redalert/event.cpp | 6 +- redalert/foot.cpp | 14 ++++- redalert/house.cpp | 102 +++++++++++++++++++++++++++++----- redalert/infantry.cpp | 15 ++++- redalert/inicode.cpp | 3 + redalert/init.cpp | 10 ++++ redalert/logic.cpp | 6 +- redalert/map.cpp | 25 +++++++-- redalert/map.h | 3 +- redalert/object.cpp | 8 +++ redalert/object.h | 1 + redalert/radar.cpp | 6 +- redalert/scenario.cpp | 3 + redalert/special.cpp | 1 + redalert/special.h | 15 ++++- redalert/startup.cpp | 4 ++ redalert/super.cpp | 4 +- redalert/super.h | 2 +- redalert/techno.cpp | 88 ++++++++++++++++++++--------- redalert/techno.h | 6 +- redalert/unit.cpp | 54 ++++++++++++++---- redalert/vessel.cpp | 28 +++++++++- redalert/vessel.h | 1 + tiberiandawn/adata.cpp | 4 +- tiberiandawn/aircraft.cpp | 44 ++++++++++++--- tiberiandawn/aircraft.h | 10 +++- tiberiandawn/anim.cpp | 17 +++++- tiberiandawn/bdata.cpp | 2 +- tiberiandawn/building.cpp | 87 +++++++++++++++++++++++------ tiberiandawn/building.h | 1 + tiberiandawn/cdata.cpp | 4 +- tiberiandawn/cell.cpp | 42 ++++++++++---- tiberiandawn/cell.h | 2 +- tiberiandawn/defines.h | 9 +++ tiberiandawn/display.cpp | 16 ++++-- tiberiandawn/dllinterface.cpp | 44 +++++++++++++-- tiberiandawn/dllinterface.h | 3 +- tiberiandawn/drive.cpp | 12 +++- tiberiandawn/event.cpp | 6 +- tiberiandawn/foot.cpp | 18 +++++- tiberiandawn/house.cpp | 62 ++++++++++++++++++--- tiberiandawn/idata.cpp | 2 +- tiberiandawn/infantry.cpp | 19 +++++-- tiberiandawn/ini.cpp | 46 +++++++++++++++ tiberiandawn/logic.cpp | 7 ++- tiberiandawn/map.cpp | 39 +++++++++---- tiberiandawn/object.cpp | 4 ++ tiberiandawn/object.h | 1 + tiberiandawn/radar.cpp | 6 +- tiberiandawn/reinf.cpp | 42 ++++++++++++-- tiberiandawn/scenario.cpp | 18 ++++++ tiberiandawn/special.h | 14 ++++- tiberiandawn/team.cpp | 9 ++- tiberiandawn/techno.cpp | 34 +++++++++--- tiberiandawn/techno.h | 3 + tiberiandawn/turret.cpp | 57 +++++++++++++++++++ tiberiandawn/turret.h | 1 + tiberiandawn/unit.cpp | 33 +++++++++-- 67 files changed, 1096 insertions(+), 205 deletions(-) diff --git a/redalert/adata.cpp b/redalert/adata.cpp index e115a9249..67d017bd6 100644 --- a/redalert/adata.cpp +++ b/redalert/adata.cpp @@ -335,7 +335,7 @@ static AnimTypeClass const LZSmoke(ANIM_LZ_SMOKE, // Animation number. 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); @@ -1048,7 +1048,7 @@ static AnimTypeClass const OilFieldBurn(ANIM_OILFIELD_BURN, // Animation number. 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 273721cc3..a30782321 100644 --- a/redalert/aircraft.cpp +++ b/redalert/aircraft.cpp @@ -2021,9 +2021,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 @@ -2448,7 +2456,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; } @@ -3148,7 +3156,7 @@ MoveType AircraftClass::Can_Enter_Cell(CELL cell, FacingType) const ObjectClass const* occupier = cellptr->Cell_Occupier(); 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)))) { @@ -3208,9 +3216,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)) + if (Map.In_Radar(actualcell) && (Session.Type != GAME_NORMAL || Map[newcell].Is_Visible(PlayerPtr)) && Cell_Seems_Ok(newcell, true)) { int dist; if (altcoord != 0) { diff --git a/redalert/anim.cpp b/redalert/anim.cpp index 2ea8f72c6..b7c8abd24 100644 --- a/redalert/anim.cpp +++ b/redalert/anim.cpp @@ -579,7 +579,7 @@ AnimClass::AnimClass(AnimType animnum, COORDINATE coord, unsigned char timedelay AnimClass::Unlimbo(coord); - VisibleFlags = 0xffff; + VisibleFlags = static_cast(-1); /* ** Drop zone smoke always reveals the map around itself. @@ -820,10 +820,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; } } @@ -1181,8 +1181,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 bdb820bdc..fbdb90597 100644 --- a/redalert/building.cpp +++ b/redalert/building.cpp @@ -174,7 +174,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass* from, RadioMessageTy 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)) + || (!ScenarioInit && Class->Type != STRUCT_REFINERY && In_Radio_Contact())) return (RADIO_NEGATIVE); switch (Class->Type) { case STRUCT_AIRSTRIP: @@ -201,7 +201,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass* from, RadioMessageTy if (from->What_Am_I() == RTTI_UNIT && *((UnitClass*)from) == UNIT_HARVESTER && (ScenarioInit || !Is_Something_Attached())) { - return (RADIO_ROGER); + return ((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE); } break; @@ -2623,13 +2623,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; } @@ -3780,9 +3788,17 @@ int BuildingClass::Mission_Deconstruction(void) } } - if (House->IsPlayerControl) { - Sound_Effect(VOC_CASHTURN, Coord); - } +// 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 5cb2bc27c..8c3491371 100644 --- a/redalert/cell.cpp +++ b/redalert/cell.cpp @@ -2859,9 +2859,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 82cfe764f..cc03042d7 100644 --- a/redalert/defines.h +++ b/redalert/defines.h @@ -3636,6 +3636,15 @@ typedef enum OptionControlType : char OPTION_COUNT } OptionControlType; +/**************************************************************************** +** Used to store firing data for a unit. +*/ +typedef struct +{ + COORDINATE Center; + int Distance; +} FireDataType; + #define size_of(typ, id) sizeof(((typ*)0)->id) #define MAX_LOG_LEVEL 10 diff --git a/redalert/display.cpp b/redalert/display.cpp index 47802a2a5..8cf690d31 100644 --- a/redalert/display.cpp +++ b/redalert/display.cpp @@ -3189,11 +3189,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 802763492..aacbd4a21 100644 --- a/redalert/dllinterface.cpp +++ b/redalert/dllinterface.cpp @@ -479,6 +479,8 @@ bool MPSuperWeaponDisable = false; bool ShareAllyVisibility = true; bool UseGlyphXStartLocations = true; +SpecialClass* SpecialBackup = NULL; + int GetRandSeed() { #if 0 @@ -870,6 +872,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 */ @@ -914,6 +918,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; } @@ -1738,6 +1749,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; @@ -1932,6 +1950,14 @@ 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); } @@ -1978,6 +2004,11 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, 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(); @@ -2188,6 +2219,11 @@ void DLLExportClass::Init(void) CurrentLocalPlayerIndex = 0; MessagesSent.clear(); + + if (SpecialBackup == NULL) { + SpecialBackup = new SpecialClass; + } + memcpy(SpecialBackup, &Special, sizeof(SpecialClass)); } /************************************************************************************************** @@ -2203,6 +2239,9 @@ void DLLExportClass::Init(void) **************************************************************************************************/ void DLLExportClass::Shutdown(void) { + delete SpecialBackup; + SpecialBackup = NULL; + for (int i = 0; i < ModSearchPaths.Count(); i++) { delete[] ModSearchPaths[i]; } @@ -3284,6 +3323,7 @@ void DLLExportClass::DLL_Draw_Intercept(int shape_number, char override_owner) { 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; @@ -4416,6 +4456,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; @@ -4429,7 +4471,10 @@ 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; @@ -4568,6 +4613,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; @@ -4582,7 +4629,14 @@ 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; @@ -8395,10 +8449,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)); @@ -8480,7 +8540,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 f54a9287b..4f060ffa2 100644 --- a/redalert/dllinterface.h +++ b/redalert/dllinterface.h @@ -719,7 +719,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 @@ -731,6 +731,7 @@ struct CNCMultiplayerOptionsStruct bool MPlayerAftermathUnits; bool CaptureTheFlag; bool DestroyStructures; // New early win condition via destroying all a player's structures + bool ModernBalance; }; struct CNCSpiedInfoStruct diff --git a/redalert/event.cpp b/redalert/event.cpp index 853aea0b4..5c9e21c74 100644 --- a/redalert/event.cpp +++ b/redalert/event.cpp @@ -560,7 +560,7 @@ void EventClass::Execute(void) if (anim) { // 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 { anim->Set_Visible_Flags(1 << Data.Anim.Owner); } @@ -715,6 +715,10 @@ 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 e8a4d821f..0c458fc10 100644 --- a/redalert/foot.cpp +++ b/redalert/foot.cpp @@ -1776,7 +1776,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 bb74f3ac4..a1819bb42 100644 --- a/redalert/house.cpp +++ b/redalert/house.cpp @@ -984,7 +984,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; @@ -1020,6 +1020,9 @@ void HouseClass::AI(void) if (IsToDie && BorrowedTime == 0) { IsToDie = false; Blowup_All(); + if (Session.Type == GAME_GLYPHX_MULTIPLAYER) { + MPlayer_Defeated(); + } } /* @@ -1227,7 +1230,7 @@ void HouseClass::AI(void) Super_Weapon_Handler(); #endif #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 { @@ -1918,13 +1921,19 @@ 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 @@ -1940,6 +1949,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 + } + } } /*********************************************************************************************** @@ -3866,6 +3891,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 @@ -4209,7 +4241,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) { @@ -4228,7 +4267,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--; @@ -4243,7 +4288,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--; @@ -4261,7 +4312,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) { @@ -4284,7 +4342,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); @@ -8148,6 +8213,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++) { @@ -8155,9 +8227,13 @@ 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 a471cbf67..347c80f16 100644 --- a/redalert/infantry.cpp +++ b/redalert/infantry.cpp @@ -513,6 +513,9 @@ int InfantryClass::Shape_Number(WindowNumberType window) const *- 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 @@ -699,7 +702,7 @@ void InfantryClass::Per_Cell_Process(PCPType why) #endif // 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) { @@ -1497,7 +1500,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 @@ -3977,6 +3980,14 @@ 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 2874578c4..72c8bb08a 100644 --- a/redalert/inicode.cpp +++ b/redalert/inicode.cpp @@ -284,6 +284,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 f2b48b982..0b53105d8 100644 --- a/redalert/init.cpp +++ b/redalert/init.cpp @@ -424,6 +424,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 3dd1ddb35..3ce427457 100644 --- a/redalert/logic.cpp +++ b/redalert/logic.cpp @@ -344,6 +344,7 @@ void LogicClass::AI(void) */ for (index = 0; index < Count(); index++) { ObjectClass* obj = (*this)[index]; + int count = Count(); BStart(BENCH_AI); obj->AI(); @@ -383,8 +384,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 e46ced1f4..eb43e8123 100644 --- a/redalert/map.cpp +++ b/redalert/map.cpp @@ -1333,6 +1333,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++) { @@ -1714,11 +1724,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)); @@ -1744,7 +1754,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; @@ -1982,7 +1992,12 @@ 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; @@ -2076,7 +2091,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 322b45dad..9897c7d0f 100644 --- a/redalert/map.h +++ b/redalert/map.h @@ -66,7 +66,8 @@ class MapClass : public GScreenClass SpeedType speed, int zone = -1, MZoneType check = MZONE_NORMAL, - bool checkflagged = false) const; + 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/object.cpp b/redalert/object.cpp index 164fbe802..1dc96ccc4 100644 --- a/redalert/object.cpp +++ b/redalert/object.cpp @@ -606,6 +606,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 d97713700..a46642a67 100644 --- a/redalert/object.h +++ b/redalert/object.h @@ -202,6 +202,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 60a6c2a45..6b2a67d17 100644 --- a/redalert/radar.cpp +++ b/redalert/radar.cpp @@ -260,7 +260,8 @@ 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) { @@ -277,7 +278,8 @@ 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/scenario.cpp b/redalert/scenario.cpp index 5aae2aa43..6cdf09388 100644 --- a/redalert/scenario.cpp +++ b/redalert/scenario.cpp @@ -2421,6 +2421,9 @@ bool Read_Scenario_INI(char* fname, bool) UnitClass::Read_INI(ini); Call_Back(); + AircraftClass::Read_INI(ini); + Call_Back(); + VesselClass::Read_INI(ini); Call_Back(); diff --git a/redalert/special.cpp b/redalert/special.cpp index bb89c1a7d..d96b6c8c2 100644 --- a/redalert/special.cpp +++ b/redalert/special.cpp @@ -81,6 +81,7 @@ void SpecialClass::Init(void) UseMCVDeploy = false; IsMCVDeploy = false; IsEarlyWin = false; + ModernBalance = false; } /*********************************************************************************************** diff --git a/redalert/special.h b/redalert/special.h index 540a4d328..b9cb69a42 100644 --- a/redalert/special.h +++ b/redalert/special.h @@ -99,11 +99,24 @@ 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. }; #endif \ No newline at end of file diff --git a/redalert/startup.cpp b/redalert/startup.cpp index 24ed10b21..33e8d7e50 100644 --- a/redalert/startup.cpp +++ b/redalert/startup.cpp @@ -893,6 +893,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 84b752b18..b9da2d73e 100644 --- a/redalert/super.cpp +++ b/redalert/super.cpp @@ -165,9 +165,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 53b5b61a1..9a4482559 100644 --- a/redalert/super.h +++ b/redalert/super.h @@ -55,7 +55,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 1c3094061..66426458b 100644 --- a/redalert/techno.cpp +++ b/redalert/techno.cpp @@ -513,6 +513,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); @@ -700,7 +719,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); @@ -1004,9 +1023,14 @@ RadioMessageType TechnoClass::Receive_Message(RadioClass* from, RadioMessageType /* ** 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; /* @@ -1118,7 +1142,7 @@ void TechnoClass::Draw_It(int x, int y, WindowNumberType window) const Class_Of().Dimensions(width, height); const bool show_health_bar = - (Strength > 0) + (Strength > 0) && !Is_Cloaked(PlayerPtr) && (Is_Selected_By_Player() || ((Rule.HealthBarDisplayMode == RulesClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) || (Rule.HealthBarDisplayMode == RulesClass::HB_ALWAYS)); @@ -1282,8 +1306,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); } @@ -1339,8 +1363,8 @@ bool TechnoClass::In_Range(ObjectClass const* target, int which, bool reciprocal 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); } @@ -1545,7 +1569,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, /* ** 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); } @@ -1574,16 +1598,12 @@ bool TechnoClass::Evaluate_Object(ThreatType method, ** 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); } /* @@ -2058,6 +2078,24 @@ bool TechnoClass::Evaluate_Object(ThreatType method, return (Weapon_Range(0) - Distance(Cell_Coord(cell))); } + 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. * * * @@ -2499,7 +2537,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, ** 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 #endif @@ -2822,9 +2860,9 @@ bool TechnoClass::Evaluate_Object(ThreatType method, 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); } @@ -4261,7 +4299,7 @@ bool TechnoClass::Evaluate_Object(ThreatType method, * 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); @@ -4277,8 +4315,8 @@ bool TechnoClass::Evaluate_Object(ThreatType method, 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 7fd03dbbb..650d483c0 100644 --- a/redalert/techno.h +++ b/redalert/techno.h @@ -295,7 +295,7 @@ class TechnoClass : public RadioClass, public FlasherClass, public StageClass, p { 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()); @@ -309,6 +309,7 @@ class TechnoClass : public RadioClass, public FlasherClass, public StageClass, p 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; @@ -382,6 +383,9 @@ class TechnoClass : public RadioClass, public FlasherClass, public StageClass, p 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 852d6094d..c487a60e2 100644 --- a/redalert/unit.cpp +++ b/redalert/unit.cpp @@ -406,6 +406,15 @@ 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. @@ -1258,6 +1267,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); } @@ -1683,7 +1697,8 @@ 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); @@ -2200,11 +2215,26 @@ 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); @@ -2247,6 +2277,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; @@ -2849,10 +2880,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); } @@ -3280,7 +3311,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,7 +3595,11 @@ 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 + 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; } @@ -3572,8 +3607,7 @@ ActionType UnitClass::What_Action(ObjectClass const* object) const /* ** 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 diff --git a/redalert/vessel.cpp b/redalert/vessel.cpp index fb20c8848..a81acf455 100644 --- a/redalert/vessel.cpp +++ b/redalert/vessel.cpp @@ -293,7 +293,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); } @@ -1180,6 +1180,32 @@ FireErrorType VesselClass::Can_Fire(TARGET target, int which) const * 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 920fb486f..57954832d 100644 --- a/redalert/vessel.h +++ b/redalert/vessel.h @@ -114,6 +114,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/tiberiandawn/adata.cpp b/tiberiandawn/adata.cpp index 03b43803c..fe3721401 100644 --- a/tiberiandawn/adata.cpp +++ b/tiberiandawn/adata.cpp @@ -312,7 +312,7 @@ static AnimTypeClass const LZSmoke(ANIM_LZ_SMOKE, // Animation number. 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); @@ -1319,7 +1319,7 @@ static AnimTypeClass const OilFieldBurn(ANIM_OILFIELD_BURN, // Animation number. 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 128e31c31..7cb049bfe 100644 --- a/tiberiandawn/aircraft.cpp +++ b/tiberiandawn/aircraft.cpp @@ -238,6 +238,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 @@ -337,6 +338,15 @@ 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. */ @@ -759,7 +769,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(); } @@ -1426,9 +1446,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); } @@ -2198,7 +2222,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; } @@ -2459,7 +2483,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)))) { @@ -2470,7 +2494,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; /* @@ -2860,8 +2884,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)) + 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) { @@ -3540,4 +3565,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 ce91f4ad9..b51913219 100644 --- a/tiberiandawn/aircraft.h +++ b/tiberiandawn/aircraft.h @@ -110,7 +110,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 @@ -161,6 +161,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. @@ -255,11 +256,16 @@ 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 61d1ff1c6..a72e7434c 100644 --- a/tiberiandawn/anim.cpp +++ b/tiberiandawn/anim.cpp @@ -630,7 +630,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. @@ -794,6 +794,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) { @@ -831,10 +841,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; } } @@ -1319,6 +1329,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 5d9a37afb..2a6a097ef 100644 --- a/tiberiandawn/bdata.cpp +++ b/tiberiandawn/bdata.cpp @@ -221,7 +221,7 @@ static BuildingTypeClass const ClassEye(STRUCT_EYE, 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 6e239f279..f6a8c75a9 100644 --- a/tiberiandawn/building.cpp +++ b/tiberiandawn/building.cpp @@ -209,7 +209,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass* from, RadioMessageTy case RADIO_CAN_LOAD: TechnoClass::Receive_Message(from, message, param); - if (BState == BSTATE_CONSTRUCTION || (!ScenarioInit && In_Radio_Contact())) + if (BState == BSTATE_CONSTRUCTION || (!ScenarioInit && Class->Type != STRUCT_REFINERY && In_Radio_Contact())) return (RADIO_NEGATIVE); switch (Class->Type) { case STRUCT_AIRSTRIP: @@ -235,7 +235,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass* from, RadioMessageTy if (from->What_Am_I() == RTTI_UNIT && *((UnitClass*)from) == UNIT_HARVESTER && (ScenarioInit || !Is_Something_Attached())) { - return (RADIO_ROGER); + return ((Contact_With_Whom() != from) ? RADIO_ROGER : RADIO_NEGATIVE); } break; @@ -1176,7 +1176,10 @@ 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); + } } } } @@ -2754,6 +2757,38 @@ 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(); @@ -3529,8 +3564,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); @@ -3841,14 +3882,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); } @@ -4102,11 +4143,19 @@ int BuildingClass::Mission_Deconstruction(void) } } - // Changed for multiplayer ST - 3/13/2019 5:31PM - if (Is_Owned_By_Player()) { - // if (IsOwnedByPlayer) { - Sound_Effect(VOC_CASHTURN, Coord); - } +// 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 @@ -4566,6 +4615,12 @@ 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 7922bb52a..1f475505d 100644 --- a/tiberiandawn/building.h +++ b/tiberiandawn/building.h @@ -209,6 +209,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 41d4650cb..4c5f1c941 100644 --- a/tiberiandawn/cdata.cpp +++ b/tiberiandawn/cdata.cpp @@ -1257,8 +1257,8 @@ static TemplateTypeClass const Patch15(TEMPLATE_PATCH15, "P15", TXT_PATCH, LAND_CLEAR, - 1, - 1, + 4, + 2, LAND_CLEAR, NULL); static TemplateTypeClass const diff --git a/tiberiandawn/cell.cpp b/tiberiandawn/cell.cpp index 0b330a02f..d4ce457ef 100644 --- a/tiberiandawn/cell.cpp +++ b/tiberiandawn/cell.cpp @@ -1951,7 +1951,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 @@ -2023,21 +2023,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; @@ -2469,9 +2478,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 2b916a92d..cf4625475 100644 --- a/tiberiandawn/cell.h +++ b/tiberiandawn/cell.h @@ -225,7 +225,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 05c3476fa..8635f51fe 100644 --- a/tiberiandawn/defines.h +++ b/tiberiandawn/defines.h @@ -2844,6 +2844,15 @@ typedef enum OptionControlType : char OPTION_COUNT } 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 98b9af6b6..60e3b833f 100644 --- a/tiberiandawn/display.cpp +++ b/tiberiandawn/display.cpp @@ -1804,8 +1804,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)) { @@ -3920,12 +3921,17 @@ void DisplayClass::Mouse_Left_Release(CELL cell, int x, int y, ObjectClass* obje 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 a4994aacc..84b6bb477 100644 --- a/tiberiandawn/dllinterface.cpp +++ b/tiberiandawn/dllinterface.cpp @@ -725,6 +725,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 @@ -1377,7 +1378,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(); @@ -1770,6 +1773,11 @@ extern "C" __declspec(dllexport) bool __cdecl CNC_Save_Load(bool save, 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(); @@ -2980,6 +2988,7 @@ void DLLExportClass::DLL_Draw_Intercept(int shape_number, 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; @@ -4030,6 +4039,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; @@ -4202,6 +4213,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; @@ -4359,6 +4372,8 @@ bool DLLExportClass::Get_Sidebar_State(uint64 player_id, unsigned char* buffer_i return true; } +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; @@ -4387,7 +4402,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)) { @@ -4480,7 +4495,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]); @@ -5126,7 +5141,7 @@ bool DLLExportClass::Place(uint64 player_id, int buildable_type, int buildable_i 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. @@ -7417,10 +7432,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)) { @@ -7534,7 +7557,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 ed2c53cf9..48212465e 100644 --- a/tiberiandawn/dllinterface.h +++ b/tiberiandawn/dllinterface.h @@ -719,7 +719,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 @@ -731,6 +731,7 @@ struct CNCMultiplayerOptionsStruct bool MPlayerAftermathUnits; bool CaptureTheFlag; bool DestroyStructures; // New early win condition via destroying all a player's structures + bool ModernBalance; }; struct CNCSpiedInfoStruct diff --git a/tiberiandawn/drive.cpp b/tiberiandawn/drive.cpp index 445d3b730..261d85a93 100644 --- a/tiberiandawn/drive.cpp +++ b/tiberiandawn/drive.cpp @@ -959,9 +959,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 65858f3c0..d3e9a9899 100644 --- a/tiberiandawn/event.cpp +++ b/tiberiandawn/event.cpp @@ -469,7 +469,7 @@ void EventClass::Execute(void) if (anim) { // 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 { anim->Set_Visible_Flags(1 << Data.Anim.Owner); } @@ -584,6 +584,10 @@ void EventClass::Execute(void) 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 78998e98e..022e211ca 100644 --- a/tiberiandawn/foot.cpp +++ b/tiberiandawn/foot.cpp @@ -1404,6 +1404,8 @@ void FootClass::Per_Cell_Process(bool center) } // } + Map[Coord_Cell(Coord)].Goodie_Check(this, true); + TechnoClass::Per_Cell_Process(center); } @@ -1613,8 +1615,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 532666e49..f8cd7e507 100644 --- a/tiberiandawn/house.cpp +++ b/tiberiandawn/house.cpp @@ -559,16 +559,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) @@ -950,6 +952,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(); + } } /* @@ -1410,7 +1418,7 @@ void HouseClass::AI(void) } #endif - if (GameToPlay != GAME_NORMAL) { + if (GameToPlay != GAME_NORMAL && Class->House != HOUSE_JP) { Check_Pertinent_Structures(); } @@ -1651,8 +1659,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 @@ -1668,6 +1683,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 } /*********************************************************************************************** @@ -4033,6 +4065,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 @@ -4765,6 +4804,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 0941f56bf..052f18392 100644 --- a/tiberiandawn/idata.cpp +++ b/tiberiandawn/idata.cpp @@ -394,7 +394,7 @@ static InfantryTypeClass const E5(INFANTRY_E5, // Infantry type number 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 | HOUSEF_MULTI3 | HOUSEF_MULTI4 | HOUSEF_MULTI5 diff --git a/tiberiandawn/infantry.cpp b/tiberiandawn/infantry.cpp index 8ce71de34..9c269996f 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) { @@ -1100,7 +1100,16 @@ 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); } /* @@ -1668,7 +1677,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 @@ -2458,7 +2467,9 @@ 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 0109f5d27..66b646d9a 100644 --- a/tiberiandawn/ini.cpp +++ b/tiberiandawn/ini.cpp @@ -317,6 +317,10 @@ 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); } @@ -448,6 +452,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). */ @@ -542,6 +549,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); @@ -561,6 +570,35 @@ 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) @@ -848,6 +886,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). */ @@ -937,6 +978,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 61267f8e1..ea1640a2b 100644 --- a/tiberiandawn/logic.cpp +++ b/tiberiandawn/logic.cpp @@ -203,6 +203,7 @@ void LogicClass::AI(void) */ for (index = 0; index < Count(); index++) { ObjectClass* obj = (*this)[index]; + int count = Count(); obj->AI(); @@ -210,9 +211,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 409ce422b..7300e982b 100644 --- a/tiberiandawn/map.cpp +++ b/tiberiandawn/map.cpp @@ -659,11 +659,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); } } @@ -1253,6 +1251,15 @@ void MapClass::Logic(void) 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); @@ -1261,11 +1268,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; @@ -1275,7 +1288,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. @@ -1308,6 +1322,11 @@ void MapClass::Logic(void) } } } + TiberiumSpread[pick] = TiberiumSpread[TiberiumSpreadCount - 1]; + TiberiumSpreadCount--; + if (TiberiumSpreadCount <= 0) { + break; + } } } TiberiumSpreadCount = 0; @@ -1702,10 +1721,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; @@ -1733,7 +1752,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)) { diff --git a/tiberiandawn/object.cpp b/tiberiandawn/object.cpp index ff105a899..01abdafad 100644 --- a/tiberiandawn/object.cpp +++ b/tiberiandawn/object.cpp @@ -550,6 +550,10 @@ 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; diff --git a/tiberiandawn/object.h b/tiberiandawn/object.h index 1f5b7c811..e978f79c7 100644 --- a/tiberiandawn/object.h +++ b/tiberiandawn/object.h @@ -178,6 +178,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 e654430e7..fbfb3e051 100644 --- a/tiberiandawn/radar.cpp +++ b/tiberiandawn/radar.cpp @@ -230,7 +230,8 @@ 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) { @@ -247,7 +248,8 @@ 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 e402e5553..88ae9c92b 100644 --- a/tiberiandawn/reinf.cpp +++ b/tiberiandawn/reinf.cpp @@ -344,6 +344,7 @@ bool Do_Reinforcements(TeamTypeClass* teamtype) */ case SOURCE_AIR: { AircraftClass* thisone = (AircraftClass*)object; + TARGET target = TARGET_NONE; while (thisone) { AircraftClass* next = (AircraftClass*)thisone->Next; @@ -351,25 +352,58 @@ 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 3ed3c8e64..2452b9202 100644 --- a/tiberiandawn/scenario.cpp +++ b/tiberiandawn/scenario.cpp @@ -794,4 +794,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 6cc081708..957ecd3e1 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; } /* @@ -249,11 +250,22 @@ 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]; }; #endif \ No newline at end of file diff --git a/tiberiandawn/team.cpp b/tiberiandawn/team.cpp index c8ec05d84..3d8ae0ddc 100644 --- a/tiberiandawn/team.cpp +++ b/tiberiandawn/team.cpp @@ -1251,7 +1251,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 6f1310268..89a0f0c04 100644 --- a/tiberiandawn/techno.cpp +++ b/tiberiandawn/techno.cpp @@ -1005,7 +1005,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()) + const bool show_health_bar = (Strength > 0) && !Is_Cloaked(PlayerPtr) && (Is_Selected_By_Player() || ((Special.HealthBarDisplayMode == SpecialClass::HB_DAMAGED) && (Strength < Techno_Type_Class()->MaxStrength)) @@ -1173,7 +1173,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); } @@ -1227,8 +1228,8 @@ bool TechnoClass::In_Range(ObjectClass const* target, int which, bool reciprocal 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); } @@ -1356,7 +1357,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)) return (false); /* @@ -2117,7 +2118,7 @@ FireErrorType TechnoClass::Can_Fire(TARGET target, int which) const // 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) { + && ((TechnoClass*)object)->Is_Cloaked(this)) { return (FIRE_CANT); } @@ -2438,7 +2439,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); @@ -2446,7 +2447,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); @@ -4508,6 +4509,8 @@ 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; } } } @@ -4666,6 +4669,21 @@ bool TechnoClass::Is_Owned_By_Player(HouseClass* player) const return (House == player) ? true : false; } +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); +} + #ifdef USE_RA_AI /* diff --git a/tiberiandawn/techno.h b/tiberiandawn/techno.h index 6cb6e9e01..5c77bca5b 100644 --- a/tiberiandawn/techno.h +++ b/tiberiandawn/techno.h @@ -287,6 +287,9 @@ class TechnoClass : public RadioClass, 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 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 eb55f9eac..bc95fd2e3 100644 --- a/tiberiandawn/turret.cpp +++ b/tiberiandawn/turret.cpp @@ -354,6 +354,63 @@ 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 82e5dc0ed..7808c7214 100644 --- a/tiberiandawn/turret.h +++ b/tiberiandawn/turret.h @@ -74,6 +74,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/unit.cpp b/tiberiandawn/unit.cpp index 3dacd1026..c73502427 100644 --- a/tiberiandawn/unit.cpp +++ b/tiberiandawn/unit.cpp @@ -334,7 +334,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; @@ -447,6 +447,15 @@ 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. */ @@ -1209,6 +1218,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); } @@ -1768,7 +1782,8 @@ 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); @@ -2297,7 +2312,7 @@ int UnitClass::Tiberium_Check(CELL& center, int x, int y) // using function for IsVisible so we have different results for different players - JAS 2019/09/30 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); @@ -2321,6 +2336,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; @@ -2645,7 +2661,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); @@ -2839,7 +2862,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) {