Stardew Valley

About this mod

Framework to use the in game mailbox. Console commands to adjust the mail. Code and Content Pack interfaces.

Requirements
Permissions and credits
Changelogs
Donations
Objective:
Simplify the send of mail to the player, giving more control over when and what should be received.
Also giving more options not possible just editing the data files, like changing the text color and background of the letter at will.

Installation:

Change Log:

1.18.0
  • New Ids property to replace Index and Name for CollectionConditions. It can aggregate multiple items count, summing their value to compare against the amount.
  • New ReplyConfig property to add a reply behavior. You can show some option to the player after reading a mail and it will add one or more MailReceived text based on the option chosen. Additional mod logic can be implemented based on that choice.
  • Fix to letter with recipe not supporting CustomTextColorName property.
  • Fix null reference exception when an item with null name was loading in the game.
1.17.1
  • Content pack mails with recipe property need to not have attachments to be redelivered if the recipe is not learned. (recipe property is ignored if there is attachments)
  • Fix cooking recipes with the same logic applied to crafting recipes before.
1.17.0
  • New property to set the quality of the attachment
  • Content pack mails with recipe should be redelivered if the recipe is not learned.
  • Fix warnings about Slingshot if using the qualified item id.
  • Fix recipes for craftables been delivered even if the recipe was already learned.
  • For legacy reasons, if the recipe id is not found in the learned recipes, it will look for a recipe that create an item with the name of the recipe id.
1.16.1
  • Fix to recipe translated name.
1.16.0
  • Update mod to be compatible with Stardew Valley 1.6
  • New command to reload content packs
  • Changed MailDao name to MailRepository. (Only affect SMAPI mods using it)
  • New Item Type 'QualifiedItemId' for attachments, should work for every type of item in game if you use the proper qualification.
  • New property for special dates conditions. (Weding and ChildBirth)
  • New property for CustomTextColor, deprecated old one.
  • New HasMods property
  • New RequireAllMods property
  • Events id are now strings. (numbers should still work)
  • Removed obsolete logic SV 1.6 incorporated from MFM.
  • Fixed issue from alpha where letter would not open in the mail collection page.
1.15.0
  • New API Interface for SMAPI mods.
  • API method to load content pack folder from inside your SMAPI mod.
  • API method to load a letter.
  • API method to get a letter data.
  • API method to get the mail data string of a letter.
  • Fix mailbox not working properly when an error happened when closing the letter.
  • Better log on those errors.
1.14.0
  • More changes for compatibility with SMAPI 4
  • New attribute in the letter class to set the ITranslationHelper.
  • Content pack don't need to change anything, but this change should avoid some rare i18n issues.
  • Mods that use MFM through code can update their implementation.
1.13.0
  • Make the mod compatible with SMAPI 4.
  • New property for Deepest mine level.
  • New property for current money.
  • New property for total money earned.
  • New console command to force mailbox to update to help modders debug.
  • Letter collection menu now show custom close button too.
1.12.2
  • Make the mod compatible with 1.5.5, without forcing SMAPI to convert it.
  • Should fix some problems with attachment not working on Unix OS.
  • New property for House Upgrade Level.
1.12.1
  • Fixed error when Expanded Preconditions Utility was not loaded.
1.12.0
  • New properties to support the use of Expanded Preconditions
  • New property to make it possible to add text besides the letter id to the player's MailReceived list.
1.11.0
  • Support to DGA(Dynamic Game Assets) as attachments.
Spoiler:  
Show
1.10.2
  • Fix to cooking recipes names in English showing as null, when added by Json Assets.
1.10.1
  • Fix to Auto Open letters with no text breaking the mod when there is i18n files.
  • Fix to letter from content pack not loading after one collection condition warning.
  • Adds new collection requirement type for crafting recipes.
1.10.0
  • All vanilla tools are now supported.
  • Support to vanilla Slingshots as weapons. If you use the index or the name of an slingshot it will be properly created as such.
  • New "Auto Open" property, for when you want to use MFM conditions to save a letter id as read without writing an actual letter.
1.9.2
  • Optimization for assets loading from content packs.
  • Fix to callback of letters not being called if a menu was opened to add an item to the inventory.
1.9.1
  • Fix a bug where MFM letter would not be opened and removed from the box for the current day if you were holding an object when clicking the mailbox.
1.9.0
  • New property for letter created with SMAPI to add items dynamically to the letter.
  • New method to remove letters from the mailbox.
1.8.1
  • Fix to avoid conflict mails in local coop.
  • Fix to rare situations the mod would stop work if a letter menu was not properly closed by the game.
1.8.0
  • Support for Furniture, Weapons and Boots as attachments.
  • New property to set the upgrade level of attached tools.
1.7.0
  • Support for Rings as attachments.
  • New conditions for recipes.
  • New property for background customization with content packs.
  • Support to Close Button customization with content pack and code.
1.6.0
  • Support to i18n in content packs.
  • New conditions properties for buildings in the farm.
  • New conditions properties for mail received. (this is used for quest flags)
  • New conditions properties for events.
  • New conditions list for collections menu items.
  • New conditions list for game stats.
  • New property for friendship status for the conditions list for NPC friendship.
  • New random functionality for when Group Ids collide.
1.5.1
  • Fix to letters with no group ids being grouped together.
1.5.0
  • Letter will now be shown in the collection menu if an Title is defined. Custom textures and color will also show in the collection menu.
  • Letter now can have a group id. Letter with the same group id are never delivered in the same day.
  • New condition for content packs: "RandomChance".
  • BigObject is now called BigCraftable, like in the game code.
  • Fix of typos in the template.
1.4.1
  • Adds support to Stardew Valley 1.4.0
  • Adds Title property for Letters to be shown in the collection menu. Not fully implemented yet, but modders can start to set it.
1.4.0
  • Adds an content pack support for modders to add custom letters, with configurations to all features this framework provides, and with mostly common used delivery conditions.
  • Fix controllers not working to get attached items.
  • Fix to properly show attachments if the mail has more than one item attached.
  • Fix to not let the letter close if your inventory is full and a menu is opened to manage it.
  • Fix to not adding an stack of items to the mail even when configured to.
  • Adds support to SMAPI 3
1.3.4
  • Adds support to Stardew Valley 1.3.36
1.3.3
  • Adds support to SMAPI 3
1.3.2
  • Adds support to Stardew Valley 1.3.32
1.3.1
  • Adds commands to remove and add to the player received mail list.
1.3.0-beta.6
  • Adds support to crafting recipes
  • Adds support to custom background
  • Adds support to custom text color
  • New way to open custom letter, now using harmony
  • Config file to enable old way of opening custom letters.
1.2.0
  • Support to i18n recipes.
  • Fix to the mod not working properly after returning to title menu.
  • Fix to no mail being delivered if the condition of a letter threw an exception.
1.1.0
  • Fix to receiving "tax passed" mail when sleeping and quit the game without having read a custom mail.
  • Custom mails can have recipes.
  • This version is not compatible with the older one. Mods need to be updated to use this version.
1.0.0
  • Add custom mails
  • Custom mails can have items.

For Modders to use content pack:
  • There is a template inside the mod folder with commented instructions on what each property do.
  • All letter features but the conditions and callback are the same as coding with the framework.
  • Conditions are limited to the ones implemented, and the callback always set the letter id as received. If you need advanced conditions you still should code them. Or you can politely suggest me to add a condition to the content pack, if its something that makes sense, I might add it.
  • Here is the template as added in the mod file:
Spoiler:  
Show
[
    {
        "Id": "MyMod.MyMailId", // Letter id. It's important to be an unique string to avoid conflicts. Also it shouldn't have space characters.
        "GroupId": "MyMod.MyGroupId", // Letter group id. Letters with the same group id are never delivered in the same day. Letters registered first have priority, unless the group id ends with ".Random", in that case, a random letter will be chosen. Default is null.
        "Title": "My Letter Title", // Letter title. Will be shown in the collections menu. Set it null or remove the line if you don't want the letter to appear in the collection. If an translation file is provide, you should put a translation key here, but you can still leave if null for no Title.
        "Text": "Dear @^This is my custom mail.", // Text of the letter. You can use @ to put the players name and ^ for line breaks. You can also use the base game commands to add money, items and stuff. If an translation file is provide, you should put a translation key here.
        "Attachments": [ // List of attachments. Remove the property to not attach items to the mail.
            {
                "Type": "Object", // [Object|BigCraftable|Tool|Ring|Furniture|Weapon|Boots|DGA|FullId] Required. The type of item that will be attached. If not provided the item will be ignored.
                "Name": "Cave Carrot", // Used to find the item index. That's required if using custom objects like Json Assets ones. Should be the full DGA ID is using DGA. If not provided, the index will be used. Default is null.
                "Index": "(0)78", // The index of an item. Should be the qualified item id as a string, but will also work as an integer for retro compatibility. If no name is provided or an item for the name is not found, the index is used. Otherwise, the attachment is ignored. Ignored if the type is DGA.
                "Stack": 1, // The stack value of the item to be delivered. Used only for Objects and BigCraftable. Default is 1.
                "Quality": 2 // The quality value of the item to be delivered. Used only for Objects. 0 = none, 1 = silver, 2 = gold, 4 = iridium. Default is 0;
            },
            {
                "Type": "Tool", // When using tool, only supported ones can be attached.
                "Name": "Axe", // [Axe|Hoe|Watering Can|Pickaxe|Scythe|Golden Scythe|Milk Pail|Shears|Fishing Rod|Pan|Return Scepter] Required for tools. The name of the supported tool. Otherwise, the attachment is ignored.
                "UpgradeLevel": 1 // The upgrade level of the tool. Regular tools: 0 = stone, 1 = cooper, 2 = steel, 3 = gold, 4 = iridium. Fishing Rod: 0 = Bamboo Pole, 1 = Training Rod, 2 = Fiberglass Rod, 3 = Iridium Rod. Ignored for the other types. Default is 0.
            },
            {
                "Type": "DGA", //DGA item
                "Name": "spacechase0.DynamicGameAssets.Example/My Custom Item", // use the full DGA ID. Required.
                "Stack": 10, // The stack value of the item to be delivered. Used only for Objects and BigCraftable. Default is 1.                
                "Quality": 2 // The quality value of the item to be delivered. Used only for Objects. 0 = none, 1 = silver, 2 = gold, 4 = iridium. Default is 0;
            },
            {
                "Type": "QualifiedItemId", //Any supported item
                "Index": "(0)78", // The Qualified Item Id of an item.
                "Stack": 10, // The stack value of the item to be delivered. Used only for Objects and BigCraftable. Default is 1;
                "Quality": 2 // The quality value of the item to be delivered. Used only for Objects. 0 = none, 1 = silver, 2 = gold, 4 = iridium. Default is 0;
            }
        ],
        "Recipe": "Recipe Name", // Remove the line if you don't want to attach a recipe to the mail. It will only work if you have no other attachments to the mail. For DGA recipes only use the ID part (leave away the ModID)
        "AdditionalMailReceived": ["MyMod.AnotherMailId", "VANILLA_FLAG"], // Use this to add additional text to the MailReceived list. Can be useful to add vanilla flags or other MFM letter ids.
        "LetterBG": "CustomLetterBG.png", // Name of the file in your content pack that has the custom letter background to use. It should follow the same structure of the game LetterBG file . WhichBG will be relative to this file for this letter. If null or removed the mod will use the game LetterBG.
        "WhichBG": 0, //The id of the letter background. 0 = classic, 1 = notepad, 2 = pyramids
        "TextColor": -1, //Remove this line to use the default color. Will be ignored if a CustomTextColor is set. -1 = Dark Red, 0 = Black, 1 = Sky Blue, 2 = Red, 3 = Blue Violet, 4 = White, 5 = Orange Red, 6 = Lime Green, 7 = Cyan, 8 = Darkest Gray
        "CustomTextColorName": "White", //The color of the text.[http://www.foszor.com/blog/xna-color-chart/] Default will use the TextColor property.
        "UpperRightCloseButton": "CustomCloseButton.png", // Name of the file in your content pack that has the custom close button to use. It should be 12 x 12 . If null or removed it will use the default button.
        "ReplyConfig": { // If you want the player to send a reply after reading the letter. It will show the reply options that will add one or more "ReceivedMail" based on the player answer. Any additional logic related to the reply needs to be implemented based on the ReceivedMail added.
            "QuestionKey": "MyMod.MyMailId.Question", //A key to identify your question. It has only internal use, but should be unique in your content pack to avoid conflict between replies.
            "QuestionDialog": "Send a reply choosing your reward:", //Your question or text that will show over the reply options. If an translation file is provided, you should put a translation key here.
            "Replies": [
                {
                    "ReplyKey": "MyMod.MyMailId.Reply1", //Must be unique between the question replies.
                    "ReplyOptionDialog": "I want seeds.", //The option text that will show in the reply list. If an translation file is provided, you should put a translation key here.
                    "MailReceivedToAdd": [ "MyMod.MyMailId.PlayerSeedOption" ], // Text to add to the MailReceived list. This can be used to trigger new mail, events, vanilla flags, stop other MFM mail from being sent...
                    "ReplyResponseDialog": "Your letter requesting seeds was sent." //Text that will show after this reply option is chosen. If an translation file is provided, you should put a translation key here.
                }
            ]
        },
        "Repeatable": false, // If true the mod won't check it the letter Id has already been delivered. Default is false.
        "AutoOpen": false, // If true the mod will open the letter at the begin of the day after the conditions are met. The letter id will be marked as read and if there is a recipe set, it will be learned. Since the letter will never show, visual properties like title, text, background... will never be used, as well as the attachments.
        // CONDITIONS FOR DELIVERY
        //Below are conditions for the delivery. Remove any of the lines if you don't want to check that condition.
        "Date": "10 spring Y1", // Must be that date or after it. The format is "[1-28] [spring|summer|fall|winter] Y[1-999]".
        "Days": [7,14,21,28], // Must be one of the days in the list.
        "Seasons": ["fall"], // Must be one of the seasons in the list. [spring|summer|fall|winter]
        "Weather": "sunny", // Must be that game weather. The format is "[sunny|rainy]".
        "HouseUpgradeLevel": 2, // House upgrade level must be equal or higher what is defined. 0 - starter house(no reason to use this, just remove the line), 1 - kitchen, 2 - second floor, 3 - cellar.
        "DeepestMineLevel": 80, // Deepest mine level must be equal or higher what is defined. 120 is the last level of the mine, 121 is the first level of the skull cavern.
        "CurrentMoney": 10000, // Current money must be equal or higher what is defined.
        "TotalMoneyEarned": 500000, // Total money earned must be equal or higher what is defined.
        "FriendshipConditions": // Each NPC of the list must check all conditions.
        [
            {
                "NpcName": "Lewis", //Name of the NPC. Can use custom NPCs.
                "FriendshipLevel": 8, // NPC must have a friendship heart level equal or higher what is defined. Default is 0.
                "FriendshipStatus": ["Dating","Engaged","Married"] // [Friendly|Dating|Engaged|Married|Divorced]  Require the NPC friendship status to be one from the list. Remove to not require a status.
            }
        ],
        "SkillConditions": // Each skill of the list must have a level equal or higher what is defined. Can use all coded skills in the vanilla game, including Luck. Can't use custom skills.
        [
            { "SkillName": "Farming", "SkillLevel": 1 }
        ],
        "StatsConditions": // Each stats of the list must have a value equal or higher what is defined. Choose a StatsName or a StatsLabel
        [
            {
                "StatsName": "CheeseMade", //[SeedsSown|ItemsShipped|ItemsCooked|ItemsCrafted|ChickenEggsLayed|DuckEggsLayed|CowMilkProduced|GoatMilkProduced|RabbitWoolProduced|SheepWoolProduced|CheeseMade|GoatCheeseMade|TrufflesFound|StoneGathered|RocksCrushed|DirtHoed|GiftsGiven|TimesUnconscious|AverageBedtime|TimesFished|FishCaught|BouldersCracked|StumpsChopped|StepsTaken|MonstersKilled|DiamondsFound|PrismaticShardsFound|OtherPreciousGemsFound|CaveCarrotsFound|CopperFound|IronFound|CoalFound|CoinsFound|GoldFound|IridiumFound|BarsSmelted|BeveragesMade|PreservesMade|PiecesOfTrashRecycled|MysticStonesCrushed|DaysPlayed|WeedsEliminated|SticksChopped|NotesFound|QuestsCompleted|StarLevelCropsShipped|CropsShipped|ItemsForaged|SlimesKilled|GeodesCracked|GoodFriends|IndividualMoneyEarned] Default is null.
                "StatsLabel": "Name", // [exMemoriesWiped|childrenTurnedToDoves|trashCansChecked|boatRidesToIsland|beachFarmSpawns|hardModeMonstersKilled|timesEnchanted] This are the current game stats that are identified by label, if more are added, they should also be supported. It will also identify custom stats labels added by other mods. Default is null.
                "Amount": 1 // The amount the status should be equal or greater for the condition to be valid.
            }
        ],
        "CollectionConditions": // Each collection condition of the list must have a value equal or higher than the defined amount.
        [
            {
                "Collection": "Shipped", //[Shipped|Fish|Artifacts|Minerals|Cooking|Crafting] Required.
                "Name": "Oil", //Deprecated, uses Ids instead. The name of the object or recipe for 'Crafting' collection. If not a crafting colection, it will look for that name in the object list to find the index. If the name is not found, the letter is ignored. Will combine with the other properties. Default is null.
                "Index": 211, //Deprecated, uses Ids instead. The index of the object. Will combine with the other properties. Default is null.
                "Ids": [ "282", "MossSoup" ], //The items ids or the crafting recipe names. Will combine with the other properties. The amounts are summed to compare with the Amount property.
                "Amount": 10 // The total amount the objects in the collection should be equal or greater for the condition to be valid.
            }
        ],
        "SpecialDateCondition": // Must be that date or after it.
        {
            "SpecialDate": "ChildBirth", //[Wedding|ChildBirth] Required
            "YearsSince": 1, //The amount of years since the date happen. 0 will match the actual date, 1 the flowing year. Default is 0.
            "WhichChild": 1 //If the SpecialDate property is ChildBirth, it will reference which chield. Default is 1;
        },
        "ExpandedPrecondition": "d Mon Fri/HasItem Pink Cake/!JojaMartComplete/!w rainy", //Needs the Expanded Preconditions Utility mod. Sees that mod documentation to see how this works. If the mod is not loaded, the letter will not be delivered.
        "ExpandedPreconditions": [ "!z spring/t 600 1000", "f Linus 1000/w rainy/z spring", "f Linus 2500" ], //Needs the Expanded Preconditions Utility mod. Sees that mod documentation to see how this works. If the mod is not loaded, the letter will not be delivered.
        "RandomChance": 0.25, // The mod will check if a random number from 0 to 1 is bellow the given number. The same save, on the same day for the same letter will always have the same result to avoid cheating.
        "Buildings": ["Coop","Big Coop","Deluxe Coop"], // Require one of the buildings to be currently constructed in the farm.
        "RequireAllBuildings": false, // If true, require that all the buildings in the "Buildings" list to be currently constructed in the farm. Default is false.
        "MailReceived": ["jojaVault","ccVault"], // Require one of the mails to have been received. The game list also contain others thing that are not mail, like community center flags.
        "RequireAllMailReceived": false, // If true, require that all mails in the "MailReceived" list to have been received. Default is false.
        "MailNotReceived": ["jojaVault","ccVault"], // Require the mails to have not been received. The game list also contain others thing that are not mail, like community center flags.
        "EventsSeen": [ "4", "32423" ], // Require one of the events to have been seen by the player. Should be a string, but will also work as an integer for retro compatibility.
        "RequireAllEventsSeen": false, // If true, require that all the events in the "EventsSeen" list to have been seen by the player. Default is false.
        "HasMods": [ "SMAPI.ConsoleCommands", "SMAPI.SaveBackup" ], // Require one of the mods to be loaded. Should be the mod UniqueID.
        "RequireAllMods": false, // If true, require that all the mods in the "HasMods" list to have been loaded. Default is false.
        "EventsNotSeen": [ "4", "32423" ], // Require the events to have not been seen by the player. Should be a string, but will also work as an integer for retro compatibility.
        "RecipeKnown": ["Pizza","Survival Burger"], // Require one of the recipes to have been learned by the player.
        "RequireAllRecipeKnown": false, // If true, require that all the recipes in the "RecipeKnown" list to have been learned by the player. Default is false.
        "RecipeNotKnown": ["Wild Bait"] // Require the recipes to have not been learned by the player.
    }
]


For Modder to use the API:
  • Copy IMailFrameworkModApi interface with the method you want to use from MFM interface.
  • Copy ILetter if one of the methods need it.
  • Methods:
    • RegisterContentPack - Use Helper.ContentPacks.CreateTemporary to load a folder from your mod that contain the mail.json and possible i18n folder for translations. Manifest is not needed since you can use that method parameter.
    • RegisterLetter - Fill an ILetter with the properties you need. A condition is also required to register an letter. A callback is advisable, to at least add the letter ID as received. See examples bellow, on the "For Modder to code with MFM dependency" session.
    • GetLetter- You will get the ILetter object for the letter ID.
    • GetMailDataString- You will get the mail data string for the letter ID. Mail data string is basically the translated text plus the translated title with a [#] separator.

For Modder to code with MFM dependency:
  • Reference the MailFrameworkMod.dll on your SMAPI project.
    • You register your Letter using MailRepository.SaveLetter(Letter)
    • At the begin of each day the mod will check for Letters that have reached its condition for delivery and add them to the mailbox.
    • This Letters will be opened first when the player check the mailbox.
    • When the Letter is opened, it is removed from the mailbox.
    • The callback condition of the letter is them called.
    • At the end of the day Letter still on the mailbox will be removed, so they are not saved by the game.
  • You can think of this mod as an repository for your letters. It works best if you load all your letter when the game loads.


Exemples:

Spoiler:  
Show

Loading a simple Letter that will show for the player if not read before, and will not be be delivered again after being opened by the player:
MailRepository.SaveLetter(
new Letter(
"LetterUniqueId"
,"Letter custom text."
,(l)=>!Game1.player.mailReceived.Contains(l.Id)
           ,(l)=>Game1.player.mailReceived.Add(l.Id)
)
);


Loading a simple Letter as before, but with 5 Emerald annexed:
MailRepository.SaveLetter(
  new Letter(
"LetterUniqueId"
,"Letter custom text."
,new List<Item> { new StardewValley.Object(60,5) }
,(l)=>!Game1.player.mailReceived.Contains(l.Id)
,(l)=>Game1.player.mailReceived.Add(l.Id)
  )
);

The player will not receive the same item, but a copy returned from the getOne() method.

Loading a simple Letter that will teach a cooking recipe, it will be delivered if the player doesn't know the recipe. The mod automatically teaches the recipes when the player loads, no need for a callback here:
MailRepository.SaveLetter(
  new Letter(
"LetterUniqueId"
, "Letter custom text."
, "RecipeUniqueName"
, (l) => !Game1.player.cookingRecipes.ContainsKey(l.Recipe)
  )
);

The "RecipeUniqueName" must be the same as stored on "Data//CookingRecipes" or "Data//CraftingRecipes".

Loading a simple Letter with different text and background:
MailRepository.SaveLetter(
  new Letter(
"LetterUniqueId"
,"Letter custom text."
,(l)=>!Game1.player.mailReceived.Contains(l.Id)
,(l)=>Game1.player.mailReceived.Add(l.Id)
1
  ){TextColor=8}
);


Loading a simple Letter with custom background:
MailRepository.SaveLetter(
  new Letter(
"LetterUniqueId"
,"Letter custom text."
,(l)=>!Game1.player.mailReceived.Contains(l.Id)
,Game1.player.mailReceived.Add(l.Id)
1
  ){
LetterTexture=helper.Content.Load<Texture2D>("CustomLetterBG.png")
,TextColor=4
  }
);
Where CustomLetterBG.png is a image file that follows the same structure as "LooseSprites//letterBG"

Loading a simple Letter with translation for text and title.
MailRepository.SaveLetter(
new Letter(
"LetterUniqueId"
,"myletter.translation.key.text"
,(l)=>!Game1.player.mailReceived.Contains(l.Id)
           ,(l)=>Game1.player.mailReceived.Add(l.Id)
){
Title = "myletter.translation.key.title",
I18N = helper.Translation
    }
);



My Other Mods: