Feature Random Books - bh679/dungeon-train-mc GitHub Wiki
Home > Features > Random Books
Standalone flavour books that spawn in carriage chests as vanilla written books — rolled from a JSON pool at carriage-spawn time, tracked per-world so re-reads don't repeat until everything's been seen.
A parallel content track to the Narrative Lectern. Where the lectern delivers long-form ordered narratives one Letter at a time, Random Books are short standalone musings that get scattered through carriage loot at varied weights. As of v0.184.0, both systems share world-scoped progression.
Two layers of randomness:
-
Pool layer: which book file wins the slot (weighted by each book's
weightfield). -
Variant layer: which musing within that book is shown (uniform within the book's
variantsarray).
A single editor-only placeholder item dungeontrain:random_book is the only thing authors interact with. At chest-spawn time, ContainerContentsRoller.rollItemStack substitutes it with a stamped vanilla WRITTEN_BOOK from the pool. Players never see the placeholder — only the rolled written book sits in the chest.
A second, organic source alongside the editor placeholder. When a player breaks a vanilla minecraft:bookshelf, each book that would drop has a 1-in-N chance — server config narrative.randomBookFromBookshelfOneIn, default 100 (≈1%); 0 disables — to be replaced by a Random Book rolled from the same pool via RandomBookFactory.rollFromPool. The total drop count is unchanged: one plain book is swapped for one narrative book.
Handled by RandomBookDropEvents, a server-side BlockDropsEvent listener:
-
Only
minecraft:bookshelf— Silk Touch drops the block (no books → natural no-op), and chiseled bookshelves are excluded so player-stored books are never converted. - Only player-broken blocks.
- New
ItemEntitys inherit the replaced drop's position + velocity, so a book dropped on a moving Sable carriage rides the ship exactly like the books it replaces.
Freshly-rolled books carry no held marker, so per BurnableBookTag (see Starting Books' burn lifecycle) they are non-burnable until a player picks them up — they land on the ground and can be collected, just like a random book falling out of a broken pot.
Every spawned random book carries an identity tag (bookBasename, variantIndex) in DataComponents.CUSTOM_DATA. When any player on the world equips the book (hand-held in main or off-hand), a LivingEquipmentChangeEvent handler:
- On first equip of that tuple → mark it seen on the world in
NarrativeProgressData, do nothing else. - On equip of an already-world-seen tuple → ask
RandomBookFactory.pickUnseenForWorldfor an alternative, mutate the stack'sWRITTEN_BOOK_CONTENTand identity tag in place, mark the new tuple world-seen.
Either branch stamps a sticky dt_random_book_held marker on the stack. v0.397.0 (PR #655) fixed the handler to bail out immediately when that marker is already set — previously, reselecting the same held book in the hotbar (which re-fires LivingEquipmentChangeEvent for MAINHAND) would re-run step 2 every time and keep swapping the stack to a new random title/body. Now a book's content locks permanently the first time it's held; subsequent hotbar reselects are no-ops.
When every variant of every loaded book has been seen by the world, the random-book tracking resets silently and the cycle starts over.
Pre-mutation happens on equip (not on right-click to read) because WrittenBookItem.use opens the book screen client-side from the local stack before the server's response arrives — mutating later is too late.
-
Drop a JSON file in
data/dungeontrain/narratives/random_books/<basename>.json:{ "id": "musings_of_faulthurst", "title": "Musings of Faulthurst", "author": "Faulthurst", "generation": 0, "weight": 3, "variants": [ "First musing body...", "Second musing body...", "..." ] }-
weight= pool-level rarity vs other books -
generation= vanilla 0–3 (Original / Copy / Copy of Copy / Tattered) - Each
variantsentry becomes one chosen-at-spawn book body, auto-paginated to vanilla limits
-
-
Place
dungeontrain:random_bookinto any container variant via the Container Contents Menu (C-key editor) or directly in a<prefab>.jsonloot template. Set itscountandweightlike any other item entry. -
/dungeontrain narrative reloadto pick up new pool files without restarting the server.
| Command | Purpose |
|---|---|
/dungeontrain narrative randombook list |
Chat-print every loaded random book with variant count + weight + generation |
/dungeontrain narrative randombook give [basename] |
Give the calling player a rolled vanilla written book; no basename → pool-weighted random pick |
/dungeontrain narrative randombook progress |
Show the world's seen-variant count per book |
/dungeontrain narrative randombook reset |
Clear the world's random-book read tracking |
/dungeontrain narrative reload |
Re-scan both random books and stories |
All gated on permission level 2 (OP).
v0.182.0 (PR #228) seeds dungeontrain:random_book into 16 prefab loot tables at varied weights — pot is the most book-heavy at weight 9, narrative/themed prefabs (mining, villager, horse, loot_irongold) at 2, the rest at 1. The bundled pool ships one book — musings_of_faulthurst (16 short musings on the train and its passengers).
v0.265.1 (PR #328) adds bookshelf-break drops (see above) as a second source, and rebalances the bricks/fish/mining prefab pools (raising non-book weights / lowering random_book weight) so random books read rarer in chest loot.
A new Dungeon Train — Narrative creative tab (icon: vanilla written book) groups the two narrative-system items:
-
Random Book(orange Knowledge Book parent — visually distinct so authors don't confuse it with real written books in the editor) -
Narrative Lectern(also still in vanilla Functional Blocks)
World-scoped tracking lives in the existing NarrativeProgressData saved-data (overworld-keyed). The map shape is Map<bookBasename, Set<variantIndex>> serialised under random_books at the root of <world>/data/dungeontrain_narratives.dat. Pre-v0.184.0 saves had a per-player layout (TAG_PLAYER_RANDOM_BOOKS under each player entry); on first load with v0.184.0+ that legacy data is silently dropped and the world starts fresh.
Tracking is world-scoped, so every player shares the same seen-set. The first player to encounter a (book, variant) tuple marks it world-seen; subsequent holders of an already-seen tuple get swapped to an unseen pick. The mutate-on-equip approach still races if two players grab tuples on the same tick — fine for single-player and small-server use.
- Lectern-mounted random books (lecterns remain narrative-story-only)
- Per-player chest view (the second opener of a chest sees what the first opener resolved)
- Custom 16×16 texture for the placeholder (Knowledge Book parent works for MVP; can upgrade)
- Tag-based per-biome pool filtering (
tagsis parsed but unused — reserved for a future placement system)
- Feature: Narrative Lectern — the lectern-based ordered-story track
- Feature: Narrative Loader — the per-Letter content loader the random-books loader sits next to
- Feature: Container Contents Menu — the C-key editor that authors random_book into chest pools
-
Feature: Prefab Library — the prefab loot tables
random_bookis seeded into