diff --git a/CREDITS.md b/CREDITS.md index 656399b2bd..6780d561b9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -736,6 +736,7 @@ This page lists all the individual contributions to the project by their author. - Miners back to work when ore regenerated - Allow disable an over-optimization in targeting - Extra threat + - Extended auto-targeting - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 7634f6cf05..6ea5571fae 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1725,6 +1725,20 @@ RateDown.Cover.Value=0 ; integer RateDown.Cover.AmmoBelow=-2 ; integer ``` +### Extended auto-targeting + +- Now you can activate multiple auto-targeting optimizations by setting `ExtendedAutoTargeting=true`. These include: + - When using stop, guard, or mouse commands, refresh the auto-targeting cooldown. + - When the target becomes invalid, refresh the auto-targeting cooldown (In vanilla, this cooldown will be reduced to less than 10 frames). + - When a unit is on a task that allows auto-targeting but already has a target, it will still auto-target. If a target with a threat level higher than the original target by more than `ExtendedAutoTargeting.SwitchTargetThreshold` is found, switch the target. + +In `rulesmd.ini`: +```ini +[General] +ExtendedAutoTargeting=false ; boolean +ExtendedAutoTargeting.SwitchTargetThreshold=1000 ; integer +``` + ### Extra threat - Now you can adjust the techno's evaluation of the threat posed by the target in more ways. This will help the techno in auto - targeting. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index d8eb1523de..5287961ed0 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -562,6 +562,7 @@ New: - [Allow customize jumpjet properties on warhead](Fixed-or-Improved-Logics.md#customizing-locomotor-warhead) (by NetsuNegi) - Customize effects range of power plant enhancer (by NetsuNegi) - Allow each side to customize the color when the proportion of working miners is higher than `HarvesterCounter.ConditionYellow` (by Noble_Fish) +- [Extended auto-targeting](New-or-Enhanced-Logics.md#extended-auto-targeting) (by TaranDahl) 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/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 5c50966b48..3aa43d863d 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -1,4 +1,4 @@ -#include "Body.h" +#include "Body.h" #include #include @@ -385,6 +385,9 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->ExtraThreatCoefficient_Facing.Read(exINI, GameStrings::General, "ExtraThreatCoefficient.Facing"); this->ExtraThreatCoefficient_DistanceToLastTarget.Read(exINI, GameStrings::General, "ExtraThreatCoefficient.DistanceToLastTarget"); + this->ExtendedAutoTargeting.Read(exINI, GameStrings::General, "ExtendedAutoTargeting"); + this->ExtendedAutoTargeting_SwitchTargetThreshold.Read(exINI, GameStrings::General, "ExtendedAutoTargeting.SwitchTargetThreshold"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -699,6 +702,8 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->ExtraThreatCoefficient_InRangeDistance) .Process(this->ExtraThreatCoefficient_Facing) .Process(this->ExtraThreatCoefficient_DistanceToLastTarget) + .Process(this->ExtendedAutoTargeting) + .Process(this->ExtendedAutoTargeting_SwitchTargetThreshold) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 5d2954ed57..d8d1606219 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -320,6 +320,8 @@ class RulesExt Valueable DefaultToGuardArea; Valueable DisableOveroptimizationInTargeting; + Valueable ExtendedAutoTargeting; + Valueable ExtendedAutoTargeting_SwitchTargetThreshold; Valueable CylinderRangefinding; @@ -609,6 +611,8 @@ class RulesExt , ExtraThreatCoefficient_InRangeDistance { 0.0 } , ExtraThreatCoefficient_Facing { 0.0 } , ExtraThreatCoefficient_DistanceToLastTarget { 0.0 } + , ExtendedAutoTargeting { false } + , ExtendedAutoTargeting_SwitchTargetThreshold { 1000 } { } virtual ~ExtData() = default; diff --git a/src/Ext/Techno/Hooks.Targeting.cpp b/src/Ext/Techno/Hooks.Targeting.cpp index 3f9162dd64..2691b1b1c1 100644 --- a/src/Ext/Techno/Hooks.Targeting.cpp +++ b/src/Ext/Techno/Hooks.Targeting.cpp @@ -75,3 +75,262 @@ DEFINE_HOOK(0x6F9AF4, TechnoClass_SelectAutoTarget_DisableStupid, 0x6) { return RulesExt::Global()->DisableOveroptimizationInTargeting ? 0x6F9B1B : 0; } + +#pragma region ExtendedAutoTargeting + +namespace ExtendedAutoTargetingContext +{ + AbstractClass* OldTarget = nullptr; + int OldThreat = -1; + + AbstractClass* NewTarget = nullptr; + int NewThreat = -1; + + void Clear() + { + OldTarget = nullptr; + OldThreat = -1; + NewTarget = nullptr; + NewThreat = -1; + } +} + +// Record old target +DEFINE_HOOK(0x6FA6C9, TechnoClass_Update_RecordOldTarget, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + + auto pOldTarget = pThis->Target; + + if (pOldTarget && (pOldTarget->AbstractFlags & AbstractFlags::Techno) != AbstractFlags::None && pThis->IsCloseEnoughToAttack(pOldTarget) && pThis->ShouldLoseTargetNow) // OpportunityFire, in-range only + { + auto crd = pThis->GetCoords(); + ExtendedAutoTargetingContext::OldTarget = pOldTarget; + ExtendedAutoTargetingContext::OldThreat = (int)pThis->ThreatCoeffients(static_cast(pOldTarget), &crd); + pThis->Target = 0; + } + + return 0; +} + +DEFINE_HOOK(0x4DF3A0, TechnoClass_UpdateAttackMove_RecordOldTarget1, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(FootClass*, pThis, ECX); + + auto pOldTarget = pThis->Target; + + if (pOldTarget && (pOldTarget->AbstractFlags & AbstractFlags::Techno) != AbstractFlags::None && pThis->InAuxiliarySearchRange(pOldTarget)) // AttackMove has its own range checking + { + auto crd = pThis->GetCoords(); + ExtendedAutoTargetingContext::OldTarget = pOldTarget; + ExtendedAutoTargetingContext::OldThreat = (int)pThis->ThreatCoeffients(static_cast(pOldTarget), &crd); + pThis->Target = 0; + pThis->HaveAttackMoveTarget = false; + } + + return 0; +} + +DEFINE_HOOK(0x4DF42A, TechnoClass_UpdateAttackMove_SkipCheck, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + enum { SkipCheck = 0x4DF462, FuncEnd = 0x4DF4AB }; + + GET(FootClass*, pThis, ESI); + + return pThis->MegaTarget ? SkipCheck : FuncEnd; +} + +DEFINE_HOOK(0x4D6ED1, TechnoClass_MissionAreaGuard_RecordOldTarget, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + + auto pOldTarget = pThis->Target; + + if (pOldTarget && (pOldTarget->AbstractFlags & AbstractFlags::Techno) != AbstractFlags::None && pThis->CanPassiveAcquireTargets() && pThis->TargetingTimerFinished() && pThis->DistanceFrom(pOldTarget) <= pThis->GetGuardRange(1)) // AreaGuard, use GetGuardRange + { + auto crd = pThis->ArchiveTarget->GetCoords(); + ExtendedAutoTargetingContext::OldTarget = pOldTarget; + ExtendedAutoTargetingContext::OldThreat = (int)pThis->ThreatCoeffients(static_cast(pOldTarget), &crd); + pThis->Target = 0; + } + + return 0; +} + +// Use old target when targeting +DEFINE_HOOK(0x6F8E1F, TechnoClass_SelectAutoTarget_UseContext, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + R->Stack(STACK_OFFSET(0x6C, -0x4C), ExtendedAutoTargetingContext::OldTarget); + R->Stack(STACK_OFFSET(0x6C, -0x50), ExtendedAutoTargetingContext::OldThreat); + return 0; +} + +// Record new target +DEFINE_HOOK(0x6F936F, TechnoClass_SelectAutoTarget_RecordNew1, 0x8) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(AbstractClass*, pBestTarget, EBP); + GET(int, bestThreat, EAX); + + if (ExtendedAutoTargetingContext::OldTarget) + { + ExtendedAutoTargetingContext::NewTarget = pBestTarget; + ExtendedAutoTargetingContext::NewThreat = bestThreat; + } + + return 0; +} + +DEFINE_HOOK(0x6F955B, TechnoClass_SelectAutoTarget_RecordNew2, 0x8) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(AbstractClass*, pBestTarget, EDX); + GET(int, bestThreat, EAX); + + if (ExtendedAutoTargetingContext::OldTarget) + { + ExtendedAutoTargetingContext::NewTarget = pBestTarget; + ExtendedAutoTargetingContext::NewThreat = bestThreat; + } + + return 0; +} + +// Check if new target has enough threat +DEFINE_HOOK(0x709938, TechnoClass_TargetAndEstimateDamage_CheckContext, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + + if (ExtendedAutoTargetingContext::OldTarget && ExtendedAutoTargetingContext::NewTarget && ExtendedAutoTargetingContext::OldTarget != ExtendedAutoTargetingContext::NewTarget) + { + auto crd = pThis->GetCoords(); + //if (ExtendedAutoTargetingContext::NewThreat < ExtendedAutoTargetingContext::OldThreat + RulesExt::Global()->ExtendedAutoTargeting_SwitchTargetThreshold) + if (pThis->ThreatCoeffients(static_cast(ExtendedAutoTargetingContext::NewTarget), &crd) < ExtendedAutoTargetingContext::OldThreat + RulesExt::Global()->ExtendedAutoTargeting_SwitchTargetThreshold) + R->EAX(ExtendedAutoTargetingContext::OldTarget); + } + + return 0; +} + +// Reset context +DEFINE_HOOK_AGAIN(0x6FA6F5, ExtendedAutoTargetingContext_Clear, 0x5); // Update +DEFINE_HOOK_AGAIN(0x4DF4AB, ExtendedAutoTargetingContext_Clear, 0x5); // UpdateAttackMove +DEFINE_HOOK(0x4DF41E, ExtendedAutoTargetingContext_Clear, 0x7) // UpdateAttackMove +{ + ExtendedAutoTargetingContext::Clear(); + return 0; +} + +DEFINE_HOOK(0x6FA6E6, TechnoClass_Update_SetIsOpportunityTarget, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + + // This field indicate that the target is acquired by OpportunityFire, thus should be cleared if out of range. + // Must set it manually because the SetTarget in TargetAndEstimateDamage will reset it. + if (pThis->Target == ExtendedAutoTargetingContext::OldTarget) + pThis->ShouldLoseTargetNow = true; + + return 0; +} + +DEFINE_HOOK(0x4D6F0C, FootClass_MissionAreaGuard_AfterTargeting, 0x6) // AreaGuard +{ + GET(FootClass*, pThis, ESI); + + if (pThis->Target) + pThis->ApproachTarget(0); + + ExtendedAutoTargetingContext::Clear(); + return 0; +} + +// Stop command +DEFINE_HOOK(0x4C757D, EventClass_RespondToEvent_IDLE_ClearTargetingTimer, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + pThis->TargetingTimer.Start(0); + return 0; +} + +// Clicked mission +DEFINE_HOOK(0x4C72E8, EventClass_RespondToEvent_MegaMission_ClearTargetingTimer, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, EDI); + pThis->TargetingTimer.Start(0); + return 0; +} + +// Target expired. +DEFINE_HOOK(0x7079D1, TechnoClass_PointerExpired_ClearTargetingTimer, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + GET(TechnoClass*, pThis, ESI); + pThis->TargetingTimer.Start(0); + + if (pThis->MegaMissionIsAttackMove()) + pThis->UpdateTimer.Start(0); + + return 0; +} + +// ContinueMegaMission +DEFINE_HOOK(0x4DF320, FootClass_ContinueMegaMission_Start, 0x6) +{ + if (!RulesExt::Global()->ExtendedAutoTargeting) + return 0; + + enum { RETN = 0x4DF395 }; + + GET(FootClass*, pThis, ECX); + + if (!pThis->Target) + { + pThis->HaveAttackMoveTarget = 0; + pThis->HaveAttackMoveTarget = pThis->TargetAndEstimateDamage(pThis->Location, ThreatType::Range); + return pThis->HaveAttackMoveTarget ? RETN : 0; + } + + return 0; +} + +// This is the bug fixed by #2078. +// The new feature of auto-targeting when having a target will not take effect unless this fix is enabled. +DEFINE_HOOK(0x6F9AF4, TEST, 0x6) +{ + return 0x6F9B1B; +} + +#pragma endregion