Campaign features

A campaign feature is a named part of the game content or behavior. Each campaign can enable or disable each feature. Examples are the Redux parts, the Redux tech tree, the rescaled galaxy of a mod, and an optional hard mode of a mod. Each campaign has a feature set, which is the list of features that the campaign enables. Patches, assets, prefab patches, Lua hooks, settings and C# behavior each specify the features that they belong to. The game builds each campaign from only the content that its feature set allows.

Features apply to every aspect of modding: patches, assets, settings and code. This page describes the full system:

  • what a feature is

  • how mods declare features

  • how the game selects the feature set of a campaign

  • what a feature gates

  • the runtime APIs

  • how to update an existing mod

For more information about features in Patch Manager, see Features under Core Concepts.

Feature sets

  • Each installed mod has a default feature. The content of a mod is part of a campaign only when that campaign enables the default feature of the mod.

  • A mod can declare more features for optional or alternative content. The mod can gate any of its content on these features.

  • The game selects the feature set of a campaign when the player creates the campaign. The feature set comes from the campaign pack and from the choices of the player. The player can change the feature set later.

  • A feature set has no order. The content of a campaign is the content that its gates let load, with all enabled patches applied to it.

  • Each feature set has its own patch run and its own patch cache. Thus, two campaigns with different feature sets can use the same install, and each campaign gets only its own content.

Feature ids

A feature id has the form ModId:FeatureId, for example KerbolarUpscale:Rescale or Ksp2Redux:Parts. When a mod writes a bare id, such as Rescale, the bare id is qualified with the id of that mod. Id comparison is not case-sensitive.

The default feature

Each mod has a default feature with the name of the mod, ModId:ModId. The mod does not declare it. The default feature means "this mod is enabled in this campaign".

  • It requires the default feature of each mod in the dependencies list of the swinfo.json of the mod.

  • It conflicts with the default feature of each mod in the conflicts list.

  • Each other feature of the mod requires the default feature of the mod. Thus, when any feature of a mod is enabled, the mod is enabled.

  • All content that a mod registers or ships needs the default feature of the mod. This applies in addition to the features that the content specifies. The content includes patches, created assets, prefab patches, Addressables content assets, run callbacks, runtime hooks and campaign settings.

Space Warp (SpaceWarp2), Patch Manager (PatchManager) and V-SwiFT (VSwift) are the modding framework. Their default features are enabled in all campaigns. Players never change them.

Relations between features

A feature can specify four relations. A bare id in a relation refers to a feature of the declaring mod.

Relation

What it means

requires

When this feature is enabled, these features are also enabled. Their own requirements are also enabled, and so on.

conflicts

These features cannot be enabled in the same campaign as this feature.

provides

This feature replaces these ids when the game resolves requirements.

recommended

The mod recommends this feature. The Enable recommended buttons enable it.

The game resolves the relations with these rules:

  • Requirements are transitive. When KerbolarUpscale:Rescale is enabled, KerbolarUpscale:KerbolarUpscale (the default feature of the mod) and Ksp2Redux:Galaxy are also enabled. Ksp2Redux:Galaxy requires Ksp2Redux:Resources, so Ksp2Redux:Resources is also enabled.

  • A conflict applies in both directions. If one of two features names the other as a conflict, the two features cannot both be enabled.

  • A provider replaces the ids that it provides. The feature with an id satisfies a requirement on that id. Any feature that provides the id also satisfies the requirement. A mod can provide a feature of any mod. A mod can also provide a virtual id that no mod declares, such as MyMod:LifeSupport. A feature cannot provide itself.

  • Providers of one id conflict with each other. This includes the real feature with that id. Thus, two life support mods that both provide MyMod:LifeSupport cannot both be enabled.

  • When a requirement has more than one installed provider, the player selects one. If only one installed provider can be enabled, the game selects it automatically and records it in the save. Thus, if the player installs a second provider later, that campaign does not change.

  • Recommended is only a hint. The game does not automatically enable a recommended feature in new campaigns. The feature becomes enabled when the player clicks Enable recommended in the mods menu or in the feature editor.

  • The menus do not show features that cannot be satisfied. If no installed mod can satisfy the requirements of a feature, the menus do not show that feature. For example, this occurs when the mod depends on a mod that is not installed.

  • Two declarations of one id merge. Their requires, conflicts and provides lists combine. The feature is recommended if one of the two declarations makes it recommended. A mod uses this to add relations to its default feature, in addition to the relations from its dependencies.

  • A mod declares only its own features. Its relations can name the features of any mod.

Declaring features

A mod can declare its features in its manifest, in Lua, in C#, or on its SDK Mod asset. All four methods put the features into the same registry. With each method, a mod can add relations to its default feature. To do this, declare the default feature again with the id of the mod.

In swinfo.json

Add a features object. Each key is a bare feature id. Each value lists the relations of that feature. The relation keys are lowercase.

"features": {
    "Rescale": { "requires": ["Ksp2Redux:Galaxy"] },
    "Antennas": {
        "requires": ["Ksp2Redux:Parts"],
        "conflicts": [],
        "provides": [],
        "recommended": true
    }
}

Each list is optional. To give the default feature more relations, use the mod id as the key:

"features": {
    "BetterParts": { "requires": ["Ksp2Redux:Parts"] }
}

From Lua

PM:DefineFeature(id, options) declares a feature of the mod that calls it. It operates only during patch registration. Thus, call it at the top level of a patch script. A Lua mod with a single file does not need a swinfo, because it declares its features with this function.

PM:DefineFeature("HardMode", {
    requires = { "Ksp2Redux:Parts" },
    conflicts = { "EasyMode" },
    recommended = false,
})

From C#

A C# mod declares features through its Patch Manager scope, which is the scope that it uses for its patches. Call DefineFeature from OnPreInitialized, together with your patches:

Patching.Mod.DefineFeature("HardMode", requires: new[] { "Ksp2Redux:Parts" });

On the SDK Mod asset

The inspector of the Mod asset of a mod has a Features section:

  • The default feature of the mod is always at the top, and shows ModId:ModId. Its Requires, Conflicts and Provides lists contain the relations of the default feature. These relations are in addition to the relations from the dependencies and conflicts of the mod.

  • Under it, + Add Feature adds a feature. Each feature has an id, a Recommended toggle, and the lists Requires, Conflicts and Provides. The fields in these lists autocomplete feature ids.

The SDK writes these features into the features section of the generated swinfo.json.

Naming features for players

The menus show the name and the description of each feature. They read them from two localization keys:

Feature/<ModId>/<FeatureId>/Name
Feature/<ModId>/<FeatureId>/Description

If a feature has no name key, the menus show its qualified id. The row of a mod shows the display name of the mod. The description of that row comes from Feature/<ModId>/<ModId>/Description. Put the keys in the localization CSV of your mod, the same as other keys:

Key,Type,Desc,English
Feature/MyMod/HardMode/Name,Text,,Hard Mode
Feature/MyMod/HardMode/Description,Text,,"Halves engine thrust and doubles part costs"

Which features a campaign has

A new campaign

A new campaign starts with these features:

  • each feature in the Features list of its campaign pack

  • the default features of Space Warp, Patch Manager and V-SwiFT

  • each feature that the player selected under Default Features in the mods menu

  • all the features that these features require

All other installed mods are disabled in the new campaign. Their default features are also disabled. A mod does not go into new campaigns only because it is installed. A mod goes into a campaign for one of these reasons:

  • The pack lists it.

  • The player selected it under Default Features.

  • The player enabled it in the feature editor.

To make a mod enabled in its own campaigns, ship a pack that lists the features of the mod. The 2.5x Kerbolar System does this.

The Redux pack, Default, lists Ksp2Redux:All.

Default Features in the mods menu

In the mods menu, the details of each mod have a Default Features foldout. The foldout contains:

  • a toggle for each feature, with the default feature of the mod first

  • an Enable recommended button, which selects the recommended features of the mod

All new campaigns that a player starts get the features that the player selects there. The foldout does not show the Redux features or the modding framework, because packs control the Redux content.

The feature editor

On the new campaign menu, the Edit Pack button of the pack picker opens the Feature Editor. The editor shows the feature set of the selected pack. It lists all the features that can be enabled, grouped by mod, and has a search box:

  • Each mod has a row for its default feature. The row expands to show the other features of the mod. Each mod row has its own Enable recommended button.

  • When you enable a feature, the editor enables its requirements and locks them. The editor also disables and locks each feature that conflicts with it. When you disable a mod, the editor also disables its features.

  • When you select a row, the editor shows the name, id and description of the feature, and the ids that it provides.

  • The editor lists the problems with the feature set when they occur:

    • a requirement that no installed mod provides ("Missing provider for ...")

    • a requirement with more than one provider to select from ("Choose a provider for ...")

    • a real conflict, in red

  • Apply keeps the changed feature set for the new campaign. Cancel discards it.

Existing campaigns

On the campaign selection screen, Edit Pack opens the same editor with the feature set of the selected campaign. When you apply the changes, the editor writes the new feature set into the saves of that campaign. The game builds the new feature set the next time that the campaign loads.

Loading a save with new mods

If a save loads with installed mods that the save never had, the game shows the differences. If the player continues to load the save, the feature editor opens as New Mod Features. The editor lists only the features of the new mods that the save can accept. At the start, the features that the player selected as Default Features are enabled. The save gets the features that the player enables there. A save never gets the features of a mod unless the player selects them.

What a save stores

A save stores its requested features, its pack id and the versions of its mods. Each time the save loads, it uses exactly those features. If a mod is no longer installed, the save keeps its features as missing features. Thus, if the player installs the mod again, the save gets its features back.

The game migrates saves from before campaign features when they load. Such a save gets the Features of its pack and the default feature of each mod in its mod list. A save on the retired Classic pack migrates to Default. A sandbox save that selected a galaxy instead of a pack also migrates to Default.

The main menu

The main menu uses Ksp2Redux:All and the default features of Space Warp, Patch Manager and V-SwiFT. Other mods are disabled in the main menu. Their assets that are not content still load, so a mod can show its UI and icons there.

The active feature set changes at three times:

  • when the game boots into the main menu

  • when a campaign loads

  • when the player goes back to the main menu

What a feature gates

Content

What it needs

Patch Manager patches

The mod's default feature, plus any :Feature / :NoFeature on the patch.

Created assets (PM.JSON:New, NewResource, CreateGalaxy and so on)

The same, on the created asset.

Prefab patches

The same, from :Feature / :NoFeature in code or the authoring metadata's feature lists.

A mod's Addressables content assets

The mod's default feature, plus every feature: label and no nofeature: label on the asset.

A mod's other Addressables assets, such as UI and icons

Every feature: label and no nofeature: label on the asset. Without labels, they load in all feature sets.

Patch run callbacks (PM:OnBeginRun, PM:OnEndRun)

The mod's default feature, plus any :Feature / :NoFeature.

Lua runtime hooks (Game:Update, Game:OnFlight and the rest)

The mod's default feature.

Game:OnSetActivated, Game:OnCampaignLoaded, Game:OnCampaignUnloading

The mod's default feature, plus any :Feature / :NoFeature.

C# OnCampaignLoaded / OnCampaignUnloading

The mod's default feature.

Config entries in the settings menu

The mod's default feature, plus any :Feature / :NoFeature on the entry.

Campaign settings (CampaignConfig)

The mod's default feature.

App bar buttons a mod registers

The mod's default feature.

Campaign packs, game modes and difficulty settings are boot-time content. By design, they are the same in all feature sets and do not take feature gates. Patches and created assets under the campaign_pack, game_mode, difficulty and difficulty_definition labels always apply, in all feature sets. Thus, the game can show the pack of a mod before the mod is enabled.

Disabled mods

A mod is disabled when its default feature is not in the active feature set. A disabled mod is still installed:

  • Its assembly loads.

  • The OnPreInitialized, OnInitialized and OnPostInitialized methods of its plugin run.

  • Its Lua scripts run one time at boot to register.

Redux holds back all the items in the table above. Other actions that the mod does outside these hooks continue, for example Harmony patches or UI that the mod creates directly. These actions continue unless the mod checks Enabled (see Runtime below).

Redux features

Redux is the game itself, so it is enabled in all feature sets. Instead, Redux divides its content into features, so that a pack can exclude parts of that content:

Feature

Requires

Covers

Ksp2Redux:Resources


Resources and recipes.

Ksp2Redux:Parts

Resources

Parts and part audio presets.

Ksp2Redux:Missions

Parts

Missions and mission granters.

Ksp2Redux:Nodes

Parts

The tech tree: tech nodes and their icons.

Ksp2Redux:Galaxy

Resources

The galaxy and its bodies, science regions, discoverables, region maps, body resources, body cards, and atmosphere and cloud overrides.

Ksp2Redux:Science

Parts

Science experiments.

Ksp2Redux:Visuals


Redux's visual changes: colored reentry gradients, cloud layers, drill dust colors, cryogenic frost flags, the Panther nozzle animation fix, Redux's engine plume and persistent smoke prefab patches, and Bop's scaled lighting.

Ksp2Redux:Gameplay


Redux's gameplay changes: the time warp altitude limits on airless bodies.

Ksp2Redux:All

every one above

Redux's whole game.

The stock game content has no feature labels. If an untagged stock asset loads under one of these Addressables labels, it belongs to the feature next to the label:

Label

Feature

parts_data, part_audio_presets

Ksp2Redux:Parts

resources

Ksp2Redux:Resources

missions, missionGranters

Ksp2Redux:Missions

techNodeData, techNodeIcon

Ksp2Redux:Nodes

galaxy_definition, celestial_bodies, science_region, science_region_discoverables, science_region_map, cb_resources, cb_cards, atmosphere_overrides, volume_cloud_overrides

Ksp2Redux:Galaxy

scienceExperiment

Ksp2Redux:Science

Redux tags the content that it adds to the stock game with the same features. Redux gates its own patches on these features, and this includes its bug fixes. Thus, when a feature is disabled, the game removes the stock content and the Redux changes to it together.

Large mods can use this, and do not need to copy content:

  • To replace the full galaxy, a mod ships a pack that lists the Redux features without Ksp2Redux:Galaxy, and the features of the mod. The pack names the galaxy of the mod as its Galaxy. The stock bodies do not load in that campaign.

  • A part overhaul ships a pack that lists the Redux features without Ksp2Redux:Parts, Ksp2Redux:Nodes and the features that require them. The mod supplies its own parts and tech tree.

  • If the content of a mod is applicable only with some Redux content, the default feature of the mod specifies this. For example, the default feature can require Ksp2Redux:Parts.

Gating content on a feature

Patches

Use :Feature(...) and :NoFeature(...) to gate Patch Manager patches, created assets, prefab patches and run callbacks. To gate a full block of registrations, use PM:WithFeature:

PM:WithFeature("HardMode", function()
    PM.Parts:Patch("HalveThrust")
        :Has("Module_Engine")
        :Do(function(part) ... end)
end)

PM.Parts:Patch("NormalThrustTweak"):NoFeature("HardMode"):Do(function(part) ... end)

Patches register one time, at boot. Each feature set gets its own patch run, with the patches that its features allow. Gate with these methods. Do not check Features:Has during registration. The Features page under Core Concepts gives all the details:

  • the AND and OR rules

  • scopes

  • created assets

  • prefab patches

  • run callbacks

  • why checks during registration give incorrect results

Assets built with the SDK

The content assets of a mod load only when the default feature of the mod is enabled. These assets are content:

  • assets under one of the labels in the table above (parts_data, missions, celestial_bodies and so on)

  • assets under a label that a mod loads with Loading.AddSetLoadingAction

  • the input of a Patch Manager run, and prefab patch manifests

Other assets of a mod, such as its UI and icons, load in all feature sets, also in the main menu.

To make an asset also need a different feature of the mod, or a feature of a different mod, give the asset Addressables labels:

  • feature:<qualified id>, such as feature:MyMod:HardMode: the asset needs that feature. If an asset has more than one feature: label, it needs all of these features.

  • nofeature:<qualified id>: the asset does not load when that feature is enabled.

Only content assets need feature labels. Content assets are the assets that load under one of the labels in the table above (parts_data, missions, celestial_bodies and so on). The prefab, icon and textures of a part load because the JSON of the part names them. Thus, they follow the JSON and do not need their own labels.

You do not need to type these labels manually. The SDK has editors for them:

  • Modding > Asset Features..., or Open Asset Features on the Mod asset, opens the Asset Features window. The window lists the content assets of a mod, with the features that each asset requires and excludes. To change the features of assets:

    1. Filter by kind, or search.

    2. Select the assets.

    3. Type a feature id.

    4. Click Require, Exclude or Remove.

  • The Core Part Data inspector, the mission inspector and the General tab of the planet inspector each have a Features section. This section has a Features list and an Excluded Features list for that asset.

  • The PrefabPatchAuthoringMetadata of a visual prefab patch has a Required Features list and an Excluded Features list.

Two enabled assets must never define the same item. If two loaded parts have the same partName, or two bodies have the same bodyName, the game keeps the first. It also logs an error that names the duplicate. Put alternative versions of an asset in features that conflict, or keep them apart with nofeature:. To change the stock version of an asset, patch it in place. Do not ship a second copy.

Runtime

Lua

Each mod script environment has these globals:

  • Features:Has(id) returns whether a feature is enabled in the active feature set. A bare id refers to a feature of the calling mod.

  • Features:All() returns the active feature set as a table that maps each id to true. Before the first feature set activates, it returns nil.

  • Features:OnChanged(fn) calls fn with the new feature set after each feature set activates. It calls fn only when the calling mod is enabled in that feature set.

  • Enabled is true when the default feature of the mod is enabled. Its value changes when the feature set changes.

  • CampaignConfig operates like Config, but each campaign keeps its own values. See Config integration.

The campaign hooks of the Game global return a gated action. Thus, you can chain :Feature(id) and :NoFeature(id) on them, with one id in each call:

  • Game:OnSetActivated(fn) runs each time a feature set becomes active. Use it for data that a mod keeps for each feature set. The hook gives the feature set to fn.

  • Game:OnCampaignLoaded(fn) runs each time a campaign finishes loading, if the mod is enabled in that campaign. This includes quickloads.

  • Game:OnCampaignUnloading(fn) runs before that campaign unloads.

Game:OnCampaignLoaded(function()
    Log:Info("hard mode campaign loaded")
end):Feature("HardMode")

A config entry shows in the settings menu only when its features allow it. To set these features, use :Feature(id) and :NoFeature(id) on the builder. Entries that you define in PM:WithFeature automatically get the feature of that scope.

C#

  • Enabled on GeneralMod, MonoBehaviourMod and KerbalMod is true when the default feature of the mod is enabled in the active feature set. Check it before each action that the mod must do only when it is enabled.

  • Put [Feature("Id")] on a bool field of your plugin class. The field then always shows whether the feature is enabled. A bare id refers to a feature of your mod. The game writes the field each time a feature set activates.

  • OnCampaignLoaded() and OnCampaignUnloading() are virtual methods on the same base classes. They run only for campaigns in which the mod is enabled. In OnCampaignUnloading, undo all the changes that OnCampaignLoaded made.

  • SpaceWarp2.API.Features.ActiveFeatures reads the active feature set. It has these members:

    • Enabled: the enabled ids, or null before a feature set activates

    • IsEnabled(id)

    • IsModEnabled(modId)

    • Changed, an event of type Action<IReadOnlyCollection<string>?>. It occurs after each feature set activates, and gives the enabled ids.

  • [ConfigFeature("Id")] and [ConfigNoFeature("Id")] on a [ConfigValue] show that value in the settings menu only when the feature is enabled or disabled.

  • SWMetadata.CampaignConfigFile is the config file of the mod for each campaign.

using SpaceWarp2.API.Features;
using SpaceWarp2.API.Mods;

public class MyModPlugin : GeneralMod
{
    [Feature("HardMode")]
    private bool _hardMode;

    public override void OnInitialized()
    {
        ActiveFeatures.Changed += _ => SWLogger.LogInfo($"Enabled: {Enabled}, hard mode: {_hardMode}");
    }

    public override void OnCampaignLoaded()
    {
        if (_hardMode)
        {
            // Set up hard mode for this campaign
        }
    }
}

Gate C# patches the same as Lua patches. Use .Feature(...) and .NoFeature(...) on the fluent builder. On a patch of the attribute type, use [NeedsFeature(...)] and [NoFeature(...)]. See Patching from C#.

The patch cache

Patch Manager keeps a separate cache for each feature set that it patched. The caches share identical archives. When you change to a campaign with a feature set that Patch Manager built before, the content loads directly from the cache. See Caching and invalidation.

Updating an existing mod

These changes apply to mods made before campaign features:

  • **PM.InvalidatesOnChange** is removed. Config values never cause a patch rebuild. If a player enables or disables content for each campaign, that content must be a feature.

  • The **Classic** pack is removed. Saves on it migrate to Default.

  • New campaigns do not automatically include installed mods. See A new campaign.

To update a mod:

  1. Declare your features. Make one feature for each part of the content that a player can want without the rest. Give the features names with localization keys.

  2. Replace patches that use config values. If a patch reads a config value to decide whether it runs, change it into a patch gated on a feature. Continue to use config for runtime behavior.

  3. Gate your C# on **Enabled**. Your plugin must check Enabled before each action outside the hooks that Redux gates.

  4. Tag optional assets. If SDK content belongs to one of your features, give it feature: labels in the Asset Features window.

Worked example

The 2.5x Kerbolar System is a full small mod that uses features. It has:

  • two features, declared in swinfo.json

  • rescale patches that all apply in place under one of these features

  • a campaign pack that enables these features together with Ksp2Redux:All

Campaigns without its features keep the Redux galaxy with no changes.