addColors applied 44 regex replaceAll calls per localized string even though every pattern is a literal quote; plain String.replace does the same substitutions without compiling a pattern per call. matchSkill now caches successful name lookups keyed by lowercased input, invalidated via the locale generation counter, so repeated lookups skip the per-skill localized name scans.
SkillTools.getLocalizedSkillName now reads the Overhaul.Name locale
entries; the all-caps SkillName values move behind the new
SkillTools.getHeaderBannerSkillName, which stylized headers,
scoreboards, and skill command matching keep using.
Mass level changes like /mmoedit all queue dozens of staggered unlock
notifications, each of which played the unlock sound. A per player
quiet window anchored on the last notification now lets the first
sound through and keeps the rest of the stream silent; the messages
themselves are unchanged. External callers of the existing
NotificationManager method keep the always-play behavior through a
new overload.
Server owners can configure commands in the new level_up_commands.yml file with skill level or power level triggers, and other plugins can register commands or callbacks through LevelUpCommandAPI.
Both wrote raw values into the profile, so plugins using them left
skills over their XP threshold with no level ups, events, or XP bar
updates. addXp now runs the normal XP gain path with the amount
unmodified, and addLevels fires the level change events like the
addlevels command. Internal callers that revert cancelled events or
apply vampirism gains keep raw writes via the profile.
/addxp, /addlevels, and /mmoedit echoed 'has been modified' back to the
sender even with -s, flooding the log for plugins that award XP by
dispatching these commands from console.
Unmatched skill name lookups logged an 'Invalid mcMMO skill' warning on
every call, so plugins validating names through ExperienceAPI flooded the
console. The message is now debug output shown only with Verbose_Logging.
The dirty flag was overwritten unconditionally once the database
returned, so a change that landed while the async save was writing its
copy was marked clean and skipped by later saves until the next
unrelated change. The flag is now cleared before the copy is taken and
only restored on failure, so concurrent changes re-mark the profile
dirty and the next save picks them up. Answers the old TODO about
synchronizing the copy: value races during the copy self-heal through
the dirty flag, so no lock is needed.
Child skills store no XP entry of their own, so the raw XP read
crashed on the missing entry. Zero is returned instead, matching
getSkillXpLevel. In-repo callers filter child skills first; only
plugins calling the profile API directly were affected.
Compute the modified XP once in beginUnsharedXpGain instead of running
the permission-heavy perk modifier twice per gain (which also sent the
XP perk debug report to debug-mode players twice), and reuse the local
player reference in logout instead of re-reading a field that is never
null.
With 'Experience_Formula.Cumulative_Curve' enabled, PlayerProfile
resolved the power level through UserManager, which has no entry for
offline-loaded profiles (offline /inspect, ExperienceAPI offline
lookups) and crashed. The profile's own level sum now stands in when
no online player is found; permission-aware filtering is impossible
offline anyway. Also corrects the stale getLastLogin javadoc.
Child skills have no level entry of their own, so the read-then-modify
in PlayerProfile#addLevels crashed on the missing entry before the
child guard in modifySkill could run. Added levels now split evenly
across the child's parent skills, matching how child skill XP and the
offline ExperienceAPI level variants already behave. The addlevels
command rejects child skills up front, so only API callers were
affected.
The inventory click and open handlers strip ability buffs from item
stacks, which mutates item state; the MONITOR contract forbids that.
They now run at HIGHEST like the other mutating handlers moved during
the listener priority cleanup.
Extract the duplicated PvP combat debug report in EntityListener into
a shared helper, correct the entity explode handler name, and fix two
comments that no longer matched the code (traveling-block cleanup
timer cadence, piston retract direction).
The saturation splash workaround re-added the effect with the short
PotionEffect constructor, which resets particle and icon visibility to
the defaults; custom potions brewed to hide their particles started
showing them after the splash.
CAUGHT_FISH normally carries an Item, but the event does not enforce
the caught entity's type; the HIGH-priority handler cast it unchecked
and crashed on plugin-fired events with other entity types.