Tags
Creators
Details
3.0.0
Compatibility
Changes
[3.0.0] - 2026-01-08
🎉 Main Features
Ban Presets System (NEW!)
- Shift + Right-Click to switch between predefined ban presets
- Actionbar feedback shows current preset when switching
- Sound feedback individually configurable per preset
- Unlimited presets definable in config.yml
- Each preset with own duration, reason, IP-ban flag and sound
- Per-player tracking of active preset
- Configurable switch cooldown (250ms default)
- Works everywhere: on players, blocks or in the air
Kick/Jail Presets System (NEW!)
- Shift + Left-Click to switch between predefined kick/jail presets
- Mix kicks and jails in one preset list (no duration = kick, with duration = jail)
- Actionbar feedback shows current preset when switching
- Sound feedback individually configurable per preset
- Unlimited presets definable in config.yml
- Per-player tracking of active kick/jail preset
- Same configurable cooldown as ban presets
- Left-Click on Player executes kick or jail with active preset
Modrinth Update Checker (NEW!)
- Automatic update checking on server startup
- Periodic checks with configurable interval (default: 6 hours)
- Admin notifications on login (permission:
banhammer.updatenotify) - Semantic version comparison
- Download URLs and changelog links
- Rate limiting (max 1 check per hour)
- Fully configurable in config.yml
Database System
- SQLite support for single-server (no setup required)
- MySQL support for multi-server networks
- Complete ban history with detailed tracking
- Automatic schema creation and migrations
- Connection pooling with HikariCP for optimal performance
- WAL mode for SQLite (better concurrency)
- Optimized indexes for fast queries
Auto-Unban System
- Automatic unbanning when temporary bans expire
- Configurable check intervals (default: 60 seconds)
- Asynchronous processing (doesn't block main thread)
- Event triggers for external plugins
- Discord notifications on auto-unbans
- Supports temp-bans, temp-mutes and jail times
Discord Integration
- Webhook-based notifications
- Color-coded embeds for different actions:
- 🔴 Bans (Red)
- 🟠 Kicks (Orange)
- 🟢 Unbans (Green)
- 🟡 Mutes (Yellow)
- Staff names, player names, duration and reason in embeds
- Server names for multi-server setups
- Configurable which events are sent
- NEW: Discord now works independently of database (no database required)
- NEW: Discord webhook reinitializes on
/bh reloadwithout server restart
Essentials Jail Integration (NEW!)
- Soft Dependency: Optional Essentials plugin integration via
softdependin plugin.yml - Reflection-Based: No hard dependency - uses reflection to access Essentials API
- Smart Preference System: Automatically uses Essentials jail if available, falls back to built-in
- Automatic Detection: Checks for Essentials on startup and enables integration
- Built-in Fallback: Own jail system continues to work if Essentials not installed
- Seamless Integration: Works with existing Essentials jail configurations
- Debug Logging: Comprehensive DEBUG TELEPORT logs for troubleshooting
✨ New Features
Extended Punishment Types
- Mute System: Blocks chat and commands
- Permanent and temporary
- Cache-based for performance
- Shows remaining time
- Jail System: Locks players in place
- Configurable jail location via
/setjail - Prevents teleportation and movement
- Optional: Prevents damage and commands
- Configurable jail location via
- Warning System: Warnings with auto-ban
- Counts warnings per player
- Auto-ban after X warnings (configurable)
- Non-active punishment (only tracking)
Ban Appeals System
- Players can submit appeals via
/appeal <text> - Cooldown system (default: 24 hours)
- Maximum appeals per punishment (default: 3)
- Staff can approve/deny appeals
- Discord notifications for new appeals
- Appeal status tracking (PENDING, APPROVED, DENIED)
Statistics & Leaderboards
- Tracking of all punishments per staff member
- Total counts: Bans, kicks, mutes, warnings
- Leaderboard system
- GUI view via
/bh stats - Exportable via REST API
GDPR & Privacy Features
- IP anonymization with 4 levels:
NONE: Full IP storagePARTIAL: Last octet removed (192.168.1.0)HASH: One-way hash with server saltFULL: Complete masking
- Automatic salt generation
- Salt validation (min. 16 characters, 3 character types)
- Configurable data retention
🔧 Improvements
Performance Optimizations
- Asynchronous database initialization (no longer blocks server start)
loadActiveMutes()loads mutes into cache on startup- Optimized warning count via
SELECT COUNT(*)instead of N+1 query - SQLite connection pool set to 1 (single-writer)
- Race condition in
getActiveMute()fixed viacomputeIfPresent() - Composite indexes for common query patterns
Error Handling & Robustness
- Separate try-catch blocks in ban flow (prevents partial state)
- Improved Discord webhook validation
- URL format check
- Clear error messages
- Graceful degradation
- Robust duration parsing
- Supports: "7d", "1h30m", "PT24H", "permanent"
- Case-insensitive
- Warnings for invalid format
- Improved IPv6 validation via
InetAddress
Thread-Safety
- Mute cache removes expired entries atomically
- Switch cooldown via ConcurrentHashMap
- Database operations fully async
- Events fired on main thread
📝 Commands
New commands:
/bh appeals- Shows pending appeals/bh approve <id> [response]- Approves appeal/bh deny <id> [response]- Denies appeal/bh stats [player]- Shows statistics/bh reload- IMPROVED: Now reinitializes Discord webhook and Database connection/appeal <text>- Submits appeal/mute <player> <duration> [reason]- Mutes player/unmute <player> [reason]- Unmutes player/jail <player> <duration> [reason]- Jails player (uses Essentials if available)/unjail <player> [reason]- Releases from jail (works without database)/setjail- Sets jail location (only needed for built-in jail)/warn <player> [reason]- Warns player
🔑 Permissions
New permissions:
banhammer.appeals- Can view appealsbanhammer.appeals.review- Can review appealsbanhammer.appeal- Can submit appeals (default: true)banhammer.mute- Can mute playersbanhammer.jail- Can jail playersbanhammer.warn- Can warn playersbanhammer.stats- Can view statisticsbanhammer.updatenotify- Receive update notifications
🎨 API
Custom Events
PlayerPunishEvent- Fired BEFORE punishment (cancellable)PlayerPunishedEvent- Fired AFTER successful punishmentPlayerUnpunishedEvent- Fired when punishment removed
PunishmentManager API
// Fully async with CompletableFuture
pm.banPlayer(staff, victim, reason, duration, ipBan)
pm.mutePlayer(staff, victim, reason, duration)
pm.jailPlayer(staff, victim, reason, duration)
pm.warnPlayer(staff, victim, reason)
pm.getHistory(playerUuid, limit)
pm.getWarningCount(playerUuid)
Database Interface
- Abstract interface for SQLite/MySQL
- All operations via CompletableFuture
- Prepared statements against SQL injection
🐛 Bug Fixes
Critical Bug Fixes
-
✅ Expired Punishment Spam (CRITICAL): Fixed infinite "Found 1 expired punishment(s), processing..." spam
- Root Cause: NULL UUID handling in
deactivatePunishment()caused NullPointerException - Fix: Added null check for staffUuid in SQLiteDatabase.java:396 and MySQLDatabase.java:406
- Impact: Prevented database corruption and console spam
- Root Cause: NULL UUID handling in
-
✅ Jail Auto-Release Not Working (CRITICAL): Players weren't automatically released after jail time expired
- Root Cause: Missing JAIL case in UnbanScheduler switch statement
- Fix: Added JAIL case to UnbanScheduler.java:110-114
- Impact: Jail time now properly expires and releases players automatically
-
✅ Unjail Required Database:
/unjailcommand didn't work without database even with Essentials- Root Cause: Database check blocked everything in handleUnjail()
- Fix: Removed database requirement, calls JailManager.releasePlayer() directly
- Impact: Unjail now works with both Essentials and built-in jail without database
-
✅ Resourcepack Not Loading: Resourcepack wasn't being sent to players
- Root Cause: Empty URL and hash in config.yml
- Fix: Added proper URL and SHA-1 hash to config.yml
- Impact: Resourcepack now loads correctly for all players
Translation & Localization Fixes
- ✅ Incomplete Translations: Many messages were hardcoded in German
- Fix: Added 30+ new message keys to messages_de.yml and messages_en.yml
- Fix: Replaced 28 hardcoded strings across PunishmentCommands.java (15), BanHammerCommand.java (11), and AppealCommand.java (2)
- Fix: Extended Messages.java with 30+ new methods
- Impact: Plugin is now fully translatable, no hardcoded strings remaining
Reload Functionality Fixes
-
✅ Discord Not Reinitializing on Reload: Discord webhook required server restart after config changes
- Fix: Added
reinitializeDiscord()method in BanHammerPlugin.java - Impact: Discord webhook now reloads with
/bh reloadcommand
- Fix: Added
-
✅ Database Not Reinitializing on Reload: Database connection required server restart after config changes
- Fix: Added
reinitializeDatabase()method in BanHammerPlugin.java - Impact: Database connection now reloads with
/bh reloadcommand
- Fix: Added
Essentials Integration Fixes
-
✅ Essentials Jail Teleport Errors: "wrong number of arguments" and "Location.getLocation()" errors
- Fix: Fixed reflection to get Location directly from getJail() method
- Fix: Get jail list from Essentials and use first available jail
- Impact: Essentials jail integration now works correctly
-
✅ Jail Messages Not Colored: Jail/unjail messages showed raw MiniMessage tags instead of colors
- Fix: Added proper jail/unjail message calls to both Essentials and built-in jail paths
- Impact: Messages now display with proper colors
Logging Improvements
-
✅ Console Spam: Too many DEBUG messages during normal operation
- Fix: Reduced routine logs to DEBUG level, only show important info (Jail, Essentials, Discord, critical problems)
- Impact: Console is now clean and readable
-
✅ Missing Debug Logs for Troubleshooting: Hard to diagnose teleport and resourcepack issues
- Fix: Added comprehensive DEBUG TELEPORT logs (EssentialsJailIntegration.java:107-133)
- Fix: Added comprehensive DEBUG RESOURCEPACK logs (ResourcePackSender.java)
- Impact: Easier troubleshooting with debug mode enabled
Previous Bug Fixes
- ✅ Temporal bans were permanent → Duration parsing fixed
- ✅ Discord webhook no error messages → Validation added
- ✅ Server start blocks on DB init → Async initialization
- ✅ Mute cache memory leak → UnbanScheduler cleans cache
- ✅ Race condition in getActiveMute() → Atomic operation
- ✅ IPv6 validation incomplete → InetAddress validation
- ✅ SQL indexes wrongly defined → Separate CREATE INDEX statements
- ✅ N+1 query for warning count → Optimized COUNT query
- ✅ Resource leaks in ban flow → Separate error handling
🔄 Breaking Changes
No breaking changes - Version 3.0.0 is fully backwards compatible with 2.x configurations.
New features are optional and must be explicitly enabled:
database:
enabled: false # Default: off
discord:
enabled: false # Default: off
presets:
# Created automatically if not present
kickJailPresets:
# Created automatically if not present
updateChecker:
enabled: true # Default: on
📦 Dependencies
Required:
- Paper API: 1.21.1-R0.1-SNAPSHOT
- Java: 21
- HikariCP: 5.1.0
- SQLite JDBC: 3.47.1.0
- MySQL Connector/J: 9.1.0
- Gson: 2.11.0
- Discord Webhooks: 0.8.4
Optional (Soft Dependencies):
- Essentials (any recent version) - For enhanced jail system integration
📋 Technical Details
Code Quality
- Modern Java 21 features (Records, Text Blocks, Switch Expressions)
- Thread-safe implementations
- Async-first approach with CompletableFuture
- Event-based architecture
- Clean code principles
Architecture
- Manager pattern for business logic
- Database abstraction layer
- Event system for extensibility
- Preset system for flexible ban types
- Cache layer for performance
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:odcCYmcd:M1pwHaAE"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:odcCYmcd:M1pwHaAE"
}

