riderSirius Posted May 12, 2016 Share Posted May 12, 2016 (edited) Hi there, This is my firt post on this forum so i'll introduce myself quickly : Being a casual modder (Dawn of War, State of Decay, Civilization V ) and having a background in development (computer science engineer degree) I am currently working on a (quite ambitious) mod project for Xcom 2. I will make a full mod project description topic when the first class of the class collection I intend to make will be near completion. Now getting to the point : Ive been working on an ability based on BattleScanner Item and ScanningProtocol Ability called ReconArea. In the ability template i want to use a custom X2AbilityMultiTargetStyle class (X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat) that extends X2AbilityMultiTarget_Radius. I was almost sure it was possible to extend native classes (read the topic with Amineri explanations on how it was done in LW LeaderPack mod) but i have a very hard time to make it work ... Here is the code of the X2AbilityMultiTargetStyle class : class X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat extends X2AbilityMultiTarget_Radius; var ECharStatType StatUsedForRange; // Can be eStat_SightRadius, eStat_DetectionRadius or eStat_HearingRadius var float StatMultiplier; // Can be used to increase the range by a multiplier. Allow to increase or decrease radius but still based on stat (rather than wwith a fixed value). simulated function GetMultiTargetOptions(const XComGameState_Ability Ability, out array<AvailableTarget> Targets) { `LOG("SF_SU_Scout Class: GetMultiTargetOptions from X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat triggered " ); UpdateTargetRadius(Ability); super.GetMultiTargetOptions(Ability, Targets); } simulated function GetMultiTargetsForLocation(const XComGameState_Ability Ability, const vector Location, out AvailableTarget Target) { local XComGameState_Unit SourceUnit; `LOG("SF_SU_Scout Class: GetMultiTargetsForLocation from X2AbilityMultiTarget_LWOfficerCommandRange triggered " ); SourceUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(Target.PrimaryTarget.ObjectID)); UpdateTargetRadius(Ability); return; super.GetMultiTargetsForLocation(Ability, Location, Target); } simulated function GetValidTilesForLocation(const XComGameState_Ability Ability, const vector Location, out array<TTile> ValidTiles) { local XComGameState_Unit SourceUnit; `LOG("SF_SU_Scout GetValidTilesForLocationrLocation from X2AbilityMultiTarget_LWOfficerCommandRange triggered " ); SourceUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(Ability.OwnerStateObject.ObjectID)); UpdateTargetRadius(Ability); return; super.GetValidTilesForLocation(Ability, Location, ValidTiles); } simulated function float GetTargetRadius(const XComGameState_Ability Ability) { local XComGameState_Unit SourceUnit; `LOG("SF_SU_Scout GetTargetRadius from X2AbilityMultiTarget_LWOfficerCommandRange triggered " ); SourceUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(Ability.OwnerStateObject.ObjectID)); UpdateTargetRadius(Ability); return(`METERSTOUNITS(fTargetRadius)); } simulated function float GetActiveTargetRadiusScalar(const XComGameState_Ability Ability) { local XComGameState_Unit SourceUnit; `LOG("SF_SU_Scout GetActiveTargetRadiusScalar from X2AbilityMultiTarget_LWOfficerCommandRange triggered " ); SourceUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(Ability.OwnerStateObject.ObjectID)); UpdateTargetRadius(Ability); return(`METERSTOUNITS(fTargetRadius)); } simulated function float GetTargetCoverage(const XComGameState_Ability Ability) { local XComGameState_Unit SourceUnit; `LOG("SF_SU_Scout GetTargetCoverage from X2AbilityMultiTarget_LWOfficerCommandRange triggered " ); SourceUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(Ability.OwnerStateObject.ObjectID)); UpdateTargetRadius(Ability); return(`METERSTOUNITS(fTargetRadius)); } function UpdateTargetRadius(const XComGameState_Ability Ability) { local XcomGameState_Unit Unit; local XComGameStateHistory History; `LOG("SF_SU_Scout Class: UpdateTargetRadius from X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat triggered " ); History = `XCOMHISTORY; Unit = XcomGameState_Unit(History.GetGameStateForObjectID(Ability.OwnerStateObject.ObjectID)); `LOG("SF_SU_Scout Class: fTargetRadius " @ fTargetRadius); if(fTargetRadius == 0.0) { `LOG("SF_SU_Scout Class: Unit " @ StatUsedForRange @ " : " @ Unit.GetCurrentStat(StatUsedForRange)); fTargetRadius = StatMultiplier * Unit.GetCurrentStat(StatUsedForRange); `LOG("SF_SU_Scout Class: New fTargetRadius " @ fTargetRadius); } } defaultProperties { bUseWeaponRadius=false bIgnoreBlockingCover=true // unused here, but kept for reference //fTargetRadius; // Meters! (for now) If bUseWeaponRadius is true, this value is added on. //fTargetCoveragePercentage; bAddPrimaryTargetAsMultiTarget=false //unused here, but kept for reference -- GetMultiTargetOptions & GetMultiTargetsForLocation will remove the primary target and add it to the multi target array. bAllowDeadMultiTargetUnits=false //unused here, but kept for reference bAllowSameTarget=false } And Here is the ability template creation : static function X2DataTemplate CreateReconAreaAbility() { local X2AbilityTemplate Template; local X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat RadiusMultiTarget; local X2AbilityCost_ActionPoints ActionPointCost; local X2Condition_UnitProperty CivilianProperty; local X2Condition_UnitEffects UnitEffectsCondition; local X2Effect_ScanningProtocol ScanningEffect; local X2AbilityCooldown_LocalAndGlobal Cooldown; local X2Condition_Visibility TargetVisibilityCondition; local X2Effect_PersistentStatChange SightIncrease; `CREATE_X2ABILITY_TEMPLATE(Template, 'ReconArea'); Template.IconImage = "img:///UILibrary_PerkIcons.UIPerk_observer"; Template.eAbilityIconBehaviorHUD = eAbilityIconBehavior_AlwaysShow; Template.AbilitySourceName = 'eAbilitySource_Perk'; // Hostility set to Defensive so that the ability does not break concealment (EConcelamentRule : Default is based on ability hostility) Template.Hostility = eHostility_Defensive; Cooldown = new class'X2AbilityCooldown_LocalAndGlobal'; Cooldown.iNumTurns = default.RECONAREA_LOCAL_COOLDOWN; Template.AbilityCooldown = Cooldown; ActionPointCost = new class'X2AbilityCost_ActionPoints'; ActionPointCost.iNumPoints = default.RECONAREA_ACTION_POINTS_COST; ActionPointCost.bFreeCost = false; Template.AbilityCosts.AddItem(ActionPointCost); // The shooter cannot be mind controlled UnitEffectsCondition = new class'X2Condition_UnitEffects'; UnitEffectsCondition.AddExcludeEffect(class'X2Effect_MindControl'.default.EffectName, 'AA_UnitIsMindControlled'); Template.AbilityShooterConditions.AddItem(UnitEffectsCondition); Template.AbilityShooterConditions.AddItem(default.LivingShooterProperty); // Targeting Template.AbilityToHitCalc = default.DeadEye; Template.AbilityTargetStyle = default.SelfTarget; Template.AbilityTriggers.AddItem(default.PlayerInputTrigger); // Stat change bonuses effect SightIncrease = class'X2StatusEffects_SF_SU_Scout_Effects'.static.CreateReconAreaStatChangeEffect(); Template.AddShooterEffect(SightIncrease); // Target Radius Definition With sight radius RadiusMultiTarget = new class'X2AbilityMultiTargetStyle_SF_SU_Scout_MultiTargetRadiusFromStat'; RadiusMultiTarget.bUseWeaponRadius = false; //RadiusMultiTarget.bIgnoreBlockingCover = true; //RadiusMultiTarget.bDontAcceptNeutralUnits = false; //RadiusMultiTarget.bAcceptFriendlyUnits = false; //RadiusMultiTarget.bAcceptEnemyUnits = true; RadiusMultiTarget.fTargetRadius = default.RECONAREA_MULTI_TARGET_RADIUS; RadiusMultiTarget.StatUsedForRange = default.RECONAREA_MULTI_TARGET_STAT_USED_FOR_RADIUS; // eStat_DetectionRadius; RadiusMultiTarget.StatMultiplier = default.RECONAREA_MULTI_TARGET_RADIUS_MULTIPLIER; Template.AbilityMultiTargetStyle = RadiusMultiTarget; // Allowed targets in RadiusMultiTarget radius for Hostile scaning effect ScanningEffect = new class'X2Effect_ScanningProtocol'; ScanningEffect.BuildPersistentEffect(1, false, false, false, eGameRule_PlayerTurnEnd); ScanningEffect.TargetConditions.AddItem(default.LivingHostileUnitOnlyProperty); Template.AddMultiTargetEffect(ScanningEffect); ScanningEffect = new class'X2Effect_ScanningProtocol'; ScanningEffect.BuildPersistentEffect(1, false, false, false, eGameRule_PlayerTurnEnd); CivilianProperty = new class'X2Condition_UnitProperty'; CivilianProperty.ExcludeNonCivilian = true; CivilianProperty.ExcludeHostileToSource = false; CivilianProperty.ExcludeFriendlyToSource = false; ScanningEffect.TargetConditions.AddItem(CivilianProperty); Template.AddMultiTargetEffect(ScanningEffect); Template.CustomFireAnim = 'HL_SignalPoint'; Template.BuildNewGameStateFn = TypicalAbility_BuildGameState; Template.BuildInterruptGameStateFn = TypicalAbility_BuildInterruptGameState; Template.BuildVisualizationFn = TypicalAbility_BuildVisualization; //Template.BuildVisualizationFn = ReconArea_BuildVisualization; Template.CinescriptCameraType = "Mark_Target"; return Template; } The problem is that GetMultiTargetOptions function from the X2AbilityMultiTargetStyle custom class seems to never be called. I tried to replicate the use of X2AbilityMultiTarget_LWOfficerCommandRange from LeaderPack but the only function that is called from the extended class when i use it in my ability template is GetValidTilesForLocation (that i think is called because the ability template uses the default TopDown targeting method). My UpdateTargetRadius function is called from here but too late to be usefull (Template.AbilityMultiTargetStyle still uses the value of 0 defined in the config ini file). Any idea of what i am doing wrong ? Edited May 12, 2016 by riderSirius Link to comment Share on other sites More sharing options...
Stormhunter117 Posted May 12, 2016 Share Posted May 12, 2016 The problem is that GetMultiTargetOptions function from the X2AbilityMultiTargetStyle custom class seems to never be called. Yeah you're definitely not alone here, I've also had the exact same issue attempting to create a new MultiTargetStyle. Can't help, though, I gave up eventually. Sorry man. Link to comment Share on other sites More sharing options...
riderSirius Posted May 13, 2016 Author Share Posted May 13, 2016 (edited) Thanks for the answer. Sad to know that you gave up on this. have you asked for help/ ideas on the forum ? I don't recall reading about it when searching clues but maybe I missed it. Anyone having issues with this ? Any ideas on how to make it work or workarounds ? Edited May 13, 2016 by riderSirius Link to comment Share on other sites More sharing options...
Amineri Posted May 13, 2016 Share Posted May 13, 2016 So the issue here is that the function is called, it's just called from another native function. Unfortunately this means that you can't intercept it, as native-to-native calls don't pass through unrealscript to check for the override. To override this practically, what I've had to do is to override the upstream native function that is calling GetMultiTargetOptions. Since it is a member of X2AbilityMultiTargetStyle, the options are fairly limited. This is the method I used in the OfficerPack override to create the variable range Leader abilities. However in that case I skipped on re-implementing the GetValidTiles, since I didn't want a tile preview (having built a separate preview mechanic), and was worried about the overhead of computing all of the valid tiles in unrealscript -- things generally get optimized into native code for a reason. There are some other tricks possible to get around X2AbilityMultiTargetStyle native code, depending on the situation, which don't require completely rewriting the native code functionality in UScript. Since you are extending X2AbilityMultiTarget_Radius, what you can do is override all of the native classes, with each calling a helper that will update the fTargetRadius value based on your criteria, then continue on calling the super. native function. For example : simulated function float GetTargetCoverage(const XComGameState_Ability Abillity) { fTargetRadius = MyTargetRadiusHelper(Ability); super.GetTargetCoverage(Ability); } If you do this for each native function in the class (and the ancestor classes), as well as override GetTargetRadius completely, then whatever the hidden call order is in native code, your fTargetRadius will get set. Link to comment Share on other sites More sharing options...
GrimyBunyip Posted May 13, 2016 Share Posted May 13, 2016 You can try taking a look at my headhunter mod: http://steamcommunity.com/sharedfiles/filedetails/?id=678149207 I'm under the impression that getmultitargetoptions is meant for the UI, because I'm pretty sure getsingletargetoptions populates the list of possible targets for the UI, for stuff like standard shot. ANYWAYS in my case I just ignored that altogether and manipulated the targets via the BuildGameStateFn and BuildVisualizationFn Link to comment Share on other sites More sharing options...
riderSirius Posted May 22, 2016 Author Share Posted May 22, 2016 Thank you for your answers. I had to stop modding for a while so i didn't try any of the solutions above. I think i will finish to code the other abilities first (6 remaining for the scout class) and then improve the ones i am not satisfied with (use an eStat parameter type + multiply modifier for ReconArea ScanningProtocol effect radius). I ll keep you updated on my progress. Thanks again for your help. Link to comment Share on other sites More sharing options...
Recommended Posts