Astral Realms Documentation Help

Jobs

A job is one YAML file under plugins/AstralJobs/jobs/<id>.yml, deserialized to a Job and loaded by JobService#load (ConfigurationManager#loadFolder) — every file in the jobs/ folder is loaded, one job per file. A job whose id duplicates an already-loaded job is skipped with a warning. See Overview for the five shipped jobs and their ids.

Minimal example

id: "miner" display-name: "<secondary_color>Mineur" # Menu menu-title: "%img_internal:neg_8%<white>%img_guis:jobs%%img_internal:neg_110%<font:alphabet:shift_1>mineur" menu-icon: copy-from: "%stacksuppliers_ce_guis:miner%" menu-secondary-icon: copy-from: "%stacksuppliers_ce_icons:jobs_miner%" name: "<bold><primary_color>Métier Mineur" lore: - "" - " <text_color>Gagne de l'<secondary_color>argent <text_color>en minant des <secondary_color>pierres précieuses<text_color>!" # Leveling equations level-progress-equation: "(level>=0&level<1)*10+(level>=1&level<25)*(40.5+5.58*(level-1)+0.612*(level-1)^2+0.00983*(level-1)^3)" experience-progress-equation: "experience*(1+(level-1)*0.08)" experience-income-equation: "income*(1+(level-1)*0.08)" # Actions actions: coal_ore: block-break: reward: 1.70387 experience: 14.61777 # Rewards rewards: 1: description: - "<secondary_color><bold>| Récompenses" - " <text_color>250<white>%img_icons:coins%" actions: - "[message] <success>Vous obtenez <secondary_color><bold>250<white>%img_icons:coins% </bold><#80ed99>!" - "[console] eco give %player_name% default 250"

(Trimmed from the shipped jobs/miner.yml, which carries ~90 actions entries and 200 rewards tiers.)

Top-level fields

Field

Type

Description

id

String

Job identifier. Used as the key in JobService's job map, in LevelService/JobLevelEntity lookups, as the <job> argument for /jobs level and /jobs experience, and as the job-scope filter on job-specific modifiers.

display-name

ComponentWrapper

The job's display name (e.g. Mineur). Exposed as %jobs_job_<id>_name% and used in the level-up message (%job%).

menu-title

ComponentWrapper

Title text for the job's row in the jobs-main menu. See Menu integration.

menu-icon

ItemStackWrapper

The job's icon in jobs-main, normally built with copy-from pointing at a stack supplier.

menu-secondary-icon

ItemStackWrapper

A second icon (e.g. for a detail/info view), also normally copy-from plus an overriding name/lore.

level-progress-equation

Crunch expression, var level

Total experience required to reach level. See Leveling equations.

experience-progress-equation

Crunch expression, vars level, experience

Converts a raw action XP value into the XP actually granted.

experience-income-equation

Crunch expression, vars level, income

Converts a raw action money value into the money actually granted.

actions

Map<Key, Map<String, JobActionValue>>

Material/entity key → action-type key → reward/XP. See Actions.

rewards

Map<Integer, JobReward>

Level → description + claim actions. See Rewards.

All five shipped jobs follow the same <primary/secondary/text>_color placeholder convention for display-name/menu-secondary-icon.name/menu-secondary-icon.lore and the same %img_*%/%stacksuppliers_*% placeholders for menu-title /icons, but nothing in Job enforces that — it's just the house style.

Leveling equations

Each job compiles three Crunch expressions once at load (CompiledExpression, cached per-thread) and evaluates them per action or per level-up check:

Field

Compiled with variables

Job method

level-progress-equation

level

computeLevelProgress(level)

experience-progress-equation

level, experience

computeExperienceProgress(level, experience)

experience-income-equation

level, income

computeExperienceIncome(level, income)

In the shipped jobs, level-progress-equation is a single expression made of &-gated bands ((level>=0&level<1)*10 + (level>=1&level<25)*(...) + …), each band scaled by (1 + (level-1)*0.08) and mostly a cubic polynomial in (level - bandStart) (the first band, 0–1, and the last, 199–201, are flat constants instead), covering ranges up to level 201; a non-positive result signals the equation's domain is exhausted (used to detect "maxed" — see Level-up flow). experience-progress-equation and experience-income-equation are both shipped as <var>*(1+(level-1)*0.08) — an 8%-per-level multiplier on top of the action's base experience/reward. All five shipped jobs use the same constants; nothing requires that.

Actions

actions: <material-or-entity-key>: <action-type-key>: reward: <double> # money experience: <double>

actions is a map of Key (a bare name like coal_ore or cat deserializes to minecraft:<name>) to a map of action-type key → JobActionValue(reward, experience, operation). JobService#load warns (but does not reject the job) if an action-type key isn't one of the ten registered keys below. At load time JobService#buildCache re-indexes every job's actions by action-type key then by Key, so an incoming event resolves straight to the set of (jobId, JobActionValue) pairs that match, across all jobs.

operation (UP/DOWN/NONE, rendered as //-) is not authored in YAML — no shipped job file sets it. RandomUtils#randomizeValues, run once per job right after load, deterministically perturbs every action's reward/experience by up to ±25% (seeded from the job id, action-type key, material/entity key, and the current ISO week — so the perturbation is stable for a week and changes only when the week rolls over) and sets operation to UP/DOWN/NONE depending on whether the perturbed reward came out above, below, or equal to the value written in the YAML file. The perturbed values are what actually get cached and paid out; operation exists purely to drive the up/down indicator shown in the menu-computed action rows.

Action types

ActionService registers ten JobActionTypes, grouping them by Bukkit event class so only one listener per event class is installed:

key

Event

Fires on

block-break

BlockBreakEvent

Breaking a block, keyed by the broken block's type. Skipped if the event is cancelled, or if the block is ageable and not fully grown (except SUGAR_CANE).

block-place

BlockPlaceEvent

Placing a block, keyed by the placed block's type.

harvest-block

PlayerHarvestBlockEvent

Harvesting a block (e.g. a berry bush) without breaking it, keyed by the harvested block.

shear-block

PlayerShearBlockEvent

Shearing a block (e.g. a pumpkin), keyed by the sheared block.

strip-block

EntityChangeBlockEvent

A player strips a log/wood block — the resulting block type must be tagged STRIPPED_*, keyed by the resulting block.

gather-honey

PlayerInteractEvent

Right-clicking a full beehive/bee nest with a glass bottle in the main hand, keyed by the beehive block.

kill-entity

EntityDeathEvent

A player lands the killing blow on an entity, keyed by the entity type.

shear-entity

PlayerShearEntityEvent

Shearing an entity (e.g. a sheep), keyed by the entity type.

fish

PlayerFishEvent

State CAUGHT_FISH where the caught entity is an Item, keyed by the caught item's material; reward/XP scale with the caught stack's amount.

cook

FurnaceExtractEvent

Taking a cooked/smelted/smoked item out of a furnace/blast furnace/smoker, keyed by the extracted item's material; reward/XP scale with the extracted amount.

For every block-based action type except block-place itself, a block that was placed by a player is skipped (BlockService#wasPlacedByPlayer, checked on the main thread) — so breaking a player-placed stone block doesn't grant Miner XP, but breaking naturally-generated stone does.

block-break/block-place/harvest-block/shear-block/strip-block/gather-honey build their menu item and translatable name from Material (via BlockBasedActionType); kill-entity/shear-entity build them from EntityType (a spawn egg, via EntityBasedActionType); fish/cook build them from the extracted ItemStack's Material (via ItemBasedActionType).

Rewards

rewards: <level>: description: - "<component>" - … actions: - "[message] …" - "[console] …"

rewards maps a level (int) to a JobReward(description, actions):

Field

Type

Description

description

ComponentWrapperList

Lines shown for that reward tier in the job menu (e.g. %jobs_job_<id>_rewards_at_<n>_description%).

actions

PaperActionList

AstralCore action pipeline run when the reward is claimed.

The shipped jobs pair a [message] line per granted item/currency with a [console] line that actually performs the grant (eco give %player_name% default <amount>, give %player_name% <item> [amount], …) — [message] doesn't grant anything by itself, it only informs the player.

Rewards are not granted automatically on level-up. A player claims them with /jobs claim, which walks every job, finds every level <= the player's current level not yet in their JobsPlayerData claimed set, marks it claimed, fires a JobClaimRewardEvent, and runs that tier's actions. The same claim path is exposed as the claim-job-reward action for a menu button targeting one specific job/level.

Action resolution — how a gain is computed

When a tracked event fires, ActionService resolves the affected block/entity/item's Key on the main thread, looks up matching cached actions (JobService#findActionsByType), and — if any match — moves the rest of the work to a 2-thread worker pool:

  1. For each matching (jobId, JobActionValue), load that job and the player's JobLevelEntity.

  2. Multiply the action's base reward/experience by the event's item amount (1 for block/entity actions).

  3. Snapshot the player's current (level, experience) under the level entity's lock.

  4. Run the scaled base values through the job's equations at the snapshotted level: job.computeExperienceProgress(level, baseExperience) and job.computeExperienceIncome(level, baseMoney).

  5. Apply the player's stacked modifiers: ModifierService#compute(playerId, JobModifierScope.EXPERIENCE, …, jobId) and the same for MONEY — folding in both timed boosters and permissions-boosters-derived modifiers cached for that player.

  6. Fire a cancellable JobExperienceGainEvent and JobMoneyGainEvent; a cancelled event zeroes that gain, a listener may rewrite newExperience/newAmount.

  7. Apply the surviving experience under the lock and run the level-up loop.

The same job/scope math is also exposed as ModifierService#computeWithLevel(player, job, scope, baseValue) — it looks up the player's live level via LevelService (rather than a snapshot) and applies the equations plus modifiers in one call. This is what the jobs-main menu uses to show a player what an action is currently worth — see Menu integration — rather than the action-gain path above, which snapshots the level once per event for lock-scoping reasons.

Level-up flow

Still under the level entity's lock, after experience is applied:

nextThreshold = job.computeLevelProgress(level + 1) while ((max-level <= 0 || level < max-level) && nextThreshold > 0 && nextThreshold <= experience): fire JobLevelUpEvent(player, job, level + 1) if cancelled: break experience -= nextThreshold level += 1 send LEVEL_UP message (%level%, %job%) nextThreshold = job.computeLevelProgress(level + 1)

max-level (from config.yml, default 200) caps leveling; a value <= 0 is treated as uncapped. Once computeLevelProgress returns a non-positive value the equation's domain is exhausted and the loop stops, treating the player as maxed — this is what prevents an uncapped config from spinning forever once a band-based equation like the shipped ones runs out of bands. Players past the cap (or past the equation's domain) keep accumulating experience but stop gaining levels.

The surviving experience/money from step 6 above (not just level-ups) are handed to NotificationService#update, which accumulates them per player+job into a NotificationEntry. The level/experience fields on JobLevelEntity are already updated in-memory by ActionService itself, but persisting them to the database and depositing the accumulated money via EconomyService#deposit both happen only when that entry later flushes — after 5 seconds with no further gain for that player+job (NotificationCleanUpTask, polled every second), or immediately when the player quits. The 350 ms cooldown only governs how often the action-bar notification itself re-renders in the meantime (see Overview → Integrations). Money is granted independently of whether a level-up occurred on that particular action.

/jobs opens the jobs-main menu blueprint (MenuContainer#computeAndOpen(player, "jobs-main"), see Commands). A job's placeholder tree (namespace jobs, sub-key job, then the job id — Job itself is a ComplexPlaceholder reached that way) feeds that menu:

Placeholder path

Source

Yields

%jobs_job_<id>_name%

display-name

The job's display name.

%jobs_job_<id>_menu_title%

menu-title

The menu row title.

%jobs_job_<id>_menu_icon%

menu-icon

The menu row icon (built with copy-from against a stack supplier in the shipped configs).

%jobs_job_<id>_menu_secondary-icon%

menu-secondary-icon

The secondary icon.

%jobs_job_<id>_player_level%/_player_experience%

Live JobLevelEntity for the viewing player

Current level/experience in that job.

%jobs_job_<id>_actions_...% (iterated/indexed via ItemProvider)

actions

Per-action-entry name (translatable component), item (built ItemStack), and computed.

%jobs_job_<id>_rewards_...% (iterated/indexed)

rewards

Per-tier level, description, claimed, claimable.

The computed sub-key on an action entry (JobActionPlaceholder) is what actually renders an action row: for each action-type value on that material/entity, it looks up the display name from config.yml's action-type-displays map (e.g. block-break: "Casser"), computes the live experience/money via ModifierService#computeWithLevel for the viewing player, and formats one line per config.yml's action-display template list (%experience%, %money%, %formattedName%, %operation% — the ↑/↓/- indicator from operation). See Placeholders for the full jobs namespace.

Where to go next

  • Overview — how jobs, actions, and rewards fit together across the plugin.

  • Configurationmax-level, permissions-boosters, action-display, action-type-displays, leaderboards.

  • Commands/jobs, /jobs claim, /jobs level, /jobs experience.

  • Boosters — the other source of ModifierService modifiers.

  • Placeholders — the full %jobs_*% namespace.

  • Developer APIJobExperienceGainEvent, JobMoneyGainEvent, JobLevelUpEvent, JobClaimRewardEvent, and the claim-job-reward action.

Last modified: 25 July 2026