Skip to content

Commit

Permalink
Enhance r-click melee check to include target object
Browse files Browse the repository at this point in the history
  • Loading branch information
FearTheBunnies authored and Iridar committed Aug 19, 2023
1 parent e75c912 commit 43c368b
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 12 deletions.
104 changes: 104 additions & 0 deletions X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,24 @@ struct OverrideHasHeightAdvantageStruct
var protectedwrite array<OverrideHasHeightAdvantageStruct> OverrideHasHeightAdvantageCallbacks;
// End Issue #851

// Start Issue #1138
struct PrioritizeRightClickMeleeStruct
{
var delegate<PrioritizeRightClickMeleeDelegate> PrioritizeRightClickMeleeFn;
var int Priority;

structdefaultproperties
{
Priority = 50
}
};
var protectedwrite array<PrioritizeRightClickMeleeStruct> 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, out XComGameState_Ability PrioritizedMeleeAbility, optional XComGameState_BaseObject TargetObject); // Issue #1138

// Start Issue #123
simulated static function RebuildPerkContentCache() {
Expand Down Expand Up @@ -955,6 +970,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. The default Priority is 50.
///```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 final function bool AddPrioritizeRightClickMeleeCallback(delegate<PrioritizeRightClickMeleeDelegate> 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, use this function to remove a delegate.
///```unrealscript
///CHHelpersObj.RemovePrioritizeRightClickMeleeCallback(PrioritizeRightClickMelee);
///```
/// The function will return `true` if the Callback was successfully deleted, return `false` otherwise.
simulated final function bool RemovePrioritizeRightClickMeleeCallback(delegate<PrioritizeRightClickMeleeDelegate> 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 final function TriggerPrioritizeRightClickMelee(XComGameState_Unit UnitState, out XComGameState_Ability PrioritizedMeleeAbility, optional XComGameState_BaseObject TargetObject)
{
local delegate<PrioritizeRightClickMeleeDelegate> PrioritizeRightClickMeleeFn;
local int i;

for (i = 0; i < PrioritizeRightClickMeleeCallbacks.Length; i++)
{
PrioritizeRightClickMeleeFn = PrioritizeRightClickMeleeCallbacks[i].PrioritizeRightClickMeleeFn;

if (PrioritizeRightClickMeleeFn(UnitState, PrioritizedMeleeAbility, TargetObject) == EHLDR_InterruptDelegates)
{
break;
}
}
}
// End Issue #1138

// Start Issue #855
static function name GetPlaceEvacZoneAbilityName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,88 @@ static function bool AbilityHasEndOfMoveTrigger(X2AbilityTemplate Template)
return false;
}

// Begin Issue #1138
/// HL-Docs: feature:PrioritizeRightClickMelee; issue:1138; tags:tactical
/// This feature allows mods to override the logic for selecting the ability
/// that should be used when the player triggers a right-click melee action on a target.
///
/// ## How to use
///
/// Implement the following code in your mod's class that `extends X2DownloadableContentInfo`:
/// ```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, out XComGameState_Ability PrioritizedMeleeAbility, optional XComGameState_BaseObject TargetObject)
/// {
// // 'PrioritizedMeleeAbility' will store the ability selected by the game's default logic.
/// // Replace it with a different ability if needed.
///
/// // Important! `TargetObject` can be `none`, if other mods call the original `GetAvailableEndOfMoveAbilityForUnit()`
/// // instead of `GetAvailableEndOfMoveAbilityForUnit_CH()` or do not pass a target object to it.
///
/// // Return EHLDR_NoInterrupt if you want to allow other delegates to run after yours
/// // and potentially set a different PrioritizedMeleeAbility (recommended).
/// // Return EHLDR_InterruptDelegates if you want your decision to be final.
/// return EHLDR_NoInterrupt;
///}
/// ```
static function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit(XComGameState_Unit UnitState)
{
local XComGameStateHistory History;
local GameRulesCache_Unit UnitCache;
return GetAvailableEndOfMoveAbilityForUnit_Internal(UnitState);
}

// Issue #1138 - new version of the GetAvailableEndOfMoveAbilityForUnit() that takes TargetObject as an argument.
static final function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit_CH(XComGameState_Unit UnitState, optional XComGameState_BaseObject TargetObject)
{
return GetAvailableEndOfMoveAbilityForUnit_Internal(UnitState, TargetObject);
}

// Issue #1138 - original implementation of GetAvailableEndOfMoveAbilityForUnit with support for the delegate and optional TargetObject
// Responsible for finding the ability that should be activated by right-clicking a target.
static private final function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit_Internal(XComGameState_Unit UnitState, optional XComGameState_BaseObject TargetObject)
{
local XComGameStateHistory History;
local GameRulesCache_Unit UnitCache;
local XComGameState_Ability AbilityState;
local int ActionIndex;
local AvailableAction AvAction;

// Variables for Issue #1138
local CHHelpers CHHelpersObj;
local XComGameState_Ability PrioritizedMeleeAbility;

if(`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache))
// Otherwise use the original logic for selecting the ability
if (`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache))
{
History = `XCOMHISTORY;
for(ActionIndex = 0; ActionIndex < UnitCache.AvailableActions.Length; ActionIndex++)

// Issue #1138 - optimization: replaced for() with faster foreach()
foreach UnitCache.AvailableActions(AvAction)
{
AbilityState = XComGameState_Ability(History.GetGameStateForObjectID(UnitCache.AvailableActions[ActionIndex].AbilityObjectRef.ObjectID));
if(AbilityState != none && AbilityHasEndOfMoveTrigger(AbilityState.GetMyTemplate()))
AbilityState = XComGameState_Ability(History.GetGameStateForObjectID(AvAction.AbilityObjectRef.ObjectID));
if (AbilityState != none && AbilityHasEndOfMoveTrigger(AbilityState.GetMyTemplate()))
{
return AbilityState;
PrioritizedMeleeAbility = AbilityState;
break;
}
}
}

return None;
}
// Issue #1138 - Trigger the override
CHHelpersObj = class'CHHelpers'.static.GetCDO();
CHHelpersObj.TriggerPrioritizeRightClickMelee(UnitState, PrioritizedMeleeAbility, TargetObject);

return PrioritizedMeleeAbility;
}
// End Issue #1138
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ 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);
MeleeAbility = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnit_CH(UnitState, TargetObject); // Issue #1138
if(MeleeAbility == none)
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3113,7 +3113,7 @@ function bool ClickToPath()
History = `XCOMHISTORY;

ActiveUnitState = XComGameState_Unit(History.GetGameStateForObjectID(GetActiveUnit().ObjectID));
AbilityState = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnit(ActiveUnitState);
AbilityState = class'X2AbilityTrigger_EndOfMove'.static.GetAvailableEndOfMoveAbilityForUnit_CH(ActiveUnitState, PathingPawn.LastTargetObject); // Issue #1138
if(AbilityState != none
&& PathingPawn.LastTargetObject != none
&& `TACTICALRULES.GetGameRulesCache_Unit(ActiveUnitState.GetReference(), UnitCache))
Expand Down

0 comments on commit 43c368b

Please sign in to comment.