NPC Helper 2/Docs

For Developers

C# SDK

Define NPCs in C# and generate a TOML content pack or a SMAPI DLL mod with the NpcHelper.StardewValley.Sdk package.

The SDK is a NuGet package for writing NPCs in C# instead of TOML. It has no dependency on SMAPI or Stardew Valley, so it works in a build script, a console tool, or a SMAPI mod. The runtime is always the normal NPC Helper 2 mod, which players must have installed.

You can use the SDK two ways:

Output What you get Use when
TOML content pack A pack of plain files, with no DLL You want a normal pack but prefer to define the NPC in code.
C# DLL mod A SMAPI mod that writes the same files at startup Your NPCs are part of a larger C# mod.

Both produce NPCs the runtime loads identically.

Step 1: Install the package#

In your C# project:

sh
dotnet add package NpcHelper.StardewValley.Sdk

With the .NET 10 SDK or later, dotnet package add NpcHelper.StardewValley.Sdk also works.

You can also install the command-line tool, which uses the SDK.

Step 2: Define an NPC#

csharp
using NpcHelper.Sdk;

var rei = new NpcBuilder("Rei")
    .DisplayName("Rei the Baker")
    .Gender(Gender.Female).Age(AgeGroup.Adult).Manner(Manner.Polite)
    .Birthday(Season.Summer, 12)
    .Spawn("Town", 10, 12, Facing.Down)
    .SpriteFile("art/rei_sprite.png").PortraitFile("art/rei_portrait.png")
    .SpriteVariantFile("Hospital", "art/rei_hospital.png")
    .Introduction("Hi! I'm Rei.")
    .Dialogue("Mon", "Another week.")
    .GiftReaction(GiftTaste.Love, "You remembered!")
    .Loves("Diamond", "Coffee").Hates("Trash")
    .OnWeekdays(s => s.At(600, "Hospital", 1, 18, Facing.Right).At(12, 30, "Town", 29, 54))
    .OnWeekends(s => s.At(900, "Beach", 20, 20))
    .OnDate(Season.Summer, 12, s => s.At(1000, "Town", 5, 5))
    .Spouse(m => m.EngagementAccept("Yes!").OnWeekdays(s => s.At(800, "FarmHouse", 8, 8)))
    .Locale("th", l => l.DisplayName("เรย์").Dialogue("Introduction", "สวัสดี"));

Each method matches a setting in the TOML files:

  • SpriteFile, PortraitFile, and SpriteVariantFile take image paths and copy them to sprites/ and portraits/.
  • The taste methods, such as Loves and Hates, accept item names, IDs, qualified IDs such as (O)74, category numbers such as -75, and context tags such as egg_item.
  • Dialogue takes any dialogue key, including AcceptGift_* keys.
  • Spouse(...) defines engagement lines and the married schedule.
  • Locale(code, ...) adds a translation.

Schedules#

Schedule scopes (together they cover every key the game reads): OnWeekdays, OnWeekends, OnDays(days, stops, season?, hearts?), OnDate(season, day), OnDayOfMonth(day, stops, hearts?), OnRainyDays(stops, secondVariant?), OnGreenRain, OnFestival(id, stops, day?), InSeason, Otherwise, OnKey(key, stops), and RawSchedule(key, value).

Each takes a callback that lists the stops:

Call Adds
At(time, map, x, y, facing?, action?, endMessage?, arriveBy?) A stop at a game time such as 600.
At(hour, minute, map, x, y, ...) A stop at a clock time.
AtSamePlace(time, x, y) A stop at a new tile in the same place.
GoToBed(time) The NPC goes to bed.
StartAt(map, x, y) Where the NPC is when the day starts.

And commands: GoTo(key) or GoTo("season"), NoSchedule(), and UnlessFriendsWith(npc, hearts).

The same scopes exist inside .Spouse(...). For how spouse keys are written, see Marriage.

Heart events and festivals#

csharp
new NpcBuilder("Rei")
    .HeartEvent("RainyClinic", "Hospital", 4, e => e
        .Weather("rainy").Between(900, 1500)
        .Farmer(10, 18, Facing.Up).Npc(8, 12, Facing.Down)
        .Wait(500)
        .Say("You came in out of the rain.$h")
        .Walk("Rei", Facing.Down, 2)
        .Emote("Rei", "heart"))
    .FestivalDialogue("spring13", "I only eat them.", spouseDialogue: "Hold my basket, dear.");

The SDK writes Events.toml and Festivals.toml and reports a bad heart level, an incomplete scene, or a bad festival ID when the NPC is validated. See Heart events and festivals.

Game options#

The SDK enums mirror the game's real option sets: Gender, AgeGroup, Manner, SocialAnxiety, Optimism, Season, HomeRegion, Facing, GiftTaste, NpcLanguage, SocialTabBehavior, CalendarBehavior, EndSlideShowBehavior, and ItemCategory (all 39 categories). On/off options have builder methods too, such as DarkSkinned, CanReceiveGifts, and CountsForPerfection. Game options lists every value.

If your file also has using StardewValley;, Gender and Season become ambiguous with the game's types. Add these aliases:

csharp
using Gender = NpcHelper.Sdk.Gender;
using Season = NpcHelper.Sdk.Season;

Step 3: Validate#

Everything is checked as you build.

csharp
ValidationReport report = rei.Validate();

Validate() returns a report of errors and warnings. Build() throws an NpcDefinitionException that lists every problem.

Output 1: a TOML content pack#

csharp
var result = new NpcPackBuilder("Me.ReiPack", "Rei Pack")
    .Author("Me")
    .Add(rei)
    .WriteTo("out/[HP] ReiPack");

Console.WriteLine(result.Ok ? "valid" : result.Report.ToString());
  1. NpcPackBuilder takes the pack's unique ID and display name.
  2. WriteTo writes manifest.json and the standard layout (Pack structure).
  3. It then loads the pack back the way the game does, so result.Report shows exactly what the game would say.
  4. Zip it with npchelper pack and publish it (Test and publish).

NpcBuilder.ToToml() returns just the main TOML text if that is all you need.

Output 2: a C# DLL mod#

  1. Create the project:

    sh
    npchelper new dll-mod MyMod --id Me.MyMod --npc Rei
    
  2. Edit Npcs.cs to define your NPCs.

  3. Build with dotnet build.

The generated mod entry gets NPC Helper 2's API and registers each NPC:

csharp
var api = Helper.ModRegistry.GetApi<INpcHelperApi>(NpcHelperIds.UniqueId);
api.Register(npc, Path.Combine(Helper.DirectoryPath, "generated"), msg => Monitor.Log(msg, LogLevel.Warn));

Register validates the NPC, writes it in the standard layout under generated/, and registers it with the runtime. The runtime then picks up images, dialogue, schedule, marriage, and locale files from that folder. The mod's manifest lists NPC Helper 2 as a required dependency, so SMAPI loads it first and tells players if it is missing.

What the SDK talks to#

The SDK's INpcHelperApi is a subset of the runtime's Mod API that uses only basic types (strings and string lists), the form SMAPI's API proxy can connect between two separately loaded mods.

Safety#

  • Writing never touches a folder it did not create. A non-empty folder that the SDK did not generate is refused (IOException) unless you pass overwrite: true.
  • A generated folder is rebuilt from scratch every run, so renamed or removed images do not linger. Edit the C#, not the generated files.
  • Names, variant keys, locale codes, and unique IDs are validated, so nothing can write outside the target folder.
  • Keep the pack's ContentPackFor.MinimumVersion at or above what your features need (NpcHelperIds.MinimumVersion).