Developer API
AstralClasses exposes its services through the ClassAPI static façade and the plugin instance. Integration typically falls into three categories: reading class/ability data, accessing player progression state via AstralSync, or building custom plugins around the ClassService and SkillService.
Maven coordinates
<dependency>
<groupId>com.astralrealms</groupId>
<artifactId>classes</artifactId>
<version>2.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
Repository: https://maven.astralrealms.fr/repository/maven-public/.
ClassAPI
Static lookups for reading a player's current class:
import com.astralrealms.classes.ClassAPI;
AstralClass playerClass = ClassAPI.selectedClass(player);
Method | Returns | Use |
|---|
selectedClass(Player player)
| AstralClass
| Resolves the player's currently selected class. Falls back to the default-class if no selection is found or if player data is unavailable. |
ClassAPI is bootstrapped once from AstralClasses#onEnable — do not call init() from your plugin.
The AstralClass model
Represents a loaded class definition from the YAML configuration. Provides both static metadata and helper methods for computing player-specific ability data.
@ConfigSerializable
@Getter
public class AstralClass implements ComplexPlaceholder {
String id;
ComponentWrapper name;
ItemStackWrapper weaponItem;
ItemStackWrapper weaponDisplay;
PlaceholderWrapper<Integer> experienceToLevelUp;
Map<String, List<SkillEntry>> subClassesAbilities;
Map<String, PaperActionList> subClassesActions;
// Helper methods
public List<Object> getSkillData(Player player, InputType input);
public List<Object> getSkillData(Player player, String abilityId, ClassPlayerData data, InputType input);
public Optional<SkillEntry> findSkillEntry(String subClassId, String abilityId);
public SkillEntry findSkillEntry(String abilityId);
}
Component | Type | Purpose |
|---|
id()
| String
| The class's registry key — used in config files and as a reference everywhere. |
name()
| ComponentWrapper
| MiniMessage-formatted display name of the class. Render via ComponentWrapper#get(PlaceholderContainer) in a placeholder context. |
weaponItem()
| ItemStackWrapper
| The weapon/item associated with this class. Resolve it for display via the placeholder wrapper. |
weaponDisplay()
| ItemStackWrapper
| Display variant of the weapon item (may differ from the actual weapon used in gameplay). |
experienceToLevelUp()
| PlaceholderWrapper<Integer>
| Experience required to advance one level. Resolve it per-player via PlaceholderWrapper#get(PlaceholderContainer). |
subClassesAbilities()
| Map<String, List<SkillEntry>>
| Map of subclass id → list of abilities available in that subclass. The key "default" always exists. |
subClassesActions()
| Map<String, PaperActionList>
| Actions to run when a subclass is equipped (keyed by subclass id). |
SkillEntry
A nested class representing a single ability within a subclass:
@ConfigSerializable
@Getter
public static class SkillEntry implements ComplexPlaceholder {
String id;
ComponentWrapper name;
ComponentWrapper info;
String skillId; // The underlying skill id (from AstralSkill)
List<Integer> unlockableLevels; // Levels at which this ability unlocks (e.g. [1, 10, 20])
Integer unlockableBoostLevel; // Level at which boost variant unlocks
PlaceholderWrapper<Long> cooldown; // Cooldown in milliseconds, player-dependent
ComponentWrapper displayDamage;
InputType defaultInput; // Default input binding (e.g. LEFT_CLICK)
boolean forceInputBinding; // If true, input binding cannot be changed
}
Helper methods
Computes the active skill data for a player, given an input type. Returns a list of 5 elements (or fewer if no ability is bound):
// Returns: [skillId, cooldownMillis, classLevel, skillLevel, isBoosted]
List<Object> data = astralClass.getSkillData(player, InputType.LEFT_CLICK);
if (data.size() == 5) {
String skillId = (String) data.get(0);
long cooldown = (Long) data.get(1);
int classLevel = (Integer) data.get(2);
int skillLevel = (Integer) data.get(3);
boolean boosted = (Boolean) data.get(4);
}
If no ability is bound to that input, returns List.of("", 0L).
findSkillEntry(String subClassId, String abilityId) → Optional<SkillEntry>
Looks up an ability within a specific subclass only. Returns empty if the subclass or ability does not exist:
Optional<SkillEntry> entry = astralClass.findSkillEntry("default", "fireball");
entry.ifPresent(skill -> {
System.out.println("Ability: " + skill.name());
System.out.println("Underlying skill: " + skill.skillId());
});
findSkillEntry(String abilityId) → SkillEntry
Searches all subclasses for an ability by id. Returns the first match or null if not found anywhere in the class.
Reading player progression via AstralSync
Per-player class state is stored and synced through AstralSync. Query it via SyncAPI:
import com.astralrealms.classes.storage.ClassPlayerData;
import com.astralrealms.sync.SyncAPI;
SyncAPI.findData(player.getUniqueId(), ClassPlayerData.class)
.ifPresent(data -> {
// Read player's selected class and progression
String selectedClassId = data.selectedClassId();
List<String> unlockedClasses = data.unlockedClassIds();
Map<InputType, String> inputBindings = data.inputBindings();
// Class-specific progress
ClassProgressData progress = data.classProgressDataMap().get(selectedClassId);
if (progress != null) {
int level = progress.level();
int experience = progress.experience();
boolean isSubClassActive = progress.asSelectedSubClass();
String activeSubClass = progress.selectedSubClassId();
Map<String, Integer> skillLevels = progress.skillLevels(); // ability id -> level
Map<String, Boolean> boostedSkills = progress.boostedSkills(); // ability id -> boosted
}
// Settings
boolean toggleAutoAttack = data.classSettings().toggleAutoAttack();
});
ClassPlayerData
A mutable Lombok class (fluent @Getter/@Setter, not a Java record) with a reset() method that clears progress back to the default class:
Field | Type | Purpose |
|---|
selectedClassId()
| String
| The class currently active for this player. |
unlockedClassIds()
| List<String>
| All classes the player has unlocked. |
classProgressDataMap()
| Map<String, ClassProgressData>
| Progress per class (keyed by class id). |
inputBindings()
| Map<InputType, String>
| Custom input → ability id bindings. |
classSettings()
| ClassSettings
| Player-specific toggles (e.g. toggleAutoAttack). |
ClassProgressData record
Nested inside the map above — one entry per class the player has interacted with:
Field | Type | Purpose |
|---|
level()
| int
| Current class level. |
experience()
| int
| Current experience toward next level. |
asSelectedSubClass()
| boolean
| If true, player is actively using a subclass other than "default". |
selectedSubClassId()
| String
| The active subclass id (only meaningful if asSelectedSubClass() == true). |
skillLevels()
| Map<String, Integer>
| Per-ability level (ability id → level). Unlocked abilities have a level >= 1. |
boostedSkills()
| Map<String, Boolean>
| Which abilities are currently in boost state. |
AstralSync adapter
The plugin registers a SnapshotAdapter with AstralSync to persist class state across servers:
public class ClassSnapshotAdapter implements SnapshotAdapter<ClassPlayerData> {
@Override
public Key key() {
return Key.key("astralclasses", "player_data");
}
@Override
public ClassPlayerData create(Player player) {
// Seed default class at configured starting level
return new ClassPlayerData(
plugin.configuration().defaultClass(),
plugin.configuration().startingLevel()
);
}
}
Method | Behavior |
|---|
key()
| Returns Key.key("astralclasses", "player_data") — the sync adapter's identity. |
create()
| Seeds a new player snapshot with the default-class from config and the starting-level. No plugin code should manually call this — it is part of AstralSync's initialization contract. |
update()
| Pass-through (no modification during sync). |
apply()
| Currently a no-op. Handlers that need to react to synced class changes should use AstralClass placeholders or query via ClassService directly. |
serialize() / deserialize()
| Read/write all fields (selectedClassId, unlockedClassIds, classProgressDataMap, inputBindings, classSettings) to the binary protocol. |
Note: Treat this adapter as a persistence contract, not for manual registration. It is registered once by AstralClasses itself at startup.
ItemStack supplier (weapon items)
The plugin registers a stack supplier under the id classes.weapon, allowing other plugins to resolve class weapon items via the standard item supplier API:
import com.astralrealms.core.paper.AstralPaperAPI;
import net.kyori.adventure.key.Key;
// Get an itemstack by class id
ItemStack weapon = AstralPaperAPI.itemStack("classes.weapon", classId, 1);
// Resolve a held weapon back to its class
AstralClass classe = plugin.classes().findByWeaponItem(player.getInventory().getItemInMainHand());
The supplier is keyed as Key.key("classes.weapon", classId). Use it in any context that accepts an itemstack key (e.g., [give-item] action in menus).
Plugin instance and services
Access the plugin and its services:
import com.astralrealms.classes.AstralClasses;
AstralClasses plugin = AstralClasses.getInstance();
// Class lookups
ClassService classes = plugin.classes();
classes.findById(classId); // Optional<AstralClass>
classes.findByPlayer(player); // AstralClass (falls back to default)
classes.findByWeaponItem(itemStack); // Optional<AstralClass>
classes.ids(); // Set<String> of all class ids
classes.all(); // Collection<AstralClass> of all loaded classes
classes.selectedSubClass(player); // String — the player's active subclass id
SkillService
Only available when skills are enabled for the server group (see configuration.skill-enable-groups):
SkillService skills = plugin.skills(); // May be null if skills are not enabled
if (skills != null) {
// Trigger a skill
skills.tryTriggerSkill(player, InputType.LEFT_CLICK, false);
// Cooldown manager
CooldownManager cooldowns = skills.cooldownManager();
boolean canUse = cooldowns.canUse(player, "fireball");
long remaining = cooldowns.getRemainingCooldown(player, "fireball");
// Trigger manager (internal rate-limiting)
SkillTriggerManager triggers = skills.triggerManager();
boolean canTriggerThisTick = triggers.canTrigger(player);
}
Important: plugin.skills() returns null if skills are disabled. Always null-check before use.
Registered helpers
The plugin registers two helpers with AstralCore for command/menu integration:
Tab completion for InputType enum values in ACF commands:
@CommandCompletion("inputType")
public void bindAbility(CommandSender sender, InputType input) {
// input is one of: SPACE, SNEAK, LEFT_CLICK, RIGHT_CLICK, DROP, SWAP_HANDS, HOTBAR_1…8
}
Context resolver: net.kyori.adventure.key.Key
Parses a string argument into a Key for use in commands or menus:
@CommandCompletion("@nothing")
public void doSomething(CommandSender sender, Key key) {
// key is parsed from the string argument
// Used internally for the itemstack supplier key resolution
}
Skill casting integration
When a skill ability is triggered, the underlying SkillService.tryTriggerSkill method calls into AstralSkill via:
SkillAPI.service().castAtPos(
player,
casterUuid,
skillId,
eyeLocation,
direction,
placeholderContainer
);
For details on writing skill implementations and how classes invoke them, see the AstralSkill developer documentation — AstralClasses is a consumer of the AstralSkill engine and does not define the skill DSL itself.
Last modified: 25 July 2026