Difference between revisions of "Modding:Modder Guide/APIs/Data"

From Stardew Valley Wiki
Jump to navigation Jump to search
(update for SMAPI 2.8 release)
(→‎Mod data: rewrite, add more info from migration guide)
 
(18 intermediate revisions by 6 users not shown)
Line 5: Line 5:
 
==Storage options==
 
==Storage options==
 
===JSON files===
 
===JSON files===
You can store data in arbitrary <tt>.json</tt> files in your mod folder. Note that these files will be lost if the player deletes them (e.g. when updating your mod), so this is mainly useful for bundled files, cache files, etc.
+
You can store data in arbitrary <samp>.json</samp> files in your mod folder. Note that these files will be lost if the player deletes them (''e.g.,'' when updating your mod), so this is mainly useful for bundled files, cache files, etc.
  
 
<ol>
 
<ol>
 
<li>[[#Data model|Create your data model]].</li>
 
<li>[[#Data model|Create your data model]].</li>
<li>In your mod code, use the mod helper to read/write a named file. This example assumes you created a class named <tt>ModData</tt>, but you can use different names too.
+
<li>In your mod code, use the mod helper to read/write a named file. This example assumes you created a class named <samp>ModData</samp>, but you can use different names too.
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
// read file
 
// read file
var model = this.Helper.ReadJsonFile<ModData>("data.json") ?? new ModData();
+
var model = this.Helper.Data.ReadJsonFile<ModData>("data.json") ?? new ModData();
  
 
// save file (if needed)
 
// save file (if needed)
this.Helper.WriteJsonFile("data.json", model);
+
this.Helper.Data.WriteJsonFile("data.json", model);
</source>
+
</syntaxhighlight>
  
Note that <tt>ReadJsonFile</tt> will return <tt>null</tt> if the file doesn't exist. The above example will create a default instance if that happens; if you don't want to do that, just remove the <code>?? new ModData()</code> part.</li>
+
Note that <samp>ReadJsonFile</samp> will return <samp>null</samp> if the file doesn't exist. The above example will create a default instance if that happens; if you don't want to do that, just remove the <code>?? new ModData()</code> part.</li>
 
</ol>
 
</ol>
  
Line 27: Line 27:
  
 
===Save data===
 
===Save data===
You can store arbitrary data in the current save file. This is mainly useful for save-specific data, like player progression and custom items. Note that a save file must be loaded (e.g. it won't work on the title screen), and the data won't be persisted until the player saves the current day.
+
You can store arbitrary data in the current save file. This is mainly useful for save-specific data, like player progression and custom items. This is subject to some restrictions: the save file must be loaded (''e.g.,'' it won't work on the title screen), it can't be used by farmhands in multiplayer (you can [[Modding:Modder Guide/APIs/Utilities#Context|check <samp>Context.IsMainPlayer</samp>]]), and it'll be discarded if the player exits without saving. If the data needs to be prepared prior to saving, the [[Modding:Modder Guide/APIs/Events#GameLoop.Saving|<samp>GameLoop.Saving</samp> event]] is a good time to do it.
  
 
<ol>
 
<ol>
 
<li>[[#Data model|Create your data model]].</li>
 
<li>[[#Data model|Create your data model]].</li>
 
<li>[[#Data key|Choose your data key]].</li>
 
<li>[[#Data key|Choose your data key]].</li>
<li>In your mod code, use the data API to read/write a named entry. This example assumes your data model is <tt>ModData</tt> and your key is <tt>example-key</tt>, but you can use different values.
+
<li>In your mod code, use the data API to read/write a named entry. This example assumes your data model is <samp>ModData</samp> and your key is <samp>example-key</samp>, but you can use different values.
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
// read file
+
// read data
var model = this.Helper.ReadSaveData<ModData>("example-key");
+
var model = this.Helper.Data.ReadSaveData<ModData>("example-key");
  
// save file (if needed)
+
// save data (if needed)
this.Helper.WriteSaveData("example-key", model);
+
this.Helper.Data.WriteSaveData("example-key", model);
</source>
+
</syntaxhighlight>
  
Note that <tt>ReadSaveData</tt> will return <tt>null</tt> if the data doesn't exist.</li>
+
Note that <samp>ReadSaveData</samp> will return <samp>null</samp> if the data doesn't exist. Additionally, <samp>ReadSaveData</samp> is scoped to your mod, so you do not need to prepend with your mod's uniqueID</li>
 
</ol>
 
</ol>
  
Line 51: Line 51:
 
<li>[[#Data model|Create your data model]].</li>
 
<li>[[#Data model|Create your data model]].</li>
 
<li>[[#Data key|Choose your data key]].</li>
 
<li>[[#Data key|Choose your data key]].</li>
<li>In your mod code, use the data API to read/write a named entry. This example assumes your data model is <tt>ModData</tt> and your key is <tt>example-key</tt>, but you can use different values.
+
<li>In your mod code, use the data API to read/write a named entry. This example assumes your data model is <samp>ModData</samp> and your key is <samp>example-key</samp>, but you can use different values.
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
// read file
+
// read data
var model = this.Helper.ReadGlobalData<ModData>("example-key");
+
var model = this.Helper.Data.ReadGlobalData<ModData>("example-key");
  
// save file (if needed)
+
// save data (if needed)
this.Helper.WriteGlobalData("example-key", model);
+
this.Helper.Data.WriteGlobalData("example-key", model);
</source>
+
</syntaxhighlight>
  
Note that <tt>ReadGlobalData</tt> will return <tt>null</tt> if the data doesn't exist.</li>
+
Note that <samp>ReadGlobalData</samp> will return <samp>null</samp> if the data doesn't exist.</li>
 
</ol>
 
</ol>
  
Line 67: Line 67:
 
===Creating a data model===
 
===Creating a data model===
 
The ''data model'' is a C# class you create, with properties representing the data you want to store. It can contain almost anything from a few boolean fields to a complex object graph. Here's a simple data model:
 
The ''data model'' is a C# class you create, with properties representing the data you want to store. It can contain almost anything from a few boolean fields to a complex object graph. Here's a simple data model:
<source lang="c#">
+
<syntaxhighlight lang="c#">
class ModData
+
public sealed class ModData
 
{
 
{
 
   public bool ExampleBoolean { get; set; }
 
   public bool ExampleBoolean { get; set; }
 
   public int ExampleNumber { get; set; }
 
   public int ExampleNumber { get; set; }
 
}
 
}
</source>
+
</syntaxhighlight>
  
 
If you save that model to a JSON file, the file would look like this:
 
If you save that model to a JSON file, the file would look like this:
<source lang="json">
+
<syntaxhighlight lang="json">
 
{
 
{
 
   "ExampleBoolean": false,
 
   "ExampleBoolean": false,
 
   "ExampleNumber": 0
 
   "ExampleNumber": 0
 
}
 
}
</source>
+
</syntaxhighlight>
 +
 
 +
Only public properties and fields will be serialized, and your class needs a zero-parameter constructor. SMAPI uses NewtonSoft to serialize these models, so if you need finer-grained control over what gets serialized, use NewtonSoft annotations.
  
 
===Default values===
 
===Default values===
 
You can optionally set default values in your data model:
 
You can optionally set default values in your data model:
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
class ModData
 
class ModData
 
{
 
{
Line 91: Line 93:
 
   public int ExampleNumber { get; set; } = 5;
 
   public int ExampleNumber { get; set; } = 5;
 
}
 
}
</source>
+
</syntaxhighlight>
  
 
...or set defaults with a constructor:
 
...or set defaults with a constructor:
  
<source lang="c#">
+
<syntaxhighlight lang="c#">
 
class ModData
 
class ModData
 
{
 
{
Line 107: Line 109:
 
   }
 
   }
 
}
 
}
</source>
+
</syntaxhighlight>
  
 
==Data key==
 
==Data key==
 
A ''data key'' identifies your data, which lets you access the data again later. It should be unique within your mod, but there's no need to worry about conflicts with other mods (SMAPI will namespace the key internally). A data key can only contain letters, numbers, underscores, hyphens, or dots.
 
A ''data key'' identifies your data, which lets you access the data again later. It should be unique within your mod, but there's no need to worry about conflicts with other mods (SMAPI will namespace the key internally). A data key can only contain letters, numbers, underscores, hyphens, or dots.
  
{{modding guide footer
+
==Deletion==
|prev = [[Modding:Modder Guide/APIs|SMAPI reference]]
+
To remove an entry or file, just pass <code>null</code> as the data model. This works with any of the <samp>Write*</samp> methods:
|next =  
+
<syntaxhighlight lang="c#">
}}
+
// delete entry (if present)
 +
this.Helper.Data.WriteSaveData<DataModel>("example-key", null);
 +
</syntaxhighlight>
 +
 
 +
==See also==
 +
===Mod data===
 +
You can also store custom data for individual game entities which have a <samp>modData</samp> dictionary field. That includes NPCs and players (<samp>Character</samp>), <samp>GameLocation</samp>, <samp>Item</samp>, and <samp>TerrainFeature</samp>. This is persisted to the save file and synchronized in multiplayer.
 +
 
 +
Usage notes:
 +
* To avoid mod conflicts, prefixing data fields with your mod ID is strongly recommended (see the example below).
 +
* When you split an item stack, the new stack copies the previous one's <samp>modData</samp> field; when merged into another stack, the merged items adopt the target stack's mod data. Otherwise mod data has no effect on item split/merge logic (''e.g.,'' you can still merge items with different mod data).</li>
 +
 
 +
For example, this writes and then reads a custom 'age' value for an item:
 +
<syntaxhighlight lang="c#">
 +
// write a custom value
 +
item.modData[$"{this.ModManifest.UniqueID}/item-age"] = "30";
 +
 
 +
// read it
 +
if (item.modData.TryGetValue($"{this.ModManifest.UniqueID}/item-age", out string rawAge) && int.TryParse(rawAge, int age))
 +
  ...
 +
</syntaxhighlight>

Latest revision as of 00:47, 31 January 2023

Creating SMAPI mods SMAPI mascot.png


Modding:Index

The data API lets you store arbitrary data and retrieve it later. For example, you can use this to store player progression, custom items that can't be saved by the game, etc.

Storage options

JSON files

You can store data in arbitrary .json files in your mod folder. Note that these files will be lost if the player deletes them (e.g., when updating your mod), so this is mainly useful for bundled files, cache files, etc.

  1. Create your data model.
  2. In your mod code, use the mod helper to read/write a named file. This example assumes you created a class named ModData, but you can use different names too.
    // read file
    var model = this.Helper.Data.ReadJsonFile<ModData>("data.json") ?? new ModData();
    
    // save file (if needed)
    this.Helper.Data.WriteJsonFile("data.json", model);
    
    Note that ReadJsonFile will return null if the file doesn't exist. The above example will create a default instance if that happens; if you don't want to do that, just remove the ?? new ModData() part.

By changing the file path you specify, you can...

  • store JSON files in a subfolder, by specifying a path relative to your mod folder (like "data/some-file.json"). SMAPI will create the folders automatically if needed.
  • create per-save JSON files by using the save ID in the name, like $"data/{Constants.SaveFolderName}.json".

Save data

You can store arbitrary data in the current save file. This is mainly useful for save-specific data, like player progression and custom items. This is subject to some restrictions: the save file must be loaded (e.g., it won't work on the title screen), it can't be used by farmhands in multiplayer (you can check Context.IsMainPlayer), and it'll be discarded if the player exits without saving. If the data needs to be prepared prior to saving, the GameLoop.Saving event is a good time to do it.

  1. Create your data model.
  2. Choose your data key.
  3. In your mod code, use the data API to read/write a named entry. This example assumes your data model is ModData and your key is example-key, but you can use different values.
    // read data
    var model = this.Helper.Data.ReadSaveData<ModData>("example-key");
    
    // save data (if needed)
    this.Helper.Data.WriteSaveData("example-key", model);
    
    Note that ReadSaveData will return null if the data doesn't exist. Additionally, ReadSaveData is scoped to your mod, so you do not need to prepend with your mod's uniqueID

Global app data

You can store arbitrary data on the local computer, synchronised by GOG/Steam if applicable. This data is global (not per-save) and changes are saved immediately.

  1. Create your data model.
  2. Choose your data key.
  3. In your mod code, use the data API to read/write a named entry. This example assumes your data model is ModData and your key is example-key, but you can use different values.
    // read data
    var model = this.Helper.Data.ReadGlobalData<ModData>("example-key");
    
    // save data (if needed)
    this.Helper.Data.WriteGlobalData("example-key", model);
    
    Note that ReadGlobalData will return null if the data doesn't exist.

Data model

Creating a data model

The data model is a C# class you create, with properties representing the data you want to store. It can contain almost anything from a few boolean fields to a complex object graph. Here's a simple data model:

public sealed class ModData
{
   public bool ExampleBoolean { get; set; }
   public int ExampleNumber { get; set; }
}

If you save that model to a JSON file, the file would look like this:

{
   "ExampleBoolean": false,
   "ExampleNumber": 0
}

Only public properties and fields will be serialized, and your class needs a zero-parameter constructor. SMAPI uses NewtonSoft to serialize these models, so if you need finer-grained control over what gets serialized, use NewtonSoft annotations.

Default values

You can optionally set default values in your data model:

class ModData
{
   public bool ExampleBoolean { get; set; } = true;
   public int ExampleNumber { get; set; } = 5;
}

...or set defaults with a constructor:

class ModData
{
   public bool ExampleBoolean { get; set; }
   public int ExampleNumber { get; set; }

   public ModData()
   {
      this.ExampleBoolean = true;
      this.ExampleNumber = 5;
   }
}

Data key

A data key identifies your data, which lets you access the data again later. It should be unique within your mod, but there's no need to worry about conflicts with other mods (SMAPI will namespace the key internally). A data key can only contain letters, numbers, underscores, hyphens, or dots.

Deletion

To remove an entry or file, just pass null as the data model. This works with any of the Write* methods:

// delete entry (if present)
this.Helper.Data.WriteSaveData<DataModel>("example-key", null);

See also

Mod data

You can also store custom data for individual game entities which have a modData dictionary field. That includes NPCs and players (Character), GameLocation, Item, and TerrainFeature. This is persisted to the save file and synchronized in multiplayer.

Usage notes:

  • To avoid mod conflicts, prefixing data fields with your mod ID is strongly recommended (see the example below).
  • When you split an item stack, the new stack copies the previous one's modData field; when merged into another stack, the merged items adopt the target stack's mod data. Otherwise mod data has no effect on item split/merge logic (e.g., you can still merge items with different mod data).

For example, this writes and then reads a custom 'age' value for an item:

// write a custom value
item.modData[$"{this.ModManifest.UniqueID}/item-age"] = "30";

// read it
if (item.modData.TryGetValue($"{this.ModManifest.UniqueID}/item-age", out string rawAge) && int.TryParse(rawAge, int age))
   ...