Skip to content

Latest commit

 

History

History
248 lines (199 loc) · 7.7 KB

conditions-api.md

File metadata and controls

248 lines (199 loc) · 7.7 KB

README

This doc helps SMAPI mod authors use Content Patcher's condition system in their own mods.

To add custom tokens for content packs to use, see the extensibility API. See the main README for other info.

Contents

Overview

Content Patcher has a conditions system which lets content packs check dozens of contextual values for conditional changes. For example:

"When": {
   "PlayerGender": "male",             // player is male
   "Relationship: Abigail": "Married", // player is married to Abigail
   "HavingChild": "{{spouse}}",        // Abigail is having a child
   "Season": "Winter"                  // current season is winter
}

Other SMAPI mods can use this conditions system too. They essentially create a dictionary representing the conditions they want to check (e.g. by parsing them from their own content packs), call the API below to get a 'managed conditions' object, then use that to manage the conditions.

Access the API

To access the API:

  1. Add Content Patcher as a required dependency in your mod's manifest.json:
    "Dependencies": [
       { "UniqueID": "Pathoschild.ContentPatcher", "MinimumVersion": "2.4.0" }
    ]
  2. Add a reference to the Content Patcher DLL in your mod's .csproj file. Make sure you set Private="False", so the DLL isn't added to your mod folder:
    <ItemGroup>
      <Reference Include="ContentPatcher" HintPath="$(GameModsPath)\ContentPatcher\ContentPatcher.dll" Private="False" />
    </ItemGroup>
  3. Somewhere in your mod code (e.g. in the GameLaunched event), get a reference to Content Patcher's API:
    var api = this.Helper.ModRegistry.GetApi<ContentPatcher.IContentPatcherAPI>("Pathoschild.ContentPatcher");

Parse conditions

Note: see caveats before calling this API.

Now that you have access to the API, you can parse conditions.

  1. Create a Dictionary<string, string> model of the conditions you want to check. This can use Content Patcher features like tokens. For this example, let's assume you have these hardcoded conditions (see the conditions documentation for the format):

    var rawConditions = new Dictionary<string, string>
    {
       ["PlayerGender"] = "male",             // player is male
       ["Relationship: Abigail"] = "Married", // player is married to Abigail
       ["HavingChild"] = "{{spouse}}",        // Abigail is having a child
       ["Season"] = "Winter"                  // current season is winter
    };
  2. Call the API to parse the conditions into an IManagedConditions wrapper. The formatVersion matches the Format field described in the author guide to enable forward compatibility with future versions of Content Patcher.

    var conditions = api.ParseConditions(
       manifest: this.ModManifest,
       raw: rawConditions,
       formatVersion: new SemanticVersion("1.20.0")
    );
  3. Get the result from the IsMatch property. For example:

    conditions.UpdateContext();
    if (conditions.IsMatch)
       ...

If you want to allow custom tokens added by other SMAPI mods, you can specify a list of mod IDs to assume are installed. You don't need to do this for your own mod ID, for mods listed as required dependencies in your mod's manifest.json, or for mods listed via HasMod in the conditions dictionary.

var conditions = api.ParseConditions(
   manifest: this.ModManifest,
   raw: rawConditions,
   formatVersion: new SemanticVersion("1.20.0"),
   assumeModIds: new[] { "spacechase0.JsonAssets" }
);

Manage conditions

The IManagedConditions object you got above provides a number of properties and methods to manage the parsed conditions. You can check IntelliSense in Visual Studio to see what's available, but here are some of the most useful properties:

property type description
IsValid bool

Whether the conditions were parsed successfully (regardless of whether they're in scope currently).

ValidationError string

When IsValid is false, an error phrase indicating why the conditions failed to parse, formatted like this:

'seasonz' isn't a valid token name; must be one of <token list>

If the conditions are valid, this is null.

IsReady bool

Whether the conditions' tokens are all valid in the current context. For example, this would be false if the conditions use Season and a save isn't loaded yet.

IsMatch bool

Whether IsReady is true, and the conditions all match in the current context.

If there are no conditions (i.e. you parsed an empty dictionary), this is always true.

IsMutable bool

Whether IsMatch may change depending on the context. For example, Season is mutable since it depends on the in-game season. HasMod is not mutable, since it can't change after the game is launched.

And methods:

method type description
GetReasonNotMatched string

If IsMatch is false, this analyzes the conditions/context and provides a human-readable reason phrase explaining why the conditions don't match the context. For example:

conditions don't match: season

If the conditions do match, this returns null.

UpdateContext bool

Updates the conditions based on Content Patcher's current context, and returns whether IsMatch changed. It's safe to call this as often as you want, but it has no effect if the Content Patcher context hasn't changed since you last called it.

Caveats

The conditions API isn't available immediately.

The conditions API is available two ticks after the GameLaunched event (and anytime after that point). That's due to the Content Patcher lifecycle:

  1. GameLaunched: other mods can register custom tokens.
  2. GameLaunched + 1 tick: Content Patcher initializes the token context (including custom tokens).
  3. GameLaunched + 2 ticks: other mods can use the conditions API.
Conditions should be cached.

Parsing conditions through the API is a relatively expensive operation. If you'll recheck the same conditions often, it's best to save and reuse the IManagedConditions instance.

Conditions don't update automatically.

When using a cached IManagedConditions object, make sure to update it using conditions.UpdateContext() as needed.

Note that condition updates are limited to Content Patcher's update rate. When you call conditions.UpdateContext(), it will reflect the tokens as of Content Patcher's last internal context update.

Conditions handle split-screen automatically.

For example, IsMatch returns whether it matches the current screen's context. The exception is UpdateContext, which updates the context for all active screens.

See also