Перейти к содержимому
Mineforgian

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.

Загрузки
1K
Подписчики
2
Обновлён
20 июня 2026 г.
Лицензия
MIT

Опубликован 7 мая 2026 г.

🍩 DonutShard

A feature-rich custom currency plugin for PaperMC servers — earn Shards by killing, AFK farming, and spending them in a configurable shop.

Version API Java License


✨ 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

  1. Download DonutShard-1.0.0.jar and drop it into your /plugins folder.
  2. Start the server — config files generate automatically.
  3. Edit plugins/DonutShard/config.yml to your liking.
  4. Run /shard reload in-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

  1. 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
  1. Add the category to shop.yml under categories:
categories:
  MyCategory:
    icon: DIAMOND
    name: "<aqua>My Category"
    slot: 4
    lore:
      - "<gray>Buy diamonds and spawners"
  1. 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

Ченджлог

1.1.0Релиз1.21.9, 1.21.10, 1.21.11 · 9 июня 2026 г.

[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 hookDonutShardAPI.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.sqlitedev.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 cachegetTopBalances() 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.

Комментарии

Загружаем…