Custom Quests
Custom quests are sagas, saga quests and side quests you write yourself with JSON alone, with no Java code or add-on jar. Story content is per-world data (unlike races and forms, which live in config/dragonminez), so these files live inside the world save.
Where files go
<world save>/dragonminez/
sagas/ # saga manifests
quests/<questFolder>/ # quest files for one saga, loaded alphabetically
sidequests/ # standalone side quests (any subfolder layout, e.g. sidequests/<category>/)
Source: QuestRegistry.loadAll(server) (src/main/java/com/dragonminez/common/quest/QuestRegistry.java). See Locating Specific Folders for the full folder map.
Story toggles
gameplayThese toggles live in general-server.json → gameplay, and all of them default to true:
| Field | Type | What it does |
|---|---|---|
storyModeEnabled |
Boolean | Master switch for the saga and quest system. |
createDefaultSagas |
Boolean | Generates DragonMineZ's default saga and saga quest files (needs storyModeEnabled too).
|
sideQuestsEnabled |
Boolean | Master switch for the side quest system. |
createDefaultSideQuests |
Boolean | Generates DragonMineZ's default side quest files. |
autoUpdateQuests |
Boolean | Upgrades DragonMineZ's default files in place when a new DragonMineZ version changes them (see Default file upgrades). |
Default files are generated one by one, by filename, whenever they are missing, so a default file you delete comes back on the next load while its createDefault... toggle is on. Your own files (any other filename) are never touched.
Step 1: Create a saga manifest
Create one file per saga in dragonminez/sagas/, for example my_saga.json:
{
"id": "my_saga",
"name": "dmz.saga.my_saga",
"requirements": {
"previousSaga": "saiyan_saga"
},
"questFolder": "saga_my_saga"
}
| Field | Type | What it does |
|---|---|---|
id |
String | Unique saga id. |
name |
String | Translation key for the saga's display name. |
requirements.previousSaga |
String | Id of the saga that must be completed first, or "" for none.
|
questFolder |
String | Name of the subfolder under dragonminez/quests/ that holds this saga's quest files.
|
The default saga ids are classic_saga, saiyan_saga, frieza_saga, android_saga, future_saga, buu_saga, movies_saga, gt_saga and daima_saga (see Sagas).
Source: SagaDefaults.java, Saga.java.
Step 2: Add quest files
Inside dragonminez/quests/saga_my_saga/, add one file per quest, named so the files sort in play order (01_...json, 02_...json, ...). Filename order is the quest order within the saga.
{
"id": 1,
"title": "dmz.quest.my_saga1.name",
"description": "dmz.quest.my_saga1.desc",
"type": "SAGA",
"category": "saga_my_saga",
"parallel_objectives": false,
"party_scaling": true,
"secret": false,
"claim_mode": "TREE_OR_NPC",
"requirements": {
"operator": "AND",
"conditions": [
{ "type": "LEVEL", "minLevel": 1 },
{ "type": "DIMENSION", "dimension": "minecraft:overworld" }
]
},
"objectives": [
{ "type": "KILL", "entity": "dragonminez:saga_raditz", "count": 1, "health": 450.0, "meleeDamage": 22.0, "kiDamage": 39.0 }
],
"rewards": [
{ "type": "ATTRIBUTES", "amount": 10 },
{ "type": "ITEM", "item": "dragonminez:senzu_bean", "count": 1 }
]
}
A saga quest is identified as <sagaId>:<id> (for example my_saga:1), using the numeric id, not the filename. Two files with the same id in one folder overwrite each other.
To insert a quest between two existing ones, give it a new, unused id and a filename that sorts between them. DragonMineZ's own sagas do this: Frieza Saga quest 17 is 01b_scout_the_ruined_village.json, between 01_... and 02_.... Renumbering the existing quests instead would break saved progress and every SAGA_QUEST condition or form requisite that points at them.
Quest fields
Saga quests and side quests share exactly the same schema, so these fields apply to both.
| Field | Type | What it does |
|---|---|---|
id |
Number or string | Numeric for saga quests. Side quests use a string id instead. |
title / description |
String | Translation keys (plain text also works). |
type |
String | SAGA, SIDEQUEST, DAILY or EVENT. A DAILY quest can be repeated once per real-world day (server time zone).
|
category |
String | Free-form grouping label. Defaults to "general".
|
parallel_objectives |
Boolean | If true, all objectives progress at the same time instead of one after another.
|
party_scaling |
Boolean | If true, item and kill counts scale up for each extra party member (defaultQuestPartyMultiplier in general-server.json, default 1.45), and quest enemies get more health and damage for each extra member (enemyHealthPerPartyPlayer 1.25, enemyDamagePerPartyPlayer 1.1).
|
secret |
Boolean | Hides the quest from the quest log until it is started or available. |
claim_mode |
String | TREE_OR_NPC (claim from the quest screen or from an NPC) or NPC_ONLY (the player must talk to the turn-in NPC).
|
quest_giver |
String or null |
Id of the NPC that offers the quest (see Quest giver and turn-in NPCs). |
turn_in |
String or null |
Id of the NPC that accepts the completed quest. |
prerequisites |
Object | Decides whether the quest can be unlocked at all. Same schema as requirements (see Prerequisites and requirements).
|
requirements |
Object | Decides whether the quest can be started right now (for example the current dimension, biome or level). |
repeatable |
Boolean | If true, the quest can be started again after it is completed. Default false.
|
repeat_cooldown_seconds |
Integer | Real-time seconds before a repeatable quest can be started again. 0 (the default) means right away.
|
time_limit_seconds |
Integer | Time limit after accepting the quest, in in-game seconds. When it runs out, the quest fails. 0 (the default) means no limit.
|
objectives |
Array | See Objective types. |
rewards |
Array | See Reward types. |
defaultsVersion |
String | Only used on DragonMineZ's own default files (see Default file upgrades). |
When a repeatable or daily quest is started again, its objective progress and reward claims are reset, so the rewards can be claimed again.
Source: QuestParser.parseQuest, Quest.java.
Objective types
Each entry in objectives needs a type plus that type's fields. Fields marked with * are optional, and sensible defaults apply.
type |
Fields | What it does |
|---|---|---|
ITEM |
item, count |
Have this many of an item in your inventory. The items are not taken; use DELIVER for that.
|
KILL |
entity, count, health*, meleeDamage*, kiDamage*, spawn*, count_mode*, AITier*, TextureVariant*, canTransform*, TransformHealth*, TransformMeleeDamage*, TransformKiDamage*, TransformHealthMultiplier*, TransformMeleeDamageMultiplier*, TransformKiMultiplier*, TransformTriggerPercent* |
entity can be a registry id or an entity tag (#minecraft:zombies; quest spawns pick a random member). health, meleeDamage and kiDamage default to 20, 1 and 1. spawn: QUEST (default, spawned when the quest starts) or NATURAL. count_mode: QUEST_SPAWNED_ONLY (default) or ANY_MATCHING. See Kill objective: AI tier and transformations.
|
SPAR |
Same as KILL, without the Transform* fields |
A non-lethal fight: when a quest-spawned partner would drop below 15% health, the fight ends, it counts as a win for the whole party and the partner leaves. |
INTERACT |
entity*, entityName* |
Right-click interact with a matching entity. |
TALK_TO |
npcId |
The player must talk to a specific NPC or master. |
DELIVER |
item, count* (default 1), npcId (aliases npc_id and npc) |
Right-click the NPC to hand over the items. They are taken from the inventory, and partial deliveries count. |
STRUCTURE |
structure |
The player must enter a generated structure. |
BIOME |
biome |
The player must be standing in this biome (or biome tag). |
DIMENSION |
dimension |
The player must be in this dimension. |
COORDS |
x, y, z, radius* |
The player must be within radius blocks (default 10) of a point.
|
CHECKPOINT_RACE |
checkpoints (list of { "x", "y", "z" }), radius* (default 8) |
Reach every checkpoint in order. |
SURVIVE_WAVES |
entity, waves* (default 3), mobs_per_wave* (default 3), wave_delay_seconds* (default 10), health*, meleeDamage*, kiDamage*, AITier*, TextureVariant*, CanTransform* |
Waves of enemies spawn around the player; the next one comes after a short pause once a wave is cleared. Each cleared wave counts as one step of progress. |
ESCORT |
entity, x, y, z, radius* (default 6), health* |
An escort spawns next to the player and follows them; bring it within radius of the target. If it dies, the quest fails. health overrides its maximum health.
|
DRAGON_SUMMON |
dragon*, ball_set* |
The player must summon a specific dragon and/or ball set (aliases: dragon_id and dragonId; ballSet, ball_set_id, ballSetId and set).
|
SKILL |
skill, level* |
The player must reach a skill level (aliases: skillId and id; minLevel and required; default level 1).
|
Source: QuestParser.parseObjective and the classes under common/quest/objectives/.
While a COORDS, ESCORT or CHECKPOINT_RACE objective is tracked, the quest HUD shows the distance and direction to the target (or to the next checkpoint).
In a quest with parallel_objectives: false, kill objectives that follow each other directly are unlocked together, so a "kill A, kill B" pair can be fought in any order. Objectives of the other types still go one at a time.
Kill objective: AI tier and transformations
AITieris optional:1Novice,2Smart,3Elite,4Expert. If it is left out (or set to0or a negative value), the tier comes from the difficulty the quest was started on:EASYgives 1,NORMALgives 2,HARDgives 3, andHARDagainst a saga's final boss gives 4. SetAITierexplicitly to force a specific tier regardless of difficulty.TextureVariantpicks one of the textures of entities that have several.canTransform(boolean, defaulttrue) controls whether the enemy may transform mid-fight at all. Set it tofalseto lock the enemy out of transforming entirely; this is enforced on the server, not just cosmetic.SURVIVE_WAVESreads it asCanTransform.- When
canTransformistrue, the sevenTransform*fields tune the enemy's transformed stats without touching the global config defaults. All of them are optional (nullfalls back to the global transformation defaults inentities.json):TransformHealth,TransformMeleeDamage,TransformKiDamage: absolute stat values for the transformed form.TransformHealthMultiplier,TransformMeleeDamageMultiplier,TransformKiMultiplier: multipliers applied to the base (pre-transform) stats instead of absolute values.- Absolute values take precedence over multipliers when both are set for the same stat.
TransformTriggerPercent: fraction of maximum health (0.0–1.0) at which the enemy triggers its transformation.
{ "type": "KILL", "entity": "dragonminez:saga_vegeta", "count": 1, "health": 900.0, "meleeDamage": 40.0, "kiDamage": 60.0,
"canTransform": true, "TransformTriggerPercent": 0.5, "TransformHealthMultiplier": 1.8, "TransformMeleeDamageMultiplier": 1.6 }
How a quest can fail
A failed quest loses its objective progress, and its quest-spawned enemies and escorts are removed. It can be started again from the quest log or from its NPC (the start requirements are checked again).
- Party wiped out: when the player (or every member of their party) dies, accepted quests with a
KILL,SPAR,SURVIVE_WAVESorESCORTobjective fail. - Escort died: the
ESCORTtarget was killed. - Time ran out:
time_limit_secondspassed since the quest was accepted.
Reward types
Each entry in rewards needs a type plus its fields. Every reward type also accepts an optional difficulty field that restricts the difficulties it is granted on (see below).
type |
Fields | What it does |
|---|---|---|
ITEM |
item, count* (default 1) |
Grants an item stack. |
GENERIC_ITEM |
itemReward (an item object) |
Grants an item with enchantments, potion data or an armor trim. See Item objects. |
ATTRIBUTES |
amount |
Grants Attributes, the reward every default quest uses: amount × attributeRewardPointRatio (by default half, rounded down) as Attribute Points, and the rest as the TP those stat points would cost the player when claiming. That TP is not boosted again and not shared with the party. The tooltip shows the split. See Stats and Attributes.
|
ATTRIBUTE_POINTS |
amount |
Grants Attribute Points only (placed by the player in the stats menu). |
TPS |
amount |
Grants a fixed amount of training points (goes through the server's STORY TP boosts).
|
ALIGNMENT |
amount |
Adjusts the player's alignment (can be negative). See Alignment. |
SKILL |
skill, level |
Raises a skill to at least this level. |
TRANSFORMATION |
formGroup, formName, mastery* (default 100.0), stack* (default false) |
Unlocks a form of the player's race (or a stack form if stack is true): levels the group's form skill up to that form and grants at least that much mastery. Forms listed in the form's formRequisite are raised to the mastery they require too. Aliases: form_group and group; form_name and form.
|
KI_TECHNIQUE |
code |
Grants a ki technique from an exported technique code (aliases: techniqueCode and technique_code). See Techniques.
|
COMMAND |
command, translationKey* |
Runs a command on the server (operator level), with %player% replaced by the player's name. translationKey is the text shown in the reward list instead of the command.
|
TRANSFORMATION and SKILL rewards act on the player's own race groups and skills. To give race- or class-specific rewards, put them in quests gated with a RACE or CLASS condition (see Prerequisites and requirements).
Source: QuestParser.parseReward, the classes under common/quest/rewards/, Difficulty.java.
Restricting a reward by difficulty
The optional difficulty field of a reward is an allow-set of the difficulties this reward is granted on (aliases: difficulties, difficultyType and minDifficulty). It accepts a JSON array (["NORMAL","HARD"]), a string separated by commas or spaces ("NORMAL, HARD") or a single value ("HARD"). Valid values are EASY, NORMAL and HARD. When it is left out or empty, the reward is granted on all difficulties.
This is a set membership check, not a minimum threshold: "EASY" alone means Easy only, and ["EASY","HARD"] (skipping Normal) is valid. Unknown values are dropped.
{ "type": "ITEM", "item": "dragonminez:senzu_bean", "count": 1, "difficulty": ["NORMAL", "HARD"] }
"hard:ITEM", "normal:ITEM"). Those files still load: hard: means Hard only and normal: means Easy and Normal, unless the reward also has a difficulty field. New files should use the difficulty field.Reward scaling
ITEM, GENERIC_ITEM, TPS, ATTRIBUTES, ATTRIBUTE_POINTS and ALIGNMENT amounts are scaled by the quest reward multiplier of the difficulty the quest was started on (easyModeQuestRewardMultiplier and hardModeQuestRewardMultiplier in general-server.json). TPS, ATTRIBUTES and ATTRIBUTE_POINTS rewards are also multiplied by storyResetTPMultiplier (default 0.5) once per story reset. SKILL, TRANSFORMATION, KI_TECHNIQUE and COMMAND are unlock-type rewards and are never scaled.
Rewards are per player. Every party member claims their own rewards when the quest is done (from the quest log, or from the turn-in NPC with claim_mode: NPC_ONLY).
Item objects
GENERIC_ITEM rewards (and item_list_wish wishes, see Custom Wishes) describe items with a typed object. itemType picks the kind of item; itemId and count are always available.
itemType |
Extra fields | What it does |
|---|---|---|
generic_item (default) |
— | Any item. |
enchanted_item |
enchantments |
enchantments is a map of enchantment id to level, for example { "minecraft:sharpness": 3 }.
|
enchanted_book |
enchantments |
Stored enchantments on an enchanted book (itemId minecraft:enchanted_book).
|
potion, splash_potion, lingering_potion, tipped_arrow |
potion*, mobEffects* |
potion is a potion id (for example minecraft:strong_healing). mobEffects is a list of { "effect", "duration", "amplifier", "ambient", "visible", "showIcon" } (duration in ticks, at least 1). Set itemId to the matching vanilla item (minecraft:potion, minecraft:splash_potion, minecraft:lingering_potion or minecraft:tipped_arrow).
|
trimmed_armor |
material, pattern, enchantments* |
Armor with a trim, for example "material": "minecraft:gold" and "pattern": "minecraft:coast".
|
itemId is required and count must be at least 1. An invalid item object (a missing itemId, an unknown itemType, a bad level or duration) makes the whole quest file fail to load, and the file is named in the JSON load report. Unknown enchantment, potion or effect ids only skip that entry, with a warning in the log.
{ "type": "GENERIC_ITEM", "itemReward": {
"itemType": "enchanted_item", "itemId": "minecraft:diamond_sword", "count": 1,
"enchantments": { "minecraft:sharpness": 5, "minecraft:unbreaking": 3 } } }
Source: src/main/java/com/dragonminez/common/util/types/items/, GenericItemTypeAdapterFactory.java.
Prerequisites and requirements
Both prerequisites (can this quest ever be unlocked) and requirements (can it be started right now) use the same condition-tree schema:
{
"operator": "AND",
"conditions": [
{ "type": "LEVEL", "minLevel": 30 },
{ "type": "SAGA_QUEST", "sagaId": "saiyan_saga", "questId": 8 },
{ "operator": "OR", "conditions": [ ... ] }
]
}
operator is AND or OR. conditions can nest another { "operator": ..., "conditions": [...] } block.
type |
Fields |
|---|---|
SAGA_QUEST |
sagaId, questId (int): a specific saga quest must be completed.
|
QUEST |
questId (string): a specific side quest must be completed.
|
LEVEL |
minLevel
|
STAT |
stat (STR, SKP, RES, VIT, PWR or ENE), minValue
|
RACE |
race (aliases: raceName and race_name): the player's race id, case-insensitive. Built-in ids: human, saiyan, namekian, frostdemon, bioandroid, majin, glind. Watch out: it is frostdemon (not frieza or colddemon) and bioandroid (not android or cell). Custom races use their folder name in config/dragonminez/races/.
|
CLASS |
class (aliases: className, class_name and characterClass): the player's class id, case-insensitive.
|
SKILL |
skill, minLevel* (aliases: skillId and id; level and required)
|
BIOME |
biome
|
DIMENSION |
dimension
|
STRUCTURE |
structure, hint* ({ "dimension", "x", "y", "z" }, all optional; it helps point the player towards the structure)
|
ALIGNMENT |
min*, max*
|
TIME |
mode (GAME_TIME with ticks, or REAL_TIME with milliseconds): time elapsed since the quest became eligible.
|
Source: QuestParser.parseCondition, QuestPrerequisites.java. The same condition blocks gate choices in NPC Dialogue.
Side quests
Side quests live anywhere under dragonminez/sidequests/ (subfolders are scanned too) and use the same quest schema as saga quests, with a few practical differences:
"id"is a string instead of a number (for example"bulma_gero_blueprints")."type"is"SIDEQUEST"(or"DAILY"or"EVENT") instead of"SAGA".- There is no enclosing saga folder or manifest: each file stands alone.
quest_giverandturn_inare used more heavily, since side quests are usually driven by NPCs.prerequisitescommonly referenceSAGA_QUESTorQUESTconditions to gate a side quest behind story progress.
Example (dragonminez/sidequests/collection/bulma_gero_blueprints.json):
{
"id": "bulma_gero_blueprints",
"title": "dmz.sidequest.bulma_gero.name",
"description": "dmz.sidequest.bulma_gero.desc",
"type": "SIDEQUEST",
"category": "collection",
"parallel_objectives": false,
"party_scaling": true,
"secret": false,
"claim_mode": "TREE_OR_NPC",
"quest_giver": "bulma",
"turn_in": "bulma",
"prerequisites": {
"operator": "AND",
"conditions": [
{ "type": "SAGA_QUEST", "sagaId": "android_saga", "questId": 6 }
]
},
"objectives": [
{ "type": "STRUCTURE", "structure": "dragonminez:gero_lab" },
{ "type": "ITEM", "item": "minecraft:redstone", "count": 32 },
{ "type": "ITEM", "item": "dragonminez:kikono_shard", "count": 8 },
{ "type": "TALK_TO", "npcId": "bulma" }
],
"rewards": [
{ "type": "ATTRIBUTES", "amount": 25 }
]
}
A repeatable errand, delivered to an NPC:
{
"id": "my_market_run",
"title": "dmz.sidequest.my_market_run.name",
"description": "dmz.sidequest.my_market_run.desc",
"type": "SIDEQUEST",
"category": "collection",
"quest_giver": "bulma",
"turn_in": "bulma",
"repeatable": true,
"repeat_cooldown_seconds": 3600,
"objectives": [
{ "type": "DELIVER", "item": "minecraft:emerald", "count": 12, "npcId": "bulma" },
{ "type": "TALK_TO", "npcId": "bulma" }
],
"rewards": [
{ "type": "ITEM", "item": "dragonminez:senzu_bean", "count": 2 }
]
}
Quest giver and turn-in NPCs
quest_giver and turn_in reference an NPC by id: the same id used by quest NPC entities and by master and trainer NPCs (TALK_TO and DELIVER objectives use the same ids). DragonMineZ indexes quests by both fields, so an NPC can show "available quest" markers and accept turn-ins without any extra wiring on your end. Both fields are optional: leave them out or set them to null if the quest isn't tied to an NPC (for example a saga quest that is only about exploring or killing).
Talking to the giver opens its dialogue, where the Quests option lists the quests it offers, the ones in progress and the ones ready to turn in, plus its locked quests: hovering one shows what is missing (for example the saga quest or side quest to complete first), and a completed repeatable quest shows a countdown until it can be taken again. Locked secret quests are not listed. The list is sorted by when each quest opens up in the story (saga order, then quest order, then level requirement), so an NPC's early quests come first.
If the giver is a master with an alignment requirement, its quests can only be started while the player's alignment fits, from any screen; quests already in progress can still be turned in. Goku, Gohan, King Kai, Old Kai, Roshi and Krillin need an alignment of 61 or more, and Cell and Frieza 40 or less. See Masters.
Unknown and misspelled keys
Every quest, saga, objective, reward, prerequisite or requirement condition and structure hint is checked against an explicit allow-list of fields (QuestParser.validate and validateSaga). Any key that isn't recognized (a typo, the wrong casing, a leftover field from an old schema) is not silently dropped: it is logged and shown to players in the in-game JSON load report on login (controlled by developer.reportJsonProblemsInChat, on by default), as well as in the server console. The entry still loads with the rest of its valid fields; only the unrecognized key is flagged. Use this to catch typos (for example CanTransfrom instead of canTransform) instead of having a field silently do nothing. A file with broken JSON syntax is skipped and reported in the same way.
Keys that start with _, $ or // are treated as comments and never flagged, so you can leave notes for yourself directly in the JSON.
Custom objective and reward types
Java add-ons can register their own objective and reward types through QuestObjectiveRegistry and QuestRewardRegistry. Once registered, they are written in quest JSON just like the built-in types. See Implementation and Events and Creating a DMZ Addon.
Default file upgrades
gameplay.autoUpdateQuestsEvery generated quest, saga and side quest file is stamped with a defaultsVersion field (a semver string, currently "2.2.0"). QuestUpgrader manages it automatically and uses it to run a three-way merge of your edits against DragonMineZ's shipped defaults whenever the built-in content changes:
- A field you never changed takes the new default.
- A field you changed keeps your value. If DragonMineZ also changed it, that is recorded as a conflict, and your value still wins.
- Keys you added are kept.
- When the merge changes something, the previous file is copied to
<world>/dragonminez/oldBackup/first.
A summary is written to <world>/dragonminez/quest_update_report.txt and shown in the JSON load report. Turn the whole system off with autoUpdateQuests: false. You don't need to set defaultsVersion on your own custom content. See Reloading and Updating Changes for how upgrades are reported.
Translation keys
Follow the existing naming convention so your content matches the rest of the story:
- Saga name:
dmz.saga.<saga_id> - Saga quest title and description:
dmz.quest.<key>.name/dmz.quest.<key>.desc - Side quest title and description:
dmz.sidequest.<key>.name/dmz.sidequest.<key>.desc
Add these keys to your en_us.json or to your own language file. Custom translations merge with the Crowdin ones in the same way as for custom races.
Reload and validate
- Save your saga, quest or side quest JSON in the world's
dragonminez/folder. - Validate the JSON syntax (see How to JSON).
- Run
/dmzreload story(or/dmzreload all); see Reloading and Updating Changes. - Open the quest log in game and check that your saga, quest or side quest appears with the right requirements, objectives and rewards.