Plugin Architecture
Welcome to the technical deep-dive! If you're here, you probably want to understand how DupeTrace works under the hood, contribute code, or build something similar. Let's break it down.
Project Structure
DupeTrace/
βββ src/main/kotlin/io/github/darkstarworks/dupeTrace/
β βββ DupeTrace.kt # Main plugin class
β βββ command/
β β βββ DupeTestCommand.kt # Admin command handler
β βββ db/
β β βββ DatabaseManager.kt # Database operations & connection pool
β βββ listener/
β β βββ ActivityListener.kt # Comprehensive event tracking
β β βββ InventoryScanListener.kt # Inventory scan on open
β βββ util/
β β βββ ItemIdUtil.kt # UUID tagging utilities
β βββ webhook/
β βββ DiscordWebhook.kt # Discord integration
βββ src/main/resources/
β βββ config.yml # Default configuration
β βββ plugin.yml # Plugin metadata
βββ build.gradle.kts # Gradle build scriptTech Stack
Language
Kotlin 1.9+ (targeting JVM 21)
Server Platform
PaperMC 1.21+
Database
PostgreSQL 12+
Connection Pool
HikariCP 5.1.0
Build Tool
Gradle 8.8
Dependency Management
Gradle Kotlin DSL
Core Architecture Concepts
1. Item Tagging via PersistentDataContainer
Every non-stackable item (tools, armor, weapons) gets a unique UUID stored directly in its NBT data using Bukkit's PersistentDataContainer API.
Why PersistentDataContainer?
Survives server restarts
Survives item transfers between players
Survives chunk unloads
Not visible to players (no lore clutter)
2. Event-Driven Tracking
DupeTrace uses Bukkit's event system to monitor every possible way an item can move:
Player Events: Pickup, drop, death, respawn, join, quit
Inventory Events: Click, drag, swap, craft, enchant, anvil
Container Events: Chest open/close, hopper transfer
Block Events: Break, place, dispense
Entity Events: Item frame, armor stand
World Events: Loot generation, chunk unload
Every tracked event logs to the database with:
Item UUID
Player UUID
Action type
World coordinates
Timestamp
Check out ActivityListener.kt (src/main/kotlin/io/github/darkstarworks/dupeTrace/listener/ActivityListener.kt) to see all the event handlers in action.
3. In-Memory Duplicate Detection
DupeTrace keeps a concurrent hash map of "known item locations" in memory:
How Duplicate Detection Works:
When an item is seen, check if its UUID is already in
knownItemsCompare timestamps: if the last-seen time is within the movement grace period (
movement-grace-ms), it's probably the same item moving legitimatelyIf the grace period has passed and the item appears in a different location β DUPLICATE DETECTED
This in-memory approach is fast but requires careful tuning of the grace period to avoid false positives.
4. Asynchronous Database Operations
To avoid blocking the main Minecraft server thread (which would cause lag), all database writes happen asynchronously:
Why async?
Database queries can take 10-100ms depending on load
Running synchronously would cause server TPS drops
Async operations are non-blocking and can run in parallel
Database reads (like command queries) also run async to prevent lag.
5. Database Connection Pooling (HikariCP)
Instead of opening a new database connection for every query (expensive!), DupeTrace uses HikariCP to maintain a pool of reusable connections.
Benefits:
Reduced latency (connections are pre-established)
Handles concurrent queries efficiently
Automatic connection recycling and health checks
6. Discord Webhook Integration
The DiscordWebhook class provides fully customizable Discord notifications:
Key Features:
Customizable embeds: Color, title, fields, footer, images all configurable
Template placeholders:
{player},{item_type},{uuid_short}, etc.Mention support: Role and user pings with custom content
Rate limiting: Respects Discord's 30/minute limit with alert queuing
Thread-safe: Uses
AtomicIntegerandConcurrentLinkedQueue
Rate Limiting Flow:
Alert comes in β added to queue
Background task checks queue every second
If under rate limit β send immediately
If at limit β stay in queue until next minute
Queue overflow β oldest alerts dropped
7. Periodic Scanning & Memory Management
Two scheduled tasks run in the background:
Periodic Inventory Scanner (scan-interval)
Scans all online players' inventories for duplicate UUIDs at regular intervals.
Known Items Cleanup Task (known-items-ttl-ms)
Removes stale entries from the in-memory cache to prevent memory leaks.
Lifecycle Flow
Plugin Startup
DupeTrace.onEnable()is called by PaperLoad and validate
config.ymlInitialize
DatabaseManagerand connect to PostgreSQLCreate database schema and indexes if they don't exist
Register event listeners (
ActivityListener,InventoryScanListener)Register commands (
/dupetest)Start periodic scanner and cleanup tasks
Item Tracking Flow
Plugin Shutdown
DupeTrace.onDisable()is calledClose HikariCP connection pool
Cancel scheduled tasks (automatic via Bukkit)
Flush any pending operations
Design Patterns Used
Singleton Pattern
ItemIdUtil is an object (Kotlin's singleton) since it's stateless and used everywhere.
Repository Pattern
DatabaseManager encapsulates all database logic, keeping SQL queries isolated from business logic.
Observer Pattern
Event listeners observe Bukkit events and react accordingly.
Command Pattern
DupeTestCommand implements CommandExecutor and TabCompleter for clean command handling.
Thread Safety
Concurrent Access Points
knownItems HashMap β Uses
ConcurrentHashMapfor thread-safe reads/writesDatabase connections β HikariCP handles concurrent access
Event handlers β Bukkit calls handlers on the main thread (mostly safe)
Async tasks β Run on separate thread pool, must not modify Bukkit state directly
Important Rules
β DO:
Use
ConcurrentHashMapfor shared stateRun database queries async
Use
runTask()to schedule Bukkit API calls back to main thread
β DON'T:
Modify inventories from async threads
Access Bukkit entities from async threads
Use regular
HashMapfor concurrent access
Performance Characteristics
Memory Usage
Plugin code & dependencies
~15-20 MB
In-memory item cache (10k items)
~5-10 MB
HikariCP connection pool (10 conns)
~5 MB
Total
~25-35 MB
Memory usage scales with the number of tracked items in the cache. The known-items-ttl-ms setting controls cache size.
Database Growth
Items table
~1 row per unique item (very slow)
Transfers table
~100-1000 rows per hour (active server)
Example: A 50-player server might log 50,000 transfers per day. Plan your database storage accordingly!
Error Handling Philosophy
DupeTrace follows a fail-safe approach:
β Log errors and continue (don't crash the server)
β Validate configuration on startup
β Catch exceptions in async operations
β Use
runCatchingfor safe UUID parsingβ Don't throw exceptions up to Bukkit (it disables the plugin)
Example from DatabaseManager.kt:115:
Even if the database query fails, the plugin continues running.
What's Next?
Now that you understand the architecture, dive into:
Core Functions β β Deep dive into key methods
Database Schema β β Tables, indexes, and queries
Event System β β How every event is tracked
Questions? Open an issue on GitHub or ping us on Discord!
Last updated