Requirements & Missions
A rank's requirements map is the set of individual missions a player must clear before that rank becomes unlockable. Each entry deserialises to a WrappedRankRequirement, is grouped under a category (defined in categories.yml), and is advanced one click at a time through the update-rank-requirement action from the shipped mission menus. See Ranks & Progression for the surrounding rank file shape and the unlock flow, and AstralRanks Overview for how this fits into the bigger picture.
Requirement entries
Each entry in a rank's requirements: map deserialises via WrappedRankRequirementTypeSerializer into a WrappedRankRequirement:
Field | YAML key | Type | Description |
|---|---|---|---|
| (map key) | String | Not a YAML field — taken from the entry's key in the |
|
|
| The item shown for this requirement in the |
|
|
| The typed requirement node — see Requirement types. |
|
| String | Id of a |
WrappedRankRequirement is itself a ComplexPlaceholder (namespace requirement) — see Placeholders for the full %requirement_*% chain.
Requirement types
The requirement.type field selects the concrete RankRequirement implementation via RankRequirementTypeSerializer. An unrecognised type fails deserialisation for the whole rank file.
| Class | YAML keys | Amount unit |
|
|---|---|---|---|---|
|
|
| Currency units (int) |
|
|
|
| Summed job levels |
|
|
|
| Total XP points |
|
|
|
| Item count |
|
economy
CurrencyRankRequirement#take looks up the Core EconomyService, reads the player's cached balance for currency, and withdraws min(available, amount - currentValue) (truncated to an int — fractional balances beyond the whole unit are not counted). If the withdraw call fails, no progress is recorded.
job
Backed by AstralJobs' JobsAPI.getTotalLevels(player) — the sum of the player's levels across every job, not one specific job. See shouldCountUntilFull semantics below for why this type behaves differently from the other three.
experience
Backed by Core's ExperienceUtils — getTotalExperience converts the player's level + XP bar progress into a single cumulative XP-point count (vanilla formula); take withdraws the needed amount via setTotalExperience(player, -toTake), which recomputes and re-applies the player's level and XP bar.
item
ItemRankRequirement#take scans player.getInventory().getContents() (hotbar + main storage only — armor and off-hand slots are not scanned), removing matching stacks slot by slot until amount - currentValue items have been taken. Matching is by Material only — enchantments, custom NBT, and display name are ignored, so any stack of that material counts (unlike AstralCollections' entry matching, see Blueprint YAML Reference).
shouldCountUntilFull semantics
RankRequirement#shouldCountUntilFull() controls whether RankService#updateRequirement persists partial progress after a take() call:
economy,experience,item(true) — every successfultake()adds the taken amount to the stored progress (current + amount), even if that doesn't reach the required total. Progress accumulates across multiple menu clicks — a player can deposit 3 of 8 logs now and 5 more later.job(false) — progress is only written toPlayerRankDatawhen the taken amount already meets or exceeds the required total in a single call; otherwise nothing is persisted and the stored progress stays at its previous value (typically0). In practice this means thejobrequirement is not incremental: each click recomputesJobsAPI.getTotalLevels(player)live and either the player's current total levels already cover the requirement (it completes immediately) or they don't (the progress message shows the live total/required split, but nothing is saved) — leveling up jobs elsewhere and re-opening the menu is what actually advances it, not clicking the requirement itself.
Categories
Categories are defined in categories.yml (CategoryConfiguration.categories, keyed by category id) and deserialise to RequirementCategory via CategoryTypeSerializer:
Field | YAML key | Type | Description |
|---|---|---|---|
| (map key) | String | The category id used by |
| (inline) |
| The category's display item — |
|
|
| Per-rank reward action lists, keyed by rank id. |
rewards is keyed by rank id, not by category — the same category can be reused across several ranks in the chain, each with its own reward list (default here refers to the rank whose id: "default", i.e. config.yml's default-group).
Category completion
Whenever update-rank-requirement pushes a requirement to completion, RankService#updateRequirement:
Checks
PlayerRankData#hasCompletedCategory(rankId, categoryId)— if that rank/category pair is already marked completed, it stops here (rewards are never re-granted).Otherwise calls
RankService#hasCompletedCategory, which re-checks every requirement in the rank that shares thiscategoryid against the player's stored progress.If all of them are satisfied, it calls
PlayerRankData#setCategoryCompleted(rankId, categoryId)and runscategory.rewards().get(rankId)on the main thread.
RequirementCategory is itself a ComplexPlaceholder (namespace category); its requirements case returns an ItemProvider of every WrappedRankRequirement in the current %parameters_rank% whose category matches — that's what backs %category_requirements_size% (see Placeholders in lore).
update-rank-requirement
Drives progress on a single requirement for the executing player. Record fields: rankId, category, requirement (all @Single PlaceholderWrapper<String>). Implemented by UpdateRequirementAction, which resolves the rank and delegates to RankService#updateRequirement:
Looks up the category (
REQUIREMENT_NOT_FOUNDif missing) and the matching requirement in the rank whosecategoryand map-key both match (REQUIREMENT_NOT_FOUNDif no match).Reads current progress from
PlayerRankData; ifcurrent >= requiredalready, sendsREQUIREMENT_ALREADY_COMPLETEDand stops.Calls
requirement.take(player, current). On failure sendsREQUIREMENT_TAKE_ERROR; if the taken amount is0, sendsREQUIREMENT_NO_PROGRESS.Otherwise updates stored progress per
shouldCountUntilFulland:If the new total meets or exceeds the required amount: persists the full amount, sends
REQUIREMENT_COMPLETED, and runs the category completion check.Otherwise sends
REQUIREMENT_PROGRESSwith%current%,%total%, and%c%(remaining) placeholders resolved against the new totals.
See Messages for the exact message keys and Developer API for the registered action signature.
has-completed-requirement/has-completed-category
Two PaperRequirements for gating menu items or other actions on mission progress:
Requirement | Arguments | Checks |
|---|---|---|
|
|
|
|
|
|
Both read the player's PlayerRankData via SyncAPI; has-completed-category throws if no player data is found for the executor. See Developer API for the registered requirement signatures (#has-completed-category, #has-completed-requirement).
Menus
rank-details (menus/categories.yml)
Lists a rank's categories: the categories layout binds taint: "category" to provider: "%ranks_categories%" (RanksPlaceholders → every loaded RequirementCategory). The category_open item is shown when %parameter_category_requirements_size% > 0 (i.e. the category has at least one requirement in the current rank) and opens the requirements menu:
category_close overrides it (higher priority) once [has-completed-category] %parameters_rank_id% %parameter_category_id% passes.
ranks-requirements (menus/requirements.yml)
Lists one category's requirements: provider: "%parameters_rank_requirements%" chains the menu's rank parameter (a RankConfiguration) into its requirements case (an ItemProvider of every WrappedRankRequirement on that rank). Two competing item variants are filtered to the current category via [compare] %parameter_requirement_category% == %parameters_category_id%:
requirement-open(priority: 1) — the default state. Copies the requirement'sdisplay-item(%parameter_requirement_item%) and, on left-click, runsupdate-rank-requirementfollowed by[refresh].requirement-done(priority: 2) — overrides it once%parameter_requirement_current% >= %parameter_requirement_total%, showing a locked/checkmark icon instead.
A scoreboard-display button runs set-displayed-ranks-quests scoped to the current category; rank-details's own scoreboard-display button runs it with the literal argument all.
set-displayed-ranks-quests
Single record field categoryWrapper. Toggles PlayerRankData#categoryQuestsDisplay for the executing player:
If the stored value already equals the given argument, it's cleared to
nullandQUESTS_DISPLAY_DISABLEDis sent (turning the scoreboard display off).Otherwise the argument is stored as-is and
QUESTS_DISPLAY_ENABLEDis sent.
"all" has no special handling here — it's just stored like any other category id. It's RanksPlaceholders' ranks_questsDisplay case that treats a stored value of "all" (case-insensitively) as "show every category's requirements" instead of filtering to one, rendering up to 5 incomplete requirements through the QUEST_DISPLAY_FORMAT message. See Player data & sync and Developer API.
Placeholders in lore
Placeholder | Resolves to | Requires |
|---|---|---|
|
| A |
|
| — |
| The category's matching requirements list (filtered to the current | A resolvable |
These are the exact placeholders the shipped rank files use in display-item.lore:
See Placeholders for the full %requirement_*%/%category_*% reference.
Minimal example
One requirement entry of each type, as they appear on ranks/default.yml: