Skip to content
Open
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand Down
13 changes: 10 additions & 3 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/Ext/Building/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
;
}

Expand Down
4 changes: 4 additions & 0 deletions src/Ext/Building/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class BuildingExt final : public TechnoExt, public Detach::Listener<BuildingClas
std::optional<int> 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;
Expand All @@ -43,6 +45,8 @@ class BuildingExt final : public TechnoExt, public Detach::Listener<BuildingClas
, CurrentLaserWeaponIndex {}
, PoweredUpToLevel { 0 }
, CurrentEMPulseSW {}
, RandomEMPTarget { CellStruct::Empty }
, EMPulseBurstIndex { 0 }
, IsFiringNow { false }
, TurretAnimIdleFrame { 0 }
, TurretAnimFiringFrame { -1 }
Expand Down
32 changes: 18 additions & 14 deletions src/Ext/Building/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,27 +163,31 @@ DEFINE_HOOK(0x44CEEC, BuildingClass_Mission_Missile_EMPulseSelectWeapon, 0x6)
}
}

if (pSWExt->EMPulse_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;
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/BulletType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions src/Ext/BulletType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,9 @@ class BulletTypeExt final : public ObjectTypeExt
Valueable<int> Parachuted_FallRate;
Nullable<int> Parachuted_MaxFallRate;
Nullable<AnimTypeClass*> BombParachute;

Valueable<bool> AU;

Valueable<int> ZAdjust;
Valueable<int> EMPulseCannon_InaccurateRadius;

// Ares 0.7
Nullable<Leptons> BallisticScatter_Min;
Expand All @@ -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 {}
Expand All @@ -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 }
Expand Down Expand Up @@ -148,6 +145,7 @@ class BulletTypeExt final : public ObjectTypeExt
, BombParachute {}
, AU { false }
, ZAdjust { 0 }
, EMPulseCannon_InaccurateRadius { 0 }
{ }

virtual ~BulletTypeExt() = default;
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/SWType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/SWType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class SWTypeExt final : public AbstractTypeExt
Valueable<bool> EMPulse_SuspendOthers;
ValueableVector<BuildingTypeClass*> EMPulse_Cannons;
Valueable<bool> EMPulse_TargetSelf;
Valueable<int> EMPulse_Burst;

ValueableIdxVector<SuperWeaponTypeClass> SW_Link;
Valueable<bool> SW_Link_Grant;
Expand Down Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/SWType/FireSuperWeapon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>{pSuper->Type->ArrayIndex} });
}
Expand Down
102 changes: 102 additions & 0 deletions src/Ext/Techno/Hooks.Firing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <Ext/Unit/Body.h>
#include <Ext/WarheadType/Body.h>
#include <Ext/WeaponType/Body.h>
#include <Ext/BulletType/Body.h>
#include <Ext/House/Body.h>
#include <Ext/SWType/Body.h>
#include <Utilities/EnumFunctions.h>
#include <Utilities/GeneralUtils.h>

#include <cmath>
Expand Down Expand Up @@ -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 };
Expand Down
1 change: 1 addition & 0 deletions src/Ext/WeaponType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class WeaponTypeExt final : public AbstractTypeExt
}



Valueable<double> DiskLaser_Radius;
Valueable<Leptons> ProjectileRange;
Nullable<bool> ProjectileRange_ApplyModifiers;
Expand Down
Loading