API
What another plugin can do with kill effects: the catalogue, what a player chose, what a weapon carries, playing an effect, the menus, and handing out effects and keys.
KillEffectService reads ExyliaKillEffect's catalogue, sets what a player wears, binds effects onto
weapons, plays an effect on demand, opens the plugin's own screens and hands out effects and crate
keys. One lookup gets you the whole surface.
ExyliaAPI.get(KillEffectService.class).ifPresent(effects ->
effects.chosenEffect(player.getUniqueId())
.ifPresent(effect -> player.sendMessage("Kill effect: " + effect.name())));An empty result from the lookup means kill 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. Playing an effect, the menus, unlocks, keys and
KillEffectPlayEvent arrived in exylia-api v1.133.0 and ExyliaKillEffect 1.4.0: compile against
that tag or a newer one to reach them.
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<KillEffect> 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<KillEffect> 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<KillEffectCategory> categories() | Every category, in the order the tabs are drawn. |
Optional<KillEffectCategory> category(String categoryId) | One category by id. Empty when the file declares none by that id. |
List<KillEffect> 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<KillEffect> 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 kills play. false when no effect goes by that id, and then nothing changed. |
void clearEffect(Player player) | Leaves them with no chosen effect. |
List<KillEffect> 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<KillEffect> boundEffect(ItemStack weapon) | The effect a weapon carries. Empty when it carries none. |
KillEffectBindResult bind(ItemStack weapon, String effectId) | Binds an effect. The item is changed in place, and only after every check has passed. |
KillEffectBindResult 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.
Playing an effect
| Method | What it does |
|---|---|
boolean play(String effectId, Location where, Player killer, @Nullable LivingEntity victim) | Plays an effect at a place, as though a kill had happened there. The region flag, KillEffectPlayEvent and every observer's own visibility still decide; the weapon, the menu choice, the mob list and permission are skipped, because the caller named the effect. victim is the body its steps draw, or null for none. false when no effect goes by that id, it draws nothing, the region silences it or a listener cancelled it. |
boolean playFor(Player killer, LivingEntity victim) | Plays the effect a kill earns, deciding everything a real death decides, in the same order: whether this kind of victim plays effects, the effect the killer's last blow carried, and otherwise their weapon and menu choice under the server's mode. false when the kill earns nothing, or when play would have said false. |
boolean preview(Player viewer, String effectId) | Shows the effect on the server's preview stage, alone, and puts the player back — owning it is not required, which makes it a shop's "try before you buy". false when no stage is set, and the player is told so. |
play is for a cutscene, a boss that dies outside the damage system, or a win that should look like a
kill. playFor is for a plugin that stops a lethal blow before the server calls it a death — a totem,
a duel that ends at the last heart. A death the server does report already plays its effect, so
calling playFor for it as well plays it twice.
Call play on the thread that owns where and playFor on the one that owns the victim: the main
thread on Paper, the region thread on Folia. preview belongs to the thread that owns the viewer.
Menus
| Method | What it does |
|---|---|
void openMenu(Player player) | Opens the effect menu exactly as /killeffect does, for an NPC, a hub item or a shop. The player's row is read first when it is not in memory, so the menu can appear a moment after the call. On a weapon server the player is told the menu is off. Safe from any thread. |
void openCrate(Player player) | Opens the crate exactly as the command and the crate blocks do: the screen that asks how many to open and spends keys. With the crate turned off the player is told so. Safe from any thread. |
Owning, and crate keys
| Method | What it does |
|---|---|
boolean unlock(Player player, String effectId) | Gives a player an effect for good, as winning it from the crate does — ownership without a permission node, counted by mayUse from then on. Nothing is chosen and no item is given: follow it with chooseEffect or token for that. false when no effect goes by that id, the crate already gave it to them, or their row has not arrived yet; nothing changed. |
int keys(UUID player) | How many crate keys a player holds. 0 when they have none or their row has not arrived yet. |
void addKeys(Player player, int keys) | Gives keys, or takes them with a negative number — how a store, a vote or a quest pays out a spin. The count never goes below zero. A player whose row has not arrived yet is left unchanged. |
KillEffectPlayEvent
net.exylia.lib.api.killeffect.event.KillEffectPlayEvent fires once everything else has agreed an
effect should play: it exists, it draws something, and the region allows kill effects. It fires for
the kills the server reports and for play and playFor alike, and the two are not told apart on
purpose — an arena that wants kill effects quiet wants both quiet.
@EventHandler
public void onKillEffect(KillEffectPlayEvent event) {
if (event.getLocation().getWorld().getName().equals("lobby")) event.setCancelled(true);
}| Method | What it returns |
|---|---|
Player getPlayer() | The killer: whose effect it is. |
@Nullable LivingEntity getVictim() | What it plays on. null when a plugin played it at a place rather than on a body. |
String getEffectId() | The effect about to play, normalised. |
Location getLocation() | Where it plays. A copy: changing it moves nothing. |
Cancelling draws nothing and says nothing; the kill itself is untouched. The event is called on the thread that owns the location, which on Folia is a region thread rather than the main one.
KillEffectBindResult
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
KillEffect 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.
KillEffectCategory 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 cosmic
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
HitEffectService is the same contract for effects played on a hit.
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 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. The in-game editor and the administrative writes — revoking unlocks, binding crate blocks — 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