Real dialogue screens for your NPCs — a portrait, what the character says, and the player's answers as buttons. Every answer can branch the conversation, open a menu or run commands.
A Paper plugin by Social Studio.
It uses the native dialog screens Minecraft introduced in client version 1.21.6. The verified server target for this JAR is Paper 26.1.2 / Java 25. No client mod is required. Native buttons and plain free-row text need no resource pack; the optional RPG panel appearance uses the button pack bundled with the plugin.
Each image groups real frames of the same kind of screen. The individual frames are in
docs/screenshots/. NPCs, the world, quests, Codex content, fonts and other
server resource-pack customisations shown here are not supplied with the plugin.
- Branching conversations written in YAML: nodes, answers, and where each answer leads.
- Actions on any answer: go to another node or dialogue, open a menu, run a player or console command, send a message / title / action bar, play a sound.
- Button sizes and icons: four button sizes like the pause menu (from the tiny square to full width) and a set of quest symbols (1.1.2) — quest, main / side quest / riddle markers, accept, back, exit, reward, story and chance. Plain text: no resource pack.
- The NPC's face (1.1.1): a 2D face from the NPC's skin next to its name.
- Free layouts (1.1.2):
layout: [1, 2, 2, 1]puts any number of buttons on each row — the shape Minecraft's own dialog grid can't do. - RPG buttons (1.1.2): with the optional button pack (offered after join when delivery is enabled), those
buttons use a flat dark background and a double border coloured by their function. The same
simple design applies to every NPC and chapter. Without the pack:
[ text ]. - Locked answers and navigation (1.1.2): answers you can't pick yet show greyed out with a hint, a Back button that remembers the way, an Exit pinned to the bottom, and per-node titles.
- Deliveries (1.1.1): check, take and give items — vanilla, UltraBoss trophies, or any item by its hidden tag, model or name. Taking is all-or-nothing and is checked again on click (no dupes).
- Story locks and quests (1.1.1): conditions on BeautyQuests quests (
quest: completed 12), its own BeautyQuests stage type, and placeholders for affinity and flags. - Conditions that hide answers or change where a talk starts — permissions, player flags, and any
PlaceholderAPI placeholder (
%vault_eco_balance% >= 1000). - Player flags: remember that someone already met a character, accepted a job or finished a step, and greet them differently next time. Stored on the player, no database.
- Affinity (1.1): a hidden friendship level per player and per character. Kind answers raise it, rude ones lower it, and new greetings, questions and secrets unlock as it grows. Players are never told the number changed.
- Conversations that never repeat (1.1): several variants per line (a different one every time) and questions that rotate, so talking to the same NPC twice doesn't feel scripted.
- Portraits: the NPC's own skin, any player's skin, a fixed texture, or any item.
- FancyNpcs support out of the box: link a dialogue to an NPC by name and clicking it opens the
talk instead of its FancyNpcs actions. Your
npcs.ymlis never touched. - Any entity can talk too, with a tag:
/tag @e[type=villager,limit=1,sort=nearest] add dialogue:merchant. - Old clients joining through ViaVersion (< 1.21.6) get the same conversation in chat, with clickable answers.
- English and Spanish built in.
One file per character in dialogues/. The file name is the id.
name: '&2&lThe Wandering Merchant'
npcs: [merchant] # FancyNpcs NPC names
portrait: 'npc | item:EMERALD' # the NPC skin, or an emerald if it has none
start:
- if: '!flag:met_merchant' # first time...
node: intro
- node: hello # ...and every time after
nodes:
intro:
on-show: ['flag: met_merchant']
text:
- 'Psst! You, the one with empty pockets. Come closer.'
next: hello # no answers = a "Continue" button
hello:
text: 'My stock changes every &fMonday&f.'
answers:
- text: '&2Show me your goods'
actions: ['menu: shop'] # opens a DeluxeMenus menu
- text: '&7Where do you get all this?'
tooltip: '&7Hover text'
goto: secret
- text: '&8Not interested'
goto: end
secret:
text: 'A good merchant never reveals his suppliers.'dialogues/example.yml documents every option with comments.
| Action | What it does |
|---|---|
goto: node |
Go to another node (end finishes the talk) |
dialogue: id [node] |
Switch to another dialogue |
menu: name |
Open a menu (runs menu-command from config.yml, DeluxeMenus by default) |
command: spawn |
Run a command as the player |
console: give %player% diamond 1 |
Run a command from the console |
message: · broadcast: · actionbar: |
Text to the player / everyone / action bar |
title: Title|Subtitle |
On-screen title |
sound: entity.villager.yes 1 1 |
Also accepts ENTITY_VILLAGER_YES |
flag: x · unflag: x |
Set or remove a player flag |
affinity: +2 · affinity: -3 · affinity: =10 |
Change affinity with this character (affinity: kadir +2 for another one) |
close |
Close the screen |
When an answer ends the talk (opens a menu, runs a command…) the screen closes before the actions run, so the menu stays open.
Every line of the list must match; inside a line, || means "or".
permission:group.vip !permission:group.vip
flag:met_merchant !flag:met_merchant
%vault_eco_balance% >= 1000 ( == != >= <= > < contains )
affinity >= 20 affinity:kadir >= 20
Every player has a hidden affinity with every character, starting at 0. Answers change it, and conditions read it — so the same NPC can be cold with strangers and open up to friends:
start:
- if: 'affinity >= 25'
node: friend
- node: stranger
nodes:
stranger:
variants: # a different one each time, never the same twice in a row
- 'Yes? What do you want?'
- "I don't usually talk to strangers."
show: 2 # only 2 of these questions appear, picked at random
answers:
- text: '&7Hello. Who are you?'
actions: ['affinity: +2']
goto: who
- text: '&8Out of my way.'
actions: ['affinity: -3']
goto: end
- text: '&8Goodbye.'
always: true # never rotated out
goto: end- The player never sees the number and is never told it changed. Staff can check it with
/ud affinity <player> <id>. - Anti-farming: a character only gives affinity once every
cooldown-minutes(30 by default), and at mostmax-per-talkpoints (3) per conversation. Losing affinity has no limit. - Defaults live in
config.ymlunderaffinity:; each dialogue can override them with its ownaffinity:section. - It only exists in dialogues that use it. Menu NPCs that don't mention affinity behave exactly as before.
- A variant can have its own
if:({text: ..., if: 'affinity >= 10'}).
dialogues/kadir.yml is a complete example with four friendship levels, rotating questions and a
one-time gift.
nodes:
confirm:
text: 'Will you take the job?'
columns: 2 # this node only: two buttons per row
answers:
- text: '&aYes'
icon: accept
size: 2 # half width, like "Options..."
goto: accepted
- text: '&cNo'
icon: exit
size: 2
goto: end
- text: ''
icon: story # size 1 fits only the icon, like the "report" button
size: 1
tooltip: '&7What is this job?'
goto: details| Size | Width | Like in the pause menu |
|---|---|---|
size: 1 |
20 px | the small square "report" button |
size: 2 |
98 px | "Options...", half width |
size: 3 |
150 px | a medium button |
size: 4 |
204 px | "Disconnect", full width |
width: 130 |
any, 1–1024 | exact width |
Native buttons are 20 px tall. Pack-drawn free-row panels are 24 px tall. columns: works on
the native grid for the whole dialogue or a single node; layout: controls free rows.
Icons (/ud icons lists them in game). Since 1.1.2 they're text symbols, so they look the
same with or without a resource pack:
| Name | English | Symbol | Meaning |
|---|---|---|---|
mision |
quest |
! (orange) |
there's a quest |
principal |
main |
• (green) |
marker: main quest |
secundaria |
side |
• (blue) |
marker: side quest |
acertijo |
riddle |
• (purple) |
marker: riddle |
aceptar |
accept |
✓ (green) |
accept |
volver |
back |
« |
back |
salir |
exit |
× (red) |
exit |
premio |
reward |
± (yellow) |
quest rewards |
cuento |
story |
♪ (light blue) |
a story the NPC tells for a quest |
probabilidad |
chance |
% |
something may happen (or drop) by chance |
Several icons on one button, separated by spaces: icon: quest side shows ! • with the blue
marker. [quest, side] and quest+side work too.
1.1.2 replaced the 20 pixel-art icons of 1.1.1 (
heart,gem,gift,star,shop…). An icon that no longer exists doesn't break anything: the button shows without it and the console warns once per dialogue. Text symbols need no pack.resource-pack.enabled: falsedisables the internal button-pack server/public-IP lookup; use it for text-only presentation or when another plugin delivers a merged pack. Update removed icon-only answers so their labels are not blank.
The NPC's face: portraits npc, player:Name and texture:... now show as a 2D face from the
skin, next to the NPC's name. The client renders the face; player-name portraits can also use
asynchronous profile resolution through the server API. item: portraits show the item on the left.
nodes:
quests:
title: '&6Quests of Brann' # screen heading for this node only
text: 'Pick a job.'
columns: 2
answers:
- text: 'Side quest A'
icon: quest
size: 2
goto: job_a
- text: 'Friends only'
icon: quest
size: 2
if: 'affinity >= 20'
locked: '&7Brann needs to trust you more.' # greyed out instead of hidden
goto: job_b
- text: 'Main quest'
icon: quest
size: 4 # full width: moved to the last row by itself
goto: main
nav:
- back # previous screen of this conversation
- text: '&eReward' # any answer works as a nav button
actions: ['console: give %player% emerald 1']
- exit # pinned to the bottom of the screenlocked:needs anif:. While the condition fails the answer shows inscreen.locked-colorand clicking it only shows the hint in the action bar.locked: trueuses the language's text. It's re-checked on every click like any answer, so nothing runs while it's locked.backwalks back through the screens of this conversation, even into another dialogue. On the first screen it closes the talk (it's always shown, so the grid keeps its shape).exitgoes in the footer. Minecraft only has room for one fixed button there, sobackand the rest ofnav:go in the last row of the grid.
nodes:
menu:
title: '&6Quests of Brann'
text: 'What do you want to ask?'
layout: [1, 2, 2, 1] # buttons per row, top to bottom
answers:
- {text: '&fMain quest', icon: quest main, goto: main}
- {text: '&fSide quest', icon: quest side, goto: q1}
- {text: '&fSide quest', icon: quest side, goto: q2}
- {text: '&fRiddle', icon: quest riddle, if: 'affinity >= 10', locked: '&7Not yet.', goto: q3}
- {text: '&fA story', icon: story, goto: q4}
- {text: '&7Just passing by', goto: end}
nav: # real buttons, all on one row, in this order
- back
- exit
- {text: '&eReward', icon: reward, actions: ['console: give %player% emerald 1'], goto: menu}- Rows are filled with the answers in order. Answers left over go one per row.
nav:buttons are real Minecraft buttons, all on one row at the bottom, in the order you write them — soback,exitand a "Reward" sit side by side (with columns = number of buttons, the native grid allows it). Withoutnav:, a "Goodbye" button goes at the bottom.- How it works: Minecraft only lets servers lay out real buttons in a uniform grid, but the
text in the dialog body is clickable and every line is free. In this mode each button is a piece
of text with its own click, drawn as
[ text ]and padded with spaces so all rows line up. The plugin measures the text with the real Minecraft font, so columns stay aligned within a few pixels. - Trade-off: the text buttons don't light up on hover like real ones (the tooltip still shows);
the
nav:row does, since those are real. If you want every button real, leavelayout:out. - Use
locked:instead of a bareif:when you can: a hidden answer shifts every button after it to another row. The console warns when the number of answers doesn't match the layout. - Change the look in
config.yml→screen.layout:button-format/locked-format({text}is the answer) andgap(spaces between buttons).
Players who loaded the plugin's pack see every layout: button as a panel: a background, a border
tinted by the button's type, and the text centred on top. Everyone else sees [ text ]. Nothing
else changes — the pack only draws.
# config.yml → screen.layout
buttons: auto # auto = loaded pack only · pack = merged/external pack · text = plain text
row-width: full
look: filled
style: liso
border: doble
force: true # same background and border on every dialogue
force-colors: true # semantic colours, ignoring per-dialogue colour overrides
border-colors:
default: '#FFFFFF' # question / no icon
mision: '#F2B21E'
principal: '#4CD964'
secundaria: '#4F8BFF'
acertijo: '#B36BFF'
aceptar: '#4CD964'
volver: '#C6E86B'
salir: '#E04B4B'
premio: '#FFD23F'
cuento: '#7FD6FF'
probabilidad: '#FF9F43'
locked: '#5A5A5A'The active appearance is simple for all panels: no biome scenes, no themed textures, just a
flat dark interior and a double frame. The last coloured icon sets the frame colour:
icono: mision secundaria draws a gold !, a blue •, and a blue frame. Locked panels are grey.
Legacy style IDs and assets remain accepted for compatibility. To restore per-dialogue style /
border, set force: false; to enable border-color / fill-color overrides, set
force-colors: false. The default uniform theme needs neither. Native nav: buttons are unchanged.
How: the pack adds a font, ultradialogue:boton, whose "letters" are button pieces — caps,
segments of 1 to 128 px and negative spaces. The plugin lays them out to the exact width, draws the
border on top (tinted with the text colour, so any colour works without touching the pack) and
centres the label. The pack is served by the plugin on port 8083 and stacks with other packs.
Buttons are 24 px tall (the background inside a 4 px frame). row-width: full | fit | <px> chooses
between rows that line up and rows as wide as their text. The pack also darkens Minecraft's in-world
menu background so the world behind the dialogue doesn't distract (every in-world menu is affected).
Limits (Minecraft's, not the pack's): panels don't light up on hover, and only the text line in
the middle of the panel is clickable. The nav: row is made of real buttons and does light up.
Minecraft splits a dialog's buttons into rows of columns buttons each. Only one leftover button
at the very end gets a row of its own. So:
| Layout | Possible? |
|---|---|
| 2 + 2 + 1 (full-width last) | ✅ — UltraDialogue moves the size: 4 answer there for you |
| 1 + 2 + 2 (full-width first) | ❌ with real buttons · ✅ with layout: |
| 2 + 2 + 2 + Exit fixed at the bottom | ✅ |
| Two fixed buttons at the bottom | ❌ — one fits; put the rest in nav: |
| Two-line buttons | ❌ — buttons are one line, 20 px tall |
| Three small buttons on one row (Back · Exit · Reward) | ✅ with layout: + nav: |
| Any row shape (1-2-2-1, 3-1, ...) | ✅ with layout: (text buttons) |
The pause menu does 1-2-2-1 with real buttons because it's built into the client; server dialogs
can't ask for that, which is why layout: uses text buttons instead.
| Condition | Action |
|---|---|
has: IRON_INGOT 16 (tiene:) — anywhere in the inventory |
take: IRON_INGOT 16 (quitar:) — all or nothing |
hand: IRON_INGOT 16 (mano:) — main hand only |
give: IRON_INGOT 16 (dar:) |
Items can be written as:
| Format | Matches |
|---|---|
IRON_INGOT / minecraft:iron_ingot |
that vanilla item — only plain ones: items with a plugin's hidden tag or their own model don't count, so a plain item never takes an UltraBoss trophy |
ultraboss:<id> |
an UltraBoss item (also the ones from its older names) |
ultrarevive:self / boost / end |
an UltraRevive totem |
pdc:<namespace:key>=<value> |
any plugin's hidden tag |
item_model:<namespace:model> |
by item model |
name:&6Cellar Key (nombre:) |
by display name (last resort) |
deliver:
text: 'Did you bring the iron for the forge?'
answers:
- text: '&aHand over 16 iron ingots'
icon: accept
if: 'has: IRON_INGOT 16'
actions:
- 'take: IRON_INGOT 16'
- 'give: EMERALD 4'
- 'affinity: +3'
goto: thanks
- text: "&8I don't have them yet"
if: '!has: IRON_INGOT 16'
goto: endtake:is atomic: it counts first and takes nothing if there isn't enough. Main hand first, then the rest of the inventory.- If
take:fails, the rest of that answer's actions are cut — a reward is never given without collecting first. The player gets theitem-missingmessage. - Conditions are checked again when the button is clicked. Open the dialogue with 16 ingots, drop them, press "Hand over": nothing is taken, nothing is given, the screen redraws.
give:builds vanilla items itself; UltraBoss and UltraRevive items are given through their own command, so they come out with every tag, name and texture.- A start rule can react to what the player is holding:
if: 'hand: IRON_INGOT 16'.
Use quests (or flags, or affinity with another character) to decide what an NPC says — so players can't skip to the end of the story:
start:
- if: '!quest: completed 12'
node: too_early # "What are you doing here? You haven't even found the key."
- node: final_scene| Condition | True when |
|---|---|
quest: completed <id> (mision: completada) |
the player finished that BeautyQuests quest |
quest: started <id> (mision: en-curso) |
it's started and not finished |
quest: active <key> (mision: activa) |
the player is right now at an ULTRADIALOGUE stage with that key |
| Action | What it does |
|---|---|
quest: complete <key> (mision: completar) |
completes every ULTRADIALOGUE stage with that key the player is on |
quest: start <id> (mision: empezar) |
starts the quest, respecting its requirements |
quest: force <id> (mision: forzar) |
starts it without requirements |
The ULTRADIALOGUE stage type. UltraDialogue registers its own stage in BeautyQuests, so an NPC
with a dialogue can take part in quests natively:
# in the BeautyQuests quest file
'3':
stageType: ULTRADIALOGUE
key: brann_iron
npc: brann # optional, only for the description
customText: '§7Bring §f16 iron ingots §7to §fBrann'# in the dialogue
- text: '&aHand over the iron'
icon: accept
if: ['quest: active brann_iron', 'has: IRON_INGOT 16']
actions: ['take: IRON_INGOT 16', 'quest: complete brann_iron']It can also be created from BeautyQuests' in-game editor (it asks for the key). Two quests on the same key both advance.
quest: complete fires a plain Bukkit event, DialogueSignalEvent(player, key) — any plugin can
listen to it, with or without BeautyQuests.
Without BeautyQuests everything still loads: quest conditions are false, start/force do
nothing, and the console says so once per dialogue.
With PlaceholderAPI:
| Placeholder | Value |
|---|---|
%ultradialogue_affinity_<id>% |
the player's affinity with that character |
%ultradialogue_flag_<flag>% |
true / false |
Use them as real quest requirements in BeautyQuests (placeholderRequired), to lock menu items in
DeluxeMenus, or anywhere else.
All administrative commands require ultradialogue.admin (op by default). Aliases: /ud,
/dialogue, /dialogo. The legacy dialogos.admin permission grants it too.
| Command | |
|---|---|
/ud open <id> [player] [node] |
Open a dialogue by hand — works from console and from other plugins |
/ud reload |
Reload config, language and every dialogue |
/ud list |
Loaded dialogues and their NPCs |
/ud flags <player> [clear [flag]] |
See or clear a player's flags |
/ud affinity <player> [id] [set|add|reset] [n] |
See or change affinity (staff only; add ignores the anti-farming limits) |
/ud icons |
Every icon with its name, drawn the way that player sees them |
Players need no permission to talk to NPCs.
| Paper | 26.1.2 (verified server target) |
| Java | 25 |
The older-client ViaVersion fallback does not imply support for older servers. Folia is not supported. Bedrock/Geyser behaviour is not verified.
Everything else is optional and detected at runtime:
| Plugin | Used for |
|---|---|
| FancyNpcs | Clicking an NPC opens its dialogue |
| PlaceholderAPI | Placeholders in text, actions and conditions |
| DeluxeMenus | The menu: action (any menu plugin works — change menu-command) |
| ViaVersion | Sends old clients the chat version |
| BeautyQuests 2.1 | Quest conditions and actions, the ULTRADIALOGUE stage type |
Drop the jar in plugins/ and start the server. Three example dialogues are created.
Symbols do not need a pack. For internally served RPG panels, configure a reachable public
address and port (8083 by default). A bind failure is logged, but a firewall/NAT block may only
be visible as a client download failure. In buttons: auto, players get panels only after a
successful load; refusal/download failure leaves the text presentation. buttons: pack instead
assumes an external pack provider has delivered the font.
Data lives in plugins/UltraView/UltraDialogue/, the shared folder of the Ultra plugin family.
Your config and dialogues are never overwritten on update.
When you edit a dialogue, run /ud reload. If something is wrong the console tells you which file,
which node and which reference is broken. Close and reopen active conversations after reloading
their definitions: /ud reload does not cancel existing sessions. Pack delivery/port changes
require a server restart; reload does not restart the pack provider.
For an external provider such as EcoItems, merge the current UltraDialogue ZIP, regenerate that provider's pack and configure:
resource-pack:
enabled: false
screen:
layout:
buttons: packMerge these settings into the existing config; do not replace the whole file or duplicate its
screen: section. buttons: pack assumes the font is present and cannot confirm an external
provider's status. For the plugin's mode: url, serve the exact bundled pack bytes: its hash is
computed from the embedded ZIP, not from an arbitrary externally merged pack.
On upgrade, stop the server, replace only the JAR, and restart. Preserve
plugins/UltraView/UltraDialogue/ and player data. Existing explicit layout settings can override
the new defaults. Add/update only the chosen configuration keys; migrate removed icon names.
The bundled pack darkens the background of other in-world menus too; this is not dialogue-only. It does not change the player's blur setting. Custom fonts may alter label widths/alignment.
Internal delivery serves the ZIP over HTTP on the configured port. host: AUTO queries
api.ipify.org for the public IP; resource-pack.enabled: false disables the provider and that
lookup. Player-name portraits can use asynchronous profile resolution via the server API.
No Gradle, no Maven — just javac.
bash compilar.sh # -> a nearby Jars/ folder, or build/ when cloned standaloneDependencies download to ../_libs/ on first run. You need JDK 25: Paper 26 is compiled for
Java 25. Build this release with JDK 25: the script does not set --release, so a newer JDK
can increase the bytecode requirement. With an older JDK javac fails with a cannot access Player error that looks like an
API problem but isn't.
src/main/java/mc/gupe/ultradialogue/
UltraDialogue.java startup and reload
UltraView.java the shared plugins/UltraView/<Plugin>/ folder
Migration.java imports data from the private plugin this one came from
Registry.java reads dialogues/ and validates every reference
Dialogue.java the model: dialogue -> nodes -> answers
Screen.java draws each turn (native dialog or chat) and receives the answer
Actions.java runs actions
Conditions.java evaluates if:
Flags.java per-player flags (PersistentDataContainer)
Affinity.java per-player, per-character affinity and its anti-farming limits
Portraits.java the item next to the text (item: portraits)
Icons.java the 10 named text-symbol presets and the NPC's 2D face
PackManager.java serves the button pack and tracks confirmed loads
PackServer.java the small HTTP server behind it
NpcHook.java the FancyNpcs click, by reflection
Listeners.java entity tags, click cooldown, quit
Command.java /ud
Items.java has / hand / take / give and the item formats
QuestBridge.java what UltraDialogue needs from a quest plugin
DialogueSignalEvent the event behind "quest: complete"
hook/bq/ BeautyQuests: the bridge and the ULTRADIALOGUE stage (loaded only if present)
hook/papi/ the PlaceholderAPI expansion (loaded only if present)
Glyphs.java width of every character of the Minecraft font (for layout:)
Buttons.java builds the RPG panels from the pack's font
Lang.java / Text.java messages, colours, placeholders
src/main/resources/
config.yml
lang/{en,es}.yml
dialogues/{example,merchant,kadir}.yml
resourcepack.zip the RPG button pack (built by TexturePacks/UltraDialogue/generar_pack.py)
glyph-widths.bin width of every character of the Minecraft font, in half pixels
Every screen carries a token. A stale button — from a previous screen, an old chat line, or
from a different conversation session — does nothing. If you touch Screen, every show() must mint a new token.
The native screen uses afterAction(NONE), not WAIT_FOR_RESPONSE. With WAIT_FOR_RESPONSE,
closing the dialog sends the client back to the screen it had before the wait — the same dialog —
so "Goodbye" looked like it didn't work the first time. Every path in answer() must end in show()
or close().
Classes that import another plugin live in hook/ and are loaded by name only when that plugin
is enabled. Never import BeautyQuests or PlaceholderAPI classes from the core: without them the JVM
would throw NoClassDefFoundError and the whole plugin would fail to start.
layout: buttons are body text, not buttons. The client hands clicks on dialog body text to
DialogScreen.runAction, the same path as a real button, so they arrive as custom_click_action.
Each button is one component carrying the click; its children (padding included) inherit it, so the
text-line padding is clickable. The drawn panel is taller than that line, so do not assume a full rectangular GUI hitbox. Glyphs is generated from the 26.1.2 client font; if Mojang changes the
font, regenerate it or rows drift by a few pixels.
Conditions are checked twice: when the screen is drawn and again on click (Screen.answer).
Don't remove the second check — it's what stops item dupes.
Panel delivery needs confirmation. In buttons: auto, the plugin draws panel glyphs only
after SUCCESSFULLY_LOADED for its own pack UUID. The ten icon presets are plain text and do not
depend on that status. buttons: pack deliberately bypasses confirmation for merged/external
packs; using it without the font can produce missing glyphs.
Never ask Mojang for a skin on the main thread. player: portraits are resolved in the
background when dialogues load and cached.
Optional integrations go through reflection. FancyNpcs, PlaceholderAPI and ViaVersion are looked
up by name, so the plugin compiles and starts without them. NpcHook uses the FancyNpcs 2.12.1
signatures; if a future version changes them, the console says so and NPCs fall back to their normal
actions.
The flag key carries the plugin name. new NamespacedKey(plugin, "flags") is stored as
ultradialogue:flags. This plugin started as a private one called "Dialogos", so Flags still reads
dialogos:marcas and moves it over. Don't remove that bridge.
Both English and Spanish keys are accepted in dialogue files (text/texto, goto/ir,
answers/respuestas…), so the private plugin's files load untouched.
Code comments are written in Spanish.
MIT © 2026 Social Studio






