Astral Realms Documentation Help

Opening & Rewards

This page covers what happens when a player interacts with a placed crate block — key validation, the spin-menu animation, quick opening, the crate preview, and the vanilla Vault block hooks. For the YAML shape of a crate (rewards, key, locations, actions) see Crate Files; for crates-enabled and other global settings see Configuration.

All of this lives in CrateListener, CrateOpenMenu, CrateService, and KeyListener.

Interacting with a crate

CrateListener#onInteract handles PlayerInteractEvent at EventPriority.LOWEST for every block that matches a configured crate location (CrateService#findByLocation, matched by world + block coordinates). Any interaction with that block — left or right click, either hand — cancels the vanilla event first, so the block never opens a real container or triggers other block behavior. From there:

Interaction

Condition

Result

Any click

Off-hand (EquipmentSlot.OFF_HAND)

Ignored entirely — only main-hand (HAND) interactions are processed.

Left-click (or any non-right-click action), main hand

Opens the crate's preview menu.

Right-click, main hand

Held item is not a valid key for this crate

KEY_NEEDED message; nothing is consumed.

Right-click, main hand, valid key

Player is sneaking

Quick opening — consumes all held keys at once.

Right-click, main hand, valid key

Player is not sneaking

Normal open: runs open-actions, consumes 1 key, opens the spin menu.

A key is valid for a crate when its PersistentDataContainer carries the astralcrates:crate key (CrateService.CRATE_KEY) with a string value equal to the crate's id (case-insensitive) — CrateService#isKeyFor.

The spin menu

The normal (non-sneaking) right-click flow, after the key is validated:

  1. The crate's open-actions run against the player (plain PaperActionList.run(player) — no reward context yet).

  2. 1 key is removed from the item in hand (the whole stack is removed if the amount is 1, otherwise the amount is decremented by 1).

  3. A CrateOpenMenu is constructed and opened — a fixed 27-slot inventory titled from the crate's open-menu-title.

The winner is predetermined the instant the menu is constructed, before any animation runs: CrateOpenMenu calls CrateService#pickRandomReward, a weighted pick (RandomCollection, weight = each reward's chance) over the crate's rewards, skipping any reward whose requirements fail for this player. The rewards actually shown in the GUI are built from that same filtered (and then shuffled) list, so what the player watches spin is cosmetic — the outcome never changes based on when they close the inventory.

The spin only occupies the middle row (inventory slots 9–17); the winning item always lands on the center slot (slot 13, the 5th of the middle row):

  1. The row shifts one item at a time, playing Sound.UI_BUTTON_CLICK (volume 0.5, pitch 1.2) on every shift.

  2. The animation runs for a random number of shifts between 20 and 35 (at least one full lap of the reward pool, extended to stay in that range).

  3. Shift delay is 1 tick for the first 70% of shifts, then decelerates — linearly ramping up to 5 ticks per shift — for the final 30%, so the spin visibly slows down before landing.

  4. Once the target shift count is reached, the row pauses 5 ticks on the final result, then Sound.ENTITY_PLAYER_LEVELUP plays and the 8 non-center slots are cleared, and after another 10-tick delay the reward is actually handed out (see Reward payout).

If the player closes the inventory before the animation finishes, the win is not lost: an InventoryCloseEvent listener registered for that specific menu instance calls the same reward-granting logic immediately, guarded by a rewardGiven flag so the reward is only ever paid out once (whichever of the animation-completion path or the early-close path fires first). Clicking inside the spin menu (InventoryClickEvent) is always cancelled — items can't be dragged out mid-spin.

Reward payout

When a winning CrateReward is resolved (from the spin menu or quick opening):

  1. The reward's own actions run against the player.

  2. Only for the spin-menu path, the crate's win-actions (if configured) then run against the player with the winning reward registered on the placeholder container under namespace reward — the reward exposes %reward_chance% and %reward_item% (the reward's item, chainable with the standard item_* sub-keys) to those actions. open-actions are not re-run at this point — they already ran before the menu opened.

  3. The player's inventory closes.

Failures in either action list are caught and logged per-list; a broken win-actions entry does not prevent the reward's own actions from having already run.

No eligible reward

If every reward's requirements filter out the current player (or the crate has no rewards at all), pickRandomReward returns no winner. The intended fallback — reachable from the early-close path — sends NO_REWARDS and refunds one key item (freshly built from the crate's key template) to the player's inventory instead of granting a reward.

Quick opening (sneak + right-click)

Sneaking while right-clicking with a valid key skips the spin animation entirely:

  1. The entire held stack is consumed at once — keyCount is the full getAmount() of the item in hand, and the main-hand slot is cleared in one call, regardless of how many keys were held.

  2. CrateService#pickMultipleRewards draws keyCount independent weighted picks (pickRandomReward called once per key).

  3. For each non-null pick: the reward's own actions run, then the crate's open-actions run again (once per reward won, not once for the whole batch). win-actions are not run in this path.

  4. If a given draw comes back with no eligible reward, that key is simply consumed with no reward, no actions, and no refund — quick-opening never sends NO_REWARDS or gives a key back.

There is no animation, no CrateOpenMenu, and no sounds played for quick opening — rewards are granted instantly.

Crate preview (left-click)

Left-clicking (or any non-right-click interaction with) a crate block calls CrateService#openCratePreview, which opens the crate's configured menu.id — a standard AstralCore menu blueprint from plugins/AstralCrates/menus/, computed with the crate bound to the crate parameter. If the crate has no menu configured, nothing happens. This does not consume a key and does not touch rewards; it's purely a look-but-don't-open GUI, defined the same way as any other AstralCore menu (see Menu Items).

Wrong or missing key

Message key

Sent when

Placeholders

KEY_NEEDED

Right-click with an item that isn't a valid key for this crate (including empty hand).

%item_name% — the crate's own key item, resolved via ItemStackPlaceholder (namespace item).

NO_REWARDS

See No eligible reward.

None.

# messages.yml key-needed: "<warning>Vous devez avoir une <#26d971>%item_name% <#ffd60a>pour ouvrir cette caisse !" no-rewards: "<error>Il n'y a pas récompense défini. Veuillez réessayer plus tard."

Vault block integration

If a crate's location block is a vanilla Vault (org.bukkit.block.data.type.Vault), CrateListener also hooks two Paper events on it:

  • VaultDisplayItemEvent — whenever the vault needs an item to display floating above it, the listener picks one of the crate's reward items at random (ThreadLocalRandom, uniform over all configured rewards) and sets it as the display item. This pick ignores each reward's requirements — the display can show a reward the interacting player isn't currently eligible for.

  • VaultChangeStateEvent — cancelled whenever the vault's new state is Vault.State.EJECTING. This stops the vanilla vault from ejecting its own loot item; the crate always hands out rewards through the flows above instead.

Both handlers only apply to blocks already registered as a crate location — a bare, unconfigured Vault block is unaffected.

Keys cannot be placed

KeyListener#onPlace cancels BlockPlaceEvent whenever the item being placed is a crate key (CrateService#isKey — any item whose PDC carries the astralcrates:crate key, for any crate, not just a specific one). This listener is always registered on enable, independent of crates-enabled.

Enabling crate interactions

CrateListener (opening, preview, and the Vault hooks above) is only registered when crates-enabled: true in config.yml. If it's false, crate blocks behave like any other block — no cancellation, no key check, no menu — though key placement is still blocked by KeyListener regardless of this setting. See Configuration.

# config.yml crates-enabled: true
Last modified: 25 July 2026