Pixel Dream StudiosWiki
Mods

Magic Realms

An Iron's Spells addon adding spellcasting mobs and hireable mercenaries that level up and develop personalities.

Magic Realms is an Iron's Spells and Spellbooks addon for Minecraft NeoForge 1.21.1 that aims to expand the amount of spellcasting mobs in the world, with a strong focus on hireable mercenary NPCs that earn titles, develop personalities, and fight alongside the player.

Player guide

Mercenaries

Mercenaries are spellcasting humans that wander the world and can be hired to fight alongside you. Each Mercenary has one of five playstyles with it's own spell list:

  • Mage - Ranged spellcaster, can wield a Staff and Spellbook. Has inate Spell Power in one to four Schools of Magic. Avaliable Schools and Spells are set via Datapack through Tags.
  • Warrior - Melee fighter, can wield Swords. Gains bonus Armor from their Class and Star Level.
  • Tank - Melee fighter who can wield a Shield in addition to Swords.
  • Archer - Ranged fighter wielding a bow. Can use modded bows, and if any bow doesn't natively work you can add it to the "bows" tag from Magic Realms.
  • Assassin - Melee skirmisher with high Crit Chance and a chance to Dodge attacks.

Mercenaries earn Titles through their deeds - see Titles below.

Mercenaries can become immortal by giving them a Hell's Pass, which is dropped from the Dead King and Tyros. Mercenaries with a Hell's Pass who run out of Health will get Stunned for a few seconds (configurable in magic_realms-common.toml).

Each mercenary spawns with:

  • A Gender (male / female), which influences their Name and Appearance.
  • A randomly generated Name drawn from a configurable list (magic_realms-common.toml maleNames / femaleNames).
  • A Personality - an Archetype, a Hobby, a Hometown, and zero to three Quirks. (See Custom archetypes below.)
  • A unique Appearance - either a randomly generated combination of skin / clothes / eyes / hair textures, or a complete preset skin (with about a 10% chance to roll a preset).

Players in Creative Mode can use the Skin Customizer to customize the Appearance of randomly generated Mercenaries.

Hiring Mercenaries - Taverns and Tavernkeepers

Mercenaries hang out in Taverns, structures that generate naturally in plains biomes. Each tavern is staffed by one Tavernkeeper, who sells useful items.

Interacting with a Mercenary will tell you who they are and how long contract lasts.

To hire a Mercenary, simply right-click them with a Contract item.

From the Tavernkeeper you can buy:

  • Contracts, which are needed to hire Mercenaries. There are two kinds: a Temporary Contract (hires for a fixed stretch of time and can be used again to extend) and a Permanent Contract (binds a Mercenary to you for good, but only once they've served you long enough).
  • Maps to structures from Iron's Spells & Spellbooks
  • Food and drink
  • A Room for the night, you can buy a Sleeping Pass to sleep in one of the Tavern's beds. Sleeping in the Tavern grants you Absorption and Resistance effects

Players can also interact with the Tavernkeeper giving him an emerald to get either useful information or some chit chat.

Working with a contracted Mercenary

Once a Mercenary is contracted to you, interacting with them will tell you how much longer they will remain contracted for and triggers some chit chatting related to their Personality. When shift + right clicking them a menu will pop up, and inside it you can:

  • Change their equipment - armor, weapons, off-hand items. Mercenaries can use spells from their equipment if they are on their Class' spell list.
  • View their combat stats - full Attribute breakdown (HP, armor, spell power per school, resistances, vanilla stats) plus their Personality traits.
  • See their inventory - what they've picked up off the ground, plus what they're carrying for you.
  • Give Orders - there's a button to switch between Following you and Patrolling the area.
  • Choose their displayed Title - a selector showing every Title they've earned. Pick one to display above their name, or none at all.

Mercenaries also react to other Mercenaries you've contracted nearby. Hired Mercenaries interact with eahch other acording to their their Personalities, which will apply a temporary buff to them based on their Archetypes (rivals, friends, etc.). archetype interactions can be configured via Datapack: see Custom archetype interactions.

A Mercenary's Quirks also drive in-world behavior, for example: a Mercenary with the cant_swim Quirk will not be able to swim in water, a Mercenary with the animal_friend Quirk receives Stat Bonuses when around Animals, a Mercenary who is afraid_of_the_dark becomes weaker and slower in dark places, and so on.

Titles

Instead of levelling, Mercenaries earn Titles for the things they actually do. Killing two hundred skeletons, walking into an Ancient City, surviving ten thousand points of damage, or simply staying under contract long enough will each earn something.

Every Title carries its own rewards. Some are straightforward Attribute boosts; others are more characterful - extra damage against a particular type of monsters, a passive effect, a chance to inflict something on hit, immunity to a status effect, or a bonus that only applies while wielding a certain kind of weapon.

Titles are permanent once earned, and a Mercenary can hold as many as they qualify for. Their effects all apply at once, so a well-travelled veteran ends up meaningfully tougher than a fresh hire - but only in the ways they earned.

When a contracted Mercenary earns a Title you'll be told about it. One Title at a time is displayed above their name, and as their contractor you choose which one from the contract screen. By default the most prestigious one is shown.

Titles are entirely datapack-driven, so packs can add, retune, or replace the whole set - see Custom titles.

Other Mobs

Magic Realms adds several magic Mobs to spice up the world:

  • Chuchu - Slimes who absorb Magic. Whichever school it absorbs first determines its element going forward, and they can grow absorbing magic. They rarely drop the "Slime Rain" Spell.
  • Fizzles - Sharply dressed Creepers in wizard hats. They don't explode like normal Creepers - instead they cast spells. Be careful: a well-dressed creeper is a dangerous one.
  • Endermages - a conclave of enlightened Endermen living in the Outer End Islands. They have learned magic and use it to teleport across the End freely, no longer bound by the limits of their kin.
  • Tim - a familiar undead lurks in the depths of the Overworld. There are some who call him… Tim.

There's also a small roster of exclusive mercenaries - handcrafted named characters with unique behavior, dialogue, titles, and sometimes special quirks (Eden, Aliana, Amadeus, Catas, Jara, Lilac, Alshanex, GojoMojo). These can appear in Taverns, like normal Mercenaries, identified by their unique skin and name. They are unique to a world: once Amadeus is hired in your save, no other mercenary will ever be Amadeus. They also can't be permanently contracted, they like freedom.

Built-in compatibility

Magic Realms has built-in compatibility for a growing list of other Mods.

Spell Compatability

Spells from these addons are integrated into Mercenary spell lists and can be cast by Fizzles:

  • Tunes 'n Tomes: a Bard's Journey
  • Ender's Spells and Stuff: Requiem
  • Discerning the Eldritch
  • Magic From The East
  • SnackPirate's Aeromancy Additions
  • Cataclysm: Spellbooks

As both Mercenary spell lists and Fizzle spell lists are handled via tag, additional spell compat can be added via Datapack.

Tavern integration

Taverns can include blocks and items from these other mods:

  • Farmer's Delight
  • Brewin' & Chewin'
  • Hearth and Harvest
  • My Nether's Delight
  • Supplementaries
  • Handcrafted
  • Create

The kitchen, bar, and table jigsaw pools dynamically swap to mod-specific variants when those mods are detected. The Tavernkeep will sell food and drink items from these mods, should they be installed.

Developer guide

This section is for modpack authors, datapack authors, and other modders who want to extend Magic Realms' mercenaries with their own personalities, skins, or named characters.

Almost everything personality and appearance-related in Magic Realms is datapack-driven.

Also includes all the config options and what are they for.

Config options

Magic Realms ships with a single common config file at config/magic_realms-common.toml, generated automatically on first run.

Contracts

Controls the timing of the contract / hire system.

KeyDefaultRangePurpose
minutesPerContract101 – ∞How long a single contract lasts before it expires and the mercenary returns to neutral.
minutesUntilPermanent2001 – ∞Total contracted time (across renewals) needed before a mercenary can accept a permanent contract.

Spawning and population

KeyDefaultRangePurpose
customTextureChance0.10.0 – 1.0Probability that a newly spawned mercenary uses a complete skin preset instead of compositing skin parts. 0.1 = 10%. Set to 0.0 to disable presets, 1.0 to make every mercenary use one (requires enough presets in the catalog or some will share).
maxMercenariesInRadius81 – ∞Limits how many mercenaries can occupy chairs / bar stools inside a 20-block radius. Prevents tavern overcrowding inside taverns.

Behavior tuning

KeyDefaultRangePurpose
immortalStunDuration101 – 60Seconds an immortal entity (e.g. a Mercenary with a Hell's Pass) stays stunned after being knocked out, before recovering.
attemptCastUnclassifiedSpellsfalseboolIf true, Mercenaries will try to cast spells from addons that haven't been categorized into Magic Realms' class spell tags (classes/mage/*, classes/warrior/*, classes/archer/*, classes/assassin/*). Set at your own risk - uncategorized spells may not work as expected, may target nothing useful, or may break combat AI.

Name, hometown, and tip lists

These string lists are fully editable in the config - you can replace, add to, or trim them without writing a datapack. Translation keys in tavernTips need matching language entries in any active resource pack.

KeyTypeDefault sizePurpose
maleNameslist of strings~140 entriesPool used to name male mercenaries when no name comes from a fixed personality or skin preset.
femaleNameslist of strings~120 entriesSame, for female mercenaries.
hometownslist of strings35 entriesPool used to roll a hometown string for the personality panel ("Westhollow", "Stormkeep", etc.). Picked once per mercenary at spawn.
tavernTipslist of strings5 entriesTranslation keys the Tavernkeeper picks from when offering advice (message.magic_realms.tavernkeep_tip.1 through …tip.5 by default). Each entry must be a key, not the literal text - extend your language file in parallel to add new tips.

Datapack folder layout

All of the following live under data/magic_realms/:

data/magic_realms/
├── mercenaries/
│   ├── personality/
│   │   ├── archetypes/                    # one .json per archetype
│   │   ├── fixed_personalities/           # one .json per named character
│   │   ├── hobbies/                       # one .json per hobby
│   │   └── archetype_interactions/        # one .json per archetype pair relationship
│   ├── skin_parts/                        # one .json per skin / clothes / eyes / hair piece
│   ├── skin_presets/                      # one .json per complete preset texture
│   ├── titles/                            # one .json per earnable title
│   └── bandit_profiles/                   # one .json per bandit (hostile mercenary) preset
├── tags/
│   └── … (spell tags, item tags, biome tags, etc.)
└── … and, outside the mercenaries tree:

data/<your_namespace>/
└── neoforge/
    └── biome_modifier/                    # one .json per bandit spawn rule

Every catalog is loaded via a vanilla SimpleJsonResourceReloadListener, so /reload will pick up changes without restarting the server (with the caveat that already-spawned mercenaries keep whatever they originally rolled - only newly spawned mercenaries see the updated catalog).

Custom archetypes

An archetype is a personality bucket like "stoic", "jovial", "ruthless", and so on. Every mercenary has exactly one archetype. The archetype:

  • Drives a mercenary's chat tone (combined with their hobby).
  • Pairs with other archetypes via archetype interactions to apply attribute buffs / debuffs.
  • Biases the random class roll: archetypes can favor mages, warriors, or rogues.

File location

data/magic_realms/mercenaries/personality/archetypes/<id>.json

The file's path is the archetype's stable identifier. The catalog also indexes archetypes by the value inside the JSON's id field, which is what every other JSON file in the mod (fixed_personalities, archetype_interactions) refers to. Keep both in sync to avoid confusion.

Schema

FieldTypeDefaultNotes
idstring-Required. Short id used by every other file referring to this archetype (e.g. "stoic").
display_keystring-Required. Translation key for the human-readable name (e.g. "archetype.magic_realms.stoic").
class_weightsobject: string→int{}Per-class weight bonus on top of base_weight. Keys must be lower-case class names: "mage", "warrior", "rogue".
base_weightint10Base weight before class bonus.
in_random_poolbooltrueIf false, this archetype is only selectable through fixed personalities and is never rolled by a normal mercenary spawn. Useful for archetypes that only make sense on named characters.

The probability of an archetype being rolled is max(0, base_weight + class_weights[entity_class]).

Example

A "stoic" archetype that's slightly more common on warriors and mages, slightly less common on rogues:

{
  "id": "stoic",
  "display_key": "archetype.magic_realms.stoic",
  "base_weight": 10,
  "class_weights": {
    "mage":    2,
    "warrior": 4,
    "rogue":  -2
  },
  "in_random_pool": true
}

A "lone_wolf" archetype that exists only as a reference for an exclusive named character - never rolled randomly:

{
  "id": "lone_wolf",
  "display_key": "archetype.magic_realms.lone_wolf",
  "base_weight": 0,
  "in_random_pool": false
}

Don't forget to add the matching archetype.magic_realms.stoic (or whatever) entry to your language file.

Custom fixed personalities

A fixed personality is a pre-rolled set of personality traits - archetype, hobby, hometown, quirks, optional override name - that can be stamped onto a mercenary instead of doing a fresh random roll.

There are three ways a fixed personality reaches a mercenary:

  1. Exclusive mercenary override - entities like Amadeus and Catas declare their personality directly in code via getFixedPersonality(). They reference a fixed personality id from the catalog with FixedPersonality.fromCatalog("magic_realms:amadeus").
  2. Skin preset locking - a skin preset's JSON can specify fixed_personality_id. Whenever that preset is rolled for a mercenary, the linked personality is also stamped on. Useful for characters whose identity is "this exact skin and this exact personality, always together".
  3. Random pool roll - when a mercenary spawns, there's a flat 10% chance (FIXED_POOL_ROLL_CHANCE) that they roll from the fixed-personality random pool instead of doing a normal random personality roll.

File location

data/magic_realms/mercenaries/personality/fixed_personalities/<id>.json

Schema

FieldTypeDefaultNotes
archetypestring-Required. Short id of an archetype in the catalog (e.g. "stoic").
hobbystring"" (none)Short id of a hobby. May be omitted.
hometownstring"" (none)Free-form text shown in the personality panel. May be omitted.
quirksstring array[]List of quirk ids.
override_entity_namestringabsentIf present and non-empty, the mercenary is renamed to this when the personality is applied.
in_random_poolbooltrueWhether this entry can be rolled by the random-pool roll. Set to false for preset-locked characters who should only appear on their matching skin.
weightint (≥ 1)1Weight inside the random pool.
uniquebooltrueIf true, once this id has been assigned to any mercenary in the world (even one that has since died), no other mercenary can ever roll it. Used for one-of-a-kind named characters.

Example: a generic random-pool entry

A friendly bookworm tinker named "Old Marek" who can show up on any mercenary:

{
  "archetype": "jovial",
  "hobby": "tinkering",
  "hometown": "Eastfield",
  "quirks": ["bookworm", "early_riser"],
  "override_entity_name": "Old Marek",
  "in_random_pool": true,
  "weight": 1,
  "unique": true
}

Example: a preset-locked named character

A unique character "Vex" who only appears when their dedicated skin preset rolls - never as a random personality:

{
  "archetype": "ruthless",
  "hobby": "swordplay",
  "hometown": "the Hollow Reach",
  "quirks": ["claustrophobic"],
  "override_entity_name": "Vex",
  "in_random_pool": false,
  "unique": true
}

You'd then point a skin preset's fixed_personality_id at this entry. (See Custom skin presets.)

Custom hobbies

A hobby is a chat reaction package. When a player types a message near a contracted mercenary, the mercenary checks their hobby's keyword list and may respond from a hobby- and archetype-specific pool.

File location

data/magic_realms/mercenaries/personality/hobbies/<id>.json

Schema

FieldTypeDefaultNotes
idstring-Required. Short id used by fixed_personalities JSONs (e.g. "bookworm").
display_keystring-Required. Translation key for the hobby's human-readable name.
responsesobject: string→string array-Required. Map of archetype id → list of translation keys. The special key "default" is used when no matching archetype-specific pool exists.
in_random_poolbooltrueWhether this hobby can be picked by the random hobby roll.

When a mercenary speaks, the resolver looks up responses[archetypeId] first; if the archetype isn't in the map (or its list is empty), it falls back to responses["default"]. If neither exists, the mercenary stays silent.

This is the mechanism that lets a "Bookworm Stoic" sound different from a "Bookworm Jovial" while still sharing the same hobby.

Example

A "tinkering" hobby with a default voice plus dedicated lines for stoic and jovial mercenaries:

{
  "id": "tinkering",
  "display_key": "hobby.magic_realms.tinkering",
  "in_random_pool": true,
  "responses": {
    "default": [
      "speech.my_pack.tinkering.default.1",
      "speech.my_pack.tinkering.default.2"
    ],
    "stoic": [
      "speech.my_pack.tinkering.stoic.1",
      "speech.my_pack.tinkering.stoic.2"
    ],
    "jovial": [
      "speech.my_pack.tinkering.jovial.1",
      "speech.my_pack.tinkering.jovial.2"
    ]
  }
}

The translation keys (speech.my_pack.tinkering.stoic.1, etc.) need matching entries in your language file. Each entry is the actual line the mercenary will say, with %s available for the speaker's name (the formatter splices it in colored).

Custom archetype interactions

An archetype interaction defines what happens when two contracted mercenaries with a specific pair of archetypes are near each other while both contracted by the same player. The most common use is rivalry / friendship buffs and debuffs.

The pair is unordered: archetype_a = "stoic", archetype_b = "jovial" matches both a stoic-near-jovial and a jovial-near-stoic.

File location

data/magic_realms/mercenaries/personality/archetype_interactions/<id>.json

The file's full ResourceLocation becomes the interaction's id.

Schema

FieldTypeDefaultNotes
archetype_astring-Required. Short id of the first archetype.
archetype_bstring-Required. Short id of the second archetype. May be the same as archetype_a (a self-pair).
attribute_modifiersarray of entries[]List of attribute modifiers applied to both mercenaries while in range. See structure below.
radiusdouble12.0Search radius in blocks. Capped at 64.0.
kindstring""Optional tag (e.g. "rival", "friend") read by chat banter code. Purely informational; the attribute_modifiers list is the source of truth for stat effects.

Each entry in attribute_modifiers is:

{
  "attribute": "minecraft:generic.movement_speed",
  "id":        "magic_realms:stoic_jovial.movespeed",
  "amount":    -0.1,
  "operation": "add_multiplied_total"
}

operation accepts the vanilla values: add_value, add_multiplied_base, add_multiplied_total. The id field on each entry is purely an authoring label - at runtime, the tick handler synthesizes a stable per-mercenary modifier ResourceLocation from the interaction id and the entry's index, so collisions with other modifiers are impossible.

Example: stoic vs. jovial rivalry

The two are on the same team but don't get along - both lose 10% movement speed and 5% damage while standing near each other:

{
  "archetype_a": "stoic",
  "archetype_b": "jovial",
  "kind": "rival",
  "radius": 8.0,
  "attribute_modifiers": [
    {
      "attribute": "minecraft:generic.movement_speed",
      "id": "magic_realms:stoic_jovial.movespeed",
      "amount": -0.1,
      "operation": "add_multiplied_total"
    },
    {
      "attribute": "minecraft:generic.attack_damage",
      "id": "magic_realms:stoic_jovial.damage",
      "amount": -0.05,
      "operation": "add_multiplied_total"
    }
  ]
}

Example: stoic-stoic synergy

Two stoics near each other inspire calm - both gain 10% knockback resistance:

{
  "archetype_a": "stoic",
  "archetype_b": "stoic",
  "kind": "friend",
  "radius": 12.0,
  "attribute_modifiers": [
    {
      "attribute": "minecraft:generic.knockback_resistance",
      "id": "magic_realms:stoic_self.kbr",
      "amount": 0.1,
      "operation": "add_value"
    }
  ]
}

Modifiers are applied as transient modifiers: when the mercenaries move out of range, when one of them is unloaded, when their contract ends, or when the datapack is reloaded, the modifiers come off.

Quirks reference

Quirks are not datapack-driven - they're a hardcoded enum because each one has a corresponding piece of behavior code (a goal, a mood reaction, a dialogue hook). You can't add new quirks, but you can reference any of these in your fixed_personalities:

Quirk idEffect summary
afraid_of_the_darkBecomes weaker at dark places.
hates_rainBecomes weaker when rainy.
cant_swimCan't swim in water.
claustrophobicBecomes weaker at narrow spaces.
night_owlActive and happy at night. Mutually exclusive with early_riser.
early_riserActive and happy in the morning. Mutually exclusive with night_owl.
heat_intolerantWeaker in hot biomes. Mutually exclusive with cold_intolerant.
cold_intolerantWeaker in cold biomes. Mutually exclusive with heat_intolerant.
height_scaredWeaker at high altitude.
animal_friendStronger when surrounded by animals.
bookwormStronger when carrying books in the inventory.
gluttonStronger when carrying food in the inventory.

A random mercenary rolls 0 to 3 quirks at spawn (15% / 40% / 35% / 10% odds), filtered to remove conflicting pairs. A fixed personality can declare any subset (the conflict filter is not applied to fixed personalities - author beware).

Custom skin parts

Mercenaries that don't roll a preset are built up from four texture layers stacked on top of one another:

  • skin - base skin tone (face, hands, neutral body).
  • clothes - outfit, armor visuals, robes, etc.
  • eyes - overlaid eye texture.
  • hair - overlaid hair texture.

Each layer is a skin part loaded from a JSON file. At spawn, Magic Realms picks one part of each category, weighted, filtered by the mercenary's gender and entity class.

File location

data/magic_realms/mercenaries/skin_parts/<your_id>.json

The texture itself goes in assets/magic_realms/textures/... (or wherever you point texture to).

Schema

FieldTypeDefaultNotes
texturestring-Required. ResourceLocation of the texture (e.g. "my_pack:textures/entity/human/skin/dark.png").
categorystring-Required. One of "skin", "clothes", "eyes", "hair".
genderstring"any"One of "any", "male", "female".
entity_classstring"any"One of "any", "common", "mage", "rogue", "warrior". "common" is also a wildcard (eligible for every class) but is conventionally used to mark "neutral" pieces like plain shirts.
weightint (≥ 1)1Weight inside its category pool. Higher = more common.

Example: a female-only mage robe

{
  "texture": "my_pack:textures/entity/human/clothes/violet_robes.png",
  "category": "clothes",
  "gender": "female",
  "entity_class": "mage",
  "weight": 2
}

Example: a generic skin tone usable everywhere

{
  "texture": "my_pack:textures/entity/human/skin/freckled.png",
  "category": "skin",
  "weight": 1
}

(Both gender and entity_class default to "any", so they don't need to be listed.)

When the catalog has no matching parts for a given (category, gender, class) combination, the mercenary spawns with that layer missing - Magic Realms logs a warning and continues. So if you're filtering aggressively, make sure each (category, gender, class) triple still has something eligible (or rely on "any" fallbacks).

Custom skin presets

A skin preset is a single complete texture used as-is, instead of compositing the four-layer skin parts. They're meant for handcrafted / iconic looks where you want every pixel to be exactly right.

When a mercenary spawns, there's a chance (configurable via customTextureChance, default ~15%) that a preset is rolled instead of building from parts. If the preset specifies fixed_personality_id, the linked fixed personality is also stamped on the mercenary, so identity (look + behavior) stays welded together.

File location

data/magic_realms/mercenaries/skin_presets/<your_id>.json

Schema

FieldTypeDefaultNotes
texturestring-Required. ResourceLocation of the full preset texture.
display_namestringabsentOptional. If set, the mercenary is named this.
genderstring"any"One of "any", "male", "female". Used to filter the pool by the mercenary's gender.
weightint (≥ 1)1Weight inside the preset pool.
fixed_personality_idstringabsentOptional. Full ResourceLocation of a fixed personality to lock to this preset (e.g. "my_pack:vex").
added_to_poolbooleantrueOptional. When false, the preset is loaded but excluded from the random roll pool. Useful for future bandit profiles.

If both display_name and the linked fixed personality's override_entity_name are present, the preset's display_name wins. If only the personality has an override name, that override is used as the mercenary's display name.

Example: a generic preset

A finely-detailed female ranger that can roll on any random mercenary:

{
  "texture": "my_pack:textures/entity/human/preset/ranger_alia.png",
  "display_name": "Alia",
  "gender": "female",
  "weight": 3
}

Example: an identity-locked preset

A preset for the named character "Vex" defined earlier - appearing only when this preset rolls, and bringing their full personality with them:

{
  "texture": "my_pack:textures/entity/human/preset/vex.png",
  "gender": "female",
  "weight": 1,
  "fixed_personality_id": "my_pack:vex"
}

Notice we don't need display_name here - the override name comes from the linked fixed personality.

Custom titles

A title is an award a mercenary earns for something they did - a kill count, a place they visited, a stretch of service, a beating they survived.

Titles apply to all mercenaries, contracted or hostile. Bandits can be given titles outright through a bandit profile, and can also earn them through play like anyone else.

File location

data/magic_realms/mercenaries/titles/<id>.json

The file's path is the title's stable identifier (e.g. data/magic_realms/mercenaries/titles/dragonslayer.json becomes magic_realms:dragonslayer).

Schema

FieldTypeDefaultNotes
display_keystringrequiredTranslation key for the title's name. Needs a matching entry in your language file.
description_keystring""Translation key for the tooltip line shown under the name in the title selector.
colorstringgold"#RRGGBB" or a vanilla colour name ("gold", "dark_aqua"…). Colours the nameplate text.
priorityint0Higher wins the automatic nameplate pick when a mercenary holds several. Also the sort order in the selector.
requirementslist[]Conditions to earn it. An empty list means the title can never be earned naturally - see Unobtainable titles.
requirement_mode"all" / "any""all"Whether every requirement must be met, or just one.
rewardsobject{}What holding the title grants. See below.
announcebooltrueWhether the contractor gets a chat message when it's earned.
hiddenboolfalseIf true, the title still applies its rewards but never appears in the selector and is never auto-displayed. Useful for gating chains and for secrets.

Requirement types

Each entry in requirements is { "type": …, "target": …, "amount": … }. target and amount are optional and mean different things per type.

typetargetamount
total_kills-kills of any hostile mob
boss_kills-kills of mobs in the Magic Realms bosses tag
kill_entityentity type idkills of that type
kill_entity_tagentity type tag idkills summed across the tag
visit_structurestructure idseparate visits
visit_structure_tagstructure tag idvisits summed across the tag
contract_minutes-cumulative minutes under contract
damage_dealt-cumulative damage points dealt
damage_taken-cumulative damage points taken
star_level-minimum star level
entity_class"mage" / "warrior" / "rogue"-
has_titleanother title's id-

A leading # on a tag id is accepted and ignored, so "minecraft:undead" and "#minecraft:undead" both work.

Structure visits count the entry, not the time spent - standing in a fortress doesn't inflate the counter, but leaving and returning does.

Reward types

Everything in rewards is optional. A purely cosmetic title just leaves it out.

FieldTypeNotes
attribute_modifierslistStandard AttributeModifier entries. Same format as bandit attribute boosts.
passive_effectslist{ "effect", "amplifier", "show_particles" } - kept permanently refreshed on the mercenary.
immune_effectslist of effect idsThe mercenary cannot receive these effects from any source, and any already active are cleared.
on_hit_effectslist{ "effect", "duration", "amplifier", "chance" } - rolled onto the victim on each hit.
bonus_damagedoubleFlat damage added to every attack.
damage_bonus_vslist{ "entity_tag", "multiplier" } - Bane-of-Arthropods style scaling against a family of mobs.
weapon_bonuslist{ "item", "item_tag", "multiplier", "bonus" } - scaling that applies while holding a matching mainhand item. Covers ranged attacks, since an arrow's damage belongs to the shooter.
incoming_damage_multiplierdouble0.9 = 10% less damage taken. Applied before armor.
natural_regenboolGrants out-of-combat health regeneration.

For weapon_bonus, set item, item_tag, or both - matching either is enough. Keep them in one entry rather than two, or a weapon matching both gets the multiplier applied twice.

How rewards stack

A mercenary holding several titles gets all of their rewards at once, and multipliers multiply:

  • incoming_damage_multiplier compounds downward, so five titles at 0.9 give 0.59 (41% reduction), not 50%. This is self-limiting - each title takes a share of what's left - so stacking approaches immunity without reaching it.
  • damage_bonus_vs and weapon_bonus multipliers compound upward, and this is where over-tuning bites. A title at 1.5× against undead, plus another at , plus a 1.3× axe bonus, is 5.85× against a zombie holding an axe.

Flat values (bonus_damage, add_value attribute modifiers) simply sum. As a rule of thumb, keep early titles flat and save multiplicative rewards for capstones.

Example: a kill milestone

The simplest useful shape - one requirement, one flat reward.

{
  "display_key": "title.magic_realms.veteran",
  "description_key": "title.magic_realms.veteran.desc",
  "color": "gold",
  "priority": 40,
  "requirements": [
    { "type": "total_kills", "amount": 1000 }
  ],
  "rewards": {
    "attribute_modifiers": [
      {
        "attribute": "minecraft:generic.armor",
        "id": "magic_realms:veteran_armor",
        "amount": 3.0,
        "operation": "add_value"
      }
    ],
    "incoming_damage_multiplier": 0.95
  }
}

Example: a class-locked weapon specialist

Two requirements in "all" mode, rewarding a fighting style rather than raw stats.

{
  "display_key": "title.magic_realms.axemaster",
  "description_key": "title.magic_realms.axemaster.desc",
  "color": "#C97B4A",
  "priority": 65,
  "requirement_mode": "all",
  "requirements": [
    { "type": "entity_class", "target": "warrior" },
    { "type": "total_kills", "amount": 400 }
  ],
  "rewards": {
    "weapon_bonus": [
      { "item_tag": "c:tools/axe", "multiplier": 1.3, "bonus": 1.0 },
      { "item": "minecraft:netherite_axe", "bonus": 2.0 }
    ]
  }
}

The netherite axe matches both entries, so it picks up the tag bonus and its own - that's the intended way to give one item extra weight on top of a family.

Example: exploration, with two routes to the same reward

"any" mode, plus a status immunity and a passive effect.

{
  "display_key": "title.magic_realms.unclouded",
  "description_key": "title.magic_realms.unclouded.desc",
  "color": "#A8D8E8",
  "priority": 60,
  "requirement_mode": "any",
  "requirements": [
    { "type": "visit_structure", "target": "minecraft:ancient_city" },
    { "type": "damage_taken", "amount": 4000 }
  ],
  "rewards": {
    "immune_effects": [
      "minecraft:blindness",
      "minecraft:darkness",
      "minecraft:nausea"
    ],
    "passive_effects": [
      { "effect": "minecraft:night_vision", "show_particles": false }
    ]
  }
}

Example: a title chain

has_title lets a capstone require others first. Because evaluation cascades within a single pass, a chain resolves the moment its last prerequisite is met.

{
  "display_key": "title.magic_realms.living_legend",
  "description_key": "title.magic_realms.living_legend.desc",
  "color": "#FFE066",
  "priority": 200,
  "requirement_mode": "all",
  "requirements": [
    { "type": "has_title", "target": "magic_realms:veteran" },
    { "type": "has_title", "target": "magic_realms:sworn_sword" },
    { "type": "boss_kills", "amount": 10 }
  ],
  "rewards": {
    "attribute_modifiers": [
      {
        "attribute": "minecraft:generic.attack_damage",
        "id": "magic_realms:legend_damage",
        "amount": 0.15,
        "operation": "add_multiplied_total"
      }
    ],
    "natural_regen": true
  }
}

Unobtainable titles

A title with no requirements is never earned naturally. It can only be granted deliberately - by command, by a bandit profile, or by code. This is the shape to use for story rewards, signature titles belonging to a named character, or anything you want to hand out on your own terms.

{
  "display_key": "title.magic_realms.mace_lord",
  "description_key": "title.magic_realms.mace_lord.desc",
  "color": "#B08D57",
  "priority": 150,
  "announce": false,
  "requirements": [],
  "rewards": {
    "weapon_bonus": [
      { "item": "minecraft:mace", "item_tag": "c:tools/mace", "multiplier": 1.5 }
    ]
  }
}

Set "hidden": true as well if the title should apply its rewards without ever appearing on the nameplate or in the selector.

Managing titles in game

/human title list     <target>
/human title progress <target>
/human title grant    <target> <title>
/human title revoke   <target> <title>
/human title display  <target> <title>
/human title clear    <target>

progress is the useful one while authoring: it lists every unearned title with a completion percentage and a per-requirement breakdown, so you can see exactly how far off a mercenary is. grant bypasses requirements entirely, which is how you test a title without grinding for it.

Custom bandit profiles

A bandit profile is a preset stamped onto a hostile mercenary (bandit) at spawn time, customizing its class, titles, gear, scale, and so on. Profiles let you build varied bandit encounters - a giant brute leading a raid, a fire-mage miniboss in a tower, a hooded archer ambush - and everything data driven.

Bandit profiles are applied to hostile mercenaries only (HostileRandomHumanEntity), the bandit / evil-mercenary variant. Regular contracted mercenaries are unaffected. Each field of a profile is optional: anything you leave out falls back to the random-roll behavior an unprofiled bandit would have, so a profile only constrains what it explicitly sets.

File location

data/magic_realms/mercenaries/bandit_profiles/<id>.json

The file's path is the profile's stable identifier (e.g. data/magic_realms/mercenaries/bandit_profiles/giant_warrior.json becomes magic_realms:giant_warrior).

Schema

FieldTypeDefaultNotes
weightint1Weighted-random selection weight, used when calling pickRandomFiltered.
entity_class"mage" / "warrior" / "rogue"randomPins the class. Without this, the class is rolled normally.
gender"male" / "female"randomPins the gender.
has_shieldboolrandomIf you pined warrior, this sets if it should have shield.
is_archerboolrandomIf you pined rogue, this sets if it should be an archer or an assassin.
star_levelint 1–3randomStat tier, affects the starting attributes.
titleslist of resource locations[]Titles granted outright at spawn, bypassing their normal requirements. All of their rewards apply exactly as if earned. Unknown ids are skipped with a warning.
displayed_titleresource location-Which of the granted titles shows above the bandit's name. Must be one of them. If omitted, the highest-priority non-hidden granted title is used.
entity_scalefloat1.0Resizes the bandit, useful for tiny or giant bandits. 1.5 = 50% bigger.
override_namestring-Replaces the rolled name (e.g. "Hulking Brute").
skin_presetresource location-Id of an entry in your skin_presets/ catalog (the resource location of its JSON file, e.g. "magic_realms:vex" for data/magic_realms/mercenaries/skin_presets/vex.json). The full preset is applied - texture, optional display name, and optional fixed_personality_id binding all carry over. Unknown ids log a warning and fall back to layered random skin.
magic_schoolslist of resource locations[]Mage-only. Explicit list of schools.
magic_schools_tagresource location-Mage-only. Pulls schools from a tag (used if magic_schools is empty).
explicit_spellslist of resource locations[]Exact spells the bandit casts. Highest priority.
spells_tagresource location-Pull spells from a tag (used if explicit_spells is empty).
spells_tag_pick_countintfull tag sizeWhen spells_tag is set, randomly pick this many spells from the tag instead of taking all.
equipmentobject: slot→item id{}Slot keys: "mainhand", "offhand", "head", "chest", "legs", "feet".
attribute_boostslist[]Flat attribute modifiers applied at the end of init, on top of class and level scaling. See below.
loot_tableresource location-Overrides the bandit's loot table on death (e.g. "magic_realms:entities/chuchu").
is_mini_bossboolfalseMarker flag readable as profile.isMiniBoss() for possible features (boss bar, drops, etc.).
immortalboolfalseSets the immortal flag - bandit will be stunned instead of dying.
fixed_personality_idstring-References an entry from your fixed_personalities catalog. Replaces the random archetype roll.
in_random_poolbooltrueIf false, the profile can only be invoked by id (commands, structures, code) and never selected by random pickers.

Difficulty is expressed through star_level, attribute_boosts, equipment and titles rather than a level range. Granting titles is usually the better lever of the two: the buff shows up on the bandit's nameplate, so a player who sees Unbroken floating over a raid leader understands why it isn't dying, instead of running into an invisible stat multiplier.

The spell rules in priority order: explicit_spells first, then spells_tag (with optional spells_tag_pick_count), then fall through to the regular class-driven generator.

Attribute boost format

Each boost uses Mojang's standard AttributeModifier codec. The id must be unique per boost - convention is to namespace it under your pack and the profile id:

{
  "attribute": "minecraft:generic.max_health",
  "id": "magic_realms:bandit/giant_warrior/health",
  "amount": 0.5,
  "operation": "add_multiplied_base"
}

Operations: add_value (flat), add_multiplied_base (% of base), add_multiplied_total (% of post-modifier total). Boosts apply after class/level attribute scaling, so add_multiplied_base of 0.5 on generic.max_health cleanly adds 50% on top of whatever the entity would normally have at its level.

Example: a giant warrior raid leader

A warrior bandit scaled up 1.5×, with full iron armor, 50% more HP, 50% knockback resistance, +3 attack damage, and two granted titles doing further work:

{
  "weight": 5,
  "entity_class": "warrior",
  "has_shield": false,
  "titles": [
    "magic_realms:veteran",
    "magic_realms:unbroken"
  ],
  "displayed_title": "magic_realms:unbroken",
  "entity_scale": 1.5,
  "override_name": "Hulking Brute",
  "equipment": {
    "mainhand": "minecraft:netherite_axe",
    "head":     "minecraft:iron_helmet",
    "chest":    "minecraft:iron_chestplate",
    "legs":     "minecraft:iron_leggings",
    "feet":     "minecraft:iron_boots"
  },
  "attribute_boosts": [
    {
      "attribute": "minecraft:generic.max_health",
      "id": "magic_realms:bandit/giant_warrior/health",
      "amount": 0.5,
      "operation": "add_multiplied_base"
    },
    {
      "attribute": "minecraft:generic.knockback_resistance",
      "id": "magic_realms:bandit/giant_warrior/kb_resist",
      "amount": 0.5,
      "operation": "add_value"
    },
    {
      "attribute": "minecraft:generic.attack_damage",
      "id": "magic_realms:bandit/giant_warrior/damage",
      "amount": 3.0,
      "operation": "add_value"
    }
  ],
  "loot_table": "magic_realms:entities/giant_warrior",
  "is_mini_boss": true
}

Example: a fire-mage miniboss

A three-star mage bandit casting a hand-picked fire and lightning kit, in netherite mage armor with a blaze manual spellbook in the offhand, plus double base health:

{
  "weight": 2,
  "entity_class": "mage",
  "star_level": 3,
  "titles": [
    "magic_realms:archmage"
  ],
  "override_name": "Pyromancer Initiate",
  "magic_schools": [
    "irons_spellbooks:fire",
    "irons_spellbooks:lightning"
  ],
  "explicit_spells": [
    "irons_spellbooks:fireball",
    "irons_spellbooks:firebolt",
    "irons_spellbooks:lightning_bolt",
    "irons_spellbooks:teleport",
    "irons_spellbooks:ball_lightning"
  ],
  "equipment": {
    "head": "irons_spellbooks:netherite_mage_helmet",
    "chest": "irons_spellbooks:netherite_mage_chestplate",
    "legs": "irons_spellbooks:netherite_mage_leggings",
    "feet": "irons_spellbooks:netherite_mage_boots",
    "offhand": "irons_spellbooks:blaze_spell_book"
  },
  "attribute_boosts": [
    {
      "attribute": "minecraft:generic.max_health",
      "id": "magic_realms:bandit/mage_miniboss/health",
      "amount": 1.0,
      "operation": "add_multiplied_base"
    }
  ],
  "is_mini_boss": true
}

Spawning bandits with a profile

Command - right now it's only available via commands for testing around:

/bandit summon "magic_realms:giant_warrior"
/bandit summon "magic_realms:mage_miniboss" 100 64 -200
/bandit list

Custom bandit spawns

Bandit profiles describe what a bandit is. This describes where they come from: magic_realms:add_bandit_spawns is a custom NeoForge biome modifier that adds natural bandit spawns to a set of biomes, and optionally pins those spawns to a weighted pool of profiles.

The entity type is implicit - this modifier only ever spawns bandits, so there's no entity field to get wrong.

File location

Biome modifiers are a NeoForge datapack registry, so they don't live under mercenaries/ with the rest:

data/<your_namespace>/neoforge/biome_modifier/<id>.json

Schema

FieldTypeDefaultNotes
typestringrequiredAlways "magic_realms:add_bandit_spawns".
biomesbiome id, list, or tagrequiredWhich biomes gain the spawn entry. A tag reference needs the leading #.
weightint ≥ 0requiredSpawn weight, relative to every other entry in the monster category for that biome. Vanilla overworld zombies sit around 95, skeletons 100, so a value in the tens makes bandits uncommon but not rare. 0 disables the entry.
minCountint ≥ 11Minimum group size per spawn attempt.
maxCountint ≥ 11Maximum group size. Must be ≥ minCount - the codec rejects the file otherwise, with an error naming both values.
profileslist[]Weighted pool of bandit profiles to pin spawns to. Omit it and bandits spawned here roll from the normal random pool instead.

Each entry in profiles is:

FieldTypeDefaultNotes
idresource locationrequiredA profile id from your bandit_profiles/ catalog. Unknown ids log a warning and the bandit falls back to a random roll.
weightint ≥ 11Relative weight within this modifier's pool only.

How a profile gets chosen

Worth understanding, because it explains why weight does double duty.

Vanilla's spawner doesn't record which biome modifier produced a given spawn - it merges every entry into one weighted list and rolls once. So when a bandit spawns, Magic Realms reconstructs the choice in two steps:

  1. Which modifier? Every add_bandit_spawns modifier that has a non-empty profile pool and covers the bandit's biome becomes a candidate, weighted by its own weight. This means two modifiers overlapping the same biome divide spawns in proportion to their spawn weights, which is what you'd intuitively expect.
  2. Which profile? A second weighted roll inside that modifier's profiles list.

Example: plain spawns, no profiles

Bandits appear in the biomes in your tag, in groups of one to three, using the normal random profile pool.

{
  "type": "magic_realms:add_bandit_spawns",
  "biomes": "#magic_realms:bandit_spawns_in",
  "weight": 15,
  "minCount": 1,
  "maxCount": 3
}

Example: a weighted profile pool

Highwaymen are common, the captain is a rare escort. With these weights the captain is one spawn in six.

{
  "type": "magic_realms:add_bandit_spawns",
  "biomes": "#magic_realms:bandit_spawns_in",
  "weight": 20,
  "minCount": 2,
  "maxCount": 4,
  "profiles": [
    { "id": "magic_realms:highwayman", "weight": 5 },
    { "id": "magic_realms:bandit_captain", "weight": 1 }
  ]
}

Example: biome-specific flavour

Targeting a small set of biomes directly rather than through a tag, to give the desert its own bandit type:

{
  "type": "magic_realms:add_bandit_spawns",
  "biomes": [
    "minecraft:desert",
    "minecraft:badlands"
  ],
  "weight": 10,
  "minCount": 1,
  "maxCount": 2,
  "profiles": [
    { "id": "magic_realms:sand_raider", "weight": 1 }
  ]
}

Targeting biomes that might not exist

Referencing an unregistered biome id directly crashes on world load. If you want to target a biome from a mod that may not be installed, put it in a biome tag as an optional entry and point biomes at the tag:

{
  "replace": false,
  "values": [
    "minecraft:plains",
    { "id": "biomesoplenty:lavender_field", "required": false }
  ]
}

Turning spawns off

Because biome modifiers are a datapack registry, a pack can override any of Magic Realms' spawn modifiers by placing a file at the same path and name with NeoForge's no-op type:

{
  "type": "neoforge:none"
}

Setting "weight": 0 on your own modifier achieves the same thing for files you control.

On this page