Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
},
{
Expand All @@ -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": []
},
}
]
}
7 changes: 7 additions & 0 deletions X2WOTCCommunityHighlander/Config/XComGameCore.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
[XComGame.X2Effect_Burning]
;BURNED_IGNORES_SHIELDS=true ; Make burn and acid DOT ignore shields
; Issue #1539
; Add damage types whose burn DOTs should tick on turn end.
; These must be the same `DamageType` passed to SetBurnDamage() for a given DOT.
;+BURN_TYPES_TICKING_AFTER_TURN="Fire" ; Make burn DOT tick on turn end!
Comment on lines +4 to +6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like treating burn separately from other damage types here, unless there's a mod author who has already indicated that, if their DOT effect were moved to end-of-turn, they would add its damage type to the start-of-turn list to explicitly counter it. If such fine-grained control is required, let it be a separate issue raised by whoever wants to use it. We can implement it when (and if) it comes up without any backwards compatibility issues.

Copy link
Author

@DaloLorn DaloLorn Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(...) unless there's a mod author who has already indicated (...)

... There's me. 😂

I actually got into Highlander development to support an extensive TJ-based, LWR-inspired combat overhaul I've been thinking of. (As of yesterday, I've gotten it all to a point where I can playtest it!) I'm not actually sure whether I'll release it to the public yet - partly because it wants all my configs/events to make it into the Highlander in some form - and the closest thing I've got to a formal design doc is in a media/rambling thread in the LWotC Discord, but here's the gist of it:

  • Install Ablative Vests and Energy Shield Overhaul, because why adopt them when I can configure them?
  • Make a derivative of Ablative Vests which does the same thing for armor. (I think all the other mods that already did that were doing other things I didn't agree with or want to include?)
  • Make LW Plating items give armor instead of ablative HP.
  • Make Psi, Bleeding, and Poison damage ignore all defenses: armor, cover, ablative HP, and shields are all useless, all the time. (Might want to compensate by making the DoTs tick on turn end to make Vipers a little less oppressive. [This thought is the whole reason I made this proposal!])
  • Make half of Fire, Napalm, Cold, and Electric damage bypass ablative HP and go straight to HP. [Partial shield bypassing across an entire damage type will require either extensive OPTCing or a far more invasive event than anything I've already had to make for Add events required for cover DR and point-blank bonuses #1540, so I've decided not to do it right now.]
  • Make cover work basically as in LWR: 33/50% DR, no effect at melee range (or versus certain special abilities like the Akimbo's Trick Shot ability). This includes removing the aim penalty and granting the +40% flanking crit chance, essentially dynamically changing the weapon to use the same rules as Trick Shot.
  • Make armor/cover apply before ablative, and can reduce damage to 0. (This is just enabling a few Highlander features, I don't need to do anything besides continuing to use the mod I already use.)
  • Adjust ADVENT units to no longer be giant blobs of HP, and have a percentage of their HP pool converted to ablative HP to represent their armor. Except Avatars and Ethereals, because those can be fluffed as psionic shielding or something.
  • Make Psionic Reaper/Warp Rifle/Psionic Repeater do Psi damage now? [I settled for OPTCing the XCOM weapons to use the same Projectile_BeamAvatar damage type as the Avatar weapons, and then setting that damage type to bypass defenses... though it only now occurs to me that I need to add ESO configs for the same effect.]

TL;DR: While all of my proposals are aiming for maximum flexibility (to the extent that I'm disappointed by my current event payloads for #1540 and #1542 being so laser-focused on the exact data I'm reading), a good chunk of this is because I already need a lot of that flexibility for my own purposes. I probably wouldn't have thought of it otherwise.


[XComGame.X2StatusEffects]
;POISONED_IGNORES_SHIELDS=true ; Make poison DOT ignore shields
;BLEEDING_IGNORES_SHIELDS=true ; Make bleeding DOT ignore shields
; Issue #1539
;POISONED_TICKS_AFTER_TURN=true ; Make poison DOT tick on turn end
;BLEEDING_TICKS_AFTER_TURN=true ; Make bleeding DOT tick on turn end
Comment on lines +12 to +13

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add "instead of turn start" to these. With the lines above the inverse case is clear (ignores/does not ignore shields). For tick timing, it's not as obvious (after turn/before turn/during turn/some other trigger).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be moot, depending on the outcome of our conversation in #1539, but I'll keep it in mind!


[XComGame.X2TacticalGameRuleset]
;PlayerTurnOrder=eTeam_XCom
Expand Down
11 changes: 11 additions & 0 deletions X2WOTCCommunityHighlander/Src/XComGame/Classes/X2Effect_Burning.uc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class X2Effect_Burning extends X2Effect_Persistent

var privatewrite name BurningEffectAddedEventName;
var config bool BURNED_IGNORES_SHIELDS; //Issue #89
var config array<name> BURN_TYPES_TICKING_AFTER_TURN; //Issue #1539

function bool IsThisEffectBetterThanExistingEffect(const out XComGameState_Effect ExistingEffect)
{
Expand Down Expand Up @@ -52,8 +53,18 @@ simulated function SetBurnDamage(int Damage, int Spread, name DamageType)

ApplyOnTick.AddItem(BurnDamage);
`assert( ApplyOnTick.Length == 1 );

//Begin Issue #1539
// This isn't how I expected to do this, but I made one critical oversight
// in my initial research: SetBurnDamage isn't what calls BuildPersistentEffect. :o
Comment on lines +58 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rewrite this comment to be less conversational. An observation of where this is handled for poison/bleed, and why the same approach doesn't work for burning, would be sufficient.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this sound?

  // While the helpers used to build poison/bleed DOTs do call BuildPersistentEffect,
  // it appears that burns don't have an equivalent shared behavior we could hook into.
  // Fortunately, burns typically call SetBurnDamage after BuildPersistentEffect,
  // so we can set the WatchRule directly, overriding whatever it was previously set to.

if (BURN_TYPES_TICKING_AFTER_TURN.Find(DamageType) != INDEX_NONE)
{
self.WatchRule = eGameRule_PlayerTurnEnd;
}
//End Issue #1539
}


simulated function X2Effect_ApplyWeaponDamage GetBurnDamage()
{
return X2Effect_ApplyWeaponDamage(ApplyOnTick[0]);
Expand Down
61 changes: 59 additions & 2 deletions X2WOTCCommunityHighlander/Src/XComGame/Classes/X2StatusEffects.uc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ var config int POISONED_DAMAGE;
var config int POISONED_INFECT_DISTANCE;
var config int POISONED_INFECT_PERCENTAGE;
var config bool POISONED_IGNORES_SHIELDS; // Issue #89
var config bool POISONED_TICKS_AFTER_TURN; // Issue #1539
var localized string PoisonedFriendlyName;
var localized string PoisonedFriendlyDesc;
var localized string PoisonedEffectAcquiredString;
Expand Down Expand Up @@ -217,6 +218,7 @@ var localized string BleedingEffectAcquiredString;
var localized string BleedingEffectTickedString;
var localized string BleedingEffectLostString;
var config bool BLEEDING_IGNORES_SHIELDS; // Single variable for Issue #629
var config bool BLEEDING_TICKS_AFTER_TURN; // Issue #1539

var config int ULTRASONICLURE_TURNS;
var name UltrasonicLureName;
Expand Down Expand Up @@ -1102,10 +1104,52 @@ static function X2Effect_PersistentStatChange CreatePoisonedStatusEffect()
local X2Effect_ApplyWeaponDamage DamageEffect;
local X2Condition_UnitProperty UnitPropCondition;

//Begin Issue #1539
/// HL-Docs: feature:DOTsTickAfterTurn; issue:1539; tags:tactical
/// In the base game, DoTs such as bleeding, burning, or poison typically tick at the

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: DOTs instead of DoTs (for consistency)

/// start of the unit owner's turn. Mods can override this behavior by setting the following
/// flags in `XComGameCore.ini`:
///
///```ini
///[XComGame.X2Effect_Burning]
///+BURN_TYPES_TICKING_AFTER_TURN=Fire ; Make fire burn DOT tick on turn end!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the "!" at end of line for consistency

///
///[XComGame.X2StatusEffects]
///POISONED_TICKS_AFTER_TURN=true ; Make poison DOT tick on turn end
///BLEEDING_TICKS_AFTER_TURN=true ; Make bleeding DOT tick on turn end
///```
/// Note that `BURN_TYPES_TICKING_AFTER_TURN` will apply to all instances of `X2Effect_Burning` that
/// use its `SetBurnDamage()` helper method to set up the burn damage effect, *and* use a damage type
/// specified in the `BURN_TYPES_TICKING_AFTER_TURN` array. In the example above, only Fire damage
/// will tick on turn end, while other burning effects such as Acid Burn will continue to tick on
/// turn start.
///
/// `POISONED_TICKS_AFTER_TURN` will apply to all instances of the poisoned effect created using
/// the `X2StatusEffect::CreatePoisonedStatusEffect()` helper method.
///
/// Similarly, `BLEEDING_TICKS_AFTER_TURN` will apply to all instances of the bleeding effect
/// created using the `X2StatusEffect::CreateBleedingStatusEffect()` helper method.
///
/// This should cover all instances of these effects in the base game, but mods can potentially
/// disregard these helper methods, or explicitly override the effect's `WatchRule` property
/// after calling the helper.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would append a small addendum here: "Mods which create DOT effects without these helper methods should consider accessing these configs to implement similar behavior. This improves compatibility between mods and provides more consistency for the player."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that be in addition to the existing paragraph? If so, I might go with something more like this:

"However, it is recommended that mods which create DOT effects either use the appropriate helper methods, or access these configs to implement similar behavior. This improves compatibility between mods and provides more consistency for the player."

If it's replacing the paragraph, I might want to object, on the grounds that someone might have a legitimate interest in knowing how to bypass this feature.

local GameRuleStateChange WatchRule;

if (default.POISONED_TICKS_AFTER_TURN)
{
WatchRule = eGameRule_PlayerTurnEnd;
}
else
{
WatchRule = eGameRule_PlayerTurnBegin;
}
/// End Issue #1539


PersistentStatChangeEffect = new class'X2Effect_PersistentStatChange';
PersistentStatChangeEffect.EffectName = default.PoisonedName;
PersistentStatChangeEffect.DuplicateResponse = eDupe_Refresh;
PersistentStatChangeEffect.BuildPersistentEffect(default.POISONED_TURNS,, false,,eGameRule_PlayerTurnBegin);
PersistentStatChangeEffect.BuildPersistentEffect(default.POISONED_TURNS,, false,,WatchRule); //Issue #1539
PersistentStatChangeEffect.SetDisplayInfo(ePerkBuff_Penalty, default.PoisonedFriendlyName, default.PoisonedFriendlyDesc, "img:///UILibrary_PerkIcons.UIPerk_poisoned");
PersistentStatChangeEffect.AddPersistentStatChange(eStat_Mobility, default.POISONED_MOBILITY_ADJUST);
PersistentStatChangeEffect.AddPersistentStatChange(eStat_Offense, default.POISONED_AIM_ADJUST);
Expand Down Expand Up @@ -2285,10 +2329,23 @@ static function X2Effect_Persistent CreateBleedingStatusEffect(int NumTurns, int
local X2Effect_ApplyWeaponDamage DamageEffect;
local X2Condition_UnitProperty UnitPropCondition;

//Begin Issue #1539

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know HL convention here. I'd personally leave a comment pointing to the HLDocs comment within CreatePoisonedStatusEffect, but that can be disregarded if it's not consistent.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at it too thoroughly, but historical precedent (... including the shield bypass features this PR is derivative of...) appears to be to tag every line or block of code with the issue number, but only define HL-Docs in one location.

I suspect the HL-Docs definitions exist primarily to build the online documentation, and nobody's wanted to clog the Highlander up with duplicate doc sheets.

local GameRuleStateChange WatchRule;

if (default.BLEEDING_TICKS_AFTER_TURN)
{
WatchRule = eGameRule_PlayerTurnEnd;
}
else
{
WatchRule = eGameRule_PlayerTurnBegin;
}
/// End Issue #1539

PersistentEffect = new class'X2Effect_Persistent';
PersistentEffect.EffectName = default.BleedingName;
PersistentEffect.DuplicateResponse = eDupe_Refresh;
PersistentEffect.BuildPersistentEffect(NumTurns, , false, , eGameRule_PlayerTurnBegin);
PersistentEffect.BuildPersistentEffect(NumTurns, , false, , WatchRule); //Issue #1539
PersistentEffect.SetDisplayInfo(ePerkBuff_Penalty, default.BleedingFriendlyName, default.BleedingFriendlyDesc, "img:///UILibrary_XPACK_Common.UIPerk_bleeding");
PersistentEffect.VisualizationFn = BleedingVisualization;
PersistentEffect.EffectSyncVisualizationFn = BleedingSyncVisualization;
Expand Down