DLCRunOrder
Tracking Issue: #511
Tags: compatibility
The base game and Highlander have many "DLC hooks": Overridable
functions in X2DownloadableContentInfo that the game calls for all mods
in some order so that mods can do something. The most ubiquitous hook
is OnPostTemplatesCreated, which allows mods to modify templates.
Unfortunately, the order in which the DLC hooks of mods are executed ("run order") depends on load order, which is not guaranteed. However, run order can matter a lot. Consider a mod that creates copies of guns with different visuals: This mod really wants to run after mods that make changes to specific guns (e.g. stat changes) so that the changes translate to the copies.
The CHL Run Order system provides two ways for mods to specify their position within the run order. This information is relayed to the Highlander using configuration entries (XComGame.ini):
[zzzWeaponSkinReplacer.X2DownloadableContentInfo_WeaponSkinReplacer]
DLCIdentifier="zzzWeaponSkinReplacer"
[zzzWeaponSkinReplacer CHDLCRunOrder]
+RunAfter=PrimarySecondaries
+RunAfter=XCOM2RPGOverhaul
+RunBefore=WOTCUnderbarrelAttachments
RunPriorityGroup=RUN_LAST
Since this system is all about DLC Hooks, the important identifier here is
the DLCIdentifier corresponding to a X2DownloadableContentInfo class,
of which a mod may have zero, one or more. The DLCIdentifier is case-sensitive.
Run Order is about DLCInfos, not individual mods, and a mod may have an
X2DLCInfo that runs before a certain other X2DLCInfo and another that runs
after that other one.
Coarse (RunPriorityGroup)
RunPriorityGroup is a coarse way for DLCInfos to control when they run.
RunPriorityGroup can have three different values: RUN_STANDARD,
RUN_FIRST and RUN_LAST. RUN_STANDARD is the default. All DLCInfos
with RUN_FIRST always run before all RUN_STANDARD ones, and all
DLCInfos with RUN_LAST always run after all RUN_STANDARD ones.
A DLCInfo with RunPriorityGroup=RUN_LAST already runs after the vast
majority of other DLCInfos.
Fine (RunBefore/RunAfter)
Within these groups, DLCInfos can specify which other DLCInfos they run
before and after. [A] +RunBefore="B" is equivalent to specifying
[B] +RunAfter="A". If A and B were in a different
RunPriorityGroup, their relative RunBefore/RunAfter lines would
be ignored.
Errors
The Highlander catches some potential configuration errors and writes them to the log. Warnings and errors are printed to the log, errors are shown to the user in combination with a list of DLCIdentifiers whose authors they should report the error to.
- It is a warning for a DLCInfo B to 
RunAftera DLCInfo A, or A toRunBeforeB, if A is in an earlierRunPriorityGroupthan B, since these config lines are redundant and always fulfilled. - It is an error for a DLCInfo B to 
RunAftera DLCInfo A, or A toRunBeforeB, if A is in a laterRunPriorityGroupthan B, since these config lines are contradictions and never fulfilled. - It is an error for any number of DLCInfos to cause a cycle, since cycles have no solution that fulfills all requirements.
 
The console command CHLDumpRunOrderInternals can be used to print all
the information the CHL has about X2DownloadableContentInfo classes
to the log, for debugging purposes.
One configuration error that can't be caught is a missing DLCIdentifier.
If your X2DownloadableContentInfo subclass specifies a custom config
file via config(CustomConfig), then the DLCIdentifier needs to go in
XComCustomConfig.ini, while the CHDLCRunOrder still goes in
XComGame.ini.
Splitting DLCInfo
Sometimes it can be useful to split your DLC Infos into two different
classes, one containing all changes that can run whenever (RUN_STANDARD)
and one that needs to make its changes last. You can simply create more
subclasses and specify run order for any of them:
X2DownloadableContentInfo_NormalChanges.uc
class X2DownloadableContentInfo_NormalChanges extends X2DownloadableContentInfo;
static event OnPostTemplatesCreated()
{
    // Make regular changes here
}
X2DownloadableContentInfo_LastChanges.uc
class X2DownloadableContentInfo_LastChanges extends X2DownloadableContentInfo;
static event OnPostTemplatesCreated()
{
    // Make last changes here
}
XComGame.ini
[MyMod.X2DownloadableContentInfo_NormalChanges]
DLCIdentifier="MyModNormal"
[MyMod.X2DownloadableContentInfo_LastChanges]
DLCIdentifier="MyModLast"
[MyModLast CHDLCRunOrder]
RunPriorityGroup=RUN_LAST
This combats the trend of mods to move all their changes to RUN_LAST
because they contain only one change that actually needs to run last.
This trend can be problematic because it requires explicit RunAfter
annotations in other mods if they want to run after your normal changes.
Splitting DLCInfos can help mods that use RUN_LAST do the right thing
by default.