Links
Tags
Creators
Details
4.1.2
Compatibility
Changes
[4.1.2]
Profession & Restriction System — Complete Overhaul
Complete rewrite of the profession restriction enforcement system. The old tick-based polling approach has been replaced with event-driven handlers and Mixin injection points, eliminating all performance overhead and fixing several long-standing bugs.
Added
-
/myprofessioncommand (alias/myjob): New player-facing command to view active licenses without needing staff.- Displays all active licenses with their formatted profession name and color.
- Distinguishes permanent licenses (
[Permanent]) from RP temporary licenses ([RP] expires on dd/MM/yyyy). - Accessible to all players — no permission required.
-
Container Open Restrictions: New restriction type allowing admins to block specific containers from being opened without the required profession.
- New config key
containerOpenRestrictionsinrpessentials-professions.toml. - Format:
block_id;profession1,profession2— e.g.minecraft:anvil;forgeron. - Wildcard support (e.g.
minecraft:*_shulker_box). - Implemented via
MixinServerPlayerGameMode— fires exactly once per open attempt, zero overhead. - New message key
containerOpenBlockedMessagein[Messages]section ofrpessentials-professions.toml. - New methods in
ProfessionRestrictionManager:canOpenContainer(),getContainerOpenBlockedMessage(),getContainerRequiredProfessions().
- New config key
-
New Mixins — Craft restrictions extended to all workstations:
MixinResultSlot— Injects intoSlot.mayPickup()to block pickup from any result slot whose container is aResultContainer(coversGrindstoneMenu,StonecutterMenu,LoomMenu,CartographyTableMenu) or whose slot type isResultSlot(vanilla crafting table). Zero tick polling.MixinItemCombinerMenuBase— Abstract Mixin base declaring@Shadow protected ResultContainer resultSlots, shared by the two concrete subclass mixins below.MixinSmithingMenu— Injects intoSmithingMenu.mayPickup()to block netherite upgrades at the smithing table.MixinAnvilMenu— Injects intoAnvilMenu.mayPickup()to block repair/enchant results at the anvil.MixinServerPlayerGameMode— Injects into the container open flow to enforcecontainerOpenRestrictions.
-
New Mixins — Armor equip restrictions:
ArmorSlotAccessor—@Mixin(targets = "net.minecraft.world.inventory.ArmorSlot")accessor interface exposing theownerfield via@Accessor. Required becauseArmorSlotis package-private.MixinArmorSlot—@Mixin(targets = "net.minecraft.world.inventory.ArmorSlot")injecting intomayPlace(ItemStack)to block armor from being dragged into an armor slot.MixinArmorItemUse—@Mixin(ArmorItem.class)injecting intouse()to block armor equip via right-click. Runs on both sides: client-side usesClientProfessionRestrictions(synced at login) to prevent the equip animation before it starts; server-side enforces the restriction authoritatively. Eliminates the visual bug where armor briefly appeared equipped before being removed.
Changed
-
ProfessionRestrictionManager— Simplified via genericcanPerformAction():canCraft(),canBreakBlock(),canUseItem(),canEquip()all previously duplicated the same guard logic (init check, exemption check, global block check, permission check). All four now delegate to a single privatecanPerformAction(player, resourceId, globalList, allowedList)method, reducing the class by ~60 lines.- Added
sendCraftBlockedMessage()utility used by the new mixins. - No behavior change — all existing functionality preserved.
-
ProfessionRestrictionEventHandler— Refactored and bug-fixed:- Bug fix:
onBlockBreak,onLeftClickBlock, andonAttackEntitywere missing theif (player.isCreative()) return;guard. Creative mode players were incorrectly blocked by profession restrictions. onRightClickItemandonRightClickBlocknow share a singlecheckItemUse()helper — eliminates duplicated logic.- Removed
onEquipmentChange(LivingEquipmentChangeEvent) entirely — replaced byMixinArmorSlotwhich is event-driven and has no visual artifacts. - Removed
lastEquipmentWarningcache andEQUIPMENT_WARNING_COOLDOWNconstant (no longer needed). cleanupCaches()updated accordingly.
- Bug fix:
-
ProfessionConfig— New entries:containerOpenRestrictions(List) — blocks container opening by profession. Default: empty.msgContainerOpenBlocked(String) — message shown when container open is blocked. Variables:{profession}. Supports color codes.
-
rpessentials.mixin.json— New entries:MixinResultSlotArmorSlotAccessorMixinArmorSlotMixinArmorItemUseMixinItemCombinerMenuBaseMixinSmithingMenuMixinAnvilMenuMixinServerPlayerGameMode
Removed
CraftingAndArmorRestrictionEventHandler— Entirely deleted.- Previously polled the craft result slot every 5 ticks for every player with a crafting menu open.
- Tracked container open/close events (
PlayerContainerEvent.Open/Close) to filter active players. - Replaced by
MixinResultSlotwhich fires only when the player actually attempts to pick up the result — zero polling overhead. cleanupCaches()call removed fromRpEssentials.onServerTick().
Fixed
- Creative mode bypass: Creative players were incorrectly blocked by block break, left-click, and attack restrictions. All three handlers now return early for creative players.
- Smithing Table (netherite upgrade) not restricted: Upgrading items with netherite bypassed all profession restrictions because
SmithingMenuusesItemCombinerMenu.mayPickup()rather thanResultSlot. Fixed viaMixinSmithingMenu. - Anvil not restricted: Same root cause as smithing table. Fixed via
MixinAnvilMenu. - Grindstone, Stonecutter, Loom, Cartography Table not restricted: These menus use anonymous
Slotsubclasses withResultContaineras their container — notResultSlot.MixinResultSlotnow also checksslot.container instanceof ResultContainerto cover all these cases. - Armor equip visual bug: The previous
LivingEquipmentChangeEventapproach removed armor server-side after it had already been equipped client-side, causing a visible flash.MixinArmorItemUsenow blocks the equip on both sides simultaneously — the client never starts the equip animation. - Schedule system: The previous version was not checking the schedule time on player login, it is now fixed and checks properly when a player login.
Technical
Mixin design notes:
MixinItemCombinerMenuBaseexists solely to expose@Shadow protected ResultContainer resultSlotstoMixinSmithingMenuandMixinAnvilMenuvia Mixin inheritance. Direct@Shadowon a field declared in a parent class is not supported by the Mixin processor — the base abstract mixin pattern is the correct workaround.ArmorSlotis package-private (class ArmorSlotwithoutpublic) — direct Java references to it cause compile errors. BothArmorSlotAccessorandMixinArmorSlotuse@Mixin(targets = "net.minecraft.world.inventory.ArmorSlot")(string form) to avoid this.- All
@Injectannotations on mapped methods useremap = falseto avoid obfuscation mapping failures during compilation. - All new mixins guard against creative mode and client-side execution where appropriate.
Performance comparison:
| System | Before | After |
|---|---|---|
| Craft restriction check | Every 5 ticks per player with open menu | On pickup attempt only |
| Armor equip restriction | Every tick via LivingEquipmentChangeEvent |
On mayPlace() / use() only |
| Container open restriction | Not implemented | On open attempt only |
| Total tick overhead | ~12 checks/sec/player (crafting) + continuous armor | Zero |
Migration Notes:
- No breaking changes — fully backward compatible with 4.1.1.
CraftingAndArmorRestrictionEventHandleris gone — remove any external references if you have custom patches.- New config keys (
containerOpenRestrictions,msgContainerOpenBlocked) are generated automatically on first launch with empty defaults. - Clients connecting to a 4.1.2 server must also run 4.1.2 —
MixinArmorItemUseadds a client-side restriction path that requires theSyncProfessionRestrictionsPacketalready sent since 4.0.0.
Optional dependencies
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:yJ9CnauQ:50FMdZ6Z"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:yJ9CnauQ:50FMdZ6Z"
}

