diff --git a/CREDITS.md b/CREDITS.md index b6177be989..77d0504ace 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -961,6 +961,9 @@ This page lists all the individual contributions to the project by their author. - Add `ClampToScreen` tag for `BannerType` to control whether banner position is clamped to the visible area - **obsidianus** - Automatic conversion based on health - **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. - **frg2089 (舰队的偶像-岛风酱!)** - Fix `Slaved.OwnerWhenMasterKilled` not being respected when the master is sold or self-destructed - **weiyongxuan** - Extended `CanTargetHouses` to allow targeting neutral houses - **Sovietianqi** - Customizable crew type per country diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f0eb3f32c7..96df11926e 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -3789,6 +3789,26 @@ OmniFire.TurnToTarget=false ; boolean OmniFire.TurnToTarget= ; boolean, default to [General] -> OmniFire.TurnToTarget ``` +### 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.PenetratesTransport` to configure whether the warhead can detonate Ivan bombs on a unit that is inside a transport. The bomb will explode after the unit is unloaded. +- Use `IvanBomb.Detonate.PenetratesGarrison` to configure whether the warhead can detonate Ivan bombs on a unit that is inside a building. The bomb will explode after the unit leaves the building. +- Use `IvanBomb.Detonate.AffectTypes` 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.PenetratesTransport=true ; boolean +IvanBomb.Detonate.PenetratesGarrison=true ; boolean +IvanBomb.Detonate.AffectTypes= ; List of TechnoTypes +``` + ### Radiation enhancements - In addition to allowing custom radiation types, several enhancements are also available to the default radiation type defined in `[Radiation]`, such as ability to set owner & invoker or deal damage against buildings. See [Custom Radiation Types](#custom-radiation-types) for more details. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 913d3d774a..a2df78bbf8 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -730,6 +730,8 @@ HideShakeEffects=false ; boolean - [Disable AlphaImage during Buildup](Fixed-or-Improved-Logics.md#disable-alphaimage-during-buildup) (by Noble_Fish) - [Reload speed adjustment on promotion](New-or-Enhanced-Logics.md#reload-speed-adjustment-on-promotion) (by Nuke) - Allowed `(Pre)ProductionAnim` animations to use `Powered` & `PoweredLight/Effect/Special` keys (by Noble_Fish) +- [Customize ivan bomb visibility](Fixed-or-Improved-Logics.md#customize-ivan-bomb-visibility) (by NetsuNegi) +- [Customize whether warhead can detonate ivan bomb](New-or-Enhanced-Logics.md#Manually-detonate-Ivan-bomb) (by dh381) #### Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) 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.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index cedae2835a..042bb0616c 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -576,4 +576,4 @@ DEFINE_HOOK(0x737E6E, UnitClass_ReceiveDamage_SkipExplode, 0xA) R->EAX(pThis->GetHeight()); return ContinueCheck; -} +} \ No newline at end of file diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index adf07d1090..e831b87a2e 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -451,6 +451,12 @@ 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_AffectTypes.Read(exINI, pSection, "IvanBomb.Detonate.AffectTypes"); + this->IvanBomb_Detonate_PenetratesTransport.Read(exINI, pSection, "IvanBomb.Detonate.PenetratesTransport"); + this->IvanBomb_Detonate_PenetratesGarrison.Read(exINI, pSection, "IvanBomb.Detonate.PenetratesGarrison"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -517,6 +523,7 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI) || this->PenetratesTransport_Level > 0 || this->Taunt || this->Ammo + || this->IvanBomb_Detonate ); char tempBuffer[32]; @@ -822,6 +829,12 @@ void WarheadTypeExt::Serialize(T& Stm) .Process(this->DamageAreaTarget) .Process(this->Ammo) + + .Process(this->IvanBomb_Detonate) + .Process(this->IvanBomb_Detonate_InvokerOnly) + .Process(this->IvanBomb_Detonate_AffectTypes) + .Process(this->IvanBomb_Detonate_PenetratesTransport) + .Process(this->IvanBomb_Detonate_PenetratesGarrison) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 7ee03e1291..883a1f0f02 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -282,6 +282,12 @@ class WarheadTypeExt final : public AbstractTypeExt Valueable Damage_Deployed; Nullable PreventScatter; + Valueable IvanBomb_Detonate; + Valueable IvanBomb_Detonate_InvokerOnly; + ValueableVector IvanBomb_Detonate_AffectTypes; + Valueable IvanBomb_Detonate_PenetratesTransport; + Valueable IvanBomb_Detonate_PenetratesGarrison; + double Crit_RandomBuffer; double Crit_CurrentChance; bool Crit_Active; @@ -573,6 +579,12 @@ class WarheadTypeExt final : public AbstractTypeExt , PreventOccupantEscape { false } , Ammo { 0 } + + , IvanBomb_Detonate { false } + , IvanBomb_Detonate_InvokerOnly { true } + , IvanBomb_Detonate_AffectTypes {} + , IvanBomb_Detonate_PenetratesTransport { false } + , IvanBomb_Detonate_PenetratesGarrison { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); @@ -615,6 +627,7 @@ class WarheadTypeExt final : public AbstractTypeExt void ApplyPenetratesTransport(TechnoClass* pTarget, TechnoClass* pInvoker, HouseClass* pInvokerHouse, const CoordStruct& coords, int damage, int distance); double GetCritChance(TechnoClass* pFirer) const; void ApplyAmmoModifier(TechnoClass* pTarget); + void IvanBombDetonate(TechnoClass* pOwner,TechnoClass* pTarget); public: class ExtContainer final : public Container diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index 36a1d95d5b..a40e9d6a90 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -235,7 +235,10 @@ void WarheadTypeExt::DetonateOnOneUnit(HouseClass* pHouse, TechnoClass* pTarget, if (this->Taunt && pOwner) pTarget->Override_Mission(Mission::Attack, pOwner, nullptr); - + + if(this->IvanBomb_Detonate) + this->IvanBombDetonate(pOwner,pTarget); + // This might change the target's armor type this->ApplyShieldModifiers(pTarget); @@ -928,3 +931,84 @@ void WarheadTypeExt::ExtData::ApplyAmmoModifier(TechnoClass* pTarget) newCurrentAmmo = newCurrentAmmo < 0 ? 0 : newCurrentAmmo; pTarget->Ammo = newCurrentAmmo > maxAmmo ? maxAmmo : newCurrentAmmo; } + +void WarheadTypeExt::IvanBombDetonate(TechnoClass* pOwner,TechnoClass* pTarget) +{ + if (!pOwner) + return; + + if (const auto pBomb = pTarget->AttachedBomb) + { + const bool CanAffects = this->IvanBomb_Detonate_AffectTypes.empty() + || this->IvanBomb_Detonate_AffectTypes.Contains(pTarget->GetTechnoType()); + + if (this->IvanBomb_Detonate_InvokerOnly) + { + if (pBomb->Owner == pOwner && CanAffects) + pBomb->DetonationFrame = Unsorted::CurrentFrame; + } + else + { + if (CanAffects) + pBomb->DetonationFrame = Unsorted::CurrentFrame; + } + } + + if (this->IvanBomb_Detonate_PenetratesTransport) + { + if (auto pCurPassenger = pTarget->Passengers.FirstPassenger) + { + while (pCurPassenger) + { + auto pNextPassenger = abstract_cast(pCurPassenger->NextObject); + const bool CanAffects = this->IvanBomb_Detonate_AffectTypes.empty() + || this->IvanBomb_Detonate_AffectTypes.Contains(pCurPassenger->GetTechnoType()); + + if (const auto pPassengerBomb = pCurPassenger->AttachedBomb) + { + if (this->IvanBomb_Detonate_InvokerOnly) + { + if (pPassengerBomb->Owner == pOwner && CanAffects) + pPassengerBomb->DetonationFrame = Unsorted::CurrentFrame; + } + else + { + if (CanAffects) + pPassengerBomb->DetonationFrame = Unsorted::CurrentFrame; + } + } + + pCurPassenger = pNextPassenger; + } + } + } + + if (this->IvanBomb_Detonate_PenetratesGarrison) + { + if (pTarget->WhatAmI() != AbstractType::Building) + return; + + const auto& Occupants = abstract_cast(pTarget)->Occupants; + + for (int i = 0; i < Occupants.Count; i++) + { + auto pOccupant = Occupants.Items[i]; + const bool CanAffects = this->IvanBomb_Detonate_AffectTypes.empty() + || this->IvanBomb_Detonate_AffectTypes.Contains(pOccupant->GetTechnoType()); + + if (const auto pOccupantBomb = pOccupant->AttachedBomb) + { + if (this->IvanBomb_Detonate_InvokerOnly) + { + if (pOccupantBomb->Owner == pOwner && CanAffects) + pOccupantBomb->DetonationFrame = Unsorted::CurrentFrame; + } + else + { + if (CanAffects) + pOccupantBomb->DetonationFrame = Unsorted::CurrentFrame; + } + } + } + } +} 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 }