Shared Schemas

The Requirements gate, the Cost object, the Reward list, and the ObjectiveSpec, documented once

Quests, achievements, mastery nodes, currencies, shop entries, and bounty boards all reuse four small sub-schemas instead of each rolling their own gate/cost/reward/objective shape. This page documents each one field-by-field, once. The per-type authoring pages (Quests & Bounties, Achievements, Masteries, Shop) link back here instead of repeating the tables.

Content pack asset

These are sub-schemas embedded inside other Server/MMOSkillTree/<Type>/*.json entries, not a standalone folder of their own.

Envelope vs. body casing

Every type covered by these schemas is a raw-Payload asset (the pre-1.4.0 authoring pattern, kept deliberately for these eleven content-pack types - never copy it for a brand new type). Two different casing rules apply at two different levels of the same file:

  • The asset envelope (Name, Payload) is PascalCase - the codec throws at server start if a key does not start upper-case.
  • The Payload body (id, displayName, objectives,cost, ...) is authored in lowerCamelCase by convention - every shipped example in content-packs/ uses lowerCamelCase throughout. Field keys are read case-insensitively, so PascalCase body keys also parse, but do not mix conventions within one file.

Data keys keep their authored casing

Currency ids inside cost.currencies, skill ids inside requirements.skills, and template params names are data, not schema field names - they are never case-folded. Only the schema's own field names (id, cost, type, ...) follow the case-insensitive read.
The two-level casing rule, in one file
{
  "Name": "Bounty_Cache_Copper",          // <- PascalCase envelope key
  "Payload": {                             // <- PascalCase envelope key
    "id": "bounty_cache_copper",           // <- lowerCamelCase body key
    "displayName": "Cache: Copper Ore",    // <- lowerCamelCase body key
    "objectives": [ { "id": "main", "type": "KILL_ENTITY", "target": "*", "amount": 1 } ]
  }
}

The filename is PascalCase and becomes the asset key. For a per-entry type (Quest, Achievement, Mastery, Currency, BountyBoard, ShopEntry, ...) the runtime id comes from either the inner Payload.id field (ShopEntry falls back to the filename, lowercased, when id is absent; Quest and Achievement have no such fallback - a missing Payload.id logs a warning and the entry is dropped) or the filename itself, lowercased (Mastery, Currency, BountyBoard, ShopPool, Shop, templates). Each type's own authoring page states which.

Requirements (gate block)

Used by Quest requirements, ShopEntry/ShopPool/Shop requirements, BountyBoard requirements, and achievement requiresFeatures. This is the shallow form; it is ergonomic sugar that desugars 100% into a PrerequisiteGroup (below) via toGroup().

FieldTypeDefaultDescription
featuresstring[] (or bare string)[]Alias requiresFeatures. Entry hidden unless every listed feature id is enabled. AND-combined. See the feature-id table on Content Types & Merging.
skills{SKILL_ID: level}{}Aliases requiresSkills, skill. Skill ids are upper-cased on parse.
minCombatLevelint0 (ungated)Any one COMBAT-category skill at or above this level.
permissionstringnullA permission-node hook, checked only where a permission checker is wired.
achievementsstring[][]Must have each achievement UNLOCKED.
questsstring[][]Must have each quest COMPLETED.
prerequisitesPrerequisiteGroupnullA full recursive group (below), folded in as an extra AND branch.
* required

Empty on every field is ungated (Requirements.NONE).

PrerequisiteGroup (recursive form)

What requirements.prerequisites above actually decodes, and what a mastery track/node's own requirements key, or a currency's visibility key, decodes directly.

FieldTypeDefaultDescription
mode"AND" | "OR" | "NOT"ANDNOT is a unary negation of the AND'd contents.
questsstring[][]Quest ids that must be COMPLETED.
permissionsstring[][]Permission nodes.
achievementsstring[][]Achievement ids that must be UNLOCKED.
features1.4.0string[][]Server feature ids.
minCombatLevel1.4.0int0Minimum combat level.
skillLevels[{skill, level}][]Skill id is upper-cased.
totalLevelint0Minimum total (all-skills-summed) level.
playtimeSecondslong0Lifetime playtime stat gate.
kills[{mobPattern, min}][]mobPattern null/empty/"*" means any-mob total kill count.
deaths{min, max}nullmax omitted or negative means no upper bound.
achievementPointsint0Minimum lifetime achievement point total.
masteryNodes[{trackId, nodeId}][]Must OWN the named mastery node.
currencyBalances[{currencyId, min, max}][]Current balance gate; max negative means no cap.
lifetimeCurrencySpent[{currencyId, min}][]Lifetime-spent gate (no max).
groupsPrerequisiteGroup[][]Nested AND/OR/NOT sub-groups.
* required

An empty group (every field absent) passes vacuously. In AND mode every leaf and sub-group must pass; OR needs one; NOT inverts the AND result.

Cost (price object)

Used by ShopEntry cost and Mastery node cost (extended with statSacrifice on masteries - see Masteries).

No scalar cost form

There is no scalar cost + currencyId shorthand - that draft form was removed pre-release. A ShopEntry with no cost object is warned and dropped entirely at load; a Mastery node with no cost key defaults to Cost.FREE.
FieldTypeDefaultDescription
combine"all" | "any"allall pays every listed component; any lets the player pick one route.
currencies{currencyId: amount}{}amount is a long. Currency ids keep their authored casing (data, not a field).
items[{id, count}][]count defaults to 1, clamped to at least 1. Raw inventory items, drained on purchase.
* required
json
"cost": {
  "combine": "all",
  "currencies": { "mastery_point": 2, "life_essence": 500 },
  "items": [ { "id": "Ingredient_Lightning_Essence", "count": 1 } ]
}

Reward (unified one-shot reward list)

An array of reward objects, granted once (on quest completion, achievement unlock, or shop purchase). type selects the shape; it defaults to COMMAND when omitted, for back-compat.

FieldTypeDefaultDescription
COMMAND (default)command, runAs, delayTicks, queueIfOffline, nameKeyoptionalAn opaque (non-/give) command reward must carry a nameKey (validator-enforced) since it can't auto-render a line. A /give {player} <item> --quantity=N command still auto-renders (parsed for its item icon/name) - --quantity=N is the only accepted quantity form; a positional quantity is silently ignored.
XPskill (required), amountoptionalAuto-renders a localized "+N <Skill> XP" line.
BOOST_TOKENskill (optional), multiplier, durationMinutesoptionalOmit skill for a global boost.
CURRENCYcurrencyId (required), amountoptionalamount must be greater than 0 or the reward is dropped.
ABILITY_MOD1.6.0modId (required)optionalGrants a player-improvement to an existing ability, referencing an AbilityModAsset id. See Ability authoring.
* required

Optional on every reward type: nameKey / descriptionKey (explicit localization keys - XP/BOOST_TOKEN/CURRENCY/parseable-/give rewards need none of these, they auto-render), and icon (inline item-id icon override). A deprecated raw displayName/description literal still parses as a fallback but should never be authored - never bake a balance number into a .lang value, and a raw literal skips localization entirely.

json
"rewards": [
  { "type": "CURRENCY", "currencyId": "bounty_token", "amount": 160 },
  { "type": "XP", "skill": "MINING", "amount": 1400 },
  { "type": "COMMAND", "command": "/give {player} Ingredient_Bar_Copper --quantity=8" }
]

ObjectiveSpec (quest objective / achievement criterion)

One objective/criterion entry. Quest objectives[] and Achievement criteria[] both wrap the same ObjectiveSpec core, but with type-specific matching defaults - see the callout below.

ObjectiveType values
27
Value-based (high-water mark)
REACH_LEVEL, BOUNTY_STREAK
Everything else
accumulates
FieldTypeDefaultDescription
id*string-Required on a quest objective; not read for an achievement criterion (index-addressed). Must not contain | = : , (reserved save-file delimiters) - an id with one is dropped with a warning.
type*ObjectiveType-Alias triggerType. See the roster below. On an achievement's top level the canonical key is triggerType only - type is not accepted there; achievement criteria accept both.
targetstring""Meaning depends on type (item id, mob id, zone name, "*" = any).
amountlongquest: 1, achievement: 1Alias requiredAmount.
matchMode"EXACT" | "CONTAINS" | "PREFIX"quest: CONTAINS, achievement: EXACTSee the asymmetry callout below - always author this explicitly.
qualifierstringnull (any)Secondary filter (e.g. elite-mob tier). "" matches only an unqualified event.
zonestringnull (anywhere)Case-insensitive match against the zone display name or region/folder name.
orderint0Quest-only. Objectives sharing an order value unlock in parallel; higher orders lock until all lower orders complete.
displayTextstringinferredQuest-only. Prefer textKey or leave both unset - the objective text renderer auto-generates a localized, pluralized sentence from type/target/amount.
textKeystringnullQuest-only explicit localization key, checked before the inferred sentence.
detailMarkdownstringnullQuest-only per-objective NPC-page detail text.
turnInNpcIdstringnullQuest-only; scopes a TURN_IN objective's turn-in location.
* required

Quest vs. achievement matching are deliberately different

This is documented behavior, not a bug. Quest/bounty target matching is case-sensitive (exact asset ids) and an empty qualifier means "unqualified events only". Achievement matching is case-insensitive and an empty target means match-all - "*" and "any" on an achievement target both normalize to "" at parse time. And the matchMode default flips too: CONTAINS for quest objectives, EXACT for achievement criteria.

ObjectiveType roster

Only the "quest-producible" values below can advance a quest objective; achievements accept every one of them through the generic achievement dispatch.

BREAK_BLOCKPLACE_BLOCKCRAFT_ITEMKILL_ENTITYDEAL_DAMAGEPICKUP_ITEMREACH_LEVELTALK_TO_NPCCATCH_FISHTURN_INCOMPLETE_QUESTTAKE_FALL_DAMAGEPLAYER_DEATHGAIN_XPSPRINT_DISTANCESWIM_DISTANCEABILITY_CASTEARN_CURRENCYBREED_ANIMALFEED_ANIMALHARVEST_ANIMALCOMPANION_COMBATCOMPLETE_BOUNTYBOUNTY_STREAKREACH_LOCATIONCONSUME_ITEMSPEND_CURRENCY

See also