Changes

Jump to navigation Jump to search
1,564 bytes removed ,  19:22, 29 July 2023
no edit summary
Line 26: Line 26:     
===Spawn an item on the ground===
 
===Spawn an item on the ground===
 +
 +
You can spawn an item on the ground with the <samp>[[Modding:Modder_Guide/Game_Fundamentals#GameLocation_et_al|GameLocation]]</samp> class's <samp>dropObject</samp> method:
    
<syntaxhighlight lang='c#'>
 
<syntaxhighlight lang='c#'>
Line 61: Line 63:     
===Get all locations===
 
===Get all locations===
The list of root locations is stored in <tt>Game1.locations</tt>, but constructed building interiors aren't included. This method provides all locations in the game for the main player:
+
The list of root locations is stored in <samp>Game1.locations</samp>, but constructed building interiors aren't included. Instead, use the method <samp>Utility.ForAllLocations</samp>
 
<syntaxhighlight lang="c#">
 
<syntaxhighlight lang="c#">
/// <summary>Get all game locations.</summary>
+
Utility.ForAllLocations((GameLocation location) =>
public static IEnumerable<GameLocation> GetLocations()
   
{
 
{
    return Game1.locations
+
   // do things with location here.
        .Concat(
+
});
            from location in Game1.locations.OfType<BuildableGameLocation>()
  −
            from building in location.buildings
  −
            where building.indoors.Value != null
  −
            select building.indoors.Value
  −
        );
  −
}
  −
</syntaxhighlight>
  −
 
  −
Then you can use it to iterate all locations:
  −
<syntaxhighlight lang="c#">
  −
foreach (GameLocation location in this.GetLocations())
  −
{
  −
   // ...
  −
}
   
</syntaxhighlight>
 
</syntaxhighlight>
   −
Note that farmhands in multiplayer can't see all locations; see [[Modding:Modder Guide/APIs/Multiplayer#GetActiveLocations|<tt>GetActiveLocations</tt>]] instead.
+
Note that farmhands in multiplayer can't see all locations; see [[Modding:Modder Guide/APIs/Multiplayer#GetActiveLocations|<samp>GetActiveLocations</samp>]] instead.
    
===Edit a location map===
 
===Edit a location map===
 
See [[Modding:Maps]].
 
See [[Modding:Maps]].
  −
==Player==
  −
//todo describe section
  −
  −
===Custom Sprite===
  −
You will be needing:
  −
  −
-XNB extractor, from this link: "www.mediafire.com/file/4c0g0fk3tza384o/XNBExtract.zip"
  −
  −
-Image editor. I strongly recommend PS or another program that lets you edit the spritesheets in 8-bit mode.
  −
  −
-Farmer XNB. You'll find them here: ...StardewValley\Content\Characters\Farmer
  −
  −
-Multiplayer fix, so it doesn't make your friend's game crash.
  −
  −
So, what you'll be doing is extract the XNB you want to modify (mostly the hairstyles and the clothes) with the XNB extractor. Once there, follow the readme in the extractor.
  −
  −
As far as we know, you cannot "add" new hairstyles or clothes, but you can modify the ones there. So what you are going to do is select one line of three views of a hairstyle and one line of four views of clothing. There, you'll change them as you wish.
  −
  −
A point here. If you want to make a character like, let's say, Master Chief, you'll get one line of clothing, and draw it as Master Chief's armor. Then get one line of a kind of hairstyle, and make the first one (the one that shows when the character is facing south) and cover it completely as the helmet would do, then finish the other two views of the helmet. We say this because it's infinitely easier than going into the farmer_base spritesheet and changing one by one the faces. For characters without helmet, you'll either have to change the faces manually in the spritesheet or try to go around with the in-game creator. You can change the skin color and the boots too, accessing them in-game with the mod that enables the multiplayer fix and let's you change your clothes anytime.
      
===Position===
 
===Position===
Line 152: 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:
  −
 
  −
<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#'>
+
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)
Game1.getLocationFromName("Town").addCharacter(npc);
  −
</syntaxhighlight>
      
==User-interface (UI)==
 
==User-interface (UI)==
Line 199: 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 218: Line 173:  
Note: For those of you who want a custom HUDMessage:  
 
Note: For those of you who want a custom HUDMessage:  
 
- Almost all of these variables are public, excluding messageSubject, so feel free to customize!
 
- Almost all of these variables are public, excluding messageSubject, so feel free to customize!
 
+
To modify them make a HUDMessage variable like so : HUDMessage <name> = new HUDMessage(<message>) ,now you can even animate them with code!
 
      
For example: add a new HUDMessage to show [[File:Error-image-ingame.png]] toaster popup.  
 
For example: add a new HUDMessage to show [[File:Error-image-ingame.png]] toaster popup.  
 
<syntaxhighlight lang="c#">
 
<syntaxhighlight lang="c#">
 
Game1.addHUDMessage(new HUDMessage("MESSAGE", 3));
 
Game1.addHUDMessage(new HUDMessage("MESSAGE", 3));
 +
</syntaxhighlight>
 +
 +
Another example: add a HUDMessage that shows up like a simple rectangle with no icon, and no square for the icon:
 +
<syntaxhighlight lang="c#">
 +
Game1.addHUDMessage(new HUDMessage("MESSAGE", ""));  // second parameter is the 'leaveMeNull' parameter
 
</syntaxhighlight>
 
</syntaxhighlight>
   Line 252: Line 211:  
</syntaxhighlight>
 
</syntaxhighlight>
   −
To create a custom menu, you need to create a subclass of <tt>IClickableMenu</tt> and assign it to <tt>Game1.activeClickableMenu</tt>. At its most basic, a menu is basically just a few methods you override (usually <tt>draw</tt> and <tt>receiveLeftClick</tt> at a minimum). When <tt>draw</tt> is called, you draw whatever you want to the screen; when <tt>receiveLeftClick</tt> is called, you check if it's within one of the clickable areas and handle it. Normally you'd use some convenience classes like <tt>ClickableTextureButton</tt> (which has a texture and position, and simplifies checking if they were clicked), though that's not strictly necessary. Here's [https://github.com/janavarro95/Stardew_Valley_Mods/blob/master/GeneralMods/HappyBirthday/Framework/BirthdayMenu.cs a simple menu] you can use as an example, which draws the birthday menu for {{nexus mod|520|Birthday Mod}}.
+
To create a custom menu, you need to create a subclass of <samp>IClickableMenu</samp> and assign it to <samp>Game1.activeClickableMenu</samp>. At its most basic, a menu is basically just a few methods you override (usually <samp>draw</samp> and <samp>receiveLeftClick</samp> at a minimum). When <samp>draw</samp> is called, you draw whatever you want to the screen; when <samp>receiveLeftClick</samp> is called, you check if it's within one of the clickable areas and handle it. Normally you'd use some convenience classes like <samp>ClickableTextureButton</samp> (which has a texture and position, and simplifies checking if they were clicked), though that's not strictly necessary. Here's [https://github.com/janavarro95/Stardew_Valley_Mods/blob/d7d567a72f5feaf3a5a5afd3d054ac9598727a97/GeneralMods/HappyBirthday/Framework/Menus/BirthdayMenu.cs a simple menu] you can use as an example, which draws the birthday menu for {{nexus mod|520|Birthday Mod}}.
    
===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 270: Line 229:  
</syntaxhighlight>
 
</syntaxhighlight>
   −
// TODO: Examples with choices
+
// TODO: More examples with choices
    +
To utilise options, you are better off using createQuestionDialogue.
 +
 +
<syntaxhighlight lang="c#">
 +
private void SampleClick()
 +
{
 +
    // List of choices to give the farmer.
 +
    List<Response> choices = new List<Response>()
 +
            {
 +
                new Response("dialogue_id1","Choice 1" ),
 +
                new Response("dialogue_id2", "Choice 2"),
 +
                new Response("dialogue_id3", "Choice 3"),
 +
                new Response("dialogue_id4", "Choice 4")
 +
            };
 +
 +
    // And here we case it to pop up on the farmer's screen. When the farmer has picked a choice, it sends that information to the method below (DialogueSet
 +
    Game1.currentLocation.createQuestionDialogue($"What is the question?", choices.ToArray(), new GameLocation.afterQuestionBehavior(DialogueSet));
 +
}
 +
 +
public void DialogueSet(Farmer who, string dialogue_id)
 +
{
 +
// Here you get which option was picked as dialogue_id.
 +
Game1.addHUDMessage(new HUDMessage($"Farmer {who} chose option {dialogue_id}"));
 +
   
 +
}
 +
</syntaxhighlight>
    
==Mail==
 
==Mail==
Line 284: Line 268:  
===Inject static content===
 
===Inject static content===
   −
Most times a static, predefined letter will suffice, whether you are including an attachment (i.e. object, money, etc.) or not.  "Static" simply means you do not need to change the text once it is typed before sending the letter.  A "static" letter will always be available in the game (unless you remove it from the mod or the mod is removed by the player) so that means the letter is still available if the player quits with your letter still in the mailbox and then returns to play later.  This can be an issue with "dynamic" letters, as explained in more detail in that section, so use "static" content whenever possible.
+
Most times a static, predefined letter will suffice, whether you are including an attachment (''i.e.,'' object, money, etc.) or not.  "Static" simply means you do not need to change the text once it is typed before sending the letter.  A "static" letter will always be available in the game (unless you remove it from the mod or the mod is removed by the player) so that means the letter is still available if the player quits with your letter still in the mailbox and then returns to play later.  This can be an issue with "dynamic" letters, as explained in more detail in that section, so use "static" content whenever possible.
    
You can softly reference the player's name, using "@", but other replace codes that may work in dialog texts, like %pet or %farm, do not work in static mail content at this time.  However, you can make use of some special characters that display an icon in the letter, such as "=", which will display a purple star, "<", which will display a pink heart, the "$", which will be replaced with a gold coin, the ">", which will display a right arrow, the "`", which will display an up arrow, and the "+", which will display a head riding a skateboard (maybe?).  There may be additional special cases as well that are not yet documented.
 
You can softly reference the player's name, using "@", but other replace codes that may work in dialog texts, like %pet or %farm, do not work in static mail content at this time.  However, you can make use of some special characters that display an icon in the letter, such as "=", which will display a purple star, "<", which will display a pink heart, the "$", which will be replaced with a gold coin, the ">", which will display a right arrow, the "`", which will display an up arrow, and the "+", which will display a head riding a skateboard (maybe?).  There may be additional special cases as well that are not yet documented.
Line 295: 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;
    
             // "MyModMail1" is referred to as the mail Id.  It is how you will uniquely identify and reference your mail.
 
             // "MyModMail1" is referred to as the mail Id.  It is how you will uniquely identify and reference your mail.
             // The @ will be replaced with the player's name.  Other items do not seem to work (i.e. %pet or %farm)
+
             // The @ will be replaced with the player's name.  Other items do not seem to work (''i.e.,'' %pet or %farm)
 
             // %item object 388 50 %%  - this adds 50 pieces of wood when added to the end of a letter.
 
             // %item object 388 50 %%  - this adds 50 pieces of wood when added to the end of a letter.
 +
            // %item tools Axe Hoe %%  - this adds tools; may list any of Axe, Hoe, Can, Scythe, and Pickaxe
 
             // %item money 250 601  %%  - this sends a random amount of gold from 250 to 601 inclusive.
 
             // %item money 250 601  %%  - this sends a random amount of gold from 250 to 601 inclusive.
             // %item cookingRecipe %%  - this is for recipes (did not try myself)  Not sure how it know which recipe.  
+
             // For more details, see: https://stardewvalleywiki.com/Modding:Mail_data
 
             data["MyModMail1"] = "Hello @... ^A single carat is a new line ^^Two carats will double space.";
 
             data["MyModMail1"] = "Hello @... ^A single carat is a new line ^^Two carats will double space.";
 
             data["MyModMail2"] = "This is how you send an existing item via email! %item object 388 50 %%";
 
             data["MyModMail2"] = "This is how you send an existing item via email! %item object 388 50 %%";
Line 325: 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 346: Line 322:  
</syntaxhighlight>
 
</syntaxhighlight>
   −
The first method (Game1.player.mailbox.Add) adds the letter directly into the mailbox for the current day.  This can be accomplished in your "DayStaring" event code, for example.  Mail added directly to the mailbox is not "remembered" as being sent even after a save.  This is useful in some scenarios depending on your need.
+
The first method (Game1.player.mailbox.Add) adds the letter directly into the mailbox for the current day.  This can be accomplished in your "DayStarting" event code, for example.  Mail added directly to the mailbox is not "remembered" as being sent even after a save.  This is useful in some scenarios depending on your need.
    
The second method (Game1.addMailForTomorrow) will, as the name implies, add the letter to the player's mailbox on the next day.  This method remembers the mail (Id) sent making it possible not to send the same letter over and over.  This can be handled in "DayStaring", "DayEnding" or other events, as dictated by your need.
 
The second method (Game1.addMailForTomorrow) will, as the name implies, add the letter to the player's mailbox on the next day.  This method remembers the mail (Id) sent making it possible not to send the same letter over and over.  This can be handled in "DayStaring", "DayEnding" or other events, as dictated by your need.
Line 372: 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 412: 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 425: 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 456: 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>
Line 477: Line 436:  
location.playSound("SOUND");  
 
location.playSound("SOUND");  
 
</syntaxhighlight>
 
</syntaxhighlight>
(e.g. "junimoMeep1")
+
(''e.g.,'' "junimoMeep1")
    
==Open source==
 
==Open source==
Line 485: Line 444:     
[[ru:Модификации:Основные возможности]]
 
[[ru:Модификации:Основные возможности]]
 +
[[zh:模组:常用方法]]
106,039

edits

Navigation menu