Astral Realms Documentation Help

Configuration

AstralSkill's main configuration file is config.yml in the plugin data folder (plugins/AstralSkill/). On first enable, saveDefaultConfig() creates it with defaults. The plugin loads skills from a separate folder (see Skill Folder below).

config.yml: Skill Service

skill-service: threads: 4 warn-threshold-ms: 5 warn-min-overrun-percent: 1.0 warn-unreachable-aim: true

The skill-service block tunes the skill update loop — the background thread pool that runs active skills every millisecond.

Field

Type

Default

Description

threads

int

max(1, availableProcessors() / 2)

Number of update shards (concurrent threads). Each shard owns a fixed-rate update loop running at 1ms per tick and a subset of active skills (partitioned by entity UUID hash). The shard count is fixed at construction and is not changed by /skill reload. Increasing this value requires a server restart.

warn-threshold-ms

long

5

Duration (milliseconds) that a tick must exceed to be counted toward the console warning. Only gates the WARN log line emitted every 10 seconds when the over-budget threshold is hit; the /skill metrics command counts all ticks exceeding the hard 1ms budget regardless of this setting. Must be ≥ 1ms.

warn-min-overrun-percent

double

1.0

Minimum percentage of ticks in a 10-second window that must exceed warn-threshold-ms to trigger a console warning. Set to a high value to suppress warnings for occasional over-budget ticks; lower values catch more noise. Only affects console output; metrics always track all overruns.

warn-unreachable-aim

boolean

true

Whether to log a WARN when a projectile skill cast through a position cannot be aimed to actually reach it (out of range, too much drag, …) and the cast is therefore aborted. Set false if callers routinely aim at unreachable points and the noise is not useful. Read once at construction, so changing it needs a full restart, not just /skill reload. The key is not in the shipped file; add it yourself to turn it off.

Update threads and shards

Each shard runs as a single daemon thread named AstralSkill-Skills-<n> (where <n> is the shard index, 0-indexed). Skills are routed to shards by their author's entity UUID hash so each author's active skills always run on the same shard, keeping each shard's work thread-confined and eliminating lock contention.

The shard count is determined once when the plugin enables and remains fixed for the server's lifetime — even if you call /skill reload, the existing shards continue running and no new ones are created. To change the shard count, restart the server.

Performance metrics

/skill metrics (permission skill.metrics) displays per-shard performance counters across all time:

  • Total ticks: how many times each shard's update loop has run.

  • Over budget: count of ticks exceeding the hard 1ms tick budget (regardless of warn-threshold-ms).

  • Peak duration: the longest single tick observed on that shard.

  • Started / finished: total skills activated and deactivated on each shard.

The console warning (emitted every 10 seconds when conditions are met) is tuned by warn-threshold-ms and warn-min-overrun-percent, but the metrics counters always measure against the fixed 1ms budget, giving you the full picture without noise.

config.yml: Hit-detection caches

Hit detection reads two caches, both rebuilt on the main thread, so they cost tick time on every server they run on — whether or not anything reads them.

Cache

What it holds

Read by

caches.chunks

A ChunkSnapshot of every loaded chunk, per world

projectile skills, for block collision

caches.entities

Every living entity's hitbox, refreshed each tick (or only registered ones)

projectile and impact skills, for entity hit detection

caches: chunks: enabled: true groups: [] entities: enabled: true groups: [] registered-targets-only: false

Each cache runs only if both conditions hold:

  1. A skill that reads it is loaded. Re-checked after every load and /skill reload — no projectile skill means no chunk snapshots are captured at all, and no projectile or impact skill means the per-tick entity sweep never runs. Nothing to configure: a cache nothing can read is never built.

  2. This server's group passes the filter. An empty or absent groups list means every group; otherwise the group reported by AstralCore must be listed (case-insensitive). enabled: false switches the cache off everywhere.

Field

Type

Default

Description

caches.chunks.enabled

boolean

true

Capture chunk snapshots. Off also frees their heap — but projectiles then fly straight through terrain.

caches.chunks.groups

List<String>

[]

Server groups the chunk cache runs on. Empty = every group.

caches.entities.enabled

boolean

true

Sweep entity hitboxes. Off means skills hit nothing on this server.

caches.entities.groups

List<String>

[]

Server groups the entity cache runs on. Empty = every group.

caches.entities.registered-targets-only

boolean

false

See below.

enabled is read as a boxed boolean on purpose: a section that is present but does not spell the key out — or a config predating it — defaults to on, which a primitive would silently turn into off.

Tracking only what skills can hit

The entity cache's default sweep walks every living entity in every world, every tick. On a busy server that is thousands of vanilla mobs, and it is the single largest cost AstralSkill has. With registered-targets-only: true the cache instead tracks only the players plus the entities other plugins have handed over as skill targets (AstralMobs' mobs, for instance) — on a server whose skills only ever fight one plugin's mobs, that turns a walk of every entity into a walk of the few dozen that can actually be hit.

The trade is that skills then pass straight through anything unregistered, vanilla mobs included. The setting is ignored while no plugin has registered a source, keeping the full sweep rather than leaving skills able to hit players only.

config.yml: Damage indicators

Floating damage numbers shown to a player for the damage they deal. Rendered as per-player packet text displays in front of the attacker's face, cleaned up after ~600 ms.

damage-indicators: enabled: true groups: [] immune: "<bold><red>Immune" damaged: "<bold><white>🗡 -%amount%" critical: "<bold><red>✳ -%amount%"

Field

Type

Default

Description

enabled

boolean

true

Master switch.

groups

List<String>

[]

Server groups indicators are shown on. Empty = every group; matching is case-insensitive — so a lobby and a dungeon can share one jar and only the dungeon shows numbers.

immune

ComponentWrapper

<bold><red>Immune

Shown when the victim is invulnerable or the final damage is 0.

damaged

ComponentWrapper

🗡 -%amount%

The ordinary hit line. %amount% is the final damage.

critical

ComponentWrapper

✳ -%amount%

The critical-hit line.

Indicators are only produced when the victim is not a player and the attacker is one. The attacker is taken from the damage source's causing entity, falling back to its direct entity — so a player's melee hit, their skill and their own projectile all credit the player (for an arrow, the direct entity is the arrow).

Skill Folder

Every YAML file under <plugin-data-folder>/skills/ is loaded as a skill. Folders are scanned recursively, so nesting is allowed — skills/knight/attack-1/cast.yml, skills/mobs/tower/boss/, etc. are all valid. Folder organization is purely for humans; it has no semantic meaning to the plugin.

On enable and on /skill reload, SkillService.load() walks the skill folder and calls configurationManager().loadFolder(skillFolder, WrappedSkill.class) to parse each file into a WrappedSkill, deserializing the YAML into the skill's concrete Skill type (e.g. ProjectileSkill, AreaSkill) via the configured type adapter.

Skill identity and collisions

Each skill file must declare an id field at the top level:

id: knight-attack-1-cast skill-root: cast type: n_cast params: # ...

The id field is the map key — skills are indexed by their id, not by filename. This means two skill files with the same id will silently collide: whichever file is loaded last will overwrite the earlier one, with no warning logged.

To avoid collisions:

  • Keep filenames aligned with their skill ids (e.g. knight-attack-1-cast.yml for id: "knight-attack-1-cast").

  • Use namespace/class prefixes in skill ids across your network (e.g. knight-attack-1-cast, archer-volley-burst, tower-guardian-smash).

Skill initialization

After loading all skill files, each is initialized via its init() method, which:

  1. Parses the effect specs (under skill-root and target phases) into effect instances via EffectFactory.

  2. Parses the params section according to the skill type's expected fields.

  3. Validates that all referenced effects, targeting strategies, and other dependencies resolve correctly.

If initialization fails for a skill, an error is logged with the skill id and exception message, but the plugin continues loading the remaining skills. The failed skill will be unavailable at runtime.

Reload Behavior

/skill reload (permission skill.reload) invokes SkillService.smartLoad(), which implements a graceful reload cycle:

  1. If skills are already running, smartLoad() deactivates all active skills asynchronously.

  2. Once all skills are deactivated, all existing shard threads are stopped and the skill registry is cleared.

  3. The folder is re-scanned, each skill is re-parsed and re-initialized, and each shard's update loop is restarted.

  4. The console prints the count of skills loaded and initialized.

If no skills are running (e.g., this is the first load on plugin enable), load() is called directly without the shutdown sequence.

Importantly:

  • The shard count is never changed — the same threads continue running before and after reload.

  • Running skill instances are deactivated — they stop updating and their effects are applied to targets before they're removed.

  • Configuration changes take effect immediately — reloading picks up new/edited/deleted skill files.

Plugin Disable

When the server stops or the plugin is disabled, onDisable() shuts down gracefully:

  1. All shard threads are stopped (the ScheduledFuture for each shard's update loop is cancelled).

  2. The chunk and entity bounding-box caches are cleared.

  3. The executor services are shut down.

Any active skills are terminated without running their target-effect phases.

Last modified: 25 September 2026