「模组:常用方法」修訂間的差異

出自Stardew Valley Wiki
跳至導覽 跳至搜尋
(更新common tasks的翻译)
第243行: 第243行:
  
 
==邮件 (Mail)==
 
==邮件 (Mail)==
// 未翻译
+
  如果您不熟悉 SMAPI 或一般对 Stardew Valley 进行改装,向玩家的邮箱发送一封简单的信件是开始学习之旅的好地方。 您将接触到一些简单易懂的代码和概念,并以有形的游戏内信件的形式获得一些即时的满足,您可以在实际操作中看到这些信件。 如果本节中的示例不足,有很多人可以在 Discord 频道上为您提供帮助。
 
===概述===
 
===概述===
// 未翻译
+
  在您实际向播放器发送任何您自己的自定义邮件之前,您必须决定您的信件将如何撰写。 我的意思是,您的信件是静态的 - 总是相同的文本 - 还是动态的 - 文本会根据可变信息而变化? 显然,静态信件更容易实现,所以如果你刚刚开始,现在就走这条路。 然而,静态和动态方法都在下面解释。
 +
 
 +
要发送静态或动态邮件,您首先必须让 Stardew Valley 知道您的内容,也称为Aeest。 对于邮件,您必须将添加内容注入邮件数据。 您可以通过 IAssetEditor 界面完成此操作。 您可以从 ModEntry 类实现 IAssetEditor,或者创建一个单独的类来实现 IAssetEditor 以将新邮件内容注入“Data\Mail.xnb”。 为了清楚起见、易于重用和封装,下面引用的示例使用后一种方法:
 
===注入静态内容 (Inject static content)===
 
===注入静态内容 (Inject static content)===
//  未翻译
+
大多数情况下,一个静态的、预定义的信件就足够了,无论您是否包含附件(即对象、金钱等)。 “静态”只是意味着您不需要在发送信件之前输入文本后更改文本。 “静态”信件在游戏中始终可用(除非您将其从模组中移除或模组被玩家移除),这意味着如果玩家退出且您的信件仍在邮箱中,则该信件仍然可用稍后回来玩。这可能是“动态”字母的问题,如该部分更详细的解释,因此请尽可能使用“静态”内容。
 +
 
 +
您可以使用“@”轻轻地引用播放器的名称,但其他可能在对话文本中起作用的替换代码,如 %pet 或 %farm,目前在静态邮件内容中不起作用。但是,您可以使用一些在字母中显示图标的特殊字符,例如“=”,将显示一个紫色的星星,“<”,将显示一个粉红色的心形,“$”,将显示替换为金币,“>”将显示向右箭头,“`”将显示向上箭头,“+”将显示骑着滑板的头(也许?)。可能还有其他尚未记录的特殊情况。
 +
 
 +
下面的示例将 4 个字母添加到邮件数据集合中。请注意,下面的代码不会向玩家发送任何信件,而只是将它们提供给 Stardew Valley 游戏,以便它们可以发送。
 +
<syntaxhighlight lang="c#">
 +
using StardewModdingAPI;
 +
 
 +
namespace MyMod
 +
{
 +
   public class MyModMail : IAssetEditor
 +
   {
 +
     public MyModMail()
 +
     {
 +
     }
 +
 
 +
     public bool CanEdit<T>(IAssetInfo asset)
 +
     {
 +
       return asset.AssetNameEquals("Data\\mail");
 +
     }
 +
 
 +
     public void Edit<T>(IAssetData asset)
 +
     {
 +
       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.
 +
       // 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 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.
 +
       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["MyModMail3"] = "Coin $  Star =  Heart <  Dude + Right Arrow >  Up Arrow `";
 +
       data["MyWizardMail"] = "Include Wizard in the mail Id to use the special background on a letter";
 +
     }
 +
   }
 +
}
 +
</syntaxhighlight>
 +
 
 
===客户端调用静态内容 (Send a letter with static content)===
 
===客户端调用静态内容 (Send a letter with static content)===
//  未翻译
+
例如,要在您自己的项目中使用此类,从而使静态邮件数据可用,请挂入 OnGameLaunch 事件。
 +
 
 +
 
 +
<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:
 +
 
 +
<syntaxhighlight lang="c#">
 +
   Game1.player.mailbox.Add("MyModMail1");
 +
   Game1.addMailForTomorrow("MyModMail2");
 +
</syntaxhighlight>
 +
 
 +
第一种方法(Game1.player.mailbox.Add)将信件直接添加到当天的邮箱中。例如,这可以在您的“DayStaring”事件代码中完成。即使在保存后,直接添加到邮箱的邮件也不会被“记住”为已发送。根据您的需要,这在某些情况下很有用。
 +
 
 +
第二种方法(Game1.addMailForTomorrow),顾名思义,会在第二天将信件添加到玩家的邮箱中。此方法会记住发送的邮件 (Id),从而可以不一遍又一遍地发送相同的信件。这可以根据您的需要在“DayStaring”、“DayEnding”或其他事件中处理。
 +
 
 +
您可以将信件直接放入邮箱,并使用 mailRecieved 集合记住它。如果您希望在使用直接添加到邮箱方法时记住它,您可以简单地手动添加您的 mailId。
 +
 
 +
如果您希望 Stardew Valley 忘记已经发送了特定的信件,您可以将其从 mailReceived 集合中删除。如果您需要批量删除邮件,您也可以使用 foreach 遍历集合。
 +
 
 +
这就是发送一封简单的信件的全部内容。 附上物品和通过信件寄钱很简单,但发送食谱更复杂,以后需要一些额外的解释。
 
===注入动态内容 (Inject dynamic content)===
 
===注入动态内容 (Inject dynamic content)===
//  未翻译
+
如果您想发送一封包含需要根据情况更改的数据的信件,例如今天吃的紫色蘑菇的数量,那么您必须在每次计划发送时创建该信件内容,特别是如果您想要一个 最新值。 这就是我所说的“动态”字母。
 +
 
 +
考虑以下源代码,它基本上是上述静态邮件类的增强版本,也将支持“动态”内容。 您当然可以始终使用此代码的增强版本,除非需要,否则不要使用动态内容。 代码被分开是为了说明差异。
 +
 
 +
<syntaxhighlight lang="c#">
 +
using StardewModdingAPI;
 +
using System.Collections.Generic;
 +
 
 +
namespace MyMail
 +
{
 +
   public class MailData : IAssetEditor
 +
   {
 +
     // This collection holds any letters loaded after the initial load or last cache refresh
 +
     private Dictionary<string, string> dynamicMail = new Dictionary<string, string>();
 +
 
 +
     public MailData()
 +
     {
 +
     }
 +
 
 +
     public bool CanEdit<T>(IAssetInfo asset)
 +
     {
 +
       return asset.AssetNameEquals("Data\\mail");
 +
     }
 +
 
 +
     public void Edit<T>(IAssetData asset)
 +
     {
 +
       var data = asset.AsDictionary<string, string>().Data;
 +
 
 +
       // This is just an example
 +
       data["StaticMail"] = "If there were any letters with static content they could be placed here.";
 +
 
 +
       // Inject any mail that was added after the initial load.
 +
       foreach (var item in dynamicMail)
 +
       {
 +
         data.Add(item);
 +
       }
 +
 
 +
       dynamicMail.Clear();   // For the usage of this MOD the letters are cleared
 +
     }
 +
 
 +
     /// <summary>
 +
     /// Add a new mail asset into the collection so it can be injected by the next cache refresh.  The letter will
 +
     /// not be available to send until the cache is invalidated in the code.
 +
     /// </summary>
 +
     /// <param name="mailId">The mail key</param>
 +
     /// <param name="mailText">The mail text</param>
 +
     public void Add(string mailId, string mailText)
 +
     {
 +
       if (!string.IsNullOrEmpty(mailId))
 +
       {
 +
         if (dynamicMail.ContainsKey(mailId))
 +
           dynamicMail[mailId] = mailText;
 +
         else
 +
           dynamicMail.Add(mailId, mailText);
 +
       }
 +
     }
 +
   }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
您会注意到用于静态邮件和动态邮件的代码实际上几乎没有区别。支持动态邮件的类有一个私有字典集合,用于保存等待注入的任何邮件内容。本来可以公开以允许将邮件直接添加到集合中,但这不是一个好的做法。相反,提供了一个公共 Add 方法,以便可以将邮件发送到集合。此代码适用于特定的 MOD,而不是健壮的框架,因此不会过分关注错误处理。您可以根据自己的需要进行改进。
 +
 
 +
请注意 Edit 方法中的附加代码,其中 dynamicMail 集合中的任何邮件都被注入到 Stardew Valley 的内容中。第一次加载MOD时(在这种情况下),dynamicMail集合中将没有邮件。如果在原始加载后添加邮件,则必须通过使缓存无效来重新加载内容。更多细节请参考[[Modding:Modder_Guide/APIs/Content#Cache invalidation|Cache invalidation]]。
 +
 
 
===客户端调用动态内容 (Send a letter with dynamic content)===
 
===客户端调用动态内容 (Send a letter with dynamic content)===
// 未 翻译
+
例如,要在您自己的项目中使用此类,从而使动态邮件可用,请挂入 OnGameLaunch 事件。
 +
 
 +
<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>
 +
 
 +
您可以连接到其他事件,例如“开始日期”或“结束日期”以生成要发送的信件。 考虑这个简单的例子,它仅用于说明目的。
 +
 
 +
<syntaxhighlight lang="c#">
 +
   private void OnDayStarting(object sender, DayStartedEventArgs e)
 +
   {
 +
     string mailMessage = $"@, you have gathered {Game1.stats.rabbitWoolProduced} units of rabbit wool!";
 +
 
 +
     mailData.Add("MyModMailWool", mailMessage);    // Add this new letter to the mail collection (for next refresh).
 +
 
 +
     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)
 +
   }
 +
</syntaxhighlight>
 +
 
 +
此示例格式化一封信,显示最新的兔毛计数,使其可用于邮件收集,将该信放入邮箱,然后使缓存无效,以便在缓存刷新期间注入此新信.在这种情况下,不需要记住 mailId,因为每次需要发送信件时都会重新创建该信件,在本例中是每天。同样,此代码仅用于说明该概念。
 +
 
 +
在以这种简单的方式注入邮件时,需要了解一个重要的警告。可用的各种邮件框架处理这个问题,本节将被扩展以解释如何解决这个问题,但在此处进行介绍是为了确保您完全了解 MOD 如何与 Stardew Valley 和 SMAPI 一起工作。
 +
 
 +
如果添加动态信件并在Day Ending 的内容中注入它,显然您必须添加明天显示的邮件。这意味着游戏将通过对邮箱中待处理的动态信件(在本例中为“MyMailModWool”)的引用来保存。如果玩家此时退出游戏并稍后返回继续玩,则该动态字母不可用,从而导致“幻影字母”。邮箱将显示一封可用的信件,但单击时不会显示任何内容。这可以通过多种方式处理,包括保存自定义字母并在玩家继续时加载它们,但同样,此示例代码尚 涵盖这一点。这就是该示例使用 On Day Start 并立即使信件可用的原因。
  
 
==其他 (Other)==
 
==其他 (Other)==

於 2021年8月10日 (二) 09:22 的修訂

目錄

Robin building.png
“我這裏還有很多事情需要處理。”
— 羅賓

不完整的翻譯

本文或部分尚未完全翻譯成中文。 歡迎您通過編輯幫助其建設。
最後編輯ChenNiuniu於2021-08-10 09:22:55.

此頁面展示了製作SMAPI模組時常用功能的構建方法。在閱讀時,請結合參考模組製作入門遊戲基本架構

基礎技巧

追蹤一個值的變化

在寫模組時,你可能常常需要了解一個值的變化(什麼時候變,變化前後的值分別是多少,等等)。如果該值沒有包括在SMAPI內置的事件 (event)中,那麼你可以為該值創建一個私有變量,然後在SMAPI的update tick事件中刷新此變量,以達到追蹤值變化的目的。

示例見:模組:製作指南/APIs/Events#變化監控

物品 (Items)

物品 代表那些能夠放在背包里的東西,比如說工具、農作物等等。

創建一個物品 (Object) 的實例

Object中所有的構造函數:

 public Object(Vector2 tileLocation, int parentSheetIndex, int initialStack);
 public Object(Vector2 tileLocation, int parentSheetIndex, bool isRecipe = false);
 public Object(int parentSheetIndex, int initialStack, bool isRecipe = false, int price = -1, int quality = 0);
 public Object(Vector2 tileLocation, int parentSheetIndex, string Givenname, bool canBeSetDown, bool canBeGrabbed, bool isHoedirt, bool isSpawnedObject);

參數parentSheetIndex表示該物品的ID(儲存在 ObjectInformation.xnb 文件中)。

在地上生成物品

public virtual bool dropObject(Object obj, Vector2 dropLocation, xTile.Dimensions.Rectangle viewport, bool initialPlacement, Farmer who = null);

 // 调用:
 Game1.getLocationFromName("Farm").dropObject(new StardewValley.Object(itemId, 1, false, -1, 0), new Vector2(x, y) * 64f, Game1.viewport, true, (Farmer)null);

添加物品到背包 (Inventory)

// You can add items found in ObjectInformation using:
    Game1.player.addItemByMenuIfNecessary((Item)new StardewValley.Object(int parentSheetIndex, int initialStack, [bool isRecipe = false], [int price = -1], [int quality = 0]));

例2:

// Add a weapon directly into player's inventory
    const int WEAP_ID = 19;                  // Shadow Dagger -- see Data/weapons
    Item weapon = new MeleeWeapon(WEAP_ID);  // MeleeWeapon is a class in StardewValley.Tools
    Game1.player.addItemByMenuIfNecessary(weapon);

    // Note: This code WORKS.

從背包移除物品

取決於你背包的具體情況。很少有情況需要你親自來調用,因為相關的方法在Farmer類中已經有了。

在大多數情況下,僅需調用 .removeItemFromInventory(Item) 方法。

地點 (Locations)

遊戲基本架構#地點

獲取所有地點

Game1.locations屬性中雖然儲存着主要的地點,但是不包括建築的室內(constructed building interiors)。以下這個方法提供了主玩家的所有地點。

/// <summary>Get all game locations.</summary>
public static IEnumerable<GameLocation> GetLocations()
{
    return Game1.locations
        .Concat(
            from location in Game1.locations.OfType<BuildableGameLocation>()
            from building in location.buildings
            where building.indoors.Value != null
            select building.indoors.Value
        );
}

遍歷:

foreach (GameLocation location in this.GetLocations())
{
   // ...
}

注意:在聯機模式中,客機是拿不到上述所有地點的。要解決這一問題,見獲取有效的地點

編輯地圖

模組:地圖數據

玩家 (Player)

自定義貼圖 (Custom Sprite)

位置 (Position)

角色(Character) 的位置(Position) 表示他在當前地點(Location) 的坐標。

相對於地圖 (Map)

每個地點(location) 都有一個對應的xTile地圖(map)。如果以像素(pixel) 為單位,地圖左上角坐標代表(0, 0),坐下角則代表(location.Map.DisplayWidth, location.Map.DisplayHeight)。 角色在當前地點的位置有兩種表達方式:

  • 以像素(pixel) 為單位的絕對(absoulte) 坐標:Position.XPosition.Y
  • 以圖塊(tile) 為單位的圖塊(tile) 坐標:getTileX()getTileY()

常量Game1.tileSize規定,遊戲內每個圖塊(tile) 大小為64x64像素。於是有以下單位換算:

// 绝对坐标 → 图块坐标
Math.Floor(Game1.player.Position.X / Game1.tileSize)
Math.Floor(Game1.player.Position.Y / Game1.tileSize)

// 图块坐标 → 绝对坐标
Game1.player.getTileX() * Game1.tileSize
Game1.player.getTileY() * Game1.tileSize

// 地图大小(以图块为单位)
Math.Floor(Game1.player.currentLocation.Map.DisplayWidth / Game1.tileSize)
Math.Floor(Game1.player.currentLocation.Map.DisplayHeight / Game1.tileSize)

相對於視野 (Viewport)

視野、視口、視窗(Viewport) 代表在當前屏幕上的區域。若以像素計算,其寬高應該與遊戲的屏幕解像度相等,分別為Game1.viewport.WidthGame1.viewport.Height

玩家相對於視野的位置(像素)可表示為:

Game1.player.Position.X - Game1.viewport.X
Game1.player.Position.Y - Game1.viewport.Y

NPC

自定義NPC

想要自定義NPC,你得修改或添加以下文件:

格式: 具體操作(往)目標文件夾或文件名

  • 添加新文件:Characters\Dialogue\<文件名>
  • 添加新文件:Characters\schedules\<文件名>
  • 添加新文件:Portraits\<文件名>
  • 添加新文件:Characters\<文件名>
  • 在已有文件中添加新的條目:Data\EngagementDialogue(可結婚NPC)
  • 在已有文件中添加新的條目:Data\NPCDispositions
  • 在已有文件中添加新的條目:Data\NPCGiftTastes
  • 在已有文件中添加新的條目:Characters\Dialogue\rainy
  • 在已有文件中添加新的條目:Data\animationDescriptions(在行程(schedule) 中增加自定義的動畫)

以上所有的操作都可以通過IAssetLoaders/IAssetEditors或者Content Patcher做到。

最後,你需要調用NPC的構造器來創建實例。

 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);

 // 调用:
 Game1.getLocationFromName("Town").addCharacter(npc);

用戶界面 (UI)

用戶界面(User-interface、UI) 是指一系列有關界面元素(如按鈕、列表框、下拉框等等)以及它們組合起來呈現的畫面(如菜單、HUD等等)。

//TODO:歡迎補充UI方面的內容。

HUD消息

HUD消息是指你屏幕左下角時常彈出來的消息框。以下是它的構造函數(不包括一部分無關的):

 public HUDMessage(string message);
 public HUDMessage(string message, int whatType);
 public HUDMessage(string type, int number, bool add, Color color, Item messageSubject = null);
 public HUDMessage(string message, string leaveMeNull)
 public HUDMessage(string message, Color color, float timeLeft, bool fadeIn)
樣式一覽

可選的樣式(type):

  1. 成就 HUDMessage.achievement_type
  2. 新任務 HUDMessage.newQuest_type
  3. 錯誤 HUDMessage.error_type
  4. 體力值 HUDMessage.stamina_type
  5. 生命值 HUDMessage.health_type

顏色(color):

第1、2個構造器並沒有給出表示顏色的參數,此時顏色默認為Color.OrangeRed。 若使用第4個構造器,顏色則與遊戲內文字顏色一樣。

特別:

  • public HUDMessage(string type, int number, bool add, Color color, Item messageSubject = null); 支持消息內容擴展。常用於金錢相關。
  • public HUDMessage(string message, string leaveMeNull); 左側沒有圖標框。
  • public HUDMessage(string message, Color color, float timeLeft, bool fadeIn); 文字漸入效果。


示例1: 彈出一個帶有Error-image-ingame.png的消息框。

 Game1.addHUDMessage(new HUDMessage("MESSAGE", 3));

示例2: 彈出一個純文字的消息框。

 Game1.addHUDMessage(new HUDMessage("MESSAGE", ""));  // second parameter is the 'leaveMeNull' parameter

菜單 (Active clickable menu)

菜單指繪製於最頂層的UI,能夠接受用戶輸入。比方說,當你按下ESCB鍵時呈現的GameMenu就是一個菜單。 菜單的值儲存在Game1.activeClickableMenu,當該字段不為null時,其值便能呈現出一個菜單了。

每個菜單不盡相同,請閱讀代碼來了解其運作方式。你可能經常需要了解GameMenu的當前欄目 (tab),這是一個示例:

if (Game1.activeClickableMenu is GameMenu menu)
{
  // 获取栏目页
  IList<IClickableMenu> pages = this.Helper.Reflection.GetField<List<IClickableMenu>>(menu, "pages").GetValue();

  // 方法1:比较栏目的ID
  if (menu.currentTab == GameMenu.mapTab)
  {
     ...
  }

  // 方法2:比较菜单类型
  switch (pages[menu.currentTab])
  {
    case MapPage mapPage:
       ...
       break;
  }
}

如果你想要自定義菜單,請繼承自IClickableMenu,將對象分配給Game1.activeClickableMenu。 一個菜單基本上重寫了一些方法,如drawreceiveLeftClick 等等。 draw方法繪製屏幕上的元素;receiveLeftClick 方法處理左鍵點擊事件。 你通常可以使用一些遊戲封裝好的類作為菜單元素,如ClickableTextureButton

這裏提供了一個簡單的例子,這是Birthday Mod的菜單。

對話框 (DialogueBox)

不帶選項的對話框示例。

對話框有許多變種,比如有種對話框能夠選擇想要的對話內容。

如果想換行,請輸入"^"。

下面是一種不帶選項的對話框示例:

using StardewValley.Menus;  // 引用DialogueBox类的命名空间

string message = "This looks like a typewriter ... ^But it's not ...^It's a computer.^";
Game1.activeClickableMenu = new DialogueBox(message);

郵件 (Mail)

如果您不熟悉 SMAPI 或一般對 Stardew Valley 進行改裝,向玩家的郵箱發送一封簡單的信件是開始學習之旅的好地方。 您將接觸到一些簡單易懂的代碼和概念,並以有形的遊戲內信件的形式獲得一些即時的滿足,您可以在實際操作中看到這些信件。 如果本節中的示例不足,有很多人可以在 Discord 頻道上為您提供幫助。

概述

在您實際向播放器發送任何您自己的自定義郵件之前,您必須決定您的信件將如何撰寫。 我的意思是,您的信件是靜態的 - 總是相同的文本 - 還是動態的 - 文本會根據可變信息而變化? 顯然,靜態信件更容易實現,所以如果你剛剛開始,現在就走這條路。 然而,靜態和動態方法都在下面解釋。

要發送靜態或動態郵件,您首先必須讓 Stardew Valley 知道您的內容,也稱為Aeest。 對於郵件,您必須將添加內容注入郵件數據。 您可以通過 IAssetEditor 界面完成此操作。 您可以從 ModEntry 類實現 IAssetEditor,或者創建一個單獨的類來實現 IAssetEditor 以將新郵件內容注入「Data\Mail.xnb」。 為了清楚起見、易於重用和封裝,下面引用的示例使用後一種方法:

注入靜態內容 (Inject static content)

大多數情況下,一個靜態的、預定義的信件就足夠了,無論您是否包含附件(即對象、金錢等)。 「靜態」只是意味着您不需要在發送信件之前輸入文本後更改文本。 「靜態」信件在遊戲中始終可用(除非您將其從模組中移除或模組被玩家移除),這意味着如果玩家退出且您的信件仍在郵箱中,則該信件仍然可用稍後回來玩。這可能是「動態」字母的問題,如該部分更詳細的解釋,因此請儘可能使用「靜態」內容。

您可以使用「@」輕輕地引用播放器的名稱,但其他可能在對話文本中起作用的替換代碼,如 %pet 或 %farm,目前在靜態郵件內容中不起作用。但是,您可以使用一些在字母中顯示圖標的特殊字符,例如「=」,將顯示一個紫色的星星,「<」,將顯示一個粉紅色的心形,「$」,將顯示替換為金幣,「>」將顯示向右箭頭,「`」將顯示向上箭頭,「+」將顯示騎着滑板的頭(也許?)。可能還有其他尚未記錄的特殊情況。

下面的示例將 4 個字母添加到郵件數據集合中。請注意,下面的代碼不會向玩家發送任何信件,而只是將它們提供給 Stardew Valley 遊戲,以便它們可以發送。

using StardewModdingAPI;

namespace MyMod
{
    public class MyModMail : IAssetEditor
    {
        public MyModMail()
        {
        }

        public bool CanEdit<T>(IAssetInfo asset)
        {
            return asset.AssetNameEquals("Data\\mail");
        }

        public void Edit<T>(IAssetData asset)
        {
            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.
            // 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 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. 
            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["MyModMail3"] = "Coin $   Star =   Heart <   Dude +  Right Arrow >   Up Arrow `";
            data["MyWizardMail"] = "Include Wizard in the mail Id to use the special background on a letter";
        }
    }
}

客戶端調用靜態內容 (Send a letter with static content)

例如,要在您自己的項目中使用此類,從而使靜態郵件數據可用,請掛入 OnGameLaunch 事件。


    /// <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());
    }

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:

    Game1.player.mailbox.Add("MyModMail1");
    Game1.addMailForTomorrow("MyModMail2");

第一種方法(Game1.player.mailbox.Add)將信件直接添加到當天的郵箱中。例如,這可以在您的「DayStaring」事件代碼中完成。即使在保存後,直接添加到郵箱的郵件也不會被「記住」為已發送。根據您的需要,這在某些情況下很有用。

第二種方法(Game1.addMailForTomorrow),顧名思義,會在第二天將信件添加到玩家的郵箱中。此方法會記住發送的郵件 (Id),從而可以不一遍又一遍地發送相同的信件。這可以根據您的需要在「DayStaring」、「DayEnding」或其他事件中處理。

您可以將信件直接放入郵箱,並使用 mailRecieved 集合記住它。如果您希望在使用直接添加到郵箱方法時記住它,您可以簡單地手動添加您的 mailId。

如果您希望 Stardew Valley 忘記已經發送了特定的信件,您可以將其從 mailReceived 集合中刪除。如果您需要批量刪除郵件,您也可以使用 foreach 遍歷集合。

這就是發送一封簡單的信件的全部內容。 附上物品和通過信件寄錢很簡單,但發送食譜更複雜,以後需要一些額外的解釋。

注入動態內容 (Inject dynamic content)

如果您想發送一封包含需要根據情況更改的數據的信件,例如今天吃的紫色蘑菇的數量,那麼您必須在每次計劃發送時創建該信件內容,特別是如果您想要一個 最新值。 這就是我所說的「動態」字母。

考慮以下原始碼,它基本上是上述靜態郵件類的增強版本,也將支持「動態」內容。 您當然可以始終使用此代碼的增強版本,除非需要,否則不要使用動態內容。 代碼被分開是為了說明差異。

using StardewModdingAPI;
using System.Collections.Generic;

namespace MyMail
{
    public class MailData : IAssetEditor
    {
        // This collection holds any letters loaded after the initial load or last cache refresh
        private Dictionary<string, string> dynamicMail = new Dictionary<string, string>();

        public MailData()
        {
        }

        public bool CanEdit<T>(IAssetInfo asset)
        {
            return asset.AssetNameEquals("Data\\mail");
        }

        public void Edit<T>(IAssetData asset)
        {
            var data = asset.AsDictionary<string, string>().Data;

            // This is just an example
            data["StaticMail"] = "If there were any letters with static content they could be placed here.";

            // Inject any mail that was added after the initial load.
            foreach (var item in dynamicMail)
            {
                data.Add(item);
            }

            dynamicMail.Clear();    // For the usage of this MOD the letters are cleared
        }

        /// <summary>
        /// Add a new mail asset into the collection so it can be injected by the next cache refresh.  The letter will
        /// not be available to send until the cache is invalidated in the code.
        /// </summary>
        /// <param name="mailId">The mail key</param>
        /// <param name="mailText">The mail text</param>
        public void Add(string mailId, string mailText)
        {
            if (!string.IsNullOrEmpty(mailId))
            {
                if (dynamicMail.ContainsKey(mailId))
                    dynamicMail[mailId] = mailText;
                else
                    dynamicMail.Add(mailId, mailText);
            }
        }
    }
}

您會注意到用於靜態郵件和動態郵件的代碼實際上幾乎沒有區別。支持動態郵件的類有一個私有字典集合,用於保存等待注入的任何郵件內容。本來可以公開以允許將郵件直接添加到集合中,但這不是一個好的做法。相反,提供了一個公共 Add 方法,以便可以將郵件發送到集合。此代碼適用於特定的 MOD,而不是健壯的框架,因此不會過分關注錯誤處理。您可以根據自己的需要進行改進。

請注意 Edit 方法中的附加代碼,其中 dynamicMail 集合中的任何郵件都被注入到 Stardew Valley 的內容中。第一次加載MOD時(在這種情況下),dynamicMail集合中將沒有郵件。如果在原始加載後添加郵件,則必須通過使緩存無效來重新加載內容。更多細節請參考Cache invalidation

客戶端調用動態內容 (Send a letter with dynamic content)

例如,要在您自己的項目中使用此類,從而使動態郵件可用,請掛入 OnGameLaunch 事件。

    // 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);
    }

您可以連接到其他事件,例如「開始日期」或「結束日期」以生成要發送的信件。 考慮這個簡單的例子,它僅用於說明目的。

    private void OnDayStarting(object sender, DayStartedEventArgs e)
    {
        string mailMessage = $"@, you have gathered {Game1.stats.rabbitWoolProduced} units of rabbit wool!";

        mailData.Add("MyModMailWool", mailMessage);      // Add this new letter to the mail collection (for next refresh).

        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)
    }

此示例格式化一封信,顯示最新的兔毛計數,使其可用於郵件收集,將該信放入郵箱,然後使緩存無效,以便在緩存刷新期間注入此新信.在這種情況下,不需要記住 mailId,因為每次需要發送信件時都會重新創建該信件,在本例中是每天。同樣,此代碼僅用於說明該概念。

在以這種簡單的方式注入郵件時,需要了解一個重要的警告。可用的各種郵件框架處理這個問題,本節將被擴展以解釋如何解決這個問題,但在此處進行介紹是為了確保您完全了解 MOD 如何與 Stardew Valley 和 SMAPI 一起工作。

如果添加動態信件並在Day Ending 的內容中注入它,顯然您必須添加明天顯示的郵件。這意味着遊戲將通過對郵箱中待處理的動態信件(在本例中為「MyMailModWool」)的引用來保存。如果玩家此時退出遊戲並稍後返回繼續玩,則該動態字母不可用,從而導致「幻影字母」。郵箱將顯示一封可用的信件,但單擊時不會顯示任何內容。這可以通過多種方式處理,包括保存自定義字母並在玩家繼續時加載它們,但同樣,此示例代碼尚未涵蓋這一點。這就是該示例使用 On Day Start 並立即使信件可用的原因。

其他 (Other)

簡單動畫(animation) 的添加

location.temporarySprites.Add(new TemporaryAnimatedSprite(...));

詳見TemporaryAnimatedSprite類。

播放一段聲音

location.playSound("SOUND");  // "SOUND"为声音的名字

詳見聲音名字一覽

開源 (Open Source)

詳見模組大全原始碼(source)一欄。