Skip to main content

Events

Lasers-Enigma publishes custom Bukkit events so a companion plugin can react to what happens in a puzzle, and refuse what it cannot accept, without either plugin knowing about the other.

Two families:

Base classMeaningCancellable
ABeforeActionEventAbout to happen โ€” a veto channelYes
AAfterActionEventHas happened โ€” informationalNo

Both extend AEvent, which extends Bukkit's Event. Register a listener as you would for any Bukkit event.

โš ๏ธ Never make a permission decision on an after-event. The decision has already been taken and the action already performed; the actor is carried for logging and attribution only.

Area shape changesโ€‹

AreaBoundsChangedEvent (after)โ€‹

Announces every change of an area's shape โ€” a resize, and the growth of the surviving area when two areas are merged.

It carries the area id, the actor (which may be null), and the old and new corners.

โš ๏ธ Resolve the area by its id or by its old corners โ€” never with a lookup that loads it. The area may be unloaded when the event fires: a merge deliberately releases both areas before reshaping the data, so a loading lookup would re-materialise something the plugin has just released.

Replaces AreaResizedEvent, which required a loaded Area and therefore could never be fired for a merge. That is why merges used to change an area's shape silently, leaving companion plugins holding the pre-merge cuboid.

PlayerTryToResizeAreaEvent (before)โ€‹

Fired before an area is resized. Carries the area, the old and new bounds, and the player requesting it, so a listener can decide per player rather than for everyone.

PlayerTryToMergeAreasEvent (before)โ€‹

Fired before two areas are merged. Carries both area ids and their cuboids, so a plugin can refuse a combination it cannot represent โ€” for instance one where both areas carry something of its own and only one can survive.

Player presenceโ€‹

PlayerEnteredAreaBoundsEvent (after)โ€‹

Fires whenever a player's position enters an area, whatever they are doing there.

Use this one for anything about presence: giving or taking items, granting or revoking rights, showing a message on arrival.

โš ๏ธ Do not use PlayerEnteredAreaEvent for that. It means "started playing this puzzle" and stays silent for edition mode, creative and spectators, so those never count as plays. A guard written on it is silently dead for exactly the builders it was meant to cover.

PlayerLeftAreaEvent (after)โ€‹

Fires unconditionally when a player leaves an area โ€” including for builders, unlike the entered-area event above.

Elevatorsโ€‹

PlayerTryToRefreshElevatorSnapshotEvent (before)โ€‹

Fired before an elevator's cage snapshot is re-captured, so the operation can be vetoed.

Achievementsโ€‹

AchievementUnlockedEvent (after)โ€‹

Fired the moment a player unlocks an achievement, whatever caused it โ€” their own play, an administrator's grant, a companion plugin's push, a recognition from their history.

It carries the achievement id (namespace:name), the player's UUID and the AwardCause that explains why they now hold it. It is the one event in this catalogue that lives in the public API package eu.lasersenigma.achievement.api; the rest of that contract is on Use-API โ€บ Achievements.

Fired synchronously, on the main thread, right after the unlock is recorded. Two things follow from that. A backfill at join fires one event per achievement in the same tick, so a listener that hands out a reward must be safe to run several times in a row. And the unlock is already recorded when the event fires, so a listener that throws costs the player nothing.

โš ๏ธ The player may be offline. An administrator can hand an achievement to someone who is not connected, and a companion plugin can recognise a past fact at any time. Resolve the UUID, do not assume a Player.

Puzzle mechanicsโ€‹

AreaMechanicFactsCreditedEvent (after)โ€‹

Fired when something notable has just happened to the light in a puzzle and one player's own action is what brought it about โ€” white light taken apart by a prism, white light taken apart by a prism, and the like.

It carries the area, the UUID of the credited player and the set of MechanicFacts that have just appeared, never empty. The credit is already decided when it fires, and decided conservatively: a player is named only when there is no honest doubt about who caused the change. Nothing here says whether they went on to win โ€” this event is about doing, not about finishing.

โš ๏ธ It fires from inside the per-tick area update, on the main thread, at most once per area per tick. A listener must be as cheap as anything else in that loop.

Progress changed administrativelyโ€‹

PlayerProgressResetEvent (informational)โ€‹

Informational like an after-event, but it predates the two families above and extends Bukkit's Event directly rather than AAfterActionEvent โ€” listen for it the same way, just do not expect the base class.

Fired after an administrator changes a player's progress outside of a normal win: a reset through /lasersenigma stats resetProgress โ€ฆ or the area statistics menu's clear button, or a grant of solved areas through ProgressGrantService. Consumers that cache per-player progress โ€” portal completion displays, for instance โ€” use it to invalidate what they hold.

It carries the player's UUID, null for a server-wide reset (isAllPlayers()), plus two fields that let a listener tell the cases apart instead of guessing:

FieldValuesUse
getCause() โ€” ProgressChangeCauseRESET (progress erased) ยท GRANT (progress handed out)The two used to be indistinguishable. A companion plugin awarding achievements has to treat them oppositely: a grant may unlock a threshold achievement, a reset must never do so.
getScope() โ€” ProgressChangeScopePLAYER_FOR_AREA ยท PLAYER_FOR_AREAS ยท ALL_PLAYERS_FOR_AREA ยท ALL_AREAS_FOR_PLAYER ยท ALL_PLAYERS_ALL_AREASSizes the reaction: wiping one area's records is not the same event as starting a player โ€” or the whole server โ€” from scratch.

โš ๏ธ A server-wide reset fires once, not once per player. The UUID is null in that case; a listener that dereferences it without checking isAllPlayers() breaks on exactly the operation it most needs to handle. The affected player may also be offline.

Removed in version 11โ€‹

RemovedWhat to use instead
AreaResizedEventAreaBoundsChangedEvent (see above)
PlayerPermissionsCheckEventNothing โ€” it was never fired, so no listener on it ever ran
Permission#hasPermission(Player)Permission#hasPermission(CommandSender), which is source-compatible: a Player argument resolves to it unchanged

The first two are binary-incompatible changes: recompile your plugin against version 11.