Skip to content

Deliveries

Pétrus Pradella edited this page Nov 2, 2026 · 3 revisions

Deliveries

What cannot be handed over now - a full inventory, an offline recipient, money for a wallet that cannot take it - becomes a pending delivery instead of being lost. The core keeps them (package br.com.finalcraft.evernifecore.logic.delivery), in each player's deliveries section: it is the one piece of engine state the core keeps for plugins.

Most plugins never call it: the stock give-item effect already does it.

# a quest reward, read with Effects.parseList(caller, node, zone)
effects:
  - { type: give-item, item: minecraft:diamond, amount: 100 }          # what does not fit waits as a delivery
  - { type: give-item, item: minecraft:elytra, expires: 7d }           # waits at most a week
  - { type: money, amount: 500, to: pending }                          # straight to the deliveries: claimed later
  - { type: give-item, item: hytale:Rock_Stone, to: finalcolonies:warehouse }

Delivery

Raw effect nodes owed to one player, parsed again when claimed:

Member Meaning
getId() / getRecipient() the delivery and the player it is for
getOrigin() a DeliveryOrigin: getPlugin() (lower-cased), getEmitter() (the caller's origin), getReason()
getCreatedAt() / getExpiresAt() / isExpiredAt(now) when it was kept; when it stops waiting (null: until claimed or purged)
getPolicy() KEEP_SELF_CONTAINED (default) hands out the core's effects and keeps a missing plugin's ones raw; REQUIRES_ORIGIN waits whole until every plugin it names is loaded
getEffects() / isSelfContained() the nodes; whether every one is a core type

A node is never lost - not one whose plugin is not loaded, nor one whose effect failed on the claim and stays in the delivery: the first stays raw, and a claim tells the player what it is waiting for; the second (audited EFFECT_FAILED: a currency that is not registered or cannot pay, no inventory) is logged with its failure and tried again on the next claim. A node is one effect: a list whose one child failed comes back whole.

Deliveries

Deliveries.get(), any thread; every verb completes a future, and a claim applies its effects on the player's thread.

Call What it does
enqueue(recipient, origin, policy, ttl, effects) keeps effects for a player, online or not
keepFor(ctx, policy, ttl, node) what an effect does with what it could not hand over: inside a claim it stays in the claimed delivery, anywhere else it becomes a new one from the context's caller
enqueueSelfContained(ctx, recipient, effects) keeps only the core's nodes and audits each other one as DELIVERY_DISCARDED - the path of a delayed effect whose player left
listOf(recipient) every pending delivery, oldest first
claim(player, id) / claimAll(player) hands out what fits now; what still does not fit, and what failed, stays pending in the same delivery; answers a ClaimResult (getDelivered, getKept, getExpired)
listFrom(plugin) every player's deliveries from one plugin
purge(plugin, by) removes every pending delivery from one plugin, logs how many on the core's log
sweepExpired() drops the expired ones of every loaded player

Deliveries.start() (the core calls it at enable) claims on every login, sweeps the expired ones once a minute and on login, and registers the logic's DeferredEffectSink: a delayed or repeated effect whose player left becomes a delivery through enqueueSelfContained (a share of a reward is still dropped). Deliveries.closeAll() stops the claim and the sweep and puts the dropping sink back; the pending deliveries stay in the players' data. Each step posts a DeliveryEvent (ENQUEUED, DELIVERED, EXPIRED, PURGED, DISCARDED; getDelivery(), getBy()) and an AuditLog of the delivering plugin records it.

Destinations

The effect modifier to: <destination> sends what an effect gives somewhere else than the player's inventory or wallet. It is accepted by effects whose Effect.acceptsDestination() is true - give-item and money - and refused, naming them, on any other. Destinations live in DeliveryDestinations (a TypeRegistry "destination"):

Destination What it does
evernifecore:inventory the player's inventory: an InventoryDestination each platform registers (Bukkit, Hytale); also counts and takes items for the item cost
evernifecore:pending always pending - "claim it later"
a plugin's own DeliveryDestinations.register(owner, destination)

A to: that no registered destination answers keeps the whole node, to: included, as a pending delivery, handed out once the plugin is back.

A destination implements DeliveryDestination: getId(), deliverItem(ctx, item) (returns what did not fit, or null), and optionally deliverAmount(ctx, currency, amount) (the default pays the context owner's wallet). Any thread; a destination that needs the player's thread hops there itself.

Commands

A plugin mounts the player side on its own command:

@FinalCMD.Node(subcmd = "deliveries", permission = "myplugin.deliveries")
private final DeliveriesCommandNode deliveries = new DeliveriesCommandNode();

/<cmd> deliveries list shows the player's deliveries with origin, reason and age; claim <id>|all hands out what fits now (an id may be shortened to its first characters).

The admin side is the core's: /ecore deliveries list <plugin> (how many each player has from that plugin) and /ecore deliveries purge <plugin>, permission evernifecore.command.deliveries.

Testing

Platforms.withInventory(slots) registers a TestInventory as evernifecore:inventory for the life of the world (add, fill, count, take, getContents, clear per player); without it an item effect fails naming that builder. LogicWorld.deliveries() brings up an in-memory PlayerData layer with the deliveries section; LogicWorld.quit(player) / rejoin(player) take a player away and back, so a delayed effect can fire while they are gone. ECoreTestWorld.close() restores the dropping sink. On a live server the deliveries suite of In-Server Conformance proves both paths.

Clone this wiki locally