Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ This page lists all the individual contributions to the project by their author.
- Fix an issue that jumpjet infantries' shadow is always drawn even if they are cloaked
- Fix an issue that technos head to building's dock even they are not going to dock
- Fix an issue that the jumpjet vehicles cannot stop correctly after going berserk
- Attack and damage technos underground
- **solar-III (凤九歌)**
- Target scanning delay customization (documentation)
- Skip target scanning function calling for unarmed technos (documentation)
Expand Down
31 changes: 31 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,21 @@ OnlyUseLandSequences=false ; boolean

## Projectiles

### Attack technos underground

- Now, you can enable projectiles to attack technos underground.
- To actually damage the technos, you need [AffectsUnderground](#damage-technos-underground).

In `rulesmd.ini`:
```ini
[SOMEPROJECTILE] ; Projectile
AU=false ; boolean
```

```{note}
Only vanilla projectiles with `Inviso=yes` set or [Phobos projectiles](#projectile-trajectories) `Straight` with `Trajectory.Straight.SubjectToGround=false` enabled and `Bombard` with `Trajectory.Bombard.SubjectToGround=false` enabled can go beneath the ground. Otherwise, the projectile will be forced to detonate upon hitting the ground.
```

### Parabombs

- Restored feature from Red Alert 1 (also partially implemented in Ares but undocumented, if used together Phobos' version takes priority) that allows projectiles to be parachuted down to ground if fired by an aerial unit.
Expand Down Expand Up @@ -2271,6 +2286,22 @@ DamageTargetHealthMultiplier=0.0 ; floating point value
`DamageAlliesMultiplier` won't affect your own units like `AffectsAllies` did.
```

### Damage technos underground

- Now you can make the warhead damage technos underground!
- To allow weapons to target underground technos, you need [AU](#attack-technos-underground).
- Notice that if the projectile detonates underground, its animation effect may look strange.
- You can use `[SOMEWARHEAD] -> PlayAnimUnderground=false` to prevent the warhead animation from playing when the projectile detonates underground.
- You can also use `[SOMEWARHEAD] -> PlayAnimAboveSurface=true` to make the warhead animation play on the ground directly above when the projectile detonates underground.

In `rulesmd.ini`:
```ini
[SOMEWARHEAD] ; WarheadType
AffectsUnderground=false ; boolean
PlayAnimUnderground=true ; boolean
PlayAnimAboveSurface=false ; boolean
```

### Detonate Warhead on all objects on map

- Setting `DetonateOnAllMapObjects` to true allows a Warhead that is detonated by a projectile (for an example, this excludes things like animation `Warhead` and Ares' GenericWarhead superweapon but includes `Crit.Warhead` and animation `Weapon`) and consequently any `AirburstWeapon/ShrapnelWeapon` that may follow to detonate on each object currently alive and existing on the map regardless of its actual target, with optional filters. Note that this is done immediately prior Warhead detonation so after `PreImpactAnim` *(Ares feature)* has been displayed.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ New:
- [Customize the chained damage of the wall](Fixed-or-Improved-Logics.md#customize-the-chained-damage-of-the-wall) (by TaranDahl)
- Allow the aircraft to enter area guard mission and not crash immediately without any airport (by CrimRecya)
- [Unlimbo Detonate warhead](New-or-Enhanced-Logics.md#unlimbo-detonate-warhead) (by FlyStar)
- Attack and damage technos underground (by TaranDahl)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
77 changes: 76 additions & 1 deletion src/Ext/Bullet/Hooks.DetonateLogics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,15 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8)
if (pAnimType)
{
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH);
const int cellHeight = MapClass::Instance.GetCellFloorHeight(*coords);
auto const newCrds = pWHExt->PlayAnimAboveSurface ? CoordStruct{ coords->X, coords->Y, Math::max(cellHeight, coords->Z) } : *coords;

if (cellHeight > newCrds.Z && !pWHExt->PlayAnimUnderground)
{
R->EAX(createdAnim);
return SkipGameCode;
}

auto const pOwner = pThis->Owner;
const bool splashed = pWHExt->Splashed;
const int creationInterval = splashed ? pWHExt->SplashList_CreationInterval : pWHExt->AnimList_CreationInterval;
Expand Down Expand Up @@ -311,7 +320,7 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8)
if (!pType)
continue;

auto animCoords = *coords;
auto animCoords = newCrds;

if (allowScatter)
{
Expand Down Expand Up @@ -775,3 +784,69 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
}

#pragma endregion

#pragma region AffectsUnderground

// In vanilla, only ground is allowed, and Ares added air and top.
// But it seems that underground and surface is also working fine?
DEFINE_HOOK(0x469453, BulletClass_Logics_TemporalUnderGround, 0x6)
{
enum { NotOK = 0x469AA4, OK = 0x469475 };

GET(FootClass*, pTarget, EAX);

if (pTarget->InWhichLayer() != Layer::None)
return OK;

return NotOK;
}

DEFINE_HOOK(0x4899DA, MapClass_DamageArea_DamageUnderGround, 0x7)
{
GET_STACK(const bool, isNullified, STACK_OFFSET(0xE0, -0xC9));
GET_STACK(int, damage, STACK_OFFSET(0xE0, -0xBC));
GET_STACK(CoordStruct*, pCrd, STACK_OFFSET(0xE0, -0xB8));
GET_BASE(WarheadTypeClass*, pWH, 0xC);
GET_BASE(TechnoClass*, pSrcTechno, 0x8);
GET_BASE(HouseClass*, pSrcHouse, 0x14);
GET_STACK(bool, hitted, STACK_OFFSET(0xE0, -0xC1)); // bHitted = true

if (isNullified)
return 0;

auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);

if (!pWHExt || !pWHExt->AffectsUnderground)
return 0;

// bool cylinder = pWHExt->CellSpread_Cylinder;
const float spread = pWH->CellSpread;

for (auto const& pTechno : ScenarioExt::Global()->UndergroundTracker)
{
if (pTechno->InWhichLayer() == Layer::Underground // Layer.
&& pTechno->IsAlive && !pTechno->IsIronCurtained()
&& !pTechno->IsOnMap // Underground is not on map.
&& !pTechno->InLimbo)
{
double dist = 0.0;
auto const technoCoords = pTechno->GetCoords();

//if (cylinder)
// dist = CoordStruct{ technoCoords.X - pCrd->X, technoCoords.Y - pCrd->Y, 0 }.Magnitude();
//else
dist = technoCoords.DistanceFrom(*pCrd);

if (dist <= spread * Unsorted::LeptonsPerCell)
{
pTechno->ReceiveDamage(&damage, (int)dist, pWH, pSrcTechno, false, false, pSrcHouse);
hitted = true;
}
}
}

R->Stack8(STACK_OFFSET(0xE0, -0xC1), true);
return 0;
}

#pragma endregion
2 changes: 2 additions & 0 deletions src/Ext/BulletType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ void BulletTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->Parachuted_FallRate.Read(exINI, pSection, "Parachuted.FallRate");
this->Parachuted_MaxFallRate.Read(exINI, pSection, "Parachuted.MaxFallRate");
this->BombParachute.Read(exINI, pSection, "BombParachute");
this->AU.Read(exINI, pSection, "AU");

// Ares 0.7
this->BallisticScatter_Min.Read(exINI, pSection, "BallisticScatter.Min");
Expand Down Expand Up @@ -170,6 +171,7 @@ void BulletTypeExt::ExtData::Serialize(T& Stm)
.Process(this->Parachuted_FallRate)
.Process(this->Parachuted_MaxFallRate)
.Process(this->BombParachute)
.Process(this->AU)

.Process(this->TrajectoryType) // just keep this shit at last
;
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/BulletType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class BulletTypeExt
Nullable<int> Parachuted_MaxFallRate;
Nullable<AnimTypeClass*> BombParachute;

Valueable<bool> AU;

// Ares 0.7
Nullable<Leptons> BallisticScatter_Min;
Nullable<Leptons> BallisticScatter_Max;
Expand Down Expand Up @@ -122,6 +124,7 @@ class BulletTypeExt
, Parachuted_FallRate { 1 }
, Parachuted_MaxFallRate {}
, BombParachute {}
, AU { false }
{ }

virtual ~ExtData() = default;
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Scenario/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ void ScenarioExt::ExtData::Serialize(T& Stm)
.Process(this->DefaultLS800BkgdPal)
.Process(this->MasterDetonationBullet)
.Process(this->LimboLaunchers)
.Process(this->UndergroundTracker)
.Process(this->SpecialTracker)
;
}

Expand Down
5 changes: 5 additions & 0 deletions src/Ext/Scenario/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class ScenarioExt
BulletClass* MasterDetonationBullet; // Used to do warhead/weapon detonations on spot without having to create new BulletClass instance every time.
std::vector<TechnoExt::ExtData*> LimboLaunchers;

DynamicVectorClass<TechnoClass*> UndergroundTracker;
DynamicVectorClass<TechnoClass*> SpecialTracker;

ExtData(ScenarioClass* OwnerObject) : Extension<ScenarioClass>(OwnerObject)
, ShowBriefing { false }
, BriefingTheme { -1 }
Expand All @@ -64,6 +67,8 @@ class ScenarioExt
, DefaultLS800BkgdPal {}
, MasterDetonationBullet {}
, LimboLaunchers {}
, UndergroundTracker {}
, SpecialTracker {}
{ }

void SetVariableToByID(bool bIsGlobal, int nIndex, char bState);
Expand Down
8 changes: 8 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ TechnoExt::ExtData::~ExtData()
}

this->ElectricBolts.clear();

if (this->UndergroundTracked)
ScenarioExt::Global()->UndergroundTracker.Remove(pThis);

if (this->SpecialTracked)
ScenarioExt::Global()->SpecialTracker.Remove(pThis);
}

bool TechnoExt::IsActiveIgnoreEMP(TechnoClass* pThis)
Expand Down Expand Up @@ -931,6 +937,8 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->TintIntensityAllies)
.Process(this->TintIntensityEnemies)
.Process(this->AttackMoveFollowerTempCount)
.Process(this->UndergroundTracked)
.Process(this->SpecialTracked)
;
}

Expand Down
5 changes: 5 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ class TechnoExt

int AttackMoveFollowerTempCount;

bool UndergroundTracked;
bool SpecialTracked;

ExtData(TechnoClass* OwnerObject) : Extension<TechnoClass>(OwnerObject)
, TypeExtData { nullptr }
, Shield {}
Expand Down Expand Up @@ -156,6 +159,8 @@ class TechnoExt
, TintIntensityAllies { 0 }
, TintIntensityEnemies { 0 }
, AttackMoveFollowerTempCount { 0 }
, UndergroundTracked { false }
, SpecialTracked { false }
{ }

void OnEarlyUpdate();
Expand Down
60 changes: 52 additions & 8 deletions src/Ext/Techno/Hooks.Firing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,14 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8)
{
if (pShield->IsActive())
{
const auto secondary = pThis->GetWeapon(1)->WeaponType;
const bool secondaryIsAA = pTargetTechno && pTargetTechno->IsInAir() && secondary && secondary->Projectile->AA;

if (secondary && (allowFallback || (allowAAFallback && secondaryIsAA) || TechnoExt::CanFireNoAmmoWeapon(pThis, 1)))
const auto pSecondary = pThis->GetWeapon(1)->WeaponType;

if (pSecondary
&& (allowFallback
|| (pTargetTechno
&& ((allowAAFallback && pTargetTechno->IsInAir() && pSecondary->Projectile->AA)
|| (pTargetTechno->InWhichLayer() == Layer::Underground && BulletTypeExt::ExtMap.Find(pSecondary->Projectile)->AU)))
|| TechnoExt::CanFireNoAmmoWeapon(pThis, 1)))
{
if (!pShield->CanBeTargeted(pThis->GetWeapon(0)->WeaponType))
return Secondary;
Expand All @@ -171,12 +175,22 @@ DEFINE_HOOK(0x6F37EB, TechnoClass_WhatWeaponShouldIUse_AntiAir, 0x6)
GET_STACK(WeaponTypeClass*, pWeapon, STACK_OFFSET(0x18, -0x4));
GET(WeaponTypeClass*, pSecWeapon, EAX);

if (!pWeapon->Projectile->AA && pSecWeapon->Projectile->AA)
if (const auto pTargetTechno = abstract_cast<TechnoClass*>(pTarget))
{
const auto pTargetTechno = abstract_cast<TechnoClass*>(pTarget);
const auto pPrimaryProj = pWeapon->Projectile;
const auto pSecondaryProj = pSecWeapon->Projectile;

if (pTargetTechno && pTargetTechno->IsInAir())
return Secondary;
if (!pPrimaryProj->AA && pSecondaryProj->AA)
{
if (pTargetTechno->IsInAir())
return Secondary;
}

if (BulletTypeExt::ExtMap.Find(pSecondaryProj)->AU && !BulletTypeExt::ExtMap.Find(pPrimaryProj)->AU)
{
if (pTargetTechno->InWhichLayer() == Layer::Underground)
return Secondary;
}
}

return Primary;
Expand Down Expand Up @@ -221,6 +235,11 @@ DEFINE_HOOK(0x6F3432, TechnoClass_WhatWeaponShouldIUse_Gattling, 0xA)
{
chosenWeaponIndex = evenWeaponIndex;
}
else if (pTargetTechno->InWhichLayer() == Layer::Underground)
{
if (BulletTypeExt::ExtMap.Find(pWeaponEven->Projectile)->AU && !BulletTypeExt::ExtMap.Find(pWeaponOdd->Projectile)->AU)
chosenWeaponIndex = evenWeaponIndex;
}
else
{
auto const landType = pTargetTechno->GetCell()->LandType;
Expand Down Expand Up @@ -430,6 +449,31 @@ DEFINE_HOOK(0x6FCBE6, TechnoClass_CanFire_BridgeAAFix, 0x6)
return 0;
}

DEFINE_HOOK(0x6FC749, TechnoClass_GetFireError_AntiUnderground, 0x5)
{
enum { Illegal = 0x6FC86A, GoOtherChecks = 0x6FC762 };

GET(const Layer, layer, EAX);
GET(WeaponTypeClass*, pWeapon, EDI);

switch (layer)
{
case Layer::Air:
case Layer::Top:
if (!pWeapon->Projectile->AA)
return Illegal;
break;
case Layer::Underground:
if (!BulletTypeExt::ExtMap.Find(pWeapon->Projectile)->AU)
return Illegal;
break;
default:
break;
}

return GoOtherChecks;
}

#pragma endregion

#pragma region TechnoClass_Fire
Expand Down
Loading