diff --git a/CREDITS.md b/CREDITS.md index 4c788fe55f..735af0033f 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -372,6 +372,7 @@ This page lists all the individual contributions to the project by their author. - Allow to change the speed of gas particles - **CrimRecya** - Fix `LimboKill` not working reliably + - Avoid weapon fire overflow in the same frame - **Ollerus** - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index e553a34732..c59bb9c845 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1748,3 +1748,13 @@ In `rulesmd.ini`: CanTarget=all ; list of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all) CanTargetHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) ``` + +### Avoid weapon fire overflow in the same frame + +- Now you can make the weapon declare that the target has been locked at the moment of firing, and all weapons with positive `NoRepeatFire` value will not attack the declared target again during this period of time. Made for seckill and control weapons. + +In `rulesmd.ini`: +```ini +[SOMEWEAPON] ; WeaponType +NoRepeatFire=0 ; integer, game frames +``` diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 37c9f39e2f..b7616165be 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -469,6 +469,7 @@ New: - Allow infantry to use land sequences in water (by Starkku) - `` can now be used as owner for pre-placed objects on skirmish and multiplayer maps (by Starkku) - Allow customizing charge turret delays per burst on a weapon (by Starkku) +- Avoid weapon fire overflow in the same frame (by CrimRecya) - Unit `Speed` setting now accepts floating point values (by Starkku) Vanilla fixes: diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 0d5c6ccf02..6bbff5c52f 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -498,6 +498,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->WHAnimRemainingCreationInterval) .Process(this->FiringObstacleCell) .Process(this->IsDetachingForCloak) + .Process(this->LastBeLockedFrame) .Process(this->OriginalPassengerOwner) .Process(this->HasRemainingWarpInDelay) .Process(this->LastWarpInDelay) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index dd958380bd..02c67a2f9d 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -51,6 +51,7 @@ class TechnoExt bool CanCurrentlyDeployIntoBuilding; // Only set on UnitClass technos with DeploysInto set in multiplayer games, recalculated once per frame so no need to serialize. CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc. bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only. + int LastBeLockedFrame; // Used for Passengers.SyncOwner.RevertOnExit instead of TechnoClass::InitialOwner / OriginallyOwnedByHouse, // as neither is guaranteed to point to the house the TechnoClass had prior to entering transport and cannot be safely overridden. @@ -88,6 +89,7 @@ class TechnoExt , CanCurrentlyDeployIntoBuilding { false } , FiringObstacleCell {} , IsDetachingForCloak { false } + , LastBeLockedFrame { 0 } , OriginalPassengerOwner {} , HasRemainingWarpInDelay { false } , LastWarpInDelay { 0 } diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 1bc6f719a5..90eac72b91 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -274,6 +274,15 @@ DEFINE_HOOK(0x6FC339, TechnoClass_CanFire, 0x6) const auto pTechno = abstract_cast(pTarget); CellClass* pTargetCell = nullptr; + if (pWeaponExt->NoRepeatFire > 0) + { + if (const auto pTargetTechnoExt = TechnoExt::ExtMap.Find(pTechno)) + { + if ((Unsorted::CurrentFrame - pTargetTechnoExt->LastBeLockedFrame) < pWeaponExt->NoRepeatFire) + return CannotFire; + } + } + // AAOnly doesn't need to be checked if LandTargeting=1. if (pThis->GetTechnoType()->LandTargeting != LandTargetingType::Land_Not_OK && pWeapon->Projectile->AA && pTarget && !pTarget->IsInAir()) { @@ -414,8 +423,10 @@ DEFINE_HOOK(0x6FCBE6, TechnoClass_CanFire_BridgeAAFix, 0x6) DEFINE_HOOK(0x6FDDC0, TechnoClass_FireAt_DiscardAEOnFire, 0x6) { GET(TechnoClass* const, pThis, ESI); + GET(AbstractClass* const, pTarget, EDI); + GET(WeaponTypeClass* const, pWeapon, EBX); - auto const pExt = TechnoExt::ExtMap.Find(pThis); + const auto pExt = TechnoExt::ExtMap.Find(pThis); if (pExt->AE.HasOnFireDiscardables) { @@ -426,6 +437,15 @@ DEFINE_HOOK(0x6FDDC0, TechnoClass_FireAt_DiscardAEOnFire, 0x6) } } + if (const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon)) + { + if (pWeaponExt->NoRepeatFire > 0) + { + if (const auto pTargetTechnoExt = TechnoExt::ExtMap.Find(abstract_cast(pTarget))) + pTargetTechnoExt->LastBeLockedFrame = Unsorted::CurrentFrame; + } + } + return 0; } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 3e82a2b5bc..2d42ecb8f1 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -115,6 +115,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AttachEffect_CheckOnFirer.Read(exINI, pSection, "AttachEffect.CheckOnFirer"); this->AttachEffect_IgnoreFromSameSource.Read(exINI, pSection, "AttachEffect.IgnoreFromSameSource"); this->KickOutPassengers.Read(exINI, pSection, "KickOutPassengers"); + this->NoRepeatFire.Read(exINI, pSection, "NoRepeatFire"); } template @@ -160,6 +161,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->AttachEffect_CheckOnFirer) .Process(this->AttachEffect_IgnoreFromSameSource) .Process(this->KickOutPassengers) + .Process(this->NoRepeatFire) ; }; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 2a979bece1..26e175e1ee 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -60,6 +60,7 @@ class WeaponTypeExt Valueable AttachEffect_CheckOnFirer; Valueable AttachEffect_IgnoreFromSameSource; Valueable KickOutPassengers; + Valueable NoRepeatFire; ExtData(WeaponTypeClass* OwnerObject) : Extension(OwnerObject) , DiskLaser_Radius { DiskLaserClass::Radius } @@ -101,6 +102,7 @@ class WeaponTypeExt , AttachEffect_CheckOnFirer { false } , AttachEffect_IgnoreFromSameSource { false } , KickOutPassengers { true } + , NoRepeatFire { 0 } { } int GetBurstDelay(int burstIndex) const;