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 committed Aug 14, 2023
1 parent a5c048e commit 4399dc1
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 4 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 @@ -287,9 +287,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, XComGameState_BaseObject TargetObject, out XComGameState_Ability PrioritizedMeleeAbility); // Issue #1138

// Start Issue #123
simulated static function RebuildPerkContentCache() {
Expand Down Expand Up @@ -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<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(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject, out XComGameState_Ability PrioritizedMeleeAbility)
{
local delegate<PrioritizeRightClickMeleeDelegate> 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,70 @@ 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(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject, 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 the unit's available skills if
/// // using a different prioritization schema.
///
/// // One way to get the target is to load its state from history: `XCOMHISTORY.GetGameStateForObjectID(TargetObject.ObjectID)
/// // NOTE: TargetObject can be NONE
///
/// // One way to get the actions available for the unit is to use the unit cache: `TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache)
///
/// // 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;
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, none, 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;
Expand All @@ -48,4 +105,26 @@ static function XComGameState_Ability GetAvailableEndOfMoveAbilityForUnit(XComGa
}

return None;
}
}

/// Begin Issue #1138
/// Add new version of the GetAvailableEndOfMoveAbilityForUnit which will allow for a TaretObject
/// to be provided. This version of the ability will allow for mods to refine their targetting
/// based upon what/who is being targetted, enabling such things as right-click moving medkit heals
/// on friendly units.
static function XComGameState_Ability CHL_GetAvailableEndOfMoveAbilityForUnit(XComGameState_Unit UnitState, XComGameState_BaseObject TargetObject)
{
local CHHelpers CHHelpersObj;
local XComGameState_Ability PrioritizedMeleeAbility;

CHHelpersObj = class'CHHelpers'.static.GetCDO();
CHHelpersObj.TriggerPrioritizeRightClickMelee(UnitState, TargetObject, PrioritizedMeleeAbility);
if(PrioritizedMeleeAbility != none)
{
return PrioritizedMeleeAbility;
}

// Original logic fallback if we don't find a priority override
return GetAvailableEndOfMoveAbilityForUnit(UnitState);
}
/// End Issue #1138
Original file line number Diff line number Diff line change
Expand Up @@ -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.CHL_GetAvailableEndOfMoveAbilityForUnit(UnitState, TargetObject);
if(MeleeAbility == none)
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.CHL_GetAvailableEndOfMoveAbilityForUnit(ActiveUnitState, PathingPawn.LastTargetObject);
if(AbilityState != none
&& PathingPawn.LastTargetObject != none
&& `TACTICALRULES.GetGameRulesCache_Unit(ActiveUnitState.GetReference(), UnitCache))
Expand Down

0 comments on commit 4399dc1

Please sign in to comment.