Skip to content

DLC content handling

X4 has expanded across multiple DLCs — Cradle of Humanity, Split Vendetta, Tides of Avarice, Pirate, Hyperion Pack, Kingdom End. A mod that references DLC content needs to handle players who don’t own that DLC. This page is the DLC-awareness guide.

A script that references a non-existent macro / faction / ware fails at load. Worse: it can cascade and break other scripts in the same file.

Defensive coding: assume any DLC content might be absent.

<do_if value="@faction.terran">
<!-- Terran-dependent logic -->
</do_if>

@faction.terran returns null if Cradle of Humanity isn’t installed. Always null-check.

If an entire cue is DLC-dependent:

<cue name="TerranSpecificLogic" instantiate="true">
<conditions>
<check_value value="@faction.terran"/>
<!-- ... other conditions ... -->
</conditions>
<actions>
<!-- Safe: only runs if Cradle of Humanity installed -->
</actions>
</cue>

The check_value runs at condition-evaluation; without it, the cue references faction.terran at activation and errors.

<do_if value="@macro.ship_ter_l_destroyer_01_a_macro">
<create_ship macro="macro.ship_ter_l_destroyer_01_a_macro"
sector="$sector"
owner="faction.terran"/>
</do_if>

If the DLC isn’t installed, the macro lookup returns null. Without the guard, the create_ship errors.

For DLC-specific content in table[...] literals, conditional appending is cleaner:

❌ Bad:

<set_value name="$ships" exact="[
macro.ship_arg_l_destroyer_01_a_macro,
macro.ship_ter_l_destroyer_01_a_macro
]"/>

This errors at table evaluation if Terran DLC absent.

✅ Good:

<set_value name="$ships" exact="[
macro.ship_arg_l_destroyer_01_a_macro
]"/>
<do_if value="@faction.terran">
<set_value name="$ships.{$ships.count + 1}"
exact="macro.ship_ter_l_destroyer_01_a_macro"/>
</do_if>

Append conditionally rather than inline.

<do_if value="@ware.ware_avarice_dlc_specific">
<add_inventory ware="ware.ware_avarice_dlc_specific"
exact="5"
entity="player.entity"/>
</do_if>

Same null-check pattern.

Vanilla has a few patterns for detecting DLC presence:

The most reliable indicator that a DLC is installed:

CheckIndicates
@faction.terran not nullCradle of Humanity installed
@faction.terraintrigger not nullCradle of Humanity (alternate check)
@faction.fallensplit not nullSplit Vendetta installed
@faction.scavenger not nullPirate DLC installed
@faction.atf not nullCradle of Humanity (ATF subfaction)
<do_if value="@macro.ship_atf_xl_carrier_01_a_macro">
<!-- Cradle of Humanity carrier macros exist -->
</do_if>

Use a known-DLC macro as a marker.

<do_if value="@player.influence">
<!-- Tides of Avarice installed -->
</do_if>

The Influence resource only exists with that DLC.

Some DLCs depend on others or interact:

  • Tides of Avarice assumes Cradle of Humanity for some factions
  • Hyperion Pack adds Argon ships, no faction-level changes
  • Pirate DLC adds Riptide (scavenger faction) — distinct from Fallen Families (Split Vendetta)

If your mod references Tides content, you may also want to gate on Cradle. Test combinations.

Story missions (story_*.xml) are DLC-specific:

  • story_ventures.xml — Ventures DLC
  • story_buccaneers.xml — Pirate DLC
  • story_paranid.xml — base game
  • story_research_welfare_1.xml — base game

If your mod hooks into a story arc, gate the integration on the relevant DLC presence.

In content.xml, you can declare DLC dependencies:

<content id="mlog_my_mod" name="My Mod" version="1.0">
<dependency id="ego_dlc_terran" optional="true"
comment="Adds Terran integration"/>
<dependency id="ego_dlc_pirate" optional="false"
comment="REQUIRED — Riptide faction logic"/>
</content>
optionalBehaviour
trueMod loads with or without the DLC; uses runtime checks for DLC-specific paths
falseMod refuses to load without the DLC

Use optional="true" for mods with DLC integration that’s “nice to have”; use optional="false" only when the mod cannot function without the DLC.

<param name="default_macro"
default="macro.ship_ter_l_destroyer_01_a_macro"/>

If the param’s default references a DLC macro, the script fails to load when DLC absent. Always use vanilla macros for defaults.

<set_value name="$DefaultEnemy" exact="faction.terran"/>

Hardcoded DLC faction as default — breaks for players without DLC. Detect:

<do_if value="@faction.terran">
<set_value name="$DefaultEnemy" exact="faction.terran"/>
</do_if>
<do_else>
<set_value name="$DefaultEnemy" exact="faction.argon"/>
</do_else>

god.xml uses macro references. If your god.xml diff seeds a Terran station, you need to gate it on the DLC’s presence. XML diffs don’t support this directly — vanilla handles this via DLC-specific god.xml files in the DLC’s extension folder.

For mod authors: split DLC content into a separate XML diff file referenced from content.xml.

Some Workshop mods require specific DLCs. Check before depending.

To test your mod without DLC:

  1. Disable the DLC in Steam (Properties → DLC tab)
  2. Launch X4
  3. Check that your mod still loads
  4. Check that no Property lookup failed: faction.terran errors appear
  5. Re-enable DLC and verify full integration works

Most DLC bugs in mods surface when the player doesn’t own the DLC and the mod crashes at load.


Most “my mod doesn’t work” bug reports from players turn out to be DLC presence/absence mismatches. Defensive coding pays for itself within the first week of publication.