1.1.0
Compatibility
Changes
[1.1.0] ā Bug Fix & Refactor Release
Bug Fixes
š“ Critical
-
[BUG-1] DonutShardProvider.take() race condition ā Replaced two-step
getBalance() ā take()withshardManager.safeTake()(atomic CAS viaConcurrentHashMap.compute()). Players can no longer go negative or bypass balance checks when AFK ticker and shop purchase happen simultaneously. -
[BUG-2] ShopHook.charge() double-fires spent hook ā
DonutShardAPI.take()already callsfireSpentHooks()internally. Removed the redundantplugin.getApiProvider().fireSpent()call inShopHook.charge(). ExternalShardEventHookimplementations no longer receive 2 events for 1 purchase.
š” Medium
-
[BUG-3] DonutShardProvider.hooks not thread-safe ā Changed
ArrayList<ShardEventHook>toCopyOnWriteArrayList. PreventsConcurrentModificationExceptionwhen hooks are fired from the async SQLite writer thread while another thread callsregisterHook()/unregisterHook(). -
[BUG-4] autoDetectAfkPlayers() checks world only, not zone bounds ā Added
plugin.getAfkConfigManager().isInAnyZone(player.getLocation())check before creating an auto-AFK session. Previously any player in the correct world (not just inside the zone) would start earning rewards. -
[BUG-5] getRank() O(n log n) on every PAPI render ā Added
rankCache(Map<UUID, Integer>) with 60s TTL inShardManager, mirroring the existing leaderboard cache.invalidateLeaderboardCache()now resets both caches. PlaceholderAPI%donutshard_rank%no longer sorts all players on every tab-list refresh. -
[BUG-6] ShardShopGUI calls saveAll() after every purchase ā Added
Storage.savePlayer(UUID)default method. YamlStorage overrides it to write onlybalances.yml(vs. all 4 YAML files). SQLiteStorage keeps the default no-op (already async). Reduces synchronous main-thread I/O on busy shops.
š¢ Low
-
[BUG-7] AFK ticker does not invalidate leaderboard cache ā Added
shardManager.invalidateLeaderboardCache()at the end oftickAllSessions()whenever shards were distributed. Leaderboard now reflects AFK rewards within the same 60s cache window. -
[BUG-8] autoDetect calls startSession() ā unnecessary teleport ā Split auto-detect path into
beginSessionInPlace()which does NOT teleport.startSession()(called by/afk) continues to teleport. Players standing inside a zone are no longer sent to spawn on auto-detection. -
[BUG-9] Duplicate
afk_session_restoredkey in en.yml ā Removed the stale copy at line 88 (the parser was silently using the version at line 105). -
[BUG-10] sqlite-jdbc not relocated in maven-shade-plugin ā Added
<relocation>block inpom.xmlto repackageorg.sqliteādev.duong2012g.libs.sqlite. Prevents ClassLoader conflicts with other plugins that also shade sqlite-jdbc (e.g. LuckPerms, AdvancedBan). -
[BUG-11]
donutshard.zone.bypassnot declared in plugin.yml ā Added the permission toplugin.ymlwithdefault: opand included it as a child ofdonutshard.admin. LuckPerms / PermissionsEx now show it in autocomplete. -
[BUG-12] 17 dead message getters in ConfigManager ā Removed all
getMsgXxx()fields and getters following the completed migration ofDonutShardCommandtoLanguageManager.ConfigManagernow only holds numeric/boolean/format config values. Theprefixfield was also removed (LanguageManager is now the single source-of-truth for all messages).
Refactors
-
ConfigManager cleaned ā Down from ~230 lines to ~120 lines. Zero message strings remain; all messages live in
languages/<code>.yml. -
AfkManager restructured ā Code reorganised into three clearly labelled sections: ā Session lifecycle, ā” Reward ticker, ⢠Auto-detect. No external API change.
-
ShardManager ā dual cache ā
getTopBalances()andgetRank()now share the same 60s TTL and are invalidated together viainvalidateLeaderboardCache(). -
AfkSettingCommand migrated ā All inline response messages now use
lm().getPrefix()consistently. Thereloadsubcommand now reloads AFK config, shop config, and language files in addition to the main config. -
plugin.yml ā All permissions now fully declared including
donutshard.zone.bypassanddonutshard.language.donutshard.admingrants all child permissions including zone bypass.
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:Z4PN2FdP:UqCri47w"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:Z4PN2FdP:UqCri47w"
}

