From 54fe4e810358440bc2c899c851ff82983c4bc817 Mon Sep 17 00:00:00 2001 From: DaloLorn Date: Thu, 18 Dec 2025 09:58:47 +0100 Subject: [PATCH 1/3] VSCode tasks should no longer misbehave when paths to the workspace, SDK, or game include spaces. --- .vscode/tasks.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e829a6064..edc662e93 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,54 +6,54 @@ { "label": "Build (final release)", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\build.ps1' -srcDirectory '${workspaceRoot}' -sdkPath '${config:xcom.highlander.sdkroot}' -gamePath '${config:xcom.highlander.gameroot}' -config final_release", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\build.ps1\" -srcDirectory \"${workspaceRoot}\" -sdkPath \"${config:xcom.highlander.sdkroot}\" -gamePath \"${config:xcom.highlander.gameroot}\" -config final_release", "group": "build", "problemMatcher": [] }, { "label": "Build (cooked)", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\build.ps1' -srcDirectory '${workspaceRoot}' -sdkPath '${config:xcom.highlander.sdkroot}' -gamePath '${config:xcom.highlander.gameroot}' -config default", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\build.ps1\" -srcDirectory \"${workspaceRoot}\" -sdkPath \"${config:xcom.highlander.sdkroot}\" -gamePath \"${config:xcom.highlander.gameroot}\" -config default", "group": "build", "problemMatcher": [] }, { "label": "Build (debug)", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\build.ps1' -srcDirectory '${workspaceRoot}' -sdkPath '${config:xcom.highlander.sdkroot}' -gamePath '${config:xcom.highlander.gameroot}' -config debug", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\build.ps1\" -srcDirectory \"${workspaceRoot}\" -sdkPath \"${config:xcom.highlander.sdkroot}\" -gamePath \"${config:xcom.highlander.gameroot}\" -config debug", "group": "build", "problemMatcher": [] }, { "label": "Build (compiletest)", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\build.ps1' -srcDirectory '${workspaceRoot}' -sdkPath '${config:xcom.highlander.sdkroot}' -gamePath '${config:xcom.highlander.gameroot}' -config compiletest", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\build.ps1\" -srcDirectory \"${workspaceRoot}\" -sdkPath \"${config:xcom.highlander.sdkroot}\" -gamePath \"${config:xcom.highlander.gameroot}\" -config compiletest", "group": "build", "problemMatcher": [] }, { "label": "Build for workshop stable version", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\build.ps1' -srcDirectory '${workspaceRoot}' -sdkPath '${config:xcom.highlander.sdkroot}' -gamePath '${config:xcom.highlander.gameroot}' -config stable", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\build.ps1\" -srcDirectory \"${workspaceRoot}\" -sdkPath \"${config:xcom.highlander.sdkroot}\" -gamePath \"${config:xcom.highlander.gameroot}\" -config stable", "group": "build", "problemMatcher": [] }, { "label": "runGame", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\run.ps1' -gamePath '${config:xcom.highlander.gameroot}'", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\run.ps1\" -gamePath \"${config:xcom.highlander.gameroot}\"", "problemMatcher": [] }, { "label": "runUnrealEditor", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\runUnrealEditor.ps1' -sdkPath '${config:xcom.highlander.sdkroot}'", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\runUnrealEditor.ps1\" -sdkPath \"${config:xcom.highlander.sdkroot}\"", "problemMatcher": [] }, { "label": "updateVersions", "type": "shell", - "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file '${workspaceRoot}\\.scripts\\update_version.ps1' -ps '${workspaceRoot}\\VERSION.ps1' -srcDirectory '${workspaceRoot}' -no_cache", + "command": "powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -file \"${workspaceRoot}\\.scripts\\update_version.ps1\" -ps \"${workspaceRoot}\\VERSION.ps1\" -srcDirectory \"${workspaceRoot}\" -no_cache", "problemMatcher": [] }, { @@ -70,6 +70,6 @@ }, "command": "python ..\\..\\.scripts\\make_docs.py .\\test_src --outdir .\\test_output --docsdir .\\test_tags --dumpelt .\\test_output\\CHL_Event_Compiletest.uc | % {$_.replace('\\', '/')} | Out-File .\\test_output\\stdout.log -Encoding ASCII", "problemMatcher": [] - }, + } ] } \ No newline at end of file From 037759f3115e382c112ada60b7f3942eda88bc0f Mon Sep 17 00:00:00 2001 From: DaloLorn Date: Sun, 12 Apr 2026 09:46:16 +0200 Subject: [PATCH 2/3] Added VSCode workspaces to gitignore, on the grounds that a good workspace contains user-specific information. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7e29421a1..ba852a4a0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Project specific user settings .vscode/settings.json +*.code-workspace # Python *.pyc From bc97f5c591ea7a500c5ec259f3cff532990e0ddf Mon Sep 17 00:00:00 2001 From: DaloLorn Date: Sun, 12 Apr 2026 14:59:52 +0200 Subject: [PATCH 3/3] [#1542] Added OverrideDefenseBypass. --- .../Src/XComGame/Classes/CHHelpers.uc | 155 +++++++++++++++++- .../Classes/X2Effect_ApplyWeaponDamage.uc | 24 ++- 2 files changed, 175 insertions(+), 4 deletions(-) diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc index a61ad7328..15355d4ff 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/CHHelpers.uc @@ -332,6 +332,20 @@ struct OverrideHasHeightAdvantageStruct var protectedwrite array OverrideHasHeightAdvantageCallbacks; // End Issue #851 +// Start Issue #1542 +struct OverrideDefenseBypassStruct +{ + var delegate OverrideDefenseBypassFn; + var int Priority; + + structdefaultproperties + { + Priority = 50 + } +}; +var protectedwrite array OverrideDefenseBypassCallbacks; +// End Issue #1542 + // Start Issue #1138 struct PrioritizeRightClickMeleeStruct { @@ -351,6 +365,8 @@ delegate EHLDelegateReturn ShouldDisplayMultiSlotItemInTacticalDelegate(XComGame delegate EHLDelegateReturn OverrideHasHeightAdvantageDelegate(XComGameState_Unit Attacker, XComGameState_Unit TargetUnit, out int bHasHeightAdvantage); // Issue #851 delegate EHLDelegateReturn PrioritizeRightClickMeleeDelegate(XComGameState_Unit UnitState, out XComGameState_Ability PrioritizedMeleeAbility, optional XComGameState_BaseObject TargetObject); // Issue #1138 +delegate EHLDelegateReturn OverrideDefenseBypassDelegate(array AppliedDamageTypes, out int bIgnoreArmor, out int bIgnoreShields, EffectAppliedData ApplyEffectParams, X2Effect_ApplyWeaponDamage Source, optional XComGameState NewGameState); // Issue #1542 + // Start Issue #123 simulated static function RebuildPerkContentCache() { local XComContentManager Content; @@ -1168,4 +1184,141 @@ static function bool GeoscapeReadyForUpdate() StrategyMap != none && StrategyMap.m_eUIState != eSMS_Flight && StrategyMap.Movie.Pres.ScreenStack.GetCurrentScreen() == StrategyMap; -} \ No newline at end of file +} + +// Begin Issue #1542 +/// HL-Docs: feature:OverrideDefenseBypass; issue:1542; tags:tactical +/// This feature allows mods to override the amount of armor mitigation, +/// armor piercing, or minimum/mandatory armor mitigation for an attack +/// on a case-by-case basis. +/// +/// Normally this override would have been implemented as an event, but the implementing dev heard that events in To Hit Chance Calculation logic can cause issues, +/// (see [GetHitChanceEvents](../tactical/GetHitChanceEvents.md)), and resorted to delegates in an attempt to resolve a crash he was unable to explain at the time. +/// +/// This feature only applies to attacks using `X2Effect_ApplyWeaponDamage`, +/// or a subclass which has not overridden its `GetDamagePreview()` and `CalculateDamageAmount` +/// functions (except if they have included support for this feature). +/// +/// ## Delegate structure +/// +/// - array AppliedDamageTypes: All damage types applied to the attack, to help determine the results. +/// - out int bIgnoreArmor: Boolean value specifying whether the attack should ignore armor. Defaults to the corresponding value on the damage effect. +/// - out int bIgnoreShields: Boolean value specifying whether the attack should ignore ablative HP. Defaults to the corresponding value calculated by the damage effect. +/// - EffectAppliedData ApplyEffectParams: Effect context containing information such as the parent ability, source and target units, etc., to help determine the results. +/// - X2Effect_ApplyWeaponDamage Source: The effect itself, to help determine the results. *Do not* attempt to modify it or invoke functions which would have side effects. +/// - optional XComGameState NewGameState: The new game state after the attack resolves. If no game state was provided, then the delegate has been invoked by a damage preview. +/// +/// ## How to use +/// +/// Implement the following code in your mod's `X2DownloadableContentInfo` class: +/// ```unrealscript +/// static event OnPostTemplatesCreated() +/// { +/// local CHHelpers CHHelpersObj; +/// +/// CHHelpersObj = class'CHHelpers'.static.GetCDO(); +/// if (CHHelpersObj != none) +/// { +/// CHHelpersObj.AddOverrideDefenseBypassCallback(OverrideDefenseBypass); +/// } +/// } +/// +/// // To avoid crashes associated with garbage collection failure when transitioning between Tactical and Strategy, +/// // this function must be bound to the ClassDefaultObject of your class. Having this function in a class that +/// // `extends X2DownloadableContentInfo` is the easiest way to ensure that. +/// static private function EHLDelegateReturn OverrideDefenseBypass(array AppliedDamageTypes, out int bIgnoreArmor, out int bIgnoreShields, EffectAppliedData ApplyEffectParams, X2Effect_ApplyWeaponDamage Source, optional XComGameState NewGameState) +/// { +/// // Detect whether it's a damage preview. (... If you even care.) +/// // Then optionally modify either or both of bIgnoreArmor and bIgnoreShields. +/// +/// // Return EHLDR_NoInterrupt or EHLDR_InterruptDelegates depending on +/// // if you want to allow other delegates to run after yours +/// // and potentially modify defense bypass further. +/// return EHLDR_NoInterrupt; +///} +/// # Delegate Priority +/// You can optionally specify callback Priority. +///```unrealscript +///CHHelpersObj.AddOverrideDefenseBypassCallback(OverrideDefenseBypass, 45); +///``` +/// Delegates with higher Priority value are executed first. +/// Delegates with the same Priority are executed in the order they were added to CHHelpers, +/// which would normally be the same as [DLCRunOrder](../misc/DLCRunOrder.md). +/// This function will return `true` if the delegate was successfully registered. +simulated function bool AddOverrideDefenseBypassCallback(delegate OverrideDefenseBypassFn, optional int Priority = 50) +{ + local OverrideDefenseBypassStruct NewOverrideDefenseBypassCallback; + local int i, PriorityIndex; + local bool bPriorityIndexFound; + + if (OverrideDefenseBypassFn == none) + { + return false; + } + + //Cycle through the array of callbacks backwards + for (i = OverrideDefenseBypassCallbacks.Length - 1; i >= 0; i--) + { + // Do not allow registering the same delegate more than once. + if (OverrideDefenseBypassCallbacks[i].OverrideDefenseBypassFn == OverrideDefenseBypassFn) + { + return false; + } + + // Record the array index of the callback whose priority is higher or equal to the priority of the new callback, + // so that the new callback can be inserted right after it. + if (OverrideDefenseBypassCallbacks[i].Priority >= Priority && !bPriorityIndexFound) + { + PriorityIndex = i + 1; // +1 so that InsertItem puts the new callback *after* this one. + + // Keep cycling through the array so that the previous check for duplicate delegates can run for every currently registered delegate. + bPriorityIndexFound = true; + } + } + + NewOverrideDefenseBypassCallback.Priority = Priority; + NewOverrideDefenseBypassCallback.OverrideDefenseBypassFn = OverrideDefenseBypassFn; + OverrideDefenseBypassCallbacks.InsertItem(PriorityIndex, NewOverrideDefenseBypassCallback); + + return true; +} + +/// HL-Docs: ref:DefenseBypassOverride +/// # Removing Delegates +/// If necessary, it's possible to remove a delegate. +///```unrealscript +///CHHelpersObj.RemoveOverrideDefenseBypassCallback(OverrideDefenseBypass); +///``` +/// The function will return `true` if the Callback was successfully deleted, return false otherwise. +simulated function bool RemoveOverrideDefenseBypassCallback(delegate OverrideDefenseBypassFn) +{ + local int i; + + for (i = OverrideDefenseBypassCallbacks.Length - 1; i >= 0; i--) + { + if (OverrideDefenseBypassCallbacks[i].OverrideDefenseBypassFn == OverrideDefenseBypassFn) + { + OverrideDefenseBypassCallbacks.Remove(i, 1); + return true; + } + } + return false; +} + +// Internal helper function to trigger an OverrideDefenseBypass event. +simulated function TriggerOverrideDefenseBypass(array AppliedDamageTypes, out int bIgnoreArmor, out int bIgnoreShields, EffectAppliedData ApplyEffectParams, X2Effect_ApplyWeaponDamage Source, optional XComGameState NewGameState) +{ + local delegate OverrideDefenseBypassFn; + local int i; + + for (i = 0; i < OverrideDefenseBypassCallbacks.Length; i++) + { + OverrideDefenseBypassFn = OverrideDefenseBypassCallbacks[i].OverrideDefenseBypassFn; + + if (OverrideDefenseBypassFn(AppliedDamageTypes, bIgnoreArmor, bIgnoreShields, ApplyEffectParams, Source, NewGameState) == EHLDR_InterruptDelegates) + { + break; + } + } +} +// End Issue #1542 \ No newline at end of file diff --git a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_ApplyWeaponDamage.uc b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_ApplyWeaponDamage.uc index 46dd8ede1..1ed2725d6 100644 --- a/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_ApplyWeaponDamage.uc +++ b/X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_ApplyWeaponDamage.uc @@ -390,6 +390,8 @@ simulated function GetDamagePreview(StateObjectReference TargetRef, XComGameStat local DamageModifierInfo DamageModInfo; local array DamageMods; // Issue #923 + local int IgnoreArmor, IgnoreShields; // Issue #1542 + MinDamagePreview = UpgradeTemplateBonusDamage; MaxDamagePreview = UpgradeTemplateBonusDamage; bDoesDamageIgnoreShields = bBypassShields; @@ -664,13 +666,22 @@ simulated function GetDamagePreview(StateObjectReference TargetRef, XComGameStat MoveDamageModItems(MaxDamagePreview.BonusDamageInfo, DamageMods); // End Issue #923 - if (!bDoesDamageIgnoreShields) + // Start Issue #1542 + IgnoreArmor = bIgnoreArmor ? 1 : 0; + IgnoreShields = bDoesDamageIgnoreShields ? 1 : 0; + class'CHHelpers'.static.GetCDO().TriggerOverrideDefenseBypass(AppliedDamageTypes, IgnoreArmor, IgnoreShields, TestEffectParams, self); + bDoesDamageIgnoreShields = IgnoreShields > 0; + // End Issue #1542 + + if (!bDoesDamageIgnoreShields) + { AllowsShield += MaxDamagePreview.Damage; + } // Start Issue #1281 /// HL-Docs: ref:Bugfixes; issue:1281 /// If the effect ignores all armor, max out the Pierce value in the Damage Preview so it will show up on the unit flag damage preview. - if (bIgnoreArmor) + if (IgnoreArmor > 0) { MinDamagePreview.Pierce = MaxInt; MaxDamagePreview.Pierce = MaxInt; @@ -760,6 +771,8 @@ simulated function int CalculateDamageAmount(const out EffectAppliedData ApplyEf local DamageModifierInfo ModifierInfo; local bool bWasImmune, bHadAnyDamage; + local int IgnoreArmor; // Issue #1542 + // Issue #1299 - comment out unused Rupture Cap. //local int RuptureCap; @@ -1112,11 +1125,16 @@ simulated function int CalculateDamageAmount(const out EffectAppliedData ApplyEf } } + // Start Issue #1542 + IgnoreArmor = bIgnoreArmor ? 1 : 0; + class'CHHelpers'.static.GetCDO().TriggerOverrideDefenseBypass(AppliedDamageTypes, IgnoreArmor, bAmmoIgnoresShields, ApplyEffectParameters, self); + // End Issue #1542 + // Start Issue #923 WeaponDamage = ApplyPostDefaultDamageModifierEffects(History, kSourceUnit, kTarget, kAbility, ApplyEffectParameters, WeaponDamage, SpecialDamageMessages,, NewGameState); // End Issue #923 - if (kTarget != none && !bIgnoreArmor) + if (kTarget != none && IgnoreArmor == 0) { ArmorMitigation = kTarget.GetArmorMitigation(ApplyEffectParameters.AbilityResultContext.ArmorMitigation); if (ArmorMitigation != 0)