Content generated with AI — it may contain mistakes.

Referencedev

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.

Adding it to your project

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.

Nothing here checks permission

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

MethodWhat 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.

MethodWhat 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

MethodWhat 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

MethodWhat 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.

The thread that owns the place

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.

MethodWhat 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

MethodWhat 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);
}
MethodWhat 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.

ValueMeaning
BOUNDThe weapon now carries the effect.
UNBOUNDThe weapon no longer carries an effect.
NOT_A_WEAPONThe item is not something an effect can be bound to at all.
WRONG_WEAPONA weapon, but not one this effect declares itself to fit.
UNKNOWN_EFFECTNo effect goes by that id, which is also what a stale id reads as.
ALREADY_BOUNDThe weapon already carries an effect; unbind before binding another.
NO_EFFECTThe weapon carries no effect, so there was nothing to take off.
FAILEDRefused for a reason this contract does not name.
Write a default branch

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