diff --git a/CREDITS.md b/CREDITS.md index bdb498d967..1f56566b92 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -151,6 +151,7 @@ This page lists all the individual contributions to the project by their author. - Warhead activation target health thresholds enhancements - Event 606: AttachEffect is attaching to a Techno - Linked superweapons + - Engineer logics on Warheads - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0ed79088c9..90ae3e8082 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1560,6 +1560,7 @@ FLHKEY.BurstN= ; integer - Forward,Lateral,Height. FLHKey refers to weapon-spec - `ForceWeapon.Cloaked` forces specified weapon to be used against any cloaked targets. - `ForceWeapon.Disguised` forces specified weapon to be used against any disguised targets. - `ForceWeapon.UnderEMP` forces specified weapon to be used if the target is under EMP effect. + - `ForceWeapon.Capture` forces specified weapon to be used if the target building is capturable. - `ForceWeapon.InRange` forces specified a list of weapons to be used once the target is within their `Range`. If `ForceWeapon.InRange.TechnoOnly` set to true, it'll only be forced on TechnoTypes like other forced weapons, otherwise it'll also be forced when attacking empty grounds. The first weapon in the listed order satisfied will be selected. Can be applied to both ground and air target if `ForceAAWeapon.InRange` is not set. - `ForceAAWeapon.InRange` does the same thing but only for air target. Taking priority to `ForceWeapon.InRange`, which means that it can only be applied to ground target when they're both set. - `Force(AA)Weapon.InRange.Overrides` overrides the range when decides which weapon to use. Value from position matching the position from `Force(AA)Weapon.InRange` is used if found, or the weapon's own `Range` if not found or set to a value below 0. @@ -1577,6 +1578,7 @@ ForceWeapon.Naval.Decloaked=-1 ; integer, -1 to disable ForceWeapon.Cloaked=-1 ; integer, -1 to disable ForceWeapon.Disguised=-1 ; integer, -1 to disable ForceWeapon.UnderEMP=-1 ; integer, -1 to disable +ForceWeapon.Capture=-1 ; integer, -1 to disable ForceWeapon.InRange= ; List of integers ForceWeapon.InRange.Overrides= ; List of floating-point values ForceWeapon.InRange.ApplyRangeModifiers=false ; boolean @@ -2268,6 +2270,23 @@ DetonateOnAllMapObjects.RequireVerses=false ; boolean While this feature can provide better performance than a large `CellSpread` value, it still has potential to slow down the game, especially if used in conjunction with things like animations, alpha lights etc. Modder discretion and use of the filter keys (`AffectTargets/Houses/Types` etc.) is advised. ``` +### Engineer logics on Warheads + +- Now any `InfantryType`, `VehicleType`, `BuildingType` or `AircraftType` can execute some operations engineers do without loosing the firer in the process. +- `FakeEngineer.CanRepairBridges`, if set to true, when a building with `BridgeRepairHut=yes` linked to a bridge is affected by the Warhead then all destroyed bridge sections will be fixed. +- `FakeEngineer.CanDestroyBridges`, if set to true, when a building with `BridgeRepairHut=yes` linked to a bridge is affected by the Warhead then all the bridge will be destroyed. +- `FakeEngineer.CanCaptureBuildings`, if set to true, a building with `Capturable=true` or `NeedsEngineer=true` is affected by the Warhead then the building will be captured by the house's firer. +- `FakeEngineer.DisarmBombs`, if set to true, an attached bomb will be removed if the target is affected by the Warhead. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +FakeEngineer.CanRepairBridges=false ; boolean +FakeEngineer.CanDestroyBridges=false ; boolean +FakeEngineer.CanCaptureBuildings=false ; boolean +FakeEngineer.DisarmBombs=false ; boolean +``` + ### Fire weapon when Warhead kills something - `KillWeapon` will be fired at the target TechnoType's location once it's killed by this Warhead. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index e6dee4bdbe..69f005e0a3 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -428,6 +428,7 @@ New: - [Units can customize the attack voice that plays when using more weapons](New-or-Enhanced-Logics.md#multi-voiceattack) (by FlyStar) - Customize squid grapple animation (by NetsuNegi) - [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl) +- Engineer logics on Warheads (by FS-21) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 7521101d2f..3f20048994 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -10,6 +10,7 @@ #include #include +#include TechnoExt::ExtContainer TechnoExt::ExtMap; UnitClass* TechnoExt::Deployer = nullptr; @@ -632,6 +633,69 @@ AircraftTypeClass* TechnoExt::GetAircraftTypeExtra(AircraftClass* pAircraft) } +bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTarget, bool checkBridge, bool checkCapturableBuilding, bool checkAttachedBombs) +{ + const auto pTypeExt = TechnoExt::ExtMap.Find(pThis)->TypeExtData; + + // Force weapon check + int nWeaponIndex = pTypeExt->SelectForceWeapon(pThis, pTarget); + + if (nWeaponIndex < 0) // Multi weapon check + nWeaponIndex = pTypeExt->SelectMultiWeapon(pThis, pTarget); + + if (nWeaponIndex < 0) // Vanilla weapon check + nWeaponIndex = pThis->SelectWeapon(pTarget); + + if (nWeaponIndex < 0) + return false; + + const auto pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + + if (!pWeapon || !pTarget) + return false; + + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + bool canAffectCapturableBuildings = false; + bool canAffectBridges = false; + bool canAffectAttachedBombs = false; + + // Check if an attached bomb can be disarmed + if (checkAttachedBombs + && pWHExt->FakeEngineer_BombDisarm + && pTarget->AttachedBomb) + { + canAffectAttachedBombs = true; + } + + const auto pBuilding = abstract_cast(pTarget); + bool isBuilding = pBuilding && pBuilding->IsAlive && pBuilding->Health > 0; + + // Check if a Bridge Repair Hut can be affected + if (checkBridge && isBuilding && pBuilding->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pBuilding->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if ((isBridgeDamaged && pWHExt->FakeEngineer_CanRepairBridges) + || (!isBridgeDamaged && pWHExt->FakeEngineer_CanDestroyBridges)) + { + canAffectBridges = true; + } + } + + // Check if a capturable building can be affected + if (checkCapturableBuilding + && isBuilding + && pWHExt->FakeEngineer_CanCaptureBuildings + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer) + && !pThis->Owner->IsAlliedWith(pBuilding)) // Anti-crash check + { + canAffectCapturableBuildings = true; + } + + return canAffectCapturableBuildings || canAffectBridges || canAffectAttachedBombs; +} + void TechnoExt::ExtData::ResetDelayedFireTimer() { this->DelayedFireTimer.Stop(); diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 6493623e47..e6b91cda62 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -265,6 +265,7 @@ class TechnoExt static bool IsHealthInThreshold(TechnoClass* pObject, double min, double max); static UnitTypeClass* GetUnitTypeExtra(UnitClass* pUnit); static AircraftTypeClass* GetAircraftTypeExtra(AircraftClass* pAircraft); + static bool CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pBuilding, bool checkBridge = false, bool checkCapturableBuilding = false, bool checkAttachedBombs = false); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index 66a06fe8a6..89c02762b2 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -60,6 +61,33 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) } } + auto const pBuilding = abstract_cast(pThis); + + // Repair/Destroy bridges at Bridge Repair Huts buildings + if (pBuilding && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + const bool isBridgeDestroyed = MapClass::Instance.IsLinkedBridgeDestroyed(CellClass::Coord2Cell(pThis->GetCenterCoords())); + bool destroyBridge = !isBridgeDestroyed && pWHExt->FakeEngineer_CanRepairBridges ? false : pWHExt->FakeEngineer_CanDestroyBridges; + WarheadTypeExt::DetonateAtBridgeRepairHut(pThis, nullptr, pSourceHouse, destroyBridge); + } + + // Capture enemy buildings + if (pBuilding && pWHExt->FakeEngineer_CanCaptureBuildings + && !pSourceHouse->IsAlliedWith(pTargetHouse) + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + // Send engineer's "enter" event + auto const pTag = pBuilding->AttachedTag; + if (args->Attacker && pTag) + pTag->RaiseEvent(TriggerEvent::EnteredBy, args->Attacker, CellStruct::Empty); + + reinterpret_cast(0x448260)(pBuilding, pSourceHouse, true); + } + + // Disarm bomb + if (pThis->AttachedBomb && pWHExt->FakeEngineer_BombDisarm) + pThis->AttachedBomb->Disarm(); + // Raise Combat Alert if (RulesExt::Global()->CombatAlert && damage > 1) { diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 10daf28aac..14c67f816a 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -1,7 +1,224 @@ #include "Body.h" +#include +#include +#include // Cursor & target acquisition stuff not directly tied to other features can go here. +#pragma region FakeEngineer + +// Skipping the next 2 small checks permits the AI to target structures, if used correctly (for example with the compation of AttackFriendlies) +DEFINE_HOOK(0x6F85C8, TechnoClass_EvaluateObject_RemovingWhatMakesGuardModeAutotargetSelectionUnableToTargetStructures, 0x7) +{ + enum { skipCode = 0x74049F }; + + return 0x6F866D; +} + +// Skipping the Immune check +DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) +{ + enum { ForceNewValue = 0x74049F }; + + GET(TechnoClass* const, pThis, ESI); + GET(TechnoClass* const, pTarget, EDI); + + const auto pBuilding = abstract_cast(pTarget); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); + + if (canBeAttacked) + { + if (canBeDefused) + R->EBX(Action::DisarmBomb); + else + R->EBX(Action::Attack); + + return ForceNewValue; + } + + return 0; +} + +// Skipping the Immune check +DEFINE_HOOK(0x74049A, UnitClass_WhatAction_Immune_FakeEngineer2, 0x5) +{ + enum { ForceNewValue = 0x74049F }; + + GET(TechnoClass* const, pThis, ESI); + GET(TechnoClass* const, pTarget, EDI); + + const auto pBuilding = abstract_cast(pTarget); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); + + if (canBeAttacked) + { + if (canBeDefused) + R->EBX(Action::DisarmBomb); + else + R->EBX(Action::Attack); + + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x417F68 }; + + GET(TechnoClass* const, pThis, ESI); + GET(BuildingClass* const, pBuilding, EDI); + + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + + if (canBeAttacked) + return ForceNewValue; + + return 0; +} + +DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x44752C }; + + GET(TechnoClass* const, pThis, ESI); + GET(BuildingClass* const, pBuilding, EBP); + + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); + + if (canBeAttacked) + { + if (canBeDefused) + R->EBP(Action::DisarmBomb); + else + R->EBP(Action::Attack); + + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x51F17E }; + + GET(TechnoClass* const, pThis, EDI); + GET(BuildingClass* const, pBuilding, ESI); + + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); + + if (canBeAttacked) + { + if (canBeDefused) + R->EBP(Action::DisarmBomb); + else + R->EBP(Action::Attack); + + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x6FC31C, TechnoClass_CanFire_ForceWeapon, 0xF) +{ + enum { UseWeaponIndex = 0x0 }; + + GET(AbstractClass* const, pThis, ESI); + GET(AbstractClass* const, pTarget, EBX); + REF_STACK(int, nWeaponIdx, STACK_OFFSET(0x10, 0xC)); + + const auto pFirer = abstract_cast(pThis); + const auto pVictim = abstract_cast(pTarget); + + if (!pFirer || !pVictim) + return 0; + + const auto pTypeExt = TechnoExt::ExtMap.Find(pFirer)->TypeExtData; + + // Force weapon check + int newIndex = pTypeExt->SelectForceWeapon(pFirer, pTarget); + + if (newIndex >= 0) + { + nWeaponIdx = newIndex; + } + else + { + // Multi weapon check + newIndex = pTypeExt->SelectMultiWeapon(pFirer, pTarget); + + if (newIndex >= 0) + nWeaponIdx = newIndex; + } + + return 0; +} + +DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer_CanCaptureBuildings, 0x5) +{ + enum { ForceNewValue = 0x6FCBA6 }; + + GET_STACK(AbstractClass* const, pThis, STACK_OFFSET(0x10, 0x18)); + GET_STACK(AbstractClass* const, pTarget, STACK_OFFSET(0x10, 0x8)); + GET_STACK(int, nWeaponIdx, STACK_OFFSET(0x10, 0xC)); + + const auto pFirer = abstract_cast(pThis); + const auto pVictim = abstract_cast(pTarget); + + if (!pFirer || !pVictim) + return 0; + + const auto pWeapon = pFirer->GetWeapon(nWeaponIdx)->WeaponType; + if (!pWeapon) + return 0; + + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + const int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pFirer); + const int currentRange = pFirer->DistanceFrom(pVictim); + + if (pVictim->AttachedBomb + && pWHExt->FakeEngineer_BombDisarm) + { + if (currentRange <= weaponRange) + R->EAX(FireError::OK); + else + R->EAX(FireError::RANGE); // Out of range + + return ForceNewValue; + } + + const auto pBuilding = abstract_cast(pTarget); + + if (!pBuilding + || !pBuilding->IsAlive + || pBuilding->Health <= 0 + || pFirer->Owner->IsAlliedWith(pVictim) + || (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer)) + { + return 0; + } + + if (!pWHExt->FakeEngineer_CanCaptureBuildings) + return 0; + + if (currentRange <= weaponRange) + R->EAX(FireError::OK); + else + R->EAX(FireError::RANGE); // Out of range + + return ForceNewValue; +} + +#pragma endregion + #pragma region TargetAcquisition DEFINE_HOOK(0x7098B9, TechnoClass_TargetSomethingNearby_AutoFire, 0x6) diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 85ae427c4b..479746bef6 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -478,6 +478,19 @@ bool TechnoExt::MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* cons } } + if (pTechnoType->Immune) + { + bool canBypassImmune = WarheadTypeExt::ExtMap.Find(pWH)->FakeEngineer_CanCaptureBuildings + || WarheadTypeExt::ExtMap.Find(pWH)->FakeEngineer_CanRepairBridges + || WarheadTypeExt::ExtMap.Find(pWH)->FakeEngineer_CanDestroyBridges + || WarheadTypeExt::ExtMap.Find(pWH)->FakeEngineer_BombDisarm; + + if (canBypassImmune) + return true; + + return false; + } + if (GeneralUtils::GetWarheadVersusArmor(pWH, pTechno, pTechnoType) == 0.0) return false; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index f32b4669f9..457955b0b0 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -64,6 +64,15 @@ int TechnoTypeExt::ExtData::SelectForceWeapon(TechnoClass* pThis, AbstractClass* { forceWeaponIndex = this->ForceWeapon_UnderEMP; } + else if (this->ForceWeapon_Capture >= 0) + { + if (const auto pBuildingType = abstract_cast(pTargetType)) + { + if ((pBuildingType->Capturable || pBuildingType->NeedsEngineer) + && !pThis->Owner->IsAlliedWith(pTargetTechno->Owner)) + forceWeaponIndex = this->ForceWeapon_Capture; + } + } } if (forceWeaponIndex == -1 @@ -738,6 +747,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ForceAAWeapon_Infantry.Read(exINI, pSection, "ForceAAWeapon.Infantry"); this->ForceAAWeapon_Units.Read(exINI, pSection, "ForceAAWeapon.Units"); this->ForceAAWeapon_Aircraft.Read(exINI, pSection, "ForceAAWeapon.Aircraft"); + this->ForceWeapon_Capture.Read(exINI, pSection, "ForceWeapon.Capture"); this->ForceWeapon_Check = ( this->ForceWeapon_Naval_Decloaked >= 0 @@ -755,6 +765,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) || this->ForceAAWeapon_Infantry >= 0 || this->ForceAAWeapon_Units >= 0 || this->ForceAAWeapon_Aircraft >= 0 + || this->ForceWeapon_Capture >= 0 ); this->Ammo_Shared.Read(exINI, pSection, "Ammo.Shared"); @@ -1370,6 +1381,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->ForceAAWeapon_Infantry) .Process(this->ForceAAWeapon_Units) .Process(this->ForceAAWeapon_Aircraft) + .Process(this->ForceWeapon_Capture) .Process(this->Ammo_Shared) .Process(this->Ammo_Shared_Group) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 75d46d3169..3d31aa134f 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -191,6 +191,7 @@ class TechnoTypeExt Valueable ForceAAWeapon_Infantry; Valueable ForceAAWeapon_Units; Valueable ForceAAWeapon_Aircraft; + Valueable ForceWeapon_Capture; Valueable Ammo_Shared; Valueable Ammo_Shared_Group; @@ -568,6 +569,7 @@ class TechnoTypeExt , ForceAAWeapon_Infantry { -1 } , ForceAAWeapon_Units { -1 } , ForceAAWeapon_Aircraft { -1 } + , ForceWeapon_Capture { -1 } , Ammo_Shared { false } , Ammo_Shared_Group { -1 } diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 5c0adbd0ef..fdc7852801 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include WarheadTypeExt::ExtContainer WarheadTypeExt::ExtMap; @@ -124,6 +126,72 @@ DamageAreaResult WarheadTypeExt::ExtData::DamageAreaWithTarget(const CoordStruct return result; } +void WarheadTypeExt::DetonateAtBridgeRepairHut(AbstractClass* pTarget, TechnoClass* pOwner, HouseClass* pFiringHouse, bool destroyBridge) +{ + auto const pBuilding = abstract_cast(pTarget); + + if (!pBuilding || !pBuilding->Type->BridgeRepairHut || !pBuilding->IsAlive || pBuilding->Health <= 0) + return; + + const CoordStruct targetCoords = pTarget->GetCenterCoords(); + const CellStruct baseCell = CellClass::Coord2Cell(targetCoords); + + // Send engineer's "enter" event + auto const pTag = pBuilding->AttachedTag; + + if (pTag && pOwner) + pTag->RaiseEvent(TriggerEvent::EnteredBy, pOwner, CellStruct::Empty); + + // Check a 5x5 area for bridge tiles to determine if we should repair or destroy + bool foundWoodBridge = false; + + for (int y = -2; y <= 2; ++y) + { + for (int x = -2; x <= 2; ++x) + { + CellStruct checkCellCoords = { static_cast(baseCell.X + x), static_cast(baseCell.Y + y) }; + auto const checkCell = MapClass::Instance.GetCellAt(checkCellCoords); + + if (checkCell->Tile_Is_WoodBridge() || (checkCell->OverlayTypeIndex >= 74 && checkCell->OverlayTypeIndex <= 101)) + foundWoodBridge = true; + + if (foundWoodBridge) + break; + } + + if (foundWoodBridge) + break; + } + + // Destroying bridges + if (destroyBridge) + { + if (foundWoodBridge) // Repair wood bridges + MapClass::Instance.DestroyWoodBridgeAt(baseCell); + else // Destroy concrete bridges + MapClass::Instance.DestroyConcreteBridgeAt(baseCell); + + return; + } + + auto const pFiringOwner = pOwner ? pOwner->Owner : pFiringHouse; + + // Repairing bridges + if (pFiringOwner && pFiringOwner->IsControlledByCurrentPlayer()) + { + if (RadarEventClass::Create(RadarEventType::BridgeRepaired, CellClass::Coord2Cell(targetCoords))) + VoxClass::PlayIndex(VoxClass::FindIndex("EVA_BridgeRepaired")); + } + + if (RulesClass::Instance->RepairBridgeSound != -1) + VocClass::PlayAt(RulesClass::Instance->RepairBridgeSound, targetCoords, nullptr); + + if (foundWoodBridge) // Repair wood bridges + MapClass::Instance.RepairWoodBridgeAt(baseCell); + else // Repair concrete bridges + MapClass::Instance.RepairConcreteBridgeAt(baseCell); +} + // ============================= // load / save @@ -337,6 +405,11 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->EffectsRequireVerses.Read(exINI, pSection, "EffectsRequireVerses"); this->Malicious.Read(exINI, pSection, "Malicious"); + this->FakeEngineer_CanRepairBridges.Read(exINI, pSection, "FakeEngineer.CanRepairBridges"); + this->FakeEngineer_CanDestroyBridges.Read(exINI, pSection, "FakeEngineer.CanDestroyBridges"); + this->FakeEngineer_CanCaptureBuildings.Read(exINI, pSection, "FakeEngineer.CanCaptureBuildings"); + this->FakeEngineer_BombDisarm.Read(exINI, pSection, "FakeEngineer.BombDisarm"); + // List all Warheads here that respect CellSpread // Used in WarheadTypeExt::ExtData::Detonate this->PossibleCellSpreadDetonate = ( @@ -578,6 +651,11 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->DamageAreaTarget) .Process(this->CanKill) + + .Process(this->FakeEngineer_CanRepairBridges) + .Process(this->FakeEngineer_CanDestroyBridges) + .Process(this->FakeEngineer_CanCaptureBuildings) + .Process(this->FakeEngineer_BombDisarm) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 9c37b3c875..a60a598e59 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -208,6 +208,11 @@ class WarheadTypeExt Valueable CanKill; + Valueable FakeEngineer_CanRepairBridges; + Valueable FakeEngineer_CanDestroyBridges; + Valueable FakeEngineer_CanCaptureBuildings; + Valueable FakeEngineer_BombDisarm; + private: Valueable Shield_Respawn_Rate_InMinutes; Valueable Shield_SelfHealing_Rate_InMinutes; @@ -398,6 +403,11 @@ class WarheadTypeExt , KillWeapon_OnFirer_AffectsHouses { AffectedHouse::All } , KillWeapon_Affects { AffectedTarget::All } , KillWeapon_OnFirer_Affects { AffectedTarget::All } + + , FakeEngineer_CanRepairBridges { false } + , FakeEngineer_CanDestroyBridges { false } + , FakeEngineer_CanCaptureBuildings { false } + , FakeEngineer_BombDisarm { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); @@ -449,4 +459,5 @@ class WarheadTypeExt static void DetonateAt(WarheadTypeClass* pThis, AbstractClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr); static void DetonateAt(WarheadTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr, AbstractClass* pTarget = nullptr); + static void DetonateAtBridgeRepairHut(AbstractClass* pTarget, TechnoClass* pOwner = nullptr, HouseClass* pFiringHouse = nullptr, bool destroyBridge = false); }; diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 7164302cfe..d0f67263e1 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -344,6 +344,26 @@ DEFINE_HOOK(0x442290, BuildingClass_ReceiveDamage_Nonprovocative1, 0x6) return pTypeExt->Nonprovocative ? SkipEvents : 0; } +DEFINE_HOOK(0x4423B7, BuildingClass_ReceiveDamage_BridgeRepairHut, 0xC) +{ + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x9C, 0xC)); + GET_STACK(TechnoClass*, pSource, STACK_OFFSET(0x9C, 0x10)); + GET_STACK(HouseClass*, pHouse, STACK_OFFSET(0x9C, 0x18)); + GET(BuildingClass*, pThis, ESI); + + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); + + if (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges) + { + const bool isBridgeDestroyed = MapClass::Instance.IsLinkedBridgeDestroyed(CellClass::Coord2Cell(pThis->GetCenterCoords())); + bool destroyBridge = isBridgeDestroyed && pWHExt->FakeEngineer_CanRepairBridges ? false : pWHExt->FakeEngineer_CanDestroyBridges; + + WarheadTypeExt::DetonateAtBridgeRepairHut(pThis, pSource, pHouse, destroyBridge); + } + + return 0; +} + // Suppress all events and alerts that come from attacking a building, unlike Ares' Malicious this includes all EVA notifications AND events DEFINE_HOOK(0x442956, BuildingClass_ReceiveDamage_Nonprovocative2, 0x6) {