Astral Realms Documentation Help

Developer API

AstralSkill exposes its casting and lifecycle API through the SkillAPI static façade and the SkillService. Consumers (AstralClasses, AstralMobs, and other plugins) call cast methods with a skill id, a caster entity, and optionally a PlaceholderContainer for custom level/attribute bindings. The skill DSL and configuration are covered in Skill Files and Configuration; this page documents the entry points for programmatic skill casting.

Maven coordinates

<dependency> <groupId>com.astralrealms</groupId> <artifactId>skill</artifactId> <version>1.2-SNAPSHOT</version> <scope>provided</scope> </dependency>

Repository: https://maven.astralrealms.fr/repository/maven-public/.

Entry point

import com.astralrealms.skill.SkillAPI; SkillService skillService = SkillAPI.service();

SkillAPI.service() returns the SkillService singleton, which gates access to all casting and lookup methods. SkillAPI.targeting() returns the SkillTargetService, the registry for hit-detection rules. Both throw IllegalStateException if the plugin has not enabled yet.

The SkillService singleton If called before the AstralSkill plugin has enabled, it throws IllegalStateException. Callers should guard with a soft dependency (check if AstralSkill is loaded via Bukkit.getPluginManager().getPlugin("AstralSkill")) or defer initialization to a later event.

Casting methods

The three primary cast methods differ in how the target is specified:

castAtPos(…) — direct position cast

void castAtPos( Entity caster, UUID casterUUID, String skillId, Location location, Vector3d direction, PlaceholderContainer container )

Casts a skill at an explicit location and direction. The direction vector is typically normalized (e.g. from a player's eye direction, or a computed path). If container is null and the caster is a Player, a player-aware placeholder container is auto-created via AstralPaperAPI.createPlaceholderContainer(player); otherwise a blank PlaceholderContainer() is used. If the skill id is unknown, a warning is logged and the cast is abandoned.

Before the cast initializes, the skill's PaperRequirementList (if present and the caster is a Player) is evaluated; if requirements fail, the cast is aborted.

Parameters:

  • caster: The entity triggering the skill (may be a Player, Mob, ArmorStand, etc.).

  • casterUUID: The UUID of the caster; carried through to caster effects and targeting.

  • skillId: The id of the skill file (must match a file under skills/).

  • location: The center/spawn point of the skill (interpreted in the location's world).

  • direction: A velocity/heading vector (typically normalized). For impact skills, points along the terrain normal; for projectiles, defines the launch direction.

  • container: Custom placeholder bindings (e.g. level, custom stats). If null and caster is a Player, a container is auto-built.

castThroughPos(…) — projectile auto-aim

void castThroughPos( Entity caster, UUID casterUUID, String skillId, Location startLocation, Location passLocation, PlaceholderContainer container ) void castThroughPos( Entity caster, UUID casterUUID, String skillId, Location startLocation, Location passLocation, PlaceholderContainer container, boolean forceCast )

Solves the launch pitch so a projectile skill's simulated arc passes through passLocation. Unlike castAtPos, the caller does not supply a direction; instead the projectile's full trajectory is simulated using its configured velocity, drag, gravity, and range, and the launch pitch is computed. The horizontal heading always points from startLocation toward passLocation.

This only works for projectile skills (see Cast Types). If the skill is not a projectile, or the target cannot be reached (out of range, too much drag, etc.), the cast is aborted with a warning.

When both a direct arc and a lobbed arc can reach the point, the flatter path is preferred.

Skills that enforce a fixed pitch cannot be aimed vertically, so arbitrary heights may not be reachable; in such cases the cast is aborted.

Variant without forceCast: If the target is unreachable, the cast is abandoned.

Variant with forceCast = true: If the target is unreachable, the projectile is still fired along the straight line from startLocation toward passLocation (it simply will not reach the point). This is useful for "best-effort" aiming (e.g. chase/homing skills that should always activate even if the geometry is challenging).

Parameters:

  • startLocation: Where the projectile spawns; defines the world the cast happens in. If passLocation is in a different world, it is re-interpreted in the start location's world and a warning is logged.

  • passLocation: The location the projectile should pass through.

  • container: Custom placeholder bindings. Null-handling is the same as castAtPos.

  • forceCast: When true, fire along a direct heading if the target is unreachable; when false, abort.

castAtTarget(…) — entity targeting

void castAtTarget( Entity caster, UUID casterUUID, String skillId, UUID entityUUID, PlaceholderContainer container )

Resolves an entity on the main thread (via world.getEntity(entityUUID)) and casts at its location. If the entity no longer exists, a warning is logged and the cast is abandoned. Otherwise, delegates to castAtPos(caster, casterUUID, skillId, target.getLocation().clone(), new Vector3d(0.0), container).

Lookup methods

Method

Returns

Use

findById(String id)

Optional<WrappedSkill>

Resolve a skill by id. Returns a defensive copy.

all()

Map<String, WrappedSkill>

Every loaded skill (unmodifiable snapshot).

skillService.findById("my_laser") .ifPresent(skill -> getLogger().info("Loaded " + skill.getId())); skillService.all().forEach((id, wrapped) -> { getLogger().info(id + ": " + wrapped.getType()); });

findById() returns a defensive copy of the skill, so modifications do not affect the shared state.

Lifecycle control

Active skills are tracked per caster and automatically updated each tick. Consumers rarely call these directly; most lifecycle management happens automatically:

Method

Returns

Use

activateSkill(Entity author, WrappedSkill skill)

void

Register a skill as active (queues it for update). Called internally by cast methods.

deactivateSkill(Entity author, WrappedSkill skill)

void

Unregister one active skill instance.

deactivateSkills(Entity author, Set<WrappedSkill> skills)

void

Unregister a set of skills.

deactivateAllSkills(Entity author)

void

Deactivate all active skills for a single entity.

deactivateAllSkills()

CompletableFuture<Void>

Deactivate all active skills across all casters. Returns a future that completes when all shards have cleared.

The deactivateAllSkills() variant (no argument) is called during plugin shutdown and reload.

Performance monitoring

String summary = skillService.metricsSummary(); getLogger().info(summary);

metricsSummary() returns a human-readable snapshot of every shard's update-loop performance, including tick counts, overruns, peak timing, and active skill counts. Safe to call from any thread. The output looks like:

Skill threads (4): 10,000 ticks, 150 over 1ms (1.500%), peak 3.245ms, 500 started, 490 finished shard 0: 2,500 ticks, 40 over (last 0.823ms, peak 2.100ms), 125 started, 123 finished shard 1: 2,500 ticks, 40 over (last 1.245ms, peak 3.245ms), 125 started, 122 finished …

Requirement gating

Before a skill cast initializes, if the caster is a Player and the skill has a PaperRequirementList defined in its configuration, the requirements are evaluated. If any requirement fails (e.g. not enough money, cooldown active, level too low), the cast is aborted silently and no effects fire.

See Skill Files for how to define requirements on a skill.

Threading model

AstralSkill uses a fixed pool of shards (update threads) to isolate skill state and avoid lock contention. Each caster is routed to the same shard via hash, so all of its active skills are updated on a single thread.

Utility scheduling

ScheduledExecutorService executor = skillService.getExecutor(); executor.schedule(() -> { // Utility work, e.g. delaying a follow-up cast }, 5, TimeUnit.SECONDS);

getExecutor() exposes shard 0's scheduled executor for callers that need to schedule utility work (e.g. delayed casts, cooldown timers). Callers must not touch shard state or access active skills from this thread. Only use it for work that does not depend on the skill update loop or shared caster state.

Cast dispatch

Casts are dispatched to the caster's assigned shard. The cast initializes and the skill is queued for updates. Each update tick (1ms budget per shard, across all active skills), the skill's update() method is called; when it returns true, the skill is marked finished and removed from the active set.

Automatic cleanup

The EntityListener deactivates all of a caster's active skills when the entity dies (EntityDeathEvent). The PlayerListener manages chunk snapshots and player-specific caches when players join, change worlds, or quit. As a consumer, you do not need to call cleanup methods when an entity disappears; it happens automatically.

Target rules

SkillAPI.targeting() returns the SkillTargetService: the registry a plugin uses to say which entities skills may hit, and — optionally — which entities are worth tracking at all. A plugin holds at most one of each; registering again replaces what it registered before.

SkillAPI.targeting().register(plugin, entity -> !myPets.contains(entity.getUniqueId())); SkillAPI.targeting().registerSource(plugin, () -> myMobRegistry.spawnedMobs()); SkillAPI.targeting().unregister(plugin); // drops both

Method

Purpose

register(Plugin, SkillTargetFilter)

A rule deciding whether a particular entity may be hit.

registerSource(Plugin, SkillTargetSource)

The entities this plugin wants reachable by skills.

unregister(Plugin)

Drops both — its entities become ordinary skill targets again.

targetable(LivingEntity)

Whether every registered filter allows the hit.

hasSources()

Whether any plugin has handed over a list of entities.

SkillTargetFilter

Where a skill's targets: answers "which kinds of entity does this skill hit", a filter answers "may this particular entity be hit by a skill at all" — something the configuration cannot express, since it only compares entity types. AstralMobs uses it to keep pets out of hit detection entirely: an owned pet shares its server-side type with every other mob of that species.

Returning false hides the entity from other casters' hit detection — no damage, no knockback, no effects, and projectiles pass straight through it instead of stopping. Its owner's own self-targeted skills still reach it.

SkillTargetSource

Where a filter narrows what the sweep already found, a source replaces the search. With caches.entities.registered-targets-only on, the entity cache stops walking every living entity in every world each tick and tracks only the players plus whatever the registered sources hand it.

Called once per tick on the main thread while the snapshot is rebuilt, so it should hand back a live view of a registry the plugin already keeps — not a fresh world search. Entities returned still go through every registered filter, so a source may safely include entities its own filter then hides (pets being the obvious case). A source that throws contributes nothing and is reported once, the same as a broken filter.

Events

SkillDamageEvent

Fully qualified name: com.astralrealms.skill.event.SkillDamageEvent (extends Event, implements Cancellable).

Fired on the main thread right before a skill damages an entity, and only for a skill that declares a damage-type. A skill without one damages as it always has, without firing anything.

The damage type is whatever string the skill's damage-type resolved to — not a Bukkit DamageType, and the engine gives it no meaning of its own. It exists so other plugins can key their own rules (resistances, immunities, shields) off it.

Field

Type

Description

skill

Skill

The skill dealing the damage. getSkillId() is its YAML id.

caster

Entity (nullable)

The entity credited with the cast, or null if it is gone by the time the impact lands.

victim

LivingEntity

The entity about to be damaged.

damageType

String

The resolved damage-type. Never blank — the event is not fired otherwise.

damage

float

The damage about to be dealt. Settable; 0 or less skips the damage exactly as cancelling does.

Cancelling skips the damage entirely: no EntityDamageEvent is fired for it and the target is left untouched. The rest of the impact — knockback, effects — is unaffected, as those are applied independently.

@EventHandler public void onSkillDamage(SkillDamageEvent event) { if ("fire".equals(event.getDamageType()) && hasFireResistance(event.getVictim())) event.setDamage(event.getDamage() * 0.5F); }

Integration pattern

AstralClasses and AstralMobs call the cast methods directly with a skill id and optional custom PlaceholderContainer. They do not re-implement the skill DSL — they rely on this API to execute skills.

Example from a class ability:

import com.astralrealms.skill.SkillAPI; String skillId = "class_fireball"; SkillAPI.service().castAtPos( player, player.getUniqueId(), skillId, player.getEyeLocation(), player.getEyeLocation().getDirection(), null // auto-build container for the player );

For skill DSL details (cast types, targeting, effects, displays, hitbox shapes), see:

  • Skill Files — the overall structure and config schema.

  • Cast Types — projectile, impact, laser, auto-cast, n-cast.

  • Targeting — collision detection and entity selection.

  • Effects — caster/target effect pipeline.

  • Displays — visual rendering (particles, sounds).

Last modified: 25 September 2026