From d8f03c0982cf9d1cca2fd60277e12994f849d881 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:13:33 +0800 Subject: [PATCH 01/13] update DeployFire --- Phobos.vcxproj | 1 + src/Ext/Building/Hooks.Unload.cpp | 81 +++++++++++++++++++++++++++++++ src/Ext/Techno/Hooks.Misc.cpp | 10 +++- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/Ext/Building/Hooks.Unload.cpp diff --git a/Phobos.vcxproj b/Phobos.vcxproj index d45a6ac91f..a58ec8ab7f 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -18,6 +18,7 @@ + diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp new file mode 100644 index 0000000000..0739b66104 --- /dev/null +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -0,0 +1,81 @@ +#include "Body.h" + +DEFINE_HOOK(0x447358, BuildingClass_MouseOverObject_DeployFire, 0x6) +{ + GET(BuildingTypeClass* const, pType, EAX); + + return pType->DeployFire ? 0x4472EC : 0; +} + +DEFINE_HOOK(0x4434FF, BuildingClass_ObjectClickedAction_DeployFire, 0x6) +{ + GET(BuildingTypeClass* const, pType, EAX); + + return pType->DeployFire ? 0x443509 : 0; +} + +DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) +{ + GET(BuildingClass* const, pThis, EBP); + GET(BuildingTypeClass* const, pType, EAX); + enum { SkipGameCode = 0x44E37F, ReturnGuard = 0x44E371, Continue = 0x44E2BE }; + + if (!pType->GapGenerator || !pType->SuperGapRadiusInCells) + { + if (pType->DeployFire) + { + auto const pCell = pThis->GetCell(); + + if (pThis->Target != pCell) + pThis->SetTarget(pCell); + + const int deployFireWeapon = pType->DeployFireWeapon; + const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); + + if (pThis->GetFireError(pCell, weaponIndex, true) == FireError::OK && + pThis->Fire(pCell, weaponIndex)) + { + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + + if (pWeapon->FireOnce) + return ReturnGuard; + } + + R->EBX(ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); + return SkipGameCode; + } + + return ReturnGuard; + } + + return Continue; +} + +DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) +{ + for (const auto pObject : ObjectClass::CurrentObjects) + { + const AbstractFlags flags = pObject->AbstractFlags; + + if (!(flags & AbstractFlags::Techno) || (flags & AbstractFlags::Foot)) + continue; + + const auto pBuilding = static_cast(pObject); + const auto pHouse = pBuilding->Owner; + + if (!pHouse->IsControlledByCurrentPlayer() || !pBuilding->Type->DeployFire) + continue; + + const Mission currentMission = pBuilding->CurrentMission; + + if (currentMission == Mission::Construction || currentMission == Mission::Selling) + continue; + + if (pBuilding->EMPLockRemaining > 0 || !pBuilding->WasOnline || pBuilding->BunkerLinkedItem) + continue; + + pBuilding->ClickedMission(Mission::Unload, nullptr, nullptr, nullptr); + } + + return 0; +} diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 9bf0607d80..2a3461e911 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -881,12 +881,20 @@ DEFINE_HOOK(0x4C7512, EventClass_Execute_StopCommand, 0x6) 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; } + else if (auto const pBuilding = abstract_cast(pThis)) + { + if (pBuilding->CurrentMission == Mission::Unload && pBuilding->Type->DeployFire) + { + pBuilding->QueueMission(Mission::Guard, false); + pBuilding->NextMission(); + } + } return 0; } From 2d59ca82516f9bc8bdc43227f75e3b6f0b9dc9a4 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:24:30 +0800 Subject: [PATCH 02/13] update doc --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 5 +++++ docs/Whats-New.md | 1 + 3 files changed, 7 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index a5a21ce4b6..881ab63dd2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -412,6 +412,7 @@ This page lists all the individual contributions to the project by their author. - Fix an issue where miners affected by `Passengers/DeployFire` were unable to unload minerals - Fix an issue where mining vehicles could not move after leaving a tank bunker - Fixed the bug in AI scripts 56 and 57 that forced the launch of superweapons with index numbers 3 and 4 + - DeployFire supports buildings - **NetsuNegi**: - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 83f2fafb5d..950ff658e0 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -609,6 +609,11 @@ Adjacent.Disallowed.ProhibitDistance=0 ; integer, cell offset NoBuildAreaOnBuildup=false ; boolean ``` +### DeployFire supports + +- Building types now also support using `DeployFire` and `DeployFireWeapon`. + - If a building has other configurations such as `Factory`, `GapGenerator`, or `Passengers`, it will prioritize executing those deployment actions instead. + ### Destroyable pathfinding obstacles - It is possible to make buildings be considered pathfinding obstacles that can be destroyed by setting `IsDestroyableBlockage` to true. What this does is make the building be considered impassable and impenetrable pathfinding obstacle to every unit that is not flying or have appropriate `MovementZone` (ones that allow destroyable obstacles to be overcome, e.g `(Infantry|Amphibious)Destroyer`) akin to wall overlays and TerrainTypes. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index e5e0707e19..478af30b7b 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -541,6 +541,7 @@ New: - [Customizations for techno type target scan/guard range](Fixed-or-Improved-Logics.md#target-scan-guard-range-customizations) (by Starkku) - Spawns particle when spawns tiberium by terrain (by NetsuNegi) - Allow jumpjet climbing ignore building height (by TaranDahl) +- DeployFire supports buildings (By FlyStar) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) From 869ff47bffa082a25ee2cc724b3d2465779b2209 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:52:13 +0800 Subject: [PATCH 03/13] Update Hooks.Unload.cpp --- src/Ext/Building/Hooks.Unload.cpp | 52 ++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 0739b66104..00245cbff1 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -7,11 +7,26 @@ DEFINE_HOOK(0x447358, BuildingClass_MouseOverObject_DeployFire, 0x6) return pType->DeployFire ? 0x4472EC : 0; } -DEFINE_HOOK(0x4434FF, BuildingClass_ObjectClickedAction_DeployFire, 0x6) +DEFINE_HOOK(0x443459, BuildingClass_ObjectClickedAction_DeployFire, 0x6) { - GET(BuildingTypeClass* const, pType, EAX); + GET(BuildingClass*, pThis, EBX); + enum { SkipGameCode = 0x443568, SkipFactory = 0x4434F2 }; + + auto const pType = pThis->Type; + + // Perhaps Factory should not allow the use of DeployFire. + if (pType->Factory != AbstractType::None) + { + if (!pThis->IsPrimaryFactory) + pThis->ClickedEvent(EventType::Primary); + } + else if (pType->DeployFire) + { + pThis->ClickedMission(Mission::Unload, pThis, nullptr, nullptr); + return SkipGameCode; + } - return pType->DeployFire ? 0x443509 : 0; + return SkipFactory; } DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) @@ -20,7 +35,9 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) GET(BuildingTypeClass* const, pType, EAX); enum { SkipGameCode = 0x44E37F, ReturnGuard = 0x44E371, Continue = 0x44E2BE }; - if (!pType->GapGenerator || !pType->SuperGapRadiusInCells) + const int cells = static_cast(pType->SuperGapRadiusInCells); + + if (!pType->GapGenerator || !cells) { if (pType->DeployFire) { @@ -38,7 +55,13 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; if (pWeapon->FireOnce) - return ReturnGuard; + { + // When Turret=yes, the Unload task may not be canceled, so this handling is performed. + pThis->QueueMission(Mission::Guard, false); + pThis->NextMission(); + + return SkipGameCode; + } } R->EBX(ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); @@ -79,3 +102,22 @@ DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) return 0; } + +// I don't know why it doesn't allow OmniFire to function properly. +DEFINE_HOOK(0x447FED, BuildingClass_GetFireError_DeployFire, 0x7) +{ + enum { SkipGameCode = 0x448052 }; + + GET(BuildingClass* const, pThis, ESI); + GET_STACK(const int, weaponIndex, STACK_OFFSET(0xC, 0x8)); + + if (pThis->Type->DeployFire && pThis->CurrentMission == Mission::Unload) + { + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + + if (pWeapon->OmniFire) + return SkipGameCode; + } + + return 0; +} From 296d0b5e1bfab8d1794f52a5299c0f2f498a9a22 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:55:15 +0800 Subject: [PATCH 04/13] Support UndeployDelay --- src/Ext/Building/Hooks.Unload.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 00245cbff1..1b504943e8 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -64,7 +64,10 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } } - R->EBX(ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); + const int undeployDelay = pType->UndeployDelay; + const int result = undeployDelay < 0 ? (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14) : std::max(undeployDelay, 1); + + R->EBX(result); return SkipGameCode; } From 1d5dc667eb36758a4542508ea709489df403d2ca Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:56:00 +0800 Subject: [PATCH 05/13] Update Hooks.Unload.cpp --- src/Ext/Building/Hooks.Unload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 1b504943e8..08a529c5ee 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -65,7 +65,7 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } const int undeployDelay = pType->UndeployDelay; - const int result = undeployDelay < 0 ? (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14) : std::max(undeployDelay, 1); + const int result = undeployDelay < 0 ? (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14) : undeployDelay; R->EBX(result); return SkipGameCode; From 6b3f1a8ffa7085113769368eec8849c11f03a166 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:35:29 +0800 Subject: [PATCH 06/13] added DeployFireDelay, discontinued use of UndeployDelay, updated documentation --- CREDITS.md | 2 ++ docs/Fixed-or-Improved-Logics.md | 2 ++ docs/New-or-Enhanced-Logics.md | 7 +++++++ docs/Whats-New.md | 2 ++ src/Ext/Building/Hooks.Unload.cpp | 31 +++++++++---------------------- src/Ext/BuildingType/Body.cpp | 5 ++++- src/Ext/BuildingType/Body.h | 3 +++ src/Misc/Hooks.BugFixes.cpp | 12 ++++++++++++ 8 files changed, 41 insertions(+), 23 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 99bba579a3..8e7f96e920 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -413,6 +413,8 @@ This page lists all the individual contributions to the project by their author. - Fix an issue where mining vehicles could not move after leaving a tank bunker - Fixed the bug in AI scripts 56 and 57 that forced the launch of superweapons with index numbers 3 and 4 - DeployFire supports buildings + - Fixed an issue where `OmniFire` was ineffective on buildings with `Turret=yes` + - Fixed an issue where setting a production building as `Primary` could cause it to enter an unload state - **NetsuNegi**: - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index a2060522c5..f3d3cc2419 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -299,6 +299,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed the bug that techno in attack move will move to target if it cannot attack it. - Fixed the bug in AI scripts 56 and 57 that forced the launch of superweapons with index numbers 3 and 4. - Buildings with `NeedsEngineer=true` are now considered to have threat value of 0 under ownership of `MultiplayPassive=true` houses regardless of their `ThreatPosed` value. +- Fixed an issue where `OmniFire` was ineffective on buildings with `Turret=yes`. +- Fixed an issue where setting a production building as `Primary` could cause it to enter an unload state. ## Fixes / interactions with other extensions diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 950ff658e0..5dd67adae7 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -613,6 +613,13 @@ NoBuildAreaOnBuildup=false ; boolean - Building types now also support using `DeployFire` and `DeployFireWeapon`. - If a building has other configurations such as `Factory`, `GapGenerator`, or `Passengers`, it will prioritize executing those deployment actions instead. + - `DeployFireDelay` specifies the frame interval at which deployed weapons attempt to fire. If the weapon is unavailable due to `ROF`, `CanTarget`, or other reasons, the deployed weapon will not fire. + +In `rulesmd.ini`: +```ini +[SOMEBUILDING] ; BuildingType +DeployFireDelay= ; integer, The default value is in the range of 14 to 16. +``` ### Destroyable pathfinding obstacles diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 4c1d2d8051..cb4a33d24f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -603,6 +603,8 @@ Vanilla fixes: - Fixed the bug that techno in attack move will move to target if it cannot attack it (by NetsuNegi) - Fixed the bug in AI scripts 56 and 57 that forced the launch of superweapons with index numbers 3 and 4 (by FlyStar) - Buildings with `NeedsEngineer=true` are now considered to have threat value of 0 under ownership of `MultiplayPassive=true` houses regardless of their `ThreatPosed` value (by Starkku) +- Fixed an issue where `OmniFire` was ineffective on buildings with `Turret=yes` (by FlyStar) +- Fixed an issue where setting a production building as `Primary` could cause it to enter an unload state (by FlyStar) Phobos fixes: - Fixed the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it (by NetsuNegi) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 08a529c5ee..6ae0733b93 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -18,10 +18,15 @@ DEFINE_HOOK(0x443459, BuildingClass_ObjectClickedAction_DeployFire, 0x6) if (pType->Factory != AbstractType::None) { if (!pThis->IsPrimaryFactory) + { + // Do not enter unloading tasks simultaneously, as this may prevent buildings from producing units in a timely manner. pThis->ClickedEvent(EventType::Primary); + return SkipGameCode; + } } else if (pType->DeployFire) { + // pThis->ClickedMission(Mission::Unload, pThis, nullptr, nullptr); return SkipGameCode; } @@ -48,7 +53,7 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) const int deployFireWeapon = pType->DeployFireWeapon; const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); - + if (pThis->GetFireError(pCell, weaponIndex, true) == FireError::OK && pThis->Fire(pCell, weaponIndex)) { @@ -64,8 +69,9 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } } - const int undeployDelay = pType->UndeployDelay; - const int result = undeployDelay < 0 ? (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14) : undeployDelay; + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); + const int result = pTypeExt->DeployFireDelay.isset() ? + pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); R->EBX(result); return SkipGameCode; @@ -105,22 +111,3 @@ DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) return 0; } - -// I don't know why it doesn't allow OmniFire to function properly. -DEFINE_HOOK(0x447FED, BuildingClass_GetFireError_DeployFire, 0x7) -{ - enum { SkipGameCode = 0x448052 }; - - GET(BuildingClass* const, pThis, ESI); - GET_STACK(const int, weaponIndex, STACK_OFFSET(0xC, 0x8)); - - if (pThis->Type->DeployFire && pThis->CurrentMission == Mission::Unload) - { - auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; - - if (pWeapon->OmniFire) - return SkipGameCode; - } - - return 0; -} diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 646f47d477..9edbfad7e7 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -201,7 +201,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Refinery_UseStorage.Read(exINI, pSection, "Refinery.UseStorage"); this->UndeploysInto_Sellable.Read(exINI, pSection, "UndeploysInto.Sellable"); this->BuildingRadioLink_SyncOwner.Read(exINI, pSection, "BuildingRadioLink.SyncOwner"); - + if (pThis->NumberOfDocks > 0) { std::optional empty; @@ -226,6 +226,8 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Refinery_UseNormalActiveAnim.Read(exArtINI, pArtSection, "Refinery.UseNormalActiveAnim"); + this->DeployFireDelay.Read(exINI, pSection, "DeployFireDelay"); + // Ares tag this->SpyEffect_Custom.Read(exINI, pSection, "SpyEffect.Custom"); if (SuperWeaponTypeClass::Array.Count > 0) @@ -353,6 +355,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->HasPowerUpAnim) .Process(this->UndeploysInto_Sellable) .Process(this->BuildingRadioLink_SyncOwner) + .Process(this->DeployFireDelay) // Ares 0.2 .Process(this->CloningFacility) diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index 16a2e469b7..e5b2cd0f1c 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -103,6 +103,8 @@ class BuildingTypeExt Nullable BuildingRadioLink_SyncOwner; + Nullable DeployFireDelay; + // Ares 0.2 Valueable CloningFacility; @@ -178,6 +180,7 @@ class BuildingTypeExt , HasPowerUpAnim {} , UndeploysInto_Sellable { false } , BuildingRadioLink_SyncOwner {} + , DeployFireDelay {} // Ares 0.2 , CloningFacility { false } diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 0888540328..3125a4a051 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -3022,3 +3022,15 @@ DEFINE_HOOK(0x6EA870, TeamClass_LiberateMember_Start, 0x6) pMember->RecruitableB = true; return 0; } + +DEFINE_HOOK(0x447FED, BuildingClass_GetFireError_OmniFire, 0x7) +{ + enum { SkipGameCode = 0x448052 }; + + GET(BuildingClass* const, pThis, ESI); + GET_STACK(const int, weaponIndex, STACK_OFFSET(0xC, 0x8)); + + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + + return pWeapon->OmniFire ? SkipGameCode : 0; +} From 935d4351f7d654ca24d3ee968c80b014fb3248f4 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:58:14 +0800 Subject: [PATCH 07/13] fixed --- src/Ext/Building/Hooks.Unload.cpp | 14 +++++++++++--- src/Ext/Techno/Hooks.Misc.cpp | 9 +++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 6ae0733b93..37f9d5b8cb 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -53,12 +53,11 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) const int deployFireWeapon = pType->DeployFireWeapon; const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); - + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + if (pThis->GetFireError(pCell, weaponIndex, true) == FireError::OK && pThis->Fire(pCell, weaponIndex)) { - auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; - if (pWeapon->FireOnce) { // When Turret=yes, the Unload task may not be canceled, so this handling is performed. @@ -69,6 +68,15 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } } + if (!pWeapon) + { + // Do not allow the building to remain in the Unload task indefinitely. + pThis->QueueMission(Mission::Guard, false); + pThis->NextMission(); + + return SkipGameCode; + } + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); const int result = pTypeExt->DeployFireDelay.isset() ? pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 2a3461e911..62bd7368ba 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -874,9 +874,11 @@ DEFINE_HOOK(0x4C7512, EventClass_Execute_StopCommand, 0x6) if (auto const pUnit = abstract_cast(pThis)) { + auto const pType = pUnit->Type; + // issue #112 Make FireOnce=yes work on other TechnoType // Author: Starkku - if (pUnit->CurrentMission == Mission::Unload && pUnit->Type->DeployFire && !pUnit->Type->IsSimpleDeployer) + if (pUnit->CurrentMission == Mission::Unload && pType->DeployFire && !pType->IsSimpleDeployer) { pUnit->SetTarget(nullptr); pThis->QueueMission(Mission::Guard, true); @@ -889,8 +891,11 @@ DEFINE_HOOK(0x4C7512, EventClass_Execute_StopCommand, 0x6) } else if (auto const pBuilding = abstract_cast(pThis)) { - if (pBuilding->CurrentMission == Mission::Unload && pBuilding->Type->DeployFire) + auto const pType = pBuilding->Type; + + if (pBuilding->CurrentMission == Mission::Unload && pType->DeployFire && !pType->WeaponsFactory) { + pBuilding->SetTarget(nullptr); pBuilding->QueueMission(Mission::Guard, false); pBuilding->NextMission(); } From 7c15f55e2583433de2925780258520a121ed67ac Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:03:17 +0800 Subject: [PATCH 08/13] Update Hooks.Unload.cpp --- src/Ext/Building/Hooks.Unload.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 37f9d5b8cb..8fa93a674e 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -101,9 +101,10 @@ DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) continue; const auto pBuilding = static_cast(pObject); + auto const pType = pBuilding->Type; const auto pHouse = pBuilding->Owner; - if (!pHouse->IsControlledByCurrentPlayer() || !pBuilding->Type->DeployFire) + if (!pHouse->IsControlledByCurrentPlayer() || !pType->DeployFire || pType->Factory != AbstractType::None) continue; const Mission currentMission = pBuilding->CurrentMission; From 4592cc37052dc56e2cad18b654238dc6929db596 Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:05:50 +0800 Subject: [PATCH 09/13] Update Hooks.Unload.cpp --- src/Ext/Building/Hooks.Unload.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index 8fa93a674e..c5c24486d2 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -53,11 +53,20 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) const int deployFireWeapon = pType->DeployFireWeapon; const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); - auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + const FireError fireError = pThis->GetFireError(pCell, weaponIndex, true); - if (pThis->GetFireError(pCell, weaponIndex, true) == FireError::OK && - pThis->Fire(pCell, weaponIndex)) + if (fireError == FireError::ILLEGAL) { + // Do not allow the building to remain in the Unload task indefinitely. + pThis->QueueMission(Mission::Guard, false); + pThis->NextMission(); + + return SkipGameCode; + } + else if (fireError == FireError::OK && pThis->Fire(pCell, weaponIndex)) + { + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + if (pWeapon->FireOnce) { // When Turret=yes, the Unload task may not be canceled, so this handling is performed. @@ -68,15 +77,6 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } } - if (!pWeapon) - { - // Do not allow the building to remain in the Unload task indefinitely. - pThis->QueueMission(Mission::Guard, false); - pThis->NextMission(); - - return SkipGameCode; - } - auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); const int result = pTypeExt->DeployFireDelay.isset() ? pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); From 15f44fe1496fa7e867fc6c11c926a854f433dc7e Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:19:42 +0800 Subject: [PATCH 10/13] oh, my mistake. --- src/Ext/Techno/Hooks.Misc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 62bd7368ba..5832105103 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -893,7 +893,8 @@ DEFINE_HOOK(0x4C7512, EventClass_Execute_StopCommand, 0x6) { auto const pType = pBuilding->Type; - if (pBuilding->CurrentMission == Mission::Unload && pType->DeployFire && !pType->WeaponsFactory) + if (pBuilding->CurrentMission == Mission::Unload + && pType->DeployFire && pType->Factory == AbstractType::None) { pBuilding->SetTarget(nullptr); pBuilding->QueueMission(Mission::Guard, false); From 9d9add08b5d682db99e62cea5b308305795806ac Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:36:12 +0800 Subject: [PATCH 11/13] well --- src/Ext/Building/Hooks.Unload.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index c5c24486d2..df094589de 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -26,7 +26,6 @@ DEFINE_HOOK(0x443459, BuildingClass_ObjectClickedAction_DeployFire, 0x6) } else if (pType->DeployFire) { - // pThis->ClickedMission(Mission::Unload, pThis, nullptr, nullptr); return SkipGameCode; } @@ -78,8 +77,8 @@ DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) } auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); - const int result = pTypeExt->DeployFireDelay.isset() ? - pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); + const int result = pTypeExt->DeployFireDelay.isset() + ? pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); R->EBX(result); return SkipGameCode; From 08f0fd8aa78e8c502d5c342ebddd97c90c12fc5a Mon Sep 17 00:00:00 2001 From: Fly-Star <100747645+a851903106@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:15:59 +0800 Subject: [PATCH 12/13] Update Hooks.Unload.cpp --- src/Ext/Building/Hooks.Unload.cpp | 73 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/Ext/Building/Hooks.Unload.cpp b/src/Ext/Building/Hooks.Unload.cpp index df094589de..4d1bc1e74d 100644 --- a/src/Ext/Building/Hooks.Unload.cpp +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -33,61 +33,55 @@ DEFINE_HOOK(0x443459, BuildingClass_ObjectClickedAction_DeployFire, 0x6) return SkipFactory; } -DEFINE_HOOK(0x44E29D, BuildingClass_Mission_Unload_DeployFire, 0x6) +DEFINE_HOOK(0x44E371, BuildingClass_Mission_Unload_DeployFire, 0x6) { GET(BuildingClass* const, pThis, EBP); - GET(BuildingTypeClass* const, pType, EAX); - enum { SkipGameCode = 0x44E37F, ReturnGuard = 0x44E371, Continue = 0x44E2BE }; + enum { SkipGameCode = 0x44E37F }; - const int cells = static_cast(pType->SuperGapRadiusInCells); + auto const pType = pThis->Type; - if (!pType->GapGenerator || !cells) + if (!pType->GapGenerator && pType->DeployFire) { - if (pType->DeployFire) - { - auto const pCell = pThis->GetCell(); + auto const pCell = pThis->GetCell(); - if (pThis->Target != pCell) - pThis->SetTarget(pCell); + if (pThis->Target != pCell) + pThis->SetTarget(pCell); - const int deployFireWeapon = pType->DeployFireWeapon; - const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); - const FireError fireError = pThis->GetFireError(pCell, weaponIndex, true); + const int deployFireWeapon = pType->DeployFireWeapon; + const int weaponIndex = deployFireWeapon >= 0 ? deployFireWeapon : pThis->SelectWeapon(pCell); + const FireError fireError = pThis->GetFireError(pCell, weaponIndex, true); - if (fireError == FireError::ILLEGAL) + if (fireError == FireError::ILLEGAL) + { + // Do not allow the building to remain in the Unload task indefinitely. + pThis->QueueMission(Mission::Guard, false); + pThis->NextMission(); + + return SkipGameCode; + } + else if (fireError == FireError::OK && pThis->Fire(pCell, weaponIndex)) + { + auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + + if (pWeapon->FireOnce) { - // Do not allow the building to remain in the Unload task indefinitely. + // When Turret=yes, the Unload task may not be canceled, so this handling is performed. pThis->QueueMission(Mission::Guard, false); pThis->NextMission(); return SkipGameCode; } - else if (fireError == FireError::OK && pThis->Fire(pCell, weaponIndex)) - { - auto const pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; - - if (pWeapon->FireOnce) - { - // When Turret=yes, the Unload task may not be canceled, so this handling is performed. - pThis->QueueMission(Mission::Guard, false); - pThis->NextMission(); - - return SkipGameCode; - } - } - - auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); - const int result = pTypeExt->DeployFireDelay.isset() - ? pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); - - R->EBX(result); - return SkipGameCode; } - return ReturnGuard; + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pType); + const int result = pTypeExt->DeployFireDelay.isset() + ? pTypeExt->DeployFireDelay : (ScenarioClass::Instance->Random.RandomRanged(0, 2) + 14); + + R->EBX(result); + return SkipGameCode; } - return Continue; + return 0; } DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) @@ -103,8 +97,11 @@ DEFINE_HOOK(0x730B09, DeployCommandClass_Execute_BuildingDeploy, 0x5) auto const pType = pBuilding->Type; const auto pHouse = pBuilding->Owner; - if (!pHouse->IsControlledByCurrentPlayer() || !pType->DeployFire || pType->Factory != AbstractType::None) + if (!pHouse->IsControlledByCurrentPlayer() + || !pType->DeployFire || pType->Factory != AbstractType::None || pType->GapGenerator) + { continue; + } const Mission currentMission = pBuilding->CurrentMission; From f41fe8dae166fa6a5aaf9f0fbcc1646c6126b27a Mon Sep 17 00:00:00 2001 From: Noble_Fish <1065703286@qq.com> Date: Sat, 14 Feb 2026 21:39:39 +0800 Subject: [PATCH 13/13] update docs add link, correct the comment format --- docs/New-or-Enhanced-Logics.md | 6 +++--- docs/Whats-New.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 5dd67adae7..b1c4eb4165 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -614,11 +614,11 @@ NoBuildAreaOnBuildup=false ; boolean - Building types now also support using `DeployFire` and `DeployFireWeapon`. - If a building has other configurations such as `Factory`, `GapGenerator`, or `Passengers`, it will prioritize executing those deployment actions instead. - `DeployFireDelay` specifies the frame interval at which deployed weapons attempt to fire. If the weapon is unavailable due to `ROF`, `CanTarget`, or other reasons, the deployed weapon will not fire. - + In `rulesmd.ini`: ```ini -[SOMEBUILDING] ; BuildingType -DeployFireDelay= ; integer, The default value is in the range of 14 to 16. +[SOMEBUILDING] ; BuildingType, with DeployFire=yes +DeployFireDelay= ; integer, default value ranges from 14 to 16 ``` ### Destroyable pathfinding obstacles diff --git a/docs/Whats-New.md b/docs/Whats-New.md index cb4a33d24f..04952b1efd 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -543,7 +543,7 @@ New: - Allow jumpjet climbing ignore building height (by TaranDahl) - Allow draw SuperWeapon timer as percentage (by NetsuNegi) - Customize particle system of parasite logic (by NetsuNegi) -- DeployFire supports buildings (By FlyStar) +- [DeployFire supports buildings](New-or-Enhanced-Logics.md#deployfire-supports) (By FlyStar) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)