
DonutShards
DonutShard is a feature-rich custom currency plugin. Earn Shards by PvP kills and AFK farming, then spend them in a beautiful GUI shop with multiple categories.
Автор: Duong2012G4,7 тыс.2обновлён 20 июн. 2026 г.
Описание
🍩 DonutShard
A feature-rich custom currency plugin for PaperMC servers — earn Shards by killing, AFK farming, and spending them in a configurable shop.
✨ Features
| Feature | Description |
|---|---|
| 💰 Shard Currency | Custom economy currency with leaderboard, balance commands |
| ⚔️ Kill Rewards | Earn Shards for PvP kills with cooldown and bonus multiplier |
| 🌙 AFK System | Earn Shards passively while AFK in designated warp zones |
| 🛡️ Zone Protection | WorldGuard-like protection for AFK zones (no WorldGuard required) |
| 🏪 Shard Shop | Multi-category GUI shop — buy items with Shards |
| 🌐 Multi-language | 5 built-in languages: vi en zh es fr |
| 📊 Leaderboard | /shard top with PlaceholderAPI support |
| 🔌 Developer API | Hook into Shard events from other plugins |
| 💾 Dual Storage | YAML or SQLite, with WAL-mode async writes |
📦 Requirements
| Requirement | Version |
|---|---|
| PaperMC | 1.21.x |
| Java | 21+ |
| PlaceholderAPI | 2.11+ (optional) |
⚙️ Installation
- Download
DonutShard-1.0.0.jarand drop it into your/pluginsfolder. - Start the server — config files generate automatically.
- Edit
plugins/DonutShard/config.ymlto your liking. - Run
/shard reloadin-game to apply changes without restarting.
🗂️ File Structure
plugins/DonutShard/
├── config.yml # Main config (kill reward, storage, leaderboard)
├── afk.yml # AFK zones, protection rules, earn rates
├── shop.yml # Shop categories & GUI layout
├── shops/
│ ├── Spawners.yml # Spawner items
│ ├── Ores.yml # Ore blocks
│ └── Farm.yml # Farming items
├── languages/
│ ├── en.yml
│ ├── vi.yml
│ ├── zh.yml
│ ├── es.yml
│ └── fr.yml
└── data/ # YAML storage files (if YAML mode)
├── balances.yml
├── afk_sessions.yml
├── afk_warps.yml
└── daily_earnings.yml
🔧 Configuration
config.yml — Quick Reference
language: en # Active language (en/vi/zh/es/fr)
kill-reward:
enabled: true
shards-per-kill: 10
kill-cooldown: 30 # seconds before same kill gives reward again
ignore-afk-victims: false
bonus-multiplier:
enabled: false
threshold: 1000 # if victim has >= this many Shards
multiplier: 1.5
storage:
type: SQLITE # YAML or SQLITE
auto-save-interval: 5 # minutes
leaderboard:
size: 10
afk.yml — AFK System
enabled: true
shards-per-minute: 1
daily-limit: 100 # 0 = unlimited
teleport-cooldown: 5 # seconds between /afk uses
auto-detect-afk: true # auto-detect players already in AFK world
protection:
default:
no-damage: true
no-hunger: true
no-pvp: true
no-mob-spawn: true
no-item-drop: true
restore-health-on-enter: true
clear-effects-on-enter: true
Adding a Shop Category
- Create
plugins/DonutShard/shops/MyCategory.yml:
gui-title: "<gold>⭐ My Category"
items:
- id: DIAMOND
name: "<aqua>💎 Diamond"
price: 100
slot: 10
quantity: 1
lore:
- "<gray>Shiny and expensive"
- id: SPAWNER
name: "<red>🔥 Blaze Spawner"
spawnertype: BLAZE
price: 5000
slot: 11
quantity: 1
- Add the category to
shop.ymlundercategories:
categories:
MyCategory:
icon: DIAMOND
name: "<aqua>My Category"
slot: 4
lore:
- "<gray>Buy diamonds and spawners"
- Run
/shard reload. No restart needed!
💬 Commands
| Command | Description | Permission |
|---|---|---|
/shard balance [player] | View Shard balance | donutshard.balance |
/shard give <player> <amount> | Give Shards | donutshard.give |
/shard take <player> <amount> | Take Shards | donutshard.take |
/shard set <player> <amount> | Set Shards | donutshard.set |
/shard top [page] | View leaderboard | donutshard.top |
/shard reload | Reload config | donutshard.reload |
/shard set-afk-warp <name> | Set AFK warp to a world | donutshard.admin |
/shard remove-afk-warp <name> | Remove AFK warp | donutshard.admin |
/shard list-afk-warp | List all AFK warps | donutshard.admin |
/shard language <code> | Change language | donutshard.language |
/afk [warpname] | Enter an AFK zone | donutshard.afk |
/afk-setting | Manage AFK zone settings | donutshard.afk.admin |
/shardshop | Open the Shard Shop | donutshard.shop |
Aliases: /shard → /donutshard, /ds
🛡️ Permissions
| Permission | Description | Default |
|---|---|---|
donutshard.balance | Check own balance | true |
donutshard.balance.others | Check other's balance | op |
donutshard.give | Give Shards to others | op |
donutshard.take | Take Shards from others | op |
donutshard.set | Set Shard balance | op |
donutshard.top | View leaderboard | true |
donutshard.reload | Reload plugin | op |
donutshard.admin | All admin commands | op |
donutshard.afk | Use /afk command | true |
donutshard.afk.admin | Manage AFK settings | op |
donutshard.shop | Open Shard shop | true |
donutshard.kill.bypass | This player's deaths give no Shards | false |
donutshard.zone.bypass | Bypass zone protection | op |
donutshard.language | Change language | true |
📊 PlaceholderAPI
Register these placeholders in any compatible plugin:
| Placeholder | Returns |
|---|---|
%donutshard_balance% | Player's Shard balance |
%donutshard_rank% | Player's leaderboard rank |
%donutshard_top_1% to %donutshard_top_10% | Top player name at rank N |
%donutshard_top_balance_1% | Top player balance at rank N |
🔌 Developer API
Add DonutShard as a dependency in your plugin:
// Check availability
if (DonutShardAPI.isAvailable()) {
// Get balance
long balance = DonutShardAPI.getBalance(player);
// Give Shards with a reason
DonutShardAPI.give(player, 100, "Quest reward");
// Take Shards (returns false if insufficient)
boolean success = DonutShardAPI.take(player, 50);
// Listen to Shard events
DonutShardAPI.registerHook(new ShardEventHook() {
@Override
public void onShardEarned(UUID uuid, long amount, String reason) {
// ...
}
@Override
public void onShardSpent(UUID uuid, long amount, String reason, long newBalance) {
// ...
}
});
}
🔨 Building from Source
git clone https://github.com/duong2012g/DonutShard.git
cd DonutShard
mvn clean package
# Output: target/DonutShard-1.0.0.jar
Requirements: Java 21, Maven 3.8+
📝 License
MIT — free to use, modify, and redistribute with attribution.
Made with ❤️ by duong2012g
Последние версии
Все версии →release
DonutShards 1.2.0
1.2.020 июн. 2026 г.4 тыс.Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11bukkit, paper, purpur, spigot
release
DonutShards 1.1.0
1.1.09 июн. 2026 г.410Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11bukkit, paper, purpur, spigot
Что нового
## [1.1.0] — Bug Fix & Refactor Release
### Bug Fixes
#### 🔴 Critical
- **[BUG-1] DonutShardProvider.take() race condition** — Replaced two-step
`getBalance() → take()` with `shardManager.safeTake()` (atomic CAS via
`ConcurrentHashMap.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 calls `fireSpentHooks()` internally. Removed the redundant
`plugin.getApiProvider().fireSpent()` call in `ShopHook.charge()`. External
`ShardEventHook` implementations no longer receive 2 events for 1 purchase.
#### 🟡 Medium
- **[BUG-3] DonutShardProvider.hooks not thread-safe** — Changed
`ArrayList<ShardEventHook>` to `CopyOnWriteArrayList`. Prevents
`ConcurrentModificationException` when hooks are fired from the async SQLite
writer thread while another thread calls `registerHook()` / `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 in `ShardManager`, 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
only `balances.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 of `tickAllSessions()`
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_restored` key 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 in `pom.xml` to repackage `org.sqlite` →
`dev.duong2012g.libs.sqlite`. Prevents ClassLoader conflicts with other plugins
that also shade sqlite-jdbc (e.g. LuckPerms, AdvancedBan).
- **[BUG-11] `donutshard.zone.bypass` not declared in plugin.yml** — Added the
permission to `plugin.yml` with `default: op` and included it as a child of
`donutshard.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 of
`DonutShardCommand` to `LanguageManager`. `ConfigManager` now only holds
numeric/boolean/format config values. The `prefix` field 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()` and `getRank()` now share
the same 60s TTL and are invalidated together via `invalidateLeaderboardCache()`.
- **AfkSettingCommand migrated** — All inline response messages now use
`lm().getPrefix()` consistently. The `reload` subcommand 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.bypass` and `donutshard.language`. `donutshard.admin` grants
all child permissions including zone bypass.
release
DonutShards 1.0.0
1.0.023 мая 2026 г.317Minecraft: 1.21, 1.21.1, 1.21.2, 1.21.3, 1.21.4, 1.21.5, 1.21.6, 1.21.7, 1.21.8, 1.21.9, 1.21.10, 1.21.11bukkit, paper, purpur, spigot