diff --git a/CREDITS.md b/CREDITS.md index 6e74c1718c..8e7f96e920 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -412,6 +412,9 @@ 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 + - 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/Phobos.vcxproj b/Phobos.vcxproj index d45a6ac91f..a58ec8ab7f 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -18,6 +18,7 @@ + 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 83f2fafb5d..b1c4eb4165 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -609,6 +609,18 @@ 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. + - `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, with DeployFire=yes +DeployFireDelay= ; integer, default value ranges from 14 to 16 +``` + ### 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 95190b8f5c..04952b1efd 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -543,6 +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](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) @@ -602,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 new file mode 100644 index 0000000000..4d1bc1e74d --- /dev/null +++ b/src/Ext/Building/Hooks.Unload.cpp @@ -0,0 +1,118 @@ +#include "Body.h" + +DEFINE_HOOK(0x447358, BuildingClass_MouseOverObject_DeployFire, 0x6) +{ + GET(BuildingTypeClass* const, pType, EAX); + + return pType->DeployFire ? 0x4472EC : 0; +} + +DEFINE_HOOK(0x443459, BuildingClass_ObjectClickedAction_DeployFire, 0x6) +{ + 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) + { + // 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; + } + + return SkipFactory; +} + +DEFINE_HOOK(0x44E371, BuildingClass_Mission_Unload_DeployFire, 0x6) +{ + GET(BuildingClass* const, pThis, EBP); + enum { SkipGameCode = 0x44E37F }; + + auto const pType = pThis->Type; + + if (!pType->GapGenerator && 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); + const FireError fireError = pThis->GetFireError(pCell, weaponIndex, true); + + 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. + 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 0; +} + +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); + auto const pType = pBuilding->Type; + const auto pHouse = pBuilding->Owner; + + if (!pHouse->IsControlledByCurrentPlayer() + || !pType->DeployFire || pType->Factory != AbstractType::None || pType->GapGenerator) + { + 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/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/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 9bf0607d80..5832105103 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -874,19 +874,33 @@ 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); } - + // 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)) + { + auto const pType = pBuilding->Type; + + if (pBuilding->CurrentMission == Mission::Unload + && pType->DeployFire && pType->Factory == AbstractType::None) + { + pBuilding->SetTarget(nullptr); + pBuilding->QueueMission(Mission::Guard, false); + pBuilding->NextMission(); + } + } return 0; } 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; +}