Skip to content

[Customized] Delay automatic attack on the controlled unit #1474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ This page lists all the individual contributions to the project by their author.
- Units will not always stuck in the factory
- Technos can maintain a suitable distance after firing
- Projectile subject to ground check before firing
- Delay automatic attack on the controlled unit
- **Ollerus**:
- Build limit group enhancement
- Customizable rocker amplitude
Expand Down
13 changes: 13 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,19 @@ RecountBurst=false ; boolean
RecountBurst= ; boolean
```

### Delay automatic attack on the controlled unit

- Now you can make the techno that has just been mind controlled not be automatically attacked by its original friendly forces for a period of time defined by `MindControl.ThreatDelay` on the mind control warhead, default to `[General]->MindControl.ThreatDelay`. This will not affect the manual selection of attacks and is useless with permanent mind control.

In `rulesmd.ini`:
```ini
[General]
MindControl.ThreatDelay=0 ; integer, game frames

[SOMEWARHEAD] ; Warhead
MindControl.ThreatDelay= ; integer, game frames
```

## Terrain

### Destroy animation & sound
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ New:
- Recycle the spawner on other FLH (by TaranDahl)
- Technos can maintain a suitable distance after firing (by CrimRecya)
- Projectile subject to ground check before firing (by CrimRecya)
- Delay automatic attack on the controlled unit (by CrimRecya)

Vanilla fixes:
- Prevent the units with locomotors that cause problems from entering the tank bunker (by TaranDahl)
Expand Down
3 changes: 2 additions & 1 deletion src/Ext/Bullet/Hooks.DetonateLogics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ DEFINE_HOOK(0x4692BD, BulletClass_Logics_ApplyMindControl, 0x6)

auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH);
auto const pControlledAnimType = pWHExt->MindControl_Anim.Get(RulesClass::Instance->ControlledAnimationType);
R->AL(CaptureManagerExt::CaptureUnit(pThis->Owner->CaptureManager, pThis->Target, pControlledAnimType));
auto const threatDelay = pWHExt->MindControl_ThreatDelay.Get(RulesExt::Global()->MindControl_ThreatDelay);
R->AL(CaptureManagerExt::CaptureUnit(pThis->Owner->CaptureManager, pThis->Target, pControlledAnimType, threatDelay));

return SkipGameCode;
}
Expand Down
62 changes: 32 additions & 30 deletions src/Ext/CaptureManager/Body.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "Body.h"

#include <Ext/Techno/Body.h>

bool CaptureManagerExt::CanCapture(CaptureManagerClass* pManager, TechnoClass* pTarget)
{
if (pManager->MaxControlNodes == 1)
Expand Down Expand Up @@ -47,6 +49,8 @@ bool CaptureManagerExt::FreeUnit(CaptureManagerClass* pManager, TechnoClass* pTa
auto pOriginOwner = pNode->OriginalOwner->Defeated ?
HouseClass::FindNeutral() : pNode->OriginalOwner;

TechnoExt::ExtMap.Find(pTarget)->BeControlledThreatFrame = 0;

pTarget->SetOwningHouse(pOriginOwner, !silent);
pManager->DecideUnitFate(pTarget);
pTarget->MindControlledBy = nullptr;
Expand All @@ -64,7 +68,7 @@ bool CaptureManagerExt::FreeUnit(CaptureManagerClass* pManager, TechnoClass* pTa
}

bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, TechnoClass* pTarget,
bool bRemoveFirst, AnimTypeClass* pControlledAnimType, bool silent)
bool bRemoveFirst, AnimTypeClass* pControlledAnimType, bool silent, int threatDelay)
{
if (CaptureManagerExt::CanCapture(pManager, pTarget))
{
Expand All @@ -81,59 +85,57 @@ bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, TechnoClass*
}

auto pControlNode = GameCreate<ControlNode>();
if (pControlNode)
{
pControlNode->OriginalOwner = pTarget->Owner;
pControlNode->Unit = pTarget;
pControlNode->OriginalOwner = pTarget->Owner;
pControlNode->Unit = pTarget;

pManager->ControlNodes.AddItem(pControlNode);
pControlNode->LinkDrawTimer.Start(RulesClass::Instance->MindControlAttackLineFrames);
pManager->ControlNodes.AddItem(pControlNode);
pControlNode->LinkDrawTimer.Start(RulesClass::Instance->MindControlAttackLineFrames);

if (pTarget->SetOwningHouse(pManager->Owner->Owner, !silent))
{
pTarget->MindControlledBy = pManager->Owner;
if (threatDelay > 0)
TechnoExt::ExtMap.Find(pTarget)->BeControlledThreatFrame = Unsorted::CurrentFrame + threatDelay;

pManager->DecideUnitFate(pTarget);
if (pTarget->SetOwningHouse(pManager->Owner->Owner, !silent))
{
pTarget->MindControlledBy = pManager->Owner;

auto const pBld = abstract_cast<BuildingClass*>(pTarget);
auto const pType = pTarget->GetTechnoType();
CoordStruct location = pTarget->GetCoords();
pManager->DecideUnitFate(pTarget);

if (pBld)
location.Z += pBld->Type->Height * Unsorted::LevelHeight;
else
location.Z += pType->MindControlRingOffset;
auto const pBld = abstract_cast<BuildingClass*>(pTarget);
auto const pType = pTarget->GetTechnoType();
CoordStruct location = pTarget->GetCoords();

if (auto const pAnimType = pControlledAnimType)
{
auto const pAnim = GameCreate<AnimClass>(pAnimType, location);

pTarget->MindControlRingAnim = pAnim;
pAnim->SetOwnerObject(pTarget);
if (pBld)
location.Z += pBld->Type->Height * Unsorted::LevelHeight;
else
location.Z += pType->MindControlRingOffset;

if (pBld)
pAnim->ZAdjust = -1024;
if (auto const pAnimType = pControlledAnimType)
{
auto const pAnim = GameCreate<AnimClass>(pAnimType, location);

}
pTarget->MindControlRingAnim = pAnim;
pAnim->SetOwnerObject(pTarget);

return true;
if (pBld)
pAnim->ZAdjust = -1024;
}

return true;
}
}

return false;
}

bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, AbstractClass* pTechno, AnimTypeClass* pControlledAnimType)
bool CaptureManagerExt::CaptureUnit(CaptureManagerClass* pManager, AbstractClass* pTechno, AnimTypeClass* pControlledAnimType, int threatDelay)
{
if (const auto pTarget = generic_cast<TechnoClass*>(pTechno))
{
bool bRemoveFirst = false;
if (auto pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pManager->Owner->GetTechnoType()))
bRemoveFirst = pTechnoTypeExt->MultiMindControl_ReleaseVictim;

return CaptureManagerExt::CaptureUnit(pManager, pTarget, bRemoveFirst, pControlledAnimType);
return CaptureManagerExt::CaptureUnit(pManager, pTarget, bRemoveFirst, pControlledAnimType, false, threatDelay);
}

return false;
Expand Down
6 changes: 3 additions & 3 deletions src/Ext/CaptureManager/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class CaptureManagerExt
public:
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, AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType, bool silent = false);
static bool CaptureUnit(CaptureManagerClass* pManager, TechnoClass* pTarget, bool bRemoveFirst,
AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType, bool silent = false, int threatDelay = 0);
static bool CaptureUnit(CaptureManagerClass* pManager, AbstractClass* pTechno,
AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType);
AnimTypeClass* pControlledAnimType = RulesClass::Instance->ControlledAnimationType, int threatDelay = 0);
static void DecideUnitFate(CaptureManagerClass* pManager, FootClass* pFoot);
};
3 changes: 3 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)

this->UseFixedVoxelLighting.Read(exINI, GameStrings::AudioVisual, "UseFixedVoxelLighting");

this->MindControl_ThreatDelay.Read(exINI, GameStrings::General, "MindControl.ThreatDelay");

this->RecountBurst.Read(exINI, GameStrings::General, "RecountBurst");
this->NoRearm_UnderEMP.Read(exINI, GameStrings::General, "NoRearm.UnderEMP");
this->NoRearm_Temporal.Read(exINI, GameStrings::General, "NoRearm.Temporal");
Expand Down Expand Up @@ -429,6 +431,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->CombatAlert_UseAttackVoice)
.Process(this->CombatAlert_UseEVA)
.Process(this->UseFixedVoxelLighting)
.Process(this->MindControl_ThreatDelay)
.Process(this->RecountBurst)
.Process(this->NoRearm_UnderEMP)
.Process(this->NoRearm_Temporal)
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ class RulesExt
// Nullable<Vector3D<float>> VoxelShadowLightSource;
Valueable<bool> UseFixedVoxelLighting;

Valueable<int> MindControl_ThreatDelay;

Valueable<bool> RecountBurst;
Valueable<bool> NoRearm_UnderEMP;
Valueable<bool> NoRearm_Temporal;
Expand Down Expand Up @@ -325,6 +327,7 @@ class RulesExt
, CombatAlert_UseAttackVoice { true }
, CombatAlert_UseEVA { true }
, UseFixedVoxelLighting { false }
, MindControl_ThreatDelay { 0 }
, RecountBurst { false }
, NoRearm_UnderEMP { false }
, NoRearm_Temporal { false }
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->LastWeaponType)
.Process(this->FiringObstacleCell)
.Process(this->IsDetachingForCloak)
.Process(this->BeControlledThreatFrame)
.Process(this->LastTargetID)
.Process(this->AccumulatedGattlingValue)
.Process(this->ShouldUpdateGattlingValue)
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class TechnoExt
WeaponTypeClass* LastWeaponType;
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only.
int BeControlledThreatFrame;
DWORD LastTargetID;
int AccumulatedGattlingValue;
bool ShouldUpdateGattlingValue;
Expand Down Expand Up @@ -97,6 +98,7 @@ class TechnoExt
, LastWeaponType {}
, FiringObstacleCell {}
, IsDetachingForCloak { false }
, BeControlledThreatFrame { 0 }
, LastTargetID { 0xFFFFFFFF }
, AccumulatedGattlingValue { 0 }
, ShouldUpdateGattlingValue { false }
Expand Down
45 changes: 45 additions & 0 deletions src/Ext/Techno/Hooks.Misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,51 @@ DEFINE_HOOK(0x74691D, UnitClass_UpdateDisguise_EMP, 0x6)

#pragma endregion

#pragma region AttackMindControlledDelay

bool __fastcall CanAttackMindControlled(TechnoClass* pControlled, TechnoClass* pRetaliator)
{
const auto pMind = pControlled->MindControlledBy;

if (!pMind || pRetaliator->Berzerk)
return true;

const auto pManager = pMind->CaptureManager;

if (!pManager || !pRetaliator->Owner->IsAlliedWith(pManager->GetOriginalOwner(pControlled)))
return true;

return TechnoExt::ExtMap.Find(pControlled)->BeControlledThreatFrame <= Unsorted::CurrentFrame;
}

DEFINE_HOOK(0x7089E8, TechnoClass_AllowedToRetaliate_AttackMindControlledDelay, 0x6)
{
enum { CannotRetaliate = 0x708B17 };

GET(TechnoClass* const, pThis, ESI);
GET(TechnoClass* const, pAttacker, EBP);

return CanAttackMindControlled(pAttacker, pThis) ? 0 : CannotRetaliate;
}

DEFINE_HOOK(0x6F88BF, TechnoClass_CanAutoTargetObject_AttackMindControlledDelay, 0x6)
{
enum { CannotSelect = 0x6F894F };

GET(TechnoClass* const, pThis, EDI);
GET(ObjectClass* const, pTarget, ESI);

if (const auto pTechno = abstract_cast<TechnoClass*>(pTarget))
{
if (!CanAttackMindControlled(pTechno, pThis))
return CannotSelect;
}

return 0;
}

#pragma endregion

#pragma region ExtendedGattlingRateDown

DEFINE_HOOK(0x70DE40, BuildingClass_sub_70DE40_GattlingRateDownDelay, 0xA)
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/WarheadType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->Crit_SuppressWhenIntercepted.Read(exINI, pSection, "Crit.SuppressWhenIntercepted");

this->MindControl_Anim.Read(exINI, pSection, "MindControl.Anim");
this->MindControl_ThreatDelay.Read(exINI, pSection, "MindControl.ThreatDelay");

// Shields
this->Shield_Penetrate.Read(exINI, pSection, "Shield.Penetrate");
Expand Down Expand Up @@ -426,6 +427,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm)
.Process(this->Crit_SuppressWhenIntercepted)

.Process(this->MindControl_Anim)
.Process(this->MindControl_ThreatDelay)

.Process(this->Shield_Penetrate)
.Process(this->Shield_Break)
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/WarheadType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class WarheadTypeExt
Valueable<bool> Crit_SuppressWhenIntercepted;

Nullable<AnimTypeClass*> MindControl_Anim;
Nullable<int> MindControl_ThreatDelay;

Valueable<bool> Shield_Penetrate;
Valueable<bool> Shield_Break;
Expand Down Expand Up @@ -234,6 +235,7 @@ class WarheadTypeExt
, Crit_SuppressWhenIntercepted { false }

, MindControl_Anim {}
, MindControl_ThreatDelay {}

, Shield_Penetrate { false }
, Shield_Break { false }
Expand Down
Loading