Shared Schemas
The one Requires gate, the Cost object, the open reward vocabulary, and the objective shape
Quests, achievements, contracts, offers, storefronts, boards, wallets, NPC placements, conversations and mastery nodes all reuse the same handful of small sub-schemas instead of each rolling its own gate / price / reward / objective shape. This page documents each one, once. The per-type authoring pages (Quests & Bounties, Achievements, Masteries, Shops) link back here instead of repeating the tables.
These are sub-schemas embedded inside other asset files, not a standalone folder of their own.
The one Requires block
There is one requirement vocabulary on this server, and every gated surface speaks it: a quest, an achievement, a contract, an offer, a storefront, a wallet's visibility, an NPC placement, a conversation option, a board's per-band accept gate, a mastery node, a class advancement. A gate that works on one works on the next.
"Requires": {
"Quests": ["intro_miner"],
"Permission": "myserver.chapter2",
"Factors": [ { "Factor": "mmoskilltree:feature", "Param": "taming", "Min": 1 },
{ "Factor": "hytale:stat", "Param": "MMO_TotalLevel", "Min": 50 } ],
"AnyOf": [ { "Quests": ["route_a"] }, { "Quests": ["route_b"] } ]
}| Field | Type | Default | Description |
|---|---|---|---|
Factors | [{Factor, Param, Min, Max}] | [] | Numeric bounds on the shared factor vocabulary, ALL of which must pass. Both bounds are inclusive and both are optional. |
Permission | string | null | One permission node the player must hold. It is evaluated as the hytale:permission factor, so the two spellings can never disagree. |
Quests | string[] | [] | Quest ids the player has finished and collected the reward for. A quest parked finished-but-unclaimed does not satisfy it. |
Custom | {namespaced id: value} | {} | Requirement kinds another mod registered, keyed by their namespaced id. |
AllOf | group[] | [] | Further groups that must ALL pass. |
AnyOf | group[] | [] | Groups of which at least ONE must pass, for "either route in will do". |
Not | group[] | [] | Groups that must FAIL. A group listing two things means "not both of those"; two groups mean "neither". An empty Not group shuts the content for everybody, because a group asking for nothing passes for everyone - the content audit reports it. |
Everything at the top level must pass, every AllOf group must pass, at least one AnyOf group must pass, and no Not group may pass. An absent block asks for nothing. Nesting stops at one level of grouping on purpose: a requirement that genuinely needs more belongs in a registered Custom kind.
Factor spellings
A level is not a field of its own - it is a bound on a named reading, which is what lets one spelling serve every mod on the server.
| To ask for | Write |
|---|---|
| One skill's level | { "Factor": "hytale:stat", "Param": "MMO_Level_MINING", "Min": 40 } |
| Combat level (the best COMBAT-category skill) | Param: "MMO_CombatLevel" |
| Total level (every listable skill, summed) | Param: "MMO_TotalLevel" |
| The best level held in any one skill | Param: "MMO_HighestSkillLevel" |
| A server feature being on | { "Factor": "mmoskilltree:feature", "Param": "mastery", "Min": 1 } |
| Owning a mastery node | { "Factor": "mmoskilltree:mastery_node", "Param": "<track>:<node>", "Min": 1 } |
| A quest collected | { "Factor": "ziggfreedcommon:quest_completed", "Param": "<quest id>", "Min": 1 } |
| How many times a repeatable was collected | { "Factor": "ziggfreedcommon:quest_completions", "Param": "<quest id>", "Min": 3 } |
| An achievement earned | { "Factor": "ziggfreedcommon:achievement_earned", "Param": "<id>", "Min": 1 } |
| A lifetime achievement-point total | { "Factor": "ziggfreedcommon:achievement_points", "Min": 3000 } |
The portable hytale: readings mean the same thing in any mod, so a pack can gate on them without knowing which mods a server runs: hytale:stat (any registered stat channel), hytale:permission, hytale:held_item, hytale:held_tag, hytale:tool_power, hytale:tool_tier, hytale:tool_durability_percent, hytale:tool_quality, hytale:tool_item_level. The optional MMO Mob Scaling companion adds mmomobscaling:mob_rarity_tier, mob_rarity, mob_affix, mob_difficulty and region_power.
An unanswerable factor fails closed
/mmoconfig validate reports it as an UNKNOWN_FACTOR_ID warning rather than an error, because the mod that provides it may simply not be installed yet. A condition with NEITHER bound is therefore a presence check: it passes exactly while the factor resolves at all, which is how "only where that mod is installed" is written.What a locked surface tells the player
A locked detail panel lists every unmet requirement, not just the first one, so somebody planning a long-term goal sees the whole picture. Each one is worded for its kind: a skill level ("Requires Mining level 40 (currently 22)"), a total level, a combat level, a permission, a quest to complete, an achievement to unlock, a feature that is off, a mastery node to own, and a plain line for a condition that is blocking because it passes (a Not group). Several routes in read as one line - "Unlocked by: A or B" - because they are alternatives, not a list of things to go and do.
Naming overlays: how a locked line reads
The words a locked surface prints for a factor are content too. A Server/ZiggfreedCommon/Factors/<Id>.json file is a naming overlay: it changes only how a requirement on one factor is worded to the player, never what the gate checks. The jar ships overlays for the skill-level, feature and mastery-node wordings, and a pack can ship its own overlay on any factor, including one somebody else registered, because overlays compose.
{
"Factor": "hytale:stat",
"ParamNames": {
"KeyPattern": "mmoskilltree.skill.{param}",
"StripPrefix": "MMO_Level_",
"Case": "Lower",
"WrapKey": "mmoskilltree.factor.skill_level",
"Keys": {
"MMO_CombatLevel": "mmoskilltree.factor.combat_level",
"MMO_TotalLevel": "mmoskilltree.factor.total_level",
"MMO_HighestSkillLevel": "mmoskilltree.factor.highest_skill_level"
}
}
}Factornames the factor id the overlay words.Text.TitleKeyis the factor's own display name, used when nothing more specific resolves.ParamNamesturns the condition'sParaminto a player-facing name:Keysmaps exact params to keys;KeyPatterncomposes a key from the param afterStripPrefix/StripThrough(drop everything through a separator) andCasefolding;WrapKeywraps the resolved name in a phrase ("{0} level").- A pattern whose resolved key is not shipped is skipped, falling back to the factor's own title - so an overlay can never print a raw key.
The practical payoff for a pack: a custom skill joins the level phrasing automatically the moment its pack ships a mmoskilltree.skill.<id> lang key, and a pack shipping its own mastery nodes (or a factor of its own) words its lock lines by dropping one overlay file beside them - no Java, and no per-mod lock-line renderer anywhere.
Where the block is authored
| Surface | Written as |
|---|---|
| Quest, achievement, contract, offer, storefront, shelf | A top-level Requires group |
| Board, per band | AcceptRequires: a map from band word to a Requires block |
| Wallet | Requires, gating visibility only (never earning) |
| NPC placement | Requires.Factors: a bare list of factor conditions |
| Conversation option, screen or opening beat | A condition object: { "Type": "Factor", "Factor": "...", "Param": "...", "Min": 10 } |
| Mastery track, mastery node, class advancement (Future) | A top-level PascalCase Requires group, exactly as above - on the track, and again on any node that gates itself |
The older gate sugar is gone
PrerequisiteGroup and its shorthand form, the flat features/skills/minCombatLevel form, and the playtimeSeconds/kills/deaths/achievementPoints/ currencyBalances/lifetimeCurrencySpent leaves were removed in 1.6.0. Write the block above instead: a level is a hytale:stat bound, a feature is an mmoskilltree:feature bound, and an either-or is AnyOf.On a placement, the list is Factors
"Requires": { "Conditions": [...] } names a key the codec does not know: the gate reads empty and the character stands there unconditionally, which is the failure mode nobody notices. Spell it Factors. The Migration Converter renames it in place, and /mmomigrate reports it.Damage schools
The 9 canonical damage-school ids, referenced by an ability's Cause and by the gear/mastery MMO_Bonus_<School> / MMO_BonusFlat_<School> / MMO_SchoolResist_<School> channels. Documented once here; Abilities and Gear stats link back to this roster rather than each keeping their own list.
| School | Cause |
|---|---|
Fire | Vanilla-native cause |
Ice | Vanilla-native cause |
Lightning | MMO-authored, mirrors an essence item |
Water | MMO-authored, mirrors an essence item |
Arcane | MMO-authored, mirrors an essence item |
Void | MMO-authored, mirrors an essence item |
Poison | Vanilla-native cause |
Life | MMO-authored, mirrors an essence item |
Physical | Vanilla-native cause |
Envelope vs. body casing (the older payload types)
The shared library types and every structured MMO type are authored in PascalCase from top to bottom, and the file is the asset. That includes mastery tracks and classes, which became structured types in 1.6.0. The remaining raw-Payload types - the older quest / achievement / dialogue shapes, and the per-pack map types (XP maps, mob-kill XP, item and action requirements, boost templates, command rewards) - still carry two casing rules in one 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,rewards, ...) is authored in lowerCamelCase by convention. 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
params names are data, not schema field names - they are never case-folded. Only the schema's own field names follow the case-insensitive read.Cost (price object)
There is one price spelling in the library, and everything that charges uses it: an offer, a shelf's or a board's reroll, anything else that takes payment.
| Field | Type | Default | Description |
|---|---|---|---|
Currencies | {currencyId: amount} | {} | Currency ids keep their authored casing (they are data, not field names) and are matched case-insensitively. |
Items | [{Item, Count}] | [] | Count defaults to 1. Raw inventory items, drained on purchase. |
Combine | "All" | "Any" | All | All pays every listed component; Any lets the player pick one route. |
"Cost": {
"Currencies": { "Bounty_Token": 150 },
"Items": [ { "Item": "Ingredient_Lightning_Essence", "Count": 1 } ]
}A mastery node's Cost is this group, in this spelling - mastery tracks are a structured PascalCase type, so there is no lowerCamelCase cost dialect to learn. A node with no Cost key is free, and there is no scalar cost + currencyId shorthand anywhere. Mastery adds one extension of its own on top: see Authoring Mastery Tracks.
Rewards: an open vocabulary
A reward is { "Kind": ..., "Params": { ... } }. Kind is an open registered id, not a fixed list - anything registered can be authored, including a kind a companion mod provides, and a kind nothing on this server registers is reported by /mmoconfig validate as an UNKNOWN_REWARD_KIND warning rather than silently dropped.
{ "Kind": "Mmo_Xp", "Params": { "Skill": "MINING", "Amount": "1400" } }| Field | Type | Default | Description |
|---|---|---|---|
Item | Item, Count | optional | An item straight into the bag, fit-checked before the claim is allowed. Prefer this over a Command running a give. |
Lootable | Lootable, Trigger | optional | Rolls a shared loot table by id. See Shared loot tables. |
Stamped_Item | Item, Count, Pool, Stats, Picks | optional | Gear with rolled or authored per-stack stats. |
Effect | Effect, DurationSeconds | optional | Applies a native effect. |
Droplist | Droplist, Rolls, Position | optional | Rolls a native item drop list at the player's feet. |
Command | Command, RunAs, DelayTicks | optional | Runs an authored command line. {player}, {uuid}, {source} and the reward's own params are substituted. |
Currency | Currency, Amount | optional | Pays a wallet. |
Mmo_Xp | Skill, Amount | optional | Awards skill experience. |
Mmo_Boost_Token | Skill, Multiplier, DurationMinutes | optional | Hands over an XP boost token. |
Mmo_Ability_Mod | Mod | optional | Grants an ability improvement by id. See Ability authoring. |
NameKey, DescriptionKey, Icon and QueueIfOffline apply to every kind. An empty Kind pays nothing out, deliberately: it lets a generator row leave a reward slot unused without the table needing two shapes.
Older spellings still read
{ "type": "XP", "skill": "MINING", "amount": 1400 } - the kind under type, every other field a parameter of the same name. Historical ids (xp, item, boost_token, CURRENCY, mmo_currency, COMMAND, ability_mod, ...) translate through one table on the way in, so nothing already authored has to be touched. Write the canonical id in anything new.The two buckets: Auto and Claim
Every progression schema - a quest, an achievement, an achievement-point milestone, a board contract - pays through one Rewards group holding two lists of those rewards:
"Rewards": {
"Auto": [
{ "Kind": "Mmo_Xp", "Params": { "Skill": "MINING", "Amount": "1400" } }
],
"Claim": [
{ "Kind": "Currency", "Params": { "Currency": "bounty_token", "Amount": "160" } },
{ "Kind": "Item", "Params": { "Item": "Ingredient_Bar_Copper", "Count": "8" } }
]
}Autolands the instant the content settles - the moment the last step ticks over, wherever the player is standing.Claimwaits to be collected, and it is the default: anything inClaimparks the quest or achievement as ready-to-collect. Content paying onlyAutosettles on the spot with nothing to collect. Put anything needing bag room inClaim, where the room can be checked before it is handed over.- Each bucket is one leaf for inheritance: a file with a
Parentthat re-authorsClaimreplaces the inheritedClaimlist whole and keeps the inheritedAutountouched.
A shop offer is the one surface that keeps a bare list - "Rewards": [ ... ] with no buckets - because a purchase has no completion to wait for: it is handed over at the counter. Every other surface above takes the group.
Writing a reward kind of your own, with no Java
A kind is content too. Server/ZiggfreedCommon/RewardKinds/<Id>.json declares a parameter list and one console command line, and the filename IS the kind id - which is how this mod's own skill-tree kinds are defined.
{
"Name": "Skill experience",
"Params": {
"Skill": { "Required": true },
"Amount": { "Required": true },
"Silent": { "Default": "false" }
},
"Command": "mmoawardxp {player} {Skill} {Amount} --silent={Silent}",
"Presentation": {
"NameKey": "mmoskilltree.reward.xp.{Skill}",
"Args": ["Amount"],
"Icon": { "ByParam": "Skill", "Default": "Rock_Crystal_Iridescent_Small",
"Values": { "MINING": "Tool_Pickaxe_Crude" } }
}
}Commandtakes three substitutions, all case-sensitive:{player},{uuid}, and{<ParamName>}for any parameter. A placeholder naming nothing is left standing rather than blanked, so a typo turns up in the command that ran, and the content audit reports it before a player ever earns the reward.Paramsentries carry two independent knobs:Requiredrefuses to pay out, loudly, when a reward does not name that parameter;Defaultstands in when a reward leaves it out.Presentationsays how the kind's rewards read where one is shown before it is handed over.NameKeyis a template: each{Param}is replaced by that parameter's value, lower-cased.Argsis an ordered list saying what fills the resolved key's{0},{1}, ... blanks, with two rules: an entry that IS a parameter name binds that parameter's value (namingAmountbinds the number itself, so a{0, number}blank groups its digits in each player's own locale - +2,000 rather than +2000), and an entry that is not a parameter name is read as a localization-key template and renders as a nested translated name. A key written with a{0, number}blank but noArgsentry to fill it renders nothing in that slot, so author them together.Iconis{Default, ByParam, Values}; leaveByParamout for one picture on every reward of the kind.- Kind files layer defaults<pack<ownerlike any other asset, and a file carrying
"Parent": "<id>"starts from that kind, withValuesmerging per value - which is how a pack adding a skill of its own extends an icon table in three lines.
A kind file replaces a Java-backed kind, loudly
Shared loot tables
A Server/ZiggfreedCommon/Lootables/<Id>.json is a named, reusable table anything can point at: a mob drop, a lucky block break, a work station's rare find, a quest reward. Wherever a Loot block appears, "Lootables": ["<id>"] names one and "Rolls": [...] writes the same thing inline.
| Field | Type | Default | Description |
|---|---|---|---|
Rolls | roll[] | [] | The payouts that each stand on their own. Every one whose Conditions and Chance pass hands over what it names. Author it for "everybody gets this". Each roll's own leaves are the table below. |
Pool | {Rolls, Entries[{Weight, Conditions, Grants}]} | none | The payouts that COMPETE. Only as many entries as Pool.Rolls works out to are drawn, with replacement; an entry whose conditions do not pass is not in the bag at all, and a weight of zero is never picked. |
ContributesTo | string (another table id) | none | Folds this file's rolls and pool entries INTO that table on top of what is already there, without touching the file that owns it. Removing the contributing pack removes exactly what it added. |
One roll
A roll is one conditional payout: a gate (Conditions, then Chance), a payoff (Grants, plus whatever Ladder floor the moment reached), and an optional celebration (Cue).
| Field | Type | Default | Description |
|---|---|---|---|
Conditions | factor condition[] | [] | Every entry must pass before the roll is considered. An unanswerable factor shuts the gate, so a roll gated on an absent mod stays hidden. |
Chance | {Base, Factors, Clamp} | always fires | The shared formula, read as a PERCENT held inside 0 to 100 - the engine reads { "Base": 35.0 } as a 35% chance, never as 0.35 (the jar's own tables author 35.0 / 8.0 / 2.0). Base is the flat chance, each factor term adds its weighted reading, and Clamp.Max is the ceiling a stacking bonus may never pass. It gates the WHOLE roll, ladder included. Omit the group for a roll that always fires. |
Grants | {Items, DropLists, Commands, Rewards} | none | The payout, handed over whenever the gates passed: four independent sinks - Items (with Count and an optional CountMax for a range), DropLists (native item drop list ids), Commands, and Rewards (any kind above). |
Ladder | {Factors, Floors[{Min, Grants, Cue}]} | none | Tiers climbed on a summed factor value: the HIGHEST floor whose Min the sum reaches pays out, ON TOP of the top-level Grants (they stack, they are not alternatives). Thresholds compare against the raw sum with no ceiling, so a floor above a factor's normal range stays reachable by stacking sources. |
Cue | string | none | An opaque id the granting site maps to a celebration - and the cue id IS a feedback-moment file name, so a pack authors any Cue value plus a FeedbackMoments/<that id>.json and writes no Java. A cue fires only when the grants beside it actually paid out, so it never celebrates over an empty hand. Rare_Find is the shipped example. |
Trigger | string | the site's default moment | Which moment this roll answers to, for a granting site that offers several; omit to fire on the plain default. |
Bonus drops reuse this same roll shape inside each row's Loot block, so a table learned here reads the same there.
When the player is not there
One inventory-room probe runs before any claim charges, so a reward is never taken for granted and then lost. A grant owed to an offline player spools to mods/mmoskilltree/pending-rewards.json, keyed by player UUID only, and is handed over on their next connect. A grant with no resolvable UUID refuses loudly rather than queueing under a name that might belong to somebody else later.
Objectives and criteria
A step is the same shape in both stores, and both store it the same way: a quest keys its steps by id under Objectives, an achievement keys its criteria by id under Criteria. The key is what a player's progress is filed under and what a child under Parent re-tunes, so one criterion can be retuned by name while the rest are inherited, and re-authoring a file can never re-point a tally somebody is part-way through.
| Field | Type | Default | Description |
|---|---|---|---|
Kind* | objective kind | - | What counts. See the roster below. On the older payload shapes this is type (a quest objective) or triggerType (an achievement). |
Target | string | "" | What specifically: an item id, a mob model id, a stat channel, a zone. Left blank it matches anything, in every store. |
MatchMode | "EXACT" | "PREFIX" | "CONTAINS" | CONTAINS (both stores) | Author it explicitly anyway: it is the one field a reader has to guess at, and it decides whether a whole family of ids counts or exactly one. |
Qualifier | string | null (any) | A secondary label the kind interprets. Leave it out for "any"; author an empty string to accept only events that carry no qualifier at all. |
Amount | long | 1 | How many. |
Zone | string | null (anywhere) | Scope the step to a zone, matched case-insensitively against the zone name. |
TextKey | string | null | Override the generated sentence when a hand-written one reads better. Leave it out and a localized sentence is generated from the kind, target and amount. |
A KILL_ENTITY target matches the victim's MODEL id, not its role name
Risen_Knight wears the Skeleton_Knight model, so "Risen" counts nothing at any match mode while "Skeleton" counts it. Juveniles are their own model (Boar / Boar_Piglet), so PREFIX counts a species' young where EXACT does not, and a tamed animal reuses the wild model, so no match mode can exclude tamed animals. /mmobounty validate reports an unmatchable target as UNRESOLVABLE_KILL_TARGET.STAT_THRESHOLD and the level channels
STAT_THRESHOLD is a value-based step that completes when a native stat channel reaches Amount. Target is the channel id, so any registered channel - including one another mod writes - becomes a step with no new objective kind:
{ "Kind": "STAT_THRESHOLD", "Target": "MMO_TotalLevel", "Amount": 50 }This mod mirrors four level readings onto that substrate - MMO_Level_<SKILLID>, MMO_CombatLevel, MMO_TotalLevel, MMO_HighestSkillLevel - and they are the same channels a Requires factor bound reads. REACH_LEVEL is the friendly form and keeps working unchanged: it resolves onto STAT_THRESHOLD at runtime, with TOTAL / ALL / GLOBAL reading the total channel, * / ANY / a blank target reading the highest, and anything else reading that skill's own channel. Both render as the same sentence on screen.
DEAL_DAMAGE counts damage, not hits
{ "Kind": "DEAL_DAMAGE", "Amount": 50000 } asks for fifty thousand points of damage and not fifty thousand swings. Pick the number against how much a weapon of that tier actually lands for.One matching rule, everywhere
A quest step, a contract step and an achievement criterion are all matched by the same rule, so a step that counts on one counts the same on the next:
- Targets compare case-insensitively, under every match mode.
trorkandTrorkare the same quarry. - An empty
Targetmatches everything. That is how "kill anything in this zone" or "break any block" is written - pair it with aZoneor aQualifierto narrow it. - A missing
Qualifiermeans any; an empty one accepts only an event carrying none; a non-empty one compares case-insensitively. MatchModedefaults toCONTAINSin both stores.
Author the target you mean, not the casing you saw
CONTAINS on a stem ("Plant_Crop_") counts a whole family, EXACT counts one id and nothing else.Objective kind roster
Only the "quest-producible" values below can advance a quest step; achievements accept every one of them.
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_CURRENCYWORK_STATIONINSTANCE_ROUND_WONINSTANCE_ROUND_ENDEDSTAT_THRESHOLDSee also
- Authoring Quests & Bounties - the shared quest file, boards and contracts.
- Authoring Achievements - the shared achievement file, categories and point milestones.
- Masteries - the mastery-only stat-sacrifice cost extension and the modifier grammar.
- Shops & Currencies - the surfaces that consume Cost and Rewards most heavily.
- ZiggfreedCommon - which engine reads which of these, and the owner files that override them.
- Publishing & Translations - packaging the folder these files live in as an installable
.zip.