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
The skill-service block tunes the skill update loop — the background thread pool that runs active skills every millisecond.
Field | Type | Default | Description |
|---|---|---|---|
| int |
| 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 |
| long |
| Duration (milliseconds) that a tick must exceed to be counted toward the console warning. Only gates the |
| double |
| Minimum percentage of ticks in a 10-second window that must exceed |
| boolean |
| Whether to log a |
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 |
|---|---|---|
| A |
|
| Every living entity's hitbox, refreshed each tick (or only registered ones) |
|
Each cache runs only if both conditions hold:
A skill that reads it is loaded. Re-checked after every load and
/skill reload— noprojectileskill means no chunk snapshots are captured at all, and noprojectileorimpactskill means the per-tick entity sweep never runs. Nothing to configure: a cache nothing can read is never built.This server's group passes the filter. An empty or absent
groupslist means every group; otherwise the group reported by AstralCore must be listed (case-insensitive).enabled: falseswitches the cache off everywhere.
Field | Type | Default | Description |
|---|---|---|---|
| boolean |
| Capture chunk snapshots. Off also frees their heap — but projectiles then fly straight through terrain. |
|
|
| Server groups the chunk cache runs on. Empty = every group. |
| boolean |
| Sweep entity hitboxes. Off means skills hit nothing on this server. |
|
|
| Server groups the entity cache runs on. Empty = every group. |
| boolean |
| 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.
Field | Type | Default | Description |
|---|---|---|---|
| boolean |
| Master switch. |
|
|
| 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. |
| ComponentWrapper |
| Shown when the victim is invulnerable or the final damage is |
| ComponentWrapper |
| The ordinary hit line. |
| ComponentWrapper |
| 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:
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.ymlforid: "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:
Parses the effect specs (under
skill-rootand target phases) into effect instances viaEffectFactory.Parses the
paramssection according to the skill type's expected fields.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:
If skills are already running,
smartLoad()deactivates all active skills asynchronously.Once all skills are deactivated, all existing shard threads are stopped and the skill registry is cleared.
The folder is re-scanned, each skill is re-parsed and re-initialized, and each shard's update loop is restarted.
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:
All shard threads are stopped (the
ScheduledFuturefor each shard's update loop is cancelled).The chunk and entity bounding-box caches are cleared.
The executor services are shut down.
Any active skills are terminated without running their target-effect phases.