diff --git a/CREDITS.md b/CREDITS.md index 57f41e8e92..2db0890d40 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -469,6 +469,7 @@ This page lists all the individual contributions to the project by their author. - Fix a bug introduced by Ares where building types that have `UndeploysInto` cannot display `AltCameo` or `AltCameoPCX` even when you infiltrate enemy buildings with `Factory=UnitType` - Fix a bug where units can be promoted when created via trigger actions even if they have `Trainable=false` - Fix the bug that ai will try to product aircraft even the airport has no free dock for it + - Customize size for mind controlled unit - **Apollo** - Translucent SHP drawing patches - **ststl**: - Customizable `ShowTimer` priority of superweapons diff --git a/YRpp b/YRpp index 5af96790ce..f9a100b402 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 5af96790ce73e4ea068a390c60c124dccbc220e1 +Subproject commit f9a100b4021e6782df1bc2f0f21d4408a6a23fda diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 051ecfc2d4..60a17f9c15 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1797,6 +1797,7 @@ Please notice that if the object is a unit which carries passengers, they will n *Multiple Mind Control unit auto-releases the first victim in [Fantasy ADVENTURE](https://www.moddb.com/mods/fantasy-adventure)* - Mind controllers now can have the upper limit of the control distance. Tag values greater than 0 will activate this feature. +- Mind controlled targets can have size of control, like passengers in transport. - Mind controllers now can decide which house can see the link drawn between itself and the controlled units. - Mind controllers with multiple controlling slots can now release the first controlled unit when they have reached the control limit and are ordered to control a new target. @@ -1804,6 +1805,8 @@ In `rulesmd.ini`: ```ini [SOMETECHNO] ; TechnoType MindControlRangeLimit=-1.0 ; floating point value +MindControl.IgnoreSize=true ; boolean +MindControlSize=1 ; integer MindControlLink.VisibleToHouse=all ; Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) MultiMindControl.ReleaseVictim=false ; boolean ``` diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c292380860..73978f214f 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) +- Customize size for mind controlled unit (by NetsuNegi) 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/CaptureManager/Body.cpp b/src/Ext/CaptureManager/Body.cpp index dfc0014aa4..b8b89ebb4c 100644 --- a/src/Ext/CaptureManager/Body.cpp +++ b/src/Ext/CaptureManager/Body.cpp @@ -1,23 +1,112 @@ #include "Body.h" #include +#include + +int CaptureManagerExt::GetControlledTotalSize(CaptureManagerClass* pManager) +{ + int totalSize = 0; + + for (const auto pNode : pManager->ControlNodes) + { + if (const auto pTechno = pNode->Unit) + totalSize += TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->MindControlSize; + } + + return totalSize; +} + +struct DummyExtHere +{ + char _[0x9C]; + bool DriverKilled; +}; + +struct DummyTypeExtHere +{ + char _[0x131]; + bool Vet_PsionicsImmune; + bool __[0x6]; + bool Elite_PsionicsImmune; +}; bool CaptureManagerExt::CanCapture(CaptureManagerClass* pManager, TechnoClass* pTarget) { - if (pManager->MaxControlNodes == 1) - return pManager->CanCapture(pTarget); + // target exists and doesn't belong to capturing player + if (!pTarget) + return false; + + if (pManager->MaxControlNodes <= 0) + return false; - const auto pTechnoTypeExt = TechnoExt::ExtMap.Find(pManager->Owner)->TypeExtData; - if (pTechnoTypeExt->MultiMindControl_ReleaseVictim) + const auto pOwner = pManager->Owner; + + if (pTarget->Owner == pOwner->Owner) + return false; + + const auto pTargetType = pTarget->GetTechnoType(); + + // generally not capturable + if (pTargetType->ImmuneToPsionics) + return false; + + if (AresHelper::CanUseAres) { - // I hate Ares' completely rewritten things - secsome - pManager->MaxControlNodes += 1; - const bool result = pManager->CanCapture(pTarget); - pManager->MaxControlNodes -= 1; - return result; + const auto pTargetTypeExt_Ares = reinterpret_cast(pTargetType->align_2FC); + + switch (pTarget->Veterancy.GetRemainingLevel()) + { + case Rank::Elite: + if (pTargetTypeExt_Ares->Elite_PsionicsImmune) + return false; + + case Rank::Veteran: + if (pTargetTypeExt_Ares->Vet_PsionicsImmune) + return false; + + default: + break; + } } - return pManager->CanCapture(pTarget); + // disallow capturing bunkered units + if (pTarget->BunkerLinkedItem && pTarget->WhatAmI() == AbstractType::Unit) + return false; + + if (pTarget->IsMindControlled() || pTarget->MindControlledByHouse) + return false; + + // free slot? (move on if infinite or single slot which will be freed if used) + if (!pManager->InfiniteMindControl && pManager->MaxControlNodes != 1) + { + const auto pOwnerTypeExt = TechnoTypeExt::ExtMap.Find(pOwner->GetTechnoType()); + + if (!pOwnerTypeExt->MindControl_IgnoreSize) + { + const int totalSize = CaptureManagerExt::GetControlledTotalSize(pManager); + const int available = pOwnerTypeExt->MultiMindControl_ReleaseVictim ? pManager->MaxControlNodes : pManager->MaxControlNodes - totalSize; + + if (TechnoTypeExt::ExtMap.Find(pTargetType)->MindControlSize > available) + return false; + } + else + { + if (pManager->ControlNodes.Count >= pManager->MaxControlNodes && !pOwnerTypeExt->MultiMindControl_ReleaseVictim) + return false; + } + } + + // currently disallowed + const auto mission = pTarget->CurrentMission; + + if (pTarget->IsIronCurtained() || mission == Mission::Selling || mission == Mission::Construction) + return false; + + // driver killed. has no mind. + if (AresHelper::CanUseAres && reinterpret_cast(*(uintptr_t*)((char*)pTarget + 0x154))->DriverKilled) + return false; + + return true; } bool CaptureManagerExt::FreeUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, bool silent) @@ -68,7 +157,7 @@ bool CaptureManagerExt::FreeUnit(CaptureManagerClass* pManager, TechnoClass* pTa } bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, - bool bRemoveFirst, AnimTypeClass* pControlledAnimType, bool silent, int threatDelay) + bool removeFirst, AnimTypeClass* pControlledAnimType, bool silent, int threatDelay) { if (CaptureManagerExt::CanCapture(pManager, pTarget)) { @@ -78,10 +167,26 @@ bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, TechnoClass* if (!pManager->InfiniteMindControl) { if (pManager->MaxControlNodes == 1 && pManager->ControlNodes.Count == 1) + { CaptureManagerExt::FreeUnit(pManager, pManager->ControlNodes[0]->Unit); - else if (pManager->ControlNodes.Count == pManager->MaxControlNodes) - if (bRemoveFirst) - CaptureManagerExt::FreeUnit(pManager, pManager->ControlNodes[0]->Unit); + } + else if (pManager->ControlNodes.Count > 0 && removeFirst) + { + const auto pOwnerTypeExt = TechnoTypeExt::ExtMap.Find(pManager->Owner->GetTechnoType()); + + if (pOwnerTypeExt->MindControl_IgnoreSize) + { + if (pManager->ControlNodes.Count == pManager->MaxControlNodes) + CaptureManagerExt::FreeUnit(pManager, pManager->ControlNodes[0]->Unit); + } + else + { + const auto pTargetTypeExt = TechnoTypeExt::ExtMap.Find(pTarget->GetTechnoType()); + + while (pManager->ControlNodes.Count && pTargetTypeExt->MindControlSize > pManager->MaxControlNodes - CaptureManagerExt::GetControlledTotalSize(pManager)) + CaptureManagerExt::FreeUnit(pManager, pManager->ControlNodes[0]->Unit); + } + } } auto const pControlNode = GameCreate(); diff --git a/src/Ext/CaptureManager/Body.h b/src/Ext/CaptureManager/Body.h index 2d9a782cff..dc2c7aab4e 100644 --- a/src/Ext/CaptureManager/Body.h +++ b/src/Ext/CaptureManager/Body.h @@ -12,9 +12,11 @@ class CaptureManagerExt { public: + static int GetControlledTotalSize(CaptureManagerClass* pManager); + static bool CanCapture(CaptureManagerClass* pManager, TechnoClass* pTarget); static bool FreeUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, bool silent = false); - static bool CaptureUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, bool bRemoveFirst, + static bool CaptureUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, bool removeFirst, AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType, bool silent = false, int threatDelay = 0); static bool CaptureUnit(CaptureManagerClass* pManager, AbstractClass* pTechno, AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType, int threatDelay = 0); diff --git a/src/Ext/CaptureManager/Hooks.cpp b/src/Ext/CaptureManager/Hooks.cpp index ebf35f5e04..51c1e62ea4 100644 --- a/src/Ext/CaptureManager/Hooks.cpp +++ b/src/Ext/CaptureManager/Hooks.cpp @@ -25,14 +25,44 @@ DEFINE_HOOK(0x471FF0, CaptureManagerClass_FreeUnit, 0x8) return 0x472006; } -DEFINE_HOOK(0x6FCB34, TechnoClass_CanFire_CanCapture, 0x6) +DEFINE_HOOK(0x471C90, CaptureManagerClass_CanCapture, 0x6) { - GET(TechnoClass*, pThis, ESI); - GET(TechnoClass*, pTarget, EBP); + GET(CaptureManagerClass*, pThis, ECX); + GET_STACK(TechnoClass*, pTarget, 0x4); + + R->AL(CaptureManagerExt::CanCapture(pThis, pTarget)); + + return 0x471D39; +} + +static int __fastcall _GetControlledCount(CaptureManagerClass* pThis) +{ + const auto pOwnerTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Owner->GetTechnoType()); + + if (!pOwnerTypeExt->MindControl_IgnoreSize) + return CaptureManagerExt::GetControlledTotalSize(pThis); + + return pThis->ControlNodes.Count; +} +DEFINE_FUNCTION_JUMP(LJMP, 0x4722D0, _GetControlledCount) - R->AL(CaptureManagerExt::CanCapture(pThis->CaptureManager, pTarget)); +DEFINE_HOOK(0x4726C7, CaptureManagerClass_IsOverloading_ControlledCount, 0x6) +{ + enum { ContinueCheck = 0x4726D1, ReturnFalse = 0x4726E4 }; + + GET(CaptureManagerClass*, pThis, ECX); + + return pThis->GetControlledCount() > pThis->MaxControlNodes ? ContinueCheck : ReturnFalse; +} + +DEFINE_HOOK(0x4722AA, CaptureManagerClass_CannotControlAnyMore_ControlledCount, 0x6) +{ + enum { SkipGameCode = 0x4722B5 }; + + GET(CaptureManagerClass*, pThis, ECX); - return 0x6FCB40; + R->AL(pThis->GetControlledCount() >= pThis->MaxControlNodes); + return SkipGameCode; } DEFINE_HOOK(0x519F71, InfantryClass_UpdatePosition_BeforeBuildingChangeHouse, 0x6) @@ -107,7 +137,7 @@ static void __fastcall CaptureManagerClass_Overload_AI(CaptureManagerClass* pThi return; int nCurIdx = 0; - int const nNodeCount = pThis->ControlNodes.Count; + int const nNodeCount = pThis->GetControlledCount(); for (int i = 0; i < (int)(OverloadCount.size()); ++i) { diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 1be8e43495..a6c7b13d2f 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -992,7 +992,7 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) } else if (pOldTypeExt->Convert_ResetMindControl) { - if (!infiniteCapture && pCaptureManager->ControlNodes.Count > maxCapture) + if (!infiniteCapture && pCaptureManager->GetControlledCount() > maxCapture) { // Remove excess nodes. for (int i = pCaptureManager->ControlNodes.Count - 1; i >= maxCapture; --i) diff --git a/src/Ext/Techno/Body.Visuals.cpp b/src/Ext/Techno/Body.Visuals.cpp index b14c4b8337..1495a78768 100644 --- a/src/Ext/Techno/Body.Visuals.cpp +++ b/src/Ext/Techno/Body.Visuals.cpp @@ -540,7 +540,7 @@ void TechnoExt::GetValuesForDisplay(TechnoClass* pThis, TechnoTypeClass* pType, if (!pCaptureManager) return; - value = pCaptureManager->ControlNodes.Count; + value = pCaptureManager->GetControlledCount(); maxValue = pCaptureManager->MaxControlNodes; break; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 4bb689d3a8..2bd1591437 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -727,6 +727,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->RadarJamAffect.Read(exINI, pSection, "RadarJamAffect"); this->RadarJamIgnore.Read(exINI, pSection, "RadarJamIgnore"); this->MindControlRangeLimit.Read(exINI, pSection, "MindControlRangeLimit"); + this->MindControl_IgnoreSize.Read(exINI, pSection, "MindControl.IgnoreSize"); + this->MindControlSize.Read(exINI, pSection, "MindControlSize"); this->MindControlLink_VisibleToHouse.Read(exINI, pSection, "MindControlLink.VisibleToHouse"); this->FactoryPlant_Multiplier.Read(exINI, pSection, "FactoryPlant.Multiplier"); @@ -1385,6 +1387,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->UIDescription) .Process(this->LowSelectionPriority) .Process(this->MindControlRangeLimit) + .Process(this->MindControl_IgnoreSize) + .Process(this->MindControlSize) .Process(this->MindControlLink_VisibleToHouse) .Process(this->FactoryPlant_Multiplier) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 4de978975e..616f80edc4 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -46,6 +46,8 @@ class TechnoTypeExt Nullable DesignatorRange; Valueable FactoryPlant_Multiplier; Valueable MindControlRangeLimit; + Valueable MindControl_IgnoreSize; + Valueable MindControlSize; Valueable MindControlLink_VisibleToHouse; std::unique_ptr InterceptorType; @@ -471,6 +473,8 @@ class TechnoTypeExt , DesignatorRange { } , FactoryPlant_Multiplier { 1.0 } , MindControlRangeLimit {} + , MindControl_IgnoreSize { true } + , MindControlSize { 1 } , MindControlLink_VisibleToHouse{ AffectedHouse::All } , InterceptorType { nullptr }