diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc index f654f0e08..973813ef9 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc @@ -287,9 +287,24 @@ struct OverrideHasHeightAdvantageStruct var protectedwrite array OverrideHasHeightAdvantageCallbacks; // End Issue #851 +// Start Issue #1138 +struct PrioritizeRightClickMeleeStruct +{ + var delegate PrioritizeRightClickMeleeFn; + var int Priority; + + structdefaultproperties + { + Priority = 50 + } +}; +var protectedwrite array PrioritizeRightClickMeleeCallbacks; +// End Issue #1138 + delegate EHLDelegateReturn ShouldDisplayMultiSlotItemInStrategyDelegate(XComGameState_Unit UnitState, XComGameState_Item ItemState, out int bDisplayItem, XComUnitPawn UnitPawn, optional XComGameState CheckGameState); // Issue #885 delegate EHLDelegateReturn ShouldDisplayMultiSlotItemInTacticalDelegate(XComGameState_Unit UnitState, XComGameState_Item ItemState, out int bDisplayItem, XGUnit UnitVisualizer, optional XComGameState CheckGameState); // Issue #885 delegate EHLDelegateReturn OverrideHasHeightAdvantageDelegate(XComGameState_Unit Attacker, XComGameState_Unit TargetUnit, out int bHasHeightAdvantage); // Issue #851 +delegate EHLDelegateReturn PrioritizeRightClickMeleeDelegate(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject, out XComGameState_Ability PrioritizedMeleeAbility); // Issue #1138 // Start Issue #123 simulated static function RebuildPerkContentCache() { @@ -949,6 +964,95 @@ simulated function TriggerOverrideHasHeightAdvantage(XComGameState_Unit Attacker } // End Issue #851 +// Start Issue #1138 +/// HL-Docs: ref:PrioritizeRightClickMelee +/// # Delegate Priority +/// You can optionally specify callback Priority. +///```unrealscript +///CHHelpersObj.AddPrioritizeRightClickMeleeCallback(PrioritizeRightClickMelee, 45); +///``` +/// Delegates with higher Priority value are executed first. +/// Delegates with the same Priority are executed in the order they were added to CHHelpers, +/// which would normally be the same as [DLCRunOrder](../misc/DLCRunOrder.md). +/// This function will return `true` if the delegate was successfully registered. +simulated function bool AddPrioritizeRightClickMeleeCallback(delegate PrioritizeRightClickMeleeFn, optional int Priority = 50) +{ + local PrioritizeRightClickMeleeStruct NewPrioritizeRightClickMeleeCallback; + local int i, PriorityIndex; + local bool bPriorityIndexFound; + + if (PrioritizeRightClickMeleeFn == none) + { + return false; + } + // Cycle through the array of callbacks backwards + for (i = PrioritizeRightClickMeleeCallbacks.Length - 1; i >= 0; i--) + { + // Do not allow registering the same delegate more than once. + if (PrioritizeRightClickMeleeCallbacks[i].PrioritizeRightClickMeleeFn == PrioritizeRightClickMeleeFn) + { + return false; + } + + // Record the array index of the callback whose priority is higher or equal to the priority of the new callback, + // so that the new callback can be inserted right after it. + if (PrioritizeRightClickMeleeCallbacks[i].Priority >= Priority && !bPriorityIndexFound) + { + PriorityIndex = i + 1; // +1 so that InsertItem puts the new callback *after* this one. + + // Keep cycling through the array so that the previous check for duplicate delegates can run for every currently registered delegate. + bPriorityIndexFound = true; + } + } + + NewPrioritizeRightClickMeleeCallback.Priority = Priority; + NewPrioritizeRightClickMeleeCallback.PrioritizeRightClickMeleeFn = PrioritizeRightClickMeleeFn; + PrioritizeRightClickMeleeCallbacks.InsertItem(PriorityIndex, NewPrioritizeRightClickMeleeCallback); + + return true; +} + +/// HL-Docs: ref:PrioritizeRightClickMelee +/// # Removing Delegates +/// If necessary, it's possible to remove a delegate. +///```unrealscript +///CHHelpersObj.RemovePrioritizeRightClickMeleeCallback(PrioritizeRightClickMelee); +///``` +/// The function will return `true` if the Callback was successfully deleted, return false otherwise. +simulated function bool RemovePrioritizeRightClickMeleeCallback(delegate PrioritizeRightClickMeleeFn) +{ + local int i; + + for (i = PrioritizeRightClickMeleeCallbacks.Length - 1; i >= 0; i--) + { + if (PrioritizeRightClickMeleeCallbacks[i].PrioritizeRightClickMeleeFn == PrioritizeRightClickMeleeFn) + { + PrioritizeRightClickMeleeCallbacks.Remove(i, 1); + return true; + } + } + return false; +} + +// Called by X2AbilityTrigger_EndOfMove::GetAvailableEndOfMoveAbilityForUnit() +// This is an internal CHL API. It is not intended for use by mods and is not covered by Backwards Compatibility policy. +simulated function TriggerPrioritizeRightClickMelee(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject, out XComGameState_Ability PrioritizedMeleeAbility) +{ + local delegate PrioritizeRightClickMeleeFn; + local int i; + + for (i = 0; i < PrioritizeRightClickMeleeCallbacks.Length; i++) + { + PrioritizeRightClickMeleeFn = PrioritizeRightClickMeleeCallbacks[i].PrioritizeRightClickMeleeFn; + + if (PrioritizeRightClickMeleeFn(UnitState, TargetObject, PrioritizedMeleeAbility) == EHLDR_InterruptDelegates) + { + break; + } + } +} +// End Issue #1138 + // Start Issue #855 static function name GetPlaceEvacZoneAbilityName() { diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2AbilityTrigger_EndOfMove.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2AbilityTrigger_EndOfMove.uc index d1f2f8b23..c36f43771 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2AbilityTrigger_EndOfMove.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2AbilityTrigger_EndOfMove.uc @@ -28,12 +28,86 @@ static function bool AbilityHasEndOfMoveTrigger(X2AbilityTemplate Template) } static function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit(XComGameState_Unit UnitState) +{ + // Begin Issue #1138 + // Forward the work to the new endpoint to maintain BC + return GetAvailableEndOfMoveAbilityForUnitWithTarget(UnitState, none); + // End Issue #1138 +} + +/// HL-Docs: feature:PrioritizeRightClickMelee; issue:1138; tags:tactical +/// This feature allows mods to define which melee ability should be prioritized for use when the +/// user triggers a right-click melee action on an enemy. +/// +/// Normally this override would have been implemented as an event, but this check happens in the +/// tactical UI on user-click and movement tile hover-over which could lead to issues and slowdowns, +/// so the delegates system is used instead. +/// +/// ## How to use +/// +/// Implement the following code in your mod's `X2DownloadableContentInfo` class: +/// ```unrealscript +/// static event OnPostTemplatesCreated() +/// { +/// local CHHelpers CHHelpersObj; +/// +/// CHHelpersObj = class'CHHelpers'.static.GetCDO(); +/// if (CHHelpersObj != none) +/// { +/// CHHelpersObj.AddPrioritizeRightClickMeleeCallback(PrioritizeRightClickMelee); +/// } +/// } +/// +/// // To avoid crashes associated with garbage collection failure when transitioning between Tactical and Strategy, +/// // this function must be bound to the ClassDefaultObject of your class. Having this function in a class that +/// // `extends X2DownloadableContentInfo` is the easiest way to ensure that. +/// static private function EHLDelegateReturn PrioritizeRightClickMelee(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject, out XComGameState_Ability PrioritizedMeleeAbility) +/// { +/// local GameRulesCache_Unit UnitCache; +/// local XComGameState_BaseObject TargetState; +/// +/// // To get the current state of the target from the history, use the reference ID. BUT! TargetObject may be NONE +/// if(TargetObject != NONE) +/// { +/// TargetState = `XCOMHISTORY.GetGameStateForObjectID(TargetObject.GetReference().ObjectID); +/// } +/// +/// // To get the list of all available actions, load the unit cache from the tactical ruleset +/// if(`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache)) +/// { +/// // Optionally modify PrioritizedMeleeAbility here. +/// // `PrioritizedMeleeAbility` is the currently prioritized melee skill to use, +/// // and should be replaced with a different skill from the unit's available skills if +/// // using a different prioritization schema. Set `PrioritizedMeleeAbility` to NONE +/// // to revert to base-game behaviour. +/// } +/// +/// // Return EHLDR_NoInterrupt or EHLDR_InterruptDelegates depending on +/// // if you want to allow other delegates to run after yours +/// // and potentially modify PrioritizedMeleeAbility further. +/// return EHLDR_NoInterrupt; +///} +/// ``` +static function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnitWithTarget(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject) { local XComGameStateHistory History; local GameRulesCache_Unit UnitCache; local XComGameState_Ability AbilityState; - local int ActionIndex; + local int ActionIndex; + // Variables for Issue #1138 + local CHHelpers CHHelpersObj; + local XComGameState_Ability PrioritizedMeleeAbility; + + // Begin Issue #1138 + CHHelpersObj = class'CHHelpers'.static.GetCDO(); + CHHelpersObj.TriggerPrioritizeRightClickMelee(UnitState, TargetObject, PrioritizedMeleeAbility); + if(PrioritizedMeleeAbility != none) + { + return PrioritizedMeleeAbility; + } + // End Issue #1138 + // Issue #1138 Original logic fallback if we don't find a priority override if(`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache)) { History = `XCOMHISTORY; diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComPathingPawn.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComPathingPawn.uc index 4098c042b..13a3a27cb 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComPathingPawn.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComPathingPawn.uc @@ -512,7 +512,8 @@ simulated /* protected */ function bool CanUnitMeleeFromMove(XComGameState_BaseO // find the unit's default melee ability UnitState = XComGameState_Unit(History.GetGameStateForObjectID(LastActiveUnit.ObjectID)); - MeleeAbility = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnit(UnitState); + // Issue #1138 Provide the target for determining the melee action to take + MeleeAbility = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnitWithTarget(UnitState, TargetObject); if(MeleeAbility == none) { return false; diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComTacticalInput.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComTacticalInput.uc index 46f508814..41293ecb6 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComTacticalInput.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComTacticalInput.uc @@ -3113,7 +3113,8 @@ function bool ClickToPath() History = `XCOMHISTORY; ActiveUnitState = XComGameState_Unit(History.GetGameStateForObjectID(GetActiveUnit().ObjectID)); - AbilityState = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnit(ActiveUnitState); + // Issue #1138 Provide the target for determining the melee action to take + AbilityState = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnitWithTarget(ActiveUnitState, PathingPawn.LastTargetObject); if(AbilityState != none && PathingPawn.LastTargetObject != none && `TACTICALRULES.GetGameRulesCache_Unit(ActiveUnitState.GetReference(), UnitCache))