diff --git a/X2WOTCCommunityHighlander/Config/XComGame.ini b/X2WOTCCommunityHighlander/Config/XComGame.ini index e28d2a7d0..a5ed7bd0d 100644 --- a/X2WOTCCommunityHighlander/Config/XComGame.ini +++ b/X2WOTCCommunityHighlander/Config/XComGame.ini @@ -321,3 +321,7 @@ InterruptionsUpdateTurnStartLocation = false ; Should interruptions update tur InterruptionsResetPanicTestsPerformedThisTurn = false ; Should interruptions reset the tracker for the amount of panic tests performed this turn, just like a normal turn? Default is false, because the unit isn't taking a typical turn. InterruptionsTriggerGroupTurnBegunEvent = true ; Should interruptions trigger the GroupTurnBegunEvent, just like what happens in a normal turn. Default is true, because interruptions work closely with "groups" (XComGameState_AIGroup) in the code side of things. ; End Issue #1325 + +; Issue #1591 ++SustainAbilityNames=SustainTriggered +bEnableImprovedCanAbilityHit = true \ No newline at end of file diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc index a61ad7328..0b02abf64 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc @@ -287,6 +287,17 @@ var config bool bDisableAutomaticBondPhoto; var config bool bManualPhotoTakenOnLastMission; // End Issue #1453 +// Single line for Issue #1591 - Variable to allow use of CanAbilityHitUnit_CH +// Allows mods access to the ability context to give better control over hit function in subclasses +var config bool bEnableImprovedCanAbilityHit; + +// Begin Issue #1591 - Variables to allow abilities to bypass base-game stasis and sustain +var config array AbilitiesToBypassStasis; +var config array AbilitiesToBypassSustain; +// Issue #1591 - Ar +var config array SustainAbilityNames; + +// End Issue #1591 // Start Issue #885 enum EHLDelegateReturn { @@ -1168,4 +1179,4 @@ static function bool GeoscapeReadyForUpdate() StrategyMap != none && StrategyMap.m_eUIState != eSMS_Flight && StrategyMap.Movie.Pres.ScreenStack.GetCurrentScreen() == StrategyMap; -} \ No newline at end of file +} diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Persistent.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Persistent.uc index cac11a7f9..18ee07af7 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Persistent.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Persistent.uc @@ -687,6 +687,13 @@ function bool RetainIndividualConcealment(XComGameState_Effect EffectState, XCom function bool DoesEffectAllowUnitToBleedOut(XComGameState_Unit UnitState) { return true; } function bool DoesEffectAllowUnitToBeLooted(XComGameState NewGameState, XComGameState_Unit UnitState) { return true; } function bool CanAbilityHitUnit(name AbilityName) { return true; } +// Begin Issue #1591 - Provide a hook to allow over-riding CanAbilityHitUnit in child classes, returning default immunityFn if not over-ridden +// Gives access to abilityname to allow mods to exclude certain abilities +function bool CanAbilityHitUnit_CH(name AbilityName, optional XComGameStateContext_Ability AbilityContext, optional XComGameState_Effect EffectState) +{ + return CanAbilityHitUnit(AbilityName); +} +// End Issue #1591 function bool PreDeathCheck(XComGameState NewGameState, XComGameState_Unit UnitState, XComGameState_Effect EffectState) { return false; } function bool PreBleedoutCheck(XComGameState NewGameState, XComGameState_Unit UnitState, XComGameState_Effect EffectState) { return false; } function bool ForcesBleedout(XComGameState NewGameState, XComGameState_Unit UnitState, XComGameState_Effect EffectState) { return bEffectForcesBleedout; } diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Stasis.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Stasis.uc new file mode 100644 index 000000000..58bb0d56d --- /dev/null +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Stasis.uc @@ -0,0 +1,191 @@ +class X2Effect_Stasis extends X2Effect_Persistent + config(GameData_SoldierSkills); + +var localized string StasisFlyover, StasisRemoved, StasisRemovedText; +var name StunStartAnim, StunStopAnim; +var bool bSkipFlyover; +var float StartAnimBlendTime; + +var config array STASIS_REMOVE_EFFECTS_SOURCE, STASIS_REMOVE_EFFECTS_TARGET; + +simulated protected function OnEffectAdded(const out EffectAppliedData ApplyEffectParameters, XComGameState_BaseObject kNewTargetState, XComGameState NewGameState, XComGameState_Effect NewEffectState) +{ + local XComGameState_Unit TargetUnit; + + TargetUnit = XComGameState_Unit(kNewTargetState); + TargetUnit.bInStasis = true; + TargetUnit.ActionPoints.Length = 0; + TargetUnit.ReserveActionPoints.Length = 0; + + `XEVENTMGR.TriggerEvent('AffectedByStasis', kNewTargetState, kNewTargetState); + + super.OnEffectAdded(ApplyEffectParameters, kNewTargetState, NewGameState, NewEffectState); +} + +simulated function OnEffectRemoved(const out EffectAppliedData ApplyEffectParameters, XComGameState NewGameState, bool bCleansed, XComGameState_Effect RemovedEffectState) +{ + local XComGameState_Unit TargetUnit; + + TargetUnit = XComGameState_Unit(NewGameState.GetGameStateForObjectID(ApplyEffectParameters.TargetStateObjectRef.ObjectID)); + if (TargetUnit == none) + { + TargetUnit = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(ApplyEffectParameters.TargetStateObjectRef.ObjectID)); + `assert(TargetUnit != none); + TargetUnit = XComGameState_Unit(NewGameState.ModifyStateObject(TargetUnit.Class, TargetUnit.ObjectID)); + } + + TargetUnit.bInStasis = false; + + super.OnEffectRemoved(ApplyEffectParameters, NewGameState, bCleansed, RemovedEffectState); +} + +function UnitEndedTacticalPlay(XComGameState_Effect EffectState, XComGameState_Unit UnitState) +{ + UnitState.bInStasis = false; +} + +function ModifyTurnStartActionPoints(XComGameState_Unit UnitState, out array ActionPoints, XComGameState_Effect EffectState) +{ + // no actions allowed while in stasis + ActionPoints.Length = 0; +} + +function bool ProvidesDamageImmunity(XComGameState_Effect EffectState, name DamageType) +{ + return true; +} + +function bool CanAbilityHitUnit(name AbilityName) +{ + return false; +} +// Issue #1591 - Allow abilities in the CHHelpers array to bypass CanAbilityHit +// And allow checking the name of the effect that applied stasis (e.g. Sustain) so they can be handled seperately +function bool CanAbilityHitUnit_CH(name AbilityName, optional XComGameStateContext_Ability AbilityContext, optional XComGameState_Effect EffectState) +{ + `LOG("CanAbilityHitUnit_CH called with AbilityName: " $ AbilityName,, 'BDLOG'); + + if (class'CHHelpers'.default.AbilitiesToBypassStasis.Find(AbilityName) != INDEX_NONE) + { + `LOG("Found in AbilitiesToBypassStasis - returning true",, 'BDLOG'); + return true; + } + + if (EffectState != none) + { + `LOG("EffectState AbilityTemplateName: " $ EffectState.ApplyEffectParameters.AbilityInputContext.AbilityTemplateName,, 'BDLOG'); + `LOG("SustainAbilityNames length: " $ class'CHHelpers'.default.SustainAbilityNames.Length,, 'BDLOG'); + `LOG("AbilitiesToBypassSustain length: " $ class'CHHelpers'.default.AbilitiesToBypassSustain.Length,, 'BDLOG'); + + if (class'CHHelpers'.default.SustainAbilityNames.Find(EffectState.ApplyEffectParameters.AbilityInputContext.AbilityTemplateName) != INDEX_NONE + && class'CHHelpers'.default.AbilitiesToBypassSustain.Find(AbilityName) != INDEX_NONE) + { + `LOG("Found in SustainAbilityNames and AbilitiesToBypassSustain - returning true",, 'BDLOG'); + return true; + } + } + else + { + `LOG("EffectState is none",, 'BDLOG'); + } + + `LOG("CanAbilityHitUnit_CH returning false",, 'BDLOG'); + return false; +} +simulated function AddX2ActionsForVisualization(XComGameState VisualizeGameState, out VisualizationActionMetadata ActionMetadata, name EffectApplyResult) +{ + // Empty because we will be adding all this at the end with ModifyTracksVisualization +} + +simulated function ModifyTracksVisualization(XComGameState VisualizeGameState, out VisualizationActionMetadata ModifyTrack, const name EffectApplyResult) +{ + local X2Action_PlaySoundAndFlyOver SoundAndFlyOver; + local X2Action_PlayAnimation PlayAnimation; + + if (EffectApplyResult == 'AA_Success' && ModifyTrack.StateObject_NewState.IsA('XComGameState_Unit')) + { + if (!bSkipFlyover) + { + SoundAndFlyOver = X2Action_PlaySoundAndFlyOver(class'X2Action_PlaySoundAndFlyOver'.static.AddToVisualizationTree(ModifyTrack, VisualizeGameState.GetContext())); + SoundAndFlyOver.SetSoundAndFlyOverParameters(None, default.StasisFlyover, '', eColor_Bad, class'UIUtilities_Image'.const.UnitStatus_Stunned, 1.0, true); + } + + if( XComGameState_Unit(ModifyTrack.StateObject_NewState).IsTurret() ) + { + class'X2Action_UpdateTurretAnim'.static.AddToVisualizationTree(ModifyTrack, VisualizeGameState.GetContext()); + } + else + { + // Not a turret + // Play the start stun animation + PlayAnimation = X2Action_PlayAnimation(class'X2Action_PlayAnimation'.static.AddToVisualizationTree(ModifyTrack, VisualizeGameState.GetContext())); + PlayAnimation.Params.AnimName = StunStartAnim; + PlayAnimation.Params.BlendTime = StartAnimBlendTime; + } + class'X2StatusEffects'.static.UpdateUnitFlag(ModifyTrack, VisualizeGameState.GetContext()); + + super.AddX2ActionsForVisualization(VisualizeGameState, ModifyTrack, EffectApplyResult); + } +} + +simulated function AddX2ActionsForVisualization_Sync( XComGameState VisualizeGameState, out VisualizationActionMetadata ActionMetadata ) +{ + //We assume 'AA_Success', because otherwise the effect wouldn't be here (on load) to get sync'd + ModifyTracksVisualization(VisualizeGameState, ActionMetadata, 'AA_Success'); +} + +simulated function AddX2ActionsForVisualization_Removed(XComGameState VisualizeGameState, out VisualizationActionMetadata ActionMetadata, const name EffectApplyResult, XComGameState_Effect RemovedEffect) +{ + local X2Action_PlayAnimation PlayAnimation; + + super.AddX2ActionsForVisualization_Removed(VisualizeGameState, ActionMetadata, EffectApplyResult, RemovedEffect); + + if (XComGameState_Unit(ActionMetadata.StateObject_NewState) != none) + { + if( XComGameState_Unit(ActionMetadata.StateObject_NewState).IsTurret() ) + { + class'X2Action_UpdateTurretAnim'.static.AddToVisualizationTree(ActionMetadata, VisualizeGameState.GetContext(), false, ActionMetadata.LastActionAdded); + } + else + { + // The unit is not a turret + PlayAnimation = X2Action_PlayAnimation(class'X2Action_PlayAnimation'.static.AddToVisualizationTree(ActionMetadata, VisualizeGameState.GetContext(), false, ActionMetadata.LastActionAdded)); + PlayAnimation.Params.AnimName = StunStopAnim; + } + class'X2StatusEffects'.static.UpdateUnitFlag(ActionMetadata, VisualizeGameState.GetContext()); + + class'X2StatusEffects'.static.AddEffectSoundAndFlyOverToTrack(ActionMetadata, VisualizeGameState.GetContext(), default.StasisRemoved, '', eColor_Good, class'UIUtilities_Image'.const.UnitStatus_Stunned, 2.0f); + class'X2StatusEffects'.static.AddEffectMessageToTrack( + ActionMetadata, + default.StasisRemovedText, + VisualizeGameState.GetContext(), + class'UIEventNoticesTactical'.default.StasisTitle, + "img:///UILibrary_PerkIcons.UIPerk_stasis", + eUIState_Good); + } +} + +function RegisterForEvents(XComGameState_Effect EffectGameState) +{ + local XComGameState_Unit UnitState; + local X2EventManager EventMan; + local Object EffectObj; + + UnitState = XComGameState_Unit(`XCOMHISTORY.GetGameStateForObjectID(EffectGameState.ApplyEffectParameters.TargetStateObjectRef.ObjectID)); + EventMan = `XEVENTMGR; + + EffectObj = EffectGameState; + EventMan.RegisterForEvent(EffectObj, 'AffectedByStasis', class'XComGameState_Effect'.static.AffectedByStasis_Listener, ELD_OnStateSubmitted, , UnitState); +} + +DefaultProperties +{ + EffectName = "Stasis" + DuplicateResponse = eDupe_Refresh + CustomIdleOverrideAnim="HL_StunnedIdle" + StunStartAnim="HL_StunnedStart" + StunStopAnim="HL_StunnedStop" + ModifyTracksFn=ModifyTracksVisualization + EffectHierarchyValue=950 + StartAnimBlendTime=0.1f +} \ No newline at end of file diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameStateContext_Ability.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameStateContext_Ability.uc index 222596a65..60cd2a3db 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameStateContext_Ability.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameStateContext_Ability.uc @@ -1198,10 +1198,24 @@ static function CheckTargetForHitModification(out AvailableTarget kTarget, XComG { bIsResultHit = ModifyContext.IsResultContextHit(); - if( bIsResultHit && !TargetUnitState.CanAbilityHitUnit(AbilityTemplate.DataName) ) + if (bIsResultHit) { - ModifyContext.ResultContext.HitResult = eHit_Miss; - `COMBATLOG("Effect on Target is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + if (class'CHHelpers'.default.bEnableImprovedCanAbilityHit) + { + if (!TargetUnitState.CanAbilityHitUnit_CH(AbilityTemplate.DataName, ModifyContext)) + { + ModifyContext.ResultContext.HitResult = eHit_Miss; + `COMBATLOG("Effect on Target is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + } + } + else + { + if (!TargetUnitState.CanAbilityHitUnit(AbilityTemplate.DataName)) + { + ModifyContext.ResultContext.HitResult = eHit_Miss; + `COMBATLOG("Effect on Target is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + } + } } if (AbilityTemplate.AbilityToHitOwnerOnMissCalc != None @@ -1268,10 +1282,24 @@ static function CheckTargetForHitModification(out AvailableTarget kTarget, XComG { bIsResultHit = ModifyContext.IsResultContextMultiHit(MultiIndex); - if( bIsResultHit && !TargetUnitState.CanAbilityHitUnit(AbilityTemplate.DataName) ) + if (bIsResultHit) { - ModifyContext.ResultContext.MultiTargetHitResults[MultiIndex] = eHit_Miss; - `COMBATLOG("Effect on MultiTarget is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + if (class'CHHelpers'.default.bEnableImprovedCanAbilityHit) + { + if (!TargetUnitState.CanAbilityHitUnit_CH(AbilityTemplate.DataName, ModifyContext)) + { + ModifyContext.ResultContext.MultiTargetHitResults[MultiIndex] = eHit_Miss; + `COMBATLOG("Effect on MultiTarget is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + } + } + else + { + if (!TargetUnitState.CanAbilityHitUnit(AbilityTemplate.DataName)) + { + ModifyContext.ResultContext.MultiTargetHitResults[MultiIndex] = eHit_Miss; + `COMBATLOG("Effect on MultiTarget is forcing a miss against" @ TargetUnitState.GetName(eNameType_RankFull)); + } + } } if ( AbilityTemplate.Hostility == eHostility_Offensive ) diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameState_Unit.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameState_Unit.uc index e846734a2..ce9b217eb 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameState_Unit.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/XComGameState_Unit.uc @@ -15266,6 +15266,35 @@ function bool CanAbilityHitUnit(name AbilityName) return bCanHit; } +function bool CanAbilityHitUnit_CH(name AbilityName, optional XComGameStateContext_Ability AbilityContext) +{ + local StateObjectReference EffectRef; + local XComGameState_Effect EffectState; + local XComGameStateHistory History; + local X2Effect_Persistent Effect; + local bool bCanHit; + + History = `XCOMHISTORY; + bCanHit = true; + + foreach AffectedByEffects(EffectRef) + { + EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID)); + if (EffectState != none) + { + Effect = EffectState.GetX2Effect(); + if (Effect != none) + { + bCanHit = bCanHit && Effect.CanAbilityHitUnit_CH(AbilityName, AbilityContext, EffectState); + if (!bCanHit) + break; + } + } + } + + return bCanHit; +} + // Checks to see if all Codex that originated from an original Codex are dead event bool AreAllCodexInLineageDead(XComGameState NewGameState/*, XComGameState_Unit UnitState*/) { diff --git a/X2WOTCCommunityHighlander/X2WOTCCommunityHighlander.x2proj b/X2WOTCCommunityHighlander/X2WOTCCommunityHighlander.x2proj index 0643c0823..1ee8424b1 100644 --- a/X2WOTCCommunityHighlander/X2WOTCCommunityHighlander.x2proj +++ b/X2WOTCCommunityHighlander/X2WOTCCommunityHighlander.x2proj @@ -676,6 +676,9 @@ Content + + Content + Content