Claims for mod authors

Rewards a player collects on purpose, from CommonLib 2.4.0

CommonLib (ZiggfreedCommon) 2.4.0 adds Claims. A Claim is one payout a player has earned and not taken yet. Rewards are collected, never handed over unasked: whatever pays a player writes a Claim, and the player presses Claim when their bags have room. Every boss payout the library makes is a Claim, and so is a dungeon's completion reward. This page is for a mod author paying players through Claims.

The classes live in the one CommonLib jar: Claim, ClaimStore and ClaimAddedEvent in com.ziggfreed.common.loot.claim, DiminishSpec and DiminishLedger in com.ziggfreed.common.loot.diminish, the Claims tab with ClaimGrants in com.ziggfreed.common.instance.claims, and the boss payout in com.ziggfreed.common.encounter.payout. The worked example is Seasons of Orbis' Headless Horseman, who pays two Claims when he falls.

What a player sees: the Claims tab

  • The Claims tab sits after Reputation in the shared menu, and shows only while a Claim waits.
  • When a Claim arrives, a notice reads "A reward is waiting for you. Find it under Claims in your menu." Once a session, at login, a notice counts what waits: "3 rewards are waiting for you under Claims in your menu." A player offline when a Claim arrives hears of it at their next login. Both work on a server running CommonLib alone.
  • The tab lists Claims newest first, each with its title and its rewards. Claim takes the one picked, and Claim All takes everything. An item is handed over only when all of it fits in the player's bags; what does not fit stays on the tab. A Claim never expires.
  • The notice after a press lists every reward it handed over at the amount that landed: a reputation gain reads after the player's gear and the reputation's Cap. When a press pays several Claims, each one gets a section of its own. A boss's chest says the same when its rewards are taken there, and from 2.5.1 so does a dungeon's results screen when its Claim is pressed.

Content opens the tab with "Open": "Claims", from a conversation line, a menu tile or a character. From Java, on the player's world thread, ClaimsDestinations.open(store, ref, player, claimId) opens the page with that Claim picked (null for the newest) and answers false when nothing opened.

A boss's Claim: the ZigClaim reward

Content pack assetServer/Rewards/<Id>.json

A native Hytale reward file whose "Type" is "ZigClaim". An encounter script's native GrantReward names it, and the engine checks the id when it loads the script.

A boss pays through the game's own reward files. The fight's script grants the reward after the boss falls, and each player it pays finds one Claim on their tab:

In the encounter script, once the boss has fallen
{ "Type": "GrantReward", "Reward": "Hallows_Eve_Horseman_Daily" }
FieldTypeDefaultDescription
Lootablestring (loot table id)pays nothingThe loot table the Claim rolls, by its id under Server/ZiggfreedCommon/Lootables/, rolled once for each player it pays. A reward with none decodes, pays nothing and logs an INFO line.
Diminish{Steps, Window}first win each dayHow much a repeat win pays in one window, counted per player for this reward on this boss and difficulty. Left out, the first win each day pays in full and the rest of that day pay nothing (Steps [1, 0], Window Daily). See Diminishing returns.
ShareScaledbooltrueTrue keeps each roll a player wins with a chance equal to their share of the fight: the top contributor keeps everything, and a player at 0.3 has a 30 percent chance of keeping each roll. False pays every credited player the whole roll.
TitleKeystring (lang key)the fight's nameThe key the Claim's row is titled with, as written in your own lang file. Left out, the fight's NameKey from its binding row titles it, or the tab's own word ("Rewards") outside a fight.
* required

The Horseman's script grants two of these as his fight ends:

Server/Rewards/Hallows_Eve/Hallows_Eve_Horseman_Daily.json (Seasons of Orbis)
{
  "Type": "ZigClaim",
  "Lootable": "Hallows_Eve_Horseman_Daily",
  "ShareScaled": true
}
Server/Rewards/Hallows_Eve/Hallows_Eve_Horseman_Chase.json (Seasons of Orbis)
{
  "Type": "ZigClaim",
  "Lootable": "Hallows_Eve_Horseman_Chase",
  "Diminish": { "Steps": [1, 0], "Window": "Daily" },
  "ShareScaled": false,
  "TitleKey": "claim.hallows_eve_horseman_chase"
}

His daily Claim is scaled by each player's share and writes no Diminish, so the first win each day pays and later wins that day pay nothing; its row reads "The Headless Horseman", the fight's own name. His Boss Kill Claim, the second file, rolls the same odds for everyone who fought him and pays the first win each day only; its row reads "Headless Horseman: Boss Kill", from its own TitleKey. Players knew it as the Horseman's Hoard before Seasons of Orbis 1.2.3: its files keep their ids, so a Hoard still waiting on the tab opens under the new name.

Who a ZigClaim pays

  • A credited member of a defeated run, from the shares its defeat settled. A member below the fight's minimum share is credited with the attempt and paid nothing.
  • Nobody in a run that was wiped, leashed or never fought. A GrantReward placed after a leash, a zc:reset or a timeout in the same action list pays nothing, so grant before the run resets, as the Horseman's script does.
  • A player in no run at all, such as one a world event grants to, is paid at a full share.
  • The Claim's id is boss:<script>:<run>:<reward>:<player>, so a second grant of the same reward in one run writes nothing. Its repeat count is kept under boss:<script>:<reward>:<difficulty>, shared by every run of that boss on that difficulty.
  • A player standing in the same world hears the loot cues of what their Claim kept.

Known limit: the live roster only

The game's GrantReward pays only the players the encounter holds at that moment, so a credited player who is offline or out of range when it runs gets nothing from it. A binding row's Loot.OnDefeat does pay them (below).

A binding row's Loot.OnDefeat, and the retired ZigGrant

  • A binding row's Loot.OnDefeat pays the same way: one Claim per credited participant, share-scaled, the first win each day, titled with the row's NameKey. A credited participant who is offline at the payout still gets one where the fight's participation rule keeps their share (CreditDisconnected, true in the library's default rule).
  • A row's Loot.ToKiller and Loot.QueueIfOffline are read and ignored, with one warning per row: the killer is paid at the share they earned, and a Claim already waits for a player who is offline. Leave them out.
  • The ZigGrant script action still pays, through the same Claim path, with one warning per script naming a ZigClaim reward through GrantReward as the way to move.
  • The defeat notice of every paid player lists one row: "Your reward is waiting under Claims in your menu."

From Java, both payouts write Claims:

java
// A binding row's Loot.OnDefeat, as one Claim per credited participant (signature unchanged)
EncounterLoot.grantDefeat(store, run, encounterId, row, spec, shares);

// Any loot reference, under your own reward name; row may be null and titles the Claim
EncounterLoot.grantShares(store, run, encounterId, row, lootRef, spec, shares, "MyReward", "my payout");

Both answer each paid player's receipt, keyed by player id: one row for a player whose roll wrote them a Claim, none for a player whose roll landed nothing.

Reward chests

A won boss fight leaves a chest where the boss fell. Each player the fight credited finds their own share of its Claims in it: what is not an item first, on the chest's page, then the items in the game's own chest window. Whatever they leave waits under Claims. From CommonLib 2.5.0 the chest is a file of its own, so a pack can change how it looks and what it says, and the same kind of chest can be left by a rare kill, a rare find in a broken block, or an admin's command. What its cards show is under What a chest's cards show.

Content pack assetServer/ZiggfreedCommon/RewardChests/<Id>.jsonRewardChests/

The filename is the chest's id. Every group is optional, every leaf inherits through Parent, and a leaf left out reads the library's boss chest. The owner layer is mods/ziggfreedcommon/reward-chests.json, by chest id and leaf by leaf; a new id there is a chest of the owner's own.

defaults<pack<owner
FieldTypeDefaultDescription
Look{Prop, HintKey}the dungeon chestProp is the block item the chest looks like (any chest block; an id that is no block item falls back to Furniture_Dungeon_Chest_Epic, with a warning), and HintKey the prompt a player looking at it reads, where the game fills {key} with the button to press.
Open{Credited}Credited trueWho may open it: with Credited true, the players its source credited (a boss fight's credited players, the player a kill or a break credited), each finding only their own share. False lets nobody open it, so no chest goes up.
Contents{Lootable, Diminish, TitleKey}only what its source paysWhat the chest holds of its own: Lootable is rolled once for each player who may open it, as it goes up, and what it lands is one Claim, found in the chest and under Claims, titled with TitleKey. Diminish counts a repeat roll per player for this chest (see Diminishing returns); left out, every roll pays in full. A roll that lands nothing writes nothing, and a chest nothing pays into never goes up.
View{ItemsWindow}ItemsWindow trueTrue shows what is not an item (a currency, experience, reputation) on the chest's page first, then the items in the game's chest window. False shows everything on the Claims page, filtered to this chest.
Banner{TitleKey, SubtitleKey, Major}no bannerThe banner each player with a share sees once as the chest goes up. In TitleKey, {0} is what left the chest (the boss, the creature, the block). Major true shows the game's larger banner. A dungeon clear whose reward joins a boss's chest shows its own banner instead.
Text{LeadKey, NoticeKey}the library's linesLeadKey is the line on the chest's page; NoticeKey the notice when a reward lands in it, saying where it waits. Ship <NoticeKey>.named beside it, {0} the reward's own title, for a reward that has one.
Despawn{Lifetime, WhenEmptied}300 seconds, WhenEmptied trueLifetime is how many seconds it stands at most (zero or less reads as 300). With WhenEmptied true it goes once every player it serves has taken everything, from the chest or from Claims; false keeps it up for its whole lifetime. Whatever is left when it goes still waits under Claims.
* required

The library ships two:

  • Zc_Spoils_Chest, the boss chest every won fight leaves unless its binding row names another. It writes out exactly what the defaults read, so a won fight leaves the same chest it always did, with no banner of its own.
  • Zc_Reward_Chest, the neutral chest a kill or a break leaves when its reward names none. It holds nothing of its own, so a chest built on it as its Parent names a Contents.Lootable. Its banner reads "{0} left a chest" over "Open it to take your share. Whatever you leave waits under Claims."
Server/ZiggfreedCommon/RewardChests/MyPack_Trork_Chest.json (MyPack ids are placeholders)
{
  "Parent": "Zc_Reward_Chest",
  "Contents": {
    "Lootable": "MyPack_Trork_Hoard",
    "Diminish": { "Steps": [1, 0.5], "Window": "Daily" },
    "TitleKey": "mypack.chest.trork.title"
  }
}

What a chest's cards show

  • From CommonLib 2.5.1 every card on a chest's page names where its reward came from, in one word over the picture: Boss Kill, Dungeon Clear, Bonus, Quest, Event or Gift. The word comes from the Claim's source, the part before its first colon (boss, dungeon, chest, quest-drop, reward and admin). A chest's own Contents roll reads as the boss's when the chest stands for a boss fight, and as a Bonus anywhere else. Claims saved before 2.5.1 get their words too, since every Claim already carries its source.
  • A reward that is not an item (a currency, experience, reputation) sits on a gold card, five to a row, its name in smaller type on up to three lines so a reputation's name fits whole. Item cards keep their size, four to a row. Both kinds show their rarity the same way, the rarity's word in its colour and its slot behind the picture, Common included; a reward with no chance shows none.
  • The same reward from two places is two cards: a currency paid by the boss and by the dungeon clear gets a card for each.
  • Opening the chest plays a one-time flash behind it and one sound, picked by the rarest reward inside: the feedback moment Reward_Chest_Opened, whose shipped file a pack's same-named file or the owner layer replaces (see Feedback moments). Epic and Legendary cards shimmer. It all plays on the player's screen, so an open costs the server one page build and one sound.

A mod whose own code writes Claims under a source site of its own names that site's word once at setup, with ClaimKinds.name(site, wordKey) (com.ziggfreed.common.loot.claim); a site nobody named shows no word. Never reuse a library site's spelling, and never change a site's spelling once Claims carry it.

A boss fight's chest: Spoils

A binding row's Spoils group says whether its fight leaves a chest and which one:

FieldTypeDefaultDescription
EnabledbooltrueFalse and a won fight leaves no chest: its Claims wait under Claims alone.
Cheststring (reward chest id)Zc_Spoils_ChestThe reward chest file the fight's chest is. An id no loaded file defines leaves the library's boss chest instead, and the encounter audit warns ENCOUNTER_UNKNOWN_REWARD_CHEST (expected when the pack shipping that chest is not installed).
Propstring (item id)the chest file'sWhen written, wins over the chest file's Look.Prop.
Lifetimeint (seconds)the chest file'sWhen written, wins over the chest file's Despawn.Lifetime.
* required

A dungeon whose Rewards.InChest is true puts each credited member's clear Claim in the chest its boss leaves, under a banner naming the clear, instead of showing the results screen (see Authoring Dungeons).

A chest from a kill or a break: the Reward_Chest reward

{ "Kind": "Reward_Chest", "Params": { "Chest": "<id>" } } stands a reward chest where the creature died or the block broke, for the player the kill or the break credited alone, holding what that chest file's Contents rolls for them. Chest defaults to Zc_Reward_Chest. It works in a roll of the library's own bonus rows on a KillMob or a BreakBlock moment (files under Server/ZiggfreedCommon/BonusRows/, which mods/ziggfreedcommon/bonus-rows.json retunes); one moment stands at most one chest, the first its rolls name. Anywhere else (a quest, a boss's loot, another mod's bonus table such as MMO Skill Tree's bonus drops) it pays nothing, and the audit reports it as PASS_ONLY_REWARD_KIND.

Server/ZiggfreedCommon/BonusRows/MyPack_Trork_Chest_Find.json (a rare Trork chest, placeholder ids)
{
  "When": { "Kind": "KillMob", "Match": "*Trork*" },
  "Chance": { "Base": 2.0 },
  "Loot": {
    "Rolls": [
      { "Grants": { "Rewards": [ { "Kind": "Reward_Chest", "Params": { "Chest": "MyPack_Trork_Chest" } } ] } }
    ]
  }
}

A bonus row's Chance is a percent, so this one stands a chest at two kills in a hundred.

Trying a chest: /zigclaims chest

/zigclaims chest --lootable=Hallows_Eve_Graveyard_Clear   # the neutral chest by you, holding that table's roll
/zigclaims chest --chest=MyPack_Trork_Chest                # a chest file of your own, rolling its own Contents
/zigclaims chest --player=Steve --chest=MyPack_Trork_Chest # the same, by a player who is online
  • It stands the chest two blocks east of the player, credited to them alone, the way a rare kill would, so a chest can be seen and tested on any server. The player must be online, and leaving --player out means you.
  • --lootable rolls that table in place of the file's Contents.Lootable, and is needed when the file names none, as Zc_Reward_Chest does. The roll counts against the file's Contents.Diminish, and one that lands nothing raises no chest.
  • The permission node is ziggfreed.ziggfreedcommon.command.zigclaims.chest, beside the command's own, and the verb needs both.

Writing a Claim: ClaimStore

Anything your mod pays a player can be a Claim. Build a Claim and pay it through ClaimStore.shared().award: it is the one way a Claim is written, counted against its diminishing returns and announced.

FieldTypeDefaultDescription
id*String-What makes this payout this one. Compose it from everything that makes it unique: the source, the run, the reward, the player. Matched without regard to case.
source*String-Where it came from, for logs and an owner's tools, by convention <site>:<id>. The site picks the word a chest's card shows (see What a chest's cards show).
titleKeyStringnullThe row's title, a key as written in your own lang file. Null reads as the tab's own word, "Rewards".
titleArgsList<String>emptyThe title's {0}, {1}, ... as raw text. A name that needs translating belongs in the title's own key.
difficultyStringnullThe difficulty it was earned on, a lang key or a plain label.
rewards*List<InstanceReward>-What Claim hands over: items, and command rewards run as the server console with {player} filled at claim time.
createdAt*long-When it was written, in epoch milliseconds.
* required
java
long now = System.currentTimeMillis();
Claim claim = new Claim(
        "mymod:chest:" + chestId + ":" + player,          // what makes this payout unique
        "mymod:chest",                                   // where it came from
        "mymod.claim.chest",                             // the row's title, a key in your lang file
        List.of(),                                       // the title's {0}, {1}, ...
        null,                                            // no difficulty
        List.of(InstanceReward.item("Ingredient_Bar_Copper", 5, null)),
        now);
boolean written = ClaimStore.shared().award(player, claim, null, "mymod:chest", now);

award(player, claim, spec, diminishKey, nowMs) answers true when it wrote the Claim:

  • A Claim with no rewards, or one whose id the player still holds, answers false and counts nothing, so a roll that lands nothing never spends a player's first payout of the window.
  • Otherwise it counts one payout of diminishKey under spec (nothing is counted with a null spec), scales each reward's quantity by that payout's multiplier, drops a reward scaled to nothing, and writes the Claim unless nothing is left.
  • A written Claim is saved at once and fires ClaimAddedEvent.
  • The id guards only while the Claim waits. Once the player takes it, the same id can be written again, so a payout that must happen once keeps its own record.
  • It needs only the player's id, so it pays a player who is offline. It is safe from any thread.

The store also reads: list(player) (oldest first), has(player) and count(player). take(player, id) and takeAll(player) remove what they answer, and putBack(player, claim) returns what could not be handed over, at its place by age, with nothing counted or announced. Take a Claim only to hand it over; ClaimGrants does all three for you.

To pay a loot table, decide it once with LootEngine.select and turn the decision into rewards with DeferredRewards.fromSelection, handing it a subject named {player}, then InstanceReward.merge: that is how /zigclaims give and a dungeon's completion Claim are built, so what the player sees is what they are paid. A table's items and drop lists become item rewards, and its commands and reward kinds become command rewards; a reward kind with no line it can replay later is dropped and logged, never promised. A reward kind's row still pays through its kind at the press (see ClaimGrants). To hand rewards over yourself, InstanceRewardGranter.grantAll takes a sourceId (conventionally the Claim's source) for the kind that pays a row, grantAllLanded tells you each row with what it landed as, and LootRewardKinds.isItemKind is public.

Hearing a new Claim: ClaimAddedEvent

java
getEventRegistry().registerGlobal(ClaimAddedEvent.class, event -> {
    UUID player = event.player();
    Claim claim = event.claim();   // as written, its rewards already diminished
    // hop to the player's world before touching them
});
  • It fires once per Claim written: never for a second write of an id the player holds, a payout diminished to nothing, or a Claim put back.
  • The player may be offline.
  • It is dispatched on the thread that awarded the Claim, inside the store's lock, so a listener that needs the player's world hops there itself and never blocks.
  • The library already raises the notice that names the tab; a listener never needs to.

Handing a Claim over: ClaimGrants

The Claims tab and a dungeon's results screen hand Claims over through ClaimGrants. A screen of your own uses it the same way, on the player's world thread:

java
ClaimGrants.Result one = ClaimGrants.claim(ClaimStore.shared(), player, claimId, ref, store, ZcRewardSink.INSTANCE);
ClaimGrants.Result all = ClaimGrants.claimAll(ClaimStore.shared(), player, ref, store, ZcRewardSink.INSTANCE);
if (one.anyKept()) {
    // a full bag kept something back; it waits on the Claims tab
}
for (ClaimGrants.Collected paid : all.collected()) {
    // paid.claim() and paid.landed(): what each reward of that Claim landed as
}
  • It takes the Claim out of the store and hands its rewards over through the bag guard: an item goes only when all of it fits. What did not fit is put back, so it waits on the tab. If the hand-over throws, the whole Claim is put back.
  • claimAll takes every Claim the player has, oldest first, and hands each over in turn.
  • The answer is Result(taken, emptied, granted, kept, failed, collected): nothing() is true when there was nothing to take (a stale page, a second press), and anyKept() when a full bag kept something back. collected() lists, Claim by Claim in the order taken, a ClaimGrants.Collected(claim, landed) for each Claim that handed something over, each InstanceRewardGranter.Landed(row, receipt) carrying the receipt of the reward kind that paid the row (a reputation's gain after worn gear and the Cap), or none for a row that paid by itself at its own quantity. A screen of your own names what landed from it, as the Claims page's toast does; RewardToastLines.row(RewardChip) is public for that. From 2.5.1, ClaimsView.pressToast(ClaimGrants.Result, ClaimsText, Message) builds the Claims tab's own press toast, the last argument the line for a press a full bag kept something back from, and a dungeon's results screen shows the same toast when its Claim is pressed.
  • A reward kind's row in a Claim keeps the reward it came from, so at the press it pays through that kind, at the row's scaled quantity, and does everything the kind does: a Reputation reward counts worn gear and the Cap, and moves the bar and the rank notice. Its command line runs instead only when the kind cannot pay it (no player to pay, a kind that fails, an item kind, or a line rewritten after the reward was deferred).
  • The last argument is the sink that pays everything but items. The library's own, ZcRewardSink.INSTANCE, runs a command reward as the server console with {player} filled from the claiming player. It keeps no currency, so a currency reward written straight into a Claim fails there: a reward the sink cannot pay is logged and dropped, not put back. A mod with a wallet of its own passes its own InstanceRewardGranter.Sink (grantCurrency, runCommand).

The Claims tab uses the library's sink

The tab hands every Claim over through ZcRewardSink. A Claim built from a loot table, as above, carries items and command rewards, which that sink pays.

Players who are offline

  • award needs only the player's id, so a Claim can be written for a player who is offline. It waits, and the login notice tells them.
  • A reward chest is for players who are there: /zigclaims chest needs its player online (see Trying a chest).
  • A boss's Loot.OnDefeat pays a credited participant who is offline; GrantReward of a ZigClaim does not (see above).
  • /zigclaims give --player=<name or id> --lootable=<id> rolls a loot table and puts what it lands in a player's Claims, online or not. Leave --player out for yourself.
/zigclaims give --player=Steve --lootable=Hallows_Eve_Graveyard_Clear
  • Each give is a Claim of its own, titled "Loot from <table>", with the source admin:<table>, whose word on a reward card is Gift. A roll that lands nothing writes no Claim.
  • The table rolls as a won round with no score, so a table gated on a win pays its floor.
  • --player tries an online player with exactly that name first, then a player id, then the game's profile service (which needs an authenticated server), and an online player whose name starts with it last, so a full name typed for a player who is offline never lands on someone online whose name merely starts with it.
  • The permission nodes are ziggfreed.ziggfreedcommon.command.zigclaims for the command and ziggfreed.ziggfreedcommon.command.zigclaims.give for the verb, and give needs both (chest has its own, .zigclaims.chest). Nobody holds them until you grant them, and the console holds everything.

Diminishing returns: DiminishSpec and DiminishLedger

A repeatable payout can pay less the more often it is earned in one window. In JSON it is a Diminish group, on a ZigClaim reward, a dungeon's Rewards or a reward chest's Contents:

json
"Diminish": { "Steps": [1, 0.5, 0], "Window": "Daily" }
FieldTypeDefaultDescription
Stepsnumber[]every payout in fullThe multiplier each payout in the window pays at, in order; every payout past the end pays the last one again. A negative step reads as 0.
Window"Daily" | "Weekly"DailyWhen the count starts over: Daily at midnight UTC, Weekly at midnight UTC each Monday. Any other word fails the read.
* required
  • [1, 0] pays the first in full and nothing after it. [1, 0.5] pays half for every one after the first. [1, 0.5, 0] pays full, then half, then nothing.
  • A scaled quantity is paid whole by chance: the whole part always, and the fraction as the chance of one more, so 5 at half pays 2 or 3. A reward scaled to nothing leaves the Claim, and a Claim left with nothing is not written.
  • DiminishLedger counts per player and key, only through ClaimStore.award. A count from an earlier window, or kept under the other window, starts over.
PayoutIts keyWith no Diminish
A ZigClaim, or a binding row's Loot.OnDefeatboss:<script>:<reward>:<difficulty>The first win each day pays, later ones nothing
A dungeon's completion Claimdungeon:<id>:<difficulty>Every clear pays in full
/zigclaims givenoneEvery give pays in full
A reward chest's own Contentschest:<chest id>Every roll pays in full
Your own awardThe diminishKey you passA null spec pays in full

From Java, DiminishSpec.of(DiminishSpec.Window.WEEKLY, 1.0, 0.5) builds one, and DiminishSpec.CODEC reads the same group in an asset of your own. window(), steps() and multiplier(priorClears) read it; Window.byWord and periodIndex are the window's own reads.

The claims files

The library keeps two runtime stores in its data folder, mods/Ziggfreed_ZiggfreedCommon/: claims.json, every Claim waiting, and claims-diminish.json, the repeat counts, pruned to the current windows. They are not override files. Back them up with the world, and never edit them while the server runs.

  • Each is one versioned file, written whole on every change. A missing or empty file is a fresh start.
  • A damaged file, or one that declares a newer version than this build reads, is left unread and unwritten, with one warning in the log, so nothing on disk is lost. What it holds does not show until it is fixed, and while it is refused, that session's new Claims or counts live in memory only and are gone at the next restart. Stop the server, repair the file or restore a backup, and start it again.
  • A write that fails keeps the Claims in memory and logs a warning.

Moving from 2.3.x

Nearly everything built against 2.3.x links unchanged. A mod that handed the library a queue for rewards owed to players who are offline, or paid a boss's shares through the library itself, rebuilds against 2.4.0:

  • EncounterRewardQueue is gone, with EncounterSeams.fillRewardQueue, isRewardQueueFilled and rewardQueue. There is no replacement: every boss payout is a Claim, which waits for a player who is offline. Drop the code that filled it.
  • The nine-argument EncounterLoot.grantShares (with toKiller and queueIfOffline) is gone. Use the form above, which takes the binding row and a reward name.
  • EncounterLoot.grantDefeat keeps its signature and writes Claims. ZigGrant keeps its shape. Loot.toKiller() and queueIfOffline() still read their leaves, which no longer change a payout.
  • PendingRewardStore stays the retry queue for a reward that could not be delivered. A Claim is a payout the player collects on purpose.