Skip to content

C# API

This page is for SMAPI mod authors who want to open a book from their own code. If you're using Content Patcher, see Opening a book instead.

To build a book in code rather than open an existing one, see Building books in C#.

Getting the API

Copy this interface into your mod:

public interface IParchmentApi
{
    /// <summary>Opens a book, optionally at a chapter.</summary>
    bool TryOpenBook(string bookId, string chapterId = null);

    /// <summary>Opens a book at a page number.</summary>
    bool TryOpenBookAtPage(string bookId, string chapterId, int page);

    /// <summary>Opens a book at a page's PageData.Id.</summary>
    bool TryOpenBookAtPageId(string bookId, string chapterId, string pageId);

    /// <summary>Starts building a book in code.</summary>
    IBookBuilder CreateBook(string bookId);

    /// <summary>Removes a book your mod registered.</summary>
    bool TryUnregisterBook(string bookId, out string error);

    /// <summary>Gets whether a book with the given ID is loaded, from any source.</summary>
    bool HasBook(string bookId);

    /// <summary>Marks a book your mod registered as needing a rebuild before it's next opened.</summary>
    bool TryMarkBookStale(string bookId, out string error);

    /// <summary>Reads a variable a book declares.</summary>
    bool TryGetVariable(string bookId, string variableId, out string value);

    /// <summary>Sets a variable a book declares.</summary>
    bool TrySetVariable(string bookId, string variableId, string value, out string error);
}

CreateBook needs three more interfaces

IBookBuilder, IPageBuilder and IElementBuilder are listed in Building books in C#. Copy all four or drop CreateBook from your copy, since GetApi returns null when it can't map every member.

Then fetch it once both mods have loaded in GameLaunched:

private IParchmentApi parchment;

public override void Entry(IModHelper helper)
{
    helper.Events.GameLoop.GameLaunched += OnGameLaunched;
}

private void OnGameLaunched(object sender, GameLaunchedEventArgs e)
{
    parchment = Helper.ModRegistry.GetApi<IParchmentApi>("PeacefulEnd.Parchment.Core");
}

Use the full unique ID

Parchment's UniqueID is PeacefulEnd.Parchment.Core. GetApi returns null when the ID doesn't match, and a null API is easy to mistake for Parchment not being installed. The same ID goes in your manifest's Dependencies.

Opening a book

// The book's first page.
parchment?.TryOpenBook("YourMod_FieldGuide");

// The first page of a chapter.
parchment?.TryOpenBook("YourMod_FieldGuide", "appendix");

// A specific page, by its PageData.Id.
parchment?.TryOpenBookAtPageId("YourMod_FieldGuide", "appendix", "mushrooms");

// A specific page, searching every chapter for the ID.
parchment?.TryOpenBookAtPageId("YourMod_FieldGuide", null, "mushrooms");

// A chapter-relative page number (0-based).
parchment?.TryOpenBookAtPage("YourMod_FieldGuide", "appendix", 2);

// A page number counted across the whole book (0-based).
parchment?.TryOpenBookAtPage("YourMod_FieldGuide", null, 5);

Parameters

Parameter Type Meaning
bookId string The book's Id from Data/PeacefulEnd.Parchment/Books (its BookData.Id), not the qualified item ID.
chapterId string A page's ChapterId. Pass null to work across the whole book.
pageId string A page's Id (its PageData.Id). Scoped to chapterId when you pass one.
page int A 0-based page number, relative to chapterId when you pass one and to the whole book otherwise.

Every method returns true when the book was found and opened and false when the book, chapter or page couldn't be resolved. Failures are logged with the reason, including when a book was dropped during loading because its data was invalid.

HasBook covers books from content packs and from the C# API alike, so it's the way to check for an optional book from another mod before offering to open it:

if (parchment.HasBook("someone.Else_Book") is true)
{
    parchment.TryOpenBook("someone.Else_Book");
}

Keeping a registered book current

A book your mod registered stays as it was registered until you register it again. When its contents follow something that changes often, rebuilding on every change means rebuilding for readings that may never happen.

TryMarkBookStale says a rebuild is owed without doing one. Parchment runs the book's OnRefresh callback before the next opening, whichever route opens it:

// Cheap, so it can be called on every change
parchment.TryMarkBookStale("{{ModId}}_Logbook", out string error);
Returns false when
No book ID was given
Your mod hasn't registered that book A book from a content pack or another mod can't be marked
The book has no refresh callback Nothing would run at the next opening, so this reports rather than doing nothing

See Rebuilding before the book opens for the callbacks themselves, and for OnOpening when the book has to be rebuilt every reading rather than only after a change.

Reading a book's variables

TryGetVariable and TrySetVariable reach the variables a book declares, which is how a settings page in a book ends up in your own config file. Values are text whatever the variable's declared type, so parse them on the way out.

if (parchment.TryGetVariable("{{ModId}}_Almanac", "showSpoilers", out string value) is true)
{
    this.Config.ShowSpoilers = bool.Parse(value);
    this.Helper.WriteConfig(this.Config);
}

TrySetVariable fails when the book declares no variable by that name, when the value doesn't suit the declared type or AllowedValues, or when a Save-scoped variable is set with no save loaded. The reason comes back in error and is logged.

Variables don't reach Content Patcher, so a pack's other patches can't see one. This API is the bridge for a C# mod, not for a content pack.

Building a book

CreateBook returns a builder for assembling a book in code, either registered alongside content pack books or opened on the spot. See Building books in C#.