Skip to content

Dean904/fabric-questing

Repository files navigation

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.

Minecraft Fabric Java MongoDB


What It Does

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


Architecture

Event-Driven Pub/Sub

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.

Mixin-Based Hooks

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

Threading Model

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

Quest System

Objective Types

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

Quest Lifecycle

Quests move through a state machine managed in RightClickActionEventManager:

  1. Start node interaction — NPCs flagged as start nodes auto-advance the player to their first incomplete quest in the chain
  2. 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
  3. Book grant — when dialogue completes, a dynamic written book is generated with color-coded objective progress (red to green)
  4. Objective completion — subjects dispatch and count against requiredCount; all objectives done triggers a completion sound sequence
  5. Submission — player returns to NPC; COLLECT objectives require holding the item; TALK objectives advance on interaction
  6. Reward and trigger — item stacks, XP, and arbitrary server commands execute; next quest in the NPC chain auto-attaches

Dialogue and Quest Books

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.


Warp Stone Mechanics

Both warp stones share a charge system defined in WarpStone.java.

Harmonic Charge Decay

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.

Distance-Based Warp Cost

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.

First-Click Cost Refresh UX

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.

Soulstone Death Persistence

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.


Data Layer

MongoDB Collections

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.

Item Rewards

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

Configuration

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

Commands

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

Stack

  • 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 """

Notes

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/

https://www.reddit.com/r/selfhosted/comments/1pwy8ar/is_pterodactyl_still_the_go_to_for_server_hosting/

boss fight insp https://orian34.github.io/travelogues/posts/finalparadox/ https://www.youtube.com/watch?v=fT-E5h6caSI

setup discord bot:

https://appy.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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages