diff --git a/CREDITS.md b/CREDITS.md index 3ecead2d75..36d447fdf2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -159,6 +159,7 @@ This page lists all the individual contributions to the project by their author. - Event 606: AttachEffect is attaching to a Techno - Linked superweapons - Unit & infantry auto-conversion on ammo change + - `EMPulseCannon.InaccurateRadius` and `EMPulse.Burst` for `Type=EMPulse` superweapons - Restore the ScriptType action#24 `Play speech` from Tiberian Sun - Modify ammo on impact - **Starkku**: diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 343dae63eb..b69b393f54 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1277,12 +1277,19 @@ TabIndex=1 ; integer - Note that if you fire another `Type=EMPulse` superweapon with different weapon index that the same building is capable of launching before the first weapon was fired, the latter superweapon's settings will take precedence. - Additionally, due to technical limitations the targeting constraints will always default to primary weapon's `Range/MinimumRange` unless `SW.RangeMinimum` / `SW.RangeMaximum` are explicitly set. - Is is now also possible to have all other `Type=EMPulse` superweapons that can be fired by same buildings as current one be put on hold until first of the buildings currently set to fire goes off if the firing superweapon has `EMPulse.SuspendOthers=true`. +- It is now possible to specify how many shots are fired in a volley/burst when launching a `Type=EMPulse` superweapon by setting `EMPulse.Burst` on the superweapon. + - If the firing structure loses power, is deactivated, EMP'd, or disabled during a burst sequence, firing will pause and automatically resume once power or functionality is restored. +- `EMPulseCannon.InaccurateRadius` defines a circular area around the target location where the projectile can land randomly. In `rulesmd.ini`: ```ini -[SOMESW] ; SuperWeaponType -EMPulse.WeaponIndex=0 ; integer, weapon slot index -EMPulse.SuspendOthers=false ; boolean +[SOMESW] ; SuperWeaponType +EMPulse.WeaponIndex=0 ; integer, weapon slot index +EMPulse.SuspendOthers=false ; boolean +EMPulse.Burst=1 ; integer, number of shots fired in the burst + +[SOMEPROJECTILE] ; Projectile +EMPulseCannon.InaccurateRadius=0 ; integer (distance in cells) ``` ```{note} diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 68b6030d64..d7011180b8 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -421,6 +421,7 @@ HideShakeEffects=false ; boolean #### New: - [Customized transport plane for teams](AI-Scripting-and-Mapping.md#customized-transport-plane-for-teams) (by FlyStar) - [Modify ammo on impact](New-or-Enhanced-Logics.md#modify-ammo-on-impact) (by FS-21) +- [`EMPulseCannon.InaccurateRadius` and `EMPulse.Burst` for `Type=EMPulse` superweapons](New-or-Enhanced-Logics.md#empulse-settings) (by FS-21) - [Customize whether mind-controlled Insignificant technos can be auto-targeted](Fixed-or-Improved-Logics.md#customize-whether-mind-controlled-Insignificant-technos-can-be-auto-targeted) (by Noble_Fish) - [AutoDeath based on player power status and player credits](New-or-Enhanced-Logics.md#kill-object-automatically) (by Flactine) - [Roof production anim](New-or-Enhanced-Logics.md#roof-production-anim) (by Noble_Fish) diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 1bbc42eeb4..1be69bf302 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -599,11 +599,13 @@ void BuildingExt::Serialize(T& Stm) .Process(this->CurrentLaserWeaponIndex) .Process(this->PoweredUpToLevel) .Process(this->CurrentEMPulseSW) + .Process(this->RandomEMPTarget) + .Process(this->EMPulseBurstIndex) //.Process(this->IsFiringNow) It is set and reset within a same function. .Process(this->TurretAnimIdleFrame) .Process(this->TurretAnimFiringFrame) .Process(this->TurretAnimRateTick) - .Process(this->ConstructionStartFacing) + .Process(this->ConstructionStartFacing) ; } diff --git a/src/Ext/Building/Body.h b/src/Ext/Building/Body.h index cb8eaa3996..120d875dea 100644 --- a/src/Ext/Building/Body.h +++ b/src/Ext/Building/Body.h @@ -24,6 +24,8 @@ class BuildingExt final : public TechnoExt, public Detach::Listener CurrentLaserWeaponIndex; int PoweredUpToLevel; // Distinct from UpgradeLevel, and set to highest PowersUpToLevel out of applied upgrades regardless of how many are currently applied to this building. SuperClass* CurrentEMPulseSW; + CellStruct RandomEMPTarget; + int EMPulseBurstIndex; bool IsFiringNow; int TurretAnimIdleFrame; int TurretAnimFiringFrame; @@ -43,6 +45,8 @@ class BuildingExt final : public TechnoExt, public Detach::ListenerEMPulse_SuspendOthers) + // Only clean up on the last burst shot + if (pExt->EMPulseBurstIndex + 1 >= pSWExt->EMPulse_Burst) { - auto const pHouseExt = HouseExt::Fetch(pOwner); - const int index = pExt->CurrentEMPulseSW->Type->ArrayIndex; - - if (pHouseExt->SuspendedEMPulseSWs.count(index)) + if (pSWExt->EMPulse_SuspendOthers) { - auto& supers = pOwner->Supers; + auto const pHouseExt = HouseExt::Fetch(pOwner); + const int index = pExt->CurrentEMPulseSW->Type->ArrayIndex; - for (auto const& swidx : pHouseExt->SuspendedEMPulseSWs[index]) + if (pHouseExt->SuspendedEMPulseSWs.count(index)) { - auto const super = supers[swidx]; - super->IsSuspended = false; - } + auto& supers = pOwner->Supers; + + for (auto const& swidx : pHouseExt->SuspendedEMPulseSWs[index]) + { + auto const super = supers[swidx]; + super->IsSuspended = false; + } - pHouseExt->SuspendedEMPulseSWs[index].clear(); - pHouseExt->SuspendedEMPulseSWs.erase(index); + pHouseExt->SuspendedEMPulseSWs[index].clear(); + pHouseExt->SuspendedEMPulseSWs.erase(index); + } } - } - pExt->CurrentEMPulseSW = nullptr; + pExt->CurrentEMPulseSW = nullptr; + } EMPulseCannonTemp::weaponIndex = weaponIndex; R->EAX(pThis->GetWeapon(weaponIndex)); return SkipGameCode; diff --git a/src/Ext/BulletType/Body.cpp b/src/Ext/BulletType/Body.cpp index 197375377c..5e5574aeac 100644 --- a/src/Ext/BulletType/Body.cpp +++ b/src/Ext/BulletType/Body.cpp @@ -81,6 +81,7 @@ void BulletTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->Parachuted_MaxFallRate.Read(exINI, pSection, "Parachuted.MaxFallRate"); this->BombParachute.Read(exINI, pSection, "BombParachute"); this->AU.Read(exINI, pSection, "AU"); + this->EMPulseCannon_InaccurateRadius.Read(exINI, pSection, "EMPulseCannon.InaccurateRadius"); // Ares 0.7 this->BallisticScatter_Min.Read(exINI, pSection, "BallisticScatter.Min"); @@ -188,6 +189,7 @@ void BulletTypeExt::Serialize(T& Stm) .Process(this->Parachuted_MaxFallRate) .Process(this->BombParachute) .Process(this->AU) + .Process(this->EMPulseCannon_InaccurateRadius) .Process(this->ZAdjust) .Process(this->TrajectoryType) // just keep this shit at last diff --git a/src/Ext/BulletType/Body.h b/src/Ext/BulletType/Body.h index b6e18c26f8..fd8ca64b01 100644 --- a/src/Ext/BulletType/Body.h +++ b/src/Ext/BulletType/Body.h @@ -84,10 +84,9 @@ class BulletTypeExt final : public ObjectTypeExt Valueable Parachuted_FallRate; Nullable Parachuted_MaxFallRate; Nullable BombParachute; - Valueable AU; - Valueable ZAdjust; + Valueable EMPulseCannon_InaccurateRadius; // Ares 0.7 Nullable BallisticScatter_Min; @@ -103,11 +102,9 @@ class BulletTypeExt final : public ObjectTypeExt , Vertical_AircraftFix {} , VerticalInitialFacing {} , TrajectoryType { } - , Shrapnel_AffectsGround {} - , Shrapnel_AffectsBuildings {} - , Shrapnel_UseWeaponTargeting {} - , Shrapnel_IgnoreHitBuildings {} - , Shrapnel_ObeyWarheadTriggerConditions {} + , Shrapnel_AffectsGround { false } + , Shrapnel_AffectsBuildings { false } + , Shrapnel_UseWeaponTargeting { false } , ClusterScatter_Min { Leptons(256) } , ClusterScatter_Max { Leptons(512) } , BallisticScatter_Min {} @@ -117,9 +114,9 @@ class BulletTypeExt final : public ObjectTypeExt , SubjectToWater {} , SubjectToWater_Detonate { true } , AAOnly { false } - , Arcing_AllowElevationInaccuracy {} + , Arcing_AllowElevationInaccuracy { true } , ReturnWeapon {} - , ReturnWeapon_ApplyFirepowerMult {} + , ReturnWeapon_ApplyFirepowerMult { false } , SubjectToGround { false } , Splits { false } , AirburstSpread { 1.5 } @@ -148,6 +145,7 @@ class BulletTypeExt final : public ObjectTypeExt , BombParachute {} , AU { false } , ZAdjust { 0 } + , EMPulseCannon_InaccurateRadius { 0 } { } virtual ~BulletTypeExt() = default; diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 531a66fb0d..6389a4fab1 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -92,6 +92,7 @@ void SWTypeExt::Serialize(T& Stm) .Process(this->EMPulse_SuspendOthers) .Process(this->EMPulse_Cannons) .Process(this->EMPulse_TargetSelf) + .Process(this->EMPulse_Burst) .Process(this->SW_Link) .Process(this->SW_Link_Grant) .Process(this->SW_Link_Ready) @@ -180,6 +181,7 @@ void SWTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->EMPulse_SuspendOthers.Read(exINI, pSection, "EMPulse.SuspendOthers"); this->EMPulse_Cannons.Read(exINI, pSection, "EMPulse.Cannons"); this->EMPulse_TargetSelf.Read(exINI, pSection, "EMPulse.TargetSelf"); + this->EMPulse_Burst.Read(exINI, pSection, "EMPulse.Burst"); char tempBuffer[32]; // LimboDelivery.RandomWeights diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index e064cdcfdb..c146ef3a8a 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -110,6 +110,7 @@ class SWTypeExt final : public AbstractTypeExt Valueable EMPulse_SuspendOthers; ValueableVector EMPulse_Cannons; Valueable EMPulse_TargetSelf; + Valueable EMPulse_Burst; ValueableIdxVector SW_Link; Valueable SW_Link_Grant; @@ -199,6 +200,7 @@ class SWTypeExt final : public AbstractTypeExt , EMPulse_SuspendOthers { false } , EMPulse_Cannons {} , EMPulse_TargetSelf { false } + , EMPulse_Burst { 1 } , SW_Link {} , SW_Link_Grant { false } , SW_Link_Ready { false } diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index eae199788a..5f829d266d 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -398,7 +398,7 @@ void SWTypeExt::HandleEMPulseLaunch(SuperClass* pSW, const CellStruct& cell) con const int arrayIndex = pSW->Type->ArrayIndex; if (pHouseExt->SuspendedEMPulseSWs.count(arrayIndex)) - pHouseExt->SuspendedEMPulseSWs[arrayIndex].push_back(arrayIndex); + pHouseExt->SuspendedEMPulseSWs[arrayIndex].push_back(pSuper->Type->ArrayIndex); else pHouseExt->SuspendedEMPulseSWs.insert({ arrayIndex, std::vector{pSuper->Type->ArrayIndex} }); } diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 244b1a5cb9..f6644f118a 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -6,6 +6,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -1216,6 +1220,104 @@ DEFINE_HOOK(0x5223B3, InfantryClass_Approach_Target_DeployFireWeapon, 0x6) return 0x5223B9; } +DEFINE_HOOK(0x44CD18, BuildingClass_MissionMissile_EMPulseCannon_InaccurateRadius, 0x6) +{ + GET(BuildingClass*, pThis, ESI); + + if (!pThis->Type->EMPulseCannon) + return 0; + + const auto pExt = BuildingExt::Fetch(pThis); + + // Pause firing sequence if the structure is unpowered or offline + const bool isOffline = !pThis->IsPowerOnline() + || !pThis->StuffEnabled + || pThis->Deactivated + || pThis->IsUnderEMP() + || TechnoExt::HasWeaponsDisabled(pThis); + + if (isOffline) + { + pThis->MissionStatus = 0; + // Return to game loop to wait without advancing state + return 0; + } + + HouseClass* pHouse = pThis->Owner; + const auto pCell = MapClass::Instance.TryGetCellAt(pHouse->EMPTarget); + + if (!pCell) + return 0; + + // Obtain the weapon used by the EMP weapon + int weaponIndex = 0; + int totalBurst = 1; + + if (pExt->CurrentEMPulseSW) + { + const auto pSWExt = SWTypeExt::Fetch(pExt->CurrentEMPulseSW->Type); + totalBurst = pSWExt->EMPulse_Burst; + + if (pSWExt->EMPulse_WeaponIndex >= 0) + { + weaponIndex = pSWExt->EMPulse_WeaponIndex; + } + else + { + AbstractClass* pTarget = pCell; + + if (const auto pObject = pCell->GetContent()) + pTarget = pObject; + + weaponIndex = pThis->SelectWeapon(pTarget); + } + } + + const auto pWeapon = pThis->GetWeapon(weaponIndex)->WeaponType; + + // Inaccurate random strike area calculation + int radius = BulletTypeExt::Fetch(pWeapon->Projectile)->EMPulseCannon_InaccurateRadius; + radius = radius < 0 ? 0 : radius; + + if (radius > 0) + { + if (pExt->RandomEMPTarget == CellStruct::Empty) + { + CoordStruct targetCoords = CellClass::Cell2Coord(pHouse->EMPTarget); + int maxDistanceLeptons = radius * Unsorted::LeptonsPerCell; + int distanceLeptons = ScenarioClass::Instance->Random.RandomRanged(0, maxDistanceLeptons); + CoordStruct randomCoords = MapClass::GetRandomCoordsNear(targetCoords, distanceLeptons, false); + CellStruct randomCell = CellClass::Coord2Cell(randomCoords); + + if (MapClass::Instance.IsWithinUsableArea(randomCell, false)) + pExt->RandomEMPTarget = randomCell; + else + pExt->RandomEMPTarget = pHouse->EMPTarget; + } + + pHouse->EMPTarget = pExt->RandomEMPTarget; // Value overwritten every frame + } + + if (pThis->MissionStatus != 3) + return 0; + + pExt->RandomEMPTarget = CellStruct::Empty; + + // Increment burst index and restart if there are burst shots remaining + pExt->EMPulseBurstIndex++; + + if (pExt->EMPulseBurstIndex < totalBurst) + { + pThis->MissionStatus = 0; + } + else + { + pExt->EMPulseBurstIndex = 0; + } + + return 0; +} + DEFINE_HOOK(0x708AD0, TechnoClass_ShouldRetaliate_IronCurtain, 0x6) { enum { ContinueCheck = 0x708AD6, ReturnTrue = 0x708B0B, ReturnFalse = 0x708B17 }; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 4078c8ca93..b1d55f82bd 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -25,6 +25,7 @@ class WeaponTypeExt final : public AbstractTypeExt } + Valueable DiskLaser_Radius; Valueable ProjectileRange; Nullable ProjectileRange_ApplyModifiers;