From adec3a49c4fdee0ab5daa90b3350d46b4e06fe45 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 25 Jul 2025 18:10:16 +0200 Subject: [PATCH 01/11] Initial commit - Engineer logics on Warheads - Now `infantry` and `units` can execute some operations engineers do without loosing the firer like happens with engineers. - `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. In `rulesmd.ini`: ```ini [SOMEWARHEAD] ; WarheadType FakeEngineer.CanRepairBridges=false ; boolean FakeEngineer.CanDestroyBridges=false ; boolean FakeEngineer.CanCaptureBuildings=false ; boolean ``` --- CREDITS.md | 1 + YRpp | 2 +- docs/New-or-Enhanced-Logics.md | 15 +++ docs/Whats-New.md | 1 + src/Ext/Techno/Hooks.ReceiveDamage.cpp | 15 +++ src/Ext/Techno/Hooks.TargetEvaluation.cpp | 150 ++++++++++++++++++++++ src/Ext/WarheadType/Body.cpp | 79 ++++++++++++ src/Ext/WarheadType/Body.h | 9 ++ src/Ext/WarheadType/Hooks.cpp | 19 +++ 9 files changed, 290 insertions(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 0ba36abe22..a41c76d964 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -149,6 +149,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/YRpp b/YRpp index a8c3f616b8..13ee1b6e02 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit a8c3f616b8b421bd8b806cf90e560ec59355fd05 +Subproject commit 13ee1b6e02ce997ddc44e19ceed518b8328cf0f6 diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index aa1e948de3..ae9920a46c 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2249,6 +2249,21 @@ 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 `infantry` and `units` can execute some operations engineers do without loosing the firer like happens with engineers. +- `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. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +FakeEngineer.CanRepairBridges=false ; boolean +FakeEngineer.CanDestroyBridges=false ; boolean +FakeEngineer.CanCaptureBuildings=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 ebc79976ad..513b0a23a4 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -426,6 +426,7 @@ New: - [Damaged aircraft image changes](New-or-Enhanced-Logics.md#damaged-aircraft-image-changes) (by Fryone) - [Additional attached animation position customizations](Fixed-or-Improved-Logics.md#attached-animation-position-customization) (by Starkku) - Use `SkipCrushSlowdown=true` to avoid the bug related to `Accelerates=true` and `MovementZone=CrushAll` (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/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index a531335764..353f694c1b 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -61,6 +61,21 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) } } + // Repair/Destroy bridges at Bridge Repair Huts buildings + if (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges) + { + bool destroyBridge = pWHExt->FakeEngineer_CanRepairBridges ? false : pWHExt->FakeEngineer_CanDestroyBridges; + WarheadTypeExt::DetonateAtBridgeRepairHut(pThis, nullptr, pSourceHouse, destroyBridge); + } + + auto const pBuilding = abstract_cast(pThis); + if (pBuilding && pWHExt->FakeEngineer_CanCaptureBuildings + && !pSourceHouse->IsAlliedWith(pTargetHouse) + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + reinterpret_cast(0x448260)(pBuilding, pSourceHouse, true); + } + // Raise Combat Alert if (pRules->CombatAlert && damage > 1) { diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 676d91feb9..f7bf53284b 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -1,6 +1,156 @@ #include "Body.h" +#include +#include // Cursor & target acquisition stuff not directly tied to other features can go here. +DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x51F17E }; + + GET(TechnoClass* const, pThis, EDI); + GET(AbstractClass* const, pTarget, ESI); + + auto const pBuilding = abstract_cast(pTarget); + if (!pBuilding->Type->BridgeRepairHut && !pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) + return 0; + + int nWeaponIndex = pThis->SelectWeapon(pTarget); + + if (nWeaponIndex < 0) + return 0; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pBuilding->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + R->EBP(Action::Attack); + return ForceNewValue; + } + } + else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + R->EBP(Action::Attack); + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x6FCBA6 }; + + GET(TechnoClass* const, pThis, ESI); + GET(WeaponTypeClass* const, pWeapon, EBX); + GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x10, 0x8)); + + auto const pTechno = abstract_cast(pTarget); + + if (!pTechno) + return 0; + + auto const pBuilding = abstract_cast(pTechno); + + if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) + return 0; + + if (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) + return 0; + + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pWHExt->FakeEngineer_CanCaptureBuildings) + { + int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pThis); + int currentRange = pThis->DistanceFrom(pBuilding); + + if (currentRange <= weaponRange) + R->EAX(FireError::OK); + else + R->EAX(FireError::RANGE); // Out of range + + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) +{ + enum { ForceNewValue = 0x74049F }; + + GET(TechnoClass* const, pThis, ESI); + GET(TechnoClass* const, pTarget, EDI); + + auto const pBuilding = abstract_cast(pTarget); + + if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) + return 0; + + if (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) + return 0; + + int nWeaponIndex = pThis->SelectWeapon(pTarget); + if (nWeaponIndex < 0) + return 0; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pWHExt->FakeEngineer_CanCaptureBuildings) + { + R->EBX(Action::Attack); + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x74049A, UnitClass_WhatAction_Immune_FakeEngineer2, 0x5) +{ + enum { ForceNewValue = 0x74049F }; + + GET(TechnoClass* const, pThis, ESI); + GET(TechnoClass* const, pTarget, EDI); + GET(int, originalValue, EBX); + + auto const pBuilding = abstract_cast(pTarget); + + if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) + return 0; + + int nWeaponIndex = pThis->SelectWeapon(pTarget); + if (nWeaponIndex < 0) + return 0; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pBuilding->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + R->EBX(originalValue); + return ForceNewValue; + } + } + else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + R->EBX(Action::Attack); + return ForceNewValue; + } + + return 0; +} #pragma region TargetAcquisition diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index b3561616f7..df7d8d10db 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; @@ -123,6 +125,75 @@ 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); + + if (!MapClass::Instance.IsLinkedBridgeDestroyed(baseCell)) + return; + + // 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 @@ -335,6 +406,10 @@ 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"); + // List all Warheads here that respect CellSpread // Used in WarheadTypeExt::ExtData::Detonate this->PossibleCellSpreadDetonate = ( @@ -575,6 +650,10 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->DamageAreaTarget) .Process(this->CanKill) + + .Process(this->FakeEngineer_CanRepairBridges) + .Process(this->FakeEngineer_CanDestroyBridges) + .Process(this->FakeEngineer_CanCaptureBuildings) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index fc2ae2a31f..fdaf026401 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -207,6 +207,10 @@ class WarheadTypeExt Valueable CanKill; + Valueable FakeEngineer_CanRepairBridges; + Valueable FakeEngineer_CanDestroyBridges; + Valueable FakeEngineer_CanCaptureBuildings; + private: Valueable Shield_Respawn_Rate_InMinutes; Valueable Shield_SelfHealing_Rate_InMinutes; @@ -396,6 +400,10 @@ 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 } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); @@ -447,4 +455,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 6f0bd4241f..e6c94274db 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -344,6 +344,25 @@ 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) + { + bool destroyBridge = 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) { From a951ea5169a169733607c4a775cce03f6cf263a3 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 29 Jul 2025 09:30:19 +0200 Subject: [PATCH 02/11] Now buildings and aircrafts are supported --- docs/New-or-Enhanced-Logics.md | 2 +- src/Ext/Techno/Hooks.TargetEvaluation.cpp | 79 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 3a78bb1b97..a1b0222d62 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2257,7 +2257,7 @@ While this feature can provide better performance than a large `CellSpread` valu ### Engineer logics on Warheads -- Now `infantry` and `units` can execute some operations engineers do without loosing the firer like happens with engineers. +- 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. diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index f7bf53284b..5d5df22163 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -3,6 +3,85 @@ #include // Cursor & target acquisition stuff not directly tied to other features can go here. + +DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x417F68 }; + + GET(TechnoClass* const, pThis, ESI); + GET(BuildingClass* const, pTarget, EDI); + + //auto const pBuilding = abstract_cast(pTarget); + if (!pTarget->Type->BridgeRepairHut && !pTarget->Type->Capturable && !pTarget->Type->NeedsEngineer) + return 0; + + int nWeaponIndex = pThis->SelectWeapon(pTarget); + + if (nWeaponIndex < 0) + return 0; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pTarget->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + //R->EBP(Action::Attack); + return ForceNewValue; + } + } + else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pTarget->Type->Capturable || pTarget->Type->NeedsEngineer)) + { + //R->EBP(Action::Attack); + return ForceNewValue; + } + + return 0; +} + +DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x44752C }; + + GET(TechnoClass* const, pThis, ESI); + GET(AbstractClass* const, pTarget, EBP); + + auto const pBuilding = abstract_cast(pTarget); + if (!pBuilding->Type->BridgeRepairHut && !pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) + return 0; + + int nWeaponIndex = pThis->SelectWeapon(pTarget); + + if (nWeaponIndex < 0) + return 0; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (pBuilding->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + R->EBP(Action::Attack); + return ForceNewValue; + } + } + else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + R->EBP(Action::Attack); + return ForceNewValue; + } + + return 0; +} + DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) { enum { ForceNewValue = 0x51F17E }; From 41e1168343f8bcf10fcb3365133c9db253cff04c Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 29 Jul 2025 17:48:55 +0200 Subject: [PATCH 03/11] Code improvements Removed all repetitive code related to checks and placed all that in a new method called CanBeAffectedByFakeEngineer(...) --- src/Ext/Techno/Body.cpp | 42 +++++ src/Ext/Techno/Body.h | 1 + src/Ext/Techno/Hooks.TargetEvaluation.cpp | 182 +++++----------------- 3 files changed, 84 insertions(+), 141 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 48a7c3694c..6689dc883e 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; @@ -624,6 +625,47 @@ AircraftTypeClass* TechnoExt::GetAircraftTypeExtra(AircraftClass* pAircraft) } +bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, BuildingClass* pBuilding, bool checkBridge, bool checkCapturableBuilding) +{ + if ((!checkBridge && !checkCapturableBuilding) + || !pBuilding + || !pBuilding->IsAlive + || pBuilding->Health <= 0) + { + return false; + } + + int nWeaponIndex = pThis->SelectWeapon(pBuilding); + if (nWeaponIndex < 0) + return false; + + auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + // Check if a Bridge Repair Hut can be affected + if (checkBridge && pBuilding->Type->BridgeRepairHut) + { + CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pBuilding->GetCenterCoords()); + bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + + if (isBridgeDamaged + && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + return true; + } + } + + // Check if a capturable building can be affected + if (checkCapturableBuilding + && pWHExt->FakeEngineer_CanCaptureBuildings + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + { + return true; + } + + return false; +} + void TechnoExt::ExtData::ResetDelayedFireTimer() { this->DelayedFireTimer.Stop(); diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 6071a336c0..852ae45ae6 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -264,6 +264,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, BuildingClass* pBuilding, bool checkBridge = false, bool checkCapturableBuilding = 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.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 5d5df22163..73440cf4b8 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -4,76 +4,69 @@ // Cursor & target acquisition stuff not directly tied to other features can go here. -DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) +DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) { - enum { ForceNewValue = 0x417F68 }; + enum { ForceNewValue = 0x74049F }; GET(TechnoClass* const, pThis, ESI); - GET(BuildingClass* const, pTarget, EDI); + GET(TechnoClass* const, pTarget, EDI); - //auto const pBuilding = abstract_cast(pTarget); - if (!pTarget->Type->BridgeRepairHut && !pTarget->Type->Capturable && !pTarget->Type->NeedsEngineer) - return 0; + auto const pBuilding = abstract_cast(pTarget); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, true); - int nWeaponIndex = pThis->SelectWeapon(pTarget); + if (canBeAttacked) + { + R->EBX(Action::Attack); + return ForceNewValue; + } - if (nWeaponIndex < 0) - return 0; + return 0; +} - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); +DEFINE_HOOK(0x74049A, UnitClass_WhatAction_Immune_FakeEngineer2, 0x5) +{ + enum { ForceNewValue = 0x74049F }; - if (pTarget->Type->BridgeRepairHut) - { - CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); - bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + GET(TechnoClass* const, pThis, ESI); + GET(TechnoClass* const, pTarget, EDI); - if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) - { - //R->EBP(Action::Attack); - return ForceNewValue; - } - } - else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pTarget->Type->Capturable || pTarget->Type->NeedsEngineer)) + auto const pBuilding = abstract_cast(pTarget); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + + if (canBeAttacked) { - //R->EBP(Action::Attack); + R->EBX(Action::Attack); return ForceNewValue; } return 0; } -DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) +DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) { - enum { ForceNewValue = 0x44752C }; + enum { ForceNewValue = 0x417F68 }; GET(TechnoClass* const, pThis, ESI); - GET(AbstractClass* const, pTarget, EBP); + GET(BuildingClass* const, pBuilding, EDI); - auto const pBuilding = abstract_cast(pTarget); - if (!pBuilding->Type->BridgeRepairHut && !pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) - return 0; + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); - int nWeaponIndex = pThis->SelectWeapon(pTarget); + if (canBeAttacked) + return ForceNewValue; - if (nWeaponIndex < 0) - return 0; + return 0; +} - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); +DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) +{ + enum { ForceNewValue = 0x44752C }; - if (pBuilding->Type->BridgeRepairHut) - { - CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); - bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + GET(TechnoClass* const, pThis, ESI); + GET(BuildingClass* const, pBuilding, EBP); - if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) - { - R->EBP(Action::Attack); - return ForceNewValue; - } - } - else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + + if (canBeAttacked) { R->EBP(Action::Attack); return ForceNewValue; @@ -87,32 +80,11 @@ DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) enum { ForceNewValue = 0x51F17E }; GET(TechnoClass* const, pThis, EDI); - GET(AbstractClass* const, pTarget, ESI); - - auto const pBuilding = abstract_cast(pTarget); - if (!pBuilding->Type->BridgeRepairHut && !pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) - return 0; - - int nWeaponIndex = pThis->SelectWeapon(pTarget); - - if (nWeaponIndex < 0) - return 0; - - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + GET(BuildingClass* const, pBuilding, ESI); - if (pBuilding->Type->BridgeRepairHut) - { - CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); - bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); - if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) - { - R->EBP(Action::Attack); - return ForceNewValue; - } - } - else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + if (canBeAttacked) { R->EBP(Action::Attack); return ForceNewValue; @@ -130,7 +102,6 @@ DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer, 0x5) GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x10, 0x8)); auto const pTechno = abstract_cast(pTarget); - if (!pTechno) return 0; @@ -160,77 +131,6 @@ DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer, 0x5) return 0; } -DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) -{ - enum { ForceNewValue = 0x74049F }; - - GET(TechnoClass* const, pThis, ESI); - GET(TechnoClass* const, pTarget, EDI); - - auto const pBuilding = abstract_cast(pTarget); - - if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) - return 0; - - if (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) - return 0; - - int nWeaponIndex = pThis->SelectWeapon(pTarget); - if (nWeaponIndex < 0) - return 0; - - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); - - if (pWHExt->FakeEngineer_CanCaptureBuildings) - { - R->EBX(Action::Attack); - return ForceNewValue; - } - - return 0; -} - -DEFINE_HOOK(0x74049A, UnitClass_WhatAction_Immune_FakeEngineer2, 0x5) -{ - enum { ForceNewValue = 0x74049F }; - - GET(TechnoClass* const, pThis, ESI); - GET(TechnoClass* const, pTarget, EDI); - GET(int, originalValue, EBX); - - auto const pBuilding = abstract_cast(pTarget); - - if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) - return 0; - - int nWeaponIndex = pThis->SelectWeapon(pTarget); - if (nWeaponIndex < 0) - return 0; - - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); - - if (pBuilding->Type->BridgeRepairHut) - { - CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pTarget->GetCenterCoords()); - bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); - - if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) - { - R->EBX(originalValue); - return ForceNewValue; - } - } - else if (pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) - { - R->EBX(Action::Attack); - return ForceNewValue; - } - - return 0; -} - #pragma region TargetAcquisition DEFINE_HOOK(0x7098B9, TechnoClass_TargetSomethingNearby_AutoFire, 0x6) From 02c62d8217db38c5475665bb04cca795f70733c1 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 29 Jul 2025 18:05:26 +0200 Subject: [PATCH 04/11] tweak --- src/Ext/Techno/Hooks.TargetEvaluation.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 73440cf4b8..d742340351 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -4,6 +4,8 @@ // Cursor & target acquisition stuff not directly tied to other features can go here. +#pragma region FakeEngineer + DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) { enum { ForceNewValue = 0x74049F }; @@ -93,7 +95,7 @@ DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) return 0; } -DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer, 0x5) +DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer_CanCaptureBuildings, 0x5) { enum { ForceNewValue = 0x6FCBA6 }; @@ -131,6 +133,8 @@ DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer, 0x5) return 0; } +#pragma endregion + #pragma region TargetAcquisition DEFINE_HOOK(0x7098B9, TechnoClass_TargetSomethingNearby_AutoFire, 0x6) From d262fa0bd0622c0ed2a28f3b2baef68a31e5ac51 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Thu, 31 Jul 2025 11:34:06 +0200 Subject: [PATCH 05/11] Crash fix & code tweaks Found a silent crash. Code tweaks --- src/Ext/Techno/Body.cpp | 7 +++- src/Ext/Techno/Hooks.TargetEvaluation.cpp | 42 +++++++++++++---------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6689dc883e..161e356ab8 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -640,6 +640,10 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, BuildingClass* p return false; auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + + if (!pWeapon) + return false; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); // Check if a Bridge Repair Hut can be affected @@ -658,7 +662,8 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, BuildingClass* p // Check if a capturable building can be affected if (checkCapturableBuilding && pWHExt->FakeEngineer_CanCaptureBuildings - && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer) + && !pThis->Owner->IsAlliedWith(pBuilding)) { return true; } diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index d742340351..03d5616819 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -99,38 +99,42 @@ DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer_CanCaptureBuilding { enum { ForceNewValue = 0x6FCBA6 }; - GET(TechnoClass* const, pThis, ESI); - GET(WeaponTypeClass* const, pWeapon, EBX); - GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x10, 0x8)); + 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)); + + auto const pFirer = abstract_cast(pThis); - auto const pTechno = abstract_cast(pTarget); - if (!pTechno) + if (!pFirer) return 0; - auto const pBuilding = abstract_cast(pTechno); + auto const pBuilding = abstract_cast(pTarget); - if (!pBuilding || !pBuilding->IsAlive || pBuilding->Health <= 0) + if (!pBuilding + || !pBuilding->IsAlive + || pBuilding->Health <= 0 + || pFirer->Owner->IsAlliedWith(pBuilding) + || (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer)) return 0; - if (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer) + auto const pWeapon = pFirer->GetWeapon(nWeaponIdx)->WeaponType; + if (!pWeapon) return 0; auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); - if (pWHExt->FakeEngineer_CanCaptureBuildings) - { - int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pThis); - int currentRange = pThis->DistanceFrom(pBuilding); + if (!pWHExt->FakeEngineer_CanCaptureBuildings) + return 0; - if (currentRange <= weaponRange) - R->EAX(FireError::OK); - else - R->EAX(FireError::RANGE); // Out of range + int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pFirer); + int currentRange = pFirer->DistanceFrom(pBuilding); - return ForceNewValue; - } + if (currentRange <= weaponRange) + R->EAX(FireError::OK); + else + R->EAX(FireError::RANGE); // Out of range - return 0; + return ForceNewValue; } #pragma endregion From 9ad85f79ca29df1b62d42c0319487b2f52e0a1c6 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 6 Aug 2025 14:46:21 +0200 Subject: [PATCH 06/11] 2 new tags and improvements - Added ForceWeapon.Capture. - Added FakeEngineer.BombDisarm. - Code cleanup and improvements. --- docs/New-or-Enhanced-Logics.md | 4 + src/Ext/Techno/Body.cpp | 51 ++++++--- src/Ext/Techno/Body.h | 2 +- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 7 ++ src/Ext/Techno/Hooks.TargetEvaluation.cpp | 128 ++++++++++++++++++---- src/Ext/Techno/WeaponHelpers.cpp | 13 +++ src/Ext/TechnoType/Body.cpp | 12 ++ src/Ext/TechnoType/Body.h | 2 + src/Ext/WarheadType/Body.cpp | 2 + src/Ext/WarheadType/Body.h | 2 + 10 files changed, 181 insertions(+), 42 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index a1b0222d62..e22a91cbcc 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 @@ -2261,6 +2263,7 @@ While this feature can provide better performance than a large `CellSpread` valu - `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 @@ -2268,6 +2271,7 @@ In `rulesmd.ini`: FakeEngineer.CanRepairBridges=false ; boolean FakeEngineer.CanDestroyBridges=false ; boolean FakeEngineer.CanCaptureBuildings=false ; boolean +FakeEngineer.DisarmBombs=false ; boolean ``` ### Fire weapon when Warhead kills something diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 161e356ab8..286f6dfd20 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -625,29 +625,45 @@ AircraftTypeClass* TechnoExt::GetAircraftTypeExtra(AircraftClass* pAircraft) } -bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, BuildingClass* pBuilding, bool checkBridge, bool checkCapturableBuilding) +bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTarget, bool checkBridge, bool checkCapturableBuilding, bool checkAttachedBombs) { - if ((!checkBridge && !checkCapturableBuilding) - || !pBuilding - || !pBuilding->IsAlive - || pBuilding->Health <= 0) - { - return false; - } + 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); - int nWeaponIndex = pThis->SelectWeapon(pBuilding); if (nWeaponIndex < 0) return false; - auto const pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; + const auto pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - if (!pWeapon) + if (!pWeapon || !pTarget) return false; - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + 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 && pBuilding->Type->BridgeRepairHut) + if (checkBridge && isBuilding && pBuilding->Type->BridgeRepairHut) { CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pBuilding->GetCenterCoords()); bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); @@ -655,20 +671,21 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, BuildingClass* p if (isBridgeDamaged && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) { - return true; + 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)) + && !pThis->Owner->IsAlliedWith(pBuilding)) // Anti-crash check { - return true; + canAffectCapturableBuildings = true; } - return false; + return canAffectCapturableBuildings || canAffectBridges || canAffectAttachedBombs; } void TechnoExt::ExtData::ResetDelayedFireTimer() diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 852ae45ae6..6f04fd4ac5 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -264,7 +264,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, BuildingClass* pBuilding, bool checkBridge = false, bool checkCapturableBuilding = false); + 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 353f694c1b..f7ca0c38a0 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -68,7 +69,9 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) WarheadTypeExt::DetonateAtBridgeRepairHut(pThis, nullptr, pSourceHouse, destroyBridge); } + // Capture enemy buildings auto const pBuilding = abstract_cast(pThis); + if (pBuilding && pWHExt->FakeEngineer_CanCaptureBuildings && !pSourceHouse->IsAlliedWith(pTargetHouse) && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) @@ -76,6 +79,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) reinterpret_cast(0x448260)(pBuilding, pSourceHouse, true); } + // Disarm bomb + if (pThis->AttachedBomb && pWHExt->FakeEngineer_BombDisarm) + pThis->AttachedBomb->Disarm(); + // Raise Combat Alert if (pRules->CombatAlert && damage > 1) { diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 03d5616819..3a44c9ead6 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -1,11 +1,21 @@ #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 }; @@ -13,18 +23,24 @@ DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) GET(TechnoClass* const, pThis, ESI); GET(TechnoClass* const, pTarget, EDI); - auto const pBuilding = abstract_cast(pTarget); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, true); + 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) { - R->EBX(Action::Attack); + 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 }; @@ -32,12 +48,17 @@ DEFINE_HOOK(0x74049A, UnitClass_WhatAction_Immune_FakeEngineer2, 0x5) GET(TechnoClass* const, pThis, ESI); GET(TechnoClass* const, pTarget, EDI); - auto const pBuilding = abstract_cast(pTarget); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + 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) { - R->EBX(Action::Attack); + if (canBeDefused) + R->EBX(Action::DisarmBomb); + else + R->EBX(Action::Attack); + return ForceNewValue; } @@ -51,7 +72,7 @@ DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) GET(TechnoClass* const, pThis, ESI); GET(BuildingClass* const, pBuilding, EDI); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); if (canBeAttacked) return ForceNewValue; @@ -66,11 +87,16 @@ DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) GET(TechnoClass* const, pThis, ESI); GET(BuildingClass* const, pBuilding, EBP); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); if (canBeAttacked) { - R->EBP(Action::Attack); + if (canBeDefused) + R->EBP(Action::DisarmBomb); + else + R->EBP(Action::Attack); + return ForceNewValue; } @@ -84,17 +110,57 @@ DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) GET(TechnoClass* const, pThis, EDI); GET(BuildingClass* const, pBuilding, ESI); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true); + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); if (canBeAttacked) { - R->EBP(Action::Attack); + 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 }; @@ -103,32 +169,46 @@ DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer_CanCaptureBuilding GET_STACK(AbstractClass* const, pTarget, STACK_OFFSET(0x10, 0x8)); GET_STACK(int, nWeaponIdx, STACK_OFFSET(0x10, 0xC)); - auto const pFirer = abstract_cast(pThis); + const auto pFirer = abstract_cast(pThis); + const auto pVictim = abstract_cast(pTarget); + + if (!pFirer || !pVictim) + return 0; - if (!pFirer) + const auto pWeapon = pFirer->GetWeapon(nWeaponIdx)->WeaponType; + if (!pWeapon) return 0; - auto const pBuilding = abstract_cast(pTarget); + 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(pBuilding) + || pFirer->Owner->IsAlliedWith(pVictim) || (!pBuilding->Type->Capturable && !pBuilding->Type->NeedsEngineer)) + { return 0; - - auto const pWeapon = pFirer->GetWeapon(nWeaponIdx)->WeaponType; - if (!pWeapon) - return 0; - - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + } if (!pWHExt->FakeEngineer_CanCaptureBuildings) return 0; - int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pFirer); - int currentRange = pFirer->DistanceFrom(pBuilding); - if (currentRange <= weaponRange) R->EAX(FireError::OK); else diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 22cbf5a842..8755a7d14b 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -474,6 +474,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 ac19f75408..60d793f02e 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 @@ -691,6 +700,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 @@ -708,6 +718,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"); @@ -1317,6 +1328,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 545b6a9d33..41cddf7492 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; @@ -562,6 +563,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 df7d8d10db..2ac4bacf5c 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -409,6 +409,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) 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 @@ -654,6 +655,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .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 fdaf026401..7a5fc1f41b 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -210,6 +210,7 @@ class WarheadTypeExt Valueable FakeEngineer_CanRepairBridges; Valueable FakeEngineer_CanDestroyBridges; Valueable FakeEngineer_CanCaptureBuildings; + Valueable FakeEngineer_BombDisarm; private: Valueable Shield_Respawn_Rate_InMinutes; @@ -404,6 +405,7 @@ class WarheadTypeExt , FakeEngineer_CanRepairBridges { false } , FakeEngineer_CanDestroyBridges { false } , FakeEngineer_CanCaptureBuildings { false } + , FakeEngineer_BombDisarm { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); From 80bee1fbdb46f8cbea49740c3c49b82d9e195a6e Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 12 Aug 2025 08:24:33 +0200 Subject: [PATCH 07/11] Destroy bridge fixed and code improvements like adding the "send event" in the capture building, because I think the radar event the capture function has doesn't trigger the Enter event in map triggers. Also, now can be triggered both repair bridge & destroy bridge depending of the current bridge status if both tags are enabled in the warhead. --- src/Ext/Techno/Body.cpp | 4 ++-- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 8 +++++++- src/Ext/WarheadType/Body.cpp | 3 --- src/Ext/WarheadType/Hooks.cpp | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 0ad9327a08..3f20048994 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -676,8 +676,8 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTa CellStruct bridgeRepairHutCell = CellClass::Coord2Cell(pBuilding->GetCenterCoords()); bool isBridgeDamaged = MapClass::Instance.IsLinkedBridgeDestroyed(bridgeRepairHutCell); - if (isBridgeDamaged - && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + if ((isBridgeDamaged && pWHExt->FakeEngineer_CanRepairBridges) + || (!isBridgeDamaged && pWHExt->FakeEngineer_CanDestroyBridges)) { canAffectBridges = true; } diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index cb0e2ff23c..efe13c30d1 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -64,7 +64,8 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) // Repair/Destroy bridges at Bridge Repair Huts buildings if (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges) { - bool destroyBridge = pWHExt->FakeEngineer_CanRepairBridges ? false : 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); } @@ -75,6 +76,11 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) && !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); } diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 44caf65f02..f10a3174d4 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -136,9 +136,6 @@ void WarheadTypeExt::DetonateAtBridgeRepairHut(AbstractClass* pTarget, TechnoCla const CoordStruct targetCoords = pTarget->GetCenterCoords(); const CellStruct baseCell = CellClass::Coord2Cell(targetCoords); - if (!MapClass::Instance.IsLinkedBridgeDestroyed(baseCell)) - return; - // Send engineer's "enter" event auto const pTag = pBuilding->AttachedTag; diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 63adf3c8c7..d0f67263e1 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -355,7 +355,8 @@ DEFINE_HOOK(0x4423B7, BuildingClass_ReceiveDamage_BridgeRepairHut, 0xC) if (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges) { - bool destroyBridge = pWHExt->FakeEngineer_CanRepairBridges ? false : 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); } From f9720f0bb55d4d2052f81225d7921df1f6efd920 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 12 Aug 2025 09:20:17 +0200 Subject: [PATCH 08/11] tweak --- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index efe13c30d1..89c02762b2 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -61,8 +61,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) } } + auto const pBuilding = abstract_cast(pThis); + // Repair/Destroy bridges at Bridge Repair Huts buildings - if (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges) + 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; @@ -70,8 +72,6 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) } // Capture enemy buildings - auto const pBuilding = abstract_cast(pThis); - if (pBuilding && pWHExt->FakeEngineer_CanCaptureBuildings && !pSourceHouse->IsAlliedWith(pTargetHouse) && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) From 54b518e5bcb2a7c23d5063272ecffa4f5ac78d62 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 28 Aug 2026 13:18:06 +0200 Subject: [PATCH 09/11] changes --- docs/New-or-Enhanced-Logics.md | 4 +- src/Ext/Infantry/Hooks.cpp | 15 ++ src/Ext/Techno/Body.cpp | 86 ++++++++- src/Ext/Techno/Body.h | 1 + src/Ext/Techno/Hooks.Firing.cpp | 5 +- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 14 +- src/Ext/Techno/Hooks.TargetEvaluation.cpp | 217 ++++++++++++---------- src/Ext/Techno/WeaponHelpers.cpp | 45 +++-- src/Ext/TechnoType/Body.cpp | 6 +- src/Ext/WarheadType/Body.cpp | 73 +------- src/Ext/WarheadType/Body.h | 1 - src/Ext/WarheadType/Hooks.cpp | 19 +- 12 files changed, 281 insertions(+), 205 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index eb6e9be099..1d59d910a0 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2273,10 +2273,10 @@ While this feature can provide better performance than a large `CellSpread` valu ### Engineer logics on Warheads -- Now any `InfantryType`, `VehicleType`, `BuildingType` or `AircraftType` can execute some operations engineers do without loosing the firer in the process. +- Now any `InfantryType`, `VehicleType`, `BuildingType` or `AircraftType` can execute some operations engineers do without losing 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.CanCaptureBuildings`, if set to true, a building with `Capturable=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`: diff --git a/src/Ext/Infantry/Hooks.cpp b/src/Ext/Infantry/Hooks.cpp index 97468d4a59..3f0d657339 100644 --- a/src/Ext/Infantry/Hooks.cpp +++ b/src/Ext/Infantry/Hooks.cpp @@ -138,3 +138,18 @@ DEFINE_HOOK(0x522373, InfantryClass_ApproachTarget_InfantryAutoDeploy, 0x5) GET(InfantryClass*, pThis, ESI); return TechnoTypeExt::ExtMap.Find(pThis->Type)->InfantryAutoDeploy.Get(RulesExt::Global()->InfantryAutoDeploy) ? Deploy : 0; } + +// Pass actual target to SelectWeapon instead of -1 during What_Action evaluation +DEFINE_HOOK(0x51E6D8, InfantryClass_WhatAction_WhatWeaponShouldIUse, 0x9) +{ + GET(InfantryClass*, pThis, EDI); + GET(AbstractClass*, pTarget, ESI); + + int weaponIdx = (pThis && pTarget) ? pThis->SelectWeapon(pTarget) : -1; + + R->EAX(weaponIdx); + + return 0x51E6E1; +} + + diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 97f456944e..16795149f3 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -11,6 +11,7 @@ #include #include +#include TechnoExt::ExtContainer TechnoExt::ExtMap; UnitClass* TechnoExt::Deployer = nullptr; @@ -635,6 +636,9 @@ AircraftTypeClass* TechnoExt::GetAircraftTypeExtra(AircraftClass* pAircraft) bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTarget, bool checkBridge, bool checkCapturableBuilding, bool checkAttachedBombs) { + if (!pThis || !pTarget) + return false; + const auto pTypeExt = TechnoExt::ExtMap.Find(pThis)->TypeExtData; // Force weapon check @@ -649,12 +653,21 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTa if (nWeaponIndex < 0) return false; - const auto pWeapon = pThis->GetWeapon(nWeaponIndex)->WeaponType; - - if (!pWeapon || !pTarget) + const auto pWeaponStruct = pThis->GetWeapon(nWeaponIndex); + if (!pWeaponStruct || !pWeaponStruct->WeaponType || !pWeaponStruct->WeaponType->Warhead) return false; + const auto pWeapon = pWeaponStruct->WeaponType; const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + + if (!pWHExt->FakeEngineer_CanCaptureBuildings + && !pWHExt->FakeEngineer_CanRepairBridges + && !pWHExt->FakeEngineer_CanDestroyBridges + && !pWHExt->FakeEngineer_BombDisarm) + { + return false; + } + bool canAffectCapturableBuildings = false; bool canAffectBridges = false; bool canAffectAttachedBombs = false; @@ -688,7 +701,8 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTa && isBuilding && pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer) - && !pThis->Owner->IsAlliedWith(pBuilding)) // Anti-crash check + && pBuilding->Owner != pThis->Owner + && (!pThis->Owner->IsAlliedWith(pBuilding) || pBuilding->Owner->IsNeutral())) { canAffectCapturableBuildings = true; } @@ -696,6 +710,70 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTa return canAffectCapturableBuildings || canAffectBridges || canAffectAttachedBombs; } +void TechnoExt::RepairOrDestroyBridgeHut(BuildingClass* pBuilding, TechnoClass* pOwner, HouseClass* pFiringHouse, bool destroyBridge) +{ + if (!pBuilding || !pBuilding->Type->BridgeRepairHut || !pBuilding->IsAlive || pBuilding->Health <= 0) + return; + + const CoordStruct targetCoords = pBuilding->GetCenterCoords(); + const CellStruct baseCell = pBuilding->GetMapCoords(); + + // 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 && (checkCell->Tile_Is_WoodBridge() || (checkCell->OverlayTypeIndex >= 74 && checkCell->OverlayTypeIndex <= 101))) + foundWoodBridge = true; + + if (foundWoodBridge) + break; + } + + if (foundWoodBridge) + break; + } + + // Destroying bridges + if (destroyBridge) + { + if (foundWoodBridge) // Destroy 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, baseCell)) + 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); +} + void TechnoExt::ExtData::ResetDelayedFireTimer() { this->DelayedFireTimer.Stop(); diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 7cebe1106f..96882b410f 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -273,6 +273,7 @@ class TechnoExt 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); + static void RepairOrDestroyBridgeHut(BuildingClass* pBuilding, TechnoClass* pOwner = nullptr, HouseClass* pFiringHouse = nullptr, bool destroyBridge = false); static bool CannotMove(UnitClass* pThis); // WeaponHelpers.cpp diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 2d6decb15a..318480b7b8 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -94,9 +94,12 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6) { enum { UseWeaponIndex = 0x6F37AF }; - GET(TechnoClass*, pThis, ECX); + GET(TechnoClass*, pThis, ESI); GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x18, 0x4)); + if (!pThis || !pTarget) + return 0; + auto const pTypeExt = TechnoExt::ExtMap.Find(pThis)->TypeExtData; // Force weapon diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index 89c02762b2..f209808304 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -63,17 +63,11 @@ 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/neutral buildings + const bool canCaptureHouse = pBuilding && pBuilding->Owner != pSourceHouse + && (!pSourceHouse->IsAlliedWith(pTargetHouse) || pBuilding->Owner->IsNeutral()); - // Capture enemy buildings - if (pBuilding && pWHExt->FakeEngineer_CanCaptureBuildings - && !pSourceHouse->IsAlliedWith(pTargetHouse) + if (canCaptureHouse && pWHExt->FakeEngineer_CanCaptureBuildings && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer)) { // Send engineer's "enter" event diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp index 14c67f816a..0403574c86 100644 --- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp +++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp @@ -7,59 +7,69 @@ #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) +// Skipping the next 2 small checks permits the AI to target structures, if used correctly (for example with the combination of AttackFriendlies) +DEFINE_HOOK(0x6F85C8, TechnoClass_EvaluateObject_TargetStructures, 0x7) { - enum { skipCode = 0x74049F }; - return 0x6F866D; } // Skipping the Immune check -DEFINE_HOOK(0x740414, UnitClass_WhatAction_Immune_FakeEngineer1, 0x5) +DEFINE_HOOK(0x740402, UnitClass_WhatAction_Immune_FakeEngineer1, 0xA) { - enum { ForceNewValue = 0x74049F }; + enum { ForceNewValue = 0x740440 }; - 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); + GET(TechnoClass* const, pThis, EDI); + GET(TechnoClass* const, pTarget, ESI); - if (canBeAttacked) + if (pThis && pTarget) { - if (canBeDefused) - R->EBX(Action::DisarmBomb); - else - R->EBX(Action::Attack); + const auto pBuilding = abstract_cast(pTarget); + if (pBuilding) + { + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + bool canBeDefused = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, false, false, true); - return ForceNewValue; + 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) +// Skipping the C4 check +DEFINE_HOOK(0x740486, UnitClass_WhatAction_Immune_FakeEngineer2, 0xA) { 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); + GET(TechnoClass* const, pThis, EDI); + GET(TechnoClass* const, pTarget, ESI); - if (canBeAttacked) + if (pThis && pTarget) { - if (canBeDefused) - R->EBX(Action::DisarmBomb); - else - R->EBX(Action::Attack); + const auto pBuilding = abstract_cast(pTarget); + if (pBuilding) + { + 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 ForceNewValue; + } + } } return 0; @@ -72,10 +82,13 @@ DEFINE_HOOK(0x417F63, AircraftClass_WhatAction_Immune_FakeEngineer, 0x5) GET(TechnoClass* const, pThis, ESI); GET(BuildingClass* const, pBuilding, EDI); - bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); + if (pThis && pBuilding) + { + bool canBeAttacked = TechnoExt::CanBeAffectedByFakeEngineer(pThis, pBuilding, true, true, true); - if (canBeAttacked) - return ForceNewValue; + if (canBeAttacked) + return ForceNewValue; + } return 0; } @@ -87,17 +100,20 @@ DEFINE_HOOK(0x447527, BuildingClass_WhatAction_Immune_FakeEngineer, 0x5) 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 (pThis && pBuilding) { - if (canBeDefused) - R->EBP(Action::DisarmBomb); - else - R->EBP(Action::Attack); + 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 ForceNewValue; + } } return 0; @@ -110,26 +126,29 @@ DEFINE_HOOK(0x51F179, InfantryClass_WhatAction_Immune_FakeEngineer, 0x5) 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 (pThis && pBuilding) { - if (canBeDefused) - R->EBP(Action::DisarmBomb); - else - R->EBP(Action::Attack); + 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 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)); @@ -157,64 +176,70 @@ DEFINE_HOOK(0x6FC31C, TechnoClass_CanFire_ForceWeapon, 0xF) if (newIndex >= 0) nWeaponIdx = newIndex; } - + return 0; } -DEFINE_HOOK(0x6FCB81, TechnoClass_CanFire_Immune_FakeEngineer_CanCaptureBuildings, 0x5) +DEFINE_HOOK(0x6FC705, TechnoClass_CanFire_CivilianBlock_FakeEngineer, 0x7) { - 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)); + enum { BypassEntireBlock = 0x6FC879, ContinueVanilla = 0 }; - const auto pFirer = abstract_cast(pThis); - const auto pVictim = abstract_cast(pTarget); + GET_STACK(WeaponTypeClass*, pWeapon, 0x10); - if (!pFirer || !pVictim) - return 0; + if (pWeapon && pWeapon->Warhead) + { + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + if (pWHExt->FakeEngineer_CanCaptureBuildings + || pWHExt->FakeEngineer_CanRepairBridges + || pWHExt->FakeEngineer_CanDestroyBridges + || pWHExt->FakeEngineer_BombDisarm) + { + return BypassEntireBlock; + } + } - const auto pWeapon = pFirer->GetWeapon(nWeaponIdx)->WeaponType; - if (!pWeapon) - return 0; + return ContinueVanilla; +} - const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); +DEFINE_HOOK(0x6FCB53, TechnoClass_CanFire_FakeEngineer, 0x9) +{ + enum { BypassToHeightAndRangeChecks = 0x6FCC5D, ContinueVanilla = 0 }; - const int weaponRange = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pFirer); - const int currentRange = pFirer->DistanceFrom(pVictim); + GET(TechnoClass* const, pFirer, ESI); + GET_STACK(AbstractClass* const, pTarget, 0x24); + GET_STACK(WeaponTypeClass* const, pWeapon, 0x10); - if (pVictim->AttachedBomb - && pWHExt->FakeEngineer_BombDisarm) + if (pFirer && pTarget && pWeapon && pWeapon->Warhead) { - if (currentRange <= weaponRange) - R->EAX(FireError::OK); - else - R->EAX(FireError::RANGE); // Out of range + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWeapon->Warhead); + const auto pVictim = abstract_cast(pTarget); - return ForceNewValue; - } + if (pVictim && pVictim->AttachedBomb && pWHExt->FakeEngineer_BombDisarm) + return BypassToHeightAndRangeChecks; - const auto pBuilding = abstract_cast(pTarget); + 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 (pBuilding && pBuilding->IsAlive && pBuilding->Health > 0) + { + if (pBuilding->Type->BridgeRepairHut + && (pWHExt->FakeEngineer_CanRepairBridges || pWHExt->FakeEngineer_CanDestroyBridges)) + { + return BypassToHeightAndRangeChecks; + } - if (!pWHExt->FakeEngineer_CanCaptureBuildings) - return 0; + const bool canCaptureHouse = pBuilding->Owner != pFirer->Owner + && (!pFirer->Owner->IsAlliedWith(pBuilding->Owner) || pBuilding->Owner->IsNeutral()); - if (currentRange <= weaponRange) - R->EAX(FireError::OK); - else - R->EAX(FireError::RANGE); // Out of range + if (canCaptureHouse + && (pBuilding->Type->Capturable || pBuilding->Type->NeedsEngineer) + && pWHExt->FakeEngineer_CanCaptureBuildings) + { + return BypassToHeightAndRangeChecks; + } + } + } - return ForceNewValue; + return ContinueVanilla; } #pragma endregion diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 479746bef6..427ca25b3d 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -12,15 +12,20 @@ int TechnoExt::PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, A auto const pWeaponStructOne = pThis->GetWeapon(weaponIndexOne); auto const pWeaponStructTwo = pThis->GetWeapon(weaponIndexTwo); - if (!pWeaponStructOne && !pWeaponStructTwo) + bool hasOne = pWeaponStructOne && pWeaponStructOne->WeaponType; + bool hasTwo = pWeaponStructTwo && pWeaponStructTwo->WeaponType; + + if (!hasOne && !hasTwo) return -1; - else if (!pWeaponStructTwo) + else if (!hasTwo) return weaponIndexOne; - else if (!pWeaponStructOne) + else if (!hasOne) return weaponIndexTwo; auto const pWeaponTwo = pWeaponStructTwo->WeaponType; auto const pSecondExt = WeaponTypeExt::ExtMap.Find(pWeaponTwo); + if (!pSecondExt) + return weaponIndexOne; CellClass* pTargetCell = nullptr; @@ -50,8 +55,11 @@ int TechnoExt::PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, A } } - const bool secondIsAA = pTargetTechno && pTargetTechno->IsInAir() && pWeaponTwo->Projectile->AA; + const bool secondIsAA = pTargetTechno && pTargetTechno->IsInAir() && pWeaponTwo->Projectile && pWeaponTwo->Projectile->AA; auto const pFirstExt = WeaponTypeExt::ExtMap.Find(pWeaponStructOne->WeaponType); + if (!pFirstExt) + return weaponIndexTwo; + const bool skipPrimaryPicking = pFirstExt->SkipWeaponPicking; const bool firstAllowedAE = !skipPrimaryPicking && pFirstExt->HasRequiredAttachedEffects(pTargetTechno, pThis); @@ -421,8 +429,18 @@ bool TechnoExt::MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* cons if (pTechno) { + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + const bool isFakeEngineer = pWHExt->FakeEngineer_CanCaptureBuildings + || pWHExt->FakeEngineer_CanRepairBridges + || pWHExt->FakeEngineer_CanDestroyBridges + || pWHExt->FakeEngineer_BombDisarm; + + bool houseAllowed = EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTechnoOwner); + if (!houseAllowed && isFakeEngineer && pTechnoOwner != pOwner && pTechnoOwner->IsNeutral()) + houseAllowed = true; + if (!EnumFunctions::IsTechnoEligible(pTechno, pWeaponExt->CanTarget) - || !EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTechnoOwner) + || !houseAllowed || !pWeaponExt->IsHealthInThreshold(pTechno) || !pWeaponExt->HasRequiredAttachedEffects(pTechno, pThis)) { @@ -478,18 +496,17 @@ 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; + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + const bool isFakeEngineer = pWHExt->FakeEngineer_CanCaptureBuildings + || pWHExt->FakeEngineer_CanRepairBridges + || pWHExt->FakeEngineer_CanDestroyBridges + || pWHExt->FakeEngineer_BombDisarm; - if (canBypassImmune) - return true; + if (isFakeEngineer) + return true; + if (pTechnoType->Immune) 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 7d86623b53..37dcae6534 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -68,8 +68,10 @@ int TechnoTypeExt::ExtData::SelectForceWeapon(TechnoClass* pThis, AbstractClass* { if (const auto pBuildingType = abstract_cast(pTargetType)) { - if ((pBuildingType->Capturable || pBuildingType->NeedsEngineer) - && !pThis->Owner->IsAlliedWith(pTargetTechno->Owner)) + const bool canCaptureHouse = pTargetTechno->Owner != pThis->Owner + && (!pThis->Owner->IsAlliedWith(pTargetTechno->Owner) || pTargetTechno->Owner->IsNeutral()); + + if ((pBuildingType->Capturable || pBuildingType->NeedsEngineer) && canCaptureHouse) forceWeaponIndex = this->ForceWeapon_Capture; } } diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 22cbd24479..fe78b7e9fd 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -30,8 +30,11 @@ bool WarheadTypeExt::ExtData::CanTargetHouse(HouseClass* pHouse, TechnoClass* pT if (affectsAllies && isAllies) return pOwner != pHouse; - if (this->AffectsEnemies && !isAllies) - return true; + if (this->AffectsEnemies && (!isAllies || (this->FakeEngineer_CanCaptureBuildings && pOwner->IsNeutral()))) + return pOwner != pHouse; + + if (this->FakeEngineer_CanCaptureBuildings && pOwner->IsNeutral()) + return pOwner != pHouse; return false; } @@ -111,72 +114,6 @@ 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 diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 055a395af3..241e4d9798 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -464,5 +464,4 @@ 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 d0f67263e1..c9d35f497a 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -351,14 +351,19 @@ DEFINE_HOOK(0x4423B7, BuildingClass_ReceiveDamage_BridgeRepairHut, 0xC) 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) + if (pThis && pWarhead) { - 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); + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); + const bool isBridgeDestroyed = MapClass::Instance.IsLinkedBridgeDestroyed(pThis->GetMapCoords()); + + if (!isBridgeDestroyed && pWHExt->FakeEngineer_CanDestroyBridges) + { + TechnoExt::RepairOrDestroyBridgeHut(pThis, pSource, pHouse, true); + } + else if (isBridgeDestroyed && pWHExt->FakeEngineer_CanRepairBridges) + { + TechnoExt::RepairOrDestroyBridgeHut(pThis, pSource, pHouse, false); + } } return 0; From a03fe1b48f0f7ef909e1e307c7c931e589123f89 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 28 Aug 2026 13:34:03 +0200 Subject: [PATCH 10/11] more changes --- src/Ext/Techno/Body.cpp | 12 +----------- src/Ext/Techno/Hooks.ReceiveDamage.cpp | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 16795149f3..10bc482f9b 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -639,17 +639,7 @@ bool TechnoExt::CanBeAffectedByFakeEngineer(TechnoClass* pThis, TechnoClass* pTa if (!pThis || !pTarget) return false; - 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); - + int nWeaponIndex = pThis->SelectWeapon(pTarget); if (nWeaponIndex < 0) return false; diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index f209808304..0348e34720 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -75,7 +75,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (args->Attacker && pTag) pTag->RaiseEvent(TriggerEvent::EnteredBy, args->Attacker, CellStruct::Empty); - reinterpret_cast(0x448260)(pBuilding, pSourceHouse, true); + pBuilding->SetOwningHouse(pSourceHouse, true); } // Disarm bomb From 5de52b2fba5821108ddec15d03a1db66a5c5c38c Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 28 Aug 2026 13:42:35 +0200 Subject: [PATCH 11/11] fix docs --- docs/New-or-Enhanced-Logics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 1d59d910a0..a818cf84e4 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2277,7 +2277,7 @@ While this feature can provide better performance than a large `CellSpread` valu - `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` 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. +- `FakeEngineer.BombDisarm`, if set to true, an attached bomb will be removed if the target is affected by the Warhead. In `rulesmd.ini`: ```ini @@ -2285,7 +2285,7 @@ In `rulesmd.ini`: FakeEngineer.CanRepairBridges=false ; boolean FakeEngineer.CanDestroyBridges=false ; boolean FakeEngineer.CanCaptureBuildings=false ; boolean -FakeEngineer.DisarmBombs=false ; boolean +FakeEngineer.BombDisarm=false ; boolean ``` ### Fire weapon when Warhead kills something