diff --git a/CREDITS.md b/CREDITS.md index 9ac61c0ff4..1f58eda3d6 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -943,3 +943,4 @@ This page lists all the individual contributions to the project by their author. - **Nuke** - Reload speed adjustment on promotion - **frg2089 (舰队的偶像-岛风酱!)**: - Fix `Slaved.OwnerWhenMasterKilled` not being respected when the master is sold or self-destructed +- **dh381-1** - Custom weapons to detonate your own Ivan bombs. diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 9c25519340..1ea623b9a7 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -3682,3 +3682,19 @@ CanTargetVeterancy=all ; List of Affected Veterancy Enumeration (none|rooki ```{note} `CanTarget` explicitly requires either `all` or `empty` to be listed for the weapon to be able to fire at cells containing no TechnoTypes. ``` + +### Manually detonate Ivan bomb + +![image](_static/images/IvanBombDetonate.gif) + +- Now you can detonate planted Ivan bombs using custom werhead. The bomb attached to the targeted unit will explode immediately, provided that it was planted by the attacker. +- Use `IvanBomb.Detonate.InvokerOnly` to configure whether the warhead can detonate Ivan bombs from other sources. +- Use `IvanBomb.Detonate.AffectsType` to configure which targets' Ivan bombs can be detonated by warhead, use empty for all types. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +IvanBomb.Detonate=true ; boolean +IvanBomb.Detonate.InvokerOnly=false ; boolean +IvanBomb.Detonate.AffectsType=HTNK,E1 ; List of Registration Name, use empty list for all types +``` diff --git a/docs/_static/images/IvanBombDetonate.gif b/docs/_static/images/IvanBombDetonate.gif new file mode 100644 index 0000000000..837a7c595f Binary files /dev/null and b/docs/_static/images/IvanBombDetonate.gif differ diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index c95425b410..7c18d8b430 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -56,8 +56,8 @@ DEFINE_HOOK(0x6B0B9C, SlaveManagerClass_Killed_DecideOwner, 0x6) { // 0x6B0BA4: master sold / self-destroyed (killer == 0). // Replicate the vanilla fallback: give the slave to the neutral house, - // otherwise destroy it. Avoid `return 0` here so we bypass the vanilla - // `mov eax,[esp+arg_4]; test eax,eax` sequence and branch explicitly. + // otherwise destroy it. Avoid `return 0` here so we bypass the vanilla + // `mov eax,[esp+arg_4]; test eax,eax` sequence and branch explicitly. if (const auto pNeutral = HouseClass::FindNeutral()) { R->EAX(pNeutral); diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index c1b3e4ac38..33571a6fd4 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -566,3 +566,40 @@ DEFINE_HOOK(0x737E6E, UnitClass_ReceiveDamage_SkipExplode, 0xA) R->EAX(pThis->GetHeight()); return ContinueCheck; } + +DEFINE_HOOK(0x701DFF, TechnoClass_ReceiveDamage_IvanBombDetonate, 0x7) +{ + GET(TechnoClass*, pThis, ESI); + GET_STACK(TechnoClass*, pSource, STACK_OFFSET(0xC4, 0x10)); + GET_STACK(WarheadTypeClass*, pWH, STACK_OFFSET(0xC4, 0xC)); + + if (!pSource) + return 0; + + if(auto pBomb = pThis->AttachedBomb) + { + if(auto pSourceExt = TechnoExt::Fetch(pSource)) + { + if(auto pWHExt = WarheadTypeExt::TryFetch(pWH)) + { + if(!pWHExt->IvanBomb_Detonate) + return 0; + + bool CanAffects = pWHExt->IvanBomb_Detonate_AffectsType.Contains(pThis->GetTechnoType()) + || pWHExt->IvanBomb_Detonate_AffectsType.empty(); + + if(pWHExt->IvanBomb_Detonate_InvokerOnly) + { + if(pBomb->Owner == pSource && CanAffects) + pBomb->Detonate(); + } + else + { + if(CanAffects) + pBomb->Detonate(); + } + } + } + } + return 0; +} \ No newline at end of file diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index adf07d1090..5bc3a64f60 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -451,6 +451,10 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->Ammo.Read(exINI, pSection, "Ammo"); + this->IvanBomb_Detonate.Read(exINI, pSection, "IvanBomb.Detonate"); + this->IvanBomb_Detonate_InvokerOnly.Read(exINI, pSection, "IvanBomb.Detonate.InvokerOnly"); + this->IvanBomb_Detonate_AffectsType.Read(exINI, pSection, "IvanBomb.Detonate.AffectsType"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -822,6 +826,10 @@ void WarheadTypeExt::Serialize(T& Stm) .Process(this->DamageAreaTarget) .Process(this->Ammo) + + .Process(this->IvanBomb_Detonate) + .Process(this->IvanBomb_Detonate_InvokerOnly) + .Process(this->IvanBomb_Detonate_AffectsType) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 7ee03e1291..cd17cd421f 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -282,6 +282,10 @@ class WarheadTypeExt final : public AbstractTypeExt Valueable Damage_Deployed; Nullable PreventScatter; + Valueable IvanBomb_Detonate; + Valueable IvanBomb_Detonate_InvokerOnly; + ValueableVector IvanBomb_Detonate_AffectsType; + double Crit_RandomBuffer; double Crit_CurrentChance; bool Crit_Active; @@ -573,6 +577,10 @@ class WarheadTypeExt final : public AbstractTypeExt , PreventOccupantEscape { false } , Ammo { 0 } + + , IvanBomb_Detonate { false } + , IvanBomb_Detonate_InvokerOnly { true } + , IvanBomb_Detonate_AffectsType {} { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index d8bb3d432f..dd5669feed 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -930,4 +930,4 @@ void WarheadTypeExt::ExtData::ApplyAmmoModifier(TechnoClass* pTarget) newCurrentAmmo = newCurrentAmmo < 0 ? 0 : newCurrentAmmo; pTarget->Ammo = newCurrentAmmo > maxAmmo ? maxAmmo : newCurrentAmmo; -} +} \ No newline at end of file diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 4078c8ca93..ad8b08ff45 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -118,6 +118,7 @@ class WeaponTypeExt final : public AbstractTypeExt bool SkipWeaponPicking; Nullable CylinderRangefinding; + WeaponTypeExt(WeaponTypeClass* OwnerObject) : AbstractTypeExt(OwnerObject) , DiskLaser_Radius { DiskLaserClass::Radius }