Event System
DupeTrace monitors dozens of Bukkit events to track item movement across your server. If an item can move, we're watching it. Let's break down every event we track and why.
Event Categories
We track events in these categories:
Creation Events – Items being crafted, enchanted, brewed, etc.
Acquisition Events – Items picked up, fished, looted, etc.
Inventory Events – Items clicked, dragged, swapped in inventories
Container Events – Items moved to/from chests, hoppers, etc.
Drop & Death Events – Items dropped or lost on death
Block & Entity Events – Items in item frames, armor stands, etc.
World Events – Chunk unloads, loot generation
Creation Events
These events track items being created or modified.
CraftItemEvent
CraftItemEventFired When: Player crafts an item What We Track: The crafted result Action Logged: CRAFTED
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
fun onCraft(event: CraftItemEvent) {
val player = event.whoClicked as? Player ?: return
val result = event.currentItem ?: event.inventory.result ?: return
tagAndLog(player, result, "CRAFTED", player.location)
}Why This Matters: Crafted items need UUIDs immediately. If a dupe glitch involves crafting, we catch it.
EnchantItemEvent
EnchantItemEventFired When: Player enchants an item at an enchanting table What We Track: The enchanted item Action Logged: ENCHANTED
Edge Case: Enchanting doesn't create a new item, but we track it to log the player interaction.
BrewEvent
BrewEventFired When: Brewing stand finishes brewing What We Track: All items in the brewing stand inventory Action Logged: None (just ensures UUID exists)
Note: Potions are usually stackable, so most won't get UUIDs. But some modded servers have non-stackable potions.
CrafterCraftEvent (1.21+)
CrafterCraftEvent (1.21+)Fired When: Crafter block (new in 1.21) crafts an item What We Track: The crafted result Action Logged: None (just ensures UUID exists)
Why 1.21+: The Crafter block was added in Minecraft 1.21. Older versions don't have this event.
PrepareSmithingEvent
PrepareSmithingEventFired When: Smithing table prepares a result What We Track: The result item Action Logged: None (just ensures UUID exists)
Edge Case: Smithing modifies existing items (e.g., upgrading diamond to netherite). We track the result.
LootGenerateEvent
LootGenerateEventFired When: Loot is generated (chests, mob drops, fishing) What We Track: All loot items Action Logged: None (just ensures UUIDs exist)
Why This Matters: Loot tables can generate non-stackable items (enchanted books, tools). We tag them before players see them.
VillagerAcquireTradeEvent
VillagerAcquireTradeEventFired When: Villager acquires a new trade What We Track: The trade result item Action Logged: None (just ensures UUID exists)
Why Clone? We need to modify the result item (add UUID), so we clone it and rebuild the trade recipe.
Acquisition Events
These events track items being acquired by players.
EntityPickupItemEvent
EntityPickupItemEventFired When: Player picks up an item from the ground What We Track: The picked-up item Action Logged: PICKUP
Why This Matters: Most dupe glitches involve dropping and picking up items. We log every pickup with player + location.
PlayerFishEvent
PlayerFishEventFired When: Player fishes and catches something What We Track: The caught item Action Logged: FISHED
Edge Case: Only fires on CAUGHT_FISH state (not bites, reels, or fails).
EntityDeathEvent
EntityDeathEventFired When: Entity (mob, player, animal) dies What We Track: All items dropped Action Logged: MOB_DROP (if killer exists)
Why Killer Check? If a player killed the mob, we associate the loot with them. Otherwise, just tag it.
BlockBreakEvent
BlockBreakEventFired When: Player breaks a block What We Track: Block drops + any items in container blocks Action Logged: BLOCK_BREAK
Why Container Check? If a player breaks a chest without opening it, we still need to tag the items inside.
PlayerShearEntityEvent
PlayerShearEntityEventFired When: Player shears a sheep, mooshroom, etc. What We Track: Player's inventory (scanned next tick) Action Logged: None (inventory scan handles it)
Why Next Tick? Sheared items aren't added to inventory immediately. We wait 1 tick for the items to appear.
Inventory Events
These events track item movement within inventories.
InventoryClickEvent
InventoryClickEventFired When: Player clicks an item in any inventory What We Track: Current item, cursor item, shift-clicked items Action Logged: INVENTORY_CLICK_CURRENT, INVENTORY_CLICK_CURSOR, INVENTORY_SHIFT_CLICK
Why Three Logs? Clicks can involve multiple items (current slot, cursor, and shift-click moves entire stacks).
InventoryDragEvent
InventoryDragEventFired When: Player drags items across multiple slots What We Track: New items placed, old cursor Action Logged: INVENTORY_DRAG, INVENTORY_OLD_CURSOR
Edge Case: Dragging can split stacks. We track all affected items.
InventoryCreativeEvent
InventoryCreativeEventFired When: Creative mode player spawns or duplicates items What We Track: Current item, cursor Action Logged: CREATIVE_INVENTORY Special: Passes "CREATIVE" tag to duplicate checker
Why Special Handling? Creative mode allows intentional duplication. The "CREATIVE" tag tells the duplicate checker to honor the allow-creative-duplicates config setting.
InventoryOpenEvent
InventoryOpenEventFired When: Player opens any inventory (chest, furnace, etc.) What We Track: All items in the opened inventory Action Logged: None (just ensures UUIDs exist)
Only runs if inventory-open-scan-enabled: true in config.
PrepareAnvilEvent
PrepareAnvilEventFired When: Anvil prepares a result What We Track: Result item, input items Action Logged: None (just ensures UUIDs exist)
Special Handling: Anvils can "consume" input items. We track the UUIDs before and after to detect if items were duplicated.
Drop & Death Events
PlayerDropItemEvent
PlayerDropItemEventFired When: Player drops an item (Q key) What We Track: The dropped item Action Logged: DROP
PlayerDeathEvent
PlayerDeathEventFired When: Player dies What We Track: Items dropped on death + items kept (if keepInventory is enabled) Action Logged: DEATH
Why Next Tick for keepInventory? Items are restored after the event fires.
PlayerRespawnEvent
PlayerRespawnEventFired When: Player respawns after death What We Track: Player's inventory (scanned next tick) Action Logged: None (inventory scan handles it)
Container Events
InventoryMoveItemEvent
InventoryMoveItemEventFired When: Hopper, dropper, or other automation moves items What We Track: The moved item Action Logged: CONTAINER_TRANSFER
BlockDispenseEvent
BlockDispenseEventFired When: Dispenser dispenses an item What We Track: The dispensed item Action Logged: None (just ensures UUID exists)
Block & Entity Events
PlayerInteractEntityEvent
PlayerInteractEntityEventFired When: Player interacts with an entity (item frame, armor stand, etc.) What We Track: Items in/on the entity Action Logged: ENTITY_INTERACT
Special handling for:
Item Frames – Track the item inside
Armor Stands – Track armor and held items
BlockPlaceEvent
BlockPlaceEventFired When: Player places a block What We Track: The item being placed Action Logged: BLOCK_PLACE
VehicleDestroyEvent
VehicleDestroyEventFired When: Minecart is destroyed What We Track: Items in storage minecarts Action Logged: None (just ensures UUIDs exist)
World Events
ChunkUnloadEvent
ChunkUnloadEventFired When: Chunk unloads What We Track: Cleans up in-memory cache for items in that chunk Action Logged: None
Why This Matters: Prevents memory leaks from items in unloaded chunks.
Player State Events
PlayerJoinEvent
PlayerJoinEventFired When: Player joins the server What We Track: Player's inventory (scanned next tick) Action Logged: None
Why This Matters: Catches any items the player had when they logged off (in case they were duped offline).
PlayerQuitEvent
PlayerQuitEventFired When: Player leaves the server What We Track: Cleans up in-memory cache for that player Action Logged: None
Periodic Scanning
In addition to events, DupeTrace runs two scheduled tasks:
Periodic Inventory Scanner
Frequency: Configured by scan-interval (default: 200 ticks = 10 seconds) What It Does: Scans all online players' inventories for duplicate UUIDs
Why We Need This: Event-driven tracking can miss edge cases (lag, creative mode exploits, plugin conflicts). Periodic scanning is the safety net.
Known Items Cleanup Task
Frequency: Every 5 minutes What It Does: Removes stale entries from the in-memory knownItems cache
Why We Need This: Prevents memory leaks. Items not seen in X minutes (config: known-items-ttl-ms) are removed from the cache.
Event Priority: MONITOR
MONITORAll DupeTrace event handlers use EventPriority.MONITOR:
What This Means:
Handlers run last (after all other plugins)
We observe events after they're finalized
We never modify event behavior
ignoreCancelled = truemeans we skip cancelled events
Why MONITOR? We're passive observers, not active modifiers. Running last ensures we see the final state of items.
Thread Safety Notes
Event handlers run on the main server thread (safe to modify Bukkit state)
Database operations run asynchronously (non-blocking)
Periodic scanners run asynchronously (non-blocking)
Important: Never modify inventories or entities from async threads! Use runTask() to schedule back to main thread if needed.
Performance Characteristics
Events per Second (Busy Server)
InventoryClickEvent
100-500/sec
EntityPickupItemEvent
50-200/sec
PlayerDropItemEvent
20-100/sec
CraftItemEvent
10-50/sec
InventoryDragEvent
5-20/sec
TOTAL
~200-1000 events/sec
Memory Impact: Each event logs to database (async) and updates in-memory cache (fast).
CPU Impact: Minimal. Most handlers just tag items and return. Database writes happen off-thread.
What's Next?
You've mastered the event system! Continue your journey:
Architecture Overview ← – How everything fits together
Core Functions ← – Deep dive into key methods
Database Schema ← – Tables and queries
Ready to contribute? Check out the GitHub repo and open a PR!
Last updated