From 23355d5638cf5850f9161325fa030781feb33fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Sat, 28 Jun 2025 16:57:42 +0800 Subject: [PATCH 01/12] trackers --- src/Ext/Scenario/Body.cpp | 2 ++ src/Ext/Scenario/Body.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index 8b4a88f975..94bac3431c 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -170,6 +170,8 @@ void ScenarioExt::ExtData::Serialize(T& Stm) .Process(this->MasterDetonationBullet) .Process(this->LimboLaunchers) // .Process(this->NewMessageList); // Should not S/L + .Process(this->UndergroundTracker) + .Process(this->SpecialTracker) ; } diff --git a/src/Ext/Scenario/Body.h b/src/Ext/Scenario/Body.h index 228da80fda..33c851e9ae 100644 --- a/src/Ext/Scenario/Body.h +++ b/src/Ext/Scenario/Body.h @@ -49,6 +49,9 @@ class ScenarioExt BulletClass* MasterDetonationBullet; // Used to do warhead/weapon detonations on spot without having to create new BulletClass instance every time. std::vector LimboLaunchers; + DynamicVectorClass UndergroundTracker; + DynamicVectorClass SpecialTracker; + ExtData(ScenarioClass* OwnerObject) : Extension(OwnerObject) , ShowBriefing { false } , BriefingTheme { -1 } @@ -64,6 +67,8 @@ class ScenarioExt , DefaultLS800BkgdPal {} , MasterDetonationBullet {} , LimboLaunchers {} + , UndergroundTracker {} + , SpecialTracker {} { } void SetVariableToByID(bool bIsGlobal, int nIndex, char bState); From b4798cbb29ef81b7c831447cf86f4d43a860298b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Sat, 28 Jun 2025 19:26:38 +0800 Subject: [PATCH 02/12] tracked --- src/Ext/Techno/Body.cpp | 8 ++++++++ src/Ext/Techno/Body.h | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 4ca5765dea..b0faae47e0 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -929,6 +929,8 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->TintIntensityAllies) .Process(this->TintIntensityEnemies) .Process(this->AttackMoveFollowerTempCount) + .Process(this->UndergroundTracked) + .Process(this->SpecialTracked) ; } @@ -985,6 +987,12 @@ DEFINE_HOOK(0x6F4500, TechnoClass_DTOR, 0x5) { GET(TechnoClass*, pItem, ECX); + if (TechnoExt::ExtMap.Find(pItem)->UndergroundTracked) + ScenarioExt::Global()->UndergroundTracker.Remove(pItem); + + if (TechnoExt::ExtMap.Find(pItem)->SpecialTracked) + ScenarioExt::Global()->SpecialTracker.Remove(pItem); + TechnoExt::ExtMap.Remove(pItem); return 0; diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index f38356513c..542a437e82 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -96,6 +96,9 @@ class TechnoExt int AttackMoveFollowerTempCount; + bool UndergroundTracked; + bool SpecialTracked; + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } , Shield {} @@ -156,6 +159,8 @@ class TechnoExt , TintIntensityAllies { 0 } , TintIntensityEnemies { 0 } , AttackMoveFollowerTempCount { 0 } + , UndergroundTracked { false } + , SpecialTracked { false } { } void OnEarlyUpdate(); From d32bc609785744a424b50038a8914da3d67b4f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:47:49 +0800 Subject: [PATCH 03/12] AU & AffectsUnderground --- src/Ext/Bullet/Hooks.DetonateLogics.cpp | 67 ++++++++++ src/Ext/BulletType/Body.cpp | 2 + src/Ext/BulletType/Body.h | 3 + src/Ext/Techno/Hooks.cpp | 161 ++++++++++++++++++++++++ src/Ext/WarheadType/Body.cpp | 4 + src/Ext/WarheadType/Body.h | 4 + 6 files changed, 241 insertions(+) diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 11e5ab6ec9..169ca9b282 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -775,3 +775,70 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6) } #pragma endregion + +#pragma region AffectsUnderground + +// In vanilla, only ground is allowed, and Ares added air and top. +// But it seems that underground and surface is also working fine? +DEFINE_HOOK(0x469453, BulletClass_Logics_TemporalUnderGround, 0x6) +{ + enum { NotOK = 0x469AA4, OK = 0x469475 }; + + GET(FootClass*, pTarget, EAX); + + Layer layer = pTarget->InWhichLayer(); + + if (layer != Layer::None) + return OK; + + return NotOK; +} + +DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7) +{ + GET_STACK(bool, isNullified, STACK_OFFSET(0xE0, -0xC9)); + GET_STACK(int, damage, STACK_OFFSET(0xE0, -0xBC)); + GET_STACK(CoordStruct*, pCrd, STACK_OFFSET(0xE0, -0xB8)); + GET_BASE(WarheadTypeClass*, pWH, 0xC); + GET_BASE(TechnoClass*, pSrcTechno, 0x8); + GET_BASE(HouseClass*, pSrcHouse, 0x14); + GET_STACK(bool, hitted, STACK_OFFSET(0xE0, -0xC1)); // bHitted = true + + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + + if (isNullified || !pWHExt || !pWHExt->AffectsUnderground) + return 0; + + // bool cylinder = pWHExt->CellSpread_Cylinder; + float spread = pWH->CellSpread; + + for (auto const& pTechno : ScenarioExt::Global()->UndergroundTracker) + { + if (pTechno->InWhichLayer() == Layer::Underground // Layer. + && pTechno->IsAlive && !pTechno->IsIronCurtained() + && !pTechno->IsOnMap // Underground is not on map. + && !pTechno->InLimbo) + { + double dist = 0.0; + auto technoCoords = pTechno->GetCoords(); + + //if (cylinder) + // dist = CoordStruct{ technoCoords.X - pCrd->X, technoCoords.Y - pCrd->Y, 0 }.Magnitude(); + //else + dist = technoCoords.DistanceFrom(*pCrd); + + if (dist <= spread * 256) + { + pTechno->ReceiveDamage(&damage, (int)dist, pWH, pSrcTechno, false, false, pSrcHouse); + hitted = true; + } + } + } + + R->Stack8(STACK_OFFSET(0xE0, -0xC1), true); + return 0; +} + + + +#pragma endregion \ No newline at end of file diff --git a/src/Ext/BulletType/Body.cpp b/src/Ext/BulletType/Body.cpp index 447a856092..a0eadcb2cb 100644 --- a/src/Ext/BulletType/Body.cpp +++ b/src/Ext/BulletType/Body.cpp @@ -73,6 +73,7 @@ void BulletTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Parachuted_FallRate.Read(exINI, pSection, "Parachuted.FallRate"); this->Parachuted_MaxFallRate.Read(exINI, pSection, "Parachuted.MaxFallRate"); this->BombParachute.Read(exINI, pSection, "BombParachute"); + this->AU.Read(exINI, pSection, "AU"); // Ares 0.7 this->BallisticScatter_Min.Read(exINI, pSection, "BallisticScatter.Min"); @@ -170,6 +171,7 @@ void BulletTypeExt::ExtData::Serialize(T& Stm) .Process(this->Parachuted_FallRate) .Process(this->Parachuted_MaxFallRate) .Process(this->BombParachute) + .Process(this->AU) .Process(this->TrajectoryType) // just keep this shit at last ; diff --git a/src/Ext/BulletType/Body.h b/src/Ext/BulletType/Body.h index 84cbf8b235..e85909e894 100644 --- a/src/Ext/BulletType/Body.h +++ b/src/Ext/BulletType/Body.h @@ -72,6 +72,8 @@ class BulletTypeExt Nullable Parachuted_MaxFallRate; Nullable BombParachute; + Valueable AU; + // Ares 0.7 Nullable BallisticScatter_Min; Nullable BallisticScatter_Max; @@ -122,6 +124,7 @@ class BulletTypeExt , Parachuted_FallRate { 1 } , Parachuted_MaxFallRate {} , BombParachute {} + , AU { false } { } virtual ~ExtData() = default; diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index ab10b860c6..ebd8fa5669 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1298,3 +1299,163 @@ DEFINE_HOOK(0x71A8BD, TemporalClass_Update_WarpAwayAnim, 0x5) return 0; } + + +#pragma region AttackUnderGround + +DEFINE_HOOK(0x70023B, TechnoClass_MouseOverObject_AttackUnderGround, 0x5) +{ + enum { FireIsOK = 0x700246, FireIsNotOK = 0x70056C }; + + GET(ObjectClass*, pObject, EDI); + GET(TechnoClass*, pThis, ESI); + GET(int, wpIdx, EAX); + + if (pObject->IsSurfaced()) + return FireIsOK; + + auto const pWeapon = pThis->GetWeapon(wpIdx)->WeaponType; + + return (!pWeapon || !BulletTypeExt::ExtMap.Find(pWeapon->Projectile)->AU) ? FireIsNotOK : FireIsOK; +} + +DEFINE_HOOK_AGAIN(0x729029, TunnelLocomotionClass_Process_Track, 0x7); +DEFINE_HOOK(0x728F9A, TunnelLocomotionClass_Process_Track, 0x7) +{ + // GET(FootClass*, pTechno, ECX); + GET(ILocomotion*, pThis, ESI); + + const auto pLoco = static_cast(pThis); + auto pTechno = pLoco->LinkedTo; + ScenarioExt::Global()->UndergroundTracker.AddUnique(pTechno); + TechnoExt::ExtMap.Find(pTechno)->UndergroundTracked = true; + + return 0; +} + +DEFINE_HOOK(0x7297F6, TunnelLocomotionClass_ProcessDigging_Track, 0x7) +{ + GET(FootClass*, pTechno, ECX); + + ScenarioExt::Global()->UndergroundTracker.Remove(pTechno); + TechnoExt::ExtMap.Find(pTechno)->UndergroundTracked = false; + + return 0; +} + +DEFINE_HOOK(0x772AB3, WeaponTypeClass_AllowedThreats_AU, 0x5) +{ + GET(BulletTypeClass* const, pType, ECX); + GET(ThreatType, flags, EAX); + + if (BulletTypeExt::ExtMap.Find(pType)->AU) + R->EAX(static_cast(flags) | 0x20000u); + + return 0; +} + +namespace SelectAutoTarget_Context +{ + bool AU = false; +} + +DEFINE_HOOK(0x6F8DF0, TechnoClass_SelectAutoTarget_Start_AU, 0x9) +{ + GET_STACK(unsigned int, flags, 0x4); + SelectAutoTarget_Context::AU = (flags & 0x20000u) != 0; + return 0; +} + +DEFINE_HOOK(0x6F8FA8, TechnoClass_SelectAutoTarget_SetCanTargetWhatAmI_AU, 0x6) +{ + REF_STACK(int, canTargetWhatAmI, STACK_OFFSET(0x6C, -0x58)); + + if (SelectAutoTarget_Context::AU || ScenarioExt::Global()->SpecialTracker.Count) + { + canTargetWhatAmI |= 1 << (int)InfantryClass::AbsID; + canTargetWhatAmI |= 1 << (int)UnitClass::AbsID; + canTargetWhatAmI |= 1 << (int)AircraftClass::AbsID; + } + + return 0; +} + +DEFINE_HOOK(0x6F93BB, TechnoClass_SelectAutoTarget_Scan_AU, 0x6) +{ + enum { FuncRet = 0x6F9DA1, Continue = 0x6F93C1 }; + + REF_STACK(const TechnoClass*, pBestTarget, STACK_OFFSET(0x6C, -0x4C)); + REF_STACK(int, bestThreat, STACK_OFFSET(0x6C, -0x50)); + GET_STACK(const bool, transportMCed, STACK_OFFSET(0x6C, -0x59)); + GET_STACK(const bool, onlyTargetEnemyHouse, STACK_OFFSET(0x6C, 0xC)); + GET_STACK(const int, canTargetWhatAmI, STACK_OFFSET(0x6C, -0x58)); + GET_STACK(const int, wantedDist, STACK_OFFSET(0x6C, -0x40)); + GET_STACK(const ThreatType, flags, STACK_OFFSET(0x6C, 0x4)); + GET(TechnoClass* const, pThis, ESI); + + const auto pType = pThis->GetTechnoType(); + const auto pOwner = pThis->Owner; + const bool targetFriendly = pType->AttackFriendlies || pThis->Berzerk || transportMCed || pThis->CombatDamage(-1) < 0; + + int threatBuffer = 0; + auto tempCrd = CoordStruct::Empty; + + for (const auto pCurrent : ScenarioExt::Global()->SpecialTracker) + { + if ((!pOwner->IsAlliedWith(pCurrent) || targetFriendly) + && (!onlyTargetEnemyHouse || pCurrent->Owner->ArrayIndex == pThis->Owner->EnemyHouseIndex) + && pThis->CanAutoTargetObject(flags, canTargetWhatAmI, wantedDist, pCurrent, &threatBuffer, UINT_MAX, &tempCrd)) + { + if (pType->DistributedFire) + { + pThis->CurrentTargets.AddItem(pCurrent); + pThis->CurrentTargetThreatValues.AddItem(threatBuffer); + } + + if (threatBuffer > bestThreat) + { + pBestTarget = pCurrent; + bestThreat = threatBuffer; + } + } + } + + if (SelectAutoTarget_Context::AU) + { + for (const auto pCurrent : ScenarioExt::Global()->UndergroundTracker) + { + if ((!pOwner->IsAlliedWith(pCurrent) || targetFriendly) + && (!onlyTargetEnemyHouse || pCurrent->Owner->ArrayIndex == pThis->Owner->EnemyHouseIndex) + && pThis->CanAutoTargetObject(flags, canTargetWhatAmI, wantedDist, pCurrent, &threatBuffer, UINT_MAX, &tempCrd)) + { + if (pType->DistributedFire) + { + pThis->CurrentTargets.AddItem(pCurrent); + pThis->CurrentTargetThreatValues.AddItem(threatBuffer); + } + + if (threatBuffer > bestThreat) + { + pBestTarget = pCurrent; + bestThreat = threatBuffer; + } + } + } + } + + GET(int, rangeFindingCell, ECX); + + return rangeFindingCell <= 0 ? FuncRet : Continue; +} + +DEFINE_HOOK(0x6F7E1E, TechnoClass_CanAutoTargetObject_AU, 0x6) +{ + enum { Continue = 0x6F7E24, ReturnFalse = 0x6F894F }; + + GET(TechnoClass*, pTarget, ESI); + GET(int, height, EAX); + + return height >= -20 || SelectAutoTarget_Context::AU || TechnoExt::ExtMap.Find(pTarget)->SpecialTracked ? Continue : ReturnFalse; +} + +#pragma endregion diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 550b19d1b3..6fb20cc0a9 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -297,6 +297,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->UnlimboDetonate_KeepTarget.Read(exINI, pSection, "UnlimboDetonate.KeepTarget"); this->UnlimboDetonate_KeepSelected.Read(exINI, pSection, "UnlimboDetonate.KeepSelected"); + this->AffectsUnderground.Read(exINI, pSection, "AffectsUnderground"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -575,6 +577,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->UnlimboDetonate_KeepTarget) .Process(this->UnlimboDetonate_KeepSelected) + .Process(this->AffectsUnderground) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 4429a4548f..a0205801c2 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -200,6 +200,8 @@ class WarheadTypeExt Valueable UnlimboDetonate_KeepTarget; Valueable UnlimboDetonate_KeepSelected; + Valueable AffectsUnderground; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -420,6 +422,8 @@ class WarheadTypeExt , UnlimboDetonate_ForceLocation { false } , UnlimboDetonate_KeepTarget { true } , UnlimboDetonate_KeepSelected { true } + + , AffectsUnderground { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); From f45aa2d7fa39aea63d0ffc70b8a9cbea0f505fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:08:46 +0800 Subject: [PATCH 04/12] anim --- src/Ext/Bullet/Hooks.DetonateLogics.cpp | 9 +++++++++ src/Ext/WarheadType/Body.cpp | 4 ++++ src/Ext/WarheadType/Body.h | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 169ca9b282..488d681723 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -260,6 +260,15 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8) if (pAnimType) { auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH); + int cellHeight = MapClass::Instance.GetCellFloorHeight(*coords); + auto newCrds = pWHExt->PlayAnimAboveSurface ? CoordStruct{ coords->X, coords->Y, Math::max(cellHeight, coords->Z) } : *coords; + + if (cellHeight > newCrds.Z && !pWHExt->PlayAnimUnderground) + { + R->EAX(createdAnim); + return SkipGameCode; + } + auto const pOwner = pThis->Owner; const bool splashed = pWHExt->Splashed; const int creationInterval = splashed ? pWHExt->SplashList_CreationInterval : pWHExt->AnimList_CreationInterval; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 6fb20cc0a9..f32b351178 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -298,6 +298,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->UnlimboDetonate_KeepSelected.Read(exINI, pSection, "UnlimboDetonate.KeepSelected"); this->AffectsUnderground.Read(exINI, pSection, "AffectsUnderground"); + this->PlayAnimUnderground.Read(exINI, pSection, "PlayAnimUnderground"); + this->PlayAnimAboveSurface.Read(exINI, pSection, "PlayAnimAboveSurface"); // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -578,6 +580,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->UnlimboDetonate_KeepSelected) .Process(this->AffectsUnderground) + .Process(this->PlayAnimUnderground) + .Process(this->PlayAnimAboveSurface) // Ares tags .Process(this->AffectsEnemies) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index a0205801c2..271d1d36f5 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -201,6 +201,8 @@ class WarheadTypeExt Valueable UnlimboDetonate_KeepSelected; Valueable AffectsUnderground; + Valueable PlayAnimUnderground; + Valueable PlayAnimAboveSurface; // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html @@ -424,6 +426,8 @@ class WarheadTypeExt , UnlimboDetonate_KeepSelected { true } , AffectsUnderground { false } + , PlayAnimUnderground { true } + , PlayAnimAboveSurface { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); From 86d35c350c659c4de0f9cb5d40201fd68b7f5850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 12:59:08 +0800 Subject: [PATCH 05/12] canfire --- src/Ext/Bullet/Hooks.DetonateLogics.cpp | 2 +- src/Ext/Techno/Hooks.Firing.cpp | 55 +++++++++++++++++++++---- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 488d681723..f68cec6ffb 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -320,7 +320,7 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8) if (!pType) continue; - auto animCoords = *coords; + auto animCoords = newCrds; if (allowScatter) { diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index b38c01e854..9eee44197b 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -145,10 +145,14 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8) { if (pShield->IsActive()) { - const auto secondary = pThis->GetWeapon(1)->WeaponType; - const bool secondaryIsAA = pTargetTechno && pTargetTechno->IsInAir() && secondary && secondary->Projectile->AA; - - if (secondary && (allowFallback || (allowAAFallback && secondaryIsAA) || TechnoExt::CanFireNoAmmoWeapon(pThis, 1))) + const auto pSecondary = pThis->GetWeapon(1)->WeaponType; + + if (pSecondary + && (allowFallback + || (pTargetTechno + && ((allowAAFallback && pTargetTechno->IsInAir() && pSecondary->Projectile->AA) + || (pTargetTechno->InWhichLayer() == Layer::Underground && BulletTypeExt::ExtMap.Find(pSecondary->Projectile)->AU))) + || TechnoExt::CanFireNoAmmoWeapon(pThis, 1))) { if (!pShield->CanBeTargeted(pThis->GetWeapon(0)->WeaponType)) return Secondary; @@ -171,12 +175,22 @@ DEFINE_HOOK(0x6F37EB, TechnoClass_WhatWeaponShouldIUse_AntiAir, 0x6) GET_STACK(WeaponTypeClass*, pWeapon, STACK_OFFSET(0x18, -0x4)); GET(WeaponTypeClass*, pSecWeapon, EAX); - if (!pWeapon->Projectile->AA && pSecWeapon->Projectile->AA) + if (const auto pTargetTechno = abstract_cast(pTarget)) { - const auto pTargetTechno = abstract_cast(pTarget); + const auto pPrimaryProj = pWeapon->Projectile; + const auto pSecondaryProj = pSecWeapon->Projectile; - if (pTargetTechno && pTargetTechno->IsInAir()) - return Secondary; + if (!pPrimaryProj->AA && pSecondaryProj->AA) + { + if (pTargetTechno->IsInAir()) + return Secondary; + } + + if (BulletTypeExt::ExtMap.Find(pSecondaryProj)->AU && !BulletTypeExt::ExtMap.Find(pPrimaryProj)->AU) + { + if (pTargetTechno->InWhichLayer() == Layer::Underground) + return Secondary; + } } return Primary; @@ -221,6 +235,11 @@ DEFINE_HOOK(0x6F3432, TechnoClass_WhatWeaponShouldIUse_Gattling, 0xA) { chosenWeaponIndex = evenWeaponIndex; } + else if (pTargetTechno->InWhichLayer() == Layer::Underground) + { + if (BulletTypeExt::ExtMap.Find(pWeaponEven->Projectile)->AU && !BulletTypeExt::ExtMap.Find(pWeaponOdd->Projectile)->AU) + chosenWeaponIndex = evenWeaponIndex; + } else { auto const landType = pTargetTechno->GetCell()->LandType; @@ -428,6 +447,26 @@ DEFINE_HOOK(0x6FCBE6, TechnoClass_CanFire_BridgeAAFix, 0x6) return 0; } +DEFINE_HOOK(0x6FC749, TechnoClass_GetFireError_AntiUnderground, 0x5) +{ + enum { Illegal = 0x6FC86A, GoOtherChecks = 0x6FC762 }; + + GET(Layer, layer, EAX); + //GET(TechnoClass*, pThis, EBX); + GET(WeaponTypeClass*, pWeapon, EDI); + + auto const pProj = pWeapon->Projectile; + auto const pProjExt = BulletTypeExt::ExtMap.Find(pProj); + + if (layer == Layer::Underground && !pProjExt->AU) + return Illegal; + + if ((layer == Layer::Air || layer == Layer::Top) && !pProj->AA) + return Illegal; + + return GoOtherChecks; +} + #pragma endregion #pragma region TechnoClass_Fire From 05f65311ae6a38b6b0977fdee34532b994ab510f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:25:19 +0800 Subject: [PATCH 06/12] docs --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 27 +++++++++++++++++++++++++++ docs/Whats-New.md | 1 + 3 files changed, 29 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index aecfca49b0..9131458405 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -616,6 +616,7 @@ This page lists all the individual contributions to the project by their author. - Customize the chained damage of the wall - Fix an issue that jumpjet vehicles can not stop correctly when assigned a target in range - Fix an issue that jumpjet infantries stop incorrectly when assigned a target out of range + - Attack and damage technos underground - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 522fa43b04..530d6c5053 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -747,6 +747,17 @@ OnlyUseLandSequences=false ; boolean ## Projectiles +### Attack technos underground + +- Now, you can enable projectiles to attack technos underground. + - To actually damage the technos, you need [AffectsUnderground](#damage-technos-underground). + +In `rulesmd.ini`: +```ini +[SOMEPROJECTILE] ; Projectile +AU=false ; boolean +``` + ### Parabombs - Restored feature from Red Alert 1 (also partially implemented in Ares but undocumented, if used together Phobos' version takes priority) that allows projectiles to be parachuted down to ground if fired by an aerial unit. @@ -2271,6 +2282,22 @@ DamageTargetHealthMultiplier=0.0 ; floating point value `DamageAlliesMultiplier` won't affect your own units like `AffectsAllies` did. ``` +### Damage technos underground + +- 现在你可以让弹头伤害位于地下的technos了! + - To allow weapons to target underground technos, you need [AU](#attack-technos-underground). +- 注意到,如果抛射体在地下引爆,其动画效果可能看上去奇怪。 + - 你可以使用 `[SOMEWARHEAD] -> PlayAnimUnderground=false` 让抛射体在地下引爆时不播放弹头动画。 + - 你也可以使用 `[SOMEWARHEAD] -> PlayAnimAboveSurface=true` 让抛射体在地下引爆时在正上方的地面播放弹头动画。 + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +AffectsUnderground=false ; boolean +PlayAnimUnderground=true ; boolean +PlayAnimAboveSurface=false ; boolean, +``` + ### Detonate Warhead on all objects on map - Setting `DetonateOnAllMapObjects` to true allows a Warhead that is detonated by a projectile (for an example, this excludes things like animation `Warhead` and Ares' GenericWarhead superweapon but includes `Crit.Warhead` and animation `Weapon`) and consequently any `AirburstWeapon/ShrapnelWeapon` that may follow to detonate on each object currently alive and existing on the map regardless of its actual target, with optional filters. Note that this is done immediately prior Warhead detonation so after `PreImpactAnim` *(Ares feature)* has been displayed. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 5850eda5f3..7f2a6fc5cf 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -448,6 +448,7 @@ New: - Customize the chained damage of the wall (by TaranDahl) - Allow the aircraft to enter area guard mission and not crash immediately without any airport (by CrimRecya) - Unlimbo Detonate warhead (by FlyStar) +- Attack and damage technos underground (by TaranDahl) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) From de9647c79caf412a9a08938849d331cbd4987354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:30:11 +0800 Subject: [PATCH 07/12] Update New-or-Enhanced-Logics.md --- docs/New-or-Enhanced-Logics.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 530d6c5053..9c167d140a 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2284,11 +2284,11 @@ DamageTargetHealthMultiplier=0.0 ; floating point value ### Damage technos underground -- 现在你可以让弹头伤害位于地下的technos了! +- Now you can make the warhead damage technos underground! - To allow weapons to target underground technos, you need [AU](#attack-technos-underground). -- 注意到,如果抛射体在地下引爆,其动画效果可能看上去奇怪。 - - 你可以使用 `[SOMEWARHEAD] -> PlayAnimUnderground=false` 让抛射体在地下引爆时不播放弹头动画。 - - 你也可以使用 `[SOMEWARHEAD] -> PlayAnimAboveSurface=true` 让抛射体在地下引爆时在正上方的地面播放弹头动画。 +- Notice that if the projectile detonates underground, its animation effect may look strange. + - You can use `[SOMEWARHEAD] -> PlayAnimUnderground=false` to prevent the warhead animation from playing when the projectile detonates underground. + - You can also use `[SOMEWARHEAD] -> PlayAnimAboveSurface=true` to make the warhead animation play on the ground directly above when the projectile detonates underground. In `rulesmd.ini`: ```ini From a4086f56f9bf79901c94a23f91eb50c2ff4d7a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:32:13 +0800 Subject: [PATCH 08/12] Update New-or-Enhanced-Logics.md --- docs/New-or-Enhanced-Logics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 9c167d140a..779dec7202 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2295,7 +2295,7 @@ In `rulesmd.ini`: [SOMEWARHEAD] ; WarheadType AffectsUnderground=false ; boolean PlayAnimUnderground=true ; boolean -PlayAnimAboveSurface=false ; boolean, +PlayAnimAboveSurface=false ; boolean ``` ### Detonate Warhead on all objects on map From 8553ee7cfc18e6e1ab4368600a9ae154e2fe9def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:59:33 +0800 Subject: [PATCH 09/12] Update New-or-Enhanced-Logics.md --- docs/New-or-Enhanced-Logics.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0e8867a219..e1eb4ce4b5 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -758,6 +758,10 @@ In `rulesmd.ini`: AU=false ; boolean ``` +```{note} +Only vanilla projectiles with `Inviso=yes` set or [Phobos projectiles](#projectile-trajectories) with `SubjectToGround=yes` enabled can go beneath the ground. Otherwise, the projectile will be forced to detonate upon hitting the ground. +``` + ### Parabombs - Restored feature from Red Alert 1 (also partially implemented in Ares but undocumented, if used together Phobos' version takes priority) that allows projectiles to be parachuted down to ground if fired by an aerial unit. From ae5144e58e2e996969257a91ecc1dd46af076aa8 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Sat, 27 Sep 2025 20:26:44 +0800 Subject: [PATCH 10/12] codestyle --- src/Ext/Bullet/Hooks.DetonateLogics.cpp | 25 ++++++++++++------------- src/Ext/Techno/Hooks.Firing.cpp | 25 +++++++++++++++---------- src/Ext/Techno/Hooks.cpp | 10 +++++----- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index f68cec6ffb..7a68a207a2 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -260,8 +260,8 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8) if (pAnimType) { auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH); - int cellHeight = MapClass::Instance.GetCellFloorHeight(*coords); - auto newCrds = pWHExt->PlayAnimAboveSurface ? CoordStruct{ coords->X, coords->Y, Math::max(cellHeight, coords->Z) } : *coords; + const int cellHeight = MapClass::Instance.GetCellFloorHeight(*coords); + auto const newCrds = pWHExt->PlayAnimAboveSurface ? CoordStruct{ coords->X, coords->Y, Math::max(cellHeight, coords->Z) } : *coords; if (cellHeight > newCrds.Z && !pWHExt->PlayAnimUnderground) { @@ -795,9 +795,7 @@ DEFINE_HOOK(0x469453, BulletClass_Logics_TemporalUnderGround, 0x6) GET(FootClass*, pTarget, EAX); - Layer layer = pTarget->InWhichLayer(); - - if (layer != Layer::None) + if (pTarget->InWhichLayer() != Layer::None) return OK; return NotOK; @@ -805,7 +803,7 @@ DEFINE_HOOK(0x469453, BulletClass_Logics_TemporalUnderGround, 0x6) DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7) { - GET_STACK(bool, isNullified, STACK_OFFSET(0xE0, -0xC9)); + GET_STACK(const bool, isNullified, STACK_OFFSET(0xE0, -0xC9)); GET_STACK(int, damage, STACK_OFFSET(0xE0, -0xBC)); GET_STACK(CoordStruct*, pCrd, STACK_OFFSET(0xE0, -0xB8)); GET_BASE(WarheadTypeClass*, pWH, 0xC); @@ -813,13 +811,16 @@ DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7) GET_BASE(HouseClass*, pSrcHouse, 0x14); GET_STACK(bool, hitted, STACK_OFFSET(0xE0, -0xC1)); // bHitted = true + if (isNullified) + return 0; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); - if (isNullified || !pWHExt || !pWHExt->AffectsUnderground) + if (!pWHExt || !pWHExt->AffectsUnderground) return 0; // bool cylinder = pWHExt->CellSpread_Cylinder; - float spread = pWH->CellSpread; + const float spread = pWH->CellSpread; for (auto const& pTechno : ScenarioExt::Global()->UndergroundTracker) { @@ -829,14 +830,14 @@ DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7) && !pTechno->InLimbo) { double dist = 0.0; - auto technoCoords = pTechno->GetCoords(); + auto const technoCoords = pTechno->GetCoords(); //if (cylinder) // dist = CoordStruct{ technoCoords.X - pCrd->X, technoCoords.Y - pCrd->Y, 0 }.Magnitude(); //else dist = technoCoords.DistanceFrom(*pCrd); - if (dist <= spread * 256) + if (dist <= spread * Unsorted::LeptonsPerCell) { pTechno->ReceiveDamage(&damage, (int)dist, pWH, pSrcTechno, false, false, pSrcHouse); hitted = true; @@ -848,6 +849,4 @@ DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7) return 0; } - - -#pragma endregion \ No newline at end of file +#pragma endregion diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 2e0efba705..4b456b6cbe 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -453,18 +453,23 @@ DEFINE_HOOK(0x6FC749, TechnoClass_GetFireError_AntiUnderground, 0x5) { enum { Illegal = 0x6FC86A, GoOtherChecks = 0x6FC762 }; - GET(Layer, layer, EAX); - //GET(TechnoClass*, pThis, EBX); + GET(const Layer, layer, EAX); GET(WeaponTypeClass*, pWeapon, EDI); - auto const pProj = pWeapon->Projectile; - auto const pProjExt = BulletTypeExt::ExtMap.Find(pProj); - - if (layer == Layer::Underground && !pProjExt->AU) - return Illegal; - - if ((layer == Layer::Air || layer == Layer::Top) && !pProj->AA) - return Illegal; + switch (layer) + { + case Layer::Air: + case Layer::Top: + if (!pWeapon->Projectile->AA) + return Illegal; + break; + case Layer::Underground: + if (!BulletTypeExt::ExtMap.Find(pWeapon->Projectile)->AU) + return Illegal; + break; + default: + break; + } return GoOtherChecks; } diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index d28aeef581..fb5669c234 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1309,7 +1309,7 @@ DEFINE_HOOK(0x70023B, TechnoClass_MouseOverObject_AttackUnderGround, 0x5) GET(ObjectClass*, pObject, EDI); GET(TechnoClass*, pThis, ESI); - GET(int, wpIdx, EAX); + GET(const int, wpIdx, EAX); if (pObject->IsSurfaced()) return FireIsOK; @@ -1326,7 +1326,7 @@ DEFINE_HOOK(0x728F9A, TunnelLocomotionClass_Process_Track, 0x7) GET(ILocomotion*, pThis, ESI); const auto pLoco = static_cast(pThis); - auto pTechno = pLoco->LinkedTo; + const auto pTechno = pLoco->LinkedTo; ScenarioExt::Global()->UndergroundTracker.AddUnique(pTechno); TechnoExt::ExtMap.Find(pTechno)->UndergroundTracked = true; @@ -1346,7 +1346,7 @@ DEFINE_HOOK(0x7297F6, TunnelLocomotionClass_ProcessDigging_Track, 0x7) DEFINE_HOOK(0x772AB3, WeaponTypeClass_AllowedThreats_AU, 0x5) { GET(BulletTypeClass* const, pType, ECX); - GET(ThreatType, flags, EAX); + GET(const ThreatType, flags, EAX); if (BulletTypeExt::ExtMap.Find(pType)->AU) R->EAX(static_cast(flags) | 0x20000u); @@ -1361,7 +1361,7 @@ namespace SelectAutoTarget_Context DEFINE_HOOK(0x6F8DF0, TechnoClass_SelectAutoTarget_Start_AU, 0x9) { - GET_STACK(unsigned int, flags, 0x4); + GET_STACK(const unsigned int, flags, 0x4); SelectAutoTarget_Context::AU = (flags & 0x20000u) != 0; return 0; } @@ -1453,7 +1453,7 @@ DEFINE_HOOK(0x6F7E1E, TechnoClass_CanAutoTargetObject_AU, 0x6) enum { Continue = 0x6F7E24, ReturnFalse = 0x6F894F }; GET(TechnoClass*, pTarget, ESI); - GET(int, height, EAX); + GET(const int, height, EAX); return height >= -20 || SelectAutoTarget_Context::AU || TechnoExt::ExtMap.Find(pTarget)->SpecialTracked ? Continue : ReturnFalse; } From 6445779d4a08d4f8a7bd2b15547307eee70990ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:56:45 +0800 Subject: [PATCH 11/12] update --- src/Ext/Techno/Body.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6a856b454d..195df0c2af 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -57,6 +57,12 @@ TechnoExt::ExtData::~ExtData() } this->ElectricBolts.clear(); + + if (this->UndergroundTracked) + ScenarioExt::Global()->UndergroundTracker.Remove(pThis); + + if (this->SpecialTracked) + ScenarioExt::Global()->SpecialTracker.Remove(pThis); } bool TechnoExt::IsActiveIgnoreEMP(TechnoClass* pThis) @@ -989,12 +995,6 @@ DEFINE_HOOK(0x6F4500, TechnoClass_DTOR, 0x5) { GET(TechnoClass*, pItem, ECX); - if (TechnoExt::ExtMap.Find(pItem)->UndergroundTracked) - ScenarioExt::Global()->UndergroundTracker.Remove(pItem); - - if (TechnoExt::ExtMap.Find(pItem)->SpecialTracked) - ScenarioExt::Global()->SpecialTracker.Remove(pItem); - TechnoExt::ExtMap.Remove(pItem); return 0; From 3e237de9e88fd2ac7c48a24604f30945352eab27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 6 Oct 2025 19:00:17 +0800 Subject: [PATCH 12/12] Update New-or-Enhanced-Logics.md --- docs/New-or-Enhanced-Logics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 7c3dcb51a8..b0ea4ed27a 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -759,7 +759,7 @@ AU=false ; boolean ``` ```{note} -Only vanilla projectiles with `Inviso=yes` set or [Phobos projectiles](#projectile-trajectories) with `SubjectToGround=yes` enabled can go beneath the ground. Otherwise, the projectile will be forced to detonate upon hitting the ground. +Only vanilla projectiles with `Inviso=yes` set or [Phobos projectiles](#projectile-trajectories) `Straight` with `Trajectory.Straight.SubjectToGround=false` enabled and `Bombard` with `Trajectory.Bombard.SubjectToGround=false` enabled can go beneath the ground. Otherwise, the projectile will be forced to detonate upon hitting the ground. ``` ### Parabombs