diff --git a/CREDITS.md b/CREDITS.md index 3ecead2d75..7a3a4e06ec 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -159,6 +159,7 @@ This page lists all the individual contributions to the project by their author. - Event 606: AttachEffect is attaching to a Techno - Linked superweapons - Unit & infantry auto-conversion on ammo change + - Web logic against infantry - Restore the ScriptType action#24 `Play speech` from Tiberian Sun - Modify ammo on impact - **Starkku**: diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index 074d3917e2..5eb5f4ba45 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -1039,6 +1039,16 @@ ID=EventCount,...,606,2,0,[AttachEffectType],... ... ``` +### `607` A webby weapon hit the tagged infantry + +In `mycampaign.map`: +```ini +[Events] +... +ID=EventCount,...,607,0,0,... +... +``` + ## Teams ### Adjust recruitable status on team member liberate diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 343dae63eb..212f0a2b84 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -3371,6 +3371,43 @@ UnlimboDetonate.KeepSelected=true ; boolean `UnlimboDetonate` cannot be used in conjunction with `Parasite`. ``` +### Web logic against infantry + +- 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. +- `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 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. +- `ForceWeapon.Webby` specify what weapon should use the attacker against the affected unit. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; Warhead +Webby=false ; boolean +Webby.Anims= ; list of animations +Webby.Duration=0 ; integer, game frames +Webby.DurationVariation=0 ; integer +Webby.Cap=-1 ; integer + +[SOMEINFANTRY] ; InfantryType +ImmuneToWeb=false ; boolean +Webby.Anims= ; list of animations +Webby.Modifier=1.0 ; floating point value, duration multiplier +Webby.DurationVariation= ; integer, overrides the warhead variation if set + +[SOMETECHNO] ; TechnoType +ForceWeapon.Webby=-1 ; integer +``` + +```{warning} +`Webby.Anims` animations must be played in infinite loop with `LoopCount=-1` in artmd.ini +``` + ### Customize whether warhead can prevent crew escape from techno - Now you can customize on warheads whether to prevent survivors/passengers/occupant infantry from appearing when destroying a target. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 68b6030d64..8e548796db 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -421,6 +421,8 @@ HideShakeEffects=false ; boolean #### New: - [Customized transport plane for teams](AI-Scripting-and-Mapping.md#customized-transport-plane-for-teams) (by FlyStar) - [Modify ammo on impact](New-or-Enhanced-Logics.md#modify-ammo-on-impact) (by FS-21) +- [Web logic against infantry](New-or-Enhanced-Logics.md#web-logic-against-infantry) (by FS-21) +- [Event 607: A webby weapon hit the tagged infantry](AI-Scripting-and-Mapping.md#607-a-webby-weapon-hit-the-tagged-infantry) (by FS-21) - [Customize whether mind-controlled Insignificant technos can be auto-targeted](Fixed-or-Improved-Logics.md#customize-whether-mind-controlled-Insignificant-technos-can-be-auto-targeted) (by Noble_Fish) - [AutoDeath based on player power status and player credits](New-or-Enhanced-Logics.md#kill-object-automatically) (by Flactine) - [Roof production anim](New-or-Enhanced-Logics.md#roof-production-anim) (by Noble_Fish) @@ -1026,6 +1028,7 @@ HideShakeEffects=false ; boolean - Allow toggling `Infantry/UnitsGainSelfHeal` for `MultiplayPassive=true` houses (by Starkku) - Customizable straight trajectory detonation & snap distance and pass-through option (by Starkku) - Airstrike & spy plane fixed spawn distance & height (by Starkku) +- Web logic against infantry (FS-21) - Allow enabling application of `Verses` and `PercentAtMax` for negative damage (by Starkku) - In addition to `PlacementGrid.Translucency`, allow to set the transparency of the grid when `PlacementPreview` is enabled, using the `PlacementGrid.TranslucencyWithPreview` tag (by Belonit) - Show briefing screen on singleplayer mission start (by Starkku) diff --git a/src/Ext/TEvent/Body.cpp b/src/Ext/TEvent/Body.cpp index c7cd3cee94..9e97017ab1 100644 --- a/src/Ext/TEvent/Body.cpp +++ b/src/Ext/TEvent/Body.cpp @@ -147,6 +147,8 @@ std::optional TEventExt::Execute(TEventClass* pThis, int iEvent, HouseClas return TEventExt::CellHasAnyTechnoTypeFromListTEvent(pThis, pObject, pHouse); case PhobosTriggerEvent::AttachedIsUnderAttachedEffect: return TEventExt::AttachedIsUnderAttachedEffectTEvent(pThis, pObject); + 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. @@ -339,6 +341,20 @@ bool TEventExt::AttachedIsUnderAttachedEffectTEvent(TEventClass* pThis, ObjectCl return false; } +bool TEventExt::AttachedIsUnderWebbyTEvent(ObjectClass* pObject) +{ + if (!pObject) + return false; + + if (const auto pTechno = abstract_cast(pObject)) + { + const auto pExt = TechnoExt::Fetch(pTechno); + return pExt->IsWebbed(); + } + + return false; +} + // ============================= // container diff --git a/src/Ext/TEvent/Body.h b/src/Ext/TEvent/Body.h index b54f1755c7..a795155da6 100644 --- a/src/Ext/TEvent/Body.h +++ b/src/Ext/TEvent/Body.h @@ -52,6 +52,7 @@ enum PhobosTriggerEvent CellHasTechnoType = 604, CellHasAnyTechnoTypeFromList = 605, AttachedIsUnderAttachedEffect = 606, + AttachedIsUnderWebby = 607, _DummyMaximum, }; @@ -102,6 +103,7 @@ class TEventExt final : public AbstractExt static bool CellHasAnyTechnoTypeFromListTEvent(TEventClass* pThis, ObjectClass* pObject, HouseClass* pHouse); static bool CellHasTechnoTypeTEvent(TEventClass* pThis, ObjectClass* pObject, HouseClass* pHouse); + static bool AttachedIsUnderWebbyTEvent(ObjectClass* pObject); static bool AttachedIsUnderAttachedEffectTEvent(TEventClass* pThis, ObjectClass* pObject); diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index e5782970f6..2e7fb44366 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -26,6 +26,9 @@ void TechnoExt::OnEarlyUpdate() if (this->CheckDeathConditions()) return; + if (this->WebbyDurationTimer.IsTicking()) + this->WebbyUpdate(); + this->ApplyInterceptor(); } @@ -134,6 +137,43 @@ void TechnoExt::ApplyInterceptor() } } +void TechnoExt::WebbyUpdate() +{ + auto const pThis = this->OwnerObject(); + + if (!TechnoExt::IsActive(pThis) || pThis->WhatAmI() != AbstractType::Infantry) + return; + + if (this->WebbyDurationTimer.Completed()) + { + this->WebbyDurationTimer.Stop(); + + if (this->WebbyAnim && this->WebbyAnim->Type) // If this anim doesn't have a type pointer, just detach it + { + this->WebbyAnim->TimeToDie = true; + this->WebbyAnim->UnInit(); + } + + this->WebbyAnim = nullptr; + pThis->Target = nullptr; + auto const pLastTarget = static_cast(this->WebbyLastTarget); + + // Restore previous action + if (pLastTarget && pLastTarget->Health > 0 && pLastTarget->IsAlive && pLastTarget->IsOnMap) + { + pThis->SetDestination(this->WebbyLastTarget, false); + pThis->SetTarget(this->WebbyLastTarget); + pThis->QueueMission(this->WebbyLastMission, true); + this->WebbyLastTarget = nullptr; + this->WebbyLastMission = Mission::Sleep; + } + else + { + pThis->QueueMission(Mission::Guard, true); + } + } +} + // TODO : Merge into new AttachEffects bool TechnoExt::CheckDeathConditions(bool isInLimbo) { diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 1615942f11..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,6 +1223,10 @@ void TechnoExt::Serialize(T& Stm) .Process(this->LastTargetCrdClearTimer) .Process(this->ShouldBeDead) .Process(this->PreventCrewEscape) + .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 57a79aa69e..06d80aa1cd 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -83,6 +83,11 @@ class TechnoExt : public RadioExt, public Detach::Listener bool ShouldBeDead; + CDTimerClass WebbyDurationTimer; + AnimClass* WebbyAnim; + AbstractClass* WebbyLastTarget; + Mission WebbyLastMission; + int DropCrate; // Drop crate on death, modified by map action Powerup DropCrateType; @@ -134,6 +139,10 @@ class TechnoExt : public RadioExt, public Detach::Listener , LastTargetCrd { CoordStruct::Empty } , LastTargetCrdClearTimer {} , ShouldBeDead { false } + , WebbyDurationTimer {} + , WebbyAnim { nullptr } + , WebbyLastTarget { nullptr } + , WebbyLastMission { Mission::Sleep } , DropCrate { -1 } , DropCrateType { Powerup::Money } , PreventCrewEscape { false } @@ -182,7 +191,9 @@ class TechnoExt : public RadioExt, public Detach::Listener void ResetDelayedFireTimer(); void UpdateTintValues(); 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 0dcfa8e9da..44071fe711 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #pragma region GetTechnoType @@ -1101,6 +1102,23 @@ DEFINE_HOOK(0x5F4021, ObjectClass_Update_FallingDown_ToDead, 0x6) #pragma endregion +DEFINE_HOOK(0x518FBC, InfantryClass_DrawIt_DontRenderSHP, 0x6) +{ + enum { SkipDrawCode = 0x5192B5 }; + + GET(InfantryClass*, pThis, EBP); + + if (!pThis) + return 0; + + auto pExt = TechnoExt::Fetch(pThis); + + if (pExt->IsWebbed()) + return SkipDrawCode; + + return 0; +} + #pragma region SetTarget DEFINE_HOOK_AGAIN(0x4DF3D3, FootClass_UpdateAttackMove_SetTarget, 0xA) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index ae05a36241..93860d6eda 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -96,6 +96,13 @@ int TechnoTypeExt::SelectForceWeapon(TechnoClass* pThis, AbstractClass* pTarget) { forceWeaponIndex = this->ForceWeapon_UnderEMP; } + else if (this->ForceWeapon_Webby >= 0) + { + const auto pTargetExt = TechnoExt::Fetch(pTargetTechno); + + if (pTargetExt->IsWebbed()) + forceWeaponIndex = this->ForceWeapon_Webby; + } } if (forceWeaponIndex == -1 @@ -921,6 +928,7 @@ void TechnoTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->ForceAAWeapon_InRange.Read(exINI, pSection, "ForceAAWeapon.InRange"); this->ForceAAWeapon_InRange_Overrides.Read(exINI, pSection, "ForceAAWeapon.InRange.Overrides"); this->ForceAAWeapon_InRange_ApplyRangeModifiers.Read(exINI, pSection, "ForceAAWeapon.InRange.ApplyRangeModifiers"); + this->ForceWeapon_Webby.Read(exINI, pSection, "ForceWeapon.Webby"); this->ForceWeapon_Buildings.Read(exINI, pSection, "ForceWeapon.Buildings"); this->ForceWeapon_Defenses.Read(exINI, pSection, "ForceWeapon.Defenses"); this->ForceWeapon_Infantry.Read(exINI, pSection, "ForceWeapon.Infantry"); @@ -936,6 +944,7 @@ void TechnoTypeExt::LoadFromINIFile(CCINIClass* const pINI) || this->ForceWeapon_Cloaked >= 0 || this->ForceWeapon_Disguised >= 0 || this->ForceWeapon_UnderEMP >= 0 + || this->ForceWeapon_Webby >= 0 || !this->ForceWeapon_InRange.empty() || !this->ForceAAWeapon_InRange.empty() || this->ForceWeapon_Buildings >= 0 @@ -1008,6 +1017,11 @@ void TechnoTypeExt::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_Modifier.Read(exINI, pSection, "Webby.Modifier"); + this->Webby_DurationVariation.Read(exINI, pSection, "Webby.DurationVariation"); + this->Convert_Undeploy.Read(exINI, pSection, "Convert.Undeploy"); this->Convert_HumanToComputer.Read(exINI, pSection, "Convert.HumanToComputer"); this->Convert_ComputerToHuman.Read(exINI, pSection, "Convert.ComputerToHuman"); @@ -1586,6 +1600,7 @@ void TechnoTypeExt::Serialize(T& Stm) .Process(this->ForceAAWeapon_InRange) .Process(this->ForceAAWeapon_InRange_Overrides) .Process(this->ForceAAWeapon_InRange_ApplyRangeModifiers) + .Process(this->ForceWeapon_Webby) .Process(this->ForceWeapon_Buildings) .Process(this->ForceWeapon_Defenses) .Process(this->ForceWeapon_Infantry) @@ -1647,6 +1662,10 @@ void TechnoTypeExt::Serialize(T& Stm) .Process(this->SpawnsPipSize) .Process(this->SpawnsPipOffset) + .Process(this->Webby_Anims) + .Process(this->ImmuneToWeb) + .Process(this->Webby_Modifier) + .Process(this->Webby_DurationVariation) .Process(this->DroppodType) .Process(this->TiberiumEaterType) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index c7c2ba9ed6..5791165605 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -194,6 +194,7 @@ class TechnoTypeExt : public ObjectTypeExt Valueable ForceAAWeapon_Infantry; Valueable ForceAAWeapon_Units; Valueable ForceAAWeapon_Aircraft; + Valueable ForceWeapon_Webby; Valueable Ammo_Shared; Valueable Ammo_Shared_Group; @@ -246,6 +247,11 @@ class TechnoTypeExt : public ObjectTypeExt Nullable SpawnsPipSize; Valueable SpawnsPipOffset; + ValueableVector Webby_Anims; + Valueable ImmuneToWeb; + Valueable Webby_Modifier; + Nullable Webby_DurationVariation; + Valueable Convert_Undeploy; Valueable Convert_HumanToComputer; Valueable Convert_ComputerToHuman; @@ -613,6 +619,7 @@ class TechnoTypeExt : public ObjectTypeExt , ForceAAWeapon_Infantry { -1 } , ForceAAWeapon_Units { -1 } , ForceAAWeapon_Aircraft { -1 } + , ForceWeapon_Webby { -1 } , Ammo_Shared { false } , Ammo_Shared_Group { -1 } @@ -666,6 +673,11 @@ class TechnoTypeExt : public ObjectTypeExt , SpawnsPipSize {} , SpawnsPipOffset { { 0,0 } } + , Webby_Anims {} + , ImmuneToWeb { false } + , Webby_Modifier { 1.0 } + , Webby_DurationVariation {} + , DroppodType {} , TiberiumEaterType {} diff --git a/src/Ext/Unit/Hooks.DisallowMoving.cpp b/src/Ext/Unit/Hooks.DisallowMoving.cpp index b3d701abd7..9ccc13c59a 100644 --- a/src/Ext/Unit/Hooks.DisallowMoving.cpp +++ b/src/Ext/Unit/Hooks.DisallowMoving.cpp @@ -2,6 +2,7 @@ // Author: Starkku #include "Body.h" +#include DEFINE_HOOK(0x740A93, UnitClass_Mission_Move_DisallowMoving, 0x6) { @@ -81,6 +82,28 @@ DEFINE_HOOK(0x73891D, UnitClass_Active_Click_With_DisallowMoving, 0x6) return UnitExt::CannotMove(pThis) ? 0x738927 : 0; } +DEFINE_HOOK(0x51AA49, InfantryClass_Assign_Destination_DisallowMoving, 0x6) +{ + GET(InfantryClass*, pThis, ECX); + + if (pThis->ParalysisTimer.HasTimeLeft()) + return 0x51B1D7; + + const auto pExt = TechnoExt::Fetch(pThis); + + if (pExt->IsWebbed()) + { + if (pThis->Target) + { + pThis->SetTarget(nullptr); + pThis->SetDestination(nullptr, false); + pThis->QueueMission(Mission::Sleep, false); + } + } + + return 0; +} + DEFINE_HOOK(0x73EFC4, UnitClass_Mission_Hunt_DisallowMoving, 0x6) { GET(UnitClass*, pThis, ESI); diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index adf07d1090..2bc13b0e3e 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -399,6 +399,12 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->AirstrikeTargets.Read(exINI, pSection, "AirstrikeTargets"); + 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"); + this->Webby_Cap.Read(exINI, pSection, "Webby.Cap"); + this->AffectsBelowPercent.Read(exINI, pSection, "AffectsBelowPercent"); this->AffectsAbovePercent.Read(exINI, pSection, "AffectsAbovePercent"); this->AffectsVeterancy.Read(exINI, pSection, "AffectsVeterancy"); @@ -517,6 +523,7 @@ void WarheadTypeExt::LoadFromINIFile(CCINIClass* const pINI) || this->PenetratesTransport_Level > 0 || this->Taunt || this->Ammo + || this->Webby ); char tempBuffer[32]; @@ -776,7 +783,11 @@ void WarheadTypeExt::Serialize(T& Stm) .Process(this->AirstrikeTargets) - .Process(this->CanKill) + .Process(this->Webby) + .Process(this->Webby_Anims) + .Process(this->Webby_Duration) + .Process(this->Webby_DurationVariation) + .Process(this->Webby_Cap) .Process(this->ReverseEngineer) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 7ee03e1291..b5383df6c3 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -222,6 +222,12 @@ class WarheadTypeExt final : public AbstractTypeExt Valueable AirstrikeTargets; + Valueable Webby; + ValueableVector Webby_Anims; + Valueable Webby_Duration; + Valueable Webby_DurationVariation; + Valueable Webby_Cap; + Valueable AffectsBelowPercent; Valueable AffectsAbovePercent; Valueable AffectsVeterancy; @@ -493,6 +499,11 @@ class WarheadTypeExt final : public AbstractTypeExt , ElectricAssaultLevel { 1 } , AirstrikeTargets { AffectedTarget::Building } + , Webby { false } + , Webby_Anims {} + , Webby_Duration { 0 } + , Webby_DurationVariation { 0 } + , Webby_Cap { -1 } , AffectsBelowPercent { 1.0 } , AffectsAbovePercent { 0.0 } @@ -615,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); +}