Changes

Jump to navigation Jump to search
→‎Edit the game's assets: reorganize and expand
Line 81: Line 81:  
Note that this requires the asset name, ''not'' a filename. You can get the asset name by taking the path relative to the <tt>Content</tt> folder, and removing the language code and <tt>.xnb</tt> extension. See [[#What's an 'asset'?]].
 
Note that this requires the asset name, ''not'' a filename. You can get the asset name by taking the path relative to the <tt>Content</tt> folder, and removing the language code and <tt>.xnb</tt> extension. See [[#What's an 'asset'?]].
   −
==Edit the game's assets==
+
==Replace a game asset==
===Edit an asset after it's loaded===
+
===Basics===
You can edit any of the game's assets after it's loaded from the file (but before it's provided to the game), without changing the original files. You do this by implementing <tt>IAssetEditor</tt> in your <tt>Mod</tt> class, which adds two methods: <tt>CanEdit&lt;T&gt;</tt> returns whether the mod can edit a particular asset, and <tt>Edit&lt;T&gt;</tt> makes any changes needed. SMAPI will call your <tt>CanEdit&lt;T&gt;</tt> every time an asset is loaded (which may happen multiple times per asset), then call <tt>Edit&lt;T&gt;</tt> if it returns true.
+
You can replace an asset entirely by providing the asset to SMAPI yourself. The original file won't be read at all and won't be changed.
 
  −
For example, here's a mod which doubles the sale price of all items:
  −
<source lang="c#">
  −
public class ModEntry : Mod, IAssetEditor
  −
{
  −
    /// <summary>Get whether this instance can edit the given asset.</summary>
  −
    /// <param name="asset">Basic metadata about the asset being loaded.</param>
  −
    public bool CanEdit<T>(IAssetInfo asset)
  −
    {
  −
        if (asset.AssetNameEquals("Data/ObjectInformation"))
  −
        {
  −
            return true;
  −
        }
  −
 
  −
        return false;
  −
    }
  −
 
  −
    /// <summary>Edit a matched asset.</summary>
  −
    /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
  −
    public void Edit<T>(IAssetData asset)
  −
    {
  −
        if (asset.AssetNameEquals("Data/ObjectInformation"))
  −
        {
  −
            IDictionary<int, string> data = asset.AsDictionary<int, string>().Data;
  −
            foreach (int itemID in data.Keys)
  −
            {
  −
                string[] fields = data[itemID].Split('/');
  −
                fields[1] = (int.Parse(fields[1]) * 2).ToString();
  −
                data[itemID] = string.Join("/", fields);
  −
            }
  −
        }
  −
    }
  −
}
  −
</source>
  −
 
  −
The <tt>IAssetData asset</tt> argument your <tt>Edit</tt> method receives has some helpers to make editing data easier. The above example
  −
 
  −
<ol>
  −
<li>The above example uses <tt>asset.AsDictionary<int, string>().Data</tt> to get a reference to the dictionary being edited. You can also change a specific entry directly this way:
  −
<source lang="C#">
  −
public void Edit<T>(IAssetData asset)
  −
{
  −
  IDictionary<int, string> data = asset.AsDictionary<int, string>().Data;
  −
  data[999] = "... some new item string ...";
  −
}
  −
</source></li>
  −
<li>You can paste a custom image into a texture being loaded (e.g. to replace one sprite):
  −
<source lang="C#">
  −
public void Edit<T>(IAssetData asset)
  −
{
  −
    Texture2D customTexture = this.Helper.Content.Load<Texture2D>("custom-texture.png", ContentSource.ModFolder);
  −
    asset
  −
        .AsImage()
  −
        .PatchImage(customTexture, targetArea: new Rectangle(300, 100, 200, 200));
  −
}
  −
</source></li>
  −
<li>In rare cases, you can use <tt>asset.ReplaceWith</tt> to replace the entire asset with a new version. (Usually you shouldn't do that though; see ''Replace an asset entirely'' below.</li>
  −
</ol>
  −
 
  −
See IntelliSense for the <tt>asset</tt> argument for more info.
  −
 
  −
===Replace an asset entirely===
  −
Sometimes you may need to replace an asset entirely (without changing the original file), not just edit it after it's loaded. In other words, you're providing the asset to SMAPI yourself so the original file won't be read at all. Note that two mods can't both provide the same asset.
      
You can do this by implementing <tt>IAssetLoader</tt> in your <tt>Mod</tt> class. This adds two methods: <tt>CanLoad&lt;T&gt;</tt> returns whether the mod can provide a particular asset, and <tt>Load&lt;T&gt;</tt> provides the asset data. SMAPI will call your <tt>CanLoad&lt;T&gt;</tt> every time an asset is loaded (which may happen multiple times per asset), then call <tt>Load&lt;T&gt;</tt> if it returns true. (If multiple mods return true from <tt>CanLoad&lt;T&gt;</tt> for the same asset, SMAPI will display an error and not call <tt>Load&lt;T&gt;</tt> for any of them.)
 
You can do this by implementing <tt>IAssetLoader</tt> in your <tt>Mod</tt> class. This adds two methods: <tt>CanLoad&lt;T&gt;</tt> returns whether the mod can provide a particular asset, and <tt>Load&lt;T&gt;</tt> provides the asset data. SMAPI will call your <tt>CanLoad&lt;T&gt;</tt> every time an asset is loaded (which may happen multiple times per asset), then call <tt>Load&lt;T&gt;</tt> if it returns true. (If multiple mods return true from <tt>CanLoad&lt;T&gt;</tt> for the same asset, SMAPI will display an error and not call <tt>Load&lt;T&gt;</tt> for any of them.)
Line 242: Line 179:  
}
 
}
 
</source>
 
</source>
 +
 +
===Disadvantages===
 +
Logically there's only one initial version of an asset. If multiple mods want to provide the same asset, SMAPI will show an error and reject all of them. In some cases this is still the best approach, but if you're only changing part of the asset see ''[[#Edit the game's assets|edit the game's assets]]'' below instead.
 +
 +
==Edit a game asset==
 +
===Basics===
 +
You can edit any game asset after it's loaded (but before it's provided to the game), without changing the original files. You do this by implementing <tt>IAssetEditor</tt> in your <tt>Mod</tt> class, which adds two methods: <tt>CanEdit&lt;T&gt;</tt> returns whether the mod can edit a particular asset, and <tt>Edit&lt;T&gt;</tt> makes any changes needed. SMAPI will call your <tt>CanEdit&lt;T&gt;</tt> every time an asset is loaded (which may happen multiple times per asset), then call <tt>Edit&lt;T&gt;</tt> if it returns true.
 +
 +
For example, here's a mod which doubles the sale price of all items:
 +
<source lang="c#">
 +
public class ModEntry : Mod, IAssetEditor
 +
{
 +
    /// <summary>Get whether this instance can edit the given asset.</summary>
 +
    /// <param name="asset">Basic metadata about the asset being loaded.</param>
 +
    public bool CanEdit<T>(IAssetInfo asset)
 +
    {
 +
        if (asset.AssetNameEquals("Data/ObjectInformation"))
 +
        {
 +
            return true;
 +
        }
 +
 +
        return false;
 +
    }
 +
 +
    /// <summary>Edit a matched asset.</summary>
 +
    /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
 +
    public void Edit<T>(IAssetData asset)
 +
    {
 +
        if (asset.AssetNameEquals("Data/ObjectInformation"))
 +
        {
 +
            IDictionary<int, string> data = asset.AsDictionary<int, string>().Data;
 +
            foreach (int itemID in data.Keys)
 +
            {
 +
                string[] fields = data[itemID].Split('/');
 +
                fields[1] = (int.Parse(fields[1]) * 2).ToString();
 +
                data[itemID] = string.Join("/", fields);
 +
            }
 +
        }
 +
    }
 +
}
 +
</source>
 +
 +
The <tt>IAssetData asset</tt> argument for your <tt>Edit</tt> method has some helpers to make editing data easier, documented below. (See IntelliSense for the <tt>asset</tt> argument for more info.)
 +
 +
===Edit any file===
 +
These fields/methods are available directly on the <tt>asset</tt> received by your <tt>Edit</tt> method for any asset type, and also available through the helpers listed below.
 +
 +
; Data
 +
: A reference to the loaded asset data.
 +
 +
; ReplaceWith
 +
: Replace the entire asset with a new version. You shouldn't do that in most cases though; see ''[[#Replace a game asset|replace a game asset]]'' instead, or use one of the helpers below.
 +
 +
===Edit a dictionary===
 +
A ''dictionary'' is a key/value data structure, represented like this in JSON exports:
 +
<source lang="json">
 +
{
 +
  "key A": "value A",
 +
  "key B": "value B",
 +
  ...
 +
}
 +
</source>
 +
 +
You can get a dictionary helper using <tt>asset.AsDictionary<TKey, string>()</tt>, where <tt>TKey</tt> is replaced with the key type (usually <tt>int</tt> or <tt>string</tt>).
 +
 +
; Data
 +
: A reference to the loaded data. For example, here's how to add or replace a specific entry to the above example:
 +
: <source lang="C#">
 +
public void Edit<T>(IAssetData asset)
 +
{
 +
  var editor = asset.AsDictionary<string, string>();
 +
  editor.Data["Key C"] = "Value C";
 +
}
 +
</source>
 +
 +
===Edit an image===
 +
When editing an image file, you can get a helper using <tt>asset.AsImage()</tt>.
 +
 +
; Data
 +
: A reference to the loaded image. You can directly edit each pixel in the image through this field, though that's rarely needed.
 +
 +
; PatchImage
 +
: Edit or replace part of the image. This is basically a copy & paste operation, so the source texture is applied over the loaded texture. For example:
 +
: <source lang="C#">
 +
public void Edit<T>(IAssetData asset)
 +
{
 +
  var editor = asset.AsImage();
 +
 
 +
  Texture2D sourceImage = this.Helper.Content.Load<Texture2D>("custom-texture.png", ContentSource.ModFolder);
 +
  editor.PatchImage(sourceImage, targetArea: new Rectangle(300, 100, 200, 200));
 +
}
 +
</source>
 +
 +
: Available method arguments:
 +
: {| class="wikitable"
 +
|-
 +
! argument
 +
! usage
 +
|-
 +
| <tt>source</tt>
 +
| The source image to copy & paste onto the loaded image.
 +
|-
 +
| <tt>sourceArea</tt>
 +
| ''(optional)'' The pixel area within the source image to copy (or omit to use the entire source image). This must fit within the target image.
 +
|-
 +
| <tt>targetArea</tt>
 +
| ''(optional)'' The pixel area within the loaded image to replace (or omit to replace starting from the top-left corner up to the full source size).
 +
|-
 +
| <tt>patchMode</tt>
 +
| ''(optional)'' How the image should be patched. The possible values...
 +
* <tt>PatchMode.Replace</tt> (default): erase the original content within the area before pasting in the new content;
 +
* <tt>PatchMode.Overlay</tt>: draw the new content over the original content, so the original content shows through any ''fully'' transparent pixels.
 +
|}
    
==Advanced==
 
==Advanced==
translators
8,403

edits

Navigation menu