Books
Status: experimental.
What it is
This API lets your mod see the books the player has collected and whether each one has been read. It also tells you when the player reads a book for the first time. It covers books only. A single note is not a book and never counts as read here. To open a page in the notes window, use Hud.ShowNote (see the Hud page).
When to use it
- Show a message or give a reward the first time the player reads a certain book.
- Check whether the player has read a book before a quest step goes on.
- List the books the player is holding, with their titles and page counts.
Quick example
This mod writes a log line when the player reads a book. Once a save is loaded, it also logs every book the player holds.
using ObenseuerCore;
[ModInfo("example.bookwatch", "Book Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class BookWatch : FrameworkMod
{
protected override void OnLoad()
{
Books.Read += args => Log.Info("The player read " + args.Title);
Events.WhenReady(() =>
{
foreach (BookEntryInfo book in Books.All())
Log.Info(book.Title + " (" + book.PageCount + " pages), read: " + Books.IsRead(book.Id));
});
}
}
Read is an event: the framework calls your code each time it happens. See Using events for more on events and WhenReady.
Common tasks
Count the books the player has read.
int read = 0;
foreach (BookEntryInfo book in Books.All())
if (Books.IsRead(book.Id)) read++;
Log.Info(read + " books read.");
Check one book by its id.
if (Books.IsRead("some-book-id")) Log.Info("The player has read it.");
Reference
This is the full list of what the API gives you. Text in brackets at the end of a note is extra detail you can skip on a first read.
A book in your list is a BookEntryInfo. The list only has books the player is holding right now, because the game has no list of every book that exists.
Books the books the player holds
Use All to list the books the player holds, IsRead to check one by its id, and listen to Read to hear when a book is read.
public static class Books
{
public static bool IsAvailable { get; }
public static List<BookEntryInfo> All();
public static bool IsRead(string id);
public static event Action<BookReadArgs> Read;
}
| Method | Returns | What it does |
|---|---|---|
All() | List<BookEntryInfo> | The books the player holds, in the order the game keeps them. |
IsRead(id) | bool | true when the player has read the book. false when the player does not hold it. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the read event could not be set up. |
| Event | Gives you | When it fires |
|---|---|---|
Read | BookReadArgs | The player has just read a book. |
BookEntryInfo one book the player holds
All gives you one of these for each book the player holds. Its Id is what IsRead takes.
public sealed class BookEntryInfo
{
public string Id { get; }
public string Title { get; }
public int PageCount { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The book’s id. Use it with IsRead. |
Title | string | The book’s title. |
PageCount | int | How many pages the book has in total, not only the ones the player holds. |
BookReadArgs what you get when a book is read
Read hands you this when a book goes from unread to read. Id and Title say which book it was.
public sealed class BookReadArgs
{
public string Id { get; }
public string Title { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | The book’s id. |
Title | string | The book’s title. |
Things to know
All()only lists books the player holds. The game has no list of every book, so a book the player has not found does not appear. (the list comes from the game’s note collection (NoteController.notes); an entry with no book in it is skipped)- A book’s
Idis the first of these that the game has set: its id, its asset path, or its title. (this isBookInfo.Idin the game) IsRead(id)says whether the player has read the book. It returnsfalsefor a book the player does not hold.- When a new page arrives in a book, the game marks the whole book unread again. (it reads the
readflag of the player’sCollectedNoteInfofor that book) Readfires when a book goes from unread to read. That happens when the player selects the book in the notes window.Readfires again after a new page arrives and the player reads the book again. It does not fire for a book that is already read. (it is raised afterNoteUIPanel.Selectedwhen thereadflag changed from false to true)- A note is not a book. Single notes, including the notes the player writes in the notebooks in their inventory, are not books and never raise
Read.
Limits
- A
nullid passed toIsReadalso returnsfalse, and one line about it goes to the log. - The framework does not hold back
Readwhile a save is loading. A handler that should ignore events raised then can checkSaveData.Busy, which istruewhile a save is loading or being written. - You can’t add pages to a book or mark a book as read. This API only reads. Single notes, which are not books, are on the Notes page.
Details
Target: Obenseuer 0.5.08 (buildid 25969064), Unity 6000.0.77f1 Mono, BepInEx 6 Unity.Mono (be.788, the tested build) or BepInEx 5 (5.4.23 or later). Namespace: ObenseuerCore. Logs go to BepInEx\logs\obenseuer.moddingframework.books.log.
Everything not listed in this reference is internal and may change without notice. Items marked experimental may change too.
- The framework changes one of the game’s methods when it starts, so it can tell your mod when a book is read.
- Its feature group, the name you will see in the log, is
obenseuer.moddingframework.books.
When you can call it
- Call everything from your mod’s normal code (
OnLoad, event handlers,Update), not from a background thread you started. The game only runs safely on the main thread (see the glossary). - It is safe to call any of it at any time, even on the main menu. With nothing to read,
All()gives an empty list andIsReadgivesfalse. Neither depends onIsAvailable. Books.IsAvailabletells you whetherReadworks. It isfalsewhen the framework could not set up the read event, or when the feature group was turned off.- If the game fails while the framework reads, you get the plain default (an empty list or
false) instead of an error.
If something fails to set up
- The framework changes some of the game’s methods when it starts. If a game update breaks one of them, the framework turns off the feature group that depends on it, and writes error lines and a warning to the group’s log file in
BepInEx\logs. The rest of the framework keeps working. - If the change to the notes window fails, the feature group turns off and
Readstops. One warning goes to the log.All()andIsReadkeep working. (the method isNoteUIPanel.Selected. The log line reads “book events not installed, Read is disabled”)