WMInventoryControl is an add-on for WeaponMechanics that enforces per-player weapon rules and blocks edge-case exploits.
It uses a namespaced PDC mark to tag a certain amount of weapons from the player's inventory and only those marked weapons are allowed to fire.
- Mark on first use (main or off-hand) during WeaponMechanics’ pre-shoot stage.
- Managed titles are marked on use; if marking is denied, the shot is cancelled.
- Weight-only titles (e.g.
GRENADEwhen present only in weight rules) are not marked and behave normally. - Unmark when items truly leave the player’s control (drops, real containers, frames, admin transfers, death, etc.).
- Limits & Groups (per-weapon caps,
EXCLUSIVE,POOLwith per-member caps). - Combat Weight (with CombatLogX) to restrict heavy loadouts only while in combat.
- Configurable combat restrictions (block marking, re-marking, dropping, and moving marked weapons while in combat).
- /invsee-safe transfers with pre- / post-handling and temp tags.
- Per-listener debug toggles so you get only the logs you want.
- Java 21
- Paper 1.21+ (uses Paper APIs like
PlayerArmorChangeEventandDecoratedPotInventory) - WeaponMechanics (hard-depend)
- CombatLogX(soft-depend; enables combat/weight features)
- BlueSlimeCore(soft-depend; only if your stack uses it)
- Drop WMInventoryControl and WeaponMechanics into
/plugins(add CombatLogX if you’ll use weights). - Start the server to generate
plugins/WMInventoryControl/config.yml. - Tweak the config (limits, groups, containers, weight groups).
- Run
/wmic reloadanytime to apply changes.
Command & Permission
/wmic reload— reload the plugin configuration
Permission:wmic.reload
When a managed title is fired for the first time, WMIC writes a namespaced PDC mark on the held item (supports both main-hand and off-hand). Marking stacked items is denied.
- Managed items must be (or become) marked to fire. If
tryMark(...)denies (limits/groups/combat), the shot is cancelled with a player-facing reason. - Titles that are not managed (e.g. appear only in weight rules) are not marked and aren’t affected by per-weapon/group caps.
- If you enable combat restrictions, marking may be denied with
MARK_BLOCKED_IN_COMBAT, and re-marking the same title you unmarked during combat may be denied withREMARK_BLOCKED_IN_COMBATuntil you are untagged.
Unmark happens only when an item truly leaves the player or a rule says it should be cleared:
- Drops
- Put into real containers (chest, barrel, shulker, hopper, etc.)
- Item frames (in/out)
- /invsee transfers (pre/post flow; safe for admins)
- Death (drops and, if
keepInventory, items remaining in inventory) - Bundles (guard + cleanup)
- Decorated Pot (checked next tick because of vanilla quirks)
- Hat guard (prevents equipping marked weapons as helmets, including
/hat, and restores previous helmet)
The plugin only unmarks after a successful placement or a trusted exit; attempts that bounce back (e.g. to recipe/utility UIs) are cancelled instead so items stay marked.
A hard cap per WeaponMechanics title. Extras may exist but won’t be shootable (mark is denied).
- EXCLUSIVE — player may have only one marked item from the group.
- POOL — the group has a shared limit, and you can set per-member caps; the plugin enforces both the pool’s total and each member’s cap.
While a player is in combat:
- INDIVIDUAL: every copy of a title adds its cost.
- SHARED_POOL: each title counts once (first use during the current combat).
- Auto-reset on untag if
remove-limit-post-combat: true. - If a player unmarks a title during combat, re-marking that title is blocked until they are untagged (anti-swap abuse).
You control how per-combat weights reset once a player leaves combat:
remove-limit-post-combat: true→ reset immediately on untag (this is the default).remove-limit-post-combat: false+weight-reset-timeout-seconds: 0→ persist indefinitely across fights; only resets on death/logout/server restart.remove-limit-post-combat: false+weight-reset-timeout-seconds: N→ reset after N seconds out of combat. The timer is canceled if the player re-enters combat before it expires.
This lets you choose between strict reset-on-untag, a grace period, or “sticky” weights for hardcore servers.
Two lists control behavior:
- regular — real storage; placement allowed, weapon unmarked after it actually lands.
- non-accepting — utility/recipe UIs that typically reject random items; we cancel attempts to place marked weapons so you don’t get “bounced-back but unmarked”.
Defaults treat FURNACE/SMOKER/BLAST_FURNACE as non-accepting to avoid unmarking on rejected placements. Customize as needed.
- Proper priorities (
HIGHfor checks,MONITORfor accrual/marking), and ignoreCancelled where appropriate. - Covers shift-click, drag, and number-key flows.
- Handles admin manually transferring items from their inventories to regular players, frames, hats, decorated pots, bundles, death (with/without
keepInventory), and combat transitions.
Main listeners
WeaponShootListener(mark on pre-shoot; enforce)WeightCheckListener/WeightAccrualListenerContainerUnmarkListener/SpecialContainerListenerItemFrameListenerPlayerDeathListener/PlayerQuitListenerInvseeTransferListenerHatGuardListenerCombatEndListener
# Per-listener debug toggles (ALL default to false)debug-mode:
weapon-shoot: falsecontainer-unmark: falsecontainer-block: falseinvsee-transfer: falseplayer-drop: falseitem-frame: falseplayer-death: falsespecial-container: falsehat-guard: falseauction: falsegroups: falsecombat: falseconfig-manager: falseinventory-manager: falseweight: falsecombat-end: false# Containerscontainers:
# REGULAR CONTAINERS — real storage that accepts arbitrary items.# We allow placement and UNMARK once the item actually goes in.# Notes:# - ENDER_CHEST: persistent storage per player. Still considered a real container.# - SHULKER_BOX: allows putting items inside.# - DECORATED_POT: commented out (UI quirks); handle via a special listener if needed.# - PLAYER/CRAFTING/CREATIVE are NOT containers.regular:
- CHEST
- BARREL
- ENDER_CHEST
- SHULKER_BOX
- HOPPER
- DISPENSER
- DROPPER# - DECORATED_POT # special-cased in code# NON-ACCEPTING CONTAINERS — utility/recipe UIs that reject most items (like feathers).# We CANCEL any attempt to place a MARKED weapon here and DO NOT unmark.# This avoids the "unmarked but bounced back" problem.# Notes:# - WORKBENCH: classic crafting table. You usually don't allow marked weapons in recipes# anyway; add it here if you want to hard-cancel attempts.# - JUKEBOX: accepts only discs; add here if you want to hard-cancel attempts for weapons.non-accepting:
- BEACON
- ENCHANTING
- ANVIL
- GRINDSTONE
- CARTOGRAPHY
- LOOM
- SMITHING
- STONECUTTER
- MERCHANT
- BREWING
- FURNACE
- SMOKER
- BLAST_FURNACE# Auction commands that we treat as selling (for your flows)auction-commands:
- ah
- auction
- auc
- auchand# Per-weapon hard caps (by WeaponMechanics weapon title)weapon-limits:
AK_47: 1M4A1: 1AUG: 2# Groups — EXCLUSIVE or POOL (with optional per-member caps)groups:
Primary_Exclusive:
type: EXCLUSIVEmembers:
- AK_47
- M4A1Pistols:
type: POOLlimit: 3members:
Glock_17: 150_GS: 2# Weight rules (CombatLogX only) — active while tagged in combatweight-groups:
Individual_Explosives:
type: INDIVIDUALmaximum_limit: 80items:
GRENADE: 10FLASHBANG: 15MOLOTOV: 25Collective_Group_Explosives:
type: SHARED_POOLmaximum_limit: 5items:
GRENADE: 2MOLOTOV: 2FLASHBANG: 3# when combat ends, clear per-combat weights# true -> weights reset immediately on untag (timeout is ignored)# false -> weights persist; see weight-reset-timeout-seconds belowremove-limit-post-combat: true# Optional timeout reset while out of combat.# If > 0 AND remove-limit-post-combat is FALSE, WMIC clears the weight# after the player has been OUT OF COMBAT for this many seconds.# Set to 0 to disable the timeout.weight-reset-timeout-seconds: 0# Optional: combat-time interaction restrictions (CombatLogX)combat-restrictions:
block-marking-in-combat: false # deny first-time marking while in combatblock-re-marking-in-combat: true # deny re-marking titles unmarked during the same combatblock-dropping-in-combat: true # deny dropping marked weapons while in combatblock-moving-marked-in-combat: true # deny moving marked weapons (shift/drag/number-key) while in combatToggle specific areas under debug-mode.* (all default to false). Common keys:
weapon-shoot, container-unmark, container-block, invsee-transfer, player-drop, item-frame, player-death, special-container, hat-guard, auction, groups, combat, config-manager, inventory-manager, weight, combat-end, config-reload.
WMIC exposes a Bukkit service you can fetch at runtime:
packageyour.plugin.package;
importdestroier.WMInventoryControl.api.WMIC;
importdestroier.WMInventoryControl.api.WMICApi;
importdestroier.WMInventoryControl.api.types.DenyReason;
importdestroier.WMInventoryControl.api.types.MarkResult;
importdestroier.WMInventoryControl.api.types.UnmarkCause;
importorg.bukkit.entity.Player;
importorg.bukkit.inventory.ItemStack;
publicfinalclassWMICExampleUsage {
// You can cache the service once; WMIC.api() handles lookup + caching.privatefinalWMICApiapi = WMIC.api();
publicvoiddemo(Playerplayer, ItemStackitem) {
// Mark on demand (returns MarkResult with DenyReason on failure)MarkResultres = api.tryMark(player, item);
if (!res.allowed()) {
DenyReasonr = res.reason();
// handle: r can be NOT_MANAGED, STACKED_ITEM, EXCLUSIVE_CONFLICT,// POOL_TOTAL_LIMIT, POOL_MEMBER_CAP, PER_WEAPON_LIMIT,// MARK_BLOCKED_IN_COMBAT, REMARK_BLOCKED_IN_COMBAT, ...return;
}
// Unmark with an explicit cause (player may be null for non-player flows)api.unmark(player, item, UnmarkCause.PLAYER_DROP);
// Queriesbooleanmanaged = api.isManagedWeapon(item);
booleanmarked = api.isMarked(item);
inthave = api.countMarked(player, "AK_47");
intlimit = api.getWeaponLimit("AK_47");
}
}Call the API from the main server thread.
- WeaponPreMarkEvent — fired before marking; cancellable. You may also attach a DenyReason to explain why.
- WeaponMarkedEvent — fired after a successful mark.
- WeaponUnmarkedEvent - fired after an unmark (includes the UnmarkCause).
Example: veto marking under your own condition
@EventHandlerpackageyour.plugin.package;
importdestroier.WMInventoryControl.api.events.WeaponPreMarkEvent;
importdestroier.WMInventoryControl.api.types.DenyReason;
importorg.bukkit.event.EventHandler;
importorg.bukkit.event.Listener;
publicfinalclassWMICExampleListenerimplementsListener {
@EventHandlerpublicvoidonPreMark(WeaponPreMarkEvente) {
// Example: veto marking under your own conditionif (/* your condition */false) {
e.setDenyReason(DenyReason.PER_WEAPON_LIMIT);
e.setCancelled(true);
}
}
}Some commonly-used enum values you may observe in WeaponUnmarkedEvent:
PLAYER_DROP, PUT_IN_CONTAINER, INVSEE_TRANSFER, ITEM_FRAME,
DEATH_DROP, DEATH_KEEP_INVENTORY, DECORATED_POT, SPECIAL_CONTAINER,
HAT_GUARD, API, OTHER.
- /invsee paths use pre/post listeners + temporary PDC tags so items end up unmarked for admins and can be safely redistributed.
- Guard rails avoid dupes when shuffling items via number keys or drag operations.
- With keepInventory, the plugin will unmark weapons that remain in the inventory after death where appropriate.
- You can /wmic reload to apply config changes without a full restart.
- Author: DestroierMariusM / DMariusM
- Depends on WeaponMechanics
- Soft-depends: CombatLogX, BlueSlimeCore
- WeaponMechanics by CJCrafter & DeeCaaD — thanks for the API and plugin.
- Thanks to the Paper/Bukkit ecosystem.
If you open an issue, please include:
- Paper, WMIC, WeaponMechanics, and (if used) CombatLogX versions
- Your relevant config sections (weapon-limits, groups, weight-groups, containers)
- What you did, what you expected, and what happened (plus any WMIC debug logs if you toggled them)
Copyright © 2024–2025 DestroierMariusM (DMariusM). Licensed under GPL-3.0-or-later. See LICENSE.