A server-side Minecraft quest engine built on Fabric — featuring a pub-sub event architecture, MongoDB persistence, NPC dialogue trees, and a charge-based warp system with custom mathematical models.
Samsara Questing is a full-featured quest framework for Fabric Minecraft servers. Administrators define quests, NPC dialogue chains, and rewards through in-game commands. Players receive quest books, interact with custom NPCs, complete multi-step objectives, and earn item or XP rewards — all tracked in real time against a MongoDB backend designed to scale across multiple game servers.
On top of the quest engine, the mod ships two custom gameplay items:
-
Hearthstone — a charged warp stone that teleports the player to a pre-set location
-
Soulstone — teleports the player back to their last death location, and survives death in the player's inventory
The core of the quest system is a subject-listener pattern layered over Fabric's event API. Rather than polling each game tick, the mod registers lightweight ActionSubscription records against typed subjects. When a Fabric event fires, the corresponding subject dispatches to all matching
subscriptions, updates MongoDB, and detaches completed listeners — all inline, without a separate state polling loop.
Player Action
└─► Fabric Event Hook
└─► Subject (Kill / Collect / Talk / Break / SetSpawn / DoQuest)
└─► ActionSubscription (playerUuid, questUuid, objectiveUuid, target)
└─► MongoDB update
└─► Listener detachment on completion
Each subject holds a Map<playerUuid, List<ActionSubscription>>. On player join, subscriptions are rebuilt from the database (warm-start). On disconnect, they are cleared. Safe iterator removal allows inline detachment during event dispatch without ConcurrentModificationException.
Six Mixin injection points extend vanilla behavior without modifying base classes:
| Mixin | Target | Purpose |
|---|---|---|
PlayerInventoryMixin |
addStack() |
Ground item pickups for COLLECT objectives |
SlotMixin |
onTakeItem() |
Inventory UI interactions for COLLECT objectives |
PlayerSpawnMixin |
setSpawnPoint() |
Bed/respawn placement for SET_SPAWN objectives |
PlayerTravelMixin |
player movement | Movement detection for warp cancellation |
VillagerEntityMixin |
villager logic | Trade-based objective hooks |
TradeOutputMixin |
trade output slot | Trade completion tracking |
| Concern | Mechanism |
|---|---|
| Warp stone delays | ThreadPerTaskExecutor — one thread per warp, isolated and cancellable |
| Async DB ops | ScheduledExecutorService (2-thread pool) |
| Main-thread side effects | questRunnables queue drained on END_SERVER_TICK |
| Teleport state | ConcurrentHashMap<uuid, TeleportTask> — lock-free reads |
| Type | Trigger |
|---|---|
KILL |
Entity killed matching a target name (case-insensitive) |
COLLECT |
Item added to inventory (ground pickup or UI) |
TALK |
Player right-clicks a tagged NPC |
DO_QUEST |
Another quest completed |
SET_SPAWN |
Player sets a respawn point |
BREAK_BLOCK |
Player breaks a block matching target |
Quests move through a state machine managed in RightClickActionEventManager:
- Start node interaction — NPCs flagged as start nodes auto-advance the player to their first incomplete quest in the chain
- Dialogue cycling — NPC dialogue advances on each interaction, with a 33-second temporal offset to prevent accidental skips; the offset map is evicted on server tick
- Book grant — when dialogue completes, a dynamic written book is generated with color-coded objective progress (red to green)
- Objective completion — subjects dispatch and count against
requiredCount; all objectives done triggers a completion sound sequence - Submission — player returns to NPC; COLLECT objectives require holding the item; TALK objectives advance on interaction
- Reward and trigger — item stacks, XP, and arbitrary server commands execute; next quest in the NPC chain auto-attaches
Quest books are generated dynamically on open. A hashCode() comparison on the book's NBT detects stale content and regenerates without any separate versioning field. Objectives not yet started are omitted to keep the book focused.
WrittenBookPageBuilder wraps Minecraft's text component API for readable construction of multi-page content with inline color, click events, and hover text.
Both warp stones share a charge system defined in WarpStone.java.
Charging uses Ender Pearls with a success probability that decreases as the stone accumulates charges:
p(c) = alpha / (c + alpha) where alpha = 2.52
alpha is derived so that the expected total pearls to reach 64 charges is exactly 864 (two double chests):
pearls(N) = N * (N + 4.04) / 5.04
| Target charges | Expected pearls |
|---|---|
| 8 | ~19 |
| 16 | ~64 |
| 32 | ~229 |
| 64 | 864 |
The formula creates a soft cap through probability alone — no hard limit exists. Getting the first 16 charges is cheap; the last 16 charges to 64 cost more pearls than the first 48 combined.
Warp cost scales exponentially with XZ distance to the destination:
cost(d) = max(1, round(2^(d / 2000)))
| Distance | Cost |
|---|---|
| 2,000 blocks | 2 charges |
| 4,000 blocks | 4 charges |
| 6,000 blocks | 8 charges |
| 8,000 blocks | 16 charges |
| 12,000 blocks | 64 charges |
This incentivizes traveling partway toward the destination on foot before warping.
Because Minecraft's server-side API has no item hover event, cost is updated lazily on right-click. If the stored last-shown cost does not match the current cost, the mod updates the item lore, plays a refresh sound, and spawns a particle burst around the player — instead of starting the warp. The second click (with cost unchanged) initiates teleportation. This gives the player clear feedback while avoiding silent cost changes mid-warp.
Before a player dies, ServerLivingEntityEvents.ALLOW_DEATH fires and the Soulstone is removed from inventory and cached in memory. After respawn, ServerPlayerEvents.AFTER_RESPAWN re-grants it. The stone never hits the death loot table and cannot be lost.
| Collection | Indexes |
|---|---|
playerCharacters |
uuid (unique), name (unique, background) |
quests |
uuid (unique), title (unique, background) |
nonPlayerCharacters |
uuid (unique), name (unique, background) |
Full object state is serialized to BSON via hand-written toDocument() / fromDocument() methods — no reflection or annotation-based ORM. This keeps serialization explicit and debuggable.
The MongoDB backend allows the quest engine to operate across multiple game servers sharing a single database — player progress is consistent regardless of which server they join.
ItemStackFactory parses a structured string DSL for reward definitions:
minecraft:compass{100,64,3,minecraft:overworld} -> Lodestone tracker
minecraft:bundle{minecraft:diamond,5,gold_ingot,3} -> Filled bundle
minecraft:filled_map{42} -> Map item
minecraft:diamond_sword[enchants=sharpness:5] -> Enchanted item
Config is auto-generated at ./config/questing/fabric_quest.config on first run:
MONGO_URI=mongodb://localhost:27017
DATABASE_NAME=samsara
IS_WELCOMER_ENABLED=true
REQUIRED_WELCOME_QUEST_TITLE=Traveler Call To Action
FIRST_JOIN_COMMAND=/time set 23300
All commands require the samsara.quest.admin permission node (op level 2).
/quest add npc [isStartNode] <name> Create a new NPC
/quest spawn npc <uuid> Summon an NPC entity
/quest config trigger <uuid> <event> <cmd> Attach a lifecycle command
/questLog Open your quest journal
/quest add npc [isStartNode] <name> Create a new NPC
/quest spawn npc <uuid> Summon an NPC entity
/quest config trigger <uuid> <event> <cmd> Attach a lifecycle command
/questLog Open your quest journal
/questLog view <player> Admin: view another player's journal
/hearthstone create <pos> <dimension> <name> Give a hearthstone bound to a location
/soulstone <charges> Give a soulstone with N charges
- Minecraft 1.21.11 · Fabric Loader 0.18.4 · Fabric API 0.141.1
- Java 21 with virtual thread executor (
ThreadPerTaskExecutor) - MongoDB Sync Driver 5.1.3
- LuckPerms / Fabric Permissions API for command gating
- Sponge Mixin for bytecode injection
- Brigadier (Minecraft's command framework) with argument autocomplete """
-
Goal: Inspect Other Quest Plugin For Objectives
-
Event Hooks on Wiki @see https://wiki.fabricmc.net/tutorial:event_index
-
Modify Block Data
https://wiki.fabricmc.net/tutorial:datagen_setup https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack https://minecraft.wiki/w/Data_component_format#use_cooldown
https://minecraftitemids.com/color-codes https://text.datapackhub.net/ https://mcstacker.net/murals/
Resource Pack Ideas:
https://www.reddit.com/r/Minecraft/comments/1osp13c/some_hotbar_concepts_for_my_pack_cant_decided/
Custom Dimensions https://minecraft.wiki/w/Dimension_definition https://misode.github.io/dimension/ https://minecraft.wiki/w/Data_pack
https://www.reddit.com/r/Minecraft/comments/1ov1p69/wave_animation_with_arrows/
boss fight insp https://orian34.github.io/travelogues/posts/finalparadox/ https://www.youtube.com/watch?v=fT-E5h6caSI
setup discord bot:
analytics dashboard: https://github.com/plan-player-analytics/Plan/wiki/Bungee-Set-Up https://hangar.papermc.io/AuroraLS3/Plan-Player-Analytics/versions/5.6+build.2965 https://hangar.papermc.io/cubxity/UnifiedMetrics