diff --git a/README.md b/README.md index 7ca803ca5f..2397ca1c5b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Credits - **ChrisLv_CN** - interceptor logic, LaserTrails, laser fixes, general assistance (work relicensed under [following permission](images/ChrisLv-relicense.png)) - **Xkein** - general assistance, YRpp edits - **thomassneddon** - general assistance -- **Starkku** - Warhead shield penetration & breaking, strafing aircraft weapon customization, vehicle DeployFire fixes/improvements, stationary VehicleTypes, Burst logic improvements, TechnoType auto-firing weapons, Secondary weapon fallback customization, weapon target type filtering, AreaFire targeting customization, CreateUnit improvements, Attached animation & jumpjet unit layer customization, IsSimpleDeployer improvements, Shield modification warheads +- **Starkku** - Warhead shield penetration & breaking, strafing aircraft weapon customization, vehicle DeployFire fixes/improvements, stationary VehicleTypes, Burst logic improvements, TechnoType auto-firing weapons, Secondary weapon fallback customization, weapon target type filtering, AreaFire targeting customization, CreateUnit improvements, Attached animation & jumpjet unit layer customization, IsSimpleDeployer improvements, Shield modification warheads, Warhead decloaking toggle - **SukaHati (Erzoid)** - Minimum interceptor guard range - **Morton (MortonPL)** - XDrawOffset, Shield passthrough & absorption, building LimboDelivery, fix for Image in art rules - **mevitar** - honorary shield tester *triple* award diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index c84588203b..8fee7d195a 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -245,4 +245,16 @@ In `rulesmd.ini`: [SOMEPROJECTILE] ; Projectile Gravity=6.0 ; double Gravity.HeightFix=false ; boolean +``` + +## Warheads + +### Customizing decloak on damaging targets + +- You can now specify whether or not the warhead decloaks objects that are damaged by the warhead. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +DecloakDamagedTargets=true ; boolean ``` \ No newline at end of file diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2a332fafc1..42542b7b2c 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -37,7 +37,8 @@ HitAnim= ; animation BreakWeapon= ; WeaponType AbsorbPercent=1.0 ; double, percents PassPercent=0.0 ; double, percents - +AllowTransfer= ; boolean + [SOMETECHNO] ; TechnoType ShieldType=SOMESHIELDTYPE ; ShieldType; none by default @@ -85,6 +86,7 @@ Shield.InheritStateOnReplace=false ; boolean - `BreakWeapon`, if set, will be fired at the TechnoType once the shield breaks. - `AbsorbPercent` controls the percentage of damage that will be absorbed by the shield. Defaults to 1.0, meaning full damage absorption. - `PassPercent` controls the percentage of damage that will *not* be absorbed by the shield, and will be dealt to the unit directly even if the shield is active. Defaults to 0.0 - no penetration. +- `AllowTransfer` controls whether or not the shield can be transferred if the TechnoType changes (such as `(Un)DeploysInto` or Ares type conversion). If not set, defaults to true if shield was attached via `Shield.AttachTypes`, otherwise false. - A TechnoType with a shield will show its shield Strength. An empty shield strength bar will be left after destroyed if it is respawnable. - Buildings now use the 5th frame of `pips.shp` to display the shield strength while other units uses the 16th frame by default. - `Pips.Shield` can be used to specify which pip frame should be used as shield strength. If only 1 digit set, then it will always display it, or if 3 digits set, it will respect `ConditionYellow` and `ConditionRed`. `Pips.Shield.Building` is used for BuildingTypes. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index d99b64e3ba..49f613d702 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -255,6 +255,7 @@ New: - IsSimpleDeployer deploy direction & animation customizations (by Starkku) - Customizable projectile gravity (by secsome) - Gates can now link with walls correctly via `NSGates` or `EWGates` (by Uranusian) +- Per-warhead toggle for decloak of damaged targets (by Starkku) Vanilla fixes: - Fixed laser drawing code to allow for thicker lasers in house color draw mode (by Kerbiter, ChrisLv_CN) diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp index 6db41331d4..5883abb42c 100644 --- a/src/Ext/Techno/Hooks.Shield.cpp +++ b/src/Ext/Techno/Hooks.Shield.cpp @@ -112,7 +112,13 @@ DEFINE_HOOK(0x6F9E50, TechnoClass_AI_Shield, 0x5) { GET(TechnoClass*, pThis, ECX); const auto pExt = TechnoExt::ExtMap.Find(pThis); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + // Set current shield type if it is not set. + if (!pExt->CurrentShieldType->Strength && pTypeExt->ShieldType->Strength) + pExt->CurrentShieldType = pTypeExt->ShieldType; + + // Create shield class instance if it does not exist. if (pExt->CurrentShieldType && pExt->CurrentShieldType->Strength && !pExt->Shield) pExt->Shield = std::make_unique(pThis); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 5f78936451..a2e6b9b4d0 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -283,4 +283,18 @@ DEFINE_HOOK(0x6FE19A, TechnoClass_FireAt_AreaFire, 0x6) } return 0; +} + +DEFINE_HOOK(0x702819, TechnoClass_ReceiveDamage_Decloak, 0xA) +{ + GET(TechnoClass* const, pThis, ESI); + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFS(0xC4, -0xC)); + + if (auto pExt = WarheadTypeExt::ExtMap.Find(pWarhead)) + { + if (pExt->DecloakDamagedTargets) + pThis->Uncloak(false); + } + + return 0x702823; } \ No newline at end of file diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index ef34f1cd41..73807d5072 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -48,6 +48,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->RemoveDisguise.Read(exINI, pSection, "RemoveDisguise"); this->RemoveMindControl.Read(exINI, pSection, "RemoveMindControl"); this->AnimList_PickRandom.Read(exINI, pSection, "AnimList.PickRandom"); + this->DecloakDamagedTargets.Read(exINI, pSection, "DecloakDamagedTargets"); // Crits this->Crit_Chance.Read(exINI, pSection, "Crit.Chance"); @@ -106,6 +107,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->AnimList_PickRandom) + .Process(this->DecloakDamagedTargets) + .Process(this->Crit_Chance) .Process(this->Crit_ExtraDamage) .Process(this->Crit_Affects) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 0563b27617..5c74f11dca 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -23,6 +23,7 @@ class WarheadTypeExt Valueable RemoveDisguise; Valueable RemoveMindControl; Valueable AnimList_PickRandom; + Valueable DecloakDamagedTargets; Valueable Crit_ExtraDamage; Valueable Crit_Chance; @@ -54,7 +55,7 @@ class WarheadTypeExt Valueable Shield_Respawn_Rate; Valueable Shield_Respawn_ResetTimer; Valueable Shield_SelfHealing_Duration; - Valueable Shield_SelfHealing_Amount; + Nullable Shield_SelfHealing_Amount; Valueable Shield_SelfHealing_Rate; Valueable Shield_SelfHealing_ResetTimer; @@ -79,6 +80,7 @@ class WarheadTypeExt , RemoveDisguise { false } , RemoveMindControl { false } , AnimList_PickRandom { false } + , DecloakDamagedTargets { true } , Crit_Chance { 0.0 } , Crit_ExtraDamage { 0 } @@ -105,7 +107,7 @@ class WarheadTypeExt , Shield_Respawn_Rate_InMinutes { -1.0 } , Shield_Respawn_ResetTimer { false } , Shield_SelfHealing_Duration { 0 } - , Shield_SelfHealing_Amount { 0.0 } + , Shield_SelfHealing_Amount { } , Shield_SelfHealing_Rate { -1 } , Shield_SelfHealing_Rate_InMinutes { -1.0 } , Shield_SelfHealing_ResetTimer { false } diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index 1a02445fc1..84f9a776fb 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -102,9 +102,9 @@ void WarheadTypeExt::ExtData::ApplyShieldModifiers(TechnoClass* pTarget) if (shieldIndex >= 0) { ratio = pExt->Shield->GetHealthRatio(); + pExt->CurrentShieldType = ShieldTypeClass::FindOrAllocate(NONE_STR); pExt->Shield->KillAnim(); pExt->Shield = nullptr; - pExt->CurrentShieldType = nullptr; } } @@ -128,7 +128,7 @@ void WarheadTypeExt::ExtData::ApplyShieldModifiers(TechnoClass* pTarget) if (shieldType->Strength && (!pExt->Shield || (this->Shield_ReplaceNonRespawning && pExt->Shield->IsBrokenAndNonRespawning()))) { pExt->CurrentShieldType = shieldType; - pExt->Shield = std::make_unique(pTarget); + pExt->Shield = std::make_unique(pTarget, true); if (this->Shield_ReplaceOnly && this->Shield_InheritStateOnReplace) { @@ -154,7 +154,10 @@ void WarheadTypeExt::ExtData::ApplyShieldModifiers(TechnoClass* pTarget) pExt->Shield->SetRespawn(this->Shield_Respawn_Duration, this->Shield_Respawn_Amount, this->Shield_Respawn_Rate, this->Shield_Respawn_ResetTimer); if (this->Shield_SelfHealing_Duration > 0) - pExt->Shield->SetSelfHealing(this->Shield_SelfHealing_Duration, this->Shield_SelfHealing_Amount, this->Shield_SelfHealing_Rate, this->Shield_SelfHealing_ResetTimer); + { + double amount = this->Shield_SelfHealing_Amount.Get(pExt->Shield->GetType()->SelfHealing); + pExt->Shield->SetSelfHealing(this->Shield_SelfHealing_Duration, amount, this->Shield_SelfHealing_Rate, this->Shield_SelfHealing_ResetTimer); + } } } } diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 4dfac38cc5..c4a7c10a9a 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -17,13 +17,14 @@ ShieldClass::ShieldClass() : Techno { nullptr } , Timers { } { } -ShieldClass::ShieldClass(TechnoClass* pTechno) : Techno { pTechno } +ShieldClass::ShieldClass(TechnoClass* pTechno, bool isAttached) : Techno { pTechno } , IdleAnim { nullptr } , Timers { } , Cloak { false } , Online { true } , Temporal { false } , Available { true } + , Attached { isAttached } , SelfHealing_Rate_Warhead { -1 } , Respawn_Rate_Warhead { -1 } { @@ -52,6 +53,7 @@ bool ShieldClass::Serialize(T& Stm) .Process(this->Online) .Process(this->Temporal) .Process(this->Available) + .Process(this->Attached) .Process(this->Type) .Process(this->SelfHealing_Warhead) .Process(this->SelfHealing_Rate_Warhead) @@ -78,6 +80,7 @@ void ShieldClass::SyncShieldToAnother(TechnoClass* pFrom, TechnoClass* pTo) if (pFromExt->Shield) { + pToExt->CurrentShieldType = pFromExt->CurrentShieldType; pToExt->Shield = std::make_unique(pTo); strcpy(pToExt->Shield->TechnoID, pFromExt->Shield->TechnoID); pToExt->Shield->Available = pFromExt->Shield->Available; @@ -125,6 +128,9 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) this->Timers.SelfHealing.Start(rate); // when attacked, restart the timer this->ResponseAttack(); + if (pWHExt->DecloakDamagedTargets) + this->Techno->Uncloak(false); + int residueDamage = shieldDamage - this->HP; if (residueDamage >= 0) { @@ -260,9 +266,11 @@ void ShieldClass::AI() } } + if (this->ConvertCheck()) + return; + this->UpdateType(); this->CloakCheck(); - this->ConvertCheck(); if (!this->Available) return; @@ -365,33 +373,50 @@ void ShieldClass::TemporalCheck() } // Is used for DeploysInto/UndeploysInto and DeploysInto/UndeploysInto -void ShieldClass::ConvertCheck() +bool ShieldClass::ConvertCheck() { const auto newID = this->Techno->get_ID(); if (strcmp(this->TechnoID, newID) == 0) - return; + return false; - const auto pType = this->Type; + const auto pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); + const auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(this->Techno->GetTechnoType()); + const auto pOldType = this->Type; + bool allowTransfer = this->Type->AllowTransfer.Get(Attached); - if (pType->Strength && this->Available) - { // Update this shield for the new owner - const auto pFrom_TechnoType = TechnoTypeClass::Find(this->TechnoID); - const auto pFromType = TechnoTypeExt::ExtMap.Find(pFrom_TechnoType)->ShieldType; + // Update shield type. + if (!allowTransfer && !pTechnoTypeExt->ShieldType->Strength) + { + this->KillAnim(); + pTechnoExt->CurrentShieldType = ShieldTypeClass::FindOrAllocate(NONE_STR); + pTechnoExt->Shield = nullptr; - if (pFromType->IdleAnim.Get() != pType->IdleAnim.Get()) + return true; + } + else if (pTechnoTypeExt->ShieldType->Strength) + { + pTechnoExt->CurrentShieldType = pTechnoTypeExt->ShieldType; + } + + const auto pNewType = pTechnoExt->CurrentShieldType; + + // Update shield properties. + if (pNewType->Strength && this->Available) + { + if (pOldType->IdleAnim.Get() != pNewType->IdleAnim.Get()) this->KillAnim(); this->HP = (int)round( (double)this->HP / - (double)pFromType->Strength * - (double)pType->Strength + (double)pOldType->Strength * + (double)pNewType->Strength ); } else { const auto timer = (this->HP <= 0) ? &this->Timers.Respawn : &this->Timers.SelfHealing; - if (pType->Strength && !this->Available) + if (pNewType->Strength && !this->Available) { // Resume this shield when became Available timer->Resume(); this->Available = true; @@ -405,6 +430,8 @@ void ShieldClass::ConvertCheck() } strcpy(this->TechnoID, newID); + + return false; } void ShieldClass::SelfHealing() @@ -424,7 +451,7 @@ void ShieldClass::SelfHealing() const int rate = timerWH->InProgress() ? this->SelfHealing_Rate_Warhead : pType->SelfHealing_Rate; const auto percentageAmount = this->GetPercentageAmount(amount); - if (percentageAmount > 0) + if (percentageAmount != 0) { if (this->HP < this->Type->Strength && timer->StartTime == -1) timer->Start(rate); @@ -439,6 +466,10 @@ void ShieldClass::SelfHealing() this->HP = pType->Strength; timer->Stop(); } + else if (this->HP <= 0) + { + BreakShield(); + } } } } @@ -514,11 +545,11 @@ void ShieldClass::SetRespawn(int duration, double amount, int rate, bool resetTi this->Respawn_Warhead = amount > 0 ? amount : Type->Respawn; this->Respawn_Rate_Warhead = rate >= 0 ? rate : Type->Respawn_Rate; - + timerWH->Start(duration); - if (this->HP <= 0 && Respawn_Rate_Warhead >= 0 && (resetTimer || timer->Expired())) - { + if (this->HP <= 0 && Respawn_Rate_Warhead >= 0 && (resetTimer || timer->Expired())) + { timer->Start(Respawn_Rate_Warhead); } else if (timer->InProgress()) @@ -534,7 +565,7 @@ void ShieldClass::SetSelfHealing(int duration, double amount, int rate, bool res auto timer = &this->Timers.SelfHealing; auto timerWH = &this->Timers.SelfHealing_Warhead; - this->SelfHealing_Warhead = amount > 0 ? amount : Type->SelfHealing; + this->SelfHealing_Warhead = amount; this->SelfHealing_Rate_Warhead = rate >= 0 ? rate : Type->SelfHealing_Rate; timerWH->Start(duration); diff --git a/src/New/Entity/ShieldClass.h b/src/New/Entity/ShieldClass.h index df6123a56a..6db9ef88a5 100644 --- a/src/New/Entity/ShieldClass.h +++ b/src/New/Entity/ShieldClass.h @@ -11,7 +11,8 @@ class ShieldClass { public: ShieldClass(); - ShieldClass(TechnoClass* pTechno); + ShieldClass(TechnoClass* pTechno, bool isAttached); + ShieldClass(TechnoClass* pTechno) : ShieldClass(pTechno, false) {}; ~ShieldClass() = default; int ReceiveDamage(args_ReceiveDamage* args); @@ -61,7 +62,7 @@ class ShieldClass void CloakCheck(); void OnlineCheck(); void TemporalCheck(); - void ConvertCheck(); + bool ConvertCheck(); void DrawShieldBar_Building(int iLength, Point2D* pLocation, RectangleStruct* pBound); void DrawShieldBar_Other(int iLength, Point2D* pLocation, RectangleStruct* pBound); @@ -77,6 +78,7 @@ class ShieldClass bool Online; bool Temporal; bool Available; + bool Attached; double SelfHealing_Warhead; int SelfHealing_Rate_Warhead; diff --git a/src/New/Type/ShieldTypeClass.cpp b/src/New/Type/ShieldTypeClass.cpp index 2e273fee5f..8d5e6d8a0b 100644 --- a/src/New/Type/ShieldTypeClass.cpp +++ b/src/New/Type/ShieldTypeClass.cpp @@ -47,6 +47,8 @@ void ShieldTypeClass::LoadFromINI(CCINIClass* pINI) this->AbsorbPercent.Read(exINI, pSection, "AbsorbPercent"); this->PassPercent.Read(exINI, pSection, "PassPercent"); + + this->AllowTransfer.Read(exINI, pSection, "AllowTransfer"); } template @@ -71,6 +73,7 @@ void ShieldTypeClass::Serialize(T& Stm) .Process(this->BreakWeapon) .Process(this->AbsorbPercent) .Process(this->PassPercent) + .Process(this->AllowTransfer) ; } diff --git a/src/New/Type/ShieldTypeClass.h b/src/New/Type/ShieldTypeClass.h index 9cbea35f7c..67b4a4ccd1 100644 --- a/src/New/Type/ShieldTypeClass.h +++ b/src/New/Type/ShieldTypeClass.h @@ -29,6 +29,7 @@ class ShieldTypeClass final : public Enumerable Valueable AbsorbPercent; Valueable PassPercent; + Nullable AllowTransfer; private: Valueable Respawn_Rate__InMinutes; Valueable SelfHealing_Rate__InMinutes; @@ -55,6 +56,7 @@ class ShieldTypeClass final : public Enumerable , PassPercent(0.0) , Respawn_Rate__InMinutes(0.0) , SelfHealing_Rate__InMinutes(0.0) + , AllowTransfer() {}; virtual ~ShieldTypeClass() override = default;