API
What another plugin can do with hit effects: the catalogue, what a player chose, and what a weapon carries.
HitEffectService reads ExyliaHitEffect's catalogue, sets what a player wears, and binds effects onto
weapons. One lookup gets you the whole surface.
ExyliaAPI.get(HitEffectService.class).ifPresent(effects ->
effects.chosenEffect(player.getUniqueId())
.ifPresent(effect -> player.sendMessage("Hit effect: " + effect.name())));An empty result from the lookup means hit effects are not part of this server, rather than that something failed.
The artifact, the repository and the plugin.yml line are the same for every Exylia plugin and live
on the public API page.
Everything is an id
Effects and categories are named by the keys effects.yml declares. Ids are normalised — lowercase,
letters, digits and underscores — and matched without regard to case, so the id a shop stored last
month still resolves after the owner retyped it in a different case.
A plugin calling this has already decided the player may have the effect: a crate opened, a rank
bought, a reward claimed. Being overruled by a permission node the buyer has not been given yet is not
what it asked for. mayUse is there for callers that do want the check, on their own terms.
The catalogue
| Method | What it does |
|---|---|
List<HitEffect> effects() | Every effect the server declares, in file order. A snapshot — fine for building a shop page, wasteful inside a loop that could ask for one id. |
Optional<HitEffect> effect(String effectId) | One effect by id. Empty when the file declares none by that id. |
boolean effectExists(String effectId) | Whether an effect exists. The cheap form of effect, for validating what a player typed or what a config named. |
List<HitEffectCategory> categories() | Every category, in the order the tabs are drawn. |
Optional<HitEffectCategory> category(String categoryId) | One category by id. Empty when the file declares none by that id. |
List<HitEffect> effectsIn(String categoryId) | The effects in a category, in the order they are drawn. Empty when the category does not exist. |
boolean mayUse(Player player, String effectId) | Whether a player may use an effect: its own permission, every effect at once, or its whole category. An unknown id is never allowed, so a stale id reads as locked rather than free. |
What a player chose
Everything taking a UUID reads what is held in memory for an online player. Somebody whose row has
not arrived yet answers as though they chose nothing, rather than blocking the caller on a database.
| Method | What it does |
|---|---|
Optional<HitEffect> chosenEffect(UUID player) | The effect they picked in the menu. Empty when they chose none. |
boolean chooseEffect(Player player, String effectId) | Sets the effect their hits play. false when no effect goes by that id, and then nothing changed. |
void clearEffect(Player player) | Leaves them with no chosen effect. |
List<HitEffect> favourites(UUID player) | The effects they starred, in the order they starred them — their order, not the catalogue's. Empty when they have none or are not loaded. |
boolean isFavourite(UUID player, String effectId) | Whether they starred an effect. |
boolean toggleFavourite(Player player, String effectId) | Stars an effect, or unstars it when it is already starred. false when no effect goes by that id. |
void clearFavourites(Player player) | Empties their favourites. |
What a weapon carries
| Method | What it does |
|---|---|
boolean weaponBindingEnabled() | Whether effects bound to weapons are played on this server. Where they are not, binding writes a value nothing will ever play — so ask before offering it. |
boolean playerChoiceEnabled() | Whether the effect a player picked in the menu is played on this server. |
boolean fitsWeapon(String effectId, Material weapon) | Whether the effect accepts that kind of item. An effect that declares no weapons fits every weapon. |
Optional<HitEffect> boundEffect(ItemStack weapon) | The effect a weapon carries. Empty when it carries none. |
HitEffectBindResult bind(ItemStack weapon, String effectId) | Binds an effect. The item is changed in place, and only after every check has passed. |
HitEffectBindResult unbind(ItemStack weapon) | Takes the effect off and gives nothing back, for callers that hand the token back themselves. Changed in place. |
Optional<ItemStack> token(String effectId, Player viewer) | The item a crate or a shop hands over: applying it to a weapon binds the effect. Drawn for that viewer, because name and lore carry placeholders. Empty when no effect goes by that id. |
ItemStack remover(Player viewer) | The remover item. Applying it to a weapon takes the effect off and hands the token back. |
bind and unbind write on the caller's own copy of the stack. Pass a copy rather than an item still
sitting in an open inventory view: what a player sees is redrawn when the stack is set back.
HitEffectBindResult
Every check runs before anything is written, so any result other than BOUND or UNBOUND means both
items were left exactly as they were. isSuccess() is true for those two and nothing else.
| Value | Meaning |
|---|---|
BOUND | The weapon now carries the effect. |
UNBOUND | The weapon no longer carries an effect. |
NOT_A_WEAPON | The item is not something an effect can be bound to at all. |
WRONG_WEAPON | A weapon, but not one this effect declares itself to fit. |
UNKNOWN_EFFECT | No effect goes by that id, which is also what a stale id reads as. |
ALREADY_BOUND | The weapon already carries an effect; unbind before binding another. |
NO_EFFECT | The weapon carries no effect, so there was nothing to take off. |
FAILED | Refused for a reason this contract does not name. |
Nothing produces FAILED today. It is there so that a reason added inside the plugin later arrives as
a value you can handle, rather than as an exception thrown at whoever happened to ask — which on a
live server is a third-party plugin with no way to recover. Handle the results you care about, and let
everything else fall through one branch.
The records
HitEffect carries id(), categoryId(), name(), icon(), description(), priority() and
permission() — the description in the file's own words, the icon as the file names it, and the
permission that grants this one effect, so a shop can sell it without knowing how the node is built.
HitEffectCategory carries id(), name(), icon(), priority() and permission(). Its
permission grants every effect in it at once, which is what lets a rank be sold as "every infernal
effect" instead of as a list of ids that grows whenever the owner adds one.
Both are snapshots of what the file said when you asked. A reload replaces the whole catalogue, so an effect held across one is the old description of an id that may no longer exist — look it up again rather than keeping it.
The twin plugin
KillEffectService is the same contract for effects played on a kill.
The two are deliberately identical in shape: same method names, same order, same results. An
integration written against one ports to the other by changing the types.
What it does not expose
What an effect actually draws — the particle steps the sequence engine compiles — is not here. It is this version's implementation of the effect, changes whenever the owner edits a line, and means nothing outside the plugin that plays it. Menu openers, the in-game editor and administrative writes are left out for the same reason: a public contract cannot be broken later, so it holds what an integration genuinely needs and nothing that only made sense inside one version.
If something you need is missing, ask on Discord.
Something missing on this page? Tell us on Discord