Compatibility
Minecraft: Java Edition
Platforms
Creators
Details
🧩 What is Narrativa?
Narrativa is a modular dialogue and choice system for Minecraft datapacks, built with Beet and Bolt. It allows creators to define structured, multi-line conversations in .yml files (JSON format), compile them into datapacks, and execute them in-game using a lightweight runtime.
Narrativa is a framework, not a content pack. It does not ship with built-in storylines, quests, or gameplay content beyond minimal examples. Instead, it provides the infrastructure for you to design and implement your own narrative systems.
Core capabilities include:
- Structured, tellraw-based dialogue display
- Branching choice menus with automatic /trigger handling
- Optional automatic voice line generation (Autodub)
- Clean module separation between engine and content
- Compatibility with custom interpreters if desired
Narrativa abstracts the complexity of dialogue state management, step progression, and choice resolution, enabling mapmakers and datapack developers to focus on writing narrative content rather than low-level command logic.
TL/DR
A dialogues & choices system for Minecraft datapacks, built with Beet and Bolt, supporting structured JSON/YML dialogues and optional autogenerated voice lines.
🚀 Quick Start & Examples
Everything in the examples folder works the way it was intended, please report any bugs or issues.
Run the following command in-game to start the example Hello World dialogue:
/function ceevyte:dialogues/en_us/example/hello_world
It will explain the basics to you. At the end of the dialogue, there will be choice options (which is its own system).
Pick either [2] Cheese. for the showcase of capabilities in-use; or [3] Could you explain it a bit more? for a more technical explanation of how to actually use it.
📚 Setup & Documentation
To simplify your pain in the back, here is the technical explanation directly from the bot:
⚠️ Warning: Keep Narrativa in a separate datapack from your main project. Failing to do so will cause static dialogues to rebuild on every
beet build. This is undesirable.
Step 1: Folders
Place the dialogues folder at the top level of your project.
Place your .yml files inside it.
Please keep the name & path of it consistent everywhere.
Reference them from a module.
Step 2: Modules
Create a module. Recommended name: X_narrativa_content.bolt.
Register it in beet.json under meta/bolt/entrypoint, next to narrativa.bolt.
Important: Inside your newly created content module, you must import the Narrativa module (both should be in the same folder):
import ./narrativa as Narrativa
Step 3: Autodub & Sounds (Optional)
Autodub generates voice lines automatically.
- Place your dialogue file in the
scriptfolder, rename itinput.yml, and run the script. (Output:.oggfiles from 0 to N. Requires enough different English voices to be installed on your system). - Open
sounds.py. Add dialogue lines to thespecsarray as shown in examples. - Run the script. (Output:
sounds.json). - Place it in your resourcepack. Ensure the
.oggfile path insounds.pymatches the actual file location.
💬 Dialogues
Inside your .yml files, you'll quickly notice that the YML format is simply JSON here. Imagine yourself writing a /tellraw command, where arrays inside the main array each are a new line.
Dialogue Format
Dialogues must follow this strict structure:
DialogueArray[ IndividualLine[ FirstList{ Components } ] ]
🛑 Deviation from this structure will cause in-game silent errors. There are no exceptions. Yeah, I know, or otherwise it just breaks.
Example of a valid input structure:
[
[{"text": "<username> Hello there!"}],
[{"text": "I'm inside a dialogue. Cool, right?"}]
]
(Autodub works by inserting an autodub component inside of the first list of the line, which contains an indexed sound name).
Loading a Dialogue
To import the YML file as a Dialogue, structure your function like this:
function START_FUNCTION:
Narrativa.newDialogue(Narrativa.loadDialogue(
"PATH_TO_DIALOGUE_FILE.yml"
), "AUTODUB_SOUND_NAME")
Example:
function username:dialogues/example/hello_world:
Narrativa.newDialogue(Narrativa.loadDialogue(
"dialogues/example/hello_world.yml"
), "username:dialogues.example.hello_world.")
(You can also build your own interpreter: The Narrativa.newDialogue(ArrayJSON) just requires a valid /tellraw command by the structure mentioned above).
Dialogue Trigger Example
Since this is an example, we'll do a simple dialogue trigger. Just call function ceevyte:narrativa/dialogue/_/step when it does trigger:
Show Trigger Code (Flip-Flop Logic)
append function_tag minecraft:load {
"values": [
"username:dialogue/load"
]
}
append function_tag minecraft:tick {
"values": [
"username:dialogue/tick"
]
}
predicate username:dialogue/trigger {
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"type_specific": {
"type": "minecraft:player",
"input": {
"forward": false,
"backward": false,
"left": false,
"right": false,
"jump": false,
"sneak": false,
"sprint": true
}
}
}
}
function username:dialogue/load:
scoreboard objectives add username.dialogue.trigger dummy {"text": "Dialogue Trigger Flip-Flop", "color": "gold"}
function username:dialogue/tick:
execute as @a[tag=ceevyte.narrativa.dialogue.active]:
execute if score @s[predicate=!username:dialogue/trigger] username.dialogue.trigger matches 1:
scoreboard players reset @s username.dialogue.trigger
execute unless score @s[predicate=username:dialogue/trigger] username.dialogue.trigger matches 1..:
function ceevyte:narrativa/dialogue/_/step
scoreboard players set @s username.dialogue.trigger 1
By running beet build, the dialogues should be compiled into a datapack properly.
🔀 Choices
This one is a lot easier, and requires less fiddling with my awful code. Choice system requires three calls:
Narrativa.newChoice()at the start of your function.- Send a
tellrawwith your choice menu. Style is yours to define. - Call
Narrativa.lockChoice(...)at the end.
How Clicks Work
Use click_event to run_command, but instead of running an actual command, just put Narrativa.choiceCounter() there. It automatically generates a /trigger command, so it will work even without the cheats being on!
Full Choice Example
function ceevyte:choices/example/hello_world:
Narrativa.newChoice()
tellraw @s [
{
"text": "\n"
},
{
"text": "— [1] Wowww, that's so cool :0",
"color": "gray",
"click_event": {
"action": "run_command",
# This function exists so that you don't have to type out the numbers manually,
# and you can thank me later :^
"command": Narrativa.choiceCounter()
},
"hover_event": {
"action": "show_text",
"value": [
{
# You can do like, descriptions and stuff, but that's just
# generic Json tellraw.
"text": "False flattery. Classic."
}
]
}
},
{
"text": "\n"
},
{
"text": "— [2] Cheese.",
"color": "gray",
"click_event": {
"action": "run_command",
"command": Narrativa.choiceCounter()
},
"hover_event": {
"action": "show_text",
"value": [
{
"text": "I... honestly don't remember putting this in. O_o"
}
]
}
},
{
"text": "\n"
},
{
"text": "— [3] Could you explain it a bit more?",
"color": "gray",
"click_event": {
"action": "run_command",
"command": Narrativa.choiceCounter()
},
"hover_event": {
"action": "show_text",
"value": [
{
"text": "Nerd."
}
]
}
}
]
Narrativa.lockChoice(
[
# [1]
{
"function": "ceevyte:dialogues/example/hello_world/that_s_so_cool"
},
# [2]
{
"function": "ceevyte:dialogues/example/hello_world/cheese"
},
# [3]
{
"function": "ceevyte:dialogues/example/hello_world/could_you_explain"
}
]
)
Note: Each "function" parameter in lockChoice correlates to the according choice option. This can be any function.
❓ Commonly Asked Questions
Q: Do I really need to load the narrativa.bolt as a module?
A: Yes, the
narrativa.boltis the Library module itself, and is required to be loaded by beet, preferably before thenarrativa_contentmodules.
Q: Why is there code instead of just .mcfunction?
A: Beet.
Q: Can I rewrite your stupid code and make a better one?
A: PLEASE DO.
Q: How do I install Beet properly?
Q: How to install the datapack?
A: Either do
beet link (your world path at "../saves/")in VSCode's console, or drag them there manually OR download the latest Release from this repository.
Q: Will it be updated further?
A: Of course, I need to fix everything here, lol.


