Bandits
Bandit profiles for hostile mercenaries, and where bandits spawn.
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>.jsonThe 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
| Field | Type | Default | Notes |
|---|---|---|---|
weight | int | 1 | Weighted-random selection weight, used when calling pickRandomFiltered. |
entity_class | "mage" / "warrior" / "rogue" | random | Pins the class. Without this, the class is rolled normally. |
gender | "male" / "female" | random | Pins the gender. |
has_shield | bool | random | If you pined warrior, this sets if it should have shield. |
is_archer | bool | random | If you pined rogue, this sets if it should be an archer or an assassin. |
star_level | int 1–3 | random | Stat tier, affects the starting attributes. |
titles | list 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_title | resource 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_scale | float | 1.0 | Resizes the bandit, useful for tiny or giant bandits. 1.5 = 50% bigger. |
override_name | string | - | Replaces the rolled name (e.g. "Hulking Brute"). |
skin_preset | resource 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_schools | list of resource locations | [] | Mage-only. Explicit list of schools. |
magic_schools_tag | resource location | - | Mage-only. Pulls schools from a tag (used if magic_schools is empty). |
explicit_spells | list of resource locations | [] | Exact spells the bandit casts. Highest priority. |
spells_tag | resource location | - | Pull spells from a tag (used if explicit_spells is empty). |
spells_tag_pick_count | int | full tag size | When spells_tag is set, randomly pick this many spells from the tag instead of taking all. |
equipment | object: slot→item id | {} | Slot keys: "mainhand", "offhand", "head", "chest", "legs", "feet". |
attribute_boosts | list | [] | Flat attribute modifiers applied at the end of init, on top of class and level scaling. See below. |
loot_table | resource location | - | Overrides the bandit's loot table on death (e.g. "magic_realms:entities/chuchu"). |
is_mini_boss | bool | false | Marker flag readable as profile.isMiniBoss() for possible features (boss bar, drops, etc.). |
immortal | bool | false | Sets the immortal flag - bandit will be stunned instead of dying. |
fixed_personality_id | string | - | References an entry from your fixed_personalities catalog. Replaces the random archetype roll. |
in_random_pool | bool | true | If 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 listCustom 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>.jsonSchema
| Field | Type | Default | Notes |
|---|---|---|---|
type | string | required | Always "magic_realms:add_bandit_spawns". |
biomes | biome id, list, or tag | required | Which biomes gain the spawn entry. A tag reference needs the leading #. |
weight | int ≥ 0 | required | Spawn 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. |
minCount | int ≥ 1 | 1 | Minimum group size per spawn attempt. |
maxCount | int ≥ 1 | 1 | Maximum group size. Must be ≥ minCount - the codec rejects the file otherwise, with an error naming both values. |
profiles | list | [] | 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:
| Field | Type | Default | Notes |
|---|---|---|---|
id | resource location | required | A profile id from your bandit_profiles/ catalog. Unknown ids log a warning and the bandit falls back to a random roll. |
weight | int ≥ 1 | 1 | Relative 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:
- Which modifier? Every
add_bandit_spawnsmodifier that has a non-empty profile pool and covers the bandit's biome becomes a candidate, weighted by its ownweight. This means two modifiers overlapping the same biome divide spawns in proportion to their spawn weights, which is what you'd intuitively expect. - Which profile? A second weighted roll inside that modifier's
profileslist.
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.
