RedditPP Mascot

RedditPP

r/xcom2mods • … • u/Level3_Ghostline

Need help on reaction ability that triggers on psionic (or alternately non-psionic) attack

The goal of my tinkering is to get a Covering Fire-type reaction event to occur with two additional mutually exclusive conditions: Either trigger only if the ability targeting you is psionic (AbilityTemplate.AbilitySourceName == 'eAbilitySource_Psionic') or only if it is not psionic. This is meant to empower two planned custom abilities, one of which only reacts to psionic attacks, the other to only non-psionic attacks.

The logic for applying the covering fire check is in XComGameState_Effect, and as it's a XComGameState object, I'm fairly sure I can't override it.

So far I've tried extending X2Effect_Covering fire and overriding RegisterForEvents() to make the EventMgr.RegisterForEvent() call for my own custom callback object which performs the relevant check for whether the source ability is psionic, then continues the call on the XComGameState_Effect if the conditions are met. Basically a wrapper around the callback.

And while this technically works (it fires correctly based on whether I want it exclusive to psionic or non-psionic ability source), something else is broken, as the effect continues to trigger on subsequent attacks in the same turn when it should only be limited to one grant. Switching out the firing effect to an X2Effect_CoveringFire instead (keeping all the rest of the parameters set the same) results in correct behavior (trigger only once per turn), just obviously without the psionic check filtering.

Code below:

class X2Effect_CoveringFire_Psionic_Check extends X2Effect_CoveringFire;

var bool bTriggerOnPsionicsOnly; // if true, only triggers when the ability source is psionic var bool bTriggerExcludesPsionics; // if true, only triggers when the ability source is non-psionic var X2CoveringFirePsionicCheckAdapter PsionicCheckAdapter;

// copy of super function, but we'll listen for the event first function RegisterForEvents(XComGameState_Effect EffectGameState) { local X2EventManager EventMgr; local Object EffectObj; local Object PsionicCheckAdapterObj; //local X2CoveringFirePsionicCheckAdapter PsionicCheckAdapter; local XComGameState GameState;

EventMgr = `XEVENTMGR;

EffectObj = EffectGameState; GameState = EffectGameState.GetParentGameState();

if (bTriggerOnPsionicsOnly && bTriggerExcludesPsionics) { `Redscreen("X2Effect_CoveringFire_Psionic_Check: Useless configuration, all attacks excluded."); return; }

if (!bTriggerOnPsionicsOnly && !bTriggerExcludesPsionics) { // Nothing excluded, superclass behavior should suffice Super.RegisterForEvents(EffectGameState); return; }

// set the adapter. We will intercept, then delegate if necessary. PsionicCheckAdapter = new class'X2CoveringFirePsionicCheckAdapter'; PsionicCheckAdapter.EffectGameState = EffectGameState; PsionicCheckAdapter.bTriggerOnPsionicsOnly = bTriggerOnPsionicsOnly; PsionicCheckAdapter.bTriggerExcludesPsionics = bTriggerExcludesPsionics; PsionicCheckAdapterObj = PsionicCheckAdapter; EventMgr.RegisterForEvent(PsionicCheckAdapterObj, 'AbilityActivated', PsionicCheckAdapter.CoveringFirePsionicCheck, ELD_OnStateSubmitted); }

class X2CoveringFirePsionicCheckAdapter extends XComGameState_BaseObject;

// set this before setting callback. We will delegate to this if the ability source is psionic var XComGameState_Effect EffectGameState; var bool bTriggerOnPsionicsOnly; // if true, only triggers when the ability source is psionic var bool bTriggerExcludesPsionics; // if true, only triggers when the ability source is non-psionic

// Callback for EventMgr.RegisterForEvent // perform check for psi source first. If we're good, do covering fire check function EventListenerReturn CoveringFirePsionicCheck(Object EventData, Object EventSource, XComGameState GameState, Name EventID) { // all 4 of these referring to the ability being fired at us, unwinding the onion local XComGameStateContext_Ability AbilityContext; local StateObjectReference AbilityRef; local XComGameState_Ability AbilityState; local X2AbilityTemplate AbilityTemplate;

local bool IsPsionicAbility; local XComGameStateHistory History;

History = `XCOMHISTORY;

// keep unwinding until we get to the underlying AbilityTemplate AbilityContext = XComGameStateContext_Ability(GameState.GetContext()); AbilityRef = AbilityContext.InputContext.AbilityRef; AbilityState = XComGameState_Ability(History.GetGameStateForObjectID(AbilityRef.ObjectID)); AbilityTemplate = AbilityState.getMyTemplate();

IsPsionicAbility = AbilityTemplate.AbilitySourceName == 'eAbilitySource_Psionic';

if ((IsPsionicAbility && bTriggerExcludesPsionics) || (!IsPsionicAbility && bTriggerOnPsionicsOnly)) { return ELR_NoInterrupt; // desired psionic conditions don't match, abort }

return EffectGameState.CoveringFireCheck(EventData, EventSource, GameState, EventID); }

I'm looking for some help figuring out what's going wrong, and I'm also looking for alternate solutions. I'm wondering if extending an X2Condition might help, but I don't quite have my head wrapped around how these work, and if they can apply as checks to abilities fired at my soldier with this countershot ability. I'm rather skeptical that this could work, though, as it seems like it's more of a condition to be applied on the ability you want to fire (my countershot ability) rather than an ability you are reacting to that's targeting you.

EDIT: Adding the code for my WildTalentAbilitySet, retaining only code related to this issue. Specifically, the two abilities PrecognitiveCounter (and PrecognitiveCounterShot) and PsiFeedback (plus PsiFeedbackPulse). PrecognitiveCounter should only be triggering on non-psionic attacks, PsiFeedback only on psionic attacks.

class X2Ability_WildTalentAbilitySet extends X2Ability_PsiOperativeAbilitySet dependson (XComGameStateContext_Ability) config(WildTalentAbilities);

var name PSI_FEEDBACK_ACTION_POINT; var name PRECOGNITIVE_COUNTER_ACTION_POINT;

static function array<X2DataTemplate> CreateTemplates() { local array<X2DataTemplate> Templates;

Templates.AddItem(PrecognitiveCounterShot()); Templates.AddItem(PrecognitiveCounter()); Templates.AddItem(PsiFeedbackPulse()); Templates.AddItem(PsiFeedback());

return Templates; }

// PrecognitiveCounter // Based initially on Riposte (ReturnFire3) from Isms's Rogue Class Mod // Pre-emptively counter-fires primary weapon in response to a direct non-psionic attack

static function X2AbilityTemplate PrecognitiveCounter() { local X2AbilityTemplate Template; local X2AbilityTargetStyle TargetStyle; local X2AbilityTrigger Trigger; //local X2Effect_CoveringFire FireEffect; local X2Effect_CoveringFire_Psionic_Check FireEffect; local X2Condition_AbilityProperty FireCondition;

`CREATE_X2ABILITY_TEMPLATE(Template, 'PrecognitiveCounter'); Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_returnfire";

Template.AbilitySourceName = 'eAbilitySource_Perk'; Template.eAbilityIconBehaviorHUD = EAbilityIconBehavior_NeverShow; Template.Hostility = eHostility_Neutral;

Template.AbilityToHitCalc = default.DeadEye;

TargetStyle = new class'X2AbilityTarget_Self'; Template.AbilityTargetStyle = TargetStyle;

Trigger = new class'X2AbilityTrigger_UnitPostBeginPlay'; Template.AbilityTriggers.AddItem(Trigger);

//FireEffect = new class'X2Effect_CoveringFire'; FireEffect = new class'X2Effect_CoveringFire_Psionic_Check'; FireEffect.bTriggerExcludesPsionics = true;

FireEffect.EffectName = 'WildTalentPrecognitiveCounterShotEffect'; FireEffect.AbilityToActivate = 'PrecognitiveCounterShot'; FireEffect.GrantActionPoint = default.PRECOGNITIVE_COUNTER_ACTION_POINT; FireEffect.MaxPointsPerTurn = 1; FireEffect.bDirectAttackOnly = true; FireEffect.bPreEmptiveFire = true; FireEffect.bOnlyDuringEnemyTurn = true; FireEffect.DuplicateResponse = eDupe_Ignore; FireEffect.BuildPersistentEffect(1, true, false, false, eGameRule_PlayerTurnBegin); FireCondition = new class'X2Condition_AbilityProperty'; FireCondition.OwnerHasSoldierAbilities.AddItem('PrecognitiveCounter'); FireEffect.TargetConditions.AddItem(FireCondition); FireEffect.SetDisplayInfo(ePerkBuff_Passive, Template.LocFriendlyName, Template.GetMyLongDescription(), Template.IconImage,,,Template.AbilitySourceName); Template.AddTargetEffect(FireEffect);

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState; // NOTE: No visualization on purpose!

Template.bCrossClassEligible = false;

return Template; }

// PrecognitiveCounterShot // Reaction shot taken when PrecognitiveCounter triggers.

static function X2AbilityTemplate PrecognitiveCounterShot() { local X2AbilityTemplate Template; local X2AbilityCost_Ammo AmmoCost; local X2AbilityCost_ReserveActionPoints ReserveActionPointCost; local X2AbilityToHitCalc_StandardAim StandardAim; local X2Condition_UnitProperty ShooterCondition; local X2AbilityTarget_Single SingleTarget; local X2AbilityTrigger_Event Trigger; local X2Effect_Knockback KnockbackEffect; local X2Condition_Visibility TargetVisibilityCondition; local array<name> SkipExclusions;

`CREATE_X2ABILITY_TEMPLATE(Template, 'PrecognitiveCounterShot'); Template.bShowActivation = TRUE; Template.bDontDisplayInAbilitySummary = true; AmmoCost = new class'X2AbilityCost_Ammo'; AmmoCost.iAmmo = 1; Template.AbilityCosts.AddItem(AmmoCost);

ReserveActionPointCost = new class'X2AbilityCost_ReserveActionPoints'; ReserveActionPointCost.iNumPoints = 1; ReserveActionPointCost.AllowedTypes.AddItem(default.PRECOGNITIVE_COUNTER_ACTION_POINT); Template.AbilityCosts.AddItem(ReserveActionPointCost);

StandardAim = new class'X2AbilityToHitCalc_StandardAim'; StandardAim.bReactionFire = true; Template.AbilityToHitCalc = StandardAim; Template.AbilityToHitOwnerOnMissCalc = StandardAim;

Template.AbilityTargetConditions.AddItem(default.LivingHostileUnitDisallowMindControlProperty);

TargetVisibilityCondition = new class'X2Condition_Visibility'; TargetVisibilityCondition.bRequireGameplayVisible = true; TargetVisibilityCondition.bRequireBasicVisibility = true; TargetVisibilityCondition.bDisablePeeksOnMovement = false; // Counterfire even when fired from a peek tile Template.AbilityTargetConditions.AddItem(TargetVisibilityCondition);

Template.AbilityTargetConditions.AddItem(new class'X2Condition_EverVigilant'); Template.AbilityTargetConditions.AddItem(class'X2Ability_DefaultAbilitySet'.static.OverwatchTargetEffectsCondition());

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty); ShooterCondition = new class'X2Condition_UnitProperty'; ShooterCondition.ExcludeConcealed = true; Template.AbilityShooterConditions.AddItem(ShooterCondition);

— 0