Skip to content
Open
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 @@ -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.
16 changes: 16 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unreasonable to reinvent a filter here.

```
Binary file added docs/_static/images/IvanBombDetonate.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/Ext/Techno/Hooks.Misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
37 changes: 37 additions & 0 deletions src/Ext/Techno/Hooks.ReceiveDamage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,40 @@ DEFINE_HOOK(0x737E6E, UnitClass_ReceiveDamage_SkipExplode, 0xA)
R->EAX(pThis->GetHeight());
return ContinueCheck;
}

DEFINE_HOOK(0x701DFF, TechnoClass_ReceiveDamage_IvanBombDetonate, 0x7)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook is redundant.
You should process this in WarheadTypeExt::DetonateOnOneUnit.

{
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))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this sanity check is not nesseary

{
if(auto pWHExt = WarheadTypeExt::TryFetch(pWH))
{
if(!pWHExt->IvanBomb_Detonate)
return 0;

bool CanAffects = pWHExt->IvanBomb_Detonate_AffectsType.Contains(pThis->GetTechnoType())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be better to check empty first

|| pWHExt->IvanBomb_Detonate_AffectsType.empty();

if(pWHExt->IvanBomb_Detonate_InvokerOnly)
{
if(pBomb->Owner == pSource && CanAffects)
pBomb->Detonate();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invoking a damage in another damage process is dangerous.
IMO resetting the timer would be better.

}
else
{
if(CanAffects)
pBomb->Detonate();
}
}
}
}
return 0;
}
8 changes: 8 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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)
;
}

Expand Down
8 changes: 8 additions & 0 deletions src/Ext/WarheadType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ class WarheadTypeExt final : public AbstractTypeExt
Valueable<double> Damage_Deployed;
Nullable<bool> PreventScatter;

Valueable<bool> IvanBomb_Detonate;
Valueable<bool> IvanBomb_Detonate_InvokerOnly;
ValueableVector<TechnoTypeClass*> IvanBomb_Detonate_AffectsType;

double Crit_RandomBuffer;
double Crit_CurrentChance;
bool Crit_Active;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/Ext/WarheadType/Detonate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -930,4 +930,4 @@ void WarheadTypeExt::ExtData::ApplyAmmoModifier(TechnoClass* pTarget)

newCurrentAmmo = newCurrentAmmo < 0 ? 0 : newCurrentAmmo;
pTarget->Ammo = newCurrentAmmo > maxAmmo ? maxAmmo : newCurrentAmmo;
}
}
1 change: 1 addition & 0 deletions src/Ext/WeaponType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class WeaponTypeExt final : public AbstractTypeExt
bool SkipWeaponPicking;

Nullable<bool> CylinderRangefinding;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this ?


WeaponTypeExt(WeaponTypeClass* OwnerObject) : AbstractTypeExt(OwnerObject)
, DiskLaser_Radius { DiskLaserClass::Radius }
Expand Down