diff --git a/CREDITS.md b/CREDITS.md index 656399b2bd..6af3308d55 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -419,6 +419,9 @@ This page lists all the individual contributions to the project by their author. - Fixed an issue where parachute units would die upon landing if bridges were destroyed during their descent - Custom hover vehicles shutdown drowning death - SHP turret vehicles support the use of `*tur.shp` files + - DeployFire supports buildings + - `OmniFire` supports 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 fbdb65f7f2..529c9c719c 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -318,6 +318,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fixed units with Fly, Jumpjet or Rocket locomotors destroyed while crashing off-map never being fully cleaned up, permanently blocking production slots and counting towards unit limits. - Fixed a desync due to an inconsistent shroud state caused by `GapGenerator` and `SpySat` interaction. - Now miners will no longer withdraw from the Harvest mission due to mineral depletion and will periodically attempt to return to work. +- 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 7634f6cf05..29cce18894 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -614,6 +614,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 d8eb1523de..679b065c77 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -562,6 +562,8 @@ New: - [Allow customize jumpjet properties on warhead](Fixed-or-Improved-Logics.md#customizing-locomotor-warhead) (by NetsuNegi) - Customize effects range of power plant enhancer (by NetsuNegi) - Allow each side to customize the color when the proportion of working miners is higher than `HarvesterCounter.ConditionYellow` (by Noble_Fish) +- [DeployFire supports buildings](New-or-Enhanced-Logics.md#deployfire-supports) (By FlyStar) +- `OmniFire` supports buildings with `Turret=yes` (by FlyStar) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) @@ -637,6 +639,7 @@ Vanilla fixes: - Miners back to work when ore regenerated (by TaranDahl) - [Allow disable an over-optimization in targeting](Fixed-or-Improved-Logics.md#allow-disable-an-over-optimization-in-targeting) (by TaranDahl) - Extra threat (by TaranDahl) +- 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 0df69a20b0..674f353f16 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -222,7 +222,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; @@ -247,6 +247,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) @@ -378,6 +380,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 81fde368c3..0dc400d4cb 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -104,6 +104,8 @@ class BuildingTypeExt Nullable BuildingRadioLink_SyncOwner; + Nullable DeployFireDelay; + // Ares 0.2 Valueable CloningFacility; @@ -183,6 +185,7 @@ class BuildingTypeExt , HasPowerUpAnim {} , UndeploysInto_Sellable { false } , BuildingRadioLink_SyncOwner {} + , DeployFireDelay {} // Ares 0.2 , CloningFacility { false } diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index e6baa92582..4e2ef491fb 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -534,6 +534,18 @@ DEFINE_HOOK(0x6FC7EB, TechnoClass_CanFire_InterceptBullet, 0x7) return ContinueCheck; } +DEFINE_HOOK(0x447FED, BuildingClass_CanFire_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; +} + #pragma endregion #pragma region TechnoClass_Fire diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 6e40ab44db..e68acb01a4 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -889,19 +889,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; }