Developer API
AstralJobs exposes read access to job levels/experience and modifier management through the static JobsAPI façade, reacts to XP/money/level/reward changes through a set of cancellable Bukkit events, and registers one action and one item-stack supplier that other plugins/menus can drive directly.
Maven coordinates
Repository: https://maven.astralrealms.fr/repository/maven-public/.
JobsAPI
A @UtilityClass of static entry points, initialized with the plugin instance on onEnable. Safe to call from any other plugin once AstralJobs has finished loading.
Method | Returns | Behavior |
|---|---|---|
|
| The player's current level for |
|
| The player's current experience for |
|
| Sum of |
|
| Delegates to |
|
| Delegates to |
|
| Delegates to |
Modifier API
JobsAPI.addModifier/removeModifier/hasModifier are thin wrappers around ModifierService, which keeps one Map<Key, JobModifier> per player (PlayerJobModifiers), backing both timed boosters and the permissions-boosters config feature.
JobModifier
Field | Type | Description |
|---|---|---|
|
| Unique identifier for this modifier. Re-adding a modifier with the same |
|
| The modifier magnitude. Meaning depends on |
|
|
|
|
|
|
|
| Optional. When set, the modifier only applies to gains for that specific job ( |
A convenience constructor omits jobId (defaults to null, i.e. applies to all jobs):
ModifierService#compute(playerId, scope, baseValue, jobId) is what actually folds a player's modifiers into a gain: it skips modifiers whose scope doesn't match the request (unless GLOBAL) and skips job-specific modifiers whose jobId doesn't match the requested job, then applies every surviving ABSOLUTE modifier's value as a sum and every surviving RELATIVE modifier's value as a multiplicative percentage, in that order ((baseValue + Σabsolute) × Π(1 + relative/100)). This is the exact math behind both the action-resolution pipeline and the jobs-main menu's live "computed" value.
Job events
All job events live under com.astralrealms.jobs.event.job and extend the abstract JobEvent (getPlayer(), getJob()). ActionService fires JobExperienceGainEvent, JobMoneyGainEvent, and JobLevelUpEvent from its 2-thread worker pool (constructed with async = true) — handlers must not touch non-thread-safe Bukkit API without scheduling back onto the main thread. JobClaimRewardEvent is the exception: it is not async (async = false), since it is fired synchronously from the /jobs claim command handler.
Event | Cancellable | Async | Fired from |
|---|---|---|---|
Yes | Yes |
| |
Yes | Yes |
| |
Yes | Yes |
| |
No | No |
|
JobExperienceGainEvent
Accessor | Type | Description |
|---|---|---|
|
| The player's experience for this job before this gain (immutable). |
|
| The raw XP amount computed for this action, after equations and modifiers but before this event runs (immutable). |
|
| Initialized to |
The XP actually credited is max(0, newExperience - originalExperience) after the event returns — cancelling the event zeroes it outright. Rewriting newExperience to something other than originalExperience + gain (e.g. doubling the gain, or clamping it) changes only that one action's payout; it does not touch the player's already-stored experience.
JobMoneyGainEvent
Accessor | Type | Description |
|---|---|---|
|
| The raw money amount computed for this action (immutable). |
|
| Initialized to |
The money actually granted is max(0, newAmount) after the event returns; cancelling zeroes it. Note the asymmetry with JobExperienceGainEvent: here newAmount is the payout, not a delta over a stored base. This event fires for every matched action independently of whether that action also triggers a level-up.
JobLevelUpEvent
Accessor | Type | Description |
|---|---|---|
|
| The level the player is about to reach. |
Fired once per level inside ActionService's level-up loop, before the level/experience are actually mutated. Cancelling it stops the loop immediately (break) — the player keeps the experience accumulated so far but does not gain this level or any subsequent one in the same action. See Level-up flow.
JobClaimRewardEvent
No additional accessors beyond getPlayer()/getJob() — notably no level. /jobs claim fires one instance of this event per reward tier it marks claimed, so a player claiming three unclaimed tiers on miner in one command fires it three times, each carrying the same Job but no way to distinguish which tier. Not cancellable and not async — safe to touch other Bukkit API directly from a listener.
Booster events
Booster events live under com.astralrealms.jobs.event.booster and extend BoosterEvent (getBooster() → BoosterEntity). Unlike the job events above, BoosterEvent's isAsync flag is not fixed — it's computed at construction time from the calling thread (super(!Bukkit.isPrimaryThread())). In practice both events fire from BoosterService's repository-callback continuations (after a save/delete completes), which normally run off the main thread, but a listener should not hard-code that assumption — check event.isAsync() if it matters.
Event | Fired from | Notes |
|---|---|---|
| Fired once per booster activation, guarded so a booster doesn't double-announce. | |
| Fired regardless of whether the booster's owner is currently online. |
Neither event is Cancellable — by the time either fires, the modifier has already been applied (for activation) or the entity has already been deleted from the database (for expiry). Use them for reactive side effects (logging, notifying other players, integrations), not to veto the booster lifecycle.
BoosterActivateEvent
BoosterExpiredEvent
BoosterEntity (see Boosters) exposes getUniqueId(), getOwnerId(), getType() (EXPERIENCE/MONEY/BOTH), getScope() (INDIVIDUAL/GLOBAL), getDuration(), getMultiplier(), isActive()/isExpired(), and modifierKey() — the Key (astraljobs:<uniqueId>) under which the booster's JobModifier is registered in ModifierService.
claim-job-reward action
Registered globally (registerAction("claim-job-reward", ClaimJobRewardAction.class)) so it's usable from any menu/dialog/reward actions: list on the network, not just AstralJobs' own menus. Like every action, both arguments accept placeholder substitution — a shipped-style rewards menu row would resolve level from its iteration context rather than hardcoding it.
Arg | Type | Description |
|---|---|---|
|
| Required. Resolves to a loaded |
|
| Required. The reward tier to claim. |
Behavior: looks up the executing player's JobsPlayerData via SyncAPI.findData; if the player's cached level for jobId is below level, sends REWARD_CANNOT_BE_CLAIMED and stops. Otherwise looks up the JobReward for that exact level — sending UNEXPECTED_ERROR if none exists — runs the reward's actions against the player, then marks (jobId, level) claimed in JobsPlayerData. Also sends UNEXPECTED_ERROR if the player's JobsPlayerData snapshot isn't available (e.g. Sync hasn't finished loading it yet). Unlike /jobs claim, this does not check whether the tier was already claimed before re-running its actions — the action is meant for a menu that already hides claimed tiers (e.g. via the claimed/claimable reward placeholders).
Booster item-stack supplier
Booster items are exposed to AstralCore's item-stack-supplier registry under the namespace jobs:
That means any AstralCore call site that accepts a supplier-backed item key also accepts a jobs:<id> key, where <id> is a BoosterBlueprint's id (the filename under boosters/, minus extension). The supplier:
get(String id)— builds a freshItemStackfrom the blueprint viaBoosterBlueprintService#build; throwsIllegalArgumentExceptionif no blueprint has that id.get(Player, Key, int amount)— same lookup, then overrides the stack size.completions()— every loaded blueprint id, asKey.key("jobs", id), for tab completion.keyOf(ItemStack)— reverses anItemStackback toKey.key("jobs", blueprint.id())viaBoosterBlueprintService#fromItemStack, ornullif the stack isn't a recognized booster item.
AstralSync integration
JobsSnapshotAdapter registers JobsPlayerData (claimed rewards) with AstralSync under the key astraljobs:data:
Method | Behavior |
|---|---|
|
|
| A fresh |
| Reads a |
| Writes that same map back. |
| No-op — returns the data unchanged. |
| No-op — claimed-rewards state has no in-world effect to reapply on load. |
JobsPlayerData itself is a Map<String, Set<Integer>> of job id → claimed levels, with claimReward(jobId, level) and hasClaimedReward(jobId, level) helpers. Other plugins can read it the same way AstralJobs does internally:
Because it round-trips through AstralSync's binary snapshot format rather than a local table, claimed-reward state — unlike modifiers — follows the player across every server in the network. See Rewards for how /jobs claim and the claim-job-reward action populate it.