diff --git a/CREDITS.md b/CREDITS.md index 692247f19a..d6382266b1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -499,6 +499,7 @@ This page lists all the individual contributions to the project by their author. - Damaged unit image changes - `VoiceDeploy` through hot-key/command bar fix - Damaged aircraft image changes + - Change target Owner on warhead impact - **ZivDero**: - Re-enable the Veinhole Monster and Weeds from TS - Recreate the weed-charging of SWs like the TS Chemical Missile diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 6f6d23aaa1..9bfd9b7d6d 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2316,6 +2316,25 @@ ImmuneToCrit=false ; boolean If you set `Crit.Warhead` to the same Warhead it is defined on, or create a chain of Warheads with it that loops back to the first one there is a possibility for the game to get stuck in a loop and freeze or crash afterwards. ``` +### Change target Owner on impact + +- Warheads can now change targets owner to warhead's owner. +- `ChangeOwner.SetAsMindControl` makes the effect work like permanent mind control, which respects `ImmuneToPsionics`. + - `ChangeOwner.MindControlAnim` determines the mind control anim of this effect, which respects `MindControlRingOffset`. +- If `ChangeOwner.Duration` is set, the target will switch back to its original owner after these many frames has passed. Can't be used on effect with `ChangeOwner.SetAsMindControl=true`. + - If another house changing effect happens within the duration (f. ex. capture, mind control, other `ChangeOwner=true` warheads), this switchback of owner won't happen. + - `ChangeOwner.Duration.PreventChange` determines whether another change owner warhead effect can be applied to the target within the duration of current effect. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +ChangeOwner=false ; boolean +ChangeOwner.SetAsMindControl=false ; boolean +ChangeOwner.MindControlAnim= ; Animation +ChangeOwner.Duration= ; interger +ChangeOwner.Duration.PreventChange=false ; boolean +``` + ### Convert TechnoType on impact ![image](_static/images/convertwh.gif) diff --git a/docs/Whats-New.md b/docs/Whats-New.md index ef5401ed78..4c45d1b683 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -471,6 +471,7 @@ New: - AutoDeath upon ownership change (by Ollerus) - [Script Action 14004 for forcing all new actions to target only the main owner's enemy](AI-Scripting-and-Mapping.md#force-global-onlytargethouseenemy-value-in-teams-for-new-attack-move-actions-introduced-by-phobos) (by FS-21) - [Allow merging AOE damage to buildings into one](New-or-Enhanced-Logics.md#allow-merging-aoe-damage-to-buildings-into-one) (by CrimRecya) +- Change target Owner on warhead impact (by Fryone) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index 2152239bd1..245290dbfa 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -314,6 +314,8 @@ DEFINE_HOOK(0x7015C9, TechnoClass_Captured_UpdateTracking, 0x6) pTrail->CurrentColor = pNewOwner->LaserColor; } + pExt->OwnerTimer.Stop(); + return 0; } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 1be8e43495..a0f66a0e20 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -43,6 +43,7 @@ void TechnoExt::ExtData::OnEarlyUpdate() this->ApplyMindControlRangeLimit(); this->UpdateRecountBurst(); this->UpdateRearmInEMPState(); + this->UpdateOwnerTimer(); if (this->AttackMoveFollowerTempCount) this->AttackMoveFollowerTempCount--; @@ -2136,3 +2137,12 @@ void TechnoExt::ExtData::UpdateTintValues() calculateTint(Drawing::RGB_To_Int(pShieldType->Tint_Color), static_cast(pShieldType->Tint_Intensity * 1000), pShieldType->Tint_VisibleToHouses); } } + +void TechnoExt::ExtData::UpdateOwnerTimer() +{ + if (this->OwnerTimer.Completed()) + { + this->OwnerObject()->SetOwningHouse(this->OwnerOriginalOwner); + this->ImmuneToChangeOwner = false; + } +} diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 8ddfcf20f1..8f692693a4 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -840,7 +840,7 @@ bool TechnoExt::SimpleDeployerAllowedToDeploy(UnitClass* pThis, bool defaultValu const bool isLander = pType->DeployToLand && (isJumpjet || isHover); disallowedLandTypes = isLander ? (LandTypeFlags)(LandTypeFlags::Water | LandTypeFlags::Beach) : LandTypeFlags::None; } - + if (IsLandTypeInFlags(disallowedLandTypes, pThis->GetCell()->LandType)) return false; @@ -947,6 +947,9 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->SpecialTracked) .Process(this->FallingDownTracked) .Process(this->JumpjetStraightAscend) + .Process(this->OwnerTimer) + .Process(this->OwnerOriginalOwner) + .Process(this->ImmuneToChangeOwner) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 89ed549edf..149d04e4b2 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -102,6 +102,9 @@ class TechnoExt bool FallingDownTracked; bool JumpjetStraightAscend; // Is set to true jumpjet units will ascend straight and do not adjust rotation or position during it. + CDTimerClass OwnerTimer; + HouseClass* OwnerOriginalOwner; + bool ImmuneToChangeOwner; ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } @@ -169,6 +172,9 @@ class TechnoExt , SpecialTracked { false } , FallingDownTracked { false } , JumpjetStraightAscend { false } + , OwnerTimer {} + , OwnerOriginalOwner {} + , ImmuneToChangeOwner { false } { } void OnEarlyUpdate(); @@ -207,6 +213,7 @@ class TechnoExt int ApplyForceWeaponInRange(AbstractClass* pTarget); void ResetDelayedFireTimer(); void UpdateTintValues(); + void UpdateOwnerTimer(); void AmmoAutoConvertActions(); diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index b0dc81fb02..9284521e70 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -310,6 +310,12 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AnimZAdjust.Read(exINI, pSection, "AnimZAdjust"); + this->ChangeOwner.Read(exINI, pSection, "ChangeOwner"); + this->ChangeOwner_SetAsMindControl.Read(exINI, pSection, "ChangeOwner.SetAsMindControl"); + this->ChangeOwner_MindControlAnim.Read(exINI, pSection, "ChangeOwner.MindControlAnim"); + this->ChangeOwner_Duration.Read(exINI, pSection, "ChangeOwner.Duration"); + this->ChangeOwner_Duration_PreventChange.Read(exINI, pSection, "ChangeOwner.Duration.PreventChange"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -361,6 +367,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) || this->Convert_Pairs.size() > 0 || this->InflictLocomotor || this->RemoveInflictedLocomotor + || this->ChangeOwner || this->AttachEffects.AttachTypes.size() > 0 || this->AttachEffects.RemoveTypes.size() > 0 || this->AttachEffects.RemoveGroups.size() > 0 @@ -603,6 +610,12 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->AnimZAdjust) + .Process(this->ChangeOwner) + .Process(this->ChangeOwner_SetAsMindControl) + .Process(this->ChangeOwner_MindControlAnim) + .Process(this->ChangeOwner_Duration) + .Process(this->ChangeOwner_Duration_PreventChange) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index b61bb4a20c..bd98e35af7 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -214,6 +214,12 @@ class WarheadTypeExt Nullable AnimZAdjust; + Valueable ChangeOwner; + Valueable ChangeOwner_SetAsMindControl; + Nullable ChangeOwner_MindControlAnim; + Nullable ChangeOwner_Duration; + Valueable ChangeOwner_Duration_PreventChange; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -450,11 +456,19 @@ class WarheadTypeExt , PlayAnimAboveSurface { false } , AnimZAdjust {} + + , ChangeOwner { false } + , ChangeOwner_SetAsMindControl { false } + , ChangeOwner_MindControlAnim {} + , ChangeOwner_Duration {} + , ChangeOwner_Duration_PreventChange { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); void ApplyLocomotorInfliction(TechnoClass* pTarget); void ApplyLocomotorInflictionReset(TechnoClass* pTarget); + void ApplyOwnerChange(HouseClass* pHouse, TechnoClass* pTarget); + public: bool CanTargetHouse(HouseClass* pHouse, TechnoClass* pTechno) const; bool CanAffectTarget(TechnoClass* pTarget) const; diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index d64548ebd1..9a03ce84ee 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -196,10 +196,13 @@ void WarheadTypeExt::ExtData::DetonateOnOneUnit(HouseClass* pHouse, TechnoClass* if (!this->CanTargetHouse(pHouse, pTarget) || !this->CanAffectTarget(pTarget)) return; - // Put this at first since it can change the target's house + // Put these at first since they can change the target's house if (this->RemoveMindControl) pHouse = this->ApplyRemoveMindControl(pHouse, pTarget); + if (this->ChangeOwner) + this->ApplyOwnerChange(pHouse, pTarget); + // These can change the target's techno types if (this->Convert_Pairs.size() > 0) this->ApplyConvert(pHouse, pTarget); @@ -469,6 +472,53 @@ HouseClass* WarheadTypeExt::ExtData::ApplyRemoveMindControl(HouseClass* pHouse, return pHouse; } +void WarheadTypeExt::ExtData::ApplyOwnerChange(HouseClass* pHouse, TechnoClass* pTarget) +{ + auto const pTargetExt = TechnoExt::ExtMap.Find(pTarget); + const bool isMindControl = this->ChangeOwner_SetAsMindControl; + const bool isImmune = pTargetExt->ImmuneToChangeOwner || (isMindControl && pTarget->GetTechnoType()->ImmuneToPsionics) || pTarget->IsMindControlled(); + + if (!isImmune) + { + auto const pOwner = pTarget->Owner; + pTarget->SetOwningHouse(pHouse, true); + pTargetExt->OwnerOriginalOwner = pOwner; + + if (this->ChangeOwner_Duration && !isMindControl) + { + pTargetExt->OwnerTimer.Start(this->ChangeOwner_Duration); + + if (this->ChangeOwner_Duration_PreventChange) + pTargetExt->ImmuneToChangeOwner = true; + } + + if (isMindControl) + { + pTarget->MindControlledByAUnit = true; + + if (const auto pAnimType = this->ChangeOwner_MindControlAnim.Get()) + { + CoordStruct location = pTarget->Location; + const bool isBld = pTarget->What_Am_I() == AbstractType::Building; + + if (isBld) + location.Z += static_cast(pTarget)->Type->Height * Unsorted::LevelHeight; + else + location.Z += pTarget->GetTechnoType()->MindControlRingOffset; + + if (const auto pOwnerAnim = GameCreate(pAnimType, location)) + { + pTarget->MindControlRingAnim = pOwnerAnim; + pOwnerAnim->SetOwnerObject(pTarget); + + if (isBld) + pOwnerAnim->ZAdjust = -1024; + } + } + } + } +} + void WarheadTypeExt::ExtData::ApplyCrit(HouseClass* pHouse, TechnoClass* pTarget, TechnoClass* pOwner) { const double dice = this->Crit_ApplyChancePerTarget ? ScenarioClass::Instance->Random.RandomDouble() : this->Crit_RandomBuffer;