Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
efe8f20
AE modification
Coronia Apr 23, 2025
14f124f
ExtraWarheads AE
Coronia Apr 23, 2025
8552728
AE FeedbackWeapon
Coronia Apr 23, 2025
7355c24
RealLaunch
Coronia Apr 23, 2025
cf714e5
AuxWeapon
Coronia Apr 27, 2025
3a25891
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Apr 27, 2025
d4c7e3c
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Apr 28, 2025
3b177b9
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia May 6, 2025
a03c1f2
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia May 7, 2025
2db1169
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Jun 17, 2025
05243c2
PreventNegativeDamage
Coronia Jun 17, 2025
a7db33e
fix
Coronia Jun 23, 2025
8c1d9d1
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Jun 23, 2025
69146c7
KillWeapon
Coronia Jun 26, 2025
eff2cf0
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Jun 26, 2025
1830fd0
NegativeDamage.Multiplier
Coronia Jun 27, 2025
84066ee
tweak
Coronia Jun 29, 2025
c13fe17
tweak
Coronia Nov 22, 2025
4469185
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Nov 22, 2025
4c6a04e
fix
Coronia Nov 22, 2025
96bab12
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Nov 23, 2025
654321e
Merge remote-tracking branch 'upstream/develop' into origin/ae-attach…
Coronia Nov 26, 2025
b0b1360
tweak
Coronia Nov 26, 2025
26b45ff
Merge branch 'develop' into origin/ae-attach-discard-by-health-3
Coronia Jan 12, 2026
509239f
Merge branch 'develop' into origin/ae-attach-discard-by-health-3
Coronia Jan 12, 2026
f437fe8
fix RevengeWeapon
Coronia Jan 12, 2026
2f9ba7f
Merge branch 'develop' into origin/ae-attach-discard-by-health-3
Coronia Jan 14, 2026
862194c
fix
Coronia Jan 14, 2026
c9399df
refractor codes
Coronia Jan 14, 2026
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
3 changes: 3 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,9 @@ This page lists all the individual contributions to the project by their author.
- Toggle off laser trail and shake effects
- OpenTopped range bonus and damage multiplier customization for passengers
- AutoDeath upon ownership change
- Attached effect attach/discard by health
- Attached effect with `ExtraWarheads`, `KillWeapon` and `FeedbackWeapon`
- `AuxWeapon`
- **NaotoYuuki** - Vertical & meteor trajectory projectile prototypes
- **handama** - AI script action to `16005 Jump Back To Previous Script`
- **TaranDahl (航味麻酱)**:
Expand Down
120 changes: 103 additions & 17 deletions docs/New-or-Enhanced-Logics.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ New:
- Weapons now support `AttackFriendlies` and `AttackCursorOnFriendlies` (by FlyStar)
- Attack non-threatening structures extensions (by FlyStar)
- Customize size for mind controlled unit (by NetsuNegi)
- Attached effect attach/discard by health (by Ollerus)
- Attached effect with `ExtraWarheads`, `KillWeapon` and `FeedbackWeapon` (by Ollerus)
- [AuxWeapon](New-or-Enhanced-Logics.md#auxiliary-weapon) (by Ollerus)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
130 changes: 130 additions & 0 deletions src/Ext/Bullet/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include <Ext/Anim/Body.h>
#include <Ext/RadSite/Body.h>
#include <Ext/Techno/Body.h>
#include <Ext/WeaponType/Body.h>
#include <Ext/WarheadType/Body.h>
#include <Ext/Cell/Body.h>
#include <Ext/EBolt/Body.h>
#include <New/Entity/LaserTrailClass.h>
Expand Down Expand Up @@ -127,6 +129,134 @@ void BulletExt::ExtData::InitializeLaserTrails()
this->LaserTrails.emplace_back(std::make_unique<LaserTrailClass>(LaserTrailTypeClass::Array[idxTrail].get(), pOwner));
}

void BulletExt::RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult, TechnoClass* pFirer)
{
int damage = pWeapon->Damage;

if (applyFirepowerMult)
damage = static_cast<int>(damage * TechnoExt::GetCurrentFirepowerMultiplier(pSource));

if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pTarget, pSource,
damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright))
{
if (!pFirer)
pFirer = pSource;

BulletExt::SimulatedFiringUnlimbo(pBullet, pSource->Owner, pWeapon, pFirer->Location, true);
BulletExt::SimulatedFiringEffects(pBullet, pSource->Owner, nullptr, false, true);
}
}

bool BulletExt::IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting, bool useWarheadTargeting)
{
auto const pWH = pWeapon->Warhead;

if (useWeaponTargeting)
{
auto const pType = pTarget->GetTechnoType();

if (!pType->LegalTarget || GeneralUtils::GetWarheadVersusArmor(pWH, pType->Armor) == 0.0)
return false;

auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);

if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner)
|| !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true)
|| !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)
|| !pWeaponExt->IsHealthInThreshold(pTarget))
{
return false;
}

if (!pWeaponExt->HasRequiredAttachedEffects(pTarget, pSource))
return false;
}
else if (useWarheadTargeting)
{
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);

if (!pWHExt->CanTargetHouse(pOwner, pTarget) || !pWHExt->IsHealthInThreshold(pTarget))
return false;
}

return true;
}

void BulletExt::ExtData::ApplyExtraWarheads(const std::vector<WarheadTypeClass*>& exWH, const std::vector<int>& exWHOverrides, const std::vector<double>& exWHChances, const std::vector<bool>& exWHFull, const std::vector<bool>& exWHOwner, const std::vector<bool>& exWHMult, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker)
{
auto const pThis = this->OwnerObject();
const int defaultDamage = pThis->WeaponType ? pThis->WeaponType->Damage : 0;
auto& random = ScenarioClass::Instance->Random;

for (size_t i = 0; i < exWH.size(); i++)
{
auto const pWH = exWH[i];
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
auto const pTarget = abstract_cast<TechnoClass*>(pThis->Target);

if (pTarget && !pWHExt->IsHealthInThreshold(pTarget))
continue;

int damage = defaultDamage;
size_t size = exWHOverrides.size();

if (size > i)
damage = exWHOverrides[i];
else if (size > 0)
damage = exWHOverrides[size - 1];

bool detonate = true;
size = exWHChances.size();

if (size > i)
detonate = exWHChances[i] >= random.RandomDouble();
else if (size > 0)
detonate = exWHChances[size - 1] >= random.RandomDouble();

if (!detonate)
continue;

bool useInvoker = false;

if (pInvoker)
{
size = exWHOwner.size();

if (size > i)
useInvoker = exWHOwner[i];
else if (size > 0)
useInvoker = exWHOwner[size - 1];
}

auto const pFirer = useInvoker ? pInvoker : pThis->Owner;
auto const pHouse = useInvoker ? pInvoker->Owner : pOwner;

bool fireMult = false;
size = exWHMult.size();

if (size > i)
fireMult = exWHMult[i];
else if (size > 0)
fireMult = exWHMult[size - 1];

if (fireMult)
damage = static_cast<int>(damage * TechnoExt::GetCurrentFirepowerMultiplier(pFirer));

bool isFull = true;
size = exWHFull.size();

if (size > i)
isFull = exWHFull[i];
else if (size > 0)
isFull = exWHFull[size - 1];

if (isFull)
WarheadTypeExt::DetonateAt(pWH, coords, pFirer, damage, pHouse, pThis->Target);
else
pWHExt->DamageAreaWithTarget(coords, damage, pFirer, pWH, true, pHouse, pTarget);
}
}

static inline int SetBuildingFireAnimZAdjust(BuildingClass* pBuilding, int animY)
{
if (pBuilding->GetOccupantCount() > 0)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Bullet/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BulletExt
void InterceptBullet(TechnoClass* pSource, BulletClass* pInterceptor);
void ApplyRadiationToCell(CellStruct cell, int spread, int radLevel);
void InitializeLaserTrails();
void ApplyExtraWarheads(const std::vector<WarheadTypeClass*>& exWH, const std::vector<int>& exWHOverrides, const std::vector<double>& exWHChances, const std::vector<bool>& exWHFull, const std::vector<bool>& exWHOwner, const std::vector<bool>& exWHMult, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker = nullptr);

private:
template <typename T>
Expand All @@ -72,6 +73,8 @@ class BulletExt

static void Detonate(const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse, AbstractClass* pTarget, bool isBright, WeaponTypeClass* pWeapon, WarheadTypeClass* pWarhead);
static void ApplyArcingFix(BulletClass* pThis, const CoordStruct& sourceCoords, const CoordStruct& targetCoords, BulletVelocity& velocity);
static void RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult = true, TechnoClass* pFirer = nullptr);
static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting = true, bool useWarheadTargeting = true);

static void SimulatedFiringUnlimbo(BulletClass* pBullet, HouseClass* pHouse, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords, bool randomVelocity);
static void SimulatedFiringEffects(BulletClass* pBullet, HouseClass* pHouse, ObjectClass* pAttach, bool firingEffect, bool visualEffect);
Expand Down
146 changes: 38 additions & 108 deletions src/Ext/Bullet/Hooks.DetonateLogics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
#include <Ext/Scenario/Body.h>
#include <Utilities/Helpers.Alex.h>

namespace ExtraWarheadsTemp
{
bool AttachedExtraWarheads = false;
}

DEFINE_HOOK(0x4690D4, BulletClass_Logics_NewChecks, 0x6)
{
enum { SkipShaking = 0x469130, GoToExtras = 0x469AA4 };
Expand Down Expand Up @@ -353,90 +358,56 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5)
GET(BulletClass*, pThis, ESI);
GET_BASE(CoordStruct const* const, coords, 0x8);

auto const pBulletExt = BulletExt::ExtMap.Find(pThis);
auto const pTechno = pThis->Owner;
auto const pOwner = pTechno ? pTechno->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse;
auto const pOwner = pTechno ? pTechno->Owner : pBulletExt->FirerHouse;
auto const pWH = pThis->WH;
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
pWHExt->InDamageArea = true;

// Extra warheads
if (auto const pWeapon = pThis->WeaponType)
if (auto const pWeaponExt = WeaponTypeExt::ExtMap.TryFind(pThis->WeaponType))
{
auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);
auto const& extraWarheads = pWeaponExt->ExtraWarheads;
auto const& damageOverrides = pWeaponExt->ExtraWarheads_DamageOverrides;
auto const& detonationChances = pWeaponExt->ExtraWarheads_DetonationChances;
auto const& fullDetonation = pWeaponExt->ExtraWarheads_FullDetonation;
const int defaultDamage = pWeapon->Damage;
auto& random = ScenarioClass::Instance->Random;

for (size_t i = 0; i < extraWarheads.size(); i++)
if (pWeaponExt->ExtraWarheads.size() > 0)
{
auto const pWH = extraWarheads[i];
auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
auto const pTarget = abstract_cast<TechnoClass*>(pThis->Target);

if (pTarget && !pWHExt->IsHealthInThreshold(pTarget))
continue;

int damage = defaultDamage;
size_t size = damageOverrides.size();

if (size > i)
damage = damageOverrides[i];
else if (size > 0)
damage = damageOverrides[size - 1];

size = detonationChances.size();
bool detonate = true;

if (size > i)
detonate = detonationChances[i] >= random.RandomDouble();
else if (size > 0)
detonate = detonationChances[size - 1] >= random.RandomDouble();
std::vector<bool> vec;

size = fullDetonation.size();
bool isFull = true;

if (size > i)
isFull = fullDetonation[i];
else if (size > 0)
isFull = fullDetonation[size - 1];

if (!detonate)
continue;

if (isFull)
WarheadTypeExt::DetonateAt(pWH, *coords, pTechno, damage, pOwner, pThis->Target);
else
pWHExt->DamageAreaWithTarget(*coords, damage, pTechno, pWH, true, pOwner, pTarget);
pBulletExt->ApplyExtraWarheads(pWeaponExt->ExtraWarheads, pWeaponExt->ExtraWarheads_DamageOverrides, pWeaponExt->ExtraWarheads_DetonationChances,
pWeaponExt->ExtraWarheads_FullDetonation, vec, pWeaponExt->ExtraWarheads_ApplyFirepowerMult, *coords, pOwner);
}
}

if (!pTechno)
return 0;

auto const pTechnoExt = TechnoExt::ExtMap.Find(pTechno);

// Return to sender
if (pTechno)
if (pTechnoExt->AE.HasExtraWarheads && !ExtraWarheadsTemp::AttachedExtraWarheads)
{
auto const pTypeExt = BulletTypeExt::ExtMap.Find(pThis->Type);
ExtraWarheadsTemp::AttachedExtraWarheads = true;

if (auto const pWeapon = pTypeExt->ReturnWeapon)
for (auto const& pAE : pTechnoExt->AttachedEffects)
{
int damage = pWeapon->Damage;
auto const pType = pAE->GetType();

if (pTypeExt->ReturnWeapon_ApplyFirepowerMult)
damage = static_cast<int>(damage * TechnoExt::GetCurrentFirepowerMultiplier(pTechno));

if (auto const pBullet = pWeapon->Projectile->CreateBullet(pTechno, pTechno,
damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright))
if (pType->ExtraWarheads.size() > 0)
{
BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, pThis->Location, false);
BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, false, true);
pBulletExt->ApplyExtraWarheads(pType->ExtraWarheads, pType->ExtraWarheads_DamageOverrides, pType->ExtraWarheads_DetonationChances,
pType->ExtraWarheads_FullDetonation, pType->ExtraWarheads_UseInvokerAsOwner, pType->ExtraWarheads_ApplyFirepowerMult, *coords, pOwner, pAE->GetInvoker());
}
}

ExtraWarheadsTemp::AttachedExtraWarheads = false;
}

// Unlimbo Detonate
const auto pWH = pThis->WH;
const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWH);
pWHExt->InDamageArea = true;
// Return to sender
auto const pBulletTypeExt = BulletTypeExt::ExtMap.Find(pThis->Type);

if (auto const pWeapon = pBulletTypeExt->ReturnWeapon)
BulletExt::RealLaunch(pWeapon, pTechno, pTechno, pBulletTypeExt->ReturnWeapon_ApplyFirepowerMult);

if (pTechno && pTechno->InLimbo && !pWH->Parasite && pWHExt->UnlimboDetonate)
// Unlimbo Detonate
if (pTechno->InLimbo && !pWH->Parasite && pWHExt->UnlimboDetonate)
{
CoordStruct location = *coords;
const auto pTarget = pThis->Target;
Expand Down Expand Up @@ -481,8 +452,6 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5)
--Unsorted::ScenarioInit;
}

const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno);

if (success)
{
if (isInAir)
Expand Down Expand Up @@ -519,7 +488,7 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5)
}

pTechno->SetLocation(location);
pTechno->ReceiveDamage(&pTechno->Health, 0, RulesClass::Instance->C4Warhead, nullptr, true, false, pTechno->Owner);
pTechno->ReceiveDamage(&pTechno->Health, 0, RulesClass::Instance->C4Warhead, nullptr, true, false, pOwner);
}
}

Expand All @@ -528,42 +497,6 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5)

#pragma region Airburst

static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting)
{
auto const pWH = pWeapon->Warhead;

if (useWeaponTargeting)
{
auto const pType = pTarget->GetTechnoType();

if (!pType->LegalTarget || GeneralUtils::GetWarheadVersusArmor(pWH, pTarget, pType) == 0.0)
return false;

auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);

if (pWeaponExt->SkipWeaponPicking)
return true;

if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner)
|| !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true)
|| !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)
|| !pWeaponExt->IsHealthInThreshold(pTarget))
{
return false;
}

if (!pWeaponExt->HasRequiredAttachedEffects(pTarget, pSource))
return false;
}
else
{
if (!WarheadTypeExt::ExtMap.Find(pWH)->CanTargetHouse(pOwner, pTarget))
return false;
}

return true;
}

// Disable Ares' Airburst implementation.
DEFINE_PATCH(0x469EBA, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90);

Expand Down Expand Up @@ -681,11 +614,8 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
if (pTechno->IsInPlayfield && pTechno->IsOnMap && pTechno->IsAlive && pTechno->Health > 0 && !pTechno->InLimbo
&& (retargetSelf || pTechno != pSource))
{
if ((isAA || !pTechno->IsInAir())
&& IsAllowedSplitsTarget(pSource, pOwner, pWeapon, pTechno, useWeaponTargeting))
{
if ((isAA || !pTechno->IsInAir()) && BulletExt::IsAllowedSplitsTarget(pSource, pOwner, pWeapon, pTechno, useWeaponTargeting))
targets.AddItem(pTechno);
}
}
}

Expand Down
Loading