Jump to content

Creating multitarget abilities


Kregano

Recommended Posts

I've been trying to create a multitarget/AOE version of Suppression, and while I have gotten the multitarget cone to work, I can't get the actual suppression effect to apply to enemies. In fact, the most I get is Red Screen Errors in the tactical debugger listing so many valid tiles that I have to ALT+TAB to get out of the debug game and close it with task manager.

 

I'm obviously missing something, but I don't know what. I'll share what I've found, in the hopes that someone with more code experience can point to where I've gone wrong.

 

Generating a targeting cone and being able to move it:

local X2AbilityTarget_Cursor            CursorTarget;
local X2AbilityMultiTarget_Cone         ConeMultiTarget;

CursorTarget = new class'X2AbilityTarget_Cursor';
Template.AbilityTargetStyle = CursorTarget;	

ConeMultiTarget = new class'X2AbilityMultiTarget_Cone';
ConeMultiTarget.bExcludeSelfAsTargetIfWithinRadius = true;
ConeMultiTarget.ConeEndDiameter = 13 * class'XComWorldData'.const.WORLD_StepSize;
//ConeMultiTarget.bUseWeaponRangeForLength = true;
//ConeMultiTarget.fTargetRadius = 99;     //  large number to handle weapon range - targets will get filtered according to cone constraints
ConeMultiTarget.ConeLength = 4 * class'XComWorldData'.const.WORLD_StepSize;
ConeMultiTarget.bIgnoreBlockingCover = true;
Template.AbilityMultiTargetStyle = ConeMultiTarget;

Template.TargetingMethod = class'X2TargetingMethod_Cone';

Template.TargetingMethod is mandatory if you intend on being able to manually aim the ability; if it's not there, the targeting cone will just project from the front of the soldier.

 

Suppression Template:

 

 

static function X2AbilityTemplate Suppression()
{
local X2AbilityTemplate Template;
local X2AbilityCost_Ammo AmmoCost;
local X2AbilityCost_ActionPoints ActionPointCost;
local X2Effect_ReserveActionPoints ReserveActionPointsEffect;
local X2Effect_Suppression SuppressionEffect;

`CREATE_X2ABILITY_TEMPLATE(Template, 'Suppression');

Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_supression";

AmmoCost = new class'X2AbilityCost_Ammo';
AmmoCost.iAmmo = 2;
Template.AbilityCosts.AddItem(AmmoCost);

ActionPointCost = new class'X2AbilityCost_ActionPoints';
ActionPointCost.bConsumeAllPoints = true; // this will guarantee the unit has at least 1 action point
ActionPointCost.bFreeCost = true; // ReserveActionPoints effect will take all action points away
Template.AbilityCosts.AddItem(ActionPointCost);

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);

Template.AddShooterEffectExclusions();

ReserveActionPointsEffect = new class'X2Effect_ReserveActionPoints';
ReserveActionPointsEffect.ReserveType = 'Suppression';
Template.AddShooterEffect(ReserveActionPointsEffect);

Template.AbilityToHitCalc = default.DeadEye;
Template.AbilityTargetConditions.AddItem(default.LivingHostileUnitDisallowMindControlProperty);
Template.AbilityTargetConditions.AddItem(default.GameplayVisibilityCondition);
Template.AbilityTargetStyle = default.SimpleSingleTarget;
Template.AbilityTriggers.AddItem(default.PlayerInputTrigger);

SuppressionEffect = new class'X2Effect_Suppression';
SuppressionEffect.BuildPersistentEffect(1, false, true, false, eGameRule_PlayerTurnBegin);
SuppressionEffect.bRemoveWhenTargetDies = true;
SuppressionEffect.bRemoveWhenSourceDamaged = true;
SuppressionEffect.bBringRemoveVisualizationForward = true;
SuppressionEffect.SetDisplayInfo(ePerkBuff_Penalty, Template.LocFriendlyName, default.SuppressionTargetEffectDesc, Template.IconImage);
SuppressionEffect.SetSourceDisplayInfo(ePerkBuff_Bonus, Template.LocFriendlyName, default.SuppressionSourceEffectDesc, Template.IconImage);
Template.AddTargetEffect(SuppressionEffect);
Template.AddTargetEffect(HoloTargetEffect());

Template.AbilitySourceName = 'eAbilitySource_Perk';
Template.eAbilityIconBehaviorHUD = eAbilityIconBehavior_AlwaysShow;
Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_LIEUTENANT_PRIORITY;
Template.bDisplayInUITooltip = false;
Template.AdditionalAbilities.AddItem('SuppressionShot');
Template.bIsASuppressionEffect = true;
Template.AbilityConfirmSound = "TacticalUI_ActivateAbility";

Template.AssociatedPassives.AddItem('HoloTargeting');

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = SuppressionBuildVisualization;
Template.BuildAppliedVisualizationSyncFn = SuppressionBuildVisualizationSync;
Template.CinescriptCameraType = "StandardSuppression";

Template.Hostility = eHostility_Offensive;

return Template;
}

 

 

Deleting "Template.AbilityTargetStyle = default.SimpleSingleTarget;" or trying to replace it with a "Template.AbilityTargetStyle = CursorTarget;" doesn't seem to work. There also doesn't seem to be any variables that allow the ability or the SuppressionShot ability to repeat. Pasting the SuppressionEffect stuff into something else, whether original or based on another skill, also doesn't seem to work.

 

Here's the Build Visualization stuff, which I do not comprehend and don't know if it would be relevant to properly displaying an AOE version of the ability:

 

 

simulated function SuppressionBuildVisualization(XComGameState VisualizeGameState, out array<VisualizationTrack> OutVisualizationTracks)
{
local XComGameStateHistory History;
local XComGameStateContext_Ability Context;
local StateObjectReference InteractingUnitRef;

local VisualizationTrack EmptyTrack;
local VisualizationTrack BuildTrack;

local XComGameState_Ability Ability;
local X2Action_PlaySoundAndFlyOver SoundAndFlyOver;

History = `XCOMHISTORY;

Context = XComGameStateContext_Ability(VisualizeGameState.GetContext());
InteractingUnitRef = Context.InputContext.SourceObject;

//Configure the visualization track for the shooter
//****************************************************************************************
BuildTrack = EmptyTrack;
BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(InteractingUnitRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1);
BuildTrack.StateObject_NewState = VisualizeGameState.GetGameStateForObjectID(InteractingUnitRef.ObjectID);
BuildTrack.TrackActor = History.GetVisualizer(InteractingUnitRef.ObjectID);

class'X2Action_ExitCover'.static.AddToVisualizationTrack(BuildTrack, Context);
class'X2Action_StartSuppression'.static.AddToVisualizationTrack(BuildTrack, Context);
OutVisualizationTracks.AddItem(BuildTrack);
//****************************************************************************************
//Configure the visualization track for the target
InteractingUnitRef = Context.InputContext.PrimaryTarget;
Ability = XComGameState_Ability(History.GetGameStateForObjectID(Context.InputContext.AbilityRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1));
BuildTrack = EmptyTrack;
BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(InteractingUnitRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1);
BuildTrack.StateObject_NewState = VisualizeGameState.GetGameStateForObjectID(InteractingUnitRef.ObjectID);
BuildTrack.TrackActor = History.GetVisualizer(InteractingUnitRef.ObjectID);
SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyOver'.static.AddToVisualizationTrack(BuildTrack, Context));
SoundAndFlyOver.SetSoundAndFlyOverParameters(None, Ability.GetMyTemplate().LocFlyOverText, '', eColor_Bad);
if (XComGameState_Unit(BuildTrack.StateObject_OldState).ReserveActionPoints.Length != 0 && XComGameState_Unit(BuildTrack.StateObject_NewState).ReserveActionPoints.Length == 0)
{
SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyOver'.static.AddToVisualizationTrack(BuildTrack, Context));
SoundAndFlyOver.SetSoundAndFlyOverParameters(none, class'XLocalizedData'.default.OverwatchRemovedMsg, '', eColor_Bad);
}
OutVisualizationTracks.AddItem(BuildTrack);
}

simulated function SuppressionBuildVisualizationSync(name EffectName, XComGameState VisualizeGameState, out VisualizationTrack BuildTrack)
{
local X2Action_ExitCover ExitCover;

if (EffectName == class'X2Effect_Suppression'.default.EffectName)
{
ExitCover = X2Action_ExitCover(class'X2Action_ExitCover'.static.AddToVisualizationTrack( BuildTrack, VisualizeGameState.GetContext() ));
ExitCover.bIsForSuppression = true;

class'X2Action_StartSuppression'.static.AddToVisualizationTrack( BuildTrack, VisualizeGameState.GetContext() );
}
}

 

 

 

The SuppressionShot ability, which is the reaction shot (AFAIK):

 

 

static function X2AbilityTemplate SuppressionShot()
{
local X2AbilityTemplate Template;
local X2AbilityCost_ReserveActionPoints ReserveActionPointCost;
local X2AbilityToHitCalc_StandardAim StandardAim;
local X2Condition_Visibility TargetVisibilityCondition;
local X2AbilityTrigger_Event Trigger;
local array<name> SkipExclusions;
local X2Condition_UnitEffectsWithAbilitySource TargetEffectCondition;
local X2Effect_RemoveEffects RemoveSuppression;
local X2Effect ShotEffect;

`CREATE_X2ABILITY_TEMPLATE(Template, 'SuppressionShot');

Template.bDontDisplayInAbilitySummary = true;
ReserveActionPointCost = new class'X2AbilityCost_ReserveActionPoints';
ReserveActionPointCost.iNumPoints = 1;
ReserveActionPointCost.AllowedTypes.AddItem('Suppression');
Template.AbilityCosts.AddItem(ReserveActionPointCost);

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

Template.AbilityTargetConditions.AddItem(default.LivingHostileTargetProperty);

TargetEffectCondition = new class'X2Condition_UnitEffectsWithAbilitySource';
TargetEffectCondition.AddRequireEffect(class'X2Effect_Suppression'.default.EffectName, 'AA_UnitIsNotSuppressed');
Template.AbilityTargetConditions.AddItem(TargetEffectCondition);

TargetVisibilityCondition = new class'X2Condition_Visibility';
TargetVisibilityCondition.bRequireGameplayVisible = true;
Template.AbilityTargetConditions.AddItem(TargetVisibilityCondition);

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);

SkipExclusions.AddItem(class'X2AbilityTemplateManager'.default.DisorientedName);
Template.AddShooterEffectExclusions(SkipExclusions);
Template.bAllowAmmoEffects = true;

RemoveSuppression = new class'X2Effect_RemoveEffects';
RemoveSuppression.EffectNamesToRemove.AddItem(class'X2Effect_Suppression'.default.EffectName);
RemoveSuppression.bCheckSource = true;
RemoveSuppression.SetupEffectOnShotContextResult(true, true);
Template.AddShooterEffect(RemoveSuppression);

Template.AbilityTargetStyle = default.SimpleSingleTarget;

//Trigger on movement - interrupt the move
Trigger = new class'X2AbilityTrigger_Event';
Trigger.EventObserverClass = class'X2TacticalGameRuleset_MovementObserver';
Trigger.MethodName = 'InterruptGameState';
Template.AbilityTriggers.AddItem(Trigger);

Template.AbilitySourceName = 'eAbilitySource_Standard';
Template.eAbilityIconBehaviorHUD = EAbilityIconBehavior_NeverShow;
Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_supression";
Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_LIEUTENANT_PRIORITY;
Template.bDisplayInUITooltip = false;
Template.bDisplayInUITacticalText = false;

//don't want to exit cover, we are already in suppression/alert mode.
Template.bSkipExitCoverWhenFiring = true;

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = TypicalAbility_BuildVisualization;
Template.bAllowFreeFireWeaponUpgrade = true;

// Put holo target effect first because if the target dies from this shot, it will be too late to notify the effect.
ShotEffect = class'X2Ability_GrenadierAbilitySet'.static.HoloTargetEffect();
ShotEffect.TargetConditions.AddItem(class'X2Ability_DefaultAbilitySet'.static.OverwatchTargetEffectsCondition());
Template.AddTargetEffect(ShotEffect);
// Various Soldier ability specific effects - effects check for the ability before applying
ShotEffect = class'X2Ability_GrenadierAbilitySet'.static.ShredderDamageEffect();
ShotEffect.TargetConditions.AddItem(class'X2Ability_DefaultAbilitySet'.static.OverwatchTargetEffectsCondition());
Template.AddTargetEffect(ShotEffect);

return Template;
}

 

 

 

For comparison, Killzone, which has a lot of the AOE stuff I want in my new multitarget ability:

 

 

static function X2AbilityTemplate KillZone()
{
local X2AbilityTemplate Template;
local X2AbilityCooldown Cooldown;
local X2AbilityCost_Ammo AmmoCost;
local X2AbilityCost_ActionPoints ActionPointCost;
local X2AbilityTarget_Cursor CursorTarget;
local X2AbilityMultiTarget_Cone ConeMultiTarget;
local X2Effect_ReserveActionPoints ReservePointsEffect;
local X2Effect_MarkValidActivationTiles MarkTilesEffect;
local X2Condition_UnitEffects SuppressedCondition;

`CREATE_X2ABILITY_TEMPLATE(Template, 'KillZone');

AmmoCost = new class'X2AbilityCost_Ammo';
AmmoCost.iAmmo = 1;
AmmoCost.bFreeCost = true;
Template.AbilityCosts.AddItem(AmmoCost);

ActionPointCost = new class'X2AbilityCost_ActionPoints';
ActionPointCost.iNumPoints = 2;
ActionPointCost.bConsumeAllPoints = true; // this will guarantee the unit has at least 1 action point
ActionPointCost.bFreeCost = true; // ReserveActionPoints effect will take all action points away
Template.AbilityCosts.AddItem(ActionPointCost);

Template.AbilityToHitCalc = default.DeadEye;

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);
Template.AddShooterEffectExclusions();
SuppressedCondition = new class'X2Condition_UnitEffects';
SuppressedCondition.AddExcludeEffect(class'X2Effect_Suppression'.default.EffectName, 'AA_UnitIsSuppressed');
Template.AbilityShooterConditions.AddItem(SuppressedCondition);

Cooldown = new class'X2AbilityCooldown';
Cooldown.iNumTurns = default.KILLZONE_COOLDOWN;
Template.AbilityCooldown = Cooldown;

CursorTarget = new class'X2AbilityTarget_Cursor';
CursorTarget.bRestrictToWeaponRange = true;
Template.AbilityTargetStyle = CursorTarget;

ConeMultiTarget = new class'X2AbilityMultiTarget_Cone';
ConeMultiTarget.bUseWeaponRadius = true;
ConeMultiTarget.ConeEndDiameter = 32 * class'XComWorldData'.const.WORLD_StepSize;
ConeMultiTarget.ConeLength = 60 * class'XComWorldData'.const.WORLD_StepSize;
Template.AbilityMultiTargetStyle = ConeMultiTarget;

Template.AbilityTriggers.AddItem(default.PlayerInputTrigger);

ReservePointsEffect = new class'X2Effect_ReserveActionPoints';
ReservePointsEffect.ReserveType = default.KillZoneReserveType;
Template.AddShooterEffect(ReservePointsEffect);

MarkTilesEffect = new class'X2Effect_MarkValidActivationTiles';
MarkTilesEffect.AbilityToMark = 'KillZoneShot';
Template.AddShooterEffect(MarkTilesEffect);

Template.AdditionalAbilities.AddItem('KillZoneShot');
Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = TypicalAbility_BuildVisualization;
Template.bSkipFireAction = true;
Template.bShowActivation = true;

Template.AbilitySourceName = 'eAbilitySource_Perk';
Template.eAbilityIconBehaviorHUD = EAbilityIconBehavior_AlwaysShow;
Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_killzone";
Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_MAJOR_PRIORITY;
Template.bDisplayInUITooltip = false;
Template.bDisplayInUITacticalText = false;
Template.Hostility = eHostility_Defensive;
Template.AbilityConfirmSound = "Unreal2DSounds_OverWatch";

Template.ActivationSpeech = 'KillZone';

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = TypicalAbility_BuildVisualization;
Template.TargetingMethod = class'X2TargetingMethod_Cone';

Template.bCrossClassEligible = true;

return Template;
}

 

 

I'm not sure what the SuppressedCondition stuff does, but I think it's supposed to exclude all targets in the area that are suppressed. MarkTilesEffect seems to control where the Killzone shot goes. CrossClassEligible presumably allows the skill to be picked up by the AWC.

 

The KillzoneShot ability:

 

 

static function X2AbilityTemplate KillZoneShot()
{
local X2AbilityTemplate Template;
local X2AbilityCost_Ammo AmmoCost;
local X2AbilityCost_ReserveActionPoints ReserveActionPointCost;
local X2AbilityToHitCalc_StandardAim StandardAim;
local X2Condition_AbilityProperty AbilityCondition;
local X2AbilityTarget_Single SingleTarget;
local X2AbilityTrigger_Event Trigger;
local X2Effect_Persistent KillZoneEffect;
local X2Condition_UnitEffectsWithAbilitySource KillZoneCondition;
local X2Condition_Visibility TargetVisibilityCondition;

`CREATE_X2ABILITY_TEMPLATE(Template, 'KillZoneShot');

AmmoCost = new class'X2AbilityCost_Ammo';
AmmoCost.iAmmo = 1;
Template.AbilityCosts.AddItem(AmmoCost);

ReserveActionPointCost = new class'X2AbilityCost_ReserveActionPoints';
ReserveActionPointCost.iNumPoints = 1;
ReserveActionPointCost.bFreeCost = true;
ReserveActionPointCost.AllowedTypes.AddItem('KillZone');
Template.AbilityCosts.AddItem(ReserveActionPointCost);

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

Template.AbilityTargetConditions.AddItem(default.LivingHostileUnitDisallowMindControlProperty);
TargetVisibilityCondition = new class'X2Condition_Visibility';
TargetVisibilityCondition.bRequireGameplayVisible = true;
TargetVisibilityCondition.bDisablePeeksOnMovement = true;
TargetVisibilityCondition.bAllowSquadsight = true;
Template.AbilityTargetConditions.AddItem(TargetVisibilityCondition);
AbilityCondition = new class'X2Condition_AbilityProperty';
AbilityCondition.TargetMustBeInValidTiles = true;
Template.AbilityTargetConditions.AddItem(AbilityCondition);
Template.AbilityTargetConditions.AddItem(class'X2Ability_DefaultAbilitySet'.static.OverwatchTargetEffectsCondition());

// Do not shoot targets that were already hit by this unit this turn with this ability
KillZoneCondition = new class'X2Condition_UnitEffectsWithAbilitySource';
KillZoneCondition.AddExcludeEffect('KillZoneTarget', 'AA_UnitIsImmune');
Template.AbilityTargetConditions.AddItem(KillZoneCondition);
// Mark the target as shot by this unit so it cannot be shot again this turn
KillZoneEffect = new class'X2Effect_Persistent';
KillZoneEffect.EffectName = 'KillZoneTarget';
KillZoneEffect.BuildPersistentEffect(1, false, false, false, eGameRule_PlayerTurnBegin);
KillZoneEffect.SetupEffectOnShotContextResult(true, true); // mark them regardless of whether the shot hit or missed
Template.AddTargetEffect(KillZoneEffect);

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);
Template.AddShooterEffectExclusions();

SingleTarget = new class'X2AbilityTarget_Single';
SingleTarget.OnlyIncludeTargetsInsideWeaponRange = true;
Template.AbilityTargetStyle = SingleTarget;

// Put holo target effect first because if the target dies from this shot, it will be too late to notify the effect.
Template.AddTargetEffect(class'X2Ability_GrenadierAbilitySet'.static.HoloTargetEffect());
Template.AddTargetEffect(class'X2Ability_GrenadierAbilitySet'.static.ShredderDamageEffect());

Template.bAllowAmmoEffects = true;

//Trigger on movement - interrupt the move
Trigger = new class'X2AbilityTrigger_Event';
Trigger.EventObserverClass = class'X2TacticalGameRuleset_MovementObserver';
Trigger.MethodName = 'InterruptGameState';
Template.AbilityTriggers.AddItem(Trigger);
Trigger = new class'X2AbilityTrigger_Event';
Trigger.EventObserverClass = class'X2TacticalGameRuleset_AttackObserver';
Trigger.MethodName = 'InterruptGameState';
Template.AbilityTriggers.AddItem(Trigger);

Template.AbilitySourceName = 'eAbilitySource_Perk';
Template.eAbilityIconBehaviorHUD = EAbilityIconBehavior_NeverShow;
Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_overwatch";
Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_MAJOR_PRIORITY;
Template.bDisplayInUITooltip = false;
Template.bDisplayInUITacticalText = false;

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = TypicalAbility_BuildVisualization;

return Template;
}

 

 

bFreeCost shows up here and in the plain KillZone ability, so I think that might be related to how it can hit multiple enemies in a row. There's also an Overwatch related AbilityTargetConditions that SuppressionShot doesn't have, but that's probably not relevant.

 

Interestingly, I did find some interesting things in Faceoff's data:

 

 

static function X2AbilityTemplate Faceoff()
{
local X2AbilityTemplate Template;
local X2AbilityCooldown Cooldown;
local X2AbilityCost_ActionPoints ActionPointCost;
local X2AbilityToHitCalc_StandardAim ToHitCalc;

`CREATE_X2ABILITY_TEMPLATE(Template, 'Faceoff');

Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_faceoff";
Template.AbilitySourceName = 'eAbilitySource_Perk';
Template.eAbilityIconBehaviorHUD = eAbilityIconBehavior_AlwaysShow;
Template.Hostility = eHostility_Offensive;
Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_COLONEL_PRIORITY;
Template.AbilityConfirmSound = "TacticalUI_ActivateAbility";

Cooldown = new class'X2AbilityCooldown';
Cooldown.iNumTurns = default.FACEOFF_COOLDOWN;
Template.AbilityCooldown = Cooldown;

ToHitCalc = new class'X2AbilityToHitCalc_StandardAim';
ToHitCalc.bOnlyMultiHitWithSuccess = false;
Template.AbilityToHitCalc = ToHitCalc;

ActionPointCost = new class'X2AbilityCost_ActionPoints';
ActionPointCost.iNumPoints = 1;
ActionPointCost.bConsumeAllPoints = true;
Template.AbilityCosts.AddItem(ActionPointCost);

Template.AbilityTargetStyle = default.SimpleSingleTarget;
Template.AbilityMultiTargetStyle = new class'X2AbilityMultiTarget_AllAllies';
Template.AbilityTriggers.AddItem(default.PlayerInputTrigger);

Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);
Template.AddShooterEffectExclusions();

Template.AbilityTargetConditions.AddItem(default.GameplayVisibilityCondition);
Template.AbilityTargetConditions.AddItem(default.LivingHostileTargetProperty);

Template.AddTargetEffect(new class'X2Effect_ApplyWeaponDamage');
Template.AddMultiTargetEffect(new class'X2Effect_ApplyWeaponDamage');

Template.bAllowAmmoEffects = true;

Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
Template.BuildVisualizationFn = Faceoff_BuildVisualization;

return Template;
}

function Faceoff_BuildVisualization(XComGameState VisualizeGameState, out array<VisualizationTrack> OutVisualizationTracks)
{
local X2AbilityTemplate AbilityTemplate;
local XComGameStateContext_Ability Context;
local AbilityInputContext AbilityContext;
local StateObjectReference ShootingUnitRef;
local X2Action_Fire FireAction;
local X2Action_Fire_Faceoff FireFaceoffAction;
local XComGameState_BaseObject TargetStateObject;//Container for state objects within VisualizeGameState

local Actor TargetVisualizer, ShooterVisualizer;
local X2VisualizerInterface TargetVisualizerInterface;
local int EffectIndex, TargetIndex;

local VisualizationTrack EmptyTrack;
local VisualizationTrack BuildTrack;
local VisualizationTrack SourceTrack;
local XComGameStateHistory History;

local X2Action_PlaySoundAndFlyOver SoundAndFlyover;
local name ApplyResult;

local X2Action_StartCinescriptCamera CinescriptStartAction;
local X2Action_EndCinescriptCamera CinescriptEndAction;
local X2Camera_Cinescript CinescriptCamera;
local string PreviousCinescriptCameraType;
local X2Effect TargetEffect;


History = `XCOMHISTORY;
Context = XComGameStateContext_Ability(VisualizeGameState.GetContext());
AbilityContext = Context.InputContext;
AbilityTemplate = class'XComGameState_Ability'.static.GetMyTemplateManager().FindAbilityTemplate(AbilityContext.AbilityTemplateName);
ShootingUnitRef = Context.InputContext.SourceObject;

ShooterVisualizer = History.GetVisualizer(ShootingUnitRef.ObjectID);

SourceTrack = EmptyTrack;
SourceTrack.StateObject_OldState = History.GetGameStateForObjectID(ShootingUnitRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1);
SourceTrack.StateObject_NewState = VisualizeGameState.GetGameStateForObjectID(ShootingUnitRef.ObjectID);
if (SourceTrack.StateObject_NewState == none)
SourceTrack.StateObject_NewState = SourceTrack.StateObject_OldState;
SourceTrack.TrackActor = ShooterVisualizer;

if (AbilityTemplate.ActivationSpeech != '') // allows us to change the template without modifying this function later
{
SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyover'.static.AddToVisualizationTrack(SourceTrack, Context));
SoundAndFlyOver.SetSoundAndFlyOverParameters(None, "", AbilityTemplate.ActivationSpeech, eColor_Good);
}


// Add a Camera Action to the Shooter's track. Minor hack: To create a CinescriptCamera the AbilityTemplate
// must have a camera type. So manually set one here, use it, then restore.
PreviousCinescriptCameraType = AbilityTemplate.CinescriptCameraType;
AbilityTemplate.CinescriptCameraType = "StandardGunFiring";
CinescriptCamera = class'X2Camera_Cinescript'.static.CreateCinescriptCameraForAbility(Context);
CinescriptStartAction = X2Action_StartCinescriptCamera( class'X2Action_StartCinescriptCamera'.static.AddToVisualizationTrack(SourceTrack, Context ) );
CinescriptStartAction.CinescriptCamera = CinescriptCamera;
AbilityTemplate.CinescriptCameraType = PreviousCinescriptCameraType;


class'X2Action_ExitCover'.static.AddToVisualizationTrack(SourceTrack, Context);

// Fire at the primary target first
FireAction = X2Action_Fire(class'X2Action_Fire'.static.AddToVisualizationTrack(SourceTrack, Context));
FireAction.SetFireParameters(Context.IsResultContextHit(), , false);
// Setup target response
TargetVisualizer = History.GetVisualizer(AbilityContext.PrimaryTarget.ObjectID);
TargetVisualizerInterface = X2VisualizerInterface(TargetVisualizer);
BuildTrack = EmptyTrack;
BuildTrack.TrackActor = TargetVisualizer;
TargetStateObject = VisualizeGameState.GetGameStateForObjectID(AbilityContext.PrimaryTarget.ObjectID);
if( TargetStateObject != none )
{
History.GetCurrentAndPreviousGameStatesForObjectID(AbilityContext.PrimaryTarget.ObjectID,
BuildTrack.StateObject_OldState, BuildTrack.StateObject_NewState,
eReturnType_Reference,
VisualizeGameState.HistoryIndex);
`assert(BuildTrack.StateObject_NewState == TargetStateObject);
}
else
{
//If TargetStateObject is none, it means that the visualize game state does not contain an entry for the primary target. Use the history version
//and show no change.
BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(AbilityContext.PrimaryTarget.ObjectID);
BuildTrack.StateObject_NewState = BuildTrack.StateObject_OldState;
}
class'X2Action_WaitForAbilityEffect'.static.AddToVisualizationTrack(BuildTrack, Context);
for (EffectIndex = 0; EffectIndex < AbilityTemplate.AbilityTargetEffects.Length; ++EffectIndex)
{
ApplyResult = Context.FindTargetEffectApplyResult(AbilityTemplate.AbilityTargetEffects[EffectIndex]);

// Target effect visualization
AbilityTemplate.AbilityTargetEffects[EffectIndex].AddX2ActionsForVisualization(VisualizeGameState, BuildTrack, ApplyResult);

// Source effect visualization
AbilityTemplate.AbilityTargetEffects[EffectIndex].AddX2ActionsForVisualizationSource(VisualizeGameState, SourceTrack, ApplyResult);
}
if( TargetVisualizerInterface != none )
{
//Allow the visualizer to do any custom processing based on the new game state. For example, units will create a death action when they reach 0 HP.
TargetVisualizerInterface.BuildAbilityEffectsVisualization(VisualizeGameState, BuildTrack);
}
OutVisualizationTracks.AddItem(BuildTrack);

// Now configure a fire action for each multi target
for (TargetIndex = 0; TargetIndex < AbilityContext.MultiTargets.Length; ++TargetIndex)
{
// Add an action to pop the previous CinescriptCamera off the camera stack.
CinescriptEndAction = X2Action_EndCinescriptCamera( class'X2Action_EndCinescriptCamera'.static.AddToVisualizationTrack( SourceTrack, Context ) );
CinescriptEndAction.CinescriptCamera = CinescriptCamera;
CinescriptEndAction.bForceEndImmediately = true;

// Add an action to push a new CinescriptCamera onto the camera stack.
AbilityTemplate.CinescriptCameraType = "StandardGunFiring";
CinescriptCamera = class'X2Camera_Cinescript'.static.CreateCinescriptCameraForAbility(Context);
CinescriptCamera.TargetObjectIdOverride = AbilityContext.MultiTargets[TargetIndex].ObjectID;
CinescriptStartAction = X2Action_StartCinescriptCamera( class'X2Action_StartCinescriptCamera'.static.AddToVisualizationTrack(SourceTrack, Context ) );
CinescriptStartAction.CinescriptCamera = CinescriptCamera;
AbilityTemplate.CinescriptCameraType = PreviousCinescriptCameraType;

// Add a custom Fire action to the shooter track.
TargetVisualizer = History.GetVisualizer(AbilityContext.MultiTargets[TargetIndex].ObjectID);
FireFaceoffAction = X2Action_Fire_Faceoff(class'X2Action_Fire_Faceoff'.static.AddToVisualizationTrack(SourceTrack, Context));
FireFaceoffAction.SetFireParameters(Context.IsResultContextMultiHit(TargetIndex), AbilityContext.MultiTargets[TargetIndex].ObjectID, false);
FireFaceoffAction.vTargetLocation = TargetVisualizer.Location;


// Setup target response
TargetVisualizerInterface = X2VisualizerInterface(TargetVisualizer);
BuildTrack = EmptyTrack;
BuildTrack.TrackActor = TargetVisualizer;
TargetStateObject = VisualizeGameState.GetGameStateForObjectID(AbilityContext.MultiTargets[TargetIndex].ObjectID);
if( TargetStateObject != none )
{
History.GetCurrentAndPreviousGameStatesForObjectID(AbilityContext.MultiTargets[TargetIndex].ObjectID,
BuildTrack.StateObject_OldState, BuildTrack.StateObject_NewState,
eReturnType_Reference,
VisualizeGameState.HistoryIndex);
`assert(BuildTrack.StateObject_NewState == TargetStateObject);
}
else
{
//If TargetStateObject is none, it means that the visualize game state does not contain an entry for the primary target. Use the history version
//and show no change.
BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(AbilityContext.MultiTargets[TargetIndex].ObjectID);
BuildTrack.StateObject_NewState = BuildTrack.StateObject_OldState;
}

// Add WaitForAbilityEffect. To avoid time-outs when there are many targets, set a custom timeout
class'X2Action_WaitForAbilityEffect'.static.AddToVisualizationTrack(BuildTrack, Context);
BuildTrack.TrackActions[buildTrack.TrackActions.Length - 1].SetCustomTimeoutSeconds((10 + (10 * TargetIndex )));

for (EffectIndex = 0; EffectIndex < AbilityTemplate.AbilityMultiTargetEffects.Length; ++EffectIndex)
{
TargetEffect = AbilityTemplate.AbilityMultiTargetEffects[EffectIndex];
ApplyResult = Context.FindMultiTargetEffectApplyResult(TargetEffect, TargetIndex);

// Target effect visualization
AbilityTemplate.AbilityMultiTargetEffects[EffectIndex].AddX2ActionsForVisualization(VisualizeGameState, BuildTrack, ApplyResult);

// If the last Effect applied was weapon damage, then a weapon damage Action was added to the track.
// Find that weapon damage action, and extend its timeout so that we won't timeout if there are many
// targets to visualize before this one.
if ( X2Effect_ApplyWeaponDamage(TargetEffect) != none )
{
if ( X2Action_ApplyWeaponDamageToUnit(BuildTrack.TrackActions[buildTrack.TrackActions.Length - 1]) != none)
{
BuildTrack.TrackActions[buildTrack.TrackActions.Length - 1].SetCustomTimeoutSeconds((10 + (10 * TargetIndex )));
}
}

// Source effect visualization
AbilityTemplate.AbilityMultiTargetEffects[EffectIndex].AddX2ActionsForVisualizationSource(VisualizeGameState, SourceTrack, ApplyResult);
}
if( TargetVisualizerInterface != none )
{
//Allow the visualizer to do any custom processing based on the new game state. For example, units will create a death action when they reach 0 HP.
TargetVisualizerInterface.BuildAbilityEffectsVisualization(VisualizeGameState, BuildTrack);
}
OutVisualizationTracks.AddItem(BuildTrack);
}
class'X2Action_EnterCover'.static.AddToVisualizationTrack(SourceTrack, Context);

// Add an action to pop the last CinescriptCamera off the camera stack.
CinescriptEndAction = X2Action_EndCinescriptCamera( class'X2Action_EndCinescriptCamera'.static.AddToVisualizationTrack( SourceTrack, Context ) );
CinescriptEndAction.CinescriptCamera = CinescriptCamera;

OutVisualizationTracks.AddItem(SourceTrack);
}

 

 

Maybe the key to getting a multitarget effect working is using SimpleSingleTarget, then X2AbilityMultiTarget_AllAllies, plus a visualization that handles the rest?

Link to comment
Share on other sites

One thing it sounds like you might not be doing is adding the X2Effect_Suppression as a MultiTargetEffect instead of just a TargetEffect.

 

Here's a very similar ability template I put together for building an AoE version of Suppression for Beaglerush's Escalation classes:

 

 

 

// Replaces Suppression default ability when the soldier has Danger Zone
static function X2AbilityTemplate DangerZoneSuppression()
{
	local X2AbilityTemplate					Template;
	local X2AbilityCost_ActionPoints		ActionPointCost;
	local X2AbilityCost_Ammo				AmmoCost;
	local X2Effect_ReserveActionPoints      ReserveActionPointsEffect;
	local X2AbilityMultiTarget_Radius		RadiusMultiTarget;
	local X2Condition_UnitProperty			UnitPropertyCondition;
	local X2Effect_Suppression              SuppressionEffect;
	
	`LOG("Beags Escalation: Danger Zone Suppression radius=" @ string(default.DangerZoneSuppressionRadius));

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

	ActionPointCost = new class'X2AbilityCost_ActionPoints';
	ActionPointCost.bConsumeAllPoints = true;   //  this will guarantee the unit has at least 1 action point
	ActionPointCost.bFreeCost = true;           //  ReserveActionPoints effect will take all action points away
	Template.AbilityCosts.AddItem(ActionPointCost);
	
	AmmoCost = new class'X2AbilityCost_Ammo';	
	AmmoCost.iAmmo = 2;
	Template.AbilityCosts.AddItem(AmmoCost);
	
	Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty);
	
	Template.AddShooterEffectExclusions();
	
	ReserveActionPointsEffect = new class'X2Effect_ReserveActionPoints';
	ReserveActionPointsEffect.ReserveType = 'Suppression';
	Template.AddShooterEffect(ReserveActionPointsEffect);
	
	Template.AbilityToHitCalc = default.DeadEye;	
	Template.AbilityTargetConditions.AddItem(default.LivingHostileUnitDisallowMindControlProperty);
	Template.AbilityTargetConditions.AddItem(default.GameplayVisibilityCondition);
	Template.AbilityTriggers.AddItem(default.PlayerInputTrigger);
	Template.AbilityTargetStyle = new class'X2AbilityTarget_Cursor';
	RadiusMultiTarget = new class'X2AbilityMultiTarget_Radius';
	RadiusMultiTarget.fTargetRadius = `UNITSTOMETERS(default.DangerZoneSuppressionRadius);
	Template.AbilityMultiTargetStyle = RadiusMultiTarget;

	Template.TargetingMethod = class'X2TargetingMethod_Beags_Escalation_Radius';

	UnitPropertyCondition = new class'X2Condition_UnitProperty';
	UnitPropertyCondition.ExcludeDead = true;
	UnitPropertyCondition.ExcludeFriendlyToSource = false;
	Template.AbilityShooterConditions.AddItem(UnitPropertyCondition);
	Template.AbilityTargetConditions.AddItem(UnitPropertyCondition);
	
	SuppressionEffect = new class'X2Effect_Suppression';
	SuppressionEffect.BuildPersistentEffect(1, false, true, false, eGameRule_PlayerTurnBegin);
	SuppressionEffect.bRemoveWhenTargetDies = true;
	SuppressionEffect.bRemoveWhenSourceDamaged = true;
	SuppressionEffect.bBringRemoveVisualizationForward = true;
	SuppressionEffect.SetDisplayInfo(ePerkBuff_Penalty, Template.LocFriendlyName, class'X2Ability_GrenadierAbilitySet'.default.SuppressionTargetEffectDesc, Template.IconImage);
	SuppressionEffect.SetSourceDisplayInfo(ePerkBuff_Bonus, Template.LocFriendlyName, class'X2Ability_GrenadierAbilitySet'.default.SuppressionSourceEffectDesc, Template.IconImage);
	Template.AddMultiTargetEffect(SuppressionEffect);
	Template.AddTargetEffect(class'X2Ability_GrenadierAbilitySet'.static.HoloTargetEffect());
	Template.bAllowAmmoEffects = true;

	Template.ShotHUDPriority = class'UIUtilities_Tactical'.const.CLASS_LIEUTENANT_PRIORITY;
	Template.AbilitySourceName = 'eAbilitySource_Perk';
	Template.eAbilityIconBehaviorHUD = eAbilityIconBehavior_AlwaysShow;
	Template.bDisplayInUITooltip = false;
	Template.AdditionalAbilities.AddItem('SuppressionShot');
	Template.bIsASuppressionEffect = true;
	Template.AbilityConfirmSound = "TacticalUI_ActivateAbility";
	
	Template.AssociatedPassives.AddItem('HoloTargeting');
	
	Template.CinescriptCameraType = "StandardSuppression";
	Template.BuildNewGameStateFn = TypicalAbility_BuildGameState;
	Template.BuildVisualizationFn = DangerZoneSuppressionBuildVisualization;
	Template.BuildAppliedVisualizationSyncFn = class'X2Ability_GrenadierAbilitySet'.static.SuppressionBuildVisualizationSync;
	
	Template.OverrideAbilities.AddItem('Suppression');

	return Template;
}

simulated function DangerZoneSuppressionBuildVisualization(XComGameState VisualizeGameState, out array<VisualizationTrack> OutVisualizationTracks)
{
	local XComGameStateHistory History;
	local XComGameStateContext_Ability  Context;
	local StateObjectReference          InteractingUnitRef;

	local VisualizationTrack        EmptyTrack;
	local VisualizationTrack        BuildTrack;

	local int i;
	local XComGameState_Ability         Ability;
	local X2Action_PlaySoundAndFlyOver SoundAndFlyOver;

	local XComUnitPawn					UnitPawn;
	local XComWeapon					Weapon;

	History = `XCOMHISTORY;

	Context = XComGameStateContext_Ability(VisualizeGameState.GetContext());
	InteractingUnitRef = Context.InputContext.SourceObject;

	//Configure the visualization track for the shooter
	//****************************************************************************************
	BuildTrack = EmptyTrack;
	BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(InteractingUnitRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1);
	BuildTrack.StateObject_NewState = VisualizeGameState.GetGameStateForObjectID(InteractingUnitRef.ObjectID);
	BuildTrack.TrackActor = History.GetVisualizer(InteractingUnitRef.ObjectID);

	// Check the actor's pawn and weapon, see if they can play the suppression effect
	UnitPawn = XGUnit(BuildTrack.TrackActor).GetPawn();
	Weapon = XComWeapon(UnitPawn.Weapon);
	if (Weapon != None &&
		!UnitPawn.GetAnimTreeController().CanPlayAnimation(Weapon.WeaponSuppressionFireAnimSequenceName) &&
		!UnitPawn.GetAnimTreeController().CanPlayAnimation(class'XComWeapon'.default.WeaponSuppressionFireAnimSequenceName))
	{
		// The unit can't play their weapon's suppression effect. Replace it with the normal fire effect so at least they'll look like they're shooting
		Weapon.WeaponSuppressionFireAnimSequenceName = Weapon.WeaponFireAnimSequenceName;
	}
	
	class'X2Action_ExitCover'.static.AddToVisualizationTrack(BuildTrack, Context);
	class'X2Action_StartSuppression'.static.AddToVisualizationTrack(BuildTrack, Context);
	OutVisualizationTracks.AddItem(BuildTrack);
	//****************************************************************************************
	//Configure the visualization track for the targets
	for (i = 0; i < Context.InputContext.MultiTargets.Length; i++)
	{
		// Fake it out by assigning the first multi-target as the primary target
		if (Context.InputContext.PrimaryTarget.ObjectID == 0)
			Context.InputContext.PrimaryTarget = Context.InputContext.MultiTargets[i];

		InteractingUnitRef = Context.InputContext.MultiTargets[i];
		Ability = XComGameState_Ability(History.GetGameStateForObjectID(Context.InputContext.AbilityRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1));
		BuildTrack = EmptyTrack;
		BuildTrack.StateObject_OldState = History.GetGameStateForObjectID(InteractingUnitRef.ObjectID, eReturnType_Reference, VisualizeGameState.HistoryIndex - 1);
		BuildTrack.StateObject_NewState = VisualizeGameState.GetGameStateForObjectID(InteractingUnitRef.ObjectID);
		BuildTrack.TrackActor = History.GetVisualizer(InteractingUnitRef.ObjectID);
		SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyOver'.static.AddToVisualizationTrack(BuildTrack, Context));
		SoundAndFlyOver.SetSoundAndFlyOverParameters(None, Ability.GetMyTemplate().LocFlyOverText, '', eColor_Bad);
		if (XComGameState_Unit(BuildTrack.StateObject_OldState).ReserveActionPoints.Length != 0 && XComGameState_Unit(BuildTrack.StateObject_NewState).ReserveActionPoints.Length == 0)
		{
			SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyOver'.static.AddToVisualizationTrack(BuildTrack, Context));
			SoundAndFlyOver.SetSoundAndFlyOverParameters(none, class'XLocalizedData'.default.OverwatchRemovedMsg, '', eColor_Bad);
		}
		OutVisualizationTracks.AddItem(BuildTrack);
	}
} 

 

 

 

As you can see, I made this one a radius target instead of a cone target.

 

There were two other things I had to do to get this to work:

  • I changed the visualization function to make the first MultiTarget into the PrimaryTarget because the Suppression idle animation (which does the shooting loop) requires a primary target to aim at.
  • I replaced the weapon's animation for Suppression with its normal animation for Firing if it didn't support the suppression animation. This is the same technique I used to create the Suppression Visualization Fix mod for the single-target effect.
Edited by Lucubration
Link to comment
Share on other sites

I really appreciate the help. I'm not really surprised to see that my mistake was super simple, since this is really the first time I'm messing with Unreal code. I'll test out a tweaked version of your code, then work from there.

 

If/when I do publish the mod, I'll definitely credit you for your assistance.

Link to comment
Share on other sites

Just curious here, but could you also use the visualisation for Devastation (or whatever it's called - the one where it fires in a big arc at all the things) and loop it? That'd be pretty awesome

Edited by zingfharn
Link to comment
Share on other sites

Just curious here, but could you also use the visualisation for Devastation (or whatever it's called - the one where it fires in a big arc at all the things) and loop it? That'd be pretty awesome

Not sure if it's possible, but that would be cool.

 

Also, does anyone know where to find the actual range values for weapons? I've gotten the ability to work, but I've found that the cone extends way beyond the actual range that the cannons can reach, but I'm not sure how far. If I know the maximum range of the cannons, I can make the cone only as long as it needs to be.

 

Looking at the DefaultWeapon.uc file didn't help, because it only had text strings for the weapon ranges, instead of actual values, and as far as I can tell, the .ini file that handles weapons only has aim bonuses at different ranges.

Link to comment
Share on other sites

One issue I found working with similar abilities is that weapon ranges are actually defined as being relatively infinite (99 tiles, I think), and the targeting restrictions for normal shooting abilities are done using the soldier's max sight range stat in the targeting method. I'd suggest making a similar override of the X2TargetingMethod_Cone and chaining the cursor to the unit's max sight range in the Init function.

 

Something like:

class X2TargetingMethod_YourModNameHere_Cone extends X2TargetingMethod_Cone;

function Init(AvailableAction InAction)
{
	local float TargetingRange;

	super.Init(InAction);

	// determine our targeting range
	TargetingRange = UnitState.GetCurrentStat(eStat_SightRadius);

	// lock the cursor to that range
	Cursor.m_fMaxChainedDistance = `METERSTOUNITS(TargetingRange);
}

I think 'Cursor' is defined in the superclass here and used to restrict the cursor movement range for the ability, but I can't be certain.

Edited by Lucubration
Link to comment
Share on other sites

Yup, I was going to add - I suspect it's hooked to vision, because when you get the vision increase hack reward, your range seamlessly increases.

 

As for the animation/ability, it's called SaturationFire. I'm not sure how abilities determine their firing animation, though - I leave that to you!

 

Edit2: just looked at your first post. Doh.

 

But, could you do

ConeMultiTarget.ConeEndDiameter = UnitState.GetCurrentStat(eStat_SightRadius);
Edited by zingfharn
Link to comment
Share on other sites

I tried using the "UnitState.GetCurrentStat(eStat_SightRadius);" command on ConeEndLength (which is what actually controls how long the cone is; ConeEndDiameter is width, oddly enough), and the SDK spat out this error at me:

 

Warning/Error Summary
---------------------
E:\SteamLibrary\SteamApps\common\XCOM 2 SDK\Development\Src\ConeofFire\Classes\X2Ability_ConeOfFireStuff.uc(75) : Error, Bad or missing expression for token: UnitState, in '='

 

Usually, this is solved by adding a "local X2Ability_____ _______" line to the top of the ability template, but since ConeMultiTarget is already there, I'm not exactly sure what I'm supposed to do here.

Link to comment
Share on other sites

Oh, You don't want to set it in the ability template, but in a subclass of X2TargetingMethod_Cone which you assign as the ability template's TargetingMethod; basically, replace the line about Cursor.m_fMaxChainedDistance from the example above with the assignment that you were trying for the cone length. The ability template won't know what UnitState is.

Edited by Lucubration
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...