Content generated with AI — it may contain mistakes.

Gameplay

Rewards, loot and economy

What a player earned, what comes out of a chest, and what a balance is worth.

Rewards

A reward list is items, commands and money with odds and conditions, edited in-game and stored exactly as ExyliaCommons stored it.

PluginRewards rewards = Rewards.of(this);
 
rewards.give(player, config.winnerRewards(), context);
CallWhat it does
give(player, rewards, context)Hand over a whole list.
give(player, reward)One entry.
roll(player, rewards, context)Give what the odds picked.
pick(rewards)Pick without giving — for a preview.
giveLater(uuid, owed)Queue for somebody who is offline.
claim(player, callback)Deliver whatever they were owed.
editor(rewards)The in-game list editor.
overflow(policy)What happens when the inventory is full.
pending(store)Where owed rewards are kept.
claimOnJoin(then)Drain the queue on every join, without writing a listener.

Offline is not a lost prize

giveLater writes into a PendingRewards store and claim empties it on the player's next join. The queue is cleared before delivery rather than after, so a failure mid-delivery cannot duplicate a reward next time.

Three lines is the whole queue, table and join listener included:

rewards = Rewards.of(this)
        .overflow(OverflowPolicy.QUEUE)
        .pending(PendingRewards.database(this))
        .claimOnJoin((player, delivery) -> messages.claimed(player, delivery.given()));

PendingRewards.database(plugin) writes to exylia_pending_rewards in your own plugin's database — whatever plugins/<Plugin>/database.yml says, H2 in a file by default. ExyliaLib has no database of its own and never opens one. Two plugins pointed at the same database share the table and are told apart by the column naming them. One player is capped at 200 waiting batches per plugin, past which the write is refused and said once in the console: a queue is where a bug writes in a loop.

Keep implementing PendingRewards yourself when the rows already exist somewhere — ExyliaCapture and ExyliaEvents each keep their own table, and neither had to migrate for this.

`keep` runs on the thread that owed the reward

Writing to a database inline there is a stall on the main thread. The store above schedules the write itself; a hand-written one must call Tasks.of(plugin).runAsync(...) and return. claim is the opposite — it is already off the main thread, so read directly.

Overflow

OverflowPolicy decides what a full inventory means: drop at the player's feet, queue it, or refuse. Choosing per plugin is the point — a mob drop can fall on the floor, a crate prize should wait. QUEUE needs a pending store; without one it drops instead and says so, because asking to queue is asking not to lose it.

Loot

What comes out of a chest, a spawner or a broken block:

List<LootEntry> table = Loot.parseAll(section.getStringList("loot"));
 
List<ItemStack> filled = Loot.roll(table);      // a chest's worth
ItemStack single = Loot.pick(table);            // one draw
FieldWhat it does
ItemThe stack, with its own name, enchantments and lore.
weightHow likely this entry is relative to the others.
minAmount / maxAmountThe stack size range rolled per pick.
tierA label, so an entry can belong to a rarity band.

Weights are relative, not percentages: an entry weighted 10 beside one weighted 5 is drawn twice as often, and the numbers do not have to add up to anything.

Loot.editor(plugin, entries) is the in-game editor for one.

Economy

One economy choice for the whole server, over Vault, PlayerPoints or a currency you write:

Economy.balance(uuid);
Economy.has(uuid, amount);
 
Economy.charge(uuid, amount);                  // true when it went through
Economy.pay(uuid, amount);
 
EconomyResponse taken = Economy.withdraw(uuid, amount);
TransferResult moved  = Economy.transfer(from, to, amount);

charge and pay answer with a boolean for the common case; withdraw and deposit hand back an EconomyResponse when you need to know why it failed. Nothing throws — a charge that could not be made is a result you branch on, not an exception to catch around a menu click.

Several currencies are supported: Economy.of("points") scopes every call to one, and Economy.currencies() lists what is registered.

A server with no economy plugin gets a provider that says so, so the failure is "no economy installed" rather than a NoClassDefFoundError in the middle of a purchase.

Formats

The other half of money: what a player reads, and what a player types.

Formats.money(1234567);          // "1.23M", or whatever the owner configured
Formats.compact(1234567);        // the same shortening, without a currency
Formats.percent(0.42);
Formats.percentOf(3, 12);
Formats.date(epochMillis);
Formats.relative(epochMillis);   // "3 minutes ago"
 
Amounts.parse("10M");            // what a player typed, back to a number

Reading 10M from chat is the same parser the amount input uses, so a menu, a command and a chat prompt all accept the same shorthand.

Locale is pinned deliberately

Decimal formats use a fixed locale rather than the host's. A server in Spain would otherwise write 1,50, and that value breaks every scoreboard, comparison and config that reads it back.

Something missing on this page? Tell us on Discord