From 1dc5f5d1297aa254ad17358b67a74dd8c55d71a5 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, 27 Oct 2025 23:29:50 +0800 Subject: [PATCH 1/6] code and docs --- CREDITS.md | 1 + YRpp | 2 +- docs/Fixed-or-Improved-Logics.md | 1 + docs/Whats-New.md | 1 + src/Misc/Hooks.BugFixes.cpp | 45 ++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 16cb1d5396..dd3a59eb2c 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -632,6 +632,7 @@ This page lists all the individual contributions to the project by their author. - Fix an issue that the AI would enter a combat state when its building receiving damage from friendly units or damage not greater than 0 - Fix an issue that the techno with weapon with `AA=yes` and `AG=no` would not auto targeting units that are falling, such as paratroopers - Dehardcode the `ZAdjust` of warhead anim + - Fix an issue where some effects pointing to a unit were not properly cleared when the unit changed its owner - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/YRpp b/YRpp index 5af96790ce..f01dce96b5 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 5af96790ce73e4ea068a390c60c124dccbc220e1 +Subproject commit f01dce96b59508d0aa78f877c28c1b8e29f3857c diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index da7d9b09bb..dca8a7deec 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -269,6 +269,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed an issue that the techno with weapon with `AA=yes` and `AG=no` would not auto targeting units that are falling, such as paratroopers. - Iron Curtain/Custom Tint Support for SHP Turreted Vehicles. - Reactivate unused trigger events 2, 53, and 54. +- Fixed an issue where some effects pointing to a unit were not properly cleared when the unit changed its owner. ## Fixes / interactions with other extensions diff --git a/docs/Whats-New.md b/docs/Whats-New.md index b36ab3b5fc..47adf628da 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -457,6 +457,7 @@ New: - Fast access structure (by FlyStar) - Toggle off laser trail and shake effects (by Ollerus) - [Dehardcode the `ZAdjust` of warhead anim](Fixed-or-Improved-Logics.md#dehardcode-the-zadjust-of-warhead-anim) (by TaranDahl) +- Fixed an issue where some effects pointing to a unit were not properly cleared when the unit changed its owner (by TaranDahl) 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/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 44a113c447..9c22177f89 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -2753,3 +2753,48 @@ DEFINE_HOOK(0x44242A, BuildingClass_ReceiveDamage_SetLATime, 0x8) } #pragma endregion + +#pragma region ClearTargetOnOwnerChanged + +DEFINE_HOOK(0x701681, TechnoClass_SetOwningHouse_ClearManagerTarget, 0x6) +{ + GET(TechnoClass*, pThis, ESI); + + for (const auto pTemporal : TemporalClass::Array) + { + if (pTemporal->Target == pThis) + pTemporal->LetGo(); + } + + for (const auto pAirstrike : AirstrikeClass::Array) + { + if (pAirstrike->Target == pThis) + pAirstrike->ResetTarget(); + } + + for (const auto pSpawn : SpawnManagerClass::Array) + { + if (pSpawn->Target == pThis) + pSpawn->ResetTarget(); + } + + pThis->LastTarget = nullptr; + if (auto pFoot = abstract_cast(pThis)) + pFoot->LastDestination = nullptr; + + return 0; +} + +DEFINE_HOOK(0x70D4FD, ObjectClass_ClearTargetToMe_ClearLastTarget, 0x6) +{ + GET(TechnoClass*, pTechno, ESI); + GET(bool, shouldClear, ECX); + GET(ObjectClass*, pThis, EBP); + + if (pTechno->LastTarget == pThis && shouldClear) + { + pTechno->LastTarget = nullptr; + } +} + +#pragma endregion From fc2d2bd390f603918d44dafd8d0c1c34f3c605ae 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, 30 Oct 2025 23:48:24 +0800 Subject: [PATCH 2/6] Update YRpp --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index f01dce96b5..f247013ddc 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit f01dce96b59508d0aa78f877c28c1b8e29f3857c +Subproject commit f247013ddca7616b6f4cb56b70eb7db9625eb45b From be5db0b2835518aa54175b81c09e0a535eae108a 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: Fri, 31 Oct 2025 00:02:59 +0800 Subject: [PATCH 3/6] fix --- YRpp | 2 +- src/Misc/Hooks.BugFixes.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/YRpp b/YRpp index f247013ddc..9b1ca519a0 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit f247013ddca7616b6f4cb56b70eb7db9625eb45b +Subproject commit 9b1ca519a07a37f0e3821d51eb9ef89ef90cc3fe diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 9c22177f89..782d4cbf3f 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -2795,6 +2795,8 @@ DEFINE_HOOK(0x70D4FD, ObjectClass_ClearTargetToMe_ClearLastTarget, 0x6) { pTechno->LastTarget = nullptr; } + + return 0; } #pragma endregion From 8ebe953ef4614cf228b3f82d8dd09cd67058f880 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: Tue, 4 Nov 2025 01:18:28 +0800 Subject: [PATCH 4/6] update --- YRpp | 2 +- src/Misc/Hooks.BugFixes.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/YRpp b/YRpp index 9b1ca519a0..5af96790ce 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 9b1ca519a07a37f0e3821d51eb9ef89ef90cc3fe +Subproject commit 5af96790ce73e4ea068a390c60c124dccbc220e1 diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index bb328ddea1..0072a7956d 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -2826,11 +2826,13 @@ DEFINE_HOOK(0x701681, TechnoClass_SetOwningHouse_ClearManagerTarget, 0x6) pTemporal->LetGo(); } - for (const auto pAirstrike : AirstrikeClass::Array) - { - if (pAirstrike->Target == pThis) - pAirstrike->ResetTarget(); - } + // WW don't clear target if the techno has airstrike manager. + // No idea why, but for now we respect it and don't handle the airstrike target. + //for (const auto pAirstrike : AirstrikeClass::Array) + //{ + // if (pAirstrike->Target == pThis) + // pAirstrike->ClearTarget(); + //} for (const auto pSpawn : SpawnManagerClass::Array) { @@ -2859,4 +2861,4 @@ DEFINE_HOOK(0x70D4FD, ObjectClass_ClearTargetToMe_ClearLastTarget, 0x6) return 0; } -#pragma endregion \ No newline at end of file +#pragma endregion From d7d2654bbfb562b321e088281ba443632768e9a5 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: Fri, 7 Nov 2025 22:03:12 +0800 Subject: [PATCH 5/6] Update Hooks.BugFixes.cpp --- src/Misc/Hooks.BugFixes.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 0072a7956d..d82fbf3d7b 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -2816,9 +2816,9 @@ DEFINE_HOOK(0x54CC9C, JumpjetLocomotionClass_ProcessCrashing_DropFix, 0x5) #pragma region ClearTargetOnOwnerChanged -DEFINE_HOOK(0x701681, TechnoClass_SetOwningHouse_ClearManagerTarget, 0x6) +DEFINE_HOOK(0x70D4A0, AbstractClass_ClearTargetToMe_ClearManagerTarget, 0x5) { - GET(TechnoClass*, pThis, ESI); + GET(AbstractClass*, pThis, ECX); for (const auto pTemporal : TemporalClass::Array) { @@ -2840,23 +2840,23 @@ DEFINE_HOOK(0x701681, TechnoClass_SetOwningHouse_ClearManagerTarget, 0x6) pSpawn->ResetTarget(); } - pThis->LastTarget = nullptr; + if (auto pTechno = abstract_cast(pThis)) + pTechno->LastTarget = nullptr; + if (auto pFoot = abstract_cast(pThis)) pFoot->LastDestination = nullptr; return 0; } -DEFINE_HOOK(0x70D4FD, ObjectClass_ClearTargetToMe_ClearLastTarget, 0x6) +DEFINE_HOOK(0x70D4FD, AbstractClass_ClearTargetToMe_ClearLastTarget, 0x6) { GET(TechnoClass*, pTechno, ESI); GET(bool, shouldClear, ECX); - GET(ObjectClass*, pThis, EBP); + GET(AbstractClass*, pThis, EBP); if (pTechno->LastTarget == pThis && shouldClear) - { pTechno->LastTarget = nullptr; - } return 0; } From 2d6123362abf484fc4c0ccf5d2cd0efa25711678 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Sat, 8 Nov 2025 22:13:36 +0800 Subject: [PATCH 6/6] Merge branch 'develop' into ClearTarget --- CREDITS.md | 1 + Phobos.vcxproj | 2 +- docs/Fixed-or-Improved-Logics.md | 3 +- docs/User-Interface.md | 2 +- docs/Whats-New.md | 6 +- .../zh_CN/LC_MESSAGES/User-Interface.po | 4 +- src/Ext/Bullet/Body.cpp | 24 +--- src/Ext/Rules/Body.cpp | 8 +- src/Ext/Rules/Body.h | 4 + src/Ext/Scenario/Body.cpp | 1 - src/Ext/Scenario/Body.h | 2 - src/Ext/Techno/Body.Update.cpp | 93 ++++++++++++--- src/Ext/Techno/Body.Visuals.cpp | 8 +- src/Ext/Techno/Body.cpp | 4 +- src/Ext/Techno/Body.h | 7 +- src/Ext/Techno/Hooks.Misc.cpp | 72 +++++++++++- src/Ext/Techno/Hooks.WeaponEffects.cpp | 26 +++-- src/Ext/Techno/Hooks.WeaponRange.cpp | 11 +- src/Ext/Techno/Hooks.cpp | 40 +------ src/Ext/Unit/Hooks.DeployFire.cpp | 18 +-- src/Ext/Unit/Hooks.Harvester.cpp | 107 +++++++++++++++++- src/Misc/Hooks.BugFixes.cpp | 39 ++++--- src/New/Entity/ShieldClass.cpp | 72 +++++------- src/New/Entity/ShieldClass.h | 6 +- src/Phobos.INI.cpp | 4 +- 25 files changed, 360 insertions(+), 204 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index e087374504..35aebe7fc2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -280,6 +280,7 @@ This page lists all the individual contributions to the project by their author. - Changes / fixes to `Vertical` projectile logic and customizing projectile initial facing behavior - Bugfixes to map trigger action `125 Build At...` - Owner change during buildup bugfix + - Subterranean harvester pathfinding fix - **Morton (MortonPL)**: - `XDrawOffset` for animations - Shield passthrough & absorption diff --git a/Phobos.vcxproj b/Phobos.vcxproj index cd41a8a686..b9b624c99d 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -40,7 +40,6 @@ - @@ -91,6 +90,7 @@ + diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 79b4f486af..1b852578f4 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -86,7 +86,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed railgun particles being drawn to wrong coordinate against buildings with non-default `TargetCoordOffset` or when force-firing on bridges. - Fixed building `TargetCoordOffset` not being taken into accord for several things like fire angle calculations and target lines. - In singleplayer missions, the player can now see cloaked objects owned by allied houses. -- IvanBomb images now display and the bombs detonate at center of buildings instead of in top-leftmost cell of the building foundation. +- IvanBomb images can now display and the bombs detonate at center of buildings instead of in top-leftmost cell of the building foundation if `[CombatDamage]` -> `IvanBombAttachToCenter` is set to true. - Fixed BibShape drawing for a couple of frames during buildup for buildings with long buildup animations. - Animation with `Tiled=yes` now supports `CustomPalette`. - Attempted to avoid units from retaining previous orders (attack,grind,garrison,etc) after changing ownership (mind-control,abduction,etc). @@ -270,6 +270,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Iron Curtain/Custom Tint Support for SHP Turreted Vehicles. - Reactivate unused trigger events 2, 53, and 54. - Fixed the bug that vehicle fall on infantry will make all cell content has been removed. +- Fixed `MovementZone=Subterannean` harvesters being unable to find docks if in area enclosed by water, cliffs etc. - Fixed an issue where some effects pointing to a unit were not properly cleared when the unit changed its owner. ## Fixes / interactions with other extensions diff --git a/docs/User-Interface.md b/docs/User-Interface.md index a7056c4d12..a3b4260f3d 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -408,7 +408,7 @@ In `RA2MD.INI`: [Phobos] MessageApplyHoverState=false ; boolean MessageDisplayInCenter=false ; boolean -MessageDisplayInCenter.BoardOpacity=30 ; integer +MessageDisplayInCenter.BoardOpacity=40 ; integer MessageDisplayInCenter.LabelsCount=6 ; integer MessageDisplayInCenter.RecordsCount=12 ; integer ``` diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c83f50e69f..a504ab9a97 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -30,6 +30,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] #### From post-0.3 devbuilds +- Ivan bombs no longer automatically center on building when attached. Set `[CombatDamage]` -> `IvanBombAttachToCenter` to true to restore this behaviour. Due to technical constraints this cannot be customized per WeaponType. - `AlternateFLH` no longer affects vehicle passengers by default. To re-enable it, set `AlternateFLH.ApplyVehicle=true` on the transport unit. - Parsing priority of `ShowBriefing` and `BriefingTheme` between map file and `missionmd.ini` has been switched (from latter taking priority over former to vice-versa) due to technical limitations and compatibility issues with spawner DLL. - Game will now produce fatal error with an error message if any of the files listed in `[$Include]` in any INI file do not exist. @@ -451,7 +452,7 @@ New: - Randomized anims for several behaviors (by Ollerus) - Shield respawn animation and weapon (by Ollerus) - [Customize the chained damage of the wall](Fixed-or-Improved-Logics.md#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) +- Allow the aircraft to enter area guard mission and not crash immediately without any airport. And now when `ExtendedAircraftMissions` is enabled, aircraft that can land at the airport will check at any time to see if they have a dock. Therefore, if there are aircraft in your mission that require dock and you have not provided enough or not disabled the feature, they will crash immediately (by CrimRecya) - [Unlimbo Detonate warhead](New-or-Enhanced-Logics.md#unlimbo-detonate-warhead) (by FlyStar) - [Attack](New-or-Enhanced-Logics.md#attack-technos-underground) and [damage](New-or-Enhanced-Logics.md#damage-technos-underground) technos underground (by TaranDahl) - Fast access structure (by FlyStar) @@ -490,6 +491,7 @@ Vanilla fixes: - Reactivate unused trigger events 2, 53, and 54 (by FlyStar) - Fixed the bug that vehicle fall on infantry will make all cell content has been removed (by NetsuNegi) - Fixed buildings that have their owner changed during buildup skipping buildup and sometimes not correctly clearing the state (by Starkku) +- Fixed `MovementZone=Subterannean` harvesters being unable to find docks if in area enclosed by water, cliffs etc. Phobos fixes: - Fixed the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it (by NetsuNegi) @@ -529,7 +531,7 @@ New: - Super Weapons launching other Super Weapons (by Morton) - Launching Super Weapons on building infiltration (by Morton) - Building airstrike target eligibility customization (by Starkku) -- IvanBomb detonation & image display centered on buildings (by Starkku) +- IvanBomb detonation & image display optionally centered on buildings (by Starkku) - Forcing specific weapon against cloaked or disguised targets (by Starkku) - Customizable ROF random delay (by Starkku) - Animation with `Tiled=yes` now supports `CustomPalette` (by ststl) diff --git a/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po b/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po index 753ef7db17..c672dbc514 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po +++ b/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po @@ -1307,14 +1307,14 @@ msgid "" "[Phobos]\n" "MessageApplyHoverState=false ; boolean\n" "MessageDisplayInCenter=false ; boolean\n" -"MessageDisplayInCenter.BoardOpacity=30 ; integer\n" +"MessageDisplayInCenter.BoardOpacity=40 ; integer\n" "MessageDisplayInCenter.LabelsCount=6 ; integer\n" "MessageDisplayInCenter.RecordsCount=12 ; integer\n" msgstr "" "[Phobos]\n" "MessageApplyHoverState=false ; boolean\n" "MessageDisplayInCenter=false ; boolean\n" -"MessageDisplayInCenter.BoardOpacity=30 ; integer\n" +"MessageDisplayInCenter.BoardOpacity=40 ; integer\n" "MessageDisplayInCenter.LabelsCount=6 ; integer\n" "MessageDisplayInCenter.RecordsCount=12 ; integer\n" diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index 9b84adc0f8..00263f52fe 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -395,29 +395,12 @@ void BulletExt::ApplyArcingFix(BulletClass* pThis, const CoordStruct& sourceCoor } } -// Detonate weapon/warhead using master bullet instance. +// Detonate weapon/warhead using a bullet. void BulletExt::Detonate(const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse, AbstractClass* pTarget, bool isBright, WeaponTypeClass* pWeapon, WarheadTypeClass* pWarhead) { - auto pBullet = ScenarioExt::Global()->MasterDetonationBullet; auto const pType = pWeapon ? pWeapon->Projectile : BulletTypeExt::GetDefaultBulletType(); - - // Oct 24, 2025 - Starkku: If the warhead is supposed to detonate on all map objects we actually need to create new BulletClass instance. - // Otherwise the master bullet instance can have its properties overwritten prematurely and cause weird issues. - if (WarheadTypeExt::ExtMap.Find(pWarhead)->DetonateOnAllMapObjects) - { - pBullet = pType->CreateBullet(pTarget, pOwner, damage, pWarhead, 100, isBright); - pBullet->WeaponType = pWeapon; - } - else - { - pBullet->Type = pType; - pBullet->WeaponType = pWeapon; - pBullet->Owner = pOwner; - pBullet->Health = damage; - pBullet->Target = pTarget; - pBullet->WH = pWarhead; - pBullet->Bright = isBright; - } + auto const pBullet = pType->CreateBullet(pTarget, pOwner, damage, pWarhead, 100, isBright); + pBullet->WeaponType = pWeapon; auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); pBulletExt->IsInstantDetonation = true; @@ -429,7 +412,6 @@ void BulletExt::Detonate(const CoordStruct& coords, TechnoClass* pOwner, int dam pBullet->Explode(true); } - // ============================= // load / save diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 98c1ee65cb..e1673b2efe 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -1,4 +1,4 @@ -#include "Body.h" +#include "Body.h" #include #include #include @@ -327,6 +327,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->WarheadAnimZAdjust.Read(exINI, GameStrings::AudioVisual, "WarheadAnimZAdjust"); + this->IvanBombAttachToCenter.Read(exINI, GameStrings::CombatDamage, "IvanBombAttachToCenter"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -389,9 +391,6 @@ void RulesExt::ExtData::InitializeAfterAllLoaded() this->TintColorIronCurtain = GeneralUtils::GetColorFromColorAdd(pRules->IronCurtainColor); this->TintColorForceShield = GeneralUtils::GetColorFromColorAdd(pRules->ForceShieldColor); this->TintColorBerserk = GeneralUtils::GetColorFromColorAdd(pRules->BerserkColor); - - // Init master bullet - ScenarioExt::Global()->MasterDetonationBullet = BulletTypeExt::GetDefaultBulletType()->CreateBullet(nullptr, nullptr, 0, nullptr, 0, false); } // ============================= @@ -600,6 +599,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->InfantryAutoDeploy) .Process(this->AdjacentWallDamage) .Process(this->WarheadAnimZAdjust) + .Process(this->IvanBombAttachToCenter) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 60e4682e51..07fe5f2036 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -275,6 +275,8 @@ class RulesExt Valueable AdjacentWallDamage; Valueable WarheadAnimZAdjust; + + Valueable IvanBombAttachToCenter; ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } @@ -490,6 +492,8 @@ class RulesExt , AdjacentWallDamage { 200 } , WarheadAnimZAdjust { -15 } + + , IvanBombAttachToCenter { false } { } virtual ~ExtData() = default; diff --git a/src/Ext/Scenario/Body.cpp b/src/Ext/Scenario/Body.cpp index 2a7b6570ea..a2135dfa35 100644 --- a/src/Ext/Scenario/Body.cpp +++ b/src/Ext/Scenario/Body.cpp @@ -167,7 +167,6 @@ void ScenarioExt::ExtData::Serialize(T& Stm) .Process(this->DefaultLS640BkgdName) .Process(this->DefaultLS800BkgdName) .Process(this->DefaultLS800BkgdPal) - .Process(this->MasterDetonationBullet) .Process(this->LimboLaunchers) .Process(this->UndergroundTracker) .Process(this->SpecialTracker) diff --git a/src/Ext/Scenario/Body.h b/src/Ext/Scenario/Body.h index 71f57c83a0..9bdc3dbae5 100644 --- a/src/Ext/Scenario/Body.h +++ b/src/Ext/Scenario/Body.h @@ -46,7 +46,6 @@ class ScenarioExt PhobosFixedString<64u> DefaultLS800BkgdName; PhobosFixedString<64u> DefaultLS800BkgdPal; - BulletClass* MasterDetonationBullet; // Used to do warhead/weapon detonations on spot without having to create new BulletClass instance every time. std::vector LimboLaunchers; DynamicVectorClass UndergroundTracker; // Technos that are underground. @@ -66,7 +65,6 @@ class ScenarioExt , DefaultLS640BkgdName {} , DefaultLS800BkgdName {} , DefaultLS800BkgdPal {} - , MasterDetonationBullet {} , LimboLaunchers {} , UndergroundTracker {} , SpecialTracker {} diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 17ca8b3e09..536165a906 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -49,13 +49,13 @@ void TechnoExt::ExtData::OnEarlyUpdate() void TechnoExt::ExtData::ApplyInterceptor() { - const auto pThis = this->OwnerObject(); const auto pTypeExt = this->TypeExtData; const auto pInterceptorType = pTypeExt->InterceptorType.get(); if (!pInterceptorType) return; + const auto pThis = this->OwnerObject(); const auto pTarget = pThis->Target; if (pTarget) @@ -177,13 +177,13 @@ void TechnoExt::ExtData::ApplyInterceptor() void TechnoExt::ExtData::DepletedAmmoActions() { - auto const pThis = this->OwnerObject(); auto const pTypeExt = this->TypeExtData; auto const pType = pTypeExt->OwnerObject(); if (pType->Ammo <= 0) return; + auto const pThis = this->OwnerObject(); auto const rtti = pThis->WhatAmI(); UnitClass* pUnit = nullptr; @@ -206,8 +206,8 @@ void TechnoExt::ExtData::DepletedAmmoActions() return; int const ammo = pThis->Ammo; - bool canDeploy = TechnoExt::HasAmmoToDeploy(pThis) && (min < 0 || ammo >= min) && (max < 0 || ammo <= max); - bool isDeploying = pThis->CurrentMission == Mission::Unload || pThis->QueuedMission == Mission::Unload; + const bool canDeploy = TechnoExt::HasAmmoToDeploy(pThis) && (min < 0 || ammo >= min) && (max < 0 || ammo <= max); + const bool isDeploying = pThis->CurrentMission == Mission::Unload || pThis->QueuedMission == Mission::Unload; if (canDeploy && !isDeploying) { @@ -225,6 +225,63 @@ void TechnoExt::ExtData::DepletedAmmoActions() } } +// Subterranean harvester factory exit state machine. +void TechnoExt::ExtData::UpdateSubterraneanHarvester() +{ + auto const pThis = static_cast(this->OwnerObject()); + + // Unnecessary for AI players. + if (!pThis->Owner->IsControlledByHuman()) + return; + + switch (this->SubterraneanHarvStatus) + { + case 0: // No state to handle. + break; + case 1: // Unit has been created. + // If we're still in the factory do not advance. + if (pThis->HasAnyLink()) + break; + + pThis->ClearNavigationList(); + + // If we have rally point available, move to it and advance to next state, otherwise end here. + if (this->SubterraneanHarvRallyPoint) + { + pThis->SetDestination(this->SubterraneanHarvRallyPoint, false); + pThis->QueueMission(Mission::Move, true); + this->SubterraneanHarvRallyPoint = nullptr; + this->SubterraneanHarvStatus = 2; + break; + } + else + { + this->SubterraneanHarvStatus = 0; + } + + break; + case 2: // Out of factory and on move. + // If we're still moving don't start harvesting. + if (pThis->Destination || pThis->CurrentMission == Mission::Move) + break; + + // If harvester stops moving and becomes anything except idle, reset the state machine. + if (pThis->CurrentMission != Mission::Guard) + { + this->SubterraneanHarvStatus = 0; + break; + } + + // Go harvest ore. + pThis->ClearNavigationList(); + pThis->QueueMission(Mission::Harvest, true); + this->SubterraneanHarvStatus = 0; + break; + default: + break; + } +} + // TODO : Merge into new AttachEffects bool TechnoExt::ExtData::CheckDeathConditions(bool isInLimbo) { @@ -234,13 +291,12 @@ bool TechnoExt::ExtData::CheckDeathConditions(bool isInLimbo) return false; auto const pThis = this->OwnerObject(); - auto const pType = pThis->GetTechnoType(); // Self-destruction must be enabled const auto howToDie = pTypeExt->AutoDeath_Behavior.Get(); // Death if no ammo - if (pType->Ammo > 0 && pThis->Ammo <= 0 && pTypeExt->AutoDeath_OnAmmoDepletion) + if (pTypeExt->OwnerObject()->Ammo > 0 && pThis->Ammo <= 0 && pTypeExt->AutoDeath_OnAmmoDepletion) { TechnoExt::KillSelf(pThis, howToDie, pTypeExt->AutoDeath_VanishAnimation, isInLimbo); return true; @@ -311,10 +367,14 @@ bool TechnoExt::ExtData::CheckDeathConditions(bool isInLimbo) void TechnoExt::ExtData::EatPassengers() { - auto const pThis = this->OwnerObject(); auto const pTypeExt = this->TypeExtData; - if (!pTypeExt->PassengerDeletionType || !TechnoExt::IsActiveIgnoreEMP(pThis)) + if (!pTypeExt->PassengerDeletionType) + return; + + auto const pThis = this->OwnerObject(); + + if (!TechnoExt::IsActiveIgnoreEMP(pThis)) return; auto const pDelType = pTypeExt->PassengerDeletionType.get(); @@ -414,7 +474,7 @@ void TechnoExt::ExtData::EatPassengers() } // Handle gunner change. - auto const pTransportType = pThis->GetTechnoType(); + auto const pTransportType = pTypeExt->OwnerObject(); if (pTransportType->Gunner) { @@ -639,6 +699,13 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) this->UpdateSelfOwnedAttachEffects(); + if (auto const pShield = this->Shield.get()) + pShield->ConvertCheck(pCurrentType); + + // Recalculate and redraw + this->UpdateTintValues(); + pThis->MarkForRedraw(); + // Recreate Laser Trails if (const size_t trailCount = this->LaserTrails.size()) { @@ -1317,6 +1384,7 @@ void TechnoExt::ExtData::UpdateLaserTrails() void TechnoExt::ExtData::UpdateMindControlAnim() { auto const pThis = this->OwnerObject(); + if (pThis->IsMindControlled()) { if (pThis->MindControlRingAnim && !this->MindControlRingAnimType) @@ -1390,9 +1458,7 @@ void TechnoExt::ExtData::UpdateGattlingRateDownReset() this->ShouldUpdateGattlingValue = false; if (oldStage != 0) - { pThis->GattlingRateDown(0); - } } } } @@ -1865,7 +1931,6 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() auto const pTypeExt = this->TypeExtData; std::vector>::iterator it; std::vector> expireWeapons; - bool markForRedraw = false; bool altered = false; // Delete ones on old type and not on current. @@ -1894,7 +1959,6 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() } } - markForRedraw |= pType->HasTint(); it = this->AttachedEffects.erase(it); altered = true; } @@ -1917,9 +1981,6 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects() if (altered && !count) this->RecalculateStatMultipliers(); - - if (markForRedraw) - pThis->MarkForRedraw(); } // Updates CumulativeAnimations AE's on techno. diff --git a/src/Ext/Techno/Body.Visuals.cpp b/src/Ext/Techno/Body.Visuals.cpp index 4b932f8021..9279bcbe0d 100644 --- a/src/Ext/Techno/Body.Visuals.cpp +++ b/src/Ext/Techno/Body.Visuals.cpp @@ -439,7 +439,7 @@ void TechnoExt::ProcessDigitalDisplays(TechnoClass* pThis) case AbstractType::Building: { pDisplayTypes = &RulesExt::Global()->Buildings_DefaultDigitalDisplayTypes; - const auto pBuildingType = static_cast(pThis->GetTechnoType()); + const auto pBuildingType = static_cast(pTypeExt->OwnerObject()); const int height = pBuildingType->GetFoundationHeight(false); length = height * 7 + height / 2; break; @@ -478,7 +478,7 @@ void TechnoExt::ProcessDigitalDisplays(TechnoClass* pThis) int value = -1; int maxValue = 0; - GetValuesForDisplay(pThis, pDisplayType->InfoType, value, maxValue, pDisplayType->InfoIndex); + GetValuesForDisplay(pThis, pType, pDisplayType->InfoType, value, maxValue, pDisplayType->InfoIndex); if (value <= -1 || maxValue <= 0) continue; @@ -503,10 +503,8 @@ void TechnoExt::ProcessDigitalDisplays(TechnoClass* pThis) } } -void TechnoExt::GetValuesForDisplay(TechnoClass* pThis, DisplayInfoType infoType, int& value, int& maxValue, int infoIndex) +void TechnoExt::GetValuesForDisplay(TechnoClass* pThis, TechnoTypeClass* pType, DisplayInfoType infoType, int& value, int& maxValue, int infoIndex) { - const auto pType = pThis->GetTechnoType(); - switch (infoType) { case DisplayInfoType::Health: diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6e5f2f4097..e402e5ceea 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -338,7 +338,6 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) auto const pTypeExt = TechnoExt::ExtMap.Find(pThis); pTypeExt->UpdateTypeData(pToType); pTypeExt->UpdateTypeData_Foot(); - pTypeExt->UpdateTintValues(); return true; } @@ -437,7 +436,6 @@ bool TechnoExt::ConvertToType(FootClass* pThis, TechnoTypeClass* pToType) auto const pTypeExt = TechnoExt::ExtMap.Find(pThis); pTypeExt->UpdateTypeData(pToType); pTypeExt->UpdateTypeData_Foot(); - pTypeExt->UpdateTintValues(); return true; } @@ -888,6 +886,8 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->AE) .Process(this->PreviousType) .Process(this->AnimRefCount) + .Process(this->SubterraneanHarvStatus) + .Process(this->SubterraneanHarvRallyPoint) .Process(this->ReceiveDamage) .Process(this->LastKillWasTeamTarget) .Process(this->PassengerDeletionTimer) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index bf33cc1e92..0e27357691 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -32,6 +32,8 @@ class TechnoExt TechnoTypeClass* PreviousType; // Type change registered in TechnoClass::AI on current frame and used in FootClass::AI on same frame and reset after. std::vector ElectricBolts; // EBolts are not serialized so do not serialize this either. int AnimRefCount; // Used to keep track of how many times this techno is referenced in anims f.ex Invoker, ParentBuilding etc., for pointer invalidation. + int SubterraneanHarvStatus; // 0 = none, 1 = created, 2 = out from factory + AbstractClass* SubterraneanHarvRallyPoint; bool ReceiveDamage; bool LastKillWasTeamTarget; CDTimerClass PassengerDeletionTimer; @@ -110,6 +112,8 @@ class TechnoExt , PreviousType { nullptr } , ElectricBolts {} , AnimRefCount { 0 } + , SubterraneanHarvStatus { 0 } + , SubterraneanHarvRallyPoint { nullptr } , ReceiveDamage { false } , LastKillWasTeamTarget { false } , PassengerDeletionTimer {} @@ -172,6 +176,7 @@ class TechnoExt void ApplyInterceptor(); bool CheckDeathConditions(bool isInLimbo = false); void DepletedAmmoActions(); + void UpdateSubterraneanHarvester(); void EatPassengers(); void UpdateTiberiumEater(); void UpdateShield(); @@ -277,7 +282,7 @@ class TechnoExt static Point2D GetBuildingSelectBracketPosition(TechnoClass* pThis, BuildingSelectBracketPosition bracketPosition); static void DrawSelectBox(TechnoClass* pThis, const Point2D* pLocation, const RectangleStruct* pBounds, bool drawBefore = false); static void ProcessDigitalDisplays(TechnoClass* pThis); - static void GetValuesForDisplay(TechnoClass* pThis, DisplayInfoType infoType, int& value, int& maxValue, int infoIndex); + static void GetValuesForDisplay(TechnoClass* pThis, TechnoTypeClass* pType, DisplayInfoType infoType, int& value, int& maxValue, int infoIndex); static void GetDigitalDisplayFakeHealth(TechnoClass* pThis, int& value, int& maxValue); static void CreateDelayedFireAnim(TechnoClass* pThis, AnimTypeClass* pAnimType, int weaponIndex, bool attach, bool center, bool removeOnNoDelay, bool onTurret, CoordStruct firingCoords); static bool HandleDelayedFireWithPauseSequence(TechnoClass* pThis, WeaponTypeClass* pWeapon, int weaponIndex, int frame, int firingFrame); diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 30e0d9033a..06b02ff8e8 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -1,5 +1,6 @@ #include "Body.h" +#include #include #include #include @@ -846,13 +847,78 @@ DEFINE_HOOK(0x73D6E6, UnitClass_Unload_Subterranean, 0x6) GET(UnitClass*, pThis, ESI); - if (pThis->Type->Locomotor == LocomotionClass::CLSIDs::Tunnel) + if (auto const pLoco = locomotion_cast(pThis->Locomotor)) { - auto const pLoco = static_cast(pThis->Locomotor.GetInterfacePtr()); - if (pLoco->State != TunnelLocomotionClass::State::Idle) return ReturnFromFunction; } return 0; } + +#pragma region Events + +DEFINE_HOOK(0x4C7512, EventClass_Execute_StopCommand, 0x6) +{ + GET(TechnoClass* const, pThis, ESI); + + if (auto const pUnit = abstract_cast(pThis)) + { + // issue #112 Make FireOnce=yes work on other TechnoType + // Author: Starkku + if (pUnit->CurrentMission == Mission::Unload && pUnit->Type->DeployFire && !pUnit->Type->IsSimpleDeployer) + { + pUnit->SetTarget(nullptr); + pThis->QueueMission(Mission::Guard, true); + } + + // Explicit stop command should reset subterranean harvester state machine. + auto const pExt = TechnoExt::ExtMap.Find(pUnit); + pExt->SubterraneanHarvStatus = 0; + pExt->SubterraneanHarvRallyPoint = nullptr; + } + + return 0; +} + +DEFINE_HOOK(0x4C7462, EventClass_Execute_MegaMission_MoveCommand, 0x5) +{ + enum { SkipGameCode = 0x4C74C0 }; + + GET(TechnoClass*, pTechno, EDI); + + if (pTechno->WhatAmI() != AbstractType::Unit) + return 0; + + GET(EventClass*, pThis, ESI); + auto const mission = static_cast(pThis->MegaMission.Mission); + auto const pExt = TechnoExt::ExtMap.Find(pTechno); + + if (mission == Mission::Move) + { + // Explicitly reset subterranean harvester state machine. + pExt->SubterraneanHarvStatus = 0; + pExt->SubterraneanHarvRallyPoint = nullptr; + + // Do not explicitly reset target for KeepTargetOnMove vehicles when issued move command. + if (pExt->TypeExtData->KeepTargetOnMove && pTechno->Target) + { + GET(AbstractClass*, pTarget, EBX); + + if (!pTarget && pTechno->IsCloseEnoughToAttack(pTechno->Target)) + { + auto const pDestination = pThis->MegaMission.Destination.As_Abstract(); + pTechno->SetDestination(pDestination, true); + pExt->KeepTargetOnMove = true; + + return SkipGameCode; + } + } + } + + pExt->KeepTargetOnMove = false; + + return 0; +} + +#pragma endregion diff --git a/src/Ext/Techno/Hooks.WeaponEffects.cpp b/src/Ext/Techno/Hooks.WeaponEffects.cpp index cbd091f65e..af549f0646 100644 --- a/src/Ext/Techno/Hooks.WeaponEffects.cpp +++ b/src/Ext/Techno/Hooks.WeaponEffects.cpp @@ -38,10 +38,8 @@ DEFINE_HOOK(0x6FF15F, TechnoClass_FireAt_ObstacleCellSet, 0x6) GET_BASE(AbstractClass*, pTarget, 0x8); LEA_STACK(CoordStruct*, pSourceCoords, STACK_OFFSET(0xB0, -0x6C)); - auto coords = pTarget->GetCenterCoords(); - - if (const auto pBuilding = abstract_cast(pTarget)) - coords = pBuilding->GetTargetCoords(); + const auto pBuilding = abstract_cast(pTarget); + const auto coords = pBuilding ? pBuilding->GetTargetCoords() : pTarget->GetCenterCoords(); // This is set to a temp variable as well, as accessing it everywhere needed from TechnoExt would be more complicated. FireAtTemp::pObstacleCell = TrajectoryHelper::FindFirstObstacle(*pSourceCoords, coords, pWeapon->Projectile, pThis->Owner); @@ -117,12 +115,14 @@ DEFINE_HOOK(0x70C6B5, TechnoClass_Railgun_TargetCoords, 0x5) { GET(AbstractClass*, pTarget, EBX); - auto coords = pTarget->GetCenterCoords(); + CoordStruct coords; if (const auto pBuilding = abstract_cast(pTarget)) coords = pBuilding->GetTargetCoords(); else if (const auto pCell = abstract_cast(pTarget)) coords = pCell->GetCoordsWithBridge(); + else + coords = pTarget->GetCenterCoords(); R->EAX(&coords); return 0; @@ -318,15 +318,17 @@ DEFINE_HOOK(0x762AFF, WaveClass_AI_TargetSet, 0x6) { GET(WaveClass*, pThis, ESI); - if (pThis->Target && pThis->Owner) + if (pThis->Target) { - auto const pOwner = pThis->Owner; - auto const pObstacleCell = TechnoExt::ExtMap.Find(pThis->Owner)->FiringObstacleCell; - - if (pObstacleCell == pThis->Target && pOwner->Target) + if (auto const pOwner = pThis->Owner) { - FireAtTemp::pWaveOwnerTarget = pOwner->Target; - pOwner->Target = pThis->Target; + auto const pObstacleCell = TechnoExt::ExtMap.Find(pOwner)->FiringObstacleCell; + + if (pObstacleCell == pThis->Target && pOwner->Target) + { + FireAtTemp::pWaveOwnerTarget = pOwner->Target; + pOwner->Target = pThis->Target; + } } } diff --git a/src/Ext/Techno/Hooks.WeaponRange.cpp b/src/Ext/Techno/Hooks.WeaponRange.cpp index 6d49587261..7d0c463d52 100644 --- a/src/Ext/Techno/Hooks.WeaponRange.cpp +++ b/src/Ext/Techno/Hooks.WeaponRange.cpp @@ -19,9 +19,8 @@ DEFINE_HOOK(0x7012C2, TechnoClass_WeaponRange, 0x8) { result = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pThis); auto const pType = pThis->GetTechnoType(); - auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); - if (pType->OpenTopped && !pTypeExt->OpenTopped_IgnoreRangefinding) + if (pType->OpenTopped && !TechnoTypeExt::ExtMap.Find(pType)->OpenTopped_IgnoreRangefinding) { int smallestRange = INT32_MAX; auto pPassenger = abstract_cast(pThis->Passengers.GetFirstPassenger()); @@ -29,12 +28,8 @@ DEFINE_HOOK(0x7012C2, TechnoClass_WeaponRange, 0x8) while (pPassenger) { const int openTWeaponIndex = pPassenger->GetTechnoType()->OpenTransportWeapon; - int tWeaponIndex = openTWeaponIndex; - - if (openTWeaponIndex == -1) - tWeaponIndex = pPassenger->SelectWeapon(pThis->Target); - - WeaponTypeClass* pTWeapon = pPassenger->GetWeapon(tWeaponIndex)->WeaponType; + const int tWeaponIndex = openTWeaponIndex == -1 ? pPassenger->SelectWeapon(pThis->Target) : openTWeaponIndex; + auto const pTWeapon = pPassenger->GetWeapon(tWeaponIndex)->WeaponType; if (pTWeapon && pTWeapon->FireInTransport) { diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index d495a14c98..7d3733ea8d 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1,7 +1,6 @@ #include "Body.h" #include -#include #include #include #include @@ -78,6 +77,7 @@ DEFINE_HOOK(0x736480, UnitClass_AI, 0x6) auto const pExt = TechnoExt::ExtMap.Find(pThis); pExt->UpdateKeepTargetOnMove(); pExt->DepletedAmmoActions(); + pExt->UpdateSubterraneanHarvester(); return 0; } @@ -227,12 +227,13 @@ DEFINE_HOOK(0x6F42F7, TechnoClass_Init, 0x2) auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); pExt->TypeExtData = pTypeExt; - pExt->CurrentShieldType = pTypeExt->ShieldType; + auto const pShieldType = pTypeExt->ShieldType; + pExt->CurrentShieldType = pShieldType; pExt->InitializeAttachEffects(); pExt->InitializeDisplayInfo(); pExt->InitializeLaserTrails(); - if (!pExt->AE.HasTint && !pExt->CurrentShieldType) + if (!pExt->AE.HasTint && (!pShieldType || !pShieldType->HasTint() || pShieldType->Strength <= 0)) pExt->UpdateTintValues(); if (pTypeExt->Harvester_Counted) @@ -716,39 +717,6 @@ DEFINE_HOOK(0x414665, AircraftClass_Draw_ExtraSHP, 0x6) return Continue; } -// Do not explicitly reset target for KeepTargetOnMove vehicles when issued move command. -DEFINE_HOOK(0x4C7462, EventClass_Execute_KeepTargetOnMove, 0x5) -{ - enum { SkipGameCode = 0x4C74C0 }; - - GET(TechnoClass*, pTechno, EDI); - - if (pTechno->WhatAmI() != AbstractType::Unit) - return 0; - - GET(EventClass*, pThis, ESI); - auto const mission = static_cast(pThis->MegaMission.Mission); - auto const pExt = TechnoExt::ExtMap.Find(pTechno); - - if (mission == Mission::Move && pExt->TypeExtData->KeepTargetOnMove && pTechno->Target) - { - GET(AbstractClass*, pTarget, EBX); - - if (!pTarget && pTechno->IsCloseEnoughToAttack(pTechno->Target)) - { - auto const pDestination = pThis->MegaMission.Destination.As_Abstract(); - pTechno->SetDestination(pDestination, true); - pExt->KeepTargetOnMove = true; - - return SkipGameCode; - } - } - - pExt->KeepTargetOnMove = false; - - return 0; -} - #pragma region BuildingTypeSelectable namespace BuildingTypeSelectable diff --git a/src/Ext/Unit/Hooks.DeployFire.cpp b/src/Ext/Unit/Hooks.DeployFire.cpp index 65275c8b59..f1933c8e74 100644 --- a/src/Ext/Unit/Hooks.DeployFire.cpp +++ b/src/Ext/Unit/Hooks.DeployFire.cpp @@ -3,23 +3,7 @@ #include #include -// issue #112 Make FireOnce=yes work on other TechnoTypes -// Author: Starkku -DEFINE_HOOK(0x4C7512, EventClass_Execute_StopUnitDeployFire, 0x6) -{ - GET(TechnoClass* const, pThis, ESI); - - auto const pUnit = abstract_cast(pThis); - if (pUnit && pUnit->CurrentMission == Mission::Unload && pUnit->Type->DeployFire && !pUnit->Type->IsSimpleDeployer) - { - pUnit->SetTarget(nullptr); - pThis->QueueMission(Mission::Guard, true); - } - - return 0; -} - -DEFINE_HOOK(0x4C77E4, EventClass_Execute_UnitDeployFire, 0x6) +DEFINE_HOOK(0x4C77E4, EventClass_Execute_DeployCommand, 0x6) { enum { DoNotExecute = 0x4C8109 }; diff --git a/src/Ext/Unit/Hooks.Harvester.cpp b/src/Ext/Unit/Hooks.Harvester.cpp index 5ef8da93e2..31dcd1ee75 100644 --- a/src/Ext/Unit/Hooks.Harvester.cpp +++ b/src/Ext/Unit/Hooks.Harvester.cpp @@ -1,8 +1,4 @@ -#include -#include - #include -#include #pragma region EnterRefineryFix @@ -131,3 +127,106 @@ DEFINE_HOOK(0x73E730, UnitClass_MissionHarvest_HarvesterScanAfterUnload, 0x5) } #pragma endregion + +// Hooks that allow harvesters / weeders to work correctly with MovementZone=Subterannean (sic) - Starkku +#pragma region SubterraneanHarvesters + +// Allow scanning for docks in all map zones. +DEFINE_HOOK(0x4DEFC6, FootClass_FindDock_SubterraneanHarvester, 0x5) +{ + GET(TechnoTypeClass*, pTechnoType, EAX); + + if (auto const pUnitType = abstract_cast(pTechnoType)) + { + if ((pUnitType->Harvester || pUnitType->Weeder) && pUnitType->MovementZone == MovementZone::Subterrannean) + R->ECX(MovementZone::Fly); + } + + return 0; +} + +// Allow scanning for ore in all map zones. +DEFINE_HOOK(0x4DCF86, FootClass_FindTiberium_SubterraneanHarvester, 0x5) +{ + enum { SkipGameCode = 0x4DCF9B }; + + GET(MovementZone, mZone, ECX); + + if (mZone == MovementZone::Subterrannean) + R->ECX(MovementZone::Fly); + + return 0; +} + +// Allow scanning for weeds in all map zones. +DEFINE_HOOK(0x4DDB23, FootClass_FindWeeds_SubterraneanHarvester, 0x5) +{ + enum { SkipGameCode = 0x4DCF9B }; + + GET(MovementZone, mZone, EAX); + + if (mZone == MovementZone::Subterrannean) + R->EAX(MovementZone::Fly); + + return 0; +} + +// Set flag on factory exit to mark the harvester as having just exited factory. +DEFINE_HOOK(0x44459A, BuildingClass_ExitObject_SubterraneanHarvester, 0x5) +{ + GET(TechnoClass*, pThis, EDI); + + if (auto const pUnit = abstract_cast(pThis)) + { + auto const pType = pUnit->Type; + + if ((pType->Harvester || pType->Weeder) && pType->MovementZone == MovementZone::Subterrannean) + { + auto const pExt = TechnoExt::ExtMap.Find(pUnit); + pExt->SubterraneanHarvStatus = 1; + pExt->SubterraneanHarvRallyPoint = pThis->ArchiveTarget; + pThis->ArchiveTarget = nullptr; + } + } + + return 0; +} + +// Apply same special rules on idle to player-owned subterranean harvesters as to Teleporter=yes ones. +DEFINE_HOOK(0x740949, UnitClass_Mission_Guard_SubterraneanHarvester, 0x6) +{ + enum { Continue = 0x740957 }; + + GET(UnitTypeClass*, pType, ECX); + + if (pType->MovementZone == MovementZone::Subterrannean) + return Continue; + + return 0; +} + +// Fix an edge case issue stemming from subterranean unit nav queue handling leading +// to harvesters becoming idle randomly while harvesting. +DEFINE_HOOK(0x738A3E, UnitClass_EnterIdleMode_SubterraneanHarvester, 0x5) +{ + enum { ReturnFromFunction = 0x738D21 }; + + GET(UnitClass*, pThis, ESI); + + if (auto const pUnit = abstract_cast(pThis)) + { + auto const pType = pUnit->Type; + + if ((pType->Harvester || pType->Weeder) && pType->MovementZone == MovementZone::Subterrannean) + { + auto const mission = pUnit->CurrentMission; + + if (mission == Mission::Unload || mission == Mission::Harvest) + return ReturnFromFunction; + } + } + + return 0; +} + +#pragma endregion diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index d82fbf3d7b..c0cfe5a51c 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -563,17 +563,19 @@ namespace FetchBomb BombClass* pThisBomb; } -// Fetch the BombClass context From earlier adress +// Fetch the BombClass context From earlier address. DEFINE_HOOK(0x438771, BombClass_Detonate_SetContext, 0x6) { GET(BombClass*, pThis, ESI); FetchBomb::pThisBomb = pThis; - // Also adjust detonation coordinate. - CoordStruct coords = pThis->Target->GetCenterCoords(); + if (RulesExt::Global()->IvanBombAttachToCenter) + { + CoordStruct coords = pThis->Target->GetCenterCoords(); + R->EDX(&coords); + } - R->EDX(&coords); return 0; } @@ -609,16 +611,6 @@ static DamageAreaResult __fastcall _BombClass_Detonate_DamageArea return nDamageAreaResult; } -DEFINE_HOOK(0x6F5201, TechnoClass_DrawExtras_IvanBombImage, 0x6) -{ - GET(TechnoClass*, pThis, EBP); - - auto coords = pThis->GetCenterCoords(); - - R->EAX(&coords); - return 0; -} - // skip the Explosion Anim block and clean up the context DEFINE_HOOK(0x4387A8, BombClass_Detonate_ExplosionAnimHandled, 0x5) { @@ -629,6 +621,19 @@ DEFINE_HOOK(0x4387A8, BombClass_Detonate_ExplosionAnimHandled, 0x5) // redirect MapClass::DamageArea call to our dll for additional functionality and checks DEFINE_FUNCTION_JUMP(CALL, 0x4387A3, _BombClass_Detonate_DamageArea); +DEFINE_HOOK(0x6F5201, TechnoClass_DrawExtras_IvanBombImage, 0x6) +{ + GET(TechnoClass*, pThis, EBP); + + if (RulesExt::Global()->IvanBombAttachToCenter) + { + auto coords = pThis->GetCenterCoords(); + R->EAX(&coords); + } + + return 0; +} + // Oct 20, 2022 - Starkku: BibShape checks for BuildingClass::BState which needs to not be 0 (constructing) for bib to draw. // It is possible for BState to be 1 early during construction for frame or two which can result in BibShape being drawn during buildup, which somehow depends on length of buildup. // Trying to fix this issue at its root is problematic and most of the time causes buildup to play twice, it is simpler to simply fix the BibShape to not draw until the buildup is done @@ -2840,10 +2845,10 @@ DEFINE_HOOK(0x70D4A0, AbstractClass_ClearTargetToMe_ClearManagerTarget, 0x5) pSpawn->ResetTarget(); } - if (auto pTechno = abstract_cast(pThis)) + if (const auto pTechno = abstract_cast(pThis)) pTechno->LastTarget = nullptr; - if (auto pFoot = abstract_cast(pThis)) + if (const auto pFoot = abstract_cast(pThis)) pFoot->LastDestination = nullptr; return 0; @@ -2852,7 +2857,7 @@ DEFINE_HOOK(0x70D4A0, AbstractClass_ClearTargetToMe_ClearManagerTarget, 0x5) DEFINE_HOOK(0x70D4FD, AbstractClass_ClearTargetToMe_ClearLastTarget, 0x6) { GET(TechnoClass*, pTechno, ESI); - GET(bool, shouldClear, ECX); + GET(const bool, shouldClear, ECX); GET(AbstractClass*, pThis, EBP); if (pTechno->LastTarget == pThis && shouldClear) diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 85cbb46a15..a091498370 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -37,8 +37,9 @@ ShieldClass::ShieldClass(TechnoClass* pTechno, bool isAttached) , Respawn_Rate_Warhead { -1 } , IsSelfHealingEnabled { true } { - this->UpdateType(); - this->SetHP(this->Type->InitialStrength.Get(this->Type->Strength)); + auto const pType = TechnoExt::ExtMap.Find(pTechno)->CurrentShieldType; + this->Type = pType; + this->SetHP(pType->InitialStrength.Get(pType->Strength)); this->TechnoID = pTechno->GetTechnoType(); ShieldClass::Array.emplace_back(this); } @@ -51,11 +52,6 @@ ShieldClass::~ShieldClass() ShieldClass::Array.erase(it); } -void ShieldClass::UpdateType() -{ - this->Type = TechnoExt::ExtMap.Find(this->Techno)->CurrentShieldType; -} - void ShieldClass::PointerGotInvalid(void* ptr, bool removed) { auto const abs = static_cast(ptr); @@ -142,10 +138,14 @@ void ShieldClass::SyncShieldToAnother(TechnoClass* pFrom, TechnoClass* pTo) pToExt->Shield->TechnoID = pFromExt->Shield->TechnoID; pToExt->Shield->Available = pFromExt->Shield->Available; pToExt->Shield->HP = pFromExt->Shield->HP; - } - if (pFrom->WhatAmI() == AbstractType::Building && pFromExt->Shield) - pFromExt->Shield = nullptr; + // handle shield conversion and tint + pToExt->Shield->ConvertCheck(pToExt->TypeExtData->OwnerObject()); + pToExt->Shield->UpdateTint(); + + if (pFrom->WhatAmI() == AbstractType::Building) + pFromExt->Shield = nullptr; + } } bool ShieldClass::ShieldIsBrokenTEvent(ObjectClass* pAttached) @@ -306,7 +306,7 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) { int result = damage; - if (result * GeneralUtils::GetWarheadVersusArmor(pWH, pTechno->GetTechnoType()->Armor) > 0) + if (result * GeneralUtils::GetWarheadVersusArmor(pWH, pTechnoType->Armor) > 0) result = 0; return result; @@ -431,26 +431,21 @@ void ShieldClass::AI() if (!pTechno || pTechno->InLimbo || pTechno->IsImmobilized || pTechno->Transporter) return; + auto const pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + if (pTechno->Health <= 0 || !pTechno->IsAlive || pTechno->IsSinking) { - TechnoExt::ExtMap.Find(pTechno)->Shield = nullptr; + pTechnoExt->Shield = nullptr; return; } - if (this->ConvertCheck()) - return; - - this->UpdateType(); + this->Type = pTechnoExt->CurrentShieldType; this->CloakCheck(); if (!this->Available) return; this->TemporalCheck(); - - if (this->Temporal) - return; - this->OnlineCheck(); this->EnabledByCheck(); @@ -467,7 +462,7 @@ void ShieldClass::AI() if (GeneralUtils::HasHealthRatioThresholdChanged(LastTechnoHealthRatio, ratio)) UpdateIdleAnim(); - if (!this->Temporal && this->Online && (this->HP > 0 && pTechno->Health > 0)) + if (this->Online && this->HP > 0) this->CreateAnim(); } @@ -604,16 +599,10 @@ void ShieldClass::TemporalCheck() } // Is used for DeploysInto/UndeploysInto and Type conversion -bool ShieldClass::ConvertCheck() +void ShieldClass::ConvertCheck(TechnoTypeClass* pTechnoType) { - const auto newID = this->Techno->GetTechnoType(); - - // If there has been no actual TechnoType conversion then we bail out early. - if (this->TechnoID == newID) - return false; - const auto pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); - const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(newID); + const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); const auto pOldType = this->Type; const bool allowTransfer = pOldType->AllowTransfer.Get(Attached); @@ -623,8 +612,7 @@ bool ShieldClass::ConvertCheck() this->KillAnim(); pTechnoExt->CurrentShieldType = nullptr; pTechnoExt->Shield = nullptr; - this->UpdateTint(); - return true; + return; } else if (!allowTransfer && pTechnoTypeExt->ShieldType && pTechnoTypeExt->ShieldType->Strength > 0) { @@ -635,9 +623,11 @@ bool ShieldClass::ConvertCheck() // Our new type is either the old shield or the changed type from the above two scenarios. const auto pNewType = pTechnoExt->CurrentShieldType; + const bool hasNewType = pNewType && pNewType->Strength > 0; + bool& available = this->Available; // Update shield properties if we still have a shield. - if (pNewType && pNewType->Strength > 0 && this->Available) + if (hasNewType && available) { const bool isDamaged = this->Techno->GetHealthPercentage() <= RulesClass::Instance->ConditionYellow; const double healthRatio = this->GetHealthRatio(); @@ -654,23 +644,21 @@ bool ShieldClass::ConvertCheck() else { const auto timer = (this->HP <= 0) ? &this->Timers.Respawn : &this->Timers.SelfHealing; - if (pNewType && pNewType->Strength > 0 && !this->Available) + + if (hasNewType && !available) { // Resume this shield when became Available timer->Resume(); - this->Available = true; + available = true; } - else if (this->Available) + else if (available) { // Pause this shield when became unAvailable timer->Pause(); - this->Available = false; + available = false; this->KillAnim(); } } - this->TechnoID = newID; - this->UpdateTint(true); // Force tint update on shield type conversion. - - return false; + this->TechnoID = pTechnoType; } void ShieldClass::SelfHealing() @@ -933,9 +921,9 @@ void ShieldClass::UpdateIdleAnim() } } -void ShieldClass::UpdateTint(bool forceUpdate) +void ShieldClass::UpdateTint() { - if (this->Type->HasTint() || forceUpdate) + if (this->Type->HasTint()) { auto const pTechno = this->Techno; TechnoExt::ExtMap.Find(pTechno)->UpdateTintValues(); diff --git a/src/New/Entity/ShieldClass.h b/src/New/Entity/ShieldClass.h index 3d7effe045..ae89daf0b5 100644 --- a/src/New/Entity/ShieldClass.h +++ b/src/New/Entity/ShieldClass.h @@ -64,7 +64,8 @@ class ShieldClass ArmorType GetArmorType(TechnoTypeClass* pTechnoType = nullptr) const; int GetFramesSinceLastBroken() const; void SetAnimationVisibility(bool visible); - void UpdateTint(bool forceUpdate = false); + void UpdateTint(); + void ConvertCheck(TechnoTypeClass* pTechnoType); static void SyncShieldToAnother(TechnoClass* pFrom, TechnoClass* pTo); static bool ShieldIsBrokenTEvent(ObjectClass* pAttached); @@ -82,8 +83,6 @@ class ShieldClass template bool Serialize(T& Stm); - void UpdateType(); - void SelfHealing(); int GetPercentageAmount(double iStatus); @@ -99,7 +98,6 @@ class ShieldClass void CloakCheck(); void OnlineCheck(); void TemporalCheck(); - bool ConvertCheck(); void EnabledByCheck(); int DrawShieldBar_Pip(const bool isBuilding) const; diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index ffded10550..270d8e9770 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -55,7 +55,7 @@ bool Phobos::Config::EnableSelectBox = false; bool Phobos::Config::DigitalDisplay_Enable = false; bool Phobos::Config::MessageApplyHoverState = false; bool Phobos::Config::MessageDisplayInCenter = false; -int Phobos::Config::MessageDisplayInCenter_BoardOpacity = 30; +int Phobos::Config::MessageDisplayInCenter_BoardOpacity = 40; int Phobos::Config::MessageDisplayInCenter_LabelsCount = 6; int Phobos::Config::MessageDisplayInCenter_RecordsCount = 12; bool Phobos::Config::RealTimeTimers = false; @@ -91,7 +91,7 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::Config::ShowPlacementPreview = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "ShowPlacementPreview", true); Phobos::Config::MessageApplyHoverState = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "MessageApplyHoverState", false); Phobos::Config::MessageDisplayInCenter = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "MessageDisplayInCenter", false); - Phobos::Config::MessageDisplayInCenter_BoardOpacity = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "MessageDisplayInCenter.BoardOpacity", 30); + Phobos::Config::MessageDisplayInCenter_BoardOpacity = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "MessageDisplayInCenter.BoardOpacity", 40); Phobos::Config::MessageDisplayInCenter_LabelsCount = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "MessageDisplayInCenter.LabelsCount", 6); Phobos::Config::MessageDisplayInCenter_RecordsCount = CCINIClass::INI_RA2MD.ReadInteger(phobosSection, "MessageDisplayInCenter.RecordsCount", 12); Phobos::Config::RealTimeTimers = CCINIClass::INI_RA2MD.ReadBool(phobosSection, "RealTimeTimers", false);