Changes

1,258 bytes removed ,  19:22, 29 July 2023
no edit summary
Line 119: Line 119:  
Adding new NPCs involves editing a number of files:
 
Adding new NPCs involves editing a number of files:
   −
* New file: Characters\Dialogue\<name>
+
* New file: [[Modding:Dialogue|Characters\Dialogue\<name>.json]] (See also [[Modding:Event data]])
* New file: Characters\schedules\<name>
+
* New file: [[Modding:Schedule data|Characters\schedules\<name>.json]] (Note that the "s" in the "schedules" folder is lowercase!)
* New file: Portraits\<name>
+
* New file: [[Modding:NPC data#Portraits|Portraits\<name>.png]]
* New file: Characters\<name>
+
* New file: [[Modding:NPC data#Overworld sprites|Characters\<name>.png]]
* Add entries Data\EngagementDialogue for NPCs that are marriable
+
* Add entries [[Modding:Dialogue#Engagement dialogue|Data\EngagementDialogue]] for NPCs that are marriable
* Add entry to Data\NPCDispositions
+
* Add entry to [[Modding:NPC data#Basic info|Data\NPCDispositions]]
* Add entry to Data\NPCGiftTastes
+
* Add entry to [[Modding:Gift taste data|Data\NPCGiftTastes]]
* Add entries to Characters\Dialogue\rainy
+
* Add entries to [[Modding:Dialogue#Rain dialogue|Characters\Dialogue\rainy]]
* Add entries to Data\animationDescriptions (if you want custom animations in their schedule)
+
* Add entries to [[Modding:Schedule data#Schedule points|Data\animationDescriptions]] (if you want custom animations in their schedule)
   −
All of the above can be done with IAssetLoaders/IAssetEditors or Content Patcher. Finally, spawn the NPC with a SMAPI mod. The different constructors are:
+
All of the above can be done with an AssetRequested event or Content Patcher. If you did all of this correctly, the game will spawn the NPC in for you. (If you didn't, it swallows the error)
 
  −
<syntaxhighlight lang='c#'>
  −
public NPC(AnimatedSprite sprite, Vector2 position, int facingDir, string name, LocalizedContentManager content = null);
  −
public NPC(AnimatedSprite sprite, Vector2 position, string defaultMap, int facingDir, string name, Dictionary<int, int[]> schedule, Texture2D portrait, bool eventActor);
  −
public NPC(AnimatedSprite sprite, Vector2 position, string defaultMap, int facingDirection, string name, bool datable, Dictionary<int, int[]> schedule, Texture2D portrait);
  −
</syntaxhighlight>
  −
 
  −
For spawning:
  −
 
  −
<syntaxhighlight lang='c#'>
  −
Game1.getLocationFromName("Town").addCharacter(npc);
  −
</syntaxhighlight>
      
==User-interface (UI)==
 
==User-interface (UI)==
Line 166: Line 154:     
Types available:
 
Types available:
*1 - Achievement (HUDMessage.achievement_type)
+
#Achievement (HUDMessage.achievement_type)
*2 - New Quest (HUDMessage.newQuest_type)
+
#New Quest (HUDMessage.newQuest_type)
*3 - Error (HUDMessage.error_type)
+
#Error (HUDMessage.error_type)
*4 - Stamina (HUDMessage.stamina_type)
+
#Stamina (HUDMessage.stamina_type)
*5 - Health (HUDMessage.health_type)
+
#Health (HUDMessage.health_type)
      Line 226: Line 214:     
===DialogueBox===
 
===DialogueBox===
[[File:DialogueBox_NoChoices_Example.jpg|200px|thumb|right|Example of DialogueBox without choices.]]A '''DialogueBox''' is a text box with a slightly larger, slightly boldfaced text, with "typewriter-like" effect.
+
[[File:DialogueBox NoChoices Example.jpg|200px|thumb|right|Example of DialogueBox without choices.]]A '''DialogueBox''' is a text box with a slightly larger, slightly boldfaced text, with "typewriter-like" effect.
    
There are several variants, including ones with a dialogue/conversation choices.
 
There are several variants, including ones with a dialogue/conversation choices.
Line 291: Line 279:  
namespace MyMod
 
namespace MyMod
 
{
 
{
     public class MyModMail : IAssetEditor
+
     internal sealed class ModEntry: Mod
 
     {
 
     {
         public MyModMail()
+
         public override void Entry(IModHelper helper)
 
         {
 
         {
 +
            helper.Events.Content.AssetRequested += this.OnAssetRequested;
 
         }
 
         }
 
+
       
         public bool CanEdit<T>(IAssetInfo asset)
+
         private void OnAssetRequested(object? sender, AssetRequestedEventArgs e)
 
         {
 
         {
             return asset.AssetNameEquals("Data\\mail");
+
             if (e.NameWithoutLocale.IsEquivalentTo("Data/mail"))
 +
            {
 +
                e.Edit(this.EditImpl);
 +
            }
 
         }
 
         }
   −
         public void Edit<T>(IAssetData asset)
+
         public void EditImpl(IAssetData asset)
 
         {
 
         {
 
             var data = asset.AsDictionary<string, string>().Data;
 
             var data = asset.AsDictionary<string, string>().Data;
Line 322: Line 314:     
===Send a letter (using static content)===
 
===Send a letter (using static content)===
  −
To make uses of this class in your own project, thereby making the static mail data available, hook into the OnGameLaunch event, for example.
  −
  −
<syntaxhighlight lang="c#">
  −
    /// <summary>
  −
    /// Fires after game is launched, right before first update tick. Happens once per game session (unrelated to loading saves).
  −
    /// All mods are loaded and initialized at this point, so this is a good time to set up mod integrations.
  −
    /// </summary>
  −
    private void OnGameLaunched(object sender, GameLaunchedEventArgs e)
  −
    {
  −
        Helper.Content.AssetEditors.Add(new MyModMail());
  −
    }
  −
</syntaxhighlight>
      
Now that you have your letter loaded, it's time to send it to the player.  There are a couple different methods available to accomplish this as well, depending on your need.  Two examples are shown below.  The distinction between the two methods will be explained below:
 
Now that you have your letter loaded, it's time to send it to the player.  There are a couple different methods available to accomplish this as well, depending on your need.  Two examples are shown below.  The distinction between the two methods will be explained below:
Line 369: Line 348:  
namespace MyMail
 
namespace MyMail
 
{
 
{
     public class MailData : IAssetEditor
+
     internal sealed class ModEntry: Mod
 
     {
 
     {
 
         // This collection holds any letters loaded after the initial load or last cache refresh
 
         // This collection holds any letters loaded after the initial load or last cache refresh
         private Dictionary<string, string> dynamicMail = new Dictionary<string, string>();
+
         private Dictionary<string, string> dynamicMail = new();
 
+
     
         public MailData()
+
         public override void Entry(IModHelper helper)
 
         {
 
         {
 +
            helper.Events.Content.AssetRequested += this.OnAssetRequested;
 
         }
 
         }
   −
         public bool CanEdit<T>(IAssetInfo asset)
+
         private void OnAssetRequested(object? sender, AssetRequestedEventArgs e)
 
         {
 
         {
            return asset.AssetNameEquals("Data\\mail");
+
            if (e.NameWithoutLocale.IsEquivalentTo("Data/mail"))
 +
                e.Edit(this.EditImpl);
 
         }
 
         }
   −
         public void Edit<T>(IAssetData asset)
+
         public void EditImpl(IAssetData asset)
 
         {
 
         {
 
             var data = asset.AsDictionary<string, string>().Data;
 
             var data = asset.AsDictionary<string, string>().Data;
Line 409: Line 390:  
             if (!string.IsNullOrEmpty(mailId))
 
             if (!string.IsNullOrEmpty(mailId))
 
             {
 
             {
                 if (dynamicMail.ContainsKey(mailId))
+
                 dynamicMail[mailId] = mailText;
                    dynamicMail[mailId] = mailText;
  −
                else
  −
                    dynamicMail.Add(mailId, mailText);
   
             }
 
             }
 
         }
 
         }
Line 422: Line 400:  
You will notice that there is really very little difference in the code used for static mail and dynamic mail.  The class that supports dynamic mail has a private dictionary collection for holding on to any mail content waiting to be injected.  It could have been made public to allow mail to be added directly into the collection, but that is not good practice.  Instead a public Add method was provided so that mail could be sent, so to speak, to the collection.  This code is for a specific MOD, not a robust framework, so it isn't overly concerned with error handling. You can improve that based on your needs.
 
You will notice that there is really very little difference in the code used for static mail and dynamic mail.  The class that supports dynamic mail has a private dictionary collection for holding on to any mail content waiting to be injected.  It could have been made public to allow mail to be added directly into the collection, but that is not good practice.  Instead a public Add method was provided so that mail could be sent, so to speak, to the collection.  This code is for a specific MOD, not a robust framework, so it isn't overly concerned with error handling. You can improve that based on your needs.
   −
Notice the additional code in the Edit method, where any mail in the dynamicMail collection is injected into Stardew Valley's content.  There will be no mail in the dynamicMail collection when the MOD is loaded (in this case) the first time.  If you add mail after the original load, then the content will have to be reloaded by invalidating the cache.  Refer to [[Modding:Modder_Guide/APIs/Content#Cache invalidation|Cache invalidation]] for more details.
+
Notice the additional code in the Edit method, where any mail in the dynamicMail collection is injected into Stardew Valley's content.  There will be no mail in the dynamicMail collection when the MOD is loaded (in this case) the first time.  If you add mail after the original load, then the content will have to be reloaded by invalidating the cache.  Refer to [[Modding:Modder Guide/APIs/Content#Cache invalidation|Cache invalidation]] for more details.
    
===Send a letter (using dynamic content)===
 
===Send a letter (using dynamic content)===
  −
To make uses of this class in your own project, thereby making the dynamic mail available, hook into the OnGameLaunch event, for example.
  −
  −
<syntaxhighlight lang="c#">
  −
    // Make this available to other methods in the class to access
  −
    private MailData mailData = new MailData();
  −
  −
    /// <summary>
  −
    /// Fires after game is launched, right before first update tick. Happens once per game session (unrelated to loading saves).
  −
    /// All mods are loaded and initialized at this point, so this is a good time to set up mod integrations.
  −
    /// </summary>
  −
    private void OnGameLaunched(object sender, GameLaunchedEventArgs e)
  −
    {
  −
        Helper.Content.AssetEditors.Add(mailData);
  −
    }
  −
</syntaxhighlight>
      
You can hook into other events, such as "Day Starting" or "Day Ending" to generate the letter you want to send.  Consider this simple example, that is only for illustration purposes.
 
You can hook into other events, such as "Day Starting" or "Day Ending" to generate the letter you want to send.  Consider this simple example, that is only for illustration purposes.
Line 453: Line 415:  
         Game1.mailbox.Add("MyModMailWool");              // Add to mailbox and we don't need to track it
 
         Game1.mailbox.Add("MyModMailWool");              // Add to mailbox and we don't need to track it
   −
         modHelper.Content.InvalidateCache("Data\\mail"); // (modHelper was assigned in ModEntry for use throughout the class)
+
         this.Helper.GameContent.InvalidateCache("Data\\mail"); // note that as of SMAPI 3.14.0, this only invalidates the English version of the asset.
 
     }
 
     }
 
</syntaxhighlight>
 
</syntaxhighlight>
106,033

edits