-
Notifications
You must be signed in to change notification settings - Fork 0
Mob Scaling
Hostile mobs gain levels based on distance from spawn, biome, nearby structures, how many players are nearby, and whether it's currently night or a thunderstorm. A mob's level scales its health, attack damage, armor, movement speed, and follow (aggro) range, and shows in its name (&cLv. {level} {name} by default).
Use /rpgmood info in-game to see the zone and mob levels at your current location — the fastest way to sanity-check your tuning.
Every mob spawn (CreatureSpawnEvent, except SpawnReason.CUSTOM) is checked against mob_scaling.enabled and mob_scaling.hostile-only:
-
hostile-only: true(default) — only mobs that are aMonster(zombies, skeletons, spiders, creepers, endermen, etc.) get scaled -
hostile-only: false— all living entities get scaled, including passive mobs
A mob is scaled at most once — a scoreboard tag (rpgmood_scaled) marks it as already processed.
radialLevel = floor(distanceFromSpawn / step_distance)
adjustedBase = max(0, baseLevel - 2)
total = adjustedBase
+ max(0, radialLevel - 1)
+ biomeBonus
+ structureBonus
+ nearbyPlayers * players_bonus_per_player
+ situationalBonus
level = clamp(total, 1, max-level)
-
baseLevel— frommob_scaling.base-levels.<ENTITY_TYPE>(default1for unlisted types). This is a difficulty seed per mob type — e.g.ZOMBIE: 1,WITHER_SKELETON: 5— not the mob's actual displayed level. -
radialLevel— how manystep_distanceblocks (default180) the mob is from world spawn. The first step doesn't add a level (onlyradialLevel - 1counts), so mobs right around spawn stay near the floor. -
biomeBonus— frommob_scaling.biome-bonuses.<BIOME>(default0). -
structureBonus— frommob_scaling.structure-bonuses.<structure_key>if the mob is near a matching vanilla structure (see below). -
nearbyPlayers— count of players within ~30 blocks (900 blocks²) of the mob, each contributingplayers_bonus_per_player(default1). This means grinding spots with several players standing close together will push mob levels up — by design, but worth knowing before you build a shared farm near spawn. -
situationalBonus—mob_scaling.night_bonus(default2) if it's currently vanilla night (world time13000–23000) where the level is being computed, plusmob_scaling.thunder_bonus(default2) if that world is currently thundering — both can apply at once. Checked live (World#getTime()/World#isThundering()) every time a level is computed, not just baked in at mob spawn, so/rpgmood info, the zone scoreboard/BossBar, and/rpgmood zones's danger column all reflect the current moment. - The result is always clamped between
1andmax-level(default40).
structure-bonuses keys (e.g. stronghold, desert_pyramid, woodland_mansion, ocean_monument, nether_fortress) are vanilla structure names. When a mob spawns, RPGMood checks whether it's near any configured structure using Bukkit's locateNearestStructure — one of the more expensive calls in the API.
To keep this cheap at scale, results are cached per 128×128-block grid cell (capped at 4096 cached cells, LRU-evicted). The first mob to spawn in a given cell pays the lookup cost; every subsequent mob in that same cell reuses the cached bonus. Since vanilla structures don't move once generated, this cache never needs to be invalidated.
Given a resolved level, applied via Bukkit attributes:
| Attribute | Formula | Floor |
|---|---|---|
| Max Health | vanillaMaxHealth * multiplier(level) |
4.0 |
| Attack Damage | vanillaAttackDamage * multiplier(level) |
0.1 |
| Follow Range | vanillaFollowRange * multiplier(level) |
4.0 |
| Armor | (level - 1) * armor_per_level |
0.0 |
| Movement Speed | base speed + (level - 1) * speed_per_level
|
0.01 |
Health, damage, and follow range are read from each mob's own vanilla default (AttributeInstance# getDefaultValue()) rather than a flat number shared by every mob type — a scaled Enderman is
tankier (and, now, more alert) than a scaled Zombie at the same level, exactly like their unscaled vanilla selves.
Follow range is aggro distance — how far away a mob notices and starts chasing a player. It shares the exact same multiplier(level) curve as health/damage: a weak level-1 mob notices players from a bit less far away than vanilla (early_game_fraction of vanilla range), while a strong mob at or past parity_level notices from just as far as vanilla, or farther. Unlike health/damage there's no separate "rewards" angle here — it's purely about a scaled mob's danger feeling proportionate to its numeric level, not just its stats once a fight starts.
multiplier(level) = early_game_fraction + (level - 1) * perLevelGrowth
perLevelGrowth = (1.0 - early_game_fraction) / (parity_level - 1)
- At level 1, a mob is at
early_game_fraction(default0.85) of its own vanilla health and damage together — weaker than vanilla, evenly, without one stat crashing far harder than the other. - At
parity_level(default8), the multiplier hits exactly1.0— the mob is fully vanilla-equivalent in both stats at the same time. - Beyond that it keeps climbing at the same linear rate — at the level cap (
40, default) the multiplier is roughly1.7, i.e. about 70% stronger than vanilla in health and damage alike.
Armor and movement speed are unaffected by this curve and keep their own simple per-level
bonus. Defaults: armor_per_level: 0.25, speed_per_level: 0.0015.
Before this rework, health and damage used two disconnected flat formulas that reached "vanilla-equivalent" at wildly different levels (health around level 4, damage not until around level 21) — a mid-level mob could have full vanilla health but still hit noticeably softer than vanilla. The shared multiplier fixes that.
Killing a scaled mob grants bonus XP on top of its vanilla drop: level * mob_scaling.bonus_xp_per_level (default 2). A level 20 mob gives +40 bonus XP. Set bonus_xp_per_level: 0 to disable.
A mob's level also tracks toward your /rpgmood leaderboard level entry (highest-level mob you've ever killed), and cumulative scaled-mob kills unlock the Slayer/Danger Seeker achievements — see Commands.
Scaled mobs show a subtle, colour-tiered particle aura at their feet — visible only to players within ~30 blocks, so it doesn't clutter the view from a distance:
| Level | Aura |
|---|---|
| 1–7 | None |
| 8–14 | Barely-visible blue shimmer |
| 15–24 | Tiny warm yellow glow |
| 25–34 | Small orange ember |
| 35+ | Modest red glow |
Toggle with mob_scaling.particle_aura in config.yml (default true). See Configuration.
When a mob's computed level crosses mob_scaling.announcement.min-level (default 25), RPGMood broadcasts a server-wide message (default min-level: 25, cooldown-seconds: 120 between announcements, so heavy spawn activity near the edge of the map doesn't spam chat):
mob_scaling:
announcement:
enabled: true
min-level: 25
cooldown-seconds: 120
message: "&c⚠ A Level {level} {name} has emerged in {biome}!"Supports {level}, {name} (mob type, e.g. "Wither Skeleton"), and {biome} placeholders. Set enabled: false to disable entirely.
Hostile mobs never naturally spawn within spawn_protection.radius blocks (default 64) of
world spawn — keeps the immediate spawn area/starter base safe without delaying the first real
encounter with danger once you leave it. Only blocks natural spawns; passive mobs and
plugin/command-summoned entities are unaffected. See Configuration.
Scaled mobs can additionally roll 1–2 RPG-flavored modifiers on top of their numeric level —
pure variety, not a second difficulty system. Odds scale with level (mob_scaling.affixes. chance-by-level/second-affix-chance-by-level), starting at level 10 and maxing out at level
40 (50% chance of at least one, 20% chance of a second). Below level 10, mobs never roll an
affix.
| Affix | Effect |
|---|---|
| Swift | +25% movement speed (+40% at level 25+) |
| Wraith | Spawns invisible |
| Bleeding | On-hit chance to inflict a damage-over-time bleed on the player |
| Poisonous | On-hit chance to apply Poison |
| Regenerating | Passive health regeneration + bonus armor toughness |
| Chilling | On-hit chance to apply Slowness |
Since name tags are off by default, affixed mobs are signaled two
other ways: a small secondary particle marker (in addition to the level-color aura above), and
a one-time action-bar warning to the first player who gets within mob_scaling.affixes. warning-radius blocks (default 12) — a second player approaching later only sees the
particle, not a repeat warning. Configurable per-affix odds/values live under mob_scaling. affixes.roster in config.yml — see Configuration.
Every scaled mob's level is stored on the entity's PersistentDataContainer under the key rpgmood:level (an Integer), alongside the existing rpgmood_scaled scoreboard tag. Any other plugin can read it without a hard dependency on RPGMood:
NamespacedKey levelKey = new NamespacedKey("rpgmood", "level");
Integer level = entity.getPersistentDataContainer().get(levelKey, PersistentDataType.INTEGER);This is intended for exactly the kind of cross-plugin hook where a loot plugin (e.g. RPGLoot) wants to scale drop rarity by RPGMood's difficulty level — the tag is present whenever level is non-null, absent otherwise (mob wasn't scaled, or scaling is disabled).