Skip to content

Commit

Permalink
Create delegate handler for prioritizing right click melee #1138
Browse files Browse the repository at this point in the history
  • Loading branch information
FearTheBunnies committed Jul 29, 2022
1 parent 39d60db commit 35cc73c
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 3 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 @@ -278,9 +278,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(array<XComGameState_Ability> MeleeAbilities, out XComGameState_Ability PrioritizedMeleeAbility); // Issue #1138

// Start Issue #123
simulated static function RebuildPerkContentCache() {
Expand Down Expand Up @@ -940,6 +955,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<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, 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<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 function TriggerPrioritizeRightClickMelee(array<XComGameState_Ability> MeleeAbilities, out XComGameState_Ability PrioritizedMeleeAbility)
{
local delegate<PrioritizeRightClickMeleeDelegate> PrioritizeRightClickMeleeFn;
local int i;

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

if (PrioritizeRightClickMeleeFn(MeleeAbilities, PrioritizedMeleeAbility) == 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 @@ -9,6 +9,9 @@

class X2AbilityTrigger_EndOfMove extends X2AbilityTrigger;

// Issue #1138 Property for use by mods to support ability specific weighting in prioritization
var int RightClickMeleePriority;

static function bool AbilityHasEndOfMoveTrigger(X2AbilityTemplate Template)
{
local X2AbilityTrigger Trigger;
Expand All @@ -27,12 +30,56 @@ static function bool AbilityHasEndOfMoveTrigger(X2AbilityTemplate Template)
return false;
}

/// 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(array<XComGameState_Ability> MeleeAbilities, out XComGameState_Ability PrioritizedMeleeAbility)
/// {
/// // Optionally modify PrioritizedMeleeAbility here.
/// // `PrioritizedMeleeAbility` is the currently prioritized melee skill to use,
/// // and should be replaced with a different skill from MeleeAbilities 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 GetAvailableEndOfMoveAbilityForUnit(XComGameState_Unit UnitState)
{
local XComGameStateHistory History;
local GameRulesCache_Unit UnitCache;
local XComGameState_Ability AbilityState;
local int ActionIndex;
// Variables for Issue #1138
local CHHelpers CHHelpersObj;
local XComGameState_Ability FirstMeleeAbility, PrioritizedMeleeAbility;
local array<XComGameState_Ability> MeleeAbilities;

if(`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache))
{
Expand All @@ -42,10 +89,45 @@ static function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit(XComGa
AbilityState = XComGameState_Ability(History.GetGameStateForObjectID(UnitCache.AvailableActions[ActionIndex].AbilityObjectRef.ObjectID));
if(AbilityState != none && AbilityHasEndOfMoveTrigger(AbilityState.GetMyTemplate()))
{
return AbilityState;
// Start Issue #1138
// No longer greedily return the first melee ability, instead gather all eligible
// candidates and submit to a set of delegates for prioritization
if(FirstMeleeAbility == none)
{
FirstMeleeAbility = AbilityState;
}
MeleeAbilities.AddItem(AbilityState);

//return AbilityState;
// End Issue #1138
}
}
}

return None;
}
// Start Issue #1138
if(MeleeAbilities.Length > 0)
{
PrioritizedMeleeAbility = FirstMeleeAbility;
CHHelpersObj = class'CHHelpers'.static.GetCDO();
CHHelpersObj.TriggerPrioritizeRightClickMelee(MeleeAbilities, PrioritizedMeleeAbility);

if(PrioritizedMeleeAbility == none)
{
// If no ability was prioritized revert to default game behavior
return FirstMeleeAbility;
}
// Return the prioritized melee ability
return PrioritizedMeleeAbility;
}
else
{
return None;
}
// End Issue #1138
}

// Issue #1138 Default Properties
defaultproperties
{
RightClickMeleePriority=0
}

0 comments on commit 35cc73c

Please sign in to comment.