Kregano Posted March 23, 2016 Share Posted March 23, 2016 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 More sharing options...
Lucubration Posted March 23, 2016 Share Posted March 23, 2016 (edited) 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 March 23, 2016 by Lucubration Link to comment Share on other sites More sharing options...
Kregano Posted March 23, 2016 Author Share Posted March 23, 2016 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 More sharing options...
zingfharn Posted March 23, 2016 Share Posted March 23, 2016 (edited) 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 March 23, 2016 by zingfharn Link to comment Share on other sites More sharing options...
Kregano Posted March 23, 2016 Author Share Posted March 23, 2016 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 awesomeNot 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 More sharing options...
Lucubration Posted March 23, 2016 Share Posted March 23, 2016 (edited) 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 March 23, 2016 by Lucubration Link to comment Share on other sites More sharing options...
Kregano Posted March 24, 2016 Author Share Posted March 24, 2016 Hmm... I tried that code for the modified targeting cone and it doesn't appear to work. The cone still extends past the range where it affects targets. Link to comment Share on other sites More sharing options...
zingfharn Posted March 24, 2016 Share Posted March 24, 2016 (edited) 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 March 24, 2016 by zingfharn Link to comment Share on other sites More sharing options...
Kregano Posted March 24, 2016 Author Share Posted March 24, 2016 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 More sharing options...
Lucubration Posted March 24, 2016 Share Posted March 24, 2016 (edited) 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 March 24, 2016 by Lucubration Link to comment Share on other sites More sharing options...
Recommended Posts