From edaa34692f76065b71a480cdb35b1c54e94f0e4d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 5 Sep 2023 06:05:43 +0200 Subject: [PATCH 01/21] First commit --- src/Ext/Techno/Body.Update.cpp | 33 ++++++++++++++ src/Ext/Techno/Body.cpp | 3 ++ src/Ext/Techno/Body.h | 9 ++++ src/Ext/Techno/Hooks.cpp | 82 ++++++++++++++++++++++++++++++++++ src/Ext/TechnoType/Body.cpp | 9 ++++ src/Ext/TechnoType/Body.h | 10 +++++ src/Ext/WarheadType/Body.cpp | 10 +++++ src/Ext/WarheadType/Body.h | 10 +++++ 8 files changed, 166 insertions(+) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 955968783e..b642d3de85 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -65,6 +65,39 @@ void TechnoExt::ExtData::ApplyInterceptor() } } +void TechnoExt::ExtData::WebbyUpdate() +{ + auto const pThis = this->OwnerObject(); + + if (!TechnoExt::IsActive(pThis) || pThis->WhatAmI() != AbstractType::Infantry) + return; + + auto pExt = TechnoExt::ExtMap.Find(pThis); + if (!pExt) + return; + int aaa = pExt->WebbyDurationCountDown; + if (pExt->WebbyDurationCountDown < 0) + { + if (pExt->WebbyAnim) + pExt->WebbyAnim->Limbo(); + + pExt->WebbyAnim = nullptr; + + return; + } + + if (pExt->WebbyDurationTimer.Completed()) + { + pExt->WebbyDurationCountDown = -1; + pExt->WebbyDurationTimer.Stop(); + + if (pExt->WebbyAnim) + pExt->WebbyAnim->Limbo(); + + pExt->WebbyAnim = nullptr; + } +} + void TechnoExt::ExtData::DepletedAmmoActions() { auto const pThis = this->OwnerObject(); diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 2c80573da6..6182e0c25f 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -649,6 +649,9 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->DeployFireTimer) .Process(this->ForceFullRearmDelay) .Process(this->WHAnimRemainingCreationInterval) + .Process(this->WebbyDurationCountDown) + .Process(this->WebbyDurationTimer) + .Process(this->WebbyAnim) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 739b248486..60662a6f1e 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -42,6 +42,10 @@ class TechnoExt // as neither is guaranteed to point to the house the TechnoClass had prior to entering transport and cannot be safely overridden. HouseClass* OriginalPassengerOwner; + int WebbyDurationCountDown; + CDTimerClass WebbyDurationTimer; + AnimClass* WebbyAnim; + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } , Shield {} @@ -59,6 +63,9 @@ class TechnoExt , DeployFireTimer {} , ForceFullRearmDelay { false } , WHAnimRemainingCreationInterval { 0 } + , WebbyDurationCountDown { -1 } + , WebbyDurationTimer {} + , WebbyAnim { nullptr } { } void ApplyInterceptor(); @@ -72,6 +79,7 @@ class TechnoExt void UpdateLaserTrails(); void InitializeLaserTrails(); void UpdateMindControlAnim(); + void WebbyUpdate(); virtual ~ExtData() override; @@ -141,6 +149,7 @@ class TechnoExt static bool ConvertToType(FootClass* pThis, TechnoTypeClass* toType); static bool CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue = false); static bool IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource); + static void WebbyUpdate(TechnoClass* pThis); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 923cc8c8d9..19d66036bf 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include DEFINE_HOOK(0x6F9E50, TechnoClass_AI, 0x5) @@ -31,6 +32,7 @@ DEFINE_HOOK(0x6F9E50, TechnoClass_AI, 0x5) pExt->ApplySpawnLimitRange(); pExt->UpdateLaserTrails(); pExt->DepletedAmmoActions(); + pExt->WebbyUpdate(); TechnoExt::ApplyMindControlRangeLimit(pThis); @@ -492,3 +494,83 @@ DEFINE_HOOK(0x51BAFB, InfantryClass_ChronoSparkleDelay, 0x5) R->ECX(RulesExt::Global()->ChronoSparkleDisplayDelay); return 0x51BB00; } + +DEFINE_HOOK(0x518FBC, InfantryClass_DrawIt_DontRenderSHP, 0x6) +{ + enum { SkipDrawCode = 0x5192B5 }; + + GET(InfantryClass*, pThis, EBP); + + if (!pThis) + return 0; + + auto pTechno = static_cast(pThis); + if (!pTechno) + return 0; + + auto pExt = TechnoExt::ExtMap.Find(pTechno); + if (!pExt) + return 0; + + if (pExt->WebbyDurationCountDown > 0) + return SkipDrawCode; + + return 0; +} + +DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) +{ + GET(InfantryClass* const, pThis, ESI); + REF_STACK(args_ReceiveDamage const, receiveDamageArgs, STACK_OFFSET(0xD0, 0x4)); + + if (!receiveDamageArgs.WH) + return 0; + + auto const pWarheadExt = WarheadTypeExt::ExtMap.Find(receiveDamageArgs.WH); + if (!pWarheadExt || !pWarheadExt->Webby || pWarheadExt->Webby_Duration <= 0 || pWarheadExt->Webby_Anims.size() == 0) + return 0; + + auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + if (!pTypeExt) + return 0; + + if (pTypeExt->ImmuneToWeb.Get()) + return 0; + + auto const pExt = TechnoExt::ExtMap.Find(pThis); + if (!pExt) + return 0; + + if (!pExt->WebbyAnim) + { + bool hasCustomAnims = pTypeExt->Webby_Anims.size() > 0; + int max = hasCustomAnims ? pTypeExt->Webby_Anims.size() - 1 : pWarheadExt->Webby_Anims.size() - 1; + int selectedIndex = ScenarioClass::Instance->Random.RandomRanged(0, max); + auto const pAnimType = hasCustomAnims ? pTypeExt->Webby_Anims[selectedIndex] : pWarheadExt->Webby_Anims[selectedIndex]; + auto const pAnim = GameCreate(pAnimType, pThis->Location, 0, 1, 0x600, 0, false); + + if (pAnim) + { + pExt->WebbyAnim = pAnim; + pExt->WebbyAnim->SetOwnerObject(pThis); + } + } + + int duration = pTypeExt->Webby_Duration.Get() > 0 ? pTypeExt->Webby_Duration.Get() : pWarheadExt->Webby_Duration.Get(); + int durationVariation = pTypeExt->Webby_DurationVariation.Get() > 0 ? pTypeExt->Webby_DurationVariation.Get() : pWarheadExt->Webby_DurationVariation.Get(); + durationVariation = durationVariation < 0 ? 0 : durationVariation; + int minDuration = duration - durationVariation; + minDuration = minDuration <= 0 ? 0 : minDuration; + int maxDuration = duration + durationVariation; + + int paralysisDuration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); + pExt->WebbyDurationCountDown = paralysisDuration; + pExt->WebbyDurationTimer.Start(paralysisDuration); + + if (pThis->Locomotor && pThis->Locomotor->Is_Moving()) + pThis->Locomotor->Stop_Moving(); + + pThis->ParalysisTimer.Start(paralysisDuration); + + return 0x51804E; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 4c88726b61..655fda8b84 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -269,6 +269,11 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SpawnsPipSize.Read(exINI, pSection, "SpawnsPipSize"); this->SpawnsPipOffset.Read(exINI, pSection, "SpawnsPipOffset"); + this->Webby_Anims.Read(exINI, pSection, "Webby.Anims"); + this->ImmuneToWeb.Read(exINI, pSection, "ImmuneToWeb"); + this->Webby_Duration.Read(exINI, pSection, "Webby.Duration"); + this->Webby_DurationVariation.Read(exINI, pSection, "Webby.DurationVariation"); + // Ares 0.2 this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius"); @@ -561,6 +566,10 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->EmptySpawnsPipFrame) .Process(this->SpawnsPipSize) .Process(this->SpawnsPipOffset) + .Process(this->Webby_Anims) + .Process(this->ImmuneToWeb) + .Process(this->Webby_Duration) + .Process(this->Webby_DurationVariation) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 3af5450304..05b6387431 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -178,6 +178,11 @@ class TechnoTypeExt Nullable SpawnsPipSize; Valueable SpawnsPipOffset; + ValueableVector Webby_Anims; + Valueable ImmuneToWeb; + Valueable Webby_Duration; + Valueable Webby_DurationVariation; + struct LaserTrailDataEntry { ValueableIdx idxType; @@ -351,6 +356,11 @@ class TechnoTypeExt , EmptySpawnsPipFrame { 0 } , SpawnsPipSize {} , SpawnsPipOffset {{ 0,0 }} + + , Webby_Anims {} + , ImmuneToWeb { false } + , Webby_Duration { 0 } + , Webby_DurationVariation { 0 } { } virtual ~ExtData() = default; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index bcf53adeb4..0f24a766bb 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -196,6 +196,11 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DetonateOnAllMapObjects_AffectTypes.Read(exINI, pSection, "DetonateOnAllMapObjects.AffectTypes"); this->DetonateOnAllMapObjects_IgnoreTypes.Read(exINI, pSection, "DetonateOnAllMapObjects.IgnoreTypes"); + this->Webby.Read(exINI, pSection, "Webby"); + this->Webby_Anims.Read(exINI, pSection, "Webby.Anims"); + this->Webby_Duration.Read(exINI, pSection, "Webby.Duration"); + this->Webby_DurationVariation.Read(exINI, pSection, "Webby.DurationVariation"); + char tempBuffer[32]; // Convert.From & Convert.To for (size_t i = 0; ; ++i) @@ -375,6 +380,11 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->InflictLocomotor) .Process(this->RemoveInflictedLocomotor) + .Process(this->Webby) + .Process(this->Webby_Anims) + .Process(this->Webby_Duration) + .Process(this->Webby_DurationVariation) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index a2f0e634bb..bac615e414 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -114,6 +114,11 @@ class WarheadTypeExt Valueable InflictLocomotor; Valueable RemoveInflictedLocomotor; + Valueable Webby; + ValueableVector Webby_Anims; + Valueable Webby_Duration; + Valueable Webby_DurationVariation; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -235,6 +240,11 @@ class WarheadTypeExt , Splashed { false } , RemainingAnimCreationInterval { 0 } , PossibleCellSpreadDetonate {false} + + , Webby { false } + , Webby_Anims {} + , Webby_Duration { 0 } + , Webby_DurationVariation { 0 } { } private: From 21f0bb686546b011a783a924cf9d3e11b0026478 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 8 Sep 2023 18:04:36 +0200 Subject: [PATCH 02/21] Force AI to stop moving And added documentation. --- docs/New-or-Enhanced-Logics.md | 32 ++++++++++++++++++++ docs/Whats-New.md | 1 + src/Ext/Techno/Body.Update.cpp | 9 +++++- src/Ext/Techno/Body.cpp | 2 ++ src/Ext/Techno/Body.h | 4 +++ src/Ext/Techno/Hooks.cpp | 43 +++++++++++++++++++++++---- src/Ext/Unit/Hooks.DisallowMoving.cpp | 34 +++++++++++++++++++++ src/Ext/WarheadType/Body.cpp | 2 ++ src/Ext/WarheadType/Body.h | 2 ++ 9 files changed, 123 insertions(+), 6 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index a8c9859231..61771c1231 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1196,6 +1196,38 @@ In `rulesmd.ini`: NotHuman.DeathSequence= ; integer (1 to 5) ``` +### Web logic against infantry + +- Infantry will be temporally paralyzed by warheads with `Webby=yes`. +- `Webby.Duration` specifies the duration, in frames, of the warhead's web effect. +- `Webby.DurationVariation` allows for random variance to the duration of the warhead's web effect. +- When `Webby.Anims` contains more than 1 animation then the new animation will be picked randomly. +- `Webby.Cap` works like in EMP logic developed by Ares. + - `Webby.Cap=-1` case: The target’s web counter is set to this absolute number of frames specified by `Web.Duration`, unless the target’s web counter is already greater than this. + - `Webby.Cap=0` case: Makes this web effect stackable, but uncapped. + - `Webby.Cap >0` case: Makes this web effect stackable, but maximum value capped to `Webby.Cap` value. +- Infantry can have custom values for the web logic. +- No damage is done by the weapons warhead. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; Warhead +Webby=no ; boolean +Webby.Anims= ; list of animations +Webby.Duration=0 ; integer, game frames +Webby.DurationVariation=0 ; integer +Webby.Cap=-1 ; integer +``` + +In `rulesmd.ini`: +```ini +[SOMETECHNO] ; InfantryType +ImmuneToWeb=no ; boolean +Webby.Anims= ; list of animations +Webby.Duration=0 ; integer, game frames +Webby.DurationVariation=0 ; integer +``` + ## Weapons ### AreaFire target customization diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 14ee25cf6b..42b8245109 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -337,6 +337,7 @@ New: - Show designator & inhibitor range (by Morton) - Owner-only sound on unit creation (by Fryone) - Allow using `Secondary` weapon against walls if `Primary` cannot target them (by Starkku) +- Web logic against infantry (FS-21) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index b642d3de85..f060821e87 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -75,7 +75,7 @@ void TechnoExt::ExtData::WebbyUpdate() auto pExt = TechnoExt::ExtMap.Find(pThis); if (!pExt) return; - int aaa = pExt->WebbyDurationCountDown; + if (pExt->WebbyDurationCountDown < 0) { if (pExt->WebbyAnim) @@ -95,6 +95,13 @@ void TechnoExt::ExtData::WebbyUpdate() pExt->WebbyAnim->Limbo(); pExt->WebbyAnim = nullptr; + + // Restore previous action + pThis->SetDestination(pExt->WebbyLastTarget, false); + pThis->SetTarget(pExt->WebbyLastTarget); + pThis->QueueMission(pExt->WebbyLastMission, true); + pExt->WebbyLastTarget = nullptr; + pExt->WebbyLastMission = Mission::Sleep; } } diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 6182e0c25f..4f8ef6bfe7 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -652,6 +652,8 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->WebbyDurationCountDown) .Process(this->WebbyDurationTimer) .Process(this->WebbyAnim) + .Process(this->WebbyLastTarget) + .Process(this->WebbyLastMission) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 60662a6f1e..60ea411228 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -45,6 +45,8 @@ class TechnoExt int WebbyDurationCountDown; CDTimerClass WebbyDurationTimer; AnimClass* WebbyAnim; + AbstractClass* WebbyLastTarget; + Mission WebbyLastMission; ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } @@ -66,6 +68,8 @@ class TechnoExt , WebbyDurationCountDown { -1 } , WebbyDurationTimer {} , WebbyAnim { nullptr } + , WebbyLastTarget { nullptr } + , WebbyLastMission { Mission::Sleep } { } void ApplyInterceptor(); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 19d66036bf..b5aa1955f3 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -527,7 +527,7 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) return 0; auto const pWarheadExt = WarheadTypeExt::ExtMap.Find(receiveDamageArgs.WH); - if (!pWarheadExt || !pWarheadExt->Webby || pWarheadExt->Webby_Duration <= 0 || pWarheadExt->Webby_Anims.size() == 0) + if (!pWarheadExt || !pWarheadExt->Webby || pWarheadExt->Webby_Duration == 0 || pWarheadExt->Webby_Anims.size() == 0) return 0; auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); @@ -563,14 +563,47 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) minDuration = minDuration <= 0 ? 0 : minDuration; int maxDuration = duration + durationVariation; - int paralysisDuration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); - pExt->WebbyDurationCountDown = paralysisDuration; - pExt->WebbyDurationTimer.Start(paralysisDuration); + duration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); + + int cap = pWarheadExt->Webby_Cap; + int webbyCountDown = pExt->WebbyDurationTimer.GetTimeLeft(); + + if (cap == 0) + { + // Makes this Web effect stackable, but uncapped + duration += webbyCountDown; + } + else if (cap > 0) + { + if (webbyCountDown > cap) + { + // If current duration effect is greater than the new attempt don't change the values + duration = webbyCountDown; + } + else + { + // Makes this Web effect stackable, but capped + duration += webbyCountDown; + duration = duration > cap ? cap : duration; + } + } + else + { + // Cap=-1 case: The target’s Web counter is set to this absolute number of frames specified by Web.Duration, unless the target’s Web counter is already greater than this + if (webbyCountDown > duration) + duration = webbyCountDown; + } + + pExt->WebbyLastTarget = pThis->Target; + pExt->WebbyLastMission = pThis->CurrentMission; + + pExt->WebbyDurationCountDown = duration; + pExt->WebbyDurationTimer.Start(duration); if (pThis->Locomotor && pThis->Locomotor->Is_Moving()) pThis->Locomotor->Stop_Moving(); - pThis->ParalysisTimer.Start(paralysisDuration); + pThis->ParalysisTimer.Start(duration); return 0x51804E; } diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp index 72187c7748..e84fc53e8b 100644 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ b/src/Ext/Unit/Hooks.DisallowMoving.cpp @@ -2,9 +2,11 @@ // Author: Starkku #include "UnitClass.h" +#include "InfantryClass.h" #include #include +#include DEFINE_HOOK(0x740A93, UnitClass_Mission_Move_DisallowMoving, 0x6) { @@ -81,3 +83,35 @@ DEFINE_HOOK(0x73891D, UnitClass_Active_Click_With_DisallowMoving, 0x6) return pThis->Type->Speed == 0 ? 0x738927 : 0; } + +/*DEFINE_HOOK(0x51F663, InfantryClass_Mission_Move_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, ESI); + + return pThis->ParalysisTimer.HasTimeLeft() > 0 ? 0x51F68F : 0; +}*/ + + +DEFINE_HOOK(0x51AA49, InfantryClass_Assign_Destination_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, ECX); + + if (pThis->ParalysisTimer.HasTimeLeft()) + return 0x51B1D7; + + auto pExt = TechnoExt::ExtMap.Find(pThis); + if (!pExt) + return 0; + + if (pExt->WebbyDurationCountDown > 0) + { + if (pThis->Target) + { + pThis->SetTarget(nullptr); + pThis->SetDestination(nullptr, false); + pThis->QueueMission(Mission::Sleep, false); + } + } + + return 0; +} diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 0f24a766bb..828e0a61d3 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -200,6 +200,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Webby_Anims.Read(exINI, pSection, "Webby.Anims"); this->Webby_Duration.Read(exINI, pSection, "Webby.Duration"); this->Webby_DurationVariation.Read(exINI, pSection, "Webby.DurationVariation"); + this->Webby_Cap.Read(exINI, pSection, "Webby.Cap"); char tempBuffer[32]; // Convert.From & Convert.To @@ -384,6 +385,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Webby_Anims) .Process(this->Webby_Duration) .Process(this->Webby_DurationVariation) + .Process(this->Webby_Cap) // Ares tags .Process(this->AffectsEnemies) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index bac615e414..29e092b118 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -118,6 +118,7 @@ class WarheadTypeExt ValueableVector Webby_Anims; Valueable Webby_Duration; Valueable Webby_DurationVariation; + Valueable Webby_Cap; // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html @@ -245,6 +246,7 @@ class WarheadTypeExt , Webby_Anims {} , Webby_Duration { 0 } , Webby_DurationVariation { 0 } + , Webby_Cap { -1 } { } private: From 702e5b971470747c4a7c68213b9dc0e9ae8dcde9 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 8 Sep 2023 18:05:38 +0200 Subject: [PATCH 03/21] More docs --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index ba09f1059d..ae7fc8dbbc 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -128,6 +128,7 @@ This page lists all the individual contributions to the project by their author. - Shared ammo logic - Customizable FLH when infantry is prone or deployed - Initial strength for cloned infantry + - Web logic against infantry - **Starkku**: - Misc. minor bugfixes & improvements - AI script actions: From 4848da81fb1819bd0716b6c335b0a73ef22ef8f4 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 8 Sep 2023 18:21:17 +0200 Subject: [PATCH 04/21] EditorConfig things --- src/Ext/Techno/Body.Update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index cfa2a9c12e..ece98d2c91 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -93,7 +93,7 @@ void TechnoExt::ExtData::WebbyUpdate() { pExt->WebbyDurationCountDown = -1; pExt->WebbyDurationTimer.Stop(); - + if (pExt->WebbyAnim) pExt->WebbyAnim->Limbo(); From 9c15f2828ce1513f8718ccd35894917f5488e1b9 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 8 Sep 2023 18:30:27 +0200 Subject: [PATCH 05/21] Added missing pointer invalidation as suggested by Otamaa --- src/Ext/Techno/Body.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 8380ac2ec9..c8ae829b86 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -92,6 +92,7 @@ class TechnoExt virtual void InvalidatePointer(void* ptr, bool bRemoved) override { AnnounceInvalidPointer(OriginalPassengerOwner, ptr); + AnnounceInvalidPointer(WebbyLastTarget, ptr); } virtual void LoadFromStream(PhobosStreamReader& Stm) override; From 5e2915420cff4f0ea5ccc9b2d8d60b2b059d35dc Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 13 Sep 2023 22:53:58 +0200 Subject: [PATCH 06/21] Applied feedback --- docs/New-or-Enhanced-Logics.md | 13 +++++++++---- src/Ext/Unit/Hooks.DisallowMoving.cpp | 8 -------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 61771c1231..3172a73bbb 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1198,7 +1198,7 @@ NotHuman.DeathSequence= ; integer (1 to 5) ### Web logic against infantry -- Infantry will be temporally paralyzed by warheads with `Webby=yes`. +- Infantry will be temporally paralyzed by warheads with `Webby=true`. - `Webby.Duration` specifies the duration, in frames, of the warhead's web effect. - `Webby.DurationVariation` allows for random variance to the duration of the warhead's web effect. - When `Webby.Anims` contains more than 1 animation then the new animation will be picked randomly. @@ -1207,12 +1207,13 @@ NotHuman.DeathSequence= ; integer (1 to 5) - `Webby.Cap=0` case: Makes this web effect stackable, but uncapped. - `Webby.Cap >0` case: Makes this web effect stackable, but maximum value capped to `Webby.Cap` value. - Infantry can have custom values for the web logic. +- Infantry with `ImmuneToWeb=true` are not affected by `Webby=true` warheads. - No damage is done by the weapons warhead. In `rulesmd.ini`: ```ini [SOMEWARHEAD] ; Warhead -Webby=no ; boolean +Webby=false ; boolean Webby.Anims= ; list of animations Webby.Duration=0 ; integer, game frames Webby.DurationVariation=0 ; integer @@ -1221,13 +1222,17 @@ Webby.Cap=-1 ; integer In `rulesmd.ini`: ```ini -[SOMETECHNO] ; InfantryType -ImmuneToWeb=no ; boolean +[SOMEINFANTRY] ; InfantryType +ImmuneToWeb=false ; boolean Webby.Anims= ; list of animations Webby.Duration=0 ; integer, game frames Webby.DurationVariation=0 ; integer ``` +```{warning} +`Webby.Anims` animations must be played in infinite loop with `LoopCount=-1` in artmd.ini +``` + ## Weapons ### AreaFire target customization diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp index e84fc53e8b..e6ac13ae25 100644 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ b/src/Ext/Unit/Hooks.DisallowMoving.cpp @@ -84,14 +84,6 @@ DEFINE_HOOK(0x73891D, UnitClass_Active_Click_With_DisallowMoving, 0x6) return pThis->Type->Speed == 0 ? 0x738927 : 0; } -/*DEFINE_HOOK(0x51F663, InfantryClass_Mission_Move_DisallowMoving, 0x6) -{ - GET(InfantryClass*, pThis, ESI); - - return pThis->ParalysisTimer.HasTimeLeft() > 0 ? 0x51F68F : 0; -}*/ - - DEFINE_HOOK(0x51AA49, InfantryClass_Assign_Destination_DisallowMoving, 0x6) { GET(InfantryClass*, pThis, ECX); From 06eaabac34afdb9c29b056d71fa46b5fb3a86bb8 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 13 Sep 2023 22:57:23 +0200 Subject: [PATCH 07/21] EditorConfig fixes --- src/Ext/RadSite/Body.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ext/RadSite/Body.cpp b/src/Ext/RadSite/Body.cpp index 9f7a915707..ab696848c1 100644 --- a/src/Ext/RadSite/Body.cpp +++ b/src/Ext/RadSite/Body.cpp @@ -141,12 +141,12 @@ double RadSiteExt::ExtData::GetRadLevelAt(CellStruct const& cell) const const auto max = static_cast(pThis->SpreadInLeptons); const auto dist = coords.DistanceFrom(base); - // will produce `-nan(ind)` result if both dist and max is zero + // will produce `-nan(ind)` result if both dist and max is zero // and used on formula below this check // ,.. -Otamaa if(!dist && !max) return pThis->RadLevel; - + return (dist > max) ? 0.0 : (max - dist) / max * pThis->RadLevel; } From a40849a56ba756a6682e085edecab5861bdc7bdb Mon Sep 17 00:00:00 2001 From: FS-21 Date: Wed, 13 Sep 2023 23:33:36 +0200 Subject: [PATCH 08/21] Point to latest YRpp --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index 6b7712dca5..6b1e250ce8 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 6b7712dca53ff7979e2671e489f2f9766847fd9b +Subproject commit 6b1e250ce8757d1bb6defae82801175aa85e743d From ef1df3e895e1e64448d41a4d312fe25daab142b6 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 16 Mar 2024 16:22:50 +0100 Subject: [PATCH 09/21] Fix bug --- src/Ext/Techno/Body.Update.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 90274d3d26..a2a602be68 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -33,6 +33,9 @@ void TechnoExt::ExtData::OnEarlyUpdate() this->ApplySpawnLimitRange(); this->UpdateLaserTrails(); this->DepletedAmmoActions(); + + if (this->WebbyAnim) + this->WebbyUpdate(); } @@ -104,23 +107,16 @@ void TechnoExt::ExtData::WebbyUpdate() if (!pExt) return; - if (pExt->WebbyDurationCountDown < 0) - { - if (pExt->WebbyAnim) - pExt->WebbyAnim->Limbo(); - - pExt->WebbyAnim = nullptr; - - return; - } - if (pExt->WebbyDurationTimer.Completed()) { pExt->WebbyDurationCountDown = -1; pExt->WebbyDurationTimer.Stop(); - if (pExt->WebbyAnim) - pExt->WebbyAnim->Limbo(); + if (pExt->WebbyAnim->Type) // If this anim doesn't have a type pointer, just detach it + { + pExt->WebbyAnim->TimeToDie = true; + pExt->WebbyAnim->UnInit(); + } pExt->WebbyAnim = nullptr; From 58076119ae448457dba8bea6585469ce19d3c195 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 23 Mar 2024 17:15:51 +0100 Subject: [PATCH 10/21] Fix crash --- src/Ext/Techno/Body.Update.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index a2a602be68..42c0d60767 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -121,11 +121,14 @@ void TechnoExt::ExtData::WebbyUpdate() pExt->WebbyAnim = nullptr; // Restore previous action - pThis->SetDestination(pExt->WebbyLastTarget, false); - pThis->SetTarget(pExt->WebbyLastTarget); - pThis->QueueMission(pExt->WebbyLastMission, true); - pExt->WebbyLastTarget = nullptr; - pExt->WebbyLastMission = Mission::Sleep; + if (pExt->WebbyLastTarget) + { + pThis->SetDestination(pExt->WebbyLastTarget, false); + pThis->SetTarget(pExt->WebbyLastTarget); + pThis->QueueMission(pExt->WebbyLastMission, true); + pExt->WebbyLastTarget = nullptr; + pExt->WebbyLastMission = Mission::Sleep; + } } } From 1dc4b98a769c9ed13225ee268859361654591060 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 14 May 2024 08:50:16 +0200 Subject: [PATCH 11/21] Removed 1 obsolete declaration --- src/Ext/Techno/Body.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 73017c5160..912250d076 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -157,7 +157,6 @@ class TechnoExt static bool ConvertToType(FootClass* pThis, TechnoTypeClass* toType); static bool CanDeployIntoBuilding(UnitClass* pThis, bool noDeploysIntoDefaultValue = false); static bool IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource); - static void WebbyUpdate(TechnoClass* pThis); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); From 9e95c2abb60c418172125e64713afdc9f13fd9b9 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 30 Aug 2024 12:02:31 +0200 Subject: [PATCH 12/21] Added ForceWeapon support Added a new tag ForceWeapon.Webby=-1 that points the weapon that should use the unit against victims of the web net... --- docs/New-or-Enhanced-Logics.md | 7 ++++--- src/Ext/Techno/Hooks.Firing.cpp | 8 ++++++++ src/Ext/TechnoType/Body.cpp | 2 ++ src/Ext/TechnoType/Body.h | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 79be3ea83c..8fb1d7ae79 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1554,6 +1554,7 @@ NotHuman.DeathSequence= ; integer (1 to 5) - Infantry can have custom values for the web logic. - Infantry with `ImmuneToWeb=true` are not affected by `Webby=true` warheads. - No damage is done by the weapons warhead. +- `ForceWeapon.Webby` specify what weapon should use the attacker against the affected unit. In `rulesmd.ini`: ```ini @@ -1563,15 +1564,15 @@ Webby.Anims= ; list of animations Webby.Duration=0 ; integer, game frames Webby.DurationVariation=0 ; integer Webby.Cap=-1 ; integer -``` -In `rulesmd.ini`: -```ini [SOMEINFANTRY] ; InfantryType ImmuneToWeb=false ; boolean Webby.Anims= ; list of animations Webby.Duration=0 ; integer, game frames Webby.DurationVariation=0 ; integer + +[SOMETECHNO] ; TechnoType +ForceWeapon.Webby=-1 ; integer ``` ```{warning} diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 1d804f1dd8..87e5c5daa1 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -102,6 +102,14 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6) { forceWeaponIndex = pTypeExt->ForceWeapon_Disguised; } + else if (pTypeExt->ForceWeapon_Webby >= 0) + { + if (const auto pTargetExt = TechnoExt::ExtMap.Find(pTarget)) + { + if (pTargetExt->WebbyAnim) + forceWeaponIndex = pTypeExt->ForceWeapon_Webby; + } + } if (forceWeaponIndex >= 0) { diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index bfc0f57291..a0b64f71ed 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -240,6 +240,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ForceWeapon_Naval_Decloaked.Read(exINI, pSection, "ForceWeapon.Naval.Decloaked"); this->ForceWeapon_Cloaked.Read(exINI, pSection, "ForceWeapon.Cloaked"); this->ForceWeapon_Disguised.Read(exINI, pSection, "ForceWeapon.Disguised"); + this->ForceWeapon_Webby.Read(exINI, pSection, "ForceWeapon.Webby"); this->Ammo_Shared.Read(exINI, pSection, "Ammo.Shared"); this->Ammo_Shared_Group.Read(exINI, pSection, "Ammo.Shared.Group"); this->SelfHealGainType.Read(exINI, pSection, "SelfHealGainType"); @@ -601,6 +602,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->ForceWeapon_Naval_Decloaked) .Process(this->ForceWeapon_Cloaked) .Process(this->ForceWeapon_Disguised) + .Process(this->ForceWeapon_Webby) .Process(this->Ammo_Shared) .Process(this->Ammo_Shared_Group) .Process(this->SelfHealGainType) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 4ce14b27d7..6742f2af85 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -149,6 +149,7 @@ class TechnoTypeExt Valueable ForceWeapon_Naval_Decloaked; Valueable ForceWeapon_Cloaked; Valueable ForceWeapon_Disguised; + Valueable ForceWeapon_Webby; Valueable Ammo_Shared; Valueable Ammo_Shared_Group; @@ -376,6 +377,7 @@ class TechnoTypeExt , ForceWeapon_Naval_Decloaked { -1 } , ForceWeapon_Cloaked { -1 } , ForceWeapon_Disguised { -1 } + , ForceWeapon_Webby { -1 } , Ammo_Shared { false } , Ammo_Shared_Group { -1 } From 38cd421bf6e57665d776c1a077fd9259f4c7865c Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 20 May 2025 18:48:06 +0200 Subject: [PATCH 13/21] Compilation fix --- src/Ext/Techno/Hooks.Firing.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 5008c52744..bfed03fcd3 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -115,20 +115,20 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6) { forceWeaponIndex = pTypeExt->ForceWeapon_UnderEMP; } - else if (!pTypeExt->ForceWeapon_InRange.empty() || !pTypeExt->ForceAAWeapon_InRange.empty()) - { - ForceWeaponInRangeTemp::SelectWeaponByRange = true; - forceWeaponIndex = TechnoExt::ExtMap.Find(pThis)->ApplyForceWeaponInRange(pTargetTechno); - ForceWeaponInRangeTemp::SelectWeaponByRange = false; - } else if (pTypeExt->ForceWeapon_Webby >= 0) { - if (const auto pTargetExt = TechnoExt::ExtMap.Find(pTarget)) + if (const auto pTargetExt = TechnoExt::ExtMap.Find(pTargetTechno)) { if (pTargetExt->WebbyAnim) forceWeaponIndex = pTypeExt->ForceWeapon_Webby; } } + else if (!pTypeExt->ForceWeapon_InRange.empty() || !pTypeExt->ForceAAWeapon_InRange.empty()) + { + ForceWeaponInRangeTemp::SelectWeaponByRange = true; + forceWeaponIndex = TechnoExt::ExtMap.Find(pThis)->ApplyForceWeaponInRange(pTargetTechno); + ForceWeaponInRangeTemp::SelectWeaponByRange = false; + } if (forceWeaponIndex >= 0) { From 0b229b719f462dcca1b367192fa18ccf213e028d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Thu, 29 May 2025 13:37:50 +0200 Subject: [PATCH 14/21] Applied some feedback --- src/Ext/Techno/Body.Update.cpp | 2 -- src/Ext/Techno/Hooks.Firing.cpp | 9 ++++----- src/Ext/Techno/Hooks.cpp | 6 ------ src/Ext/Unit/Hooks.DisallowMoving.cpp | 4 +--- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 155183fa92..23097dfaf2 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -106,8 +106,6 @@ void TechnoExt::ExtData::WebbyUpdate() return; auto pExt = TechnoExt::ExtMap.Find(pThis); - if (!pExt) - return; if (pExt->WebbyDurationTimer.Completed()) { diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index bfed03fcd3..57d1028e95 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -117,11 +117,10 @@ DEFINE_HOOK(0x6F3428, TechnoClass_WhatWeaponShouldIUse_ForceWeapon, 0x6) } else if (pTypeExt->ForceWeapon_Webby >= 0) { - if (const auto pTargetExt = TechnoExt::ExtMap.Find(pTargetTechno)) - { - if (pTargetExt->WebbyAnim) - forceWeaponIndex = pTypeExt->ForceWeapon_Webby; - } + const auto pTargetExt = TechnoExt::ExtMap.Find(pTargetTechno); + + if (pTargetExt->WebbyAnim) + forceWeaponIndex = pTypeExt->ForceWeapon_Webby; } else if (!pTypeExt->ForceWeapon_InRange.empty() || !pTypeExt->ForceAAWeapon_InRange.empty()) { diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 32bb16dfb9..6dbf11d933 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -926,8 +926,6 @@ DEFINE_HOOK(0x518FBC, InfantryClass_DrawIt_DontRenderSHP, 0x6) return 0; auto pExt = TechnoExt::ExtMap.Find(pTechno); - if (!pExt) - return 0; if (pExt->WebbyDurationCountDown > 0) return SkipDrawCode; @@ -948,15 +946,11 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) return 0; auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); - if (!pTypeExt) - return 0; if (pTypeExt->ImmuneToWeb.Get()) return 0; auto const pExt = TechnoExt::ExtMap.Find(pThis); - if (!pExt) - return 0; if (!pExt->WebbyAnim) { diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp index e6ac13ae25..4ae7c93983 100644 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ b/src/Ext/Unit/Hooks.DisallowMoving.cpp @@ -91,9 +91,7 @@ DEFINE_HOOK(0x51AA49, InfantryClass_Assign_Destination_DisallowMoving, 0x6) if (pThis->ParalysisTimer.HasTimeLeft()) return 0x51B1D7; - auto pExt = TechnoExt::ExtMap.Find(pThis); - if (!pExt) - return 0; + const auto pExt = TechnoExt::ExtMap.Find(pThis); if (pExt->WebbyDurationCountDown > 0) { From 3e5fd04dea2a07a6b355e081fbc60548964b167d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Fri, 30 May 2025 13:06:56 +0200 Subject: [PATCH 15/21] Added Map Event when a tagget soldier is under Webby logic 607,0,0 --- docs/AI-Scripting-and-Mapping.md | 10 ++++++++++ src/Ext/TEvent/Body.cpp | 15 +++++++++++++++ src/Ext/TEvent/Body.h | 2 ++ src/Ext/Techno/Hooks.cpp | 4 ++++ 4 files changed, 31 insertions(+) diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index b742d1be57..9bf1a1b117 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -797,3 +797,13 @@ ID=EventCount,...,605,2,[HouseIndex],[AITargetTypes index#],... | >= 0 | The index of the current House in the map | | -1 | This value is ignored (any house is valid) | | -2 | Pick the owner of the map trigger | + +### `607` A webby weapon hit the tagged infantry + +In `mycampaign.map`: +```ini +[Events] +... +ID=EventCount,...,607,0,0,... +... +``` diff --git a/src/Ext/TEvent/Body.cpp b/src/Ext/TEvent/Body.cpp index f1119b6f1a..aef20efe30 100644 --- a/src/Ext/TEvent/Body.cpp +++ b/src/Ext/TEvent/Body.cpp @@ -151,6 +151,8 @@ std::optional TEventExt::Execute(TEventClass* pThis, int iEvent, HouseClas return TEventExt::CellHasTechnoTypeTEvent(pThis, pObject, pHouse); case PhobosTriggerEvent::CellHasAnyTechnoTypeFromList: return TEventExt::CellHasAnyTechnoTypeFromListTEvent(pThis, pObject, pHouse); + case PhobosTriggerEvent::AttachedIsUnderWebby: + return TEventExt::AttachedIsUnderWebbyTEvent(pObject); // If it requires an additional object as like mapping events 7 or 48, please fill it in here. @@ -306,6 +308,19 @@ bool TEventExt::CellHasTechnoTypeTEvent(TEventClass* pThis, ObjectClass* pObject return false; } +bool TEventExt::AttachedIsUnderWebbyTEvent(ObjectClass* pObject) +{ + if (!pObject) + return false; + + const auto pExt = TechnoExt::ExtMap.Find(abstract_cast(pObject)); + + if (pExt->WebbyDurationCountDown > 0) + return true; + + return false; +} + // ============================= // container diff --git a/src/Ext/TEvent/Body.h b/src/Ext/TEvent/Body.h index 7cf2e8e3da..9cc48dfe66 100644 --- a/src/Ext/TEvent/Body.h +++ b/src/Ext/TEvent/Body.h @@ -54,6 +54,7 @@ enum PhobosTriggerEvent HouseDoesntOwnTechnoType = 602, CellHasTechnoType = 604, CellHasAnyTechnoTypeFromList = 605, + AttachedIsUnderWebby = 607, _DummyMaximum, }; @@ -98,6 +99,7 @@ class TEventExt static bool CellHasAnyTechnoTypeFromListTEvent(TEventClass* pThis, ObjectClass* pObject, HouseClass* pHouse); static bool CellHasTechnoTypeTEvent(TEventClass* pThis, ObjectClass* pObject, HouseClass* pHouse); + static bool AttachedIsUnderWebbyTEvent(ObjectClass* pObject); class ExtContainer final : public Container diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index ada1bb5eed..c58597f4c8 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #pragma region Update @@ -1012,5 +1013,8 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) pThis->ParalysisTimer.Start(duration); + if (auto pTag = pThis->AttachedTag) + pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::AttachedIsUnderWebby, pThis, CellStruct::Empty); + return 0x51804E; } From 376f56ea68f9d8cc9aea95d9c4436a70b97e5c4e Mon Sep 17 00:00:00 2001 From: FS-21 Date: Thu, 7 Aug 2025 13:20:16 +0200 Subject: [PATCH 16/21] YRpp update --- YRpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YRpp b/YRpp index 1a4e510539..2d19944aa0 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 1a4e510539459bc3535b477e2b1a873fc62821f0 +Subproject commit 2d19944aa0846a098826b4cdd2e0ac00f36915b0 From 61e809b2d3ebf30fba17727903a71af873abf75a Mon Sep 17 00:00:00 2001 From: FS-21 Date: Tue, 9 Dec 2025 15:05:53 +0100 Subject: [PATCH 17/21] Fixed a crash --- src/Ext/Techno/Body.Update.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 6719dfd1eb..f7d4f4949a 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -199,9 +199,11 @@ void TechnoExt::ExtData::WebbyUpdate() } pExt->WebbyAnim = nullptr; + pThis->Target = nullptr; + auto const pLastTarget = static_cast(pExt->WebbyLastTarget); // Restore previous action - if (pExt->WebbyLastTarget) + if (pLastTarget && pLastTarget->Health > 0 && pLastTarget->IsAlive && pLastTarget->IsOnMap) { pThis->SetDestination(pExt->WebbyLastTarget, false); pThis->SetTarget(pExt->WebbyLastTarget); @@ -209,6 +211,10 @@ void TechnoExt::ExtData::WebbyUpdate() pExt->WebbyLastTarget = nullptr; pExt->WebbyLastMission = Mission::Sleep; } + else + { + pThis->QueueMission(Mission::Guard, true); + } } } From 4a84d04bf915bc25529c98d90b7e5af59c6c61e8 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 22 Aug 2026 15:30:46 +0200 Subject: [PATCH 18/21] applied feedback --- docs/New-or-Enhanced-Logics.md | 6 +++--- src/Ext/Techno/Hooks.cpp | 9 ++++++--- src/Ext/TechnoType/Body.cpp | 4 ++-- src/Ext/TechnoType/Body.h | 8 ++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index a3dacb520c..d41590638a 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2641,7 +2641,7 @@ UnlimboDetonate.KeepSelected=false ; boolean - `Webby.Cap=-1` case: The target’s web counter is set to this absolute number of frames specified by `Web.Duration`, unless the target’s web counter is already greater than this. - `Webby.Cap=0` case: Makes this web effect stackable, but uncapped. - `Webby.Cap >0` case: Makes this web effect stackable, but maximum value capped to `Webby.Cap` value. -- Infantry can have custom values for the web logic. +- Infantry can define a duration multiplier with `Webby.Modifier` to modify the duration of the web effect applied to them, as well as an optional `Webby.DurationVariation` to override the warhead's variation. - Infantry with `ImmuneToWeb=true` are not affected by `Webby=true` warheads. - No damage is done by the weapons warhead. - `ForceWeapon.Webby` specify what weapon should use the attacker against the affected unit. @@ -2658,8 +2658,8 @@ Webby.Cap=-1 ; integer [SOMEINFANTRY] ; InfantryType ImmuneToWeb=false ; boolean Webby.Anims= ; list of animations -Webby.Duration=0 ; integer, game frames -Webby.DurationVariation=0 ; integer +Webby.Modifier=1.0 ; floating point value, duration multiplier +Webby.DurationVariation= ; integer, overrides the warhead variation if set [SOMETECHNO] ; TechnoType ForceWeapon.Webby=-1 ; integer diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 86b4eb47e7..1628468963 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -902,7 +902,7 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); - if (pTypeExt->ImmuneToWeb.Get()) + if (pTypeExt->ImmuneToWeb.Get() || pTypeExt->Webby_Modifier <= 0.0) return 0; auto const pExt = TechnoExt::ExtMap.Find(pThis); @@ -922,8 +922,8 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) } } - int duration = pTypeExt->Webby_Duration.Get() > 0 ? pTypeExt->Webby_Duration.Get() : pWarheadExt->Webby_Duration.Get(); - int durationVariation = pTypeExt->Webby_DurationVariation.Get() > 0 ? pTypeExt->Webby_DurationVariation.Get() : pWarheadExt->Webby_DurationVariation.Get(); + int duration = pWarheadExt->Webby_Duration.Get(); + int durationVariation = pTypeExt->Webby_DurationVariation.Get(pWarheadExt->Webby_DurationVariation.Get()); durationVariation = durationVariation < 0 ? 0 : durationVariation; int minDuration = duration - durationVariation; minDuration = minDuration <= 0 ? 0 : minDuration; @@ -931,6 +931,9 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) duration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); + if (pTypeExt->Webby_Modifier != 1.0) + duration = static_cast(duration * pTypeExt->Webby_Modifier); + int cap = pWarheadExt->Webby_Cap; int webbyCountDown = pExt->WebbyDurationTimer.GetTimeLeft(); diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 2997463204..28c1f15e74 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -905,7 +905,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Webby_Anims.Read(exINI, pSection, "Webby.Anims"); this->ImmuneToWeb.Read(exINI, pSection, "ImmuneToWeb"); - this->Webby_Duration.Read(exINI, pSection, "Webby.Duration"); + this->Webby_Modifier.Read(exINI, pSection, "Webby.Modifier"); this->Webby_DurationVariation.Read(exINI, pSection, "Webby.DurationVariation"); this->Convert_Deploy.Read(exINI, pSection, "Convert.Deploy"); @@ -1555,7 +1555,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->SpawnHeight) .Process(this->Webby_Anims) .Process(this->ImmuneToWeb) - .Process(this->Webby_Duration) + .Process(this->Webby_Modifier) .Process(this->Webby_DurationVariation) .Process(this->LandingDir) .Process(this->DroppodType) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 467946d1c0..877c688565 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -273,8 +273,8 @@ class TechnoTypeExt ValueableVector Webby_Anims; Valueable ImmuneToWeb; - Valueable Webby_Duration; - Valueable Webby_DurationVariation; + Valueable Webby_Modifier; + Nullable Webby_DurationVariation; Valueable Convert_Deploy; // Ares Valueable Convert_HumanToComputer; @@ -691,8 +691,8 @@ class TechnoTypeExt , Webby_Anims {} , ImmuneToWeb { false } - , Webby_Duration { 0 } - , Webby_DurationVariation { 0 } + , Webby_Modifier { 1.0 } + , Webby_DurationVariation {} , LandingDir {} , DroppodType {} , TiberiumEaterType {} From df86a40ac489e5ef787cd35df2fbbf3a355e6d5f Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 22 Aug 2026 16:32:27 +0200 Subject: [PATCH 19/21] applied feedback --- src/Ext/TEvent/Body.cpp | 2 +- src/Ext/Techno/Body.Update.cpp | 3 +-- src/Ext/Techno/Body.cpp | 6 +++++- src/Ext/Techno/Body.h | 3 +-- src/Ext/Techno/Hooks.cpp | 9 ++------- src/Ext/TechnoType/Body.cpp | 2 +- src/Ext/Unit/Hooks.DisallowMoving.cpp | 2 +- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Ext/TEvent/Body.cpp b/src/Ext/TEvent/Body.cpp index ab63588e34..9e97017ab1 100644 --- a/src/Ext/TEvent/Body.cpp +++ b/src/Ext/TEvent/Body.cpp @@ -349,7 +349,7 @@ bool TEventExt::AttachedIsUnderWebbyTEvent(ObjectClass* pObject) if (const auto pTechno = abstract_cast(pObject)) { const auto pExt = TechnoExt::Fetch(pTechno); - return pExt->WebbyDurationCountDown > 0; + return pExt->IsWebbed(); } return false; diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index d4acebc87f..9c848ee916 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -26,7 +26,7 @@ void TechnoExt::OnEarlyUpdate() if (this->CheckDeathConditions()) return; - if (this->WebbyAnim) + if (this->WebbyDurationTimer.IsTicking()) this->WebbyUpdate(); this->ApplyInterceptor(); @@ -146,7 +146,6 @@ void TechnoExt::WebbyUpdate() if (this->WebbyDurationTimer.Completed()) { - this->WebbyDurationCountDown = -1; this->WebbyDurationTimer.Stop(); if (this->WebbyAnim && this->WebbyAnim->Type) // If this anim doesn't have a type pointer, just detach it diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index ac4faf024b..199c65ab41 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -1104,6 +1104,11 @@ int TechnoExt::GetSight() return static_cast(sight); } +bool TechnoExt::IsWebbed() const +{ + return this->WebbyDurationTimer.HasTimeLeft(); +} + bool TechnoExt::CanReceiveEvent(TechnoClass* pThis, HouseClass* pHouse) { if (pThis->Berzerk) @@ -1218,7 +1223,6 @@ void TechnoExt::Serialize(T& Stm) .Process(this->LastTargetCrdClearTimer) .Process(this->ShouldBeDead) .Process(this->PreventCrewEscape) - .Process(this->WebbyDurationCountDown) .Process(this->WebbyDurationTimer) .Process(this->WebbyAnim) .Process(this->WebbyLastTarget) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index ae4a922807..a63e45f633 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -83,7 +83,6 @@ class TechnoExt : public RadioExt, public Detach::Listener bool ShouldBeDead; - int WebbyDurationCountDown; CDTimerClass WebbyDurationTimer; AnimClass* WebbyAnim; AbstractClass* WebbyLastTarget; @@ -140,7 +139,6 @@ class TechnoExt : public RadioExt, public Detach::Listener , LastTargetCrd { CoordStruct::Empty } , LastTargetCrdClearTimer {} , ShouldBeDead { false } - , WebbyDurationCountDown { -1 } , WebbyDurationTimer {} , WebbyAnim { nullptr } , WebbyLastTarget { nullptr } @@ -195,6 +193,7 @@ class TechnoExt : public RadioExt, public Detach::Listener void UpdateLastTargetCrd(); void WebbyUpdate(); int GetSight(); + bool IsWebbed() const; static bool CanReceiveEvent(TechnoClass* pThis, HouseClass* pHouse); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 2a151629a4..27dfe5ec70 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1111,13 +1111,9 @@ DEFINE_HOOK(0x518FBC, InfantryClass_DrawIt_DontRenderSHP, 0x6) if (!pThis) return 0; - auto pTechno = static_cast(pThis); - if (!pTechno) - return 0; - - auto pExt = TechnoExt::Fetch(pTechno); + auto pExt = TechnoExt::Fetch(pThis); - if (pExt->WebbyDurationCountDown > 0) + if (pExt->IsWebbed()) return SkipDrawCode; return 0; @@ -1201,7 +1197,6 @@ DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) pExt->WebbyLastTarget = pThis->Target; pExt->WebbyLastMission = pThis->CurrentMission; - pExt->WebbyDurationCountDown = duration; pExt->WebbyDurationTimer.Start(duration); if (pThis->Locomotor && pThis->Locomotor->Is_Moving()) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 2f7ac3c546..a67abff821 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -100,7 +100,7 @@ int TechnoTypeExt::SelectForceWeapon(TechnoClass* pThis, AbstractClass* pTarget) { const auto pTargetExt = TechnoExt::Fetch(pTargetTechno); - if (pTargetExt->WebbyAnim) + if (pTargetExt->IsWebbed()) forceWeaponIndex = this->ForceWeapon_Webby; } } diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp index 026328c224..9ccc13c59a 100644 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ b/src/Ext/Unit/Hooks.DisallowMoving.cpp @@ -91,7 +91,7 @@ DEFINE_HOOK(0x51AA49, InfantryClass_Assign_Destination_DisallowMoving, 0x6) const auto pExt = TechnoExt::Fetch(pThis); - if (pExt->WebbyDurationCountDown > 0) + if (pExt->IsWebbed()) { if (pThis->Target) { From bedf1072df1e5069f1901803b3fa9c638880660d Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 22 Aug 2026 18:50:36 +0200 Subject: [PATCH 20/21] applied feedback --- src/Ext/Techno/Hooks.cpp | 91 -------------------------------- src/Ext/WarheadType/Body.cpp | 1 + src/Ext/WarheadType/Body.h | 1 + src/Ext/WarheadType/Detonate.cpp | 89 +++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 91 deletions(-) diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 27dfe5ec70..d18a13a660 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1119,97 +1119,6 @@ DEFINE_HOOK(0x518FBC, InfantryClass_DrawIt_DontRenderSHP, 0x6) return 0; } -DEFINE_HOOK(0x518016, InfantryClass_TakeDamage_Webby, 0x7) -{ - GET(InfantryClass* const, pThis, ESI); - REF_STACK(args_ReceiveDamage const, receiveDamageArgs, STACK_OFFSET(0xD0, 0x4)); - - if (!receiveDamageArgs.WH) - return 0; - - auto const pWarheadExt = WarheadTypeExt::Fetch(receiveDamageArgs.WH); - if (!pWarheadExt || !pWarheadExt->Webby || pWarheadExt->Webby_Duration == 0 || pWarheadExt->Webby_Anims.size() == 0) - return 0; - - auto const pTypeExt = TechnoTypeExt::Fetch(pThis->GetTechnoType()); - - if (pTypeExt->ImmuneToWeb.Get() || pTypeExt->Webby_Modifier <= 0.0) - return 0; - - auto const pExt = TechnoExt::Fetch(pThis); - - if (!pExt->WebbyAnim) - { - bool hasCustomAnims = pTypeExt->Webby_Anims.size() > 0; - int max = hasCustomAnims ? pTypeExt->Webby_Anims.size() - 1 : pWarheadExt->Webby_Anims.size() - 1; - int selectedIndex = ScenarioClass::Instance->Random.RandomRanged(0, max); - auto const pAnimType = hasCustomAnims ? pTypeExt->Webby_Anims[selectedIndex] : pWarheadExt->Webby_Anims[selectedIndex]; - auto const pAnim = GameCreate(pAnimType, pThis->Location, 0, 1, 0x600, 0, false); - - if (pAnim) - { - pExt->WebbyAnim = pAnim; - pExt->WebbyAnim->SetOwnerObject(pThis); - } - } - - int duration = pWarheadExt->Webby_Duration.Get(); - int durationVariation = pTypeExt->Webby_DurationVariation.Get(pWarheadExt->Webby_DurationVariation.Get()); - durationVariation = durationVariation < 0 ? 0 : durationVariation; - int minDuration = duration - durationVariation; - minDuration = minDuration <= 0 ? 0 : minDuration; - int maxDuration = duration + durationVariation; - - duration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); - - if (pTypeExt->Webby_Modifier != 1.0) - duration = static_cast(duration * pTypeExt->Webby_Modifier); - - int cap = pWarheadExt->Webby_Cap; - int webbyCountDown = pExt->WebbyDurationTimer.GetTimeLeft(); - - if (cap == 0) - { - // Makes this Web effect stackable, but uncapped - duration += webbyCountDown; - } - else if (cap > 0) - { - if (webbyCountDown > cap) - { - // If current duration effect is greater than the new attempt don't change the values - duration = webbyCountDown; - } - else - { - // Makes this Web effect stackable, but capped - duration += webbyCountDown; - duration = duration > cap ? cap : duration; - } - } - else - { - // Cap=-1 case: The target’s Web counter is set to this absolute number of frames specified by Web.Duration, unless the target’s Web counter is already greater than this - if (webbyCountDown > duration) - duration = webbyCountDown; - } - - pExt->WebbyLastTarget = pThis->Target; - pExt->WebbyLastMission = pThis->CurrentMission; - - pExt->WebbyDurationTimer.Start(duration); - - if (pThis->Locomotor && pThis->Locomotor->Is_Moving()) - pThis->Locomotor->Stop_Moving(); - - pThis->ParalysisTimer.Start(duration); - - if (auto pTag = pThis->AttachedTag) - pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::AttachedIsUnderWebby, pThis, CellStruct::Empty); - - return 0x51804E; -} - #pragma region SetTarget DEFINE_HOOK_AGAIN(0x4DF3D3, FootClass_UpdateAttackMove_SetTarget, 0xA) diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index c569bafebb..2bc13b0e3e 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -523,6 +523,7 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI) || this->PenetratesTransport_Level > 0 || this->Taunt || this->Ammo + || this->Webby ); char tempBuffer[32]; diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index e1b71b0a1a..b5383df6c3 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -626,6 +626,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 ApplyWebby(TechnoClass* pTarget); public: class ExtContainer final : public Container diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index d8bb3d432f..f781b9c6f5 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -236,6 +240,9 @@ void WarheadTypeExt::DetonateOnOneUnit(HouseClass* pHouse, TechnoClass* pTarget, if (this->Taunt && pOwner) pTarget->Override_Mission(Mission::Attack, pOwner, nullptr); + if (this->Webby) + this->ApplyWebby(pTarget); + // This might change the target's armor type this->ApplyShieldModifiers(pTarget); @@ -931,3 +938,85 @@ void WarheadTypeExt::ExtData::ApplyAmmoModifier(TechnoClass* pTarget) newCurrentAmmo = newCurrentAmmo < 0 ? 0 : newCurrentAmmo; pTarget->Ammo = newCurrentAmmo > maxAmmo ? maxAmmo : newCurrentAmmo; } + +void WarheadTypeExt::ApplyWebby(TechnoClass* pTarget) +{ + auto const pInf = abstract_cast(pTarget); + if (!pInf || this->Webby_Duration <= 0) + return; + + auto const pTypeExt = TechnoTypeExt::Fetch(pInf->Type); + if (pTypeExt->ImmuneToWeb.Get() || pTypeExt->Webby_Modifier <= 0.0) + return; + + auto const pExt = TechnoExt::Fetch(pInf); + + if (!pExt->WebbyAnim && (this->Webby_Anims.size() > 0 || pTypeExt->Webby_Anims.size() > 0)) + { + bool hasCustomAnims = pTypeExt->Webby_Anims.size() > 0; + int max = hasCustomAnims ? pTypeExt->Webby_Anims.size() - 1 : this->Webby_Anims.size() - 1; + int selectedIndex = ScenarioClass::Instance->Random.RandomRanged(0, max); + auto const pAnimType = hasCustomAnims ? pTypeExt->Webby_Anims[selectedIndex] : this->Webby_Anims[selectedIndex]; + auto const pAnim = GameCreate(pAnimType, pInf->Location, 0, 1, 0x600, 0, false); + + if (pAnim) + { + pExt->WebbyAnim = pAnim; + pExt->WebbyAnim->SetOwnerObject(pInf); + } + } + + int duration = this->Webby_Duration.Get(); + int durationVariation = pTypeExt->Webby_DurationVariation.Get(this->Webby_DurationVariation.Get()); + durationVariation = durationVariation < 0 ? 0 : durationVariation; + int minDuration = duration - durationVariation; + minDuration = minDuration <= 0 ? 0 : minDuration; + int maxDuration = duration + durationVariation; + + duration = ScenarioClass::Instance->Random.RandomRanged(minDuration, maxDuration); + + if (pTypeExt->Webby_Modifier != 1.0) + duration = static_cast(duration * pTypeExt->Webby_Modifier); + + int cap = this->Webby_Cap; + int webbyCountDown = pExt->WebbyDurationTimer.GetTimeLeft(); + + if (cap == 0) + { + // Makes this Web effect stackable, but uncapped + duration += webbyCountDown; + } + else if (cap > 0) + { + if (webbyCountDown > cap) + { + // If current duration effect is greater than the new attempt don't change the values + duration = webbyCountDown; + } + else + { + // Makes this Web effect stackable, but capped + duration += webbyCountDown; + duration = duration > cap ? cap : duration; + } + } + else + { + // Cap=-1 case: The target’s Web counter is set to this absolute number of frames specified by Web.Duration, unless the target’s Web counter is already greater than this + if (webbyCountDown > duration) + duration = webbyCountDown; + } + + pExt->WebbyLastTarget = pInf->Target; + pExt->WebbyLastMission = pInf->CurrentMission; + + pExt->WebbyDurationTimer.Start(duration); + + if (pInf->Locomotor && pInf->Locomotor->Is_Moving()) + pInf->Locomotor->Stop_Moving(); + + pInf->ParalysisTimer.Start(duration); + + if (auto pTag = pInf->AttachedTag) + pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::AttachedIsUnderWebby, pInf, CellStruct::Empty); +} From c7040a247c597406a25acd07e59d5ca4a2e997d7 Mon Sep 17 00:00:00 2001 From: FS-21 Date: Sat, 22 Aug 2026 19:00:03 +0200 Subject: [PATCH 21/21] . --- docs/New-or-Enhanced-Logics.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 625cc5b639..186a703565 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -3332,7 +3332,6 @@ UnlimboDetonate.KeepSelected=true ; boolean - `Webby.Cap >0` case: Makes this web effect stackable, but maximum value capped to `Webby.Cap` value. - Infantry can define a duration multiplier with `Webby.Modifier` to modify the duration of the web effect applied to them, as well as an optional `Webby.DurationVariation` to override the warhead's variation. - Infantry with `ImmuneToWeb=true` are not affected by `Webby=true` warheads. -- No damage is done by the weapons warhead. - `ForceWeapon.Webby` specify what weapon should use the attacker against the affected unit. In `rulesmd.ini`: