Skip to content

Mob Scaling

okereke-dev edited this page Jul 13, 2026 · 6 revisions

Mob Scaling

Hostile mobs gain levels based on distance from spawn, biome, nearby structures, and how many players are nearby. A mob's level scales its health, attack damage, armor, and movement speed, 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.


What triggers scaling

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 a Monster (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.


The level formula

radialLevel   = floor(distanceFromSpawn / step_distance)
adjustedBase  = max(0, baseLevel - 2)
total         = adjustedBase
              + max(0, radialLevel - 1)
              + biomeBonus
              + structureBonus
              + nearbyPlayers * players_bonus_per_player
level         = clamp(total, 1, max-level)
  • baseLevel — from mob_scaling.base-levels.<ENTITY_TYPE> (default 1 for 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 many step_distance blocks (default 180) the mob is from world spawn. The first step doesn't add a level (only radialLevel - 1 counts), so mobs right around spawn stay near the floor.
  • biomeBonus — from mob_scaling.biome-bonuses.<BIOME> (default 0).
  • structureBonus — from mob_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 contributing players_bonus_per_player (default 1). 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.
  • The result is always clamped between 1 and max-level (default 40).

Structure bonus and performance

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.


Stat scaling

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
Armor (level - 1) * armor_per_level 0.0
Movement Speed base speed + (level - 1) * speed_per_level 0.01

Health and damage 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 than a scaled Zombie at the same level, exactly like their unscaled vanilla selves.

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 (default 0.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 (default 8), the multiplier hits exactly 1.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 roughly 1.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.


Rewards

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.


Particle auras

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.


High-level spawn announcements

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.


Spawn protection

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.


Affixes

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.


Exposing level to other plugins

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).

Clone this wiki locally