Bonus Drops

The lucky find in a broken block, the extra in a hand harvest and the bonus a killed mob is worth

Overview

A bonus drop is something extra a moment hands over on top of whatever the game already gives for it: the lucky find in a broken block, the bonus a killed mob is worth, and the extra in a hand harvest. One table answers for all three, because the question is the same every time - which row covers this name, how likely is the moment, and what does it hand over. The table is new in 1.6.0, and hand harvests join it in 1.7.0.

Nothing here replaces vanilla loot. A block still drops what it drops, a mob still drops what it drops and a harvest still gives what it gives; a bonus row hands its own reward over beside that, at the broken block, at the corpse, or where the harvest went. A game update that re-tunes the base tables re-tunes them underneath you and your bonus keeps working.

Override configmods/mmoskilltree/bonus-drops.json

Shipped rows live in the jar and content packs ship theirs under Server/MMOSkillTree/BonusDrops/*.json. The owner file overrides any of them by name, one field at a time.

defaults<pack<owner

Replaces luck-loot.json and mob-drops.json

Both older files are folded into bonus-drops.json on the first start after upgrading, with every override carried across and the old files backed up. See What happens when you upgrade.

What a row looks like

A row is one file, and the filename is its name (its id). A pack ships rows at Server/MMOSkillTree/BonusDrops/<Name>.json; a server owner writes the same body into bonus-drops.json under that name.

// Server/MMOSkillTree/BonusDrops/Dragon_Hoard.json
{
  "When": { "Kind": "KillMob", "Match": "*Dragon*" },
  "Chance": {
    "Base": 5.0,
    "Factors": [ { "Factor": "hytale:stat", "Param": "MMO_Luck", "Weight": 0.2 } ],
    "Clamp": { "Min": 0.0, "Max": 20.0 }
  },
  "Loot": { "Rolls": [ { "Grants": { "DropLists": [ "My_Dragon_Hoard" ] } } ] },
  "Notify": { "Enabled": true }
}
FieldTypeDefaultDescription
When.Kind-optionalWhich moment the row answers for: BreakBlock, KillMob or PickupItem (see The three moments). A row naming no moment never fires and is named in the content audit.
When.Match-optionalWhich block, mob or item, as a name pattern (see Name patterns). Leave it out to cover everything in that moment.
Chance-optionalWhether the bonus moment fires at all, as a percent formula. Optional; each moment has its own default (see The odds).
Loot-optionalWhat the moment hands over: Lootables (shared table names), Rolls written inline, or both. A roll's Grants takes Items, DropLists (native item drop lists), Commands and Rewards.
Notify-optionalThe on-screen note when a bonus actually lands. Off unless the row asks for it, on every moment. See Telling the player.
Enabled-optionalfalse takes the row out of the table entirely.
* required

The three moments

MomentFires whenMatch readsNeeds skill XPNo Chance meansNote colourCommand placeholders
BreakBlocka player breaks a blockthe block nameyesthe player's own luck#ffff00{player}, {block}
PickupItem Since 1.7.0a player harvests an item by hand, after the harvest landsthe harvested item's idyesthe player's own luck#ffff00{player}, {item}
KillMoba player kills a mobthe mob's typenoalways fires#ffaa00{player}, {mob}, {tier}

"Needs skill XP" means the row only fires when the moment paid the player skill XP, so a block or a harvest no skill rewards never rolls a bonus. A harvest only ever looks at PickupItem rows, never at a BreakBlock row, so a catch-all "*" row you wrote for broken blocks keeps meaning broken blocks.

PickupItem rows need 1.7.0

A content pack that ships a PickupItem row needs MMO Skill Tree 1.7.0. On 1.6.x the moment is unknown, so the row names no moment and /mmoconfig validate reports it as an error.

The four things a roll can hand over

  • DropLists spills a native item drop list where the moment happened, exactly the way the base game spills its own loot.
  • Items hands named items over, for an exact stack rather than a rolled table.
  • Commands runs console commands. Placeholders name what the moment knows: {player} and {block} on a break row, {player}, {mob} and {tier} on a kill row, {player} and {item} (the harvested item's id) on a harvest row. {tier} resolves empty unless a companion mod attributes a rarity to the kill; the optional MMO Mob Scaling companion mod does, so with it installed a scaled kill's {tier} reads the mob's rarity (such as Legendary) and an unscaled kill's stays empty. A drop chance that weighs the mob is still written through its factors (see Reading what another mod knows), not this placeholder.
  • Rewards pays the player through the shared reward vocabulary (XP, currency, an item, a boost token, and anything another installed mod contributes). On a harvest row it also takes Moment_Item, another of the harvested stack (see below).

On a harvest row nothing spills where the moment happened: everything the row hands over goes where the player's own pickup settings send that item, topping up a stack they already carry, and only what does not fit drops as one pile at their feet.

Each roll inside Loot carries its own Chance and Conditions too, so one row can hold a common drop and a rare one side by side.

Only a player's kill counts

A mob that drowns or is killed by another mob drops its own loot and nothing from a KillMob row.

Lucky hand harvests Since 1.7.0

The lucky harvest ships as a bonus-drop row, Mmo_Harvest_Luck. When a hand harvest pays skill XP, it rolls the harvester's own luck for that skill, and on a hit it hands over one more of the stack just picked. It rolls after the harvest has landed, so a nearly full bag never loses the harvest to the bonus.

// Server/MMOSkillTree/BonusDrops/Mmo_Harvest_Luck.json (comments left out)
{
  "When": { "Kind": "PickupItem", "Match": "*" },
  "Loot": { "Rolls": [ { "Grants": { "Rewards": [ { "Kind": "Moment_Item" } ] } } ] },
  "Notify": { "Enabled": true }
}
  • Switch it off in mods/mmoskilltree/bonus-drops.json: {"drops": {"Mmo_Harvest_Luck": {"Enabled": false}}}.
  • Give it a Chance to change the odds, or a different Loot to hand over something else.
  • Add a narrower PickupItem row for one crop. The most specific pattern wins, so that row replaces Mmo_Harvest_Luck for its items: write Moment_Item into it as well if it should still hand over a copy.
// Server/MMOSkillTree/BonusDrops/Golden_Wheat.json
{
  "When": { "Kind": "PickupItem", "Match": "*Wheat*" },
  "Loot": { "Rolls": [
    { "Grants": { "Rewards": [ { "Kind": "Moment_Item" } ] } },
    { "Chance": { "Base": 10.0 },
      "Grants": { "Items": [ { "Item": "Ingredient_Life_Essence" } ] } } ] },
  "Notify": { "Enabled": true }
}

Moment_Item: another of the harvested stack

Moment_Item is a reward kind from Ziggfreed Common 2.2.0. It hands over a true copy of the stack the game gave the player: the same item and amount, with its durability, quality and any extra data kept.

FieldTypeDefaultDescription
Params.Countnumber1How many whole copies of the stack. Fractions are allowed: 0.5 is one copy half the time, 2 is two copies. Every Moment_Item in one pass adds to one total, rolled once, and one pass hands over at most 64 copies.
* required
"Rewards": [ { "Kind": "Moment_Item", "Params": { "Count": 0.5 } } ]

It only pays in a PickupItem row, the one moment with a stack to copy. /mmoconfig validate flags it where it can never pay: as a warning in a BreakBlock or KillMob row, and as an error in a quest's or an achievement's rewards. Ziggfreed Common's own shop audit warns about one in a shop offer. /mmoconfig validate also warns where its Count is not a number above zero or is above 64.

Name patterns

When.Match speaks the same name grammar the per-world Where block and a dialogue world scope speak, so a pattern means one thing everywhere. Case is ignored.

PatternMatchesReads as
Ore_CoalOre_Coal and nothing elseexactly
Ore_*Ore_Coal, Ore_Iron_Basaltstarting with
*_CrackedOre_Iron_Basalt_Crackedending with
*Trork*Trork, Trork_Chieftain, Zone1_Trork_Bruiseranywhere in the name
*everything in that momentthe catch-all

Where several rows match one name, the row pinning down the most characters wins: an exact pattern first, then the longest literal core, then the bare *. So *Trork* covers the family while a second row naming *Trork_Chieftain* takes over for that one mob, and neither file has to know about the other. Two equally specific patterns keep the first one folded. Anything more selective than a name belongs in a roll's own Conditions, never in the pattern.

The odds

Chance is a percent formula: a Base plus each factor's reading multiplied by its Weight, held inside an optional Clamp. Every number is a whole percent point.

"Chance": {
  "Base": 2.0,
  "Factors": [ { "Factor": "hytale:stat", "Param": "MMO_Luck", "Weight": 0.2 } ],
  "Clamp": { "Min": 0.0, "Max": 6.0 }
}

That reads as: 2% to start, rising with the killer's own Luck, never past 6%. Use MMO_Luck_MINING (or any other skill) as the Param to read a single skill's luck instead, and drop the Factors list entirely for a flat chance.

A percent here, a fraction in mob-drop-commands.json

A bonus-drop Chance is a percent: "Base": 5 is five percent. A plain-number chance in mob-drop-commands.json is a fraction from 0 to 1, so five percent is 0.05 there. Copying a number from one file to the other without converting it makes the bonus a hundred times rarer or more common.

What an unwritten Chance means

Each moment carries its own default, so each reads the way an author expects:

MomentA row with no Chance
BreakBlockis as likely as the player's own summed luck for the skill the break paid XP to - the MMO_Luck and MMO_Luck_<SKILL> channels the skill tree, mastery, gear and item enhancements all feed
PickupItemthe same: the player's own summed luck for the skill the harvest paid XP to
KillMobalways fires, leaving the odds entirely to the rolls inside it

Starting from the player's luck Since 1.7.0

Writing a Chance replaces the default. To keep the default and add to it, read it with the mmoskilltree:moment_skill_luck factor. It takes no Param: it reads the player's summed luck for the skill the moment paid XP to, in whole luck points, the same unit a Chance adds up in. So a Weight of 1 is exactly the unwritten default:

"Chance": {
  "Base": 1.0,
  "Factors": [ { "Factor": "mmoskilltree:moment_skill_luck", "Weight": 1 } ],
  "Clamp": { "Max": 25.0 }
}

That reads as one percent more than the player's own luck, never past 25%. It answers on BreakBlock and PickupItem rows; a kill names no skill, so on a KillMob row it is unanswered and its term adds nothing. Mind the unit if you know mmoskilltree:station_luck from a station: that one reads the same kind of sum as a fraction (0.05 for five points), this one in whole points. A single named skill's luck is still written as hytale:stat terms over MMO_Luck_<SKILL>.

Reading what another mod knows

A chance formula (and a roll's Conditions) can weigh anything the installed mods publish. With the optional MMO Mob Scaling companion mod installed, five more readings become citable, all about the mob the moment happened to:

FactorReads
mmomobscaling:mob_rarity_tierthe mob's place on the rarity ladder: 0 for a plain mob, one step per authored tier
mmomobscaling:mob_rarityParam is a rarity name; 1 when the mob is exactly that tier, 0 otherwise
mmomobscaling:mob_affixParam is an affix name; 1 when the mob carries it, 0 otherwise
mmomobscaling:mob_difficultythe mob's frozen difficulty number
mmomobscaling:region_powerthe tracked player power in the region the moment happened in
{ "When": { "Kind": "KillMob", "Match": "*Trork*" },
  "Loot": { "Rolls": [
    { "Conditions": [ { "Factor": "mmomobscaling:mob_rarity_tier", "Min": 2 } ],
      "Chance": { "Base": 20.0,
                  "Factors": [ { "Factor": "mmomobscaling:mob_difficulty", "Weight": 2.0 } ],
                  "Clamp": { "Max": 60.0 } },
      "Grants": { "DropLists": [ "My_Elite_Trork_Hoard" ] } } ] } }

On a server without that mod nothing answers those names, so the condition stays shut and a formula term adds zero: one authored file is correct everywhere, with no dependency in either direction. /mmoconfig validate names a reading nothing answers to as a warning, so a typo is visible rather than silent.

A row that hands nothing over is a deliberate hole

Naming a pattern and granting no Loot is how "this one gives nothing" is said. Because the most specific pattern wins, an empty row for *Ore_Iron_Basalt_Cracked* stops a broader *Ore_Iron_* row covering that block, while every other iron variant keeps its bonus.

{ "When": { "Kind": "BreakBlock", "Match": "*Ore_Iron_Basalt_Cracked*" } }

That is different from "Enabled": false, which takes the row out of the table entirely and lets the broader pattern cover the block again.

Telling the player

Notify puts a short message on screen when a bonus actually lands. Leave it on for something rare and turn it off for anything frequent, or a bonus stops reading as one. Every note follows the player's own luck-drop notification setting, and with the built-in wording a burst inside the notification cooldown reads as one note with a running count (a row with its own TitleKey shows its headline with no count). Lucky finds and harvests share one line; a mob drop has a line of its own, so the two never replace each other.

FieldTypeDefaultDescription
Enabled-optionalWhether the note shows at all. Off unless the row asks.
Color-optionalThe headline colour as #rrggbb. Left out, it is the moment's own: #ffff00 for BreakBlock and PickupItem, #ffaa00 for KillMob. A colour that cannot be read falls back to that default.
Icon-optionalThe icon shown beside it.
TitleKey / SubtitleKey-optionalTranslation keys, never literal text. Leave them out for the built-in wording. See Localization.
* required

Re-tuning a shipped row

Name the row in mods/mmoskilltree/bonus-drops.json and write only the parts you want changed; the body merges field by field over the shipped one.

{
  "schemaVersion": 1,
  "drops": {
    "Mmo_Trork_Kill": { "Enabled": false },
    "Mmo_Ore_Copper": { "Chance": { "Base": 2.0 } },
    "Mmo_Vortex_Charm": "__DISABLED__"
  }
}
  • {"Enabled": false} switches a row off without erasing its pattern.
  • A Loot block alone re-tunes what it hands over and leaves everything else as shipped.
  • The one-word "__DISABLED__" in place of the whole body is the shorthand for switching one off.
  • A name nothing ships decodes on its own, so you can also add a brand-new row from this file alone, with no content pack at all.

Precedence is the ordinary one: built-in defaults, then packs, then your owner file.

What ships in the box

A full set of rows ships with the mod, and every one of them is overridable by name:

  • Ten ore families (Mmo_Ore_Copper, Mmo_Ore_Iron, Mmo_Ore_Silver, Mmo_Ore_Gold, Mmo_Ore_Mithril, Mmo_Ore_Cobalt, Mmo_Ore_Thorium, Mmo_Ore_Adamantite, Mmo_Ore_Onyxium, Mmo_Ore_Prisma) - a lucky break hands over one extra ore, with no Chance block, so the odds are the breaker's own luck.
  • Four cracked-ore holes (Mmo_Ore_Iron_Basalt_Cracked, Mmo_Ore_Cobalt_Slate_Cracked, Mmo_Ore_Thorium_Mud_Cracked, Mmo_Ore_Adamantite_Magma_Cracked) - named, granting nothing, so those variants are carved out of their family row.
  • Mmo_Trork_Kill - a luck-scaled kill bonus, enabled by default. A server that wants it off writes {"drops": {"Mmo_Trork_Kill": {"Enabled": false}}}.
  • Mmo_Vortex_Charm - a rare charm from a gem block, with the full chance formula written out.
  • Mmo_Harvest_Luck Since 1.7.0 - the lucky hand harvest, one more of the stack just picked at the harvester's own luck, enabled by default. A server that wants it off writes {"drops": {"Mmo_Harvest_Luck": {"Enabled": false}}}. See Lucky hand harvests.

Each shipped file carries comments that double as the authoring tutorial for its half of the surface, so opening one in the jar is the fastest way to see a working row.

What happens when you upgrade

On the first start after upgrading, luck-loot.json and mob-drops.json are folded into bonus-drops.json automatically:

  • Every override is carried across. A row that named a drop still names it, a row that stated its own odds still states them, and a pattern switched off is still switched off.
  • Bare keys become anywhere-in-the-name patterns. An old key was a plain substring, so Trork is carried across as *Trork* to keep covering exactly what it covered.
  • One start-up line per converted row names what it became.
  • Both old files are backed up, moved into a timestamped backup/ copy rather than deleted, and a row already present in bonus-drops.json is never overwritten.
  • The shipped row ids are all one Mmo_ convention (Mmo_Ore_Adamantite, Mmo_Vortex_Charm, Mmo_Trork_Kill, and the rest). An owner override still keyed by a row's older id is re-filed under the id that row answers to now, before the file fold above even runs, with one start-up line per re-keyed row. If you have also written the current id yourself, that body wins and the older one is left in place with a line naming the clash, so nothing decides between your two versions for you.
  • The read-only _reference/defaults-luck-loot.json copy is deleted at start-up, since nothing reads it; the built-in luck rows are readable assets in the jar instead.

Packs need re-authoring

The old Server/MMOSkillTree/LuckLoot/ and Server/MMOSkillTree/MobDrops/ pack folders are gone, along with their LuckLoot and MobDrops pack control keys. A pack ships bonus rows at Server/MMOSkillTree/BonusDrops/ instead. The Migration Converter converts a pack's files for you, and /mmomigrate --action=pack reports what an installed pack still carries.

Checking your work

CommandWhat it does
/mmoconfig diff --args=luckLists your bonus-drop overrides by name, with the body you actually wrote. drops and all answer the same way.
/mmoconfig validateAudits the whole table (see the findings below).
/mmoconfig export / importThe config key is bonus-drops. Since 1.6.0 It replaced the old luck-loot key.
/mmoconfig reloadRe-reads the owner file without a restart.

The audit and the start-up check report:

  • a row naming no moment
  • an owner body that cannot be read
  • a pattern with a wildcard in the middle
  • a row-level chance that can never rise off zero
  • a factor name nothing answers to
  • a row that hands nothing over (as information - it may well be a deliberate hole)
  • two rows pinning down the same literal core in one moment
  • a Moment_Item written where it can never pay, or with a Count that is not a number above zero or is above 64

Trimming does not apply here

/mmoconfig trim strips redundant XP overrides only. The bonus-drop owner layer overrides a shipped row field by field rather than restating it whole, so there is nothing redundant for a trim to find.

Shipping rows in a content pack

Prefer a content pack: author one file per row under Server/MMOSkillTree/BonusDrops/ inside a pack folder, zip it, and drop the .zip into your server Mods/ folder. Building the zip.

mods/mmoskilltree/bonus-drops.json stays the quick per-server tweak that always wins.

A pack re-tunes another pack's row by re-authoring its name, or by naming a file with a top-level "Parent": "<name>" and restating one field.

Cross-pack composition happens through shared loot tables: a row's Loot.Lootables names a table under Server/ZiggfreedCommon/Lootables/, and a second pack enriches that table by authoring its own with "ContributesTo" pointing at it, never touching your files. Removing that pack removes exactly what it added.

// your pack
{ "When": { "Kind": "BreakBlock", "Match": "*Log_*" },
  "Loot": { "Lootables": [ "forest_finds" ] } }

// somebody else's pack, enriching it
{ "ContributesTo": "forest_finds",
  "Rolls": [ { "Chance": { "Base": 25.0 },
               "Grants": { "Items": [ { "Item": "Ingredient_Resin" } ] } } ] }

(Base is a percent, so 25.0 is a one-in-four resin drop; leave the Chance group off entirely for a roll that always fires. The roll shape itself - Conditions, Chance, Grants, Ladder, Cue - is documented once on Shared Schemas.)

Full authoring reference: Content pack asset types and the shared library's content paths. When you only want to retune a single shipped row on your own server, mods/mmoskilltree/bonus-drops.json is the quick-tweak alternative to a pack.

  • Mob Drop Commands - the simpler kill-only surface, still separate, consulted first for a kill.
  • Skill trees - where a player's luck comes from.
  • Elite mobs - what replaced the removed built-in tier system, and how to gate a bonus on it.