### Finalize Install Package Script (Bash) Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/smapi.md Completes the SMAPI installation package preparation within a WSL environment. Ensure the version and binFolder variables match your setup. ```bash # edit to match the build created in steps 1 # In WSL, \"/mnt/c/example\" accesses \"C:\\example\" on the Windows filesystem. version="4.0.0" binFolder="/mnt/e/source/_Stardew/SMAPI/bin" pwsh build/scripts/finalize-install-package.sh "$version" "$binFolder" ``` -------------------------------- ### Local Development Environment Setup Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/web.md Instructions for setting up a local development environment for SMAPI web services. ```APIDOC ## For SMAPI developers ### Local environment A local environment lets you run a complete copy of the web project (including cache database) on your machine, with no external dependencies aside from the actual mod sites. 1. Edit `appsettings.Development.json` and set these options: | Property Name | Description | |---|---| | `NexusApiKey` | [Your Nexus API key](https://www.nexusmods.com/users/myaccount?tab=api#personal_key). | Optional settings: | Property Name | Description | |---|---| | `AzureBlobConnectionString` | The connection string for the Azure Blob storage account. Defaults to using the system's temporary file folder if not specified. | | `GitHubUsername`
`GitHubPassword` | The GitHub credentials with which to query GitHub release info. Defaults to anonymous requests if not specified. | 2. Launch `SMAPI.Web` from Visual Studio to run a local version of the site. ``` -------------------------------- ### Subscribe to Day Start Event Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `TimeEvents.AfterDayStarted` to subscribe to an event that triggers when a new day begins. This event fires regardless of how the day started, including new games or loaded saves. ```csharp helper.Events.Time.AfterDayStarted += (sender, e) => { // day started logic here }; ``` -------------------------------- ### Prepare Install Package Script (PowerShell) Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/smapi.md Creates a release package for SMAPI. Replace VERSION_HERE with the desired semantic version number. This script is used for manual releases. ```powershell pwsh build/scripts/prepare-install-package.sh VERSION_HERE ``` -------------------------------- ### Migrate to SMAPI 4.0 Guide Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Guide for C# mod authors to update their mods for SMAPI 4.0.0, including information on deprecated code and new features. ```markdown your mods should still work fine in SMAPI 3.14.0. However you should review the [migration to SMAPI 4.0](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) guide and update your mods when possible. Deprecated code will be removed when SMAPI 4.0.0 releases later this year (no sooner than August 2022), and break any mods which haven't updated by that time. You can update affected mods now, there's no need to wait for 4.0.0. ``` -------------------------------- ### Get Target Platform Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Identifies the operating system the game is currently running on (Linux, macOS, or Windows). ```csharp Constants.TargetPlatform ``` -------------------------------- ### Prepare Install Package Script with Skip Bundle Deletion (PowerShell) Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/smapi.md Prepares the SMAPI release package, skipping bundle deletion for Windows-specific builds. Use this for manual Windows releases. ```powershell pwsh build/scripts/prepare-install-package.ps1 VERSION_HERE --skip-bundle-deletion ``` -------------------------------- ### Get Days Since Start with SDate in SMAPI Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Access the `DaysSinceStart` property from the `SDate` object to determine the number of days passed since the game's start. Requires the `SDate` object. ```csharp SDate.DaysSinceStart ``` -------------------------------- ### Handle Player Input Events Source: https://context7.com/pathoschild/smapi/llms.txt This example demonstrates how to register event handlers for various input events like button presses, releases, cursor movement, and mouse wheel scrolling. It shows how to parse custom keybinds and suppress specific inputs. ```csharp using StardewModdingAPI; using StardewModdingAPI.Events; using StardewModdingAPI.Utilities; public class InputMod : Mod { private KeybindList activateKeybind = null!; public override void Entry(IModHelper helper) { // Parse keybind from config (supports multi-key combos like "LeftControl + S") this.activateKeybind = KeybindList.Parse("F5, LeftControl + R"); helper.Events.Input.ButtonPressed += this.OnButtonPressed; helper.Events.Input.ButtonReleased += this.OnButtonReleased; helper.Events.Input.ButtonsChanged += this.OnButtonsChanged; helper.Events.Input.CursorMoved += this.OnCursorMoved; helper.Events.Input.MouseWheelScrolled += this.OnMouseWheelScrolled; } private void OnButtonPressed(object? sender, ButtonPressedEventArgs e) { // Skip if game isn't ready or player is in a menu if (!Context.IsWorldReady || Game1.activeClickableMenu != null) return; // Check custom keybind if (this.activateKeybind.JustPressed()) { this.Monitor.Log("Activation keybind pressed!"); return; } // Check specific button if (e.Button == SButton.F1) { this.Monitor.Log($"F1 pressed at cursor: {e.Cursor.ScreenPixels}"); // Prevent the game from seeing this button press this.Helper.Input.Suppress(e.Button); } // Check if left-click on a tile if (e.Button.IsUseToolButton()) { var tile = e.Cursor.GrabTile; this.Monitor.Log($"Tool used on tile: {tile}"); } } private void OnButtonReleased(object? sender, ButtonReleasedEventArgs e) { this.Monitor.VerboseLog($"Button released: {e.Button}"); } private void OnButtonsChanged(object? sender, ButtonsChangedEventArgs e) { // Get all buttons pressed this tick foreach (SButton button in e.Pressed) this.Monitor.VerboseLog($"Pressed: {button}"); foreach (SButton button in e.Released) this.Monitor.VerboseLog($"Released: {button}"); } private void OnCursorMoved(object? sender, CursorMovedEventArgs e) { // Track cursor for custom UI elements if (this.Helper.Input.IsDown(SButton.LeftShift)) this.Monitor.VerboseLog($"Cursor: {e.NewPosition.ScreenPixels} (tile: {e.NewPosition.Tile})"); } private void OnMouseWheelScrolled(object? sender, MouseWheelScrolledEventArgs e) { this.Monitor.VerboseLog($"Scroll: {e.Delta}"); // Suppress scroll wheel if holding a modifier key if (this.Helper.Input.IsDown(SButton.LeftControl)) this.Helper.Input.SuppressScrollWheel(); } } ``` -------------------------------- ### Load and Manipulate Game Assets Source: https://context7.com/pathoschild/smapi/llms.txt This snippet demonstrates loading various game assets such as dictionaries, textures, and maps using the IGameContentHelper. It also shows how to get current locale information and parse asset names. Ensure the SMAPI environment is set up correctly. ```csharp using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; using xTile; public class ContentHelperMod : Mod { public override void Entry(IModHelper helper) { helper.Events.GameLoop.SaveLoaded += this.OnSaveLoaded; helper.Events.Input.ButtonPressed += this.OnButtonPressed; } private void OnSaveLoaded(object? sender, SaveLoadedEventArgs e) { // Get current locale info this.Monitor.Log($"Current locale: {this.Helper.GameContent.CurrentLocale}"); this.Monitor.Log($"Language code: {this.Helper.GameContent.CurrentLocaleConstant}"); // Parse asset name IAssetName assetName = this.Helper.GameContent.ParseAssetName("Data/ObjectInformation"); this.Monitor.Log($"Asset: {assetName.BaseName}, Locale: {assetName.LocaleCode}"); // Check if asset exists if (this.Helper.GameContent.DoesAssetExist(assetName)) this.Monitor.Log("Asset exists!"); // Load game content Dictionary objectData = this.Helper.GameContent.Load>("Data/ObjectInformation"); this.Monitor.Log($"Loaded {objectData.Count} object definitions"); // Load a texture Texture2D cursors = this.Helper.GameContent.Load("LooseSprites/Cursors"); this.Monitor.Log($"Cursors texture: {cursors.Width}x{cursors.Height}"); // Load a map Map townMap = this.Helper.GameContent.Load("Maps/Town"); this.Monitor.Log($"Town map layers: {townMap.Layers.Count}"); } private void OnButtonPressed(object? sender, ButtonPressedEventArgs e) { if (!Context.IsWorldReady) return; if (e.Button == SButton.F8) { // Invalidate specific asset (forces reload) bool wasInvalidated = this.Helper.GameContent.InvalidateCache("Data/ObjectInformation"); this.Monitor.Log($"Cache invalidated: {wasInvalidated}"); } if (e.Button == SButton.F9) { // Invalidate by predicate bool anyInvalidated = this.Helper.GameContent.InvalidateCache(asset => asset.Name.StartsWith("Characters/") || asset.Name.StartsWith("Portraits/") ); this.Monitor.Log($"Invalidated character assets: {anyInvalidated}"); } if (e.Button == SButton.F10) { // Invalidate all textures (use sparingly!) bool texturesInvalidated = this.Helper.GameContent.InvalidateCache(); this.Monitor.Log($"All textures invalidated: {texturesInvalidated}"); } } } ``` -------------------------------- ### Get API from Mod Registry Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Retrieves an API exposed by another mod. Errors will now specify which interface caused the issue. ```csharp helper.ModRegistry.GetApi("Some.Mod.UniqueId") ``` -------------------------------- ### Integrate with Other Mods using IModRegistry Source: https://context7.com/pathoschild/smapi/llms.txt Use IModRegistry to check if mods are installed, retrieve their API, and list all loaded mods. Ensure the mod is loaded before accessing its API. ```csharp using StardewModdingAPI; using StardewModdingAPI.Events; public class IntegrationMod : Mod { private IJsonAssetsApi? jsonAssetsApi; private IContentPatcherApi? contentPatcherApi; public override void Entry(IModHelper helper) { helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; } private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { // Check if a mod is loaded if (this.Helper.ModRegistry.IsLoaded("spacechase0.JsonAssets")) { this.Monitor.Log("Json Assets is installed!"); // Get the mod's info IModInfo? modInfo = this.Helper.ModRegistry.Get("spacechase0.JsonAssets"); if (modInfo != null) this.Monitor.Log($"Json Assets version: {modInfo.Manifest.Version}"); // Get typed API (preferred - SMAPI creates a proxy) this.jsonAssetsApi = this.Helper.ModRegistry.GetApi("spacechase0.JsonAssets"); if (this.jsonAssetsApi != null) { // Use the API int? itemId = this.jsonAssetsApi.GetObjectId("Custom Item"); this.Monitor.Log($"Custom Item ID: {itemId}"); } } // Get untyped API (use reflection to access methods) object? rawApi = this.Helper.ModRegistry.GetApi("Pathoschild.ContentPatcher"); if (rawApi != null) this.Monitor.Log("Content Patcher API available"); // List all loaded mods foreach (IModInfo mod in this.Helper.ModRegistry.GetAll()) { this.Monitor.VerboseLog($"Loaded: {mod.Manifest.Name} v{mod.Manifest.Version} by {mod.Manifest.Author}"); } // Get mod from a namespaced ID (e.g., item ID like "AuthorName.ModName_ItemName") IModInfo? itemMod = this.Helper.ModRegistry.GetFromNamespacedId("spacechase0.JsonAssets_CustomCrop"); if (itemMod != null) this.Monitor.Log($"Item added by: {itemMod.Manifest.Name}"); } } // Define interface matching the API you want to use public interface IJsonAssetsApi { int? GetObjectId(string name); int? GetCropId(string name); void LoadAssets(string path); } public interface IContentPatcherApi { void RegisterToken(IManifest mod, string name, Func?> getValue); } ``` -------------------------------- ### Record Player Actions for Save Data Source: https://context7.com/pathoschild/smapi/llms.txt Update your mod's data structures based on player actions. This example shows how to increment a counter and add to a list when an item is crafted. ```csharp // Call this when player crafts an item public void RecordCraft(string itemId) { this.saveData.TotalItemsCrafted++; if (!this.saveData.UnlockedRecipes.Contains(itemId)) this.saveData.UnlockedRecipes.Add(itemId); } ``` -------------------------------- ### Specify Custom Game Path in stardewvalley.targets Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Use this XML configuration file to manually specify the Stardew Valley game path if the package cannot detect it automatically or if multiple installations exist. Replace PATH_HERE with the full folder path containing the Stardew Valley executable. ```xml PATH_HERE ``` -------------------------------- ### Mod Entry Point and Event Subscription Source: https://context7.com/pathoschild/smapi/llms.txt The `Entry` method is the main entry point for your mod. Subscribe to game events here to trigger your data loading and saving logic at appropriate times. ```csharp public override void Entry(IModHelper helper) { helper.Events.GameLoop.SaveLoaded += this.OnSaveLoaded; helper.Events.GameLoop.Saving += this.OnSaving; helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; helper.Events.GameLoop.ReturnedToTitle += this.OnReturnedToTitle; } ``` -------------------------------- ### Broadcast Message Source: https://context7.com/pathoschild/smapi/llms.txt Broadcasts a custom message to all connected players who have this mod installed. This example broadcasts player position data. ```csharp private void BroadcastMessage() { var message = new SyncMessage { Type = "Position", Data = "${Game1.player.Position.X},${Game1.player.Position.Y}" }; // Broadcast to all connected players who have this mod this.Helper.Multiplayer.SendMessage( message, "MyMod.Sync", modIDs: new[] { this.ModManifest.UniqueID } ); this.Monitor.Log($"Broadcasted position to ${this.Helper.Multiplayer.GetConnectedPlayers().Count()} peers") } } public class SyncMessage { public string Type { get; set; } = ""; public string Data { get; set; } = ""; } ``` -------------------------------- ### Get SMAPI Directory Path Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `helper.DirectoryPath` to reliably get the directory path of the SMAPI mod. This is the recommended alternative to `Assembly.GetExecutingAssembly().Location` which may not be valid. ```csharp string modPath = helper.DirectoryPath; ``` -------------------------------- ### Use GameLaunched Event for Initialization Code Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Mods are loaded early in the game launch. Use the `GameLaunched` event if your code needs to run after the game is fully initialized. ```csharp public void Entry(IModHelper helper) { helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; } private void OnGameLaunched(object sender, EventArgs e) { // Game is now fully initialized. } ``` -------------------------------- ### Initialize Display Event Handlers Source: https://context7.com/pathoschild/smapi/llms.txt Sets up event handlers for various display-related events. Toggles an overlay on F6 press. ```csharp using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewValley; using StardewValley.Menus; public class DisplayMod : Mod { private bool showOverlay = false; public override void Entry(IModHelper helper) { helper.Events.Display.MenuChanged += this.OnMenuChanged; helper.Events.Display.RenderedWorld += this.OnRenderedWorld; helper.Events.Display.RenderedHud += this.OnRenderedHud; helper.Events.Display.RenderedActiveMenu += this.OnRenderedActiveMenu; helper.Events.Display.WindowResized += this.OnWindowResized; helper.Events.Display.RenderingStep += this.OnRenderingStep; helper.Events.Display.RenderedStep += this.OnRenderedStep; helper.Events.Input.ButtonPressed += (s, e) => { if (e.Button == SButton.F6) this.showOverlay = !this.showOverlay; }; } private void OnMenuChanged(object? sender, MenuChangedEventArgs e) { if (e.NewMenu is ShopMenu shop) this.Monitor.Log($"Opened shop menu"); else if (e.NewMenu == null && e.OldMenu != null) this.Monitor.Log($"Closed menu: {e.OldMenu.GetType().Name}"); } private void OnRenderedWorld(object? sender, RenderedWorldEventArgs e) { if (!Context.IsWorldReady || !this.showOverlay) return; // Draw custom content over the world but under UI SpriteBatch spriteBatch = e.SpriteBatch; // Highlight tile under cursor var cursorTile = Game1.currentCursorTile; var screenPos = Game1.GlobalToLocal(Game1.viewport, cursorTile * 64f); spriteBatch.Draw( Game1.mouseCursors, new Rectangle((int)screenPos.X, (int)screenPos.Y, 64, 64), new Rectangle(194, 388, 16, 16), Color.White * 0.5f ); } private void OnRenderedHud(object? sender, RenderedHudEventArgs e) { if (!Context.IsWorldReady || !this.showOverlay) return; // Draw custom HUD element SpriteBatch spriteBatch = e.SpriteBatch; string text = $"Position: {Game1.player.Position}"; spriteBatch.DrawString( Game1.smallFont, text, new Vector2(10, 100), Color.White ); } private void OnRenderedActiveMenu(object? sender, RenderedActiveMenuEventArgs e) { // Draw over the current menu (appears above menu content) if (Game1.activeClickableMenu is InventoryPage) { e.SpriteBatch.DrawString( Game1.smallFont, "Custom Inventory Info", new Vector2(Game1.activeClickableMenu.xPositionOnScreen, Game1.activeClickableMenu.yPositionOnScreen - 40), Color.Gold ); } } private void OnWindowResized(object? sender, WindowResizedEventArgs e) { this.Monitor.Log($"Window resized: {e.OldSize} -> {e.NewSize}"); } private void OnRenderingStep(object? sender, RenderingStepEventArgs e) { // Handle specific render steps if (e.Step == StardewValley.Mods.RenderSteps.World_Background) this.Monitor.VerboseLog("About to render world background"); } private void OnRenderedStep(object? sender, RenderedStepEventArgs e) { // Draw after specific render steps complete if (e.Step == StardewValley.Mods.RenderSteps.World_Sorted) this.Monitor.VerboseLog("World sorted elements rendered"); } } ``` -------------------------------- ### Get PerScreen Value in SMAPI Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Access the `PerScreen` class to get or set values for specific screens, retrieve all active values, or clear all values. This is useful for managing screen-specific data in multiplayer. ```csharp var data = this.Helper.Multiplayer.PerScreen{

This is a sample paragraph.

``` -------------------------------- ### Get Absolute Cursor Pixels Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Retrieves the absolute pixel coordinates of the cursor position. ```csharp ICursorPosition.AbsolutePixels ``` -------------------------------- ### Basic Logging with IMonitor in C# Source: https://context7.com/pathoschild/smapi/llms.txt Demonstrates how to use the IMonitor interface to log messages at different levels. Ensure you have the StardewModdingAPI namespace imported. The default log level is Trace. ```csharp using StardewModdingAPI; public class LoggingMod : Mod { public override void Entry(IModHelper helper) { // Basic logging at different levels this.Monitor.Log("Trace message - only visible in log file or with verbose mode", LogLevel.Trace); this.Monitor.Log("Debug message - for debugging information", LogLevel.Debug); this.Monitor.Log("Info message - normal status updates", LogLevel.Info); this.Monitor.Log("Warn message - potential issues", LogLevel.Warn); this.Monitor.Log("Error message - something went wrong", LogLevel.Error); this.Monitor.Log("Alert message - critical issues", LogLevel.Alert); // Default level is Trace this.Monitor.Log("This defaults to Trace level"); // Log once (won't repeat if called multiple times) for (int i = 0; i < 10; i++) this.Monitor.LogOnce("This only appears once per session", LogLevel.Info); // Check if verbose mode is enabled this.Monitor.Log($"Verbose mode: {this.Monitor.IsVerbose}"); // Verbose logging (only shows when verbose mode is on) this.Monitor.VerboseLog("Detailed debug info - only visible in verbose mode"); // Verbose with interpolated string (efficient - string not built if verbose is off) int expensiveValue = this.CalculateExpensiveValue(); this.Monitor.VerboseLog($"Expensive calculation result: {expensiveValue}"); // Structured error logging try { this.RiskyOperation(); } catch (Exception ex) { this.Monitor.Log($"Operation failed: {ex.Message}", LogLevel.Error); this.Monitor.Log(ex.ToString(), LogLevel.Trace); } } private int CalculateExpensiveValue() => 42; private void RiskyOperation() { throw new InvalidOperationException("Demo error"); } } ``` -------------------------------- ### Load and Save Mod Configuration Source: https://context7.com/pathoschild/smapi/llms.txt In your mod's Entry method, use `helper.ReadConfig()` to load the configuration. This method automatically creates a default config file if it doesn't exist. Use `helper.WriteConfig(this.config)` to save changes. ```csharp private ModConfig config = null!; public override void Entry(IModHelper helper) { // Load config (creates default if doesn't exist) this.config = helper.ReadConfig(); this.Monitor.Log($"Feature enabled: {this.config.EnableFeature}"); this.Monitor.Log($"Activate key: {this.config.ActivateKey}"); helper.Events.Input.ButtonPressed += this.OnButtonPressed; helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; } ``` ```csharp if (this.config.ActivateKey.JustPressed()) { // Toggle feature this.config.EnableFeature = !this.config.EnableFeature; this.Helper.WriteConfig(this.config); this.Monitor.Log($"Feature toggled to: {this.config.EnableFeature}"); } ``` -------------------------------- ### GET /mods/metrics Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/web.md Retrieves a summary of update-check metrics since the server was last deployed or restarted. ```APIDOC ## GET /mods/metrics ### Description The `/mods/metrics` endpoint returns a summary of update-check metrics since the server was last deployed or restarted. ### Method GET ### Endpoint /mods/metrics ### Request Example ```js GET https://smapi.io/api/v4.0.0/mods/metrics ``` ### Response #### Success Response (200) - **metrics** (object) - An object containing various update-check metrics. #### Response Example ```json { "totalChecks": 1500, "uniqueMods": 250, "checksPerDay": 50, "lastCheck": "2023-10-27T10:00:00Z" } ``` ``` -------------------------------- ### Get Button State Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Retrieve the low-level state of a specific button. This can be used for custom input handling. ```csharp helper.Input.GetState(SButton.LeftShift); ``` -------------------------------- ### Handle Peer Connected Source: https://context7.com/pathoschild/smapi/llms.txt Logs when a peer connects and sends a welcome message to them. This is a good point to establish initial communication. ```csharp private void OnPeerConnected(object? sender, PeerConnectedEventArgs e) { this.Monitor.Log($ ``` -------------------------------- ### Check if Game is Launched Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `Context.IsGameLaunched` to determine if the game has been fully launched. ```csharp if (Context.IsGameLaunched) { // Game is launched. } ``` -------------------------------- ### Get Patch Helper for Arbitrary Data Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `helper.Content.GetPatchHelper` to apply patch helpers to data other than standard assets. ```csharp var patchHelper = this.Helper.Content.GetPatchHelper(contentPath); // ... use patchHelper ... ``` -------------------------------- ### Using ~/stardewvalley.targets File Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md SMAPI compilation now checks for and uses a `~/stardewvalley.targets` file if it exists in the project directory, allowing for custom build configurations. ```xml ``` -------------------------------- ### GET Request for SMAPI Metrics Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/web.md Use this endpoint to retrieve a summary of update-check metrics since the server was last deployed or restarted. ```javascript GET https://smapi.io/api/v4.0.0/mods/metrics ``` -------------------------------- ### Configure Local Development Environment Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/web.md Edit the appsettings.Development.json file to set Nexus API key and optional Azure Blob storage or GitHub credentials for running a local SMAPI web project. ```json { "NexusApiKey": "[Your Nexus API key]", "AzureBlobConnectionString": "The connection string for the Azure Blob storage account. Defaults to using the system's temporary file folder if not specified.", "GitHubUsername": "The GitHub credentials with which to query GitHub release info. Defaults to anonymous requests if not specified.", "GitHubPassword": "" } ``` -------------------------------- ### Enable RewriteInParallel for Faster Mod Loading Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Enable this experimental option in `smapi-internal/config.json` to potentially reduce startup time when loading mod DLLs. ```json { "RewriteInParallel": true } ``` -------------------------------- ### Handle Peer Context Received Source: https://context7.com/pathoschild/smapi/llms.txt Logs information about a received peer context, including their PlayerID and the mods they have installed if they are using SMAPI. ```csharp private void OnPeerContextReceived(object? sender, PeerContextReceivedEventArgs e) { // Earliest point to send messages - player not yet in game this.Monitor.Log($ ``` -------------------------------- ### Get SMAPI API for Input Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Accesses the input API to read and suppress keyboard, controller, and mouse input. This is available for mod authors. ```csharp helper.Input ``` -------------------------------- ### Get Translations in All Locales Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use helper.Translation.GetInAllLocales to retrieve a translation for a given key in every available locale. This is useful for mods that support multiple languages. ```csharp helper.Translation.GetInAllLocales ``` -------------------------------- ### Set SMAPI Version Script (PowerShell) Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/smapi.md Automates the process of updating the SMAPI version across multiple configuration files. Ensure PowerShell is installed on your system. ```powershell pwsh build/scripts/set-smapi-version.ps1 VERSION_HERE ``` -------------------------------- ### Content Loading API Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Introduction to `helper.GameContent` and `helper.ModContent`, which will replace `helper.Content` in SMAPI 4.0.0 for content loading. ```markdown Added [`helper.GameContent` and `helper.ModContent`](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Content_loading_API), which will replace `helper.Content` in SMAPI 4.0.0. ``` -------------------------------- ### Access Internal Menu State Source: https://context7.com/pathoschild/smapi/llms.txt Example of accessing internal state of a menu, specifically the 'forSale' field of a ShopMenu, to retrieve a list of items available for purchase. ```csharp // Access internal menu state if (e.NewMenu is ShopMenu shop) { // Get private field from shop menu IReflectedField> forSaleField = this.Helper.Reflection.GetField>(shop, "forSale"); List forSale = forSaleField.GetValue(); this.Monitor.Log($"Shop has {forSale.Count} items for sale"); } ``` -------------------------------- ### Developer Mode Command-Line Arguments Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Added command-line arguments to toggle developer mode. ```markdown Added [command-line arguments](technical/smapi.md#command-line-arguments) to toggle developer mode (thanks to Tondorian!). ``` -------------------------------- ### SemanticVersion Constructor Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md The `SemanticVersion` constructor now accepts a string representation of the version. This simplifies the creation of `SemanticVersion` objects from version strings. ```csharp var version = new SemanticVersion("1.2.3"); ``` -------------------------------- ### SMAPI Web Project Nexus API Key Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md No longer requires a Nexus API key to launch the `SMAPI.Web` project locally. ```markdown You no longer need a Nexus API key to launch the `SMAPI.Web` project locally. ``` -------------------------------- ### Get Private Property via Reflection Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `helper.Reflection.GetPrivateProperty` to access and retrieve the value of a private property from a given object. This is useful for interacting with internal game or mod states. ```csharp var privateValue = helper.Reflection.GetPrivateProperty(myObject, "PrivateFieldName"); ``` -------------------------------- ### Use Project Version in Manifest Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Utilize the %ProjectVersion% token in your manifest.json file to automatically sync the mod version with your project's version. This is useful for both main mods and bundled content packs. ```json { "Name": "Your Mod Name", "Author": "Your Name", "Version": "%ProjectVersion%", ... } ``` -------------------------------- ### JSON Schema Usage in Manifest Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/web.md Reference SMAPI schemas directly in your JSON files using the `$schema` field for editor support. This example shows how to reference the manifest schema. ```js { "$schema": "https://smapi.io/schemas/manifest.json", "Name": "Some mod", ... } ``` -------------------------------- ### Configure Unit Test Project Settings Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Recommended settings for a Stardew Valley unit test project. These settings disable game debugging, mod deployment, and zip creation, while enabling the bundling of all extra assemblies. ```xml false false false All ``` -------------------------------- ### Register a Console Command Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use `helper.ConsoleCommands.Add` to register a new console command. This method requires a command name and a callback function that accepts arguments. ```csharp helper.ConsoleCommands.Add("my_command", "My command description.", (args) => { // command logic here }); ``` -------------------------------- ### Register Multiplayer Event Handlers Source: https://context7.com/pathoschild/smapi/llms.txt Register event handlers for various multiplayer events like peer context, connection, disconnection, and mod messages. Includes an example of broadcasting a message on a button press. ```csharp using StardewModdingAPI; using StardewModdingAPI.Events; public class MultiplayerMod : Mod { public override void Entry(IModHelper helper) { helper.Events.Multiplayer.PeerContextReceived += this.OnPeerContextReceived; helper.Events.Multiplayer.PeerConnected += this.OnPeerConnected; helper.Events.Multiplayer.PeerDisconnected += this.OnPeerDisconnected; helper.Events.Multiplayer.ModMessageReceived += this.OnModMessageReceived; helper.Events.Input.ButtonPressed += (s, e) => { if (e.Button == SButton.F7 && Context.IsWorldReady) this.BroadcastMessage(); }; } private void OnPeerContextReceived(object? sender, PeerContextReceivedEventArgs e) { // Earliest point to send messages - player not yet in game this.Monitor.Log($ ``` -------------------------------- ### Implement Core Mod Interface (IMod) Source: https://context7.com/pathoschild/smapi/llms.txt Implement the IMod interface by extending the Mod base class and overriding the Entry method. This method is called when the mod is loaded and provides access to the IModHelper. Subscribe to game events and optionally expose an API for other mods. ```csharp using StardewModdingAPI; using StardewModdingAPI.Events; public class MyMod : Mod { public override void Entry(IModHelper helper) { // Log that the mod loaded successfully this.Monitor.Log("MyMod loaded!", LogLevel.Info); // Subscribe to game events helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; helper.Events.GameLoop.SaveLoaded += this.OnSaveLoaded; helper.Events.GameLoop.DayStarted += this.OnDayStarted; } private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { this.Monitor.Log("Game launched - all mods are loaded and initialized"); } private void OnSaveLoaded(object? sender, SaveLoadedEventArgs e) { this.Monitor.Log($"Save loaded: {Game1.player.Name} on {Game1.currentSeason}"); } private void OnDayStarted(object? sender, DayStartedEventArgs e) { this.Monitor.Log($"Day {Game1.dayOfMonth} of {Game1.currentSeason}, Year {Game1.year}"); } // Optional: Expose an API for other mods to use public override object? GetApi() { return new MyModApi(); } } public class MyModApi { public int GetSomeValue() => 42; } ``` -------------------------------- ### Include Content Packs in Project Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Add this ItemGroup to your .csproj file to bundle content packs with your main mod. Ensure the Include path points to your content pack folder and the Version property matches your mod's version. ```xml ``` -------------------------------- ### Implement Translation System in a SMAPI Mod Source: https://context7.com/pathoschild/smapi/llms.txt This C# code demonstrates how to use the ITranslationHelper to get translations, handle missing keys, and access all available translations and locales. Ensure your mod has an 'i18n' folder with JSON translation files. ```csharp using StardewModdingAPI; using StardewModdingAPI.Events; public class TranslationMod : Mod { public override void Entry(IModHelper helper) { helper.Events.GameLoop.GameLaunched += this.OnGameLaunched; helper.Events.Content.LocaleChanged += this.OnLocaleChanged; } private void OnGameLaunched(object? sender, GameLaunchedEventArgs e) { // Simple translation string greeting = this.Helper.Translation.Get("greeting"); this.Monitor.Log(greeting); // Translation with tokens string welcome = this.Helper.Translation.Get("welcome-player", new { playerName = "Alex" }); this.Monitor.Log(welcome); // Translation with default fallback Translation itemDesc = this.Helper.Translation.Get("item.description"); if (!itemDesc.HasValue()) this.Monitor.Log("Translation missing!"); // Check if key exists if (this.Helper.Translation.ContainsKey("special-event")) this.Monitor.Log(this.Helper.Translation.Get("special-event")); // Get all translation keys foreach (string key in this.Helper.Translation.GetKeys()) this.Monitor.VerboseLog($"Translation key: {key}"); // Get all translations foreach (Translation translation in this.Helper.Translation.GetTranslations()) this.Monitor.VerboseLog($"{translation}: {translation.ToString()}"); // Get translation in all locales var allLocales = this.Helper.Translation.GetInAllLocales("greeting"); foreach (var (locale, translation) in allLocales) this.Monitor.Log($"[{locale}] {translation}"); } private void OnLocaleChanged(object? sender, LocaleChangedEventArgs e) { this.Monitor.Log($"Language changed to: {this.Helper.Translation.Locale}"); } } ``` ```json { "greeting": "Hello!", "welcome-player": "Welcome, {{playerName}}!", "item.description": "A mysterious item.", "special-event": "Something special is happening!" } ``` ```json { "greeting": "Bonjour!", "welcome-player": "Bienvenue, {{playerName}}!" } ``` ```json { "greeting": "Hallo!", "welcome-player": "Willkommen, {{playerName}}!" } ``` -------------------------------- ### Abruptly Exit Game During Initialization Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Monitor.ExitGameImmediately allows for quicker abortion of SMAPI initialization and events. ```csharp Monitor.ExitGameImmediately now aborts SMAPI initialization and events more quickly. ``` -------------------------------- ### Replace ISemanticVersion.Build with PrereleaseTag Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md The `ISemanticVersion.Build` property is deprecated. Use `ISemanticVersion.PrereleaseTag` instead for handling pre-release version information in your mod's versioning. ```csharp /// Gets the pre-release tag (e.g., 'beta', 'alpha.1'). ``` -------------------------------- ### Set Project Version for Manifest Token Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Define the project version in your .csproj file. This version can then be referenced in your manifest.json using the %ProjectVersion% token. ```xml 1.0.0 ``` -------------------------------- ### Global Targets File Configuration Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/mod-package.md Create a stardewvalley.targets file in your user home directory to apply build properties globally to all mod projects. Add properties within the PropertyGroup tags. ```xml ``` -------------------------------- ### Constants.ContentPath Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Added `Constants.ContentPath` to retrieve the full path to the game's `Content` folder. ```csharp Added `Constants.ContentPath` to get the full path to the game's `Content` folder. ``` -------------------------------- ### List or Search Current Harmony Patches Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md Use the `harmony_summary` console command to list or search for active Harmony patches within SMAPI. ```bash harmony_summary ``` -------------------------------- ### SDate Utility for Date Calculations Source: https://github.com/pathoschild/smapi/blob/develop/docs/release-notes-archived.md The `SDate` utility provides methods for in-game date calculations. Refer to the API reference for detailed usage. ```csharp SDate today = SDate.Now(); SDate tomorrow = today.AddDays(1); ``` -------------------------------- ### Integrate with Generic Mod Config Menu Source: https://context7.com/pathoschild/smapi/llms.txt Register your mod with the Generic Mod Config Menu API to allow users to configure your mod in-game. Use `Register`, `AddBoolOption`, `AddNumberOption`, and `AddKeybindList` to define configurable options. ```csharp // Integration with Generic Mod Config Menu (optional) var configMenu = this.Helper.ModRegistry.GetApi("spacechase0.GenericModConfigMenu"); if (configMenu == null) return; configMenu.Register( mod: this.ModManifest, reset: () => this.config = new ModConfig(), save: () => this.Helper.WriteConfig(this.config) ); configMenu.AddBoolOption( mod: this.ModManifest, name: () => "Enable Feature", getValue: () => this.config.EnableFeature, setValue: value => this.config.EnableFeature = value ); configMenu.AddNumberOption( mod: this.ModManifest, name: () => "Max Items", getValue: () => this.config.MaxItems, setValue: value => this.config.MaxItems = value, min: 1, max: 999 ); configMenu.AddKeybindList( mod: this.ModManifest, name: () => "Activate Key", getValue: () => this.config.ActivateKey, setValue: value => this.config.ActivateKey = value ); ``` -------------------------------- ### Register Custom Console Commands in C# Source: https://context7.com/pathoschild/smapi/llms.txt Register simple, argument-based, and debug commands using the ICommandHelper. Ensure the game world is loaded before executing commands that interact with game state. ```csharp using StardewModdingAPI; public class CommandMod : Mod { public override void Entry(IModHelper helper) { // Simple command helper.ConsoleCommands.Add( "mymod_test", "Tests the mod. Usage: mymod_test", this.TestCommand ); // Command with arguments helper.ConsoleCommands.Add( "mymod_give", "Gives items to the player. Usage: mymod_give [amount]", this.GiveCommand ); // Debug command helper.ConsoleCommands.Add( "mymod_debug", "Shows debug information. Usage: mymod_debug [category]", this.DebugCommand ); } private void TestCommand(string command, string[] args) { if (!Context.IsWorldReady) { this.Monitor.Log("Load a save first!", LogLevel.Warn); return; } this.Monitor.Log("Test successful!", LogLevel.Info); this.Monitor.Log($"Player: {Game1.player.Name}", LogLevel.Info); this.Monitor.Log($"Location: {Game1.currentLocation.Name}", LogLevel.Info); } private void GiveCommand(string command, string[] args) { if (!Context.IsWorldReady) { this.Monitor.Log("Load a save first!", LogLevel.Warn); return; } if (args.Length == 0) { this.Monitor.Log("Usage: mymod_give [amount]", LogLevel.Info); return; } string itemId = args[0]; int amount = args.Length > 1 && int.TryParse(args[1], out int parsed) ? parsed : 1; var item = ItemRegistry.Create(itemId, amount); if (item == null) { this.Monitor.Log($"Unknown item: {itemId}", LogLevel.Error); return; } Game1.player.addItemToInventory(item); this.Monitor.Log($"Gave {amount}x {item.Name} to player", LogLevel.Info); } private void DebugCommand(string command, string[] args) { string category = args.Length > 0 ? args[0].ToLower() : "all"; switch (category) { case "player": this.Monitor.Log($"Name: {Game1.player.Name}", LogLevel.Info); this.Monitor.Log($"Money: {Game1.player.Money}g", LogLevel.Info); this.Monitor.Log($"Position: {Game1.player.Position}", LogLevel.Info); break; case "time": this.Monitor.Log($"Day: {Game1.dayOfMonth} {Game1.currentSeason} Y{Game1.year}", LogLevel.Info); this.Monitor.Log($"Time: {Game1.timeOfDay}", LogLevel.Info); break; case "all": default: this.Monitor.Log($"Player: {Game1.player.Name}", LogLevel.Info); this.Monitor.Log($"Day: {Game1.dayOfMonth} {Game1.currentSeason} Y{Game1.year}", LogLevel.Info); this.Monitor.Log($"Location: {Game1.currentLocation?.Name ?? "null"}", LogLevel.Info); break; } } } ``` -------------------------------- ### Clone SMAPI Repository Source: https://github.com/pathoschild/smapi/blob/develop/docs/technical/smapi.md Use this command to download the SMAPI source code from GitHub. This is the first step for any manual build or compilation process. ```sh git clone https://github.com/Pathoschild/SMAPI.git ```