For Developers/API Events·匠

API Events

Cultivation exposes 370 events across 51 *Events classes in plugin.siren.API: 153 cancellable Pre* events fired before a change, and 217 post-events fired once it is committed. Nearly every mechanic in the mod is exposed twice: once before the change, where a listener can veto it or re-tune its numbers, and once after.

Every class follows the conventions documented on CultivationEvents, so learning one teaches you all 51. The tables below are generated from the Java sources in plugin.siren.API: every event, its cancellability, and every getter and setter on it is read from the code, so the counts cannot drift from the jar.

Four names appear twice

PreXpGainEvent, XpGainEvent, PreLevelUpEvent and LevelUpEvent exist in both BodyTemperingEvents and FistEvents. Always qualify them by class - FistEvents.onXpGain(...), not a bare XpGainEvent import.

Pre and post

A Pre* event extends plugin.siren.API.CancellableEvent and fires before anything is applied. It gives you two powers:

A post-event is a plain Java record. It cannot be cancelled and is purely a notification; by the time it fires, the change is already visible on the component.

CancellableEvent exposes exactly two methods:

MethodReturnsDescription
isCancelled()booleanWhether some listener has vetoed this operation.
setCancelled(boolean cancelled)Vetoes, or with false un-vetoes, the operation.

Every listener runs, even after one cancels. A later listener is free to call setCancelled(false) and let the operation through, so plugin load order decides who wins a disagreement. If you only want to observe, listen for the post-event instead.

Not every pre-event has a post twin of the same name, and not every post-event has a pre-event: a number of them are pure notifications with nothing to veto (SeasonEvents, DepthsEvents, SecretRealmEvents are post-only by design). Within each table below a pre-event is listed directly before the post-event it guards, and the Cancellable column says which rows are the Pre* events.

Registering and threading

Each event has a matching on* registration method on its class - 370 of them, one per event - taking a Consumer of the event type:

CultivationEvents.onPreQiGain(event -> event.setAmount(event.amount() * 1.25F));
DaoEvents.onPathChange(event -> myPlugin.announce(event.player(), event.newPath()));

Registration is a plain CopyOnWriteArrayList.add, safe from any plugin's setup() in any load order. There is no unregister - listener lifetime is server lifetime.

Listeners are invoked synchronously on the world thread of the player the event happened to. Component reads through the usual accessors are safe inside a listener, but do not block, and hop threads yourself (for example CompletableFuture.runAsync(task, otherWorld)) before touching anything that lives on another world. A listener that throws is logged and skipped, so one broken addon can break neither the mod's own systems nor the other addons listening to the same event.

Many classes identify players by java.util.UUID instead of PlayerRef, because their operations routinely touch offline players - a kicked member, a duel that outlived a session, a seller whose listing sold while they were away. BountyEvents, DaoDuelEvents, DepthsEvents, DuelEvents, LegacyEvents, MarketEvents, OathEvents, PagodaEvents, PartyEvents, RivalEvents, RogueEvents, SectEvents, SoulEscapeEvents, StoreBenefitEvents, TeaEvents, TreasureEvents, WagerEvents and WeiqiEvents do so throughout; DwellingEvents, FormationEvents, PartnerEvents and SermonEvents mix UUID identifiers with PlayerRef players. Resolve a UUID with Universe.get().getPlayer(uuid) and check isValid().

A handful of classes carry extra constraints, all stated in their own javadoc:

ClassFires onWhile holdingWhat to do
SectEventsThe world thread of the player concerned.SectManager's lock.Read what you need, hand it off, return. Never call SectManager's mutating methods.
MarketEventsThe world thread of the player concerned.AuctionManager's lock.The same: never call back into AuctionManager's mutators.
BountyEventsThe world thread of whatever caused the event.BountyManager's monitor.Do not block, do not re-enter BountyManager, and never reach for an addon monitor - the lock order is SeasonManager, then your addon, then BountyManager.
SeasonEventsWhichever world thread reached the season pump first - server-wide, not one player's world.SeasonManager's monitor.Do not block; hop per player before touching components. A boot-time season rollover reaches no addon listener, so ask getCurrentSeasonId() instead.
WagerEventsThe betting player's command thread for the pre-event and WagerPlacedEvent; the tournament pump for the two market-resolution events.DaoDuelTournamentManager's monitor on WagerMarketResolvedEvent and WagerMarketVoidedEvent.No locking and no cross-manager calls on those two; copy what you need to your own queue.
StoreBenefitEventsThe remote checker thread - not a world thread.Nothing.Find the player, check isValid(), hop onto their world thread before touching a component.
ProfileEventsThe owning world thread, outside any ticking system.Nothing.Unusually, writing components directly through the Store is safe here.
BodyTemperingEvents, FistEventsInside the damage pipeline, on the world thread that owns the entity being hit.Nothing.Keep it cheap; never write to the Store.
LifespanEventsLifespanSystem's tick or a command handler.Nothing.Read through the accessor; queue writes on a CommandBuffer.

The WagerEvents javadoc also records that DuelEvents.DuelEndEvent, DaoDuelEvents.DaoDuelEndEvent and the CelestialEvents pair are fired from inside synchronized manager methods, so the same rule applies to listeners on those.

Finding an event

51 classes in 8 groups:

ClassCoversEventsCancellable
CultivationEventsCultivation core4621
BodyTemperingEventsBody tempering63
FistEventsFist arts42
LifespanEventsLifespan62
MeridianEventsMeridian injuries53
SoulEscapeEventsNascent Soul Escape83
LegacyEventsLegacy21
ProfileEventsCultivation profiles73
DaoEventsDao and karma146
DaoComprehensionEventsDao comprehension115
TechniqueEventsTechniques157
CombatDepthEventsCombat depth42
OathEventsHeavenly oaths84
ItemEventsItems126
AlchemyEventsAlchemy52
ForgingEventsForging21
TalismanEventsTalismans63
WeaponSpiritEventsWeapon spirits63
BeastEventsBeasts2010
BreedingEventsBeast breeding42
SectEventsSects4018
WarEventsWars94
GuardianEventsSect guardians41
SermonEventsDao sermons41
MeritEventsMerit31
PartyEventsParties51
PartnerEventsPartnered cultivation31
MarketEventsAuction House and Traveling Merchant123
BountyEventsBounty board42
DuelEventsDuels83
DaoDuelEventsDao duels32
WagerEventsTournament wagers41
TeaEventsTea ceremony41
WeiqiEventsWeiqi72
FormationEventsFormations84
DwellingEventsDwellings126
CelestialEventsCelestial events31
TideEventsBeast tides63
WorldBossEventsCalamity beasts41
RiftEventsVoid rifts51
RogueEventsRogue cultivators31
RivalEventsWandering rivals21
SecretRealmEventsSecret realms20
DepthsEventsSecret realm depths40
PagodaEventsTrial Pagoda31
TreasureEventsTreasure and ruins21
ArrayEventsTransmission array21
QuestEventsWandering-NPC quests51
CampaignEventsNarrative campaign31
SeasonEventsSeasons20
StoreBenefitEventsStore benefits30

Every class heading below has a pinned anchor of the form #<name>-events, shown in the first column above.

Progression, ladders and saves

The ladder itself, the two bare-body ladders beside it, injuries, a second life, and the saves a player keeps.

Cultivation core

plugin.siren.API.CultivationEvents - 46 events (21 cancellable) covering Qi, meditation, rituals, breakthroughs, advancements, demotions, tribulations, the Heart-Devil Trial, Qi Deviation, the Dream Trial, the Inner Demon duel, the Marrow-Cleansing Rite, Ascension, Reincarnation, races, Life-Bound Treasures, the skill tree and player kills. Player-facing docs: Cultivation Realms, Qi Gathering, Tribulations, Races, Life-Bound Treasures, Skill Tree, Ascension, Reincarnation, Qi Deviation, Dream Trial, Inner Demon Rival Duel.

Enums declared here:

EventCancellableFiresExposes
PreQiGainEventYesQi is about to be banked, for every source - meditation ticks, duel payouts, admin grants - after all the mod's own multipliers.ref(), player(), amount() / setAmount(float), baseAmount() (what it was before any listener touched it).
QiGainEventNoQi was banked.ref(), player(), amount(), totalQi().
PreMeditationStartEventYesA player is about to sit down to meditate. Cancel to keep them on their feet.ref(), player().
MeditationStartEventNoA player sat down to meditate.ref(), player().
PreMeditationStopEventYesA player is about to stop meditating. Cancel to make a ritual unbreakable, or to suppress the movement-cancel.ref(), player(), reason().
MeditationStopEventNoA player stopped. Any ritual penalty has already been applied.ref(), player(), reason().
PreRitualStartEventYesA timed ritual is about to begin. Cancel and the player keeps meditating (banking Qi) but never enters the ritual.ref(), player(), type(), requiredSeconds() / setRequiredSeconds(float).
RitualStartEventNoThe tick that first accrued ritual progress.ref(), player(), type().
PreBreakthroughEventYesA realm breakthrough is about to complete. Cancel to hold them at Peak stage - progress resets and they may retry.ref(), player(), fromRealm(), toRealm(), qiCost() / setQiCost(float).
BreakthroughEventNoThe breakthrough completed; their stage is EARLY.ref(), player(), newRealm().
PreAdvancementEventYesA sub-stage advancement is about to complete. Cancel to hold them where they are.ref(), player(), realm(), fromStage(), toStage(), qiCost() / setQiCost(float).
AdvancementEventNoThe advancement completed.ref(), player(), realm(), newStage().
PreDemotionEventYesA player is about to lose a sub-stage for abandoning a ritual. Cancel and they walk away free - their banked Qi survives too.ref(), player(), realm(), oldStage(), newStage(), wasBreakthrough().
DemotionEventNoThe demotion landed; banked Qi is wiped and the granting skill points revoked.ref(), player(), realm(), oldStage(), newStage(), wasBreakthrough().
PreTribulationStrikeEventYesTribulation lightning is about to strike a mid-ritual cultivator. Cancel to spare them entirely - no bolt, no thunder, no damage. Set damage to 0 to let the bolt fall harmlessly.ref(), player(), damage() / setDamage(float) (post-lethality-cap, pre-armor), breakthroughRitual().
TribulationStrikeEventNoThe bolt fell.ref(), player(), damage(), breakthroughRitual().
PreHeartDevilTrialEventYesA Heart-Devil pulse is about to torment a mid-ritual cultivator. Set the drain to 0 to make the apparition purely cosmetic.ref(), player(), composureDrain() / setComposureDrain(float), leanFraction() (0-1), pulseIndex() (from 0), breakthroughRitual().
HeartDevilTrialEventNoA pulse resolved.ref(), player(), composureRemaining(), deviated(), breakthroughRitual().
PreQiDeviationEventYesComposure has shattered and Qi Deviation (走火入魔) is about to be applied. Cancel to spare the penalty - the ritual still ends.ref(), player(), demotes() / setDemotes(boolean), qiLoss() / setQiLoss(float), breakthroughRitual().
QiDeviationEventNoThe penalty landed. Exactly one of demoted() / qiLost() carries it.ref(), player(), demoted(), qiLost(), breakthroughRitual().
PreRaceChangeEventYesA race is about to change. Cancel and the race menu simply reports no change.ref(), player(), oldRace(), newRace(), adminOverride().
RaceChangeEventNoThe race changed. Not fired when an admin sets the race a player already has.ref(), player(), oldRace(), newRace(), adminOverride().
PreSkillUnlockEventYesA skill tree node is about to be unlocked. Cancel and their points are not spent.ref(), player(), node(), pointCost() / setPointCost(int).
SkillUnlockEventNoThe node is unlocked; points spent and modifiers re-applied.ref(), player(), node().
PreRespecEventYesA skill tree respec is about to run.ref(), player(), refundedPoints() / setRefundedPoints(int).
RespecEventNoEvery node was cleared and the points handed back.ref(), player(), refundedPoints().
PreLifeBoundLevelUpEventYesA Life-Bound Treasure is about to level. Cancel to hold it at its current level - the XP is still banked.owner(), item() (the stack before the level-up is written), oldLevel(), newLevel().
LifeBoundLevelUpEventNoThe treasure leveled.owner(), item() (already updated), newLevel().
PreAscensionEvent v0.8.0YesA cultivator is about to begin the Ascension capstone. Cancelling keeps them at the peak untried — the one hook a server needs to gate the ladder's ending behind something of its own: a quest, an item, a date.ref(), player(), prestiged() (whether they intend to reset and climb again).
AscensionEvent v0.8.0NoAn Ascension was survived. On a prestige ending the reset to the first realm has already happened by the time this fires. Deliberately not a BreakthroughEvent with a special realm — an ascension is not a breakthrough, and a listener treating it as one would credit the wrong thing.ref(), player(), ascensionCount(), prestiged().
AscensionFailedEvent v0.8.0NoAn attempt ended in failure.ref(), player(), abandoned() (walked away, rather than killed).
PreTribulationOmenEventYesA ritual's Storm Omen roll is about to be decided, on the ritual's first tick. Cancel to force no omen regardless of what the sky rolled (no message, no latch; the opt-in request stays armed); setStorm forces the outcome either way.ref(), player(), naturalStormDetected(), storm() / setStorm(boolean), breakthroughRitual().
TribulationOmenEventNoA ritual's Storm Omen was decided on its first tick. storm is whether it latched: true arms the harder, better-rewarded variant for the rest of this ritual attempt only. Fires only when the roll actually happened (the player opted in, or Tribulation-Storm-Omen-Opt-In-Required is false).ref(), player(), storm(), breakthroughRitual().
PreDreamTrialEventYesA Dream Trial pulse is about to test a cultivator inside the Hollow Mirror. Cancel to skip the pulse entirely; adjust setComposureDrain to change how hard it bites (0 makes the reflection purely cosmetic).ref(), player(), composureDrain() / setComposureDrain(float), pressure(), pulseIndex().
DreamTrialEventNoThe Dream Trial's Hollow Mirror tested a cultivator mid-attempt. composureRemaining is what is left after this pulse's drain (0 when it broke), broken is true only on the pulse that shattered composure and failed the attempt, and pressure is the 0-1 fraction that scaled the drain.ref(), player(), composureRemaining(), broken(), pressure().
PreInnerDemonTrialEventYesAn Inner Demon Rival Duel pulse is about to strike a mid-duel cultivator. Cancel to skip the pulse entirely; adjust setComposureDrain to change how hard it bites (0 makes the apparition purely cosmetic).ref(), player(), composureDrain() / setComposureDrain(float), echoIntensity(), pulseIndex(), nemesisEcho().
InnerDemonTrialEventNoAn Inner Demon rival duel struck a mid-duel cultivator. composureRemaining and broken read as they do for the Dream Trial; echoIntensity is the 0-1 fraction that scaled the drain, and nemesisEcho is true when the phantom wore an active Nemesis's face.ref(), player(), composureRemaining(), broken(), echoIntensity(), nemesisEcho().
PreCleanseRiteEventYesA Marrow-Cleansing Rite pulse is about to test a mid-rite cultivator. Cancel to skip the pulse entirely; adjust setComposureDrain to change how hard it bites (0 makes the pulse purely cosmetic).ref(), player(), composureDrain() / setComposureDrain(float), targetMagnitude(), pulseIndex(), companionPresent().
CleanseRiteEventNoA Marrow-Cleansing Rite pulse tested a mid-rite cultivator. broken is true only on the pulse that failed the rite (deepening the targeted injury), targetMagnitude is the targeted injury's magnitude at entry, and companionPresent is true when a bonded partner or master or disciple was close enough to reduce the drain.ref(), player(), composureRemaining(), broken(), targetMagnitude(), companionPresent().
PreRitualAbortEventYesA cultivator is asking to withdraw from an armed breakthrough or advancement. Fires for the /cultivation meditate abort quote and again for abort confirm, so a veto refuses before the player is quoted a cost; it can therefore fire twice for one withdrawal. Cancel to refuse; nothing has been charged. The command sends no message of its own when cancelled, so the listener should explain itself.ref(), player(), type(), qiLost().
RitualAbortEventNoA cultivator withdrew from an armed breakthrough or advancement with /cultivation meditate abort. qiLost is the banked Qi that scattered (already deducted); no demotion applies. They were stood up through an ordinary COMMAND stop, so MeditationStopEvent fires too.ref(), player(), type(), qiLost().
AscensionLegacyEventNoAn Ascension completed with the Legacy ending (/ascend legacy sect or /ascend legacy self). Always fires immediately after AscensionEvent for the same completion, since a Legacy ending is also a prestige-shaped reset. sectBeneficiary is what was actually granted: true for a Lineage Stele inscription, false for a Legacy Mote - including a sect-aimed attempt that fell back to a Mote because the sect was gone, which sectFallback distinguishes. sectName is set only when sectBeneficiary is true.ref(), player(), sectBeneficiary(), sectFallback(), sectName().
PreReincarnationEventYesA cultivator is about to begin Reincarnation. Cancelling keeps them at their current realm untried - the one hook a server needs to gate this ending behind something of its own (a quest, an item, a date), the same role PreAscensionEvent plays for the other capstone.ref(), player().
ReincarnationEventNoA cultivator completed Reincarnation (转世) - the alternate capstone alongside Ascension. fromRealm/toRealm are the realm given up and the realm landed at after the partial reset; bloodlinePointsGranted is what was just added to their ReincarnationLedger entry (cumulative, not their new total).ref(), player(), fromRealm(), toRealm(), bloodlinePointsGranted().
ReincarnationFailedEventNoA cultivator's Reincarnation attempt ended in failure.ref(), player(), abandoned().
PlayerKillEventNoOne cultivator killed another and the kill pays. The generic player-versus-player hook: everything the mod itself pays for a kill (Devil-path Qi, Dao deeds, the Wu Xing reward, a shed manual) is credited immediately after it fires. It fires only for a kill that survives the anti-farm gate (Pk-Same-Victim-Cooldown-Seconds, Pk-Min-Victim-Realm) and never for a self-kill, an environmental death, or a kill a fleeing Nascent Soul landed. There is no Pre twin by design. sanctionedDuel is true when the death resolved a duel; the mod withholds its own PvP reward there, and an addon that pays for kills should too.killer(), killerPlayer(), victim(), victimPlayer(), sanctionedDuel().

Declared @Nullable: player() (every event that carries it); sectName(); victimPlayer().

Config keys these override: setRequiredSeconds corresponds to Breakthrough-Base-Seconds and Advancement-Base-Seconds; PreTribulationStrikeEvent.setDamage to Tribulation-Damage-Percent-Of-Max-Health and Advancement-Tribulation-Damage-Percent-Of-Max-Health; setComposureDrain to HeartDevil-Composure-Drain-Per-Pulse; setDemotes / setQiLoss to HeartDevil-Deviation-Demotes and HeartDevil-Deviation-Qi-Loss-Percent; PreRespecEvent mirrors Respec-Enabled and the /cultivation respec command. See Config and Commands.

Body tempering

plugin.siren.API.BodyTemperingEvents v0.7.1 - 6 events (3 cancellable) covering the second progression ladder, the one climbed by taking the hit rather than by gathering Qi: the XP that damage earns, the levels it buys, and the nine Tempering Stages those levels group into. Player-facing docs: Body Tempering.

Every listener here runs inside the damage pipeline on the world thread that owns the entity being hit: read through the accessor you are given, never write to the Store from one, and keep it cheap. PreXpGainEvent can scale or refuse what a blow is worth, which is how an addon adds a multiplier without reimplementing the armor scan; PreLevelUpEvent holds a body at its level while the XP stays banked.

EventCancellableFiresExposes
PreXpGainEventYesA cultivator is about to earn tempering XP from damage that reached them. The amount is editable, not merely refusable — this is the supported way to retune the whole ladder for one player or one situation.ref(), player(), amount() / setAmount(float), level().
XpGainEventNoThe XP was banked.ref(), player(), amount(), level().
PreLevelUpEventYesA tempering level is about to be gained. Cancel to hold the cultivator where they are.ref(), player(), fromLevel(), toLevel().
LevelUpEventNoThe level was gained. Fires once per level when a single blow crosses several.ref(), player(), fromLevel(), toLevel().
PreStageBreakthroughEventYesAbout to celebrate a Tempering Stage breakthrough. Unlike PreLevelUpEvent, cancelling does not hold the body at its old stage - the stage is derived from the level, which is already banked. What it suppresses is the ceremony: the title, sound, particle and broadcast.ref(), player(), fromStage(), toStage().
StageBreakthroughEventNoA body crossed into a new Tempering Stage (锻体境), the nine-rung milestone derived from the level. Fires at most once per XP grant, even when one huge blow crossed several levels at once.ref(), player(), fromStage(), toStage().

Declared @Nullable: player() (every event that carries it).

Whole-system gate: Body-Tempering-Enabled. Note that XP is measured after armor, so a listener that reads amount() is seeing what actually got through, not what was swung.

Fist arts

plugin.siren.API.FistEvents v0.7.4 - 4 events (2 cancellable) covering the third ladder, climbed by landing blows bare-handed - the mirror of body tempering, whose income is damage received. The XP is measured by the damage that actually got through, so a punch a shield ate whole teaches nothing. Player-facing docs: The Fist Arts.

These fire on every bare-handed blow a cultivator lands, from inside the damage pipeline: keep listeners cheap and never write to the Store from one. PreXpGainEvent carries a mutable amount, and setting it to 0 or below cancels the gain outright. The four event names are the same as BodyTemperingEvents' - qualify them by class.

EventCancellableFiresExposes
PreXpGainEventYesAbout to bank XP for a bare-handed blow. Like the tempering equivalent this is not merely cancellable - it carries a mutable amount, so a listener can scale the reward rather than being limited to allowing or forbidding it. Setting the amount to 0 or below cancels the gain outright.ref(), player(), level(), amount() / setAmount(float).
XpGainEventNoXP was banked. amount is what was actually granted, after any pre-event scaling.ref(), player(), level(), amount().
PreLevelUpEventYesAbout to gain a level. Cancelling holds the cultivator where they are; the XP stays banked.ref(), player(), fromLevel(), toLevel().
LevelUpEventNoA cultivator's fists gained a level. Fires once per level when one blow crosses several.ref(), player(), fromLevel(), toLevel().

Declared @Nullable: player() (every event that carries it).

Lifespan

plugin.siren.API.LifespanEvents v0.10.2 - 6 events (2 cancellable) covering Lifespan (寿元), the per-profile online-play-hour budget keyed to the highest realm ever reached: extending it, withering, restoring and expiring. The system itself is off by default on a server; CultivationAPI.isLifespanEnabled() reports whether it is on. Player-facing docs: Lifespan.

Every listener runs from LifespanSystem's tick or from a command handler on the world thread that owns the entity. Read through the accessor you are given and queue writes on a CommandBuffer.

Enums declared here:

EventCancellableFiresExposes
PreLifespanExtendEventYesAbout to grant bonus hours (ExtendSource.PILL/ExtendSource.ADMIN only - a ExtendSource.BREAKTHROUGH realm-mark rise has already happened by the time Lifespan notices it and fires only the post event). Cancel to refuse the grant outright, or scale setHours to change how much is actually banked.ref(), player(), source(), hours() / setHours(float).
LifespanExtendEventNoBonus hours were actually banked (or, for ExtendSource.BREAKTHROUGH, the realm-mark budget rose). hours is what actually applied, after any PreLifespanExtendEvent scaling.ref(), player(), hours(), source().
LifespanWitherEventNoA cultivator's Lifespan budget hit 0 and they entered the Withering grace state.ref(), player().
LifespanRestoreEventNoA cultivator's Lifespan clock was reset to a fresh budget - a real Reincarnation, a real Ascension, LifespanConfig.ExpiryAction.NOTHING clearing Withering, or Mortal Passing's own reset (which additionally fires LifespanExpireEvent).ref(), player().
PreLifespanExpireEventYesA cultivator's Lifespan budget is about to reach 0 and enter Withering. Cancelling here vetoes Withering entirely for this crossing - the clock stays at 0 and this fires again the next tick, so an addon meaning to grant a reprieve should also extend the budget (or it will simply be asked again immediately).ref(), player().
LifespanExpireEventNoA cultivator's Withering grace ran out and Lifespan-Expiry-Action is about to run. Not cancellable through this event - by the time this fires the grace period has already elapsed; use PreLifespanExpireEvent to veto Withering itself, further upstream.ref(), player(), action().

Declared @Nullable: player() (every event that carries it).

Meridian injuries

plugin.siren.API.MeridianEvents v0.9.1 - 5 events (3 cancellable) covering a named Meridian Injury being inflicted or deepened, cured, and a Cracked Dantian's Qi spill being armed. Player-facing docs: Meridian Injuries.

EventCancellableFiresExposes
PreMeridianInjuryEventYesAn injury is about to be inflicted (or deepened, if already carried). Cancel to refuse it outright; setMagnitude/setDurationSeconds to re-scale the roll before it is written.ref(), player(), injury(), cause(), magnitude() / setMagnitude(float), durationSeconds() / setDurationSeconds(float).
MeridianInjuryEventNoAn injury was inflicted (or deepened) - the write has already landed.ref(), player(), injury(), magnitude(), durationSeconds().
PreMeridianCureEventYesAn injury is about to be cured. Cancel to refuse it (it stays active).ref(), player(), injury(), cureSource().
MeridianCureEventNoAn injury fully cleared (wait-it-out, meditation recovery, or an explicit cure).ref(), player(), injury(), cureSource().
PreMeridianSpillEventYesA Cracked Dantian's Qi spill is about to be armed (fired once at inflict/deepen, not per-tick). Cancel to cap future Qi gain at the new ceiling without draining the excess already banked; setRatePerSecond to re-tune how fast the excess drains.ref(), player(), excessQi(), ratePerSecond() / setRatePerSecond(float).

Declared @Nullable: player() (every event that carries it).

Nascent Soul Escape

plugin.siren.API.SoulEscapeEvents v0.10.2 - 8 events (3 cancellable) covering the fatal-blow reprieve (元婴遁走), the chase, and how it resolves: survived, extinguished, timed out or forfeited. Player-facing docs: Nascent Soul Escape.

There is no public facade for beginning an escape; only a qualifying killing blow opens one. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Two enums live here: SanctuaryKind (ABODE, SECT_HALL, FORMATION) and ForfeitReason (DISCONNECT, RELOG_RESOLVED, WORLD_CHANGE, PLUGIN_RELOAD).

EventCancellableFiresExposes
PreSoulEscapeBeginEventYesThe fatal blow is about to be cancelled and the soul to start fleeing. Cancel for no escape: nothing is charged and the blow stays fatal. The three setters re-tune the window and the costs.soul(), killer(), realm(), windowSeconds() / setWindowSeconds(float), qiCostPercent() / setQiCostPercent(float), lifespanHours() / setLifespanHours(float).
SoulEscapeBeginEventNoThe reprieve began: the fatal blow was cancelled and the soul is now fleeing. windowSeconds is the length of the window; qiSpent and lifespanHoursSpent are what the attempt was charged.soul(), killer(), realm(), windowSeconds(), qiSpent(), lifespanHoursSpent().
PreSoulSurviveEventYesA fleeing soul just entered a sanctuary. Cancel and the sanctuary does not count: the soul keeps fleeing. The setters re-tune the health restored, the weakened time and the Merit paid to the killer.soul(), killer(), kind(), world(), chunkX(), chunkZ(), restoreHealthPercent() / setRestoreHealthPercent(float), weakenedMinutes() / setWeakenedMinutes(float), spareMerit() / setSpareMerit(float).
SoulEscapeSurviveEventNoThe fleeing soul reached a sanctuary and returned to its body, weakened for weakenedMinutes. kind says which kind of sanctuary it was; meritToKiller is the Merit credited to the killer for sparing them.soul(), killer(), kind(), world(), chunkX(), chunkZ(), weakenedMinutes(), meritToKiller().
PreSoulExtinguishEventYesA blow just landed on a fleeing soul. Cancel and this blow does nothing; the soul keeps fleeing.soul(), extinguisher(), hitsSoFar(), hitsRequired(), extraKarma() / setExtraKarma(float), extraYinShift() / setExtraYinShift(float).
SoulEscapeExtinguishEventNoA fleeing soul was struck down again before reaching safety, so the escape ended in real death. extraKarma and extraYinShift are the extra penalty charged to the extinguisher on top of a normal kill's own.soul(), extinguisher(), extraKarma(), extraYinShift().
SoulEscapeTimeoutEventNoThe window ran out before the soul reached a sanctuary, so the escape ended in real death.soul(), killer().
SoulEscapeForfeitEventNoThe escape ended without resolving into a survive, an extinguish or a timeout. reason() says why: DISCONNECT, RELOG_RESOLVED, WORLD_CHANGE or PLUGIN_RELOAD.soul(), killer(), reason().

Declared @Nullable: killer() (every event that carries it).

Legacy

plugin.siren.API.LegacyEvents v0.10.1 - 2 events (1 cancellable) covering a retiring cultivator's breakthrough-cost-reduction buff reaching the heir they chose. The payout is cancellable and re-tunable. Player-facing docs: Legacy.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreLegacyPayoutEventYesA legacy buff is about to be granted or queued. Cancel to refuse it outright - nothing has been written or queued yet. A listener may also rescale setReductionPercent / setExpiresAtMillis before dispatch finishes; whatever is left in those fields is what actually gets granted or queued.benefactorUuid(), benefactorName(), heirUuid(), retiringRealmOrdinal(), sourceProfileName(), reductionPercent() / setReductionPercent(float), expiresAtMillis() / setExpiresAtMillis(long).
LegacyPayoutEventNoA legacy buff has actually reached (or been queued for) an heir - the reduction described here is the final amount, after the recent-profile scale-down for an immediate payout, or the pre-scale base amount for a queued one (see queued).benefactorUuid(), benefactorName(), heirUuid(), reductionPercent(), expiresAtMillis(), sourceProfileName(), queued().

Cultivation profiles

plugin.siren.API.ProfileEvents v0.7.0 - 7 events (3 cancellable) covering switching between a player's saved cultivators, creating one, erasing one, and a test profile expiring. Player-facing docs: Profiles.

These carry a Profile payload (CultivationProfilesComponent.Profile) - useful members are getName(), isTest(), getExpiresAtMillis() and remainingMillis(long). Note there is no pre-event for expiry: a test profile's time limit is not vetoable.

These listeners may write. Uniquely in the API, profile listeners run on the owning world thread outside any ticking system, so reading and writing components directly through the Store is safe here - the one exception to the usual "never write from a listener" rule. That is deliberate: a progression addon must be able to save and load its own state during a switch (see ProgressionProvider.supportsProfiles()).

EventCancellableFiresExposes
PreProfileSwitchEventYesA player is about to change cultivators. Cancel to refuse the switch - the command reports it blocked.ref(), player(), from() (null on a first-time backfill), to().
ProfileSwitchEventNoThe switch committed - the arriving profile's realm, Qi, race, dao, skill tree and techniques are already on the components. Load your own per-profile state here.ref(), player(), from() (null on a first-time backfill), to().
PreProfileCreateEventYesA fresh cultivator is about to be created. Cancel to refuse it - no slot is used.ref(), player().
ProfileCreateEventNoThe profile exists and the player is on it.ref(), player(), profile().
PreProfileDeleteEventYesA save is about to be erased for good. Cancel to keep it.ref(), player(), profile().
ProfileDeleteEventNoThe save is gone. Erase your own state for it here.ref(), player(), profile().
ProfileExpireEventNoA test profile's wall-clock limit ran out. wasActive() says whether the player was playing it - and so has just been put back on a real cultivator.ref(), player(), profile(), wasActive().

Declared @Nullable: from() (every event that carries it).

If your addon keeps progression of its own, handle PreProfileSwitchEvent (save the profile being left) and ProfileSwitchEvent (load the arriving one), then return true from ProgressionProvider.supportsProfiles() - until you do, switching is refused outright while your provider is installed rather than half-performed. The caps are addon-raisable too: registerProfileCap on the API facade lifts the three-slot default (ceiling six, highest registration wins, and nothing is ever destroyed when a cap falls).

The Dao, arts and oaths

Elements, Yin-Yang and karma, the Heavenly and Personal Daos, techniques, combat depth and Heavenly Oaths.

Dao and karma

plugin.siren.API.DaoEvents - 14 events (6 cancellable) covering elements, drift, the Yin-Yang balance, moral paths and karma. Player-facing docs: Spiritual Root, The Dao and Karma.

Alignment and karma move on nearly every meditation tick and every kill, so these fire often. Keep the listeners cheap, and prefer the path and drift events when you only care about the outcome.

Enums declared here:

EventCancellableFiresExposes
PreDaoElementChangeEventYesA cultivator is about to take, or switch to, an element. Cancel and it reports an unchanged dao.ref(), player(), dao(), oldElement() (null on first choice), newElement() / setNewElement(DaoElement), reason(), qiCost() / setQiCost(float) (always 0 for a first choice and for DRIFT).
DaoElementChangeEventNoThe element changed.ref(), player(), dao(), oldElement(), newElement(), reason(), qiCost().
PreDaoAffinityGainEventYesDeed affinity is about to be added toward an element - the pressure that eventually causes drift.player(), dao(), element(), amount() / setAmount(float).
DaoAffinityGainEventNoThe affinity was added.player(), dao(), element(), amount().
DaoDriftWarningEventNoA cultivator was warned their dao is drifting. Fires once per newly-threatening element.player(), dao(), chosenElement(), driftingTo().
PreAlignmentShiftEventYesA Yin-Yang shift is about to be applied. The race-bias split happens after this.dao(), amount() / setAmount(float) (the raw shift), towardYin().
AlignmentShiftEventNoThe balance moved.dao(), yin(), yang() - the amounts actually added after race bias split the shift.
PrePathChangeEventYesA moral path is about to change. Cancel and the underlying balance is untouched, so this only suppresses the reclassification.player(), dao(), oldPath(), newPath().
PathChangeEventNoThe path changed and was announced.player(), dao(), oldPath(), newPath().
PreKarmaGainEventYesKarma (业力) is about to be charged for a kill. Cancel to leave the ledger clean.player(), dao(), amount() / setAmount(float), farmedKill() (a repeat kill inside the anti-farm window - worth no Qi, but more karma).
KarmaGainEventNoKarma was charged.player(), dao(), amount(), total() (after the Karma-Max cap), farmedKill().
KarmaClearedEventNoKarma was worked off - by enduring a tribulation strike, or by the wall-clock decay of not killing anyone.dao(), amount(), total(), fromTribulation().
PreDevilHarvestEventYesA Devil-path (魔道) cultivator is about to harvest banked Qi from a slain player.killer(), killerPlayer(), qi() / setQi(float).
DevilHarvestEventNoThe harvest happened.killer(), killerPlayer(), qi().

Declared @Nullable: ref() (every event that carries it); player() (every event that carries it); oldElement() (every event that carries it).

Config keys these override: PreDaoElementChangeEvent.setQiCost corresponds to Dao-Switch-Base-Qi-Cost and Dao-Switch-Qi-Cost-Realm-Multiplier; PreDaoAffinityGainEvent.setAmount to Dao-Affinity-Per-Elemental-Kill (with Dao-Drift-Margin deciding when drift trips); PreAlignmentShiftEvent.setAmount to Meditation-Alignment-Shift-Per-Tick and Kill-Yin-Amount, with the post-event split governed by Qi-Alignment-Yin-Bias-Percent; PreKarmaGainEvent.setAmount to Karma-Per-Player-Kill and Karma-Per-Farmed-Kill; PreDevilHarvestEvent.setQi to Path-Devil-PK-Qi-Reward.

Dao comprehension

plugin.siren.API.DaoComprehensionEvents v0.9.0 - 11 events (5 cancellable) covering the layer on top of the Elemental Dao: the Heavenly Dao (天道) understanding track, the open Personal Dao registry (seven built in — Sword, Slaughter, Time-Space, Beast Taming, Devil, Body Refinement and Elemental — plus whatever a mod adds beside them) and Dao Enlightenment (悟道). It sits beside DaoEvents and does not replace it. Player-facing docs: The Dao.

HeavenlyDaoGainEvent fires once per meditation second per meditating player (plus once per tribulation bolt survived, advancement and breakthrough), and PersonalDaoComprehensionEvent fires on every qualifying kill and technique use - keep listeners on either cheap.

EventCancellableFiresExposes
PreHeavenlyDaoGainEventYesThe Heavenly Dao track is about to advance. Cancel to refuse it; setAmount to re-scale.player(), comprehension(), amount() / setAmount(float).
HeavenlyDaoGainEventNoThe Heavenly Dao track advanced. amount is what was actually applied (after any listener re-scaled it); total is the value after.player(), comprehension(), amount(), total().
PreHeavenlyDaoRankChangeEventYesThe player's HeavenlyDaoRank is about to change. Cancel to leave them on their current rank - the underlying value is untouched, so this only suppresses the reclassification/announcement.player(), comprehension(), oldRank(), newRank().
HeavenlyDaoRankChangeEventNoThe player's HeavenlyDaoRank changed (and was announced to them).player(), comprehension(), oldRank(), newRank().
PrePersonalDaoComprehensionEventYesA Personal Dao's comprehension is about to advance. Cancel to refuse it; setAmount to re-scale.player(), comprehension(), dao(), amount() / setAmount(float).
PersonalDaoComprehensionEventNoA Personal Dao's comprehension advanced. amount is what was actually applied; total is the value after.player(), comprehension(), dao(), amount(), total().
PrePersonalDaoManifestEventYesA Personal Dao is about to manifest. Cancel to leave it comprehended but unmanifested.player(), comprehension(), dao().
PersonalDaoManifestEventNoA Personal Dao manifested for this player.player(), comprehension(), dao().
PersonalDaoSetAsideEventNoA manifested Personal Dao was set aside.player(), comprehension(), dao().
PreDaoEnlightenmentEventYesA Dao Enlightenment is about to fire. Cancel to refuse it as if the roll never happened; setComprehensionGain and setQiGain re-scale the reward. setQiGain can lower the Qi burst but cannot raise it past the server's absolute cap, which is applied both before this event and again after qiGain() is read back.player(), comprehension(), subject(), comprehensionGain() / setComprehensionGain(float), qiGain() / setQiGain(float).
DaoEnlightenmentEventNoA Dao Enlightenment (悟道) fired. subject is whichever Heavenly/Personal Dao triggered it.player(), comprehension(), subject(), comprehensionGain(), qiGain().

Declared @Nullable: player() (every event that carries it).

Techniques

plugin.siren.API.TechniqueEvents - 15 events (7 cancellable) covering performing and learning arts (功法), fusing two into a third, mastery, the Sword Flying toggle, and the timed combat buffs. Player-facing docs: Techniques and Manuals.

These fire for third-party techniques registered through CultivationAPI.registerTechnique exactly as they do for the built-in ones - the gate, cost and cooldown pipeline is shared.

Enums declared here:

EventCancellableFiresExposes
PreTechniquePerformEventYesA technique is about to be performed - every gate has already passed. Cancel to refuse it silently: no Qi spent, no cooldown.ref(), player(), technique(), qiCost() / setQiCost(float), cooldownSeconds() / setCooldownSeconds(float) (0 or less stamps no cooldown).
TechniquePerformEventNoThe Qi is spent, the cooldown stamped, and the effect has run.ref(), player(), technique(), qiCost().
PreTechniqueLearnEventYesA technique is about to be learned for good. Cancel to refuse it - the manual is consumed either way, matching how a manual for an already-known art is spent.ref(), player(), techniqueId().
TechniqueLearnEventNoThe art is learned. Sect-taught arts are resolved live and never fire this - listen for the sect inscription events instead.ref(), player(), techniqueId().
PreSwordFlightStartEventYesA cultivator is about to take to the sky on their sword.ref(), player(), horizontalSpeed() / setHorizontalSpeed(float), verticalSpeed() / setVerticalSpeed(float).
SwordFlightStartEventNoThey are airborne.ref(), player(), horizontalSpeed(), verticalSpeed().
PreSwordFlightStopEventYesA cultivator is about to come down. Safe to cancel for TOGGLE, but cancelling a DEATH stop leaves flight state on a corpse - gate on reason().ref(), player(), reason().
SwordFlightStopEventNoThey came down; their mount, if any, has already despawned.ref(), player(), reason().
PreTechniqueBuffApplyEventYesA timed technique buff is about to be applied.ref(), player(), type(), durationSeconds() / setDurationSeconds(float), magnitude() / setMagnitude(float).
TechniqueBuffApplyEventNoThe buff is on. magnitude means whatever that buff measures - a reduction percent, a damage percent, a shield pool, a speed multiplier.ref(), player(), type(), durationSeconds(), magnitude().
TechniqueBuffExpireEventNoCloud Step's speed multiplier was reverted, on expiry or on cleanup.ref(), player(), type().
PreTechniqueMasteryAdvanceEventYesAn art is about to rise a mastery rung. Cancel to hold it where it is — the XP and the studied manuals stay banked, so it simply tries again the next time anything about it changes.ref(), player(), techniqueId(), stage() (the rung it is rising to).
TechniqueMasteryAdvanceEventNoAn art’s mastery rose a rung.ref(), player(), techniqueId(), stage().
PreTechniqueFusionEvent v0.8.0YesA fusion is about to be attempted — every gate (both parents at Pinnacle, the realm floor, the Qi, the cooldown) has already passed. Cancel to refuse it silently: no Qi spent, no cooldown stamped, neither parent touched. The three setters re-tune this one attempt without touching TechniqueConfig.json.ref(), player(), fusionId(), resultTechniqueId(), qiCost() / setQiCost(float), consumeParents() / setConsumeParents(boolean), failureChancePercent() / setFailureChancePercent(float).
TechniqueFusionEvent v0.8.0NoA fusion succeeded and the result art was granted.ref(), player(), fusionId(), resultTechniqueId().

Declared @Nullable: player() on TechniqueLearnEvent, TechniqueBuffApplyEvent, TechniqueFusionEvent, TechniqueBuffExpireEvent, TechniqueMasteryAdvanceEvent, PreTechniqueLearnEvent, PreTechniqueBuffApplyEvent, PreTechniqueFusionEvent, PreTechniqueMasteryAdvanceEvent.

Config keys these override: PreTechniquePerformEvent mirrors the per-technique Qi-Cost and Cooldown-Seconds entries in the technique rules (see Config), and the /cultivation technique <id> command on the Commands page. PreTechniqueLearnEvent fires for every art a player comes by, whichever unlock route produced it — reading a manual, enlightenment while meditating, a breakthrough, a sect inscription, or a master teaching it — so a listener does not have to care which.

Whether a fusion consumed its two parents is deliberately not carried on TechniqueFusionEvent: listen for the ordinary TechniqueLearnEvent the grant fires, and note that a consuming recipe removes the parents through TechniqueUnlockManager.revoke, which fires no event of its own — nothing in normal play ever removes knowledge except a fusion and an admin tool.

Combat depth

plugin.siren.API.CombatDepthEvents v0.10.1 - 4 events (2 cancellable) covering technique interrupts and the Wu Xing PvP reward a favorable elemental matchup pays out. Punish Windows fire no events of their own - they are a damage multiplier consumed inside the combat system. Player-facing docs: Combat Depth.

EventCancellableFiresExposes
PreTechniqueInterruptEventYesA charging cultivator's gathering is about to be broken by a hard-enough hit. Cancel to let the gathering continue uninterrupted; setDamageAmount re-scales the figure carried into the paired post-event (the interrupt itself still happens once this fires - the damage that triggered it has already landed).ref(), player(), damageAmount() / setDamageAmount(float).
TechniqueInterruptEventNoA charging cultivator's gathering was broken by a hard-enough hit. damageAmount is the (possibly re-scaled) figure PreTechniqueInterruptEvent settled on.ref(), player(), damageAmount().
PreWuxingPvpRewardEventYesA favorable-matchup PvP kill is about to pay its Wu Xing reward. Cancel to deny it; setSpiritStones to re-scale how many are actually granted.killer(), killerPlayer(), spiritStones() / setSpiritStones(int).
WuxingPvpRewardEventNoA favorable-matchup PvP kill paid its Wu Xing reward.killer(), killerPlayer(), spiritStones().

Declared @Nullable: player() (every event that carries it).

Heavenly oaths

plugin.siren.API.OathEvents v0.9.0 - 8 events (4 cancellable) covering swearing, breaching and peacefully dissolving a Heavenly Oath (天道誓言), and cleansing the Dao-Heart Flaw a breach leaves behind. Player-facing docs: Heavenly Oaths.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreOathSwearEventYesA pending offer is about to be accepted and become a binding oath. Cancel to refuse it - the offer is consumed either way, so the offerer must send a fresh one.offerer(), accepter(), type(), stake() / setStake(int), sectId().
OathSwornEventNoA pending offer was accepted and the oath is now sworn (binding).oathId(), type(), partyA(), partyB(), stake(), sectId().
PreOathBreachEventYesA sworn oath is about to be recorded as broken and its penalty applied. Cancel to pardon the breach outright (nothing changes - no Qi loss, no karma, no flaw); adjust the setters to re-tune the penalty instead.oathId(), type(), breaker(), victim(), qiLossPercent() / setQiLossPercent(float), karmaSwing() / setKarmaSwing(float), flawDurationMinutes() / setFlawDurationMinutes(float), flawSeverity() / setFlawSeverity(float).
OathBreachEventNoA sworn oath was broken and its penalty has already been applied.oathId(), type(), breaker(), victim(), qiLost(), karmaGained().
PreOathFlawCleanseEventYesA Dao-Heart Flaw is about to be cleansed. Cancel to refuse the attempt (the item, if any, is still the caller's to decide whether to consume - see the call site).player(), route().
OathFlawCleanseEventNoA cultivator's Dao-Heart Flaw was cleansed by route.player(), route().
PreOathDissolveEventYesA sworn oath is about to be peacefully DISSOLVED. Cancel to keep it active (no change - the requesting side must ask again).oathId(), type(), partyA(), partyB().
OathDissolveEventNoA sworn oath was peacefully DISSOLVED - no penalty, either via a mutual /cultivation oath dissolve or the one system-triggered no-fault case (see OathManager.dissolveActiveOath).oathId(), type(), partyA(), partyB().

Items and crafting

Drops, pills, cores and manuals, and the crafts that make something new: refinement, alchemy, forging and talismans, plus the spirit inside a weapon.

Items

plugin.siren.API.ItemEvents - 12 events (6 cancellable) covering cultivation drops, pills, cores, manuals and weapon refinement (炼器). Player-facing docs: Qi Gathering, Alchemy, Manuals, Weapon Refinement.

Learning a technique from a manual fires the technique learn events in TechniqueEvents; a manual that teaches a skill node goes through the skill-unlock events in CultivationEvents. This class covers the manual item itself.

Enums declared here:

EventCancellableFiresExposes
PreLootDropEventYesA cultivation drop is about to be handed over. Cancel to deny it.ref(), player(), type(), itemId() / setItemId(String) - substitute a different item entirely.
LootDropEventNoThe drop landed in a player's inventory and was announced. Never fires when the roll missed or the item did not fit.ref(), player(), type(), itemId().
PreManualReadEventYesA manual is about to teach. Cancel to refuse - the manual is consumed either way.ref(), player(), techniqueId() (set when it teaches a technique), skillNodeId() (set when it teaches a node).
ManualReadEventNoThe manual was read and its teaching applied.ref(), player(), techniqueId(), skillNodeId().
PrePillConsumeEventYesA spirit pill is about to take effect. Cancel and the pill is not consumed.ref(), player(), effect() - the interaction's configured effect id.
PillConsumeEventNoThe pill was consumed and its effect applied.ref(), player(), effect().
PreSpiritCoreConsumeEventYesA cultivation core is about to be absorbed. Cancel and the core is not consumed.ref(), player(), qi() / setQi(float) (meditation bonus already folded in), meditating().
SpiritCoreConsumeEventNoThe core was absorbed.ref(), player(), qi() - what was actually banked.
PreRefinementStartEventYesA refinement ritual is about to begin. Cancel and no Qi is spent.ref(), player(), element(), targetTier(), qiCost() / setQiCost(float).
RefinementStartEventNoThe ritual began; the Qi is spent and the cultivator seated.ref(), player(), element(), targetTier(), qiCost().
PreRefinementCompleteEventYesA refinement ritual is about to resolve. Cancel to abandon it silently - the weapon is untouched, the up-front Qi stays spent.ref(), player(), element(), targetTier(), successChance() / setSuccessChance(float) - 1 guarantees it, 0 dooms it.
RefinementCompleteEventNoThe ritual resolved.ref(), player(), element(), targetTier(), outcome(), stack() - the weapon afterward, or null when destroyed.

Declared @Nullable: player() on LootDropEvent, PreLootDropEvent; techniqueId() (every event that carries it); skillNodeId() (every event that carries it); stack().

Config keys these override: PreLootDropEvent corresponds to the drop-chance keys (Spirit-Core-Drop-Chance, Profound-Core-Drop-Chance, Divine-Core-Drop-Chance, Egg-Kill-Drop-Chance, SpiritStone-Kill-Drop-Chance, Manual-Tribulation-Drop-Chance, Manual-Player-Kill-Drop-Chance); PreSpiritCoreConsumeEvent.setQi to Spirit-Core-Qi-Value / Profound-Core-Qi-Value / Divine-Core-Qi-Value and Meditation-Core-Bonus-Multiplier; PreRefinementStartEvent.setQiCost to Refinement-Base-Qi-Cost and Refinement-Qi-Cost-Tier-Multiplier; PreRefinementCompleteEvent.setSuccessChance to Refinement-Base-Success-Chance, Refinement-Success-Chance-Loss-Per-Tier and Refinement-Min-Success-Chance.

Alchemy

plugin.siren.API.AlchemyEvents v0.10.0 - 5 events (2 cancellable) covering the Pill Cauldron refining ritual - starting, resolving, and the Fire Watch (火候) tending prompts along the way. It complements ItemEvents.PillConsumeEvent, which covers drinking a finished pill rather than brewing one. Player-facing docs: Alchemy.

Enums declared here:

EventCancellableFiresExposes
PreRefineStartEventYesA refining ritual is about to begin. Cancel to refuse it (no herbs or Qi are spent).ref(), player(), effect(), durationSeconds() / setDurationSeconds(float).
RefineStartEventNoA refining ritual began; the herbs and Qi floor check already passed.ref(), player(), effect(), qiDrainPerSecond().
PreRefineCompleteEventYesA refining ritual is about to resolve into a grade. Cancel to abandon it silently - the herbs and Qi stay spent, nothing is produced, the same shape ItemEvents.PreRefinementCompleteEvent and TalismanEvents.PreInscribeCompleteEvent both use. The setters re-weight the inputs the grade roll actually uses (herb quality, mastery and the Fire Watch adjustment). Fires only on a natural completion, never an interruption.ref(), player(), effect(), herbQualityAvg() / setHerbQualityAvg(float), masteryLadderFraction() / setMasteryLadderFraction(float), tendDelta() / setTendDelta(float).
RefineCompleteEventNoA refining ritual resolved. grade is null unless outcome is SUCCESS.ref(), player(), effect(), outcome(), grade().
TendPromptResolvedEventNoA Fire Watch (火候) tending prompt was resolved - answered (sharp/steady), missed, or fumbled - during a running refining ritual. promptIndex is 0-based (this is the Nth prompt this ritual delivered); promptCount is the ritual's configured Tend-Prompts-Max ceiling, not how many have fired so far.ref(), player(), action(), outcome(), promptIndex(), promptCount().

Declared @Nullable: grade().

Forging

plugin.siren.API.ForgingEvents v0.9.1 - 2 events (1 cancellable) covering tempering an already-crafted Cultivation weapon or armor at a Forge Anchor - success, failure and botch outcomes. Player-facing docs: Forging.

Enums declared here:

EventCancellableFiresExposes
PreForgeEventYesA forging attempt is about to begin - every check has passed, but no materials and no item have been touched yet. Cancel to refuse it entirely; nothing is spent and no ritual starts.ref(), player(), itemId(), targetTier().
ForgeCompleteEventNoA forging attempt resolved. grade is null unless outcome is SUCCESS. resultStack is the item as it now stands (forged, demoted, or unchanged) - null only if there was nowhere to place it back into the cultivator's inventory (see ForgingManager.placeItem), which the accompanying player message already reports.ref(), player(), targetTier(), outcome(), grade(), resultStack().

Declared @Nullable: grade(); resultStack().

Talismans

plugin.siren.API.TalismanEvents v0.9.0 - 6 events (3 cancellable) covering inscribing at a Talisman Desk (start and complete, with success, failed and botch outcomes) and using a finished talisman. Player-facing docs: Talismans.

Enums declared here:

EventCancellableFiresExposes
PreInscribeStartEventYesAn inscription ritual is about to begin. Cancel to refuse it (no materials or Qi are spent).ref(), player(), talismanId(), element().
InscribeStartEventNoAn inscription ritual began; materials and the Qi floor check already passed.ref(), player(), talismanId(), element(), qiDrainPerSecond().
PreInscribeCompleteEventYesAn inscription ritual is about to resolve into a grade. Cancel to abandon it silently - the materials and Qi are already sunk and nothing is produced, the same shape ItemEvents.PreRefinementCompleteEvent uses. The setters re-weight the two inputs the grade roll actually uses, which is the supported way for an add-on to reshape a talisman's grade odds (for example a flat affinity bonus for this roll only) without touching the saved Dao. Fires only on a natural completion, never an interruption.ref(), player(), talismanId(), element(), masteryLadderFraction() / setMasteryLadderFraction(float), affinityFraction() / setAffinityFraction(float).
InscribeCompleteEventNoAn inscription ritual resolved. grade/stack are null unless outcome is SUCCESS.ref(), player(), talismanId(), element(), outcome(), grade(), stack().
PreTalismanUseEventYesA talisman is about to be used. Cancel to refuse it (the charge is not spent).ref(), player(), talismanId(), grade().
TalismanUseEventNoA talisman was used and its effect applied. remainingCharges is what is left after this use - the stack is gone once it reaches 0.ref(), player(), talismanId(), grade(), remainingCharges().

Declared @Nullable: grade() on InscribeCompleteEvent; stack().

Weapon spirits

plugin.siren.API.WeaponSpiritEvents v0.9.0 - 6 events (3 cancellable) covering a Life-Bound Treasure's spirit (器灵) stirring awake, gaining a level, and being fed Qi through /cultivation spirit nurture. Every event carries the current owner's PlayerRef. Player-facing docs: Weapon Spirits.

EventCancellableFiresExposes
PreWeaponSpiritAwakenEventYesA weapon's spirit is about to stir awake - both the refinement-tier and kill bars are already met. Cancel to hold it right at the threshold; the banked kill count is untouched, so the very next qualifying kill fires this again.owner(), item(), killsAtAwaken().
WeaponSpiritAwakenEventNoA weapon's spirit just stirred awake; item is the stack after the awaken is written.owner(), item(), killsAtAwaken().
PreWeaponSpiritLevelUpEventYesA weapon spirit is about to level up. Cancel to hold it at its current level (the Xp is still banked).owner(), item(), oldLevel(), newLevel().
WeaponSpiritLevelUpEventNoA weapon's spirit just gained a level; item is the stack after the level-up is written.owner(), item(), newLevel(), reachedMaturity().
PreWeaponSpiritNurtureEventYesA player is about to feed their weapon spirit Qi. Cancel to refuse the feeding entirely (no Qi spent, no XP gained). setQiToConvert changes how much of the offered Qi converts; this is where WeaponSpirit-Nurture-Max-Qi-Per-Use is applied by default, so an addon adjusts this field rather than the config.owner(), item(), qiToConvert() / setQiToConvert(float).
WeaponSpiritNurtureEventNoA player just fed their weapon spirit Qi; fires once per successful nurture call, whether or not it also leveled the spirit up.owner(), item(), qiConverted(), xpGained().

Spirit beasts

Companions, and the breeding that makes new ones.

Beasts

plugin.siren.API.BeastEvents - 20 events (10 cancellable) covering taming, hatching, binding, summoning and a companion's own cultivation, and since 0.7.2 its arts, evolutions and mounts, plus the Spirit Beast Den broods. Player-facing docs: Spirit Beasts.

Enums declared here:

EventCancellableFiresExposes
PreBeastTameAttemptEventYesA tame is about to be rolled. Cancel to refuse the attempt outright - no talisman is spent.ref(), player(), species(), chance() / setChance(float) - 0-1, after realm and dao-resonance weighting.
BeastTameAttemptEventNoThe roll resolved.ref(), player(), species(), chance(), success().
PreBeastBindEventYesA companion is about to be bound. Cancel and the cultivator keeps whatever beast they already had - the talisman or egg is still spent.ref(), player(), species(), source().
BeastBindEventNoThe companion is bound, replacing whatever came before.ref(), player(), species(), source().
PreBeastSummonEventYesA companion's body is about to be spawned. Cancel and the player is told the summon failed.owner(), player(), species().
BeastSummonEventNoThe body spawned beside its master.owner(), player(), beast() (the spawned entity), species().
PreBeastDismissEventYesA companion is about to be sent home or freed.owner(), player(), reason().
BeastDismissEventNoThe body left the world.owner(), player(), reason().
PreBeastXpGainEventYesA companion is about to gain XP. Fires for every source - meditation shares, kills and hand-feeding alike.owner(), player(), beast() (the live SpiritBeastComponent - realm, stage, banked XP), amount() / setAmount(float).
BeastXpGainEventNoThe XP was banked.owner(), player(), amount(), stagesGained() (0 when it only banked progress).
PreBeastAdvanceEventYesA companion is about to advance a stage. Cancel to hold it where it is - the XP for that stage is already spent, so this costs it the progress.owner(), player(), fromRealm(), fromStage().
BeastAdvanceEventNoThe companion advanced. Fires once per stage.owner(), player(), realm(), stage().
PreBeastArtEventYesA companion is about to perform an art. Cancel to stop it — the cooldown is only stamped once the effect has actually run, so a vetoed art costs the beast nothing and it tries again on its next opening.owner(), player(), beast(), art(), commanded() (false when the beast chose it itself).
BeastArtEventNoA companion performed one of its arts.owner(), player(), beast(), art(), commanded().
PreBeastEvolveEventYesA companion is about to be put through the evolution ritual. The Qi has not been taken at this point, so a veto costs the cultivator nothing.owner(), player(), beast(), from(), to(), successChance() / setSuccessChance(float).
BeastEvolveEventNoThe ritual resolved. succeeded() says which way, and to() is null on failure.owner(), player(), beast(), from(), to(), succeeded().
PreBeastMountEventYesA companion is about to be summoned in its rideable body. Cancel to refuse the mount.owner(), player(), beast(), species().
BeastMountEventNoThe companion was summoned in its rideable body.owner(), player(), beast(), species().
PreBeastDenBroodEventYesA Spirit Beast Den is about to roll a brood. Cancel to leave the den without one - it tries again the next time its appraisal sweep runs. Nothing is persisted yet, so a veto costs the keeper nothing.world(), x(), y(), z(), species(), appraisalScore().
BeastDenBroodEventNoA Spirit Beast Den rolled a brood, now waiting to be claimed once due.world(), x(), y(), z(), species(), appraisalScore().

Declared @Nullable: player() (every event that carries it); to() on BeastEvolveEvent.

Config keys these override: PreBeastTameAttemptEvent.setChance corresponds to Tame-Chance-Bonus-Per-Realm, Tame-Chance-Element-Match-Bonus and Tame-Chance-Max (with Tame-Consume-Talisman-On-Failure deciding what a miss costs); PreBeastXpGainEvent.setAmount to Beast-Xp-Per-Kill, Beast-Xp-Own-Kill-Multiplier and Beast-Xp-Per-Qi-Item-Point. Whole-system gate: Beasts-Enabled, with Beast-Arts-Enabled, Beast-Evolution-Enabled and Beast-Mounts-Enabled gating the three 0.7.2 systems independently.

Beast breeding

plugin.siren.API.BreedingEvents v0.9.0 - 4 events (2 cancellable) covering the two-cultivator ritual at a Beast Pen that produces an egg, and hatching a bred egg into a bound companion. A bred egg still fires BeastEvents.BeastBindEvent when it hatches. Player-facing docs: Spirit Beasts.

EventCancellableFiresExposes
PreBeastBreedEventYesA breeding ritual is about to be seated on both cultivators - fires once both are confirmed within a shared pen with enough Qi, before either cultivator's Qi is touched. Cancel to refuse it outright; nothing has been spent yet.offerer(), accepter(), parentA(), parentB().
BeastBreedEventNoA breeding ritual completed and produced an egg for recipient (whoever sent the offer). Fires once, from the ritual's own completion, not from either offer or accept.recipient(), partner(), parentA(), parentB(), offspring(), quality().
PreBeastEggHatchEventYesA bred egg is about to hatch. Cancel to refuse it - the egg is not consumed, matching a dormant wild-hatched egg (see BeastEggHatchInteraction's bred branch).ref(), player(), offspring(), metadata().
BeastEggHatchEventNoA bred egg hatched into a bound companion.ref(), player(), offspring(), metadata().

Declared @Nullable: partner(); player() (every event that carries it).

Sects, markets and company

Sects and the wars between them, the people who serve a hall, Merit, parties and partners, the Auction House and the Bounty Board.

Sects

plugin.siren.API.SectEvents - 40 events (18 cancellable) covering founding, disbanding, membership, ranks, abbreviations, halls, hall inscriptions, banners, diplomacy and the Sect Library. Player-facing docs: Sects, and the /sect suite on the Commands page.

Listeners hold SectManager's lock - see Registering and threading. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreSectCreateEventYesA player is about to found a sect (宗门). Cancel and it reports a refused creation.leader(), name() / setName(String) - re-validated for shape and uniqueness afterward.
SectCreateEventNoThe sect was founded and indexed.leader(), sect().
PreSectDisbandEventYesA sect is about to be disbanded. Cancel to keep it standing.leader(), sect().
SectDisbandEventNoMembers are unindexed and formations released. sect is the now-orphaned object, still readable for its final roster.leader(), sect().
PreSectInviteEventYesAn invite is about to be issued.inviter(), invitee(), sect(), expiryMillis() / setExpiryMillis(long) - wall-clock millis at which it lapses.
SectInviteEventNoThe invite is pending, not accepted.inviter(), invitee(), sect().
PreSectJoinEventYesA player is about to join. Cancel and the invite or request survives, so they can try again.player(), sect(), method().
SectJoinEventNoThey are on the roster.player(), sect(), method().
PreSectLeaveEventYesA player is about to leave or be kicked. Cancel to keep them on the roster.player(), sect(), reason(), actor().
SectLeaveEventNoThey are off the roster. actor is the kicker for KICKED, the player themselves for LEFT.player(), sect(), reason(), actor().
PreSectJoinRequestEventYesA join request is about to be queued against a REQUEST-policy sect.player(), sect().
SectJoinRequestEventNoThe request is queued.player(), sect().
SectJoinRequestDeniedEventNoA manager denied a pending request.manager(), applicant(), sect().
PreSectRankChangeEventYesAn elder rank is about to change.leader(), target(), sect(), promoted() - true for member to elder.
SectRankChangeEventNoThe rank changed.leader(), target(), sect(), promoted().
PreSectMottoChangeEventYesA motto is about to be set.manager(), sect(), oldMotto(), motto() / setMotto(String) - the 60-character cap still applies afterward.
SectMottoChangeEventNoThe motto was replaced.manager(), sect(), oldMotto(), newMotto().
PreSectJoinPolicyChangeEventYesA join policy is about to change.leader(), sect(), oldPolicy(), policy() / setPolicy(Sect.JoinPolicy).
SectJoinPolicyChangeEventNoThe policy changed.leader(), sect(), oldPolicy(), newPolicy().
PreSectRenameEventYesA sect is about to be renamed.leader(), sect(), oldName(), newName() / setNewName(String) - re-validated afterward.
SectRenameEventNoRenamed; formations, hall springs and pending invites already carried over.leader(), sect(), oldName(), newName().
PreSectInscriptionChangeEventYesA hall inscription is about to change.leader(), sect(), oldTechniqueId(), newTechniqueId() / setNewTechniqueId(String) - empty scours it away.
SectInscriptionChangeEventNoThe inscription changed.leader(), sect(), oldTechniqueId(), newTechniqueId().
PreSectHallClaimEventYesA hall is about to be claimed. Cancel and it reports the chunk as already claimed.leader(), sect(), world(), chunkX(), chunkZ(), veinTier().
SectHallClaimEventNoThe sect claimed, or moved, its hall onto a spirit vein.leader(), sect(), world(), chunkX(), chunkZ(), veinTier().
PreSectHallCaptureEventYesA hall is about to change hands to a victorious besieger. Cancel to leave it with its defender - the siege still resolves as won.attacker(), defender(), world(), chunkX(), chunkZ(), veinTier().
SectHallCaptureEventNoA won siege transferred a hall. The defender is now hall-less.attacker(), defender(), world(), chunkX(), chunkZ(), veinTier().
PreSectBannerChangeEventYesA sect's banner is about to change. Cancel to keep the old one flying. The id is not re-validated after a listener rewrites it - an unregistered id is not an error, it simply resolves to the vein-tier default light.manager(), sect(), oldBannerId(), bannerId() / setBannerId(String).
SectBannerChangeEventNoThe banner changed. The hall's standing light swaps to it on its next pulse.manager(), sect(), oldBannerId(), newBannerId().
PreSectAbbreviationChangeEvent v0.8.0YesA sect's tag is about to change. Cancel to refuse it; setAbbreviation to rewrite it — the 3–6 letters/digits rule and the server-wide uniqueness check still apply afterward.leader(), sect(), oldAbbreviation(), abbreviation() / setAbbreviation(String).
SectAbbreviationChangeEvent v0.8.0NoThe tag changed.leader(), sect(), oldAbbreviation(), newAbbreviation().
SectAllianceFormedEventNoTwo sects formed a mutual Alliance. actor is whoever accepted the proposal.actor(), sectA(), sectB().
SectAllianceBrokenEventNoA standing Alliance ended; either side's leader may break one unilaterally. actor is whoever broke it.actor(), sectA(), sectB().
SectRelationChangedEventNoA NON_AGGRESSION or TRADE relation formed or ended between two sects. Never fired for ALLIANCE: the Alliance pair above covers that rung, so a listener on both is not double-posted. formed is true when the relation just started.actor(), sectA(), sectB(), kind(), formed().
PreSectLibraryResearchStartEventYesA sect's Library is about to start researching a new subject. Cancel to leave it researching whatever it was (or nothing).actor(), sect(), oldTechniqueId(), newTechniqueId().
SectLibraryResearchStartEventNoA sect's Library started researching a new subject (replacing whatever, if anything, it was researching before).actor(), sect(), techniqueId().
PreSectLibraryCompileEventYesA sect's Library is about to compile an exclusive technique. Cancel to hold it at the door - progress stays at/above cost and this fires again on the next settle.sect(), techniqueId().
SectLibraryCompileEventNoA sect's Library finished compiling an exclusive technique manual.sect(), techniqueId().
PreSectLibraryRedeemEventYesA member is about to redeem their copy of a compiled Library manual. Cancel to refuse it (not marked redeemed, so it may be retried); setManualItemId to substitute a different item.member(), sect(), techniqueId(), manualItemId() / setManualItemId(String).
SectLibraryRedeemEventNoA member redeemed their own copy of a compiled Library manual.member(), sect(), techniqueId(), manualItemId().

Config keys these override: PreSectInviteEvent.setExpiryMillis corresponds to Sect-Invite-Expiry-Seconds; hall claims are gated by Sect-Hall-Min-Vein-Tier; inscriptions by Sect-Inscription-Enabled; the whole subsystem by Sects-Enabled and Sect-Max-Members. The banner ids the banner events carry are whatever is in the banner registry - the six built-ins plus anything an addon added through registerSectBanner.

Wars

plugin.siren.API.WarEvents - 9 events (4 cancellable) covering declaring a siege on a rival's hall, how it resolves, the Siege Banners of Formations 2.0, and the reward an allied sect's reinforcement earns. Player-facing docs: Sect Wars.

A siege is either a Suppress (the default: a win never moves the hall, so it fires no capture event) or a Conquest, whose win transfers the hall through SectManager.captureHall. That transfer is SectEvents.PreSectHallCaptureEvent and SectHallCaptureEvent: veto the pre-event to let a conquest be won without the hall moving. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreWarDeclareEventYesA siege is about to be declared. Cancel and the caller is told wars are disabled.attacker(), defender(), windowMillis() / setWindowMillis(long) - how long the attacker has to complete their hold.
WarDeclareEventNoThe siege is live and both sides have been told.attacker(), defender(), siege().
PreSiegeCaptureEventYesA siege is about to be won. Cancel to leave it running - the attacker keeps holding and will trip this again on their next presence tick, so cancel only while some condition of yours is unmet.attacker(), defender(), siege().
SiegeCaptureEventNoThe attacker held long enough. The hall transfer has already been attempted and the defender's immunity cooldown started.attacker(), defender(), siege().
SiegeFailEventNoThe siege ended with the hall still in its defender's hands.siege(), reason().
PreSiegeBannerPlaceEventYesFormations 2.0: a Siege Banner is about to be placed. Cancel to refuse it (the placer is told something prevents the placement). Fired before any monitor is taken, so no lock is held and a listener may read WarManager state; the registration cap and the siege's liveness are re-checked authoritatively afterward.attackerSect(), defenderSect(), world(), x(), y(), z().
SiegeBannerPlaceEventNoFormations 2.0: a Siege Banner was placed and is now standing.banner().
SiegeBannerBreakEventNoFormations 2.0: a Siege Banner was broken - by anyone, no protection. breakerSide tells which side (if any) the breaker belonged to.banner(), breaker(), breakerSide().
PreWarReinforcementRewardEventYesAn allied sect's reinforcement of a successfully defended (LAPSED) siege is about to be rewarded. Cancel to withhold this ally's reward without affecting any other qualifying ally; each reinforcing sect gets its own event. setContributionPerMember rescales this payout only.reinforcingSect(), defender(), siege(), contributionPerMember() / setContributionPerMember(int).

Config keys these override: PreWarDeclareEvent.setWindowMillis corresponds to War-Window-Seconds; related keys are War-Required-Hold-Seconds, War-Cooldown-Hours, War-Defender-Grace-Seconds, War-Requires-Defender-Online and the Wars-Enabled gate.

Sect guardians

plugin.siren.API.GuardianEvents v0.10.2 - 4 events (1 cancellable) covering stationing a guardian NPC at a sect's hall, one falling, and the last one falling. Stationing is cancellable. Player-facing docs: Sects.

EventCancellableFiresExposes
PreSectGuardianStationEventYesA member is about to station a new guardian post. Cancel to refuse it (reported to the actor as the station attempt simply failing) - fired after every gate in SectGuardianManager.station passes but before the contribution cost is spent, so a veto never costs the actor anything.sect(), actor(), postIndex().
SectGuardianStationedEventNoA member successfully stationed a new guardian post at their sect's hall - the post is purchased; the live NPC body itself appears on SectGuardianSystem's next heartbeat once its chunk is resident.sect(), actor(), postIndex().
SectGuardianFellEventNoOne living guardian died. killerPlayerRef is null when the killing blow never resolved to a player (environmental damage, a formation trap, or the killer despawned before resolution).sect(), postIndex(), killerPlayerRef(), consumed().
SectGuardiansFallenEventNoThe last living guardian at this sect's hall just died (or vanished into a permanent consumed state), so none remains at this instant. Purely informational: no hold-time bonus and no penalty.sect().

Declared @Nullable: killerPlayerRef().

Dao sermons

plugin.siren.API.SermonEvents v0.10.2 - 4 events (1 cancellable) covering a cultivator lecturing (讲道) at their own sect's hall, a meditating listener's first qualifying pulse, and how the sermon ends. Starting one is cancellable. Player-facing docs: Dao Sermon.

This class mixes UUID and PlayerRef identifiers; resolve a UUID with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreSermonStartEventYesA sect member is about to start a sermon. Cancel to refuse it (reported to the actor as the start attempt simply failing) - fired after every gate in SermonManager.start passes but before the contribution cost is spent or the cooldown is stamped, so a veto never costs the actor anything.sect(), lecturer(), element().
SermonStartEventNoA sect member successfully started a sermon at their own sect's hall.sect(), lecturer(), element(), endsAtMillis().
SermonListenerQualifiedEventNoOne listener's FIRST qualifying pulse of this sermon - fired once per (sermon, listener) pair, never again for the same pair even across a diminished-rate or hard-stop transition.lecturerUuid(), listener(), element(), amountApplied().
SermonEndEventNoA sermon ended - naturally (duration elapsed), early (/cultivation sermon stop), or abnormally (lecturer left the hall chunk/world, hall lost, sect besieged, feature disabled).lecturerUuid(), sectName(), qualifiedListenerCount(), meritAwarded(), forfeited().

Merit

plugin.siren.API.MeritEvents v0.10.2 - 3 events (1 cancellable) covering gaining Merit (功德) and ranking up on it. The gain is cancellable and re-weighable. Player-facing docs: Merit.

EventCancellableFiresExposes
PreMeritGainEventYesMerit is about to be credited for a deed. Cancel to pay nothing; setAmount to re-weigh the deed.player(), deed(), amount() / setAmount(float).
MeritGainEventNoMerit was actually credited - total/multiplier are the values after the throttle and caps were applied.player(), deed(), amount(), total(), multiplier().
MeritRankUpEventNoThis player's Merit rank changed - fired once per crossing, de-duplicated the same way DaoEvents.PathChangeEvent is.player(), from(), to().

Declared @Nullable: player() (every event that carries it).

Parties

plugin.siren.API.PartyEvents v0.9.1 - 5 events (1 cancellable) covering ad-hoc, session-only grouping: forming, joining, leaving, disbanding, and inviting. Player-facing docs: Party and Dungeons.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PrePartyInviteEventYesAn invite is about to be sent. Cancel to refuse it silently (the inviter still receives the manager's own result).inviterUuid(), targetUuid().
PartyFormedEventNoA solo leader's first invite was accepted - the party now exists as a group, not just a leader waiting alone.leaderUuid(), firstMemberUuid().
PartyMemberJoinedEventNoA cultivator joined an already-formed party - not the party's very first member, which is PartyFormedEvent.leaderUuid(), memberUuid().
PartyMemberLeftEventNoA cultivator left a party that still has members remaining afterward. If the leader left, leaderUuid is the newly promoted leader.leaderUuid(), memberUuid().
PartyDisbandedEventNoA party stopped existing - either the leader disbanded it outright, or its last member left. formerMembers is a snapshot, not a live view.leaderUuid(), formerMembers().

Partnered cultivation

plugin.siren.API.PartnerEvents v0.9.1 - 3 events (1 cancellable) covering two married cultivators drawing on the same spirit vein together: pairing, unpairing, and the Qi bonus the pairing grants. Inert unless the Marriage mod is installed (CultivationAPI.isMarriageInstalled()). Player-facing docs: Partnered Cultivation.

This class mixes UUID and PlayerRef identifiers; resolve a UUID with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PartnerPairedEventNoA cultivator transitioned from unpartnered to partnered - both spouses sat down to meditate within Partner-Radius-Blocks of each other in the same world.ref(), player(), partnerUuid().
PartnerUnpairedEventNoA cultivator went from partnered back to unpartnered: their spouse stood up, left the radius, changed world, or the pairing otherwise lapsed. formerPartnerUuid is who they were partnered with. Not fired for the spouse who stood up first - only the side still seated is told.ref(), player(), formerPartnerUuid().
PrePartnerQiBonusEventYesA partnered cultivator's meditation Qi bonus is about to apply. Cancel to deny the bonus entirely for this tick (equivalent to sitting alone); adjust setMultiplier to re-scale how much extra Qi this specific pairing draws.ref(), player(), partnerUuid(), multiplier() / setMultiplier(float).

Declared @Nullable: player() (every event that carries it); partnerUuid() on PrePartnerQiBonusEvent.

Auction House and Traveling Merchant

plugin.siren.API.MarketEvents v0.9.0 - 12 events (3 cancellable) covering listing, buying, bidding on, cancelling and expiring auction listings, plus the Traveling Merchant NPC opening and closing for business. Player-facing docs: Market.

Auction mutations run inside AuctionManager's synchronized methods, so a listener here is holding that lock: read what you need, hand it off, and return. Never call back into AuctionManager's mutating methods from a listener. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreAuctionListEventYesA player is about to list an item. Cancel to refuse it (reported as blocked) - nothing has been touched in the seller's inventory yet.seller(), itemId(), quantity(), price() / setPrice(long).
AuctionListingCreatedEventNoA listing was created and is now on the shelf.seller(), listing().
PreAuctionBuyEventYesA player is about to buy a listing. Cancel to refuse it - nothing has moved yet, the listing stays on the shelf.buyer(), listing().
AuctionListingSoldEventNoA listing sold. listing is the now-removed shelf entry; sellerProceeds is what the seller was credited after the house cut.listing(), buyer(), sellerProceeds().
AuctionListingCancelledEventNoA seller pulled their own still-active listing.listing().
AuctionListingExpiredEventNoAn unsold listing aged past Market-Auction-Listing-Duration-Hours and was returned to its seller as a claimable parcel.listing().
MerchantOpenedEventNoThe Traveling Merchant opened for business in a world.world(), x(), y(), z().
MerchantClosedEventNoThe Traveling Merchant's visit ended and the NPC despawned.world().
PreAuctionBidEventYesA player is about to bid on a timed auction. Cancel to refuse it - nothing has moved and the previous high bidder has not been refunded. Deliberately no setter: silently changing the amount a player just committed to would be a surprise, so the supported way to re-tune a bid is to cancel it.bidder(), listing(), amount().
AuctionBidPlacedEventNoA bid was placed on a timed auction and is now the standing high bid.listing(), bidder(), amount().
AuctionOutbidEventNoA standing high bidder was outbid, or their bid lost to an early buyout - their stones just landed in their claimable parcels (see /market claim), even while offline.listing(), outbidBidder(), refundedAmount().
AuctionClosedEventNoA timed auction closed with at least one bid - the highest bidder won the item and the seller was credited sellerProceeds after the house cut. Mirrors AuctionListingSoldEvent's shape for the timed path; a zero-bid close fires the existing AuctionListingExpiredEvent instead, unchanged.listing(), winner(), sellerProceeds().

Bounty board

plugin.siren.API.BountyEvents v0.10.2 - 4 events (2 cancellable) covering a contract being posted through BountyManager.post, a kill about to credit a SLAY contract, and a completed contract being claimed. Player-facing docs: Bounty Board.

A contract generated by the board's own rotation does not fire BountyPostedEvent - only one pushed on through BountyManager.post does. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
BountyPostedEventNoA contract was explicitly posted to the board - it is already in the board list and already persisted. bounty is the live board object, not a copy. Read it; do not mutate it.bounty().
PreBountyClaimEventYesA completed contract is about to pay out. Cancel to refuse the claim outright - nothing has been granted and nothing marked claimed yet, so the acceptance simply stays completed-but-unclaimed and the player may try again.bountyId(), claimantUuid().
PreBountyCreditEventYesA player kill is about to move a SLAY contract's progress. Cancel to skip this acceptance entirely - no progress is recorded and the killer is told nothing. This is the one verdict both the board and whatever posted the decree obey: the poster listens here, applies the rules it applies to its own reward (pair cooldowns, season caps, sandbox and admin-bypass exclusions) and cancels when its own answer is no. Fires once per matching live acceptance, after every board-side gate and before any progress is written.bountyId(), killerUuid(), victimUuid(), bounty().
BountyClaimedEventNoA contract was fully claimed - every promised reward component landed and the acceptance has been marked claimed. Never fires for a BountyManager.Result.PARTIAL claim, which leaves the acceptance completed-but-unclaimed for a retry.bountyId(), claimantUuid(), rewardQi().
Locking

Every event here fires on the world thread of whatever caused it, with BountyManager's own monitor held. Do not block, and do not call back into BountyManager's mutators (post, takeDown, retire, accept, claim, creditPlayerSlain, sweep) from a listener: they are synchronized on that same monitor, so re-entering one would run it in the middle of the operation that fired the event. The lock order this mod establishes is SeasonManager then your addon then BountyManager, so a listener here already holds the last lock in that chain - reaching for an addon monitor from inside one of these events runs it backwards and can deadlock against a season opening on another world thread. Keep listener bodies lock-free.

Duels and contests

Everything two players do against, or beside, each other by consent.

Duels

plugin.siren.API.DuelEvents - 8 events (3 cancellable) covering challenging, accepting, and how a Qi-wager duel resolves. Player-facing docs: Duels, and /cultivation duel on the Commands page.

A duel routinely outlives one participant's session - that is exactly what voids it. Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreDuelChallengeEventYesA challenge is about to be issued.challenger(), challenged(), wager() / setWager(int) - the configured maximum is re-checked afterward.
DuelChallengeEventNoThe challenge is pending the other player's answer.challenger(), challenged(), wager().
DuelDeclineEventNoThe challenge was declined; no duel started.challenger(), challenged().
PreDuelStartEventYesA duel is about to start. Cancel to refuse it - the challenge is consumed either way, so the challenger must issue a fresh one.challenger(), challenged(), wager() / setWager(int) - re-tuning it here is what the live duel actually pays out.
DuelStartEventNoThe duel is live; both players are flagged as dueling.challenger(), challenged(), wager().
DuelEndEventNoThe duel ended. For DEATH and YIELD the winner and loser are meaningful and the payout is queued; for VOIDED they are simply the two participants and nothing changes hands.winner(), loser(), wager(), reason(), killer() - for a DEATH, the player whose own damage landed the killing blow, or null when it was not a player's (lava, a fall, a formation trap) or could not be attributed; always null for YIELD and VOIDED.
PreDuelPayoutEventYesA decided duel's wager is about to move. Cancel to let the winner take nothing.winner(), loser(), amount() / setAmount(int) - what actually moves is still capped by what the loser holds.
DuelPayoutEventNoThe wager moved. amount is what the loser could cover, which is exactly what the winner gained.winner(), loser(), amount().

Declared @Nullable: killer().

Config keys these override: setWager corresponds to Duel-Max-Wager; related keys are Duel-Challenge-Expiry-Seconds, Duel-Max-Duration-Seconds, Duel-Overrides-World-Pvp and the Duels-Enabled gate.

Dao duels

plugin.siren.API.DaoDuelEvents v0.10.1 - 3 events (2 cancellable) covering the Spirit Stone escrow layered on top of the plain Qi-wager duels of DuelEvents: the challenge and the payout are cancellable, and the end is reported once the escrow settles. Player-facing docs: Duels.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreDaoDuelChallengeEventYesA Dao Duel challenge is about to be issued (the Qi wager, if any, is handled entirely by DuelEvents.PreDuelChallengeEvent - this is the Spirit Stone side only). Cancel to refuse it; setSpiritStoneAmount to re-tune the stake (the configured maximum is re-checked afterward).challenger(), challenged(), spiritStoneAmount() / setSpiritStoneAmount(long).
PreDaoDuelPayoutEventYesA decided Dao Duel's Spirit Stone pot is about to move. Cancel to return the loser's stake to the loser untouched; setAmount re-scales how much of the loser's escrowed stake is forfeited, capped by what was actually escrowed. The winner's own stake is unaffected either way.winner(), loser(), amount() / setAmount(long).
DaoDuelEndEventNoA decided Dao Duel's Spirit Stone pot has been settled - spiritStoneAmountPaid is what actually moved from the loser's escrow (plus any elemental bonus that could be charged) to the winner; the winner's own stake is always returned to them separately and is not counted here.winner(), loser(), spiritStoneAmountPaid(), winnerElement(), loserElement(), winnerCountered(), reason(), decidedByOpponentDamage().

Declared @Nullable: winnerElement(); loserElement().

Tournament wagers

plugin.siren.API.WagerEvents v0.10.2 - 4 events (1 cancellable) covering spectators staking Spirit Stones on individual Dao Duel Tournament matches - a parimutuel market with no house account. Player-facing docs: Duels.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreWagerPlacedEventYesA spectator is about to be charged for a stake. Every configured gate (feature enabled, market open, not a duelist, not a live entrant, not a sect-mate, per-bet, per-tournament and per-pool limits) has already passed. Cancel to refuse the bet with no stones moved and nothing recorded. The amount is deliberately not settable.matchNumber(), bettor(), backed(), amount().
WagerPlacedEventNoA spectator's Spirit Stones have been charged and their stake recorded. backed is the duelist they are backing.matchNumber(), bettor(), backed(), amount().
WagerMarketResolvedEventNoA market resolved and paid out. payouts maps each bettor to the total stones handed back (their own stake plus their share of the losing pool), stakes maps each to what they put in, and burned is the house cut plus the indivisible remainder - stones destroyed rather than paid. Fires while the tournament manager's monitor is held; see the locking note below.matchNumber(), winner(), stakes(), payouts(), winnersPool(), losersPool(), burned().
WagerMarketVoidedEventNoA market paid nobody and every stake in refunds was returned in full. reason() says why. Fires while the tournament manager's monitor is held; see the locking note below.matchNumber(), reason(), refunds().
Locking

PreWagerPlacedEvent and WagerPlacedEvent fire with no monitor of this mod's held, on the betting player's own command thread. WagerMarketResolvedEvent and WagerMarketVoidedEvent fire while DaoDuelTournamentManager's monitor is held, from inside its match resolution. The rule for those two is absolute: no locking and no cross-manager calls. Do not synchronize, do not touch a concurrency primitive another thread could be holding, and never call back into DaoDuelTournamentManager, TournamentWagerManager, DaoDuelManager or DuelManager. Copy what you need out of the event and hand it to your own queue. Restart refunds fire nothing at all - they run inside Cultivation's setup(), before any addon has registered a listener - so read the claimable parcels instead.

Tea ceremony

plugin.siren.API.TeaEvents v0.10.3 - 4 events (1 cancellable) covering the two-player reflex-timing duet (茶道): starting (cancellable), every step resolving, and how the ceremony ends. Player-facing docs: Tea Ceremony.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

Enums declared here:

EventCancellableFiresExposes
PreTeaCeremonyStartEventYesA pending invite is about to be accepted and a ceremony is about to begin (herbs not yet spent). Cancel to refuse it - the invite is consumed either way, so the offerer must send a fresh one.offerer(), accepter(), herbCost() / setHerbCost(int).
TeaCeremonyStartEventNoA ceremony actually began - herbs already spent, the session already live.sessionId(), playerA(), playerB().
TeaStepResolvedEventNoOne prompt (a single BOIL/STEEP/POUR/SERVE step) was scored.sessionId(), round(), step(), playerACorrect(), playerBCorrect(), scoreDelta().
TeaCeremonyEndEventNoA ceremony ended, one way or another - harmony/band are only meaningful when reason is EndReason.COMPLETED; every voided reason reports harmony=0f/band=DISCORDANT and rewarded=false, since no reward is ever paid on an abnormal end (herbs still stay spent either way - this event does not cover that, it is applied unconditionally at ceremony start).sessionId(), playerA(), playerB(), reason(), harmony(), band(), rewarded().

Weiqi

plugin.siren.API.WeiqiEvents v0.10.3 - 7 events (2 cancellable) covering Weiqi (围棋 / Go): invites and declines, the match starting, every move, and how a match resolves. The invite and the start are cancellable. Player-facing docs: Weiqi.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreWeiqiInviteEventYesAn invite is about to be sent. Cancel to refuse it outright (e.g. an addon-enforced cooldown or block list).inviter(), invitee().
WeiqiInviteEventNoAn invite was sent and is now pending the other player's answer.inviter(), invitee().
WeiqiDeclineEventNoAn invite was declined; no match started.inviter(), invitee().
PreWeiqiMatchStartEventYesA match is about to start (the accepter just accepted a pending invite). Cancel to refuse it - the invite is consumed either way, so the inviter must send a fresh one.blackUuid(), whiteUuid(), boardSize(), komi() / setKomi(float).
WeiqiMatchStartEventNoA match is now live. blackUuid is always the ACCEPTER (moves first, a courtesy to the invited player); whiteUuid is the original inviter.matchId(), blackUuid(), whiteUuid(), boardSize(), komi().
WeiqiMoveEventNoOne ply resolved - either a stone placed at index (a flat y * boardSize + x board index, decodable via WeiqiBoard.xOf/yOf) or a pass, never both.matchId(), player(), color(), index(), pass(), capturedCount().
WeiqiMatchEndEventNoA match ended. winner is null for WeiqiMatch.Outcome.ABANDONED/ WeiqiMatch.Outcome.TIMED_OUT (nobody won an undecided match); for WeiqiMatch.Outcome.TWO_PASS/WeiqiMatch.Outcome.RESIGNED it is always set.matchId(), blackUuid(), whiteUuid(), winner(), outcome(), blackScore(), whiteScore(), totalMoves().

Declared @Nullable: winner().

The world

Formations and abodes, the sky, the server-wide encounters, the places you travel to, and what you find there.

Formations

plugin.siren.API.FormationEvents - 8 events (4 cancellable) covering laying and dispersing chunk-anchored spirit arrays, an altar-anchored array changing tier, and the Trapping array wounding an intruder. Player-facing docs: Formations.

This class mixes UUID and PlayerRef identifiers; resolve a UUID with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreFormationPlaceEventYesAn array is about to be laid. Cancel and it reports the ground as warded.owner(), sectName() (empty when the array is theirs personally), world(), chunkX(), chunkZ(), type(), radiusChunks() / setRadiusChunks(int).
FormationPlaceEventNoThe array is live on its chunk.owner(), sectName(), formation().
PreFormationRemoveEventYesAn array is about to be dispersed by its controller. Cancel to leave it standing.owner(), sectName(), formation().
FormationRemoveEventNoDispersed. formation is the now-removed object.owner(), sectName(), formation().
PreFormationTrapStrikeEventYesA Trapping array (困仙阵) is about to wound an intruder. Cancel to spare them this tick entirely - no particle, no debuff, no damage. Set damage to 0 to root them harmlessly.ref(), player(), world(), chunkX(), chunkZ(), damage() / setDamage(float) - post-lethality-cap, pre-armor; 0 when the intruder's health could not be resolved.
FormationTrapStrikeEventNoThe intruder was wounded.ref(), player(), world(), chunkX(), chunkZ(), damage().
PreFormationTierChangeEventYesAn altar-anchored formation's tier is about to change. Cancel to refuse the change entirely; setToTier can dampen (but not fully block) a rise.formation(), fromTier(), toTier() / setToTier(int).
FormationTierChangeEventNoAn altar-anchored formation's tier just changed. Purely informational - the tier is already live on formation by the time this fires.formation(), fromTier(), toTier().

Declared @Nullable: player() (every event that carries it).

Config keys these override: setRadiusChunks corresponds to QiGathering-Radius-Chunks, Warding-Radius-Chunks or Trapping-Radius-Chunks, depending on type(); PreFormationTrapStrikeEvent.setDamage to Trapping-Damage-Percent-Of-Max-Health (with Trapping-Lethal, Trapping-Interval-Seconds and Trapping-Debuff-Duration-Seconds alongside it). Whole-system gates: Formations-Enabled and Max-Formations-Per-Controller.

Dwellings

plugin.siren.API.DwellingEvents - 12 events (6 cancellable) covering claiming a Cave Abode (洞府), its Spirit Spring, upkeep, and closed-door seclusion. Player-facing docs: Cave Abodes.

Sect hall springs are created and moved automatically to follow their sect's hall; that housekeeping deliberately fires nothing. Listen for the sect hall claim and capture events instead - they are what causes it. This class mixes UUID and PlayerRef identifiers; resolve a UUID with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreDwellingClaimEventYesAn abode is about to be claimed or moved. Cancel and it reports warded ground.owner(), world(), chunkX(), chunkZ(), veinTier(), moved(), radiusChunks() / setRadiusChunks(int).
DwellingClaimEventNoThe abode was claimed, or an existing one moved - a move keeps the banked spring and paid upkeep.owner(), dwelling(), moved().
PreDwellingAbandonEventYesAn abode is about to be given up. Cancel to keep it standing.owner(), dwelling().
DwellingAbandonEventNoGiven up; whatever the spring held went with it.owner(), dwelling().
PreDwellingLapseEventYesAn abode is about to be reclaimed for unpaid upkeep. Cancel to reprieve it - it survives only until the next sweep re-tests it, so cancel from a listener that keeps deciding, not a one-off.dwelling().
DwellingLapseEventNoA personal abode was reclaimed by the world, past its grace period.dwelling().
PreSpringCollectEventYesA Spirit Spring is about to be emptied. Cancel to leave it full.dwelling(), amount() / setAmount(float) - changes what the collector walks away with; the spring is emptied regardless.
SpringCollectEventNoThe spring was emptied. amount is the Qi handed over - the caller credits it.dwelling(), amount().
PreUpkeepDepositEventYesUpkeep is about to be paid. Cancel and it reports nothing banked.dwelling(), itemId(), quantity(), hours() / setHours(float) - what the whole offering is worth, before the banked-hours cap.
UpkeepDepositEventNoUpkeep was paid in. hoursGranted is what was actually banked, which is less than what was offered once the cap is hit.dwelling(), itemId(), quantity(), hoursGranted().
PreSeclusionSettleEventYesA seclusion retreat is about to pay out. Cancel to forfeit it - the player is told the spring is dry.ref(), player(), dwelling(), hours() (already capped by Seclusion-Max-Hours), qi() / setQi(float) (already limited to what the spring could cover when Seclusion-Drains-Spring is on).
SeclusionSettleEventNoThe cultivator emerged and was paid for their absence.ref(), player(), dwelling(), hours(), qi().

Declared @Nullable: player() (every event that carries it).

Config keys these override: PreDwellingClaimEvent.setRadiusChunks corresponds to Dwelling-Radius-Chunks; PreSpringCollectEvent.setAmount to Spring-Qi-Per-Hour, Spring-Pool-Base-Cap and Spring-Pool-Cap-Per-Realm; PreUpkeepDepositEvent.setHours to Upkeep-Item-Hours, capped by Upkeep-Max-Banked-Hours; PreDwellingLapseEvent to Upkeep-Grace-Hours; PreSeclusionSettleEvent.setQi to Seclusion-Qi-Per-Hour. Whole-system gates: Dwellings-Enabled, Upkeep-Enabled, Seclusion-Enabled.

Celestial events

plugin.siren.API.CelestialEvents v0.8.0 - 3 events (1 cancellable) covering the server-wide sky. Both hooks are generic to every celestial type rather than one pair per phenomenon, so switch on type().id() to react to a particular one. Player-facing docs: Celestial Events.

EventCancellableFiresExposes
PreCelestialEventStartEventYesAn event is about to begin, by schedule or by /celestial start. Cancel to veto it - the scheduler simply re-arms rather than substituting another event. setDurationMinutes runs it longer or shorter than its type's default.type(), durationMinutes() / setDurationMinutes(float), forcedByAdmin().
CelestialEventStartEventNoIt began. The sky is already switching for every player not under a tribulation storm.type(), startedAtMillis(), endsAtMillis(), forcedByAdmin().
CelestialEventEndEventNoIt ended, by timer or by /celestial stop.type(), startedAtMillis(), endedAtMillis(), forcedByAdmin().

Registering an event type of your own is a separate call - see Registries. These events carry a CelestialEventType, so a listener sees addon-contributed events on exactly the same footing as the built-ins. CelestialManager itself registers four - spirit_tide, meteor_shower, blood_moon and starfall_convergence - and three more are registered by the subsystems that own them: heavenly_schism (Path War), beast_tide (Beast Tide) and beast_omen (Calamity Beast). The last two register with a weight of zero and are never picked by the scheduler; their own managers start them, and fire their own events alongside these.

Beast tides

plugin.siren.API.TideEvents v0.9.0 - 6 events (3 cancellable) covering a siege (兽潮) on a sect hall or a Cave Abode: the warning phase, each wave, and the resolution. These fire alongside CelestialEvents for the tide's own celestial type, not instead of them. Player-facing docs: Beast Tide.

EventCancellableFiresExposes
PreTideStartEventYesA target has been picked and a tide is about to enter WARNING. Cancel to abandon this pick entirely - the scheduler simply waits for its next check.worldName(), abode(), targetName(), waveCount() / setWaveCount(int).
TideStartEventNoA tide's warning phase has begun - its target is locked and its beacon will spawn shortly.assaultId(), worldName(), abode(), targetName(), waveCount().
PreTideWaveEventYesOne wave is about to be triggered. Cancel to skip just this attempt (the timer and wave index still advance - defenders get a breather, not an extra wave).assaultId(), waveIndex().
TideWaveEventNoOne wave was just triggered (or attempted - see PreTideWaveEvent).assaultId(), waveIndex(), waveCount().
PreTideResolveEventYesThe assault is about to resolve. Cancel to skip applying the reward (WON) or suppression/cooldown (LOST) - the assault still ends and cleans up either way, only the outcome-specific side effect is skipped. setSuppressionSteps/setSuppressionDurationMinutes retune a LOSS; setContributionReward retunes a WIN.assaultId(), result(), suppressionSteps() / setSuppressionSteps(int), suppressionDurationMinutes() / setSuppressionDurationMinutes(float), contributionReward() / setContributionReward(int).
TideResolveEventNoThe assault is over - the outcome (and any reward/suppression) has already been applied.assaultId(), worldName(), abode(), targetName(), result().

Calamity beasts

plugin.siren.API.WorldBossEvents v0.9.1 - 4 events (1 cancellable) covering a wandering, solo world boss (灾劫兽) with no fixed target: its omen phase beginning, the boss actually spawning, and the encounter resolving. Only the start is cancellable. Player-facing docs: Calamity Beast.

EventCancellableFiresExposes
PreWorldBossStartEventYesA Calamity Beast is about to begin its omen phase. Cancel to abandon this start entirely - the scheduler simply waits for its next check. It carries no re-tunable numbers.worldName(), roleId(), position().
WorldBossStartEventNoA Calamity Beast's OMEN phase has begun - its spawn point and species are locked and the omen sky/announcement is already live.encounterId(), worldName(), roleId(), position().
WorldBossSpawnEventNoThe OMEN ended and the boss NPC now actually exists in the world (ACTIVE phase begun).encounterId(), worldName(), roleId(), position(), bossRef().
WorldBossResolveEventNoThe encounter is over, for any reason - any reward payout has already been queued.encounterId(), worldName(), roleId(), result(), contributorCount(), totalContribution().

Void rifts

plugin.siren.API.RiftEvents v0.10.0 - 5 events (1 cancellable) covering a randomly triggered, server-wide world event: a rift opens, throws a fixed number of corrupted-beast waves, spawns a boss-tier Warden, then resolves SEALED or COLLAPSED. Only the open is cancellable. Player-facing docs: Void Rift.

EventCancellableFiresExposes
PreRiftOpenEventYesA Void Rift is about to begin its opening phase. Cancel to abandon this open entirely - the scheduler simply waits for its next check. It carries no re-tunable numbers.worldName(), position().
RiftOpenEventNoA Void Rift's OPENING phase has begun - its anchor and spawn point are locked and the world-event marker/announcement is already live.riftId(), worldName(), position().
RiftWaveEventNoOne corrupted-beast wave was just thrown during the ASSAULT phase. waveIndex counts from 0; waveCount is the total for this rift.riftId(), worldName(), position(), waveIndex(), waveCount(), spawnConfigId().
RiftWardenSpawnEventNoThe ASSAULT phase ended and the Warden NPC now actually exists in the world (WARDEN phase begun).riftId(), worldName(), roleId(), position(), wardenRef().
RiftResolveEventNoThe encounter is over, for any reason - any reward payout has already been queued. outcome is always RiftPhase.SEALED or RiftPhase.COLLAPSED.riftId(), worldName(), position(), outcome(), contributorCount(), totalContribution().

Rogue cultivators

plugin.siren.API.RogueEvents v0.10.2 - 3 events (1 cancellable) covering a rogue cultivator NPC spawning and being slain. The spawn is cancellable. Player-facing docs: Rogue Cultivators.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreRogueCultivatorSpawnEventYesA Rogue Cultivator is about to spawn near anchorPosition in worldName. Cancel to refuse the spawn entirely - the world's next-spawn-due clock still advances normally, exactly like a refused roll that found no valid ground.worldName(), anchorPosition(), anchorPlayerUuid(), realmOrdinal() / setRealmOrdinal(int), daoElementName() / setDaoElementName(String).
RogueCultivatorSpawnedEventNoA Rogue Cultivator has finished spawning and is tagged/live in the world.encounterId(), npcRef(), worldName(), realmOrdinal(), daoElementName().
RogueCultivatorSlainEventNoA Rogue Cultivator has been slain - any manual/core/Testament roll has already resolved by the time this fires.encounterId(), killerUuid(), worldName(), manualAwarded(), bonusCoreAwarded(), testamentAwarded().

Declared @Nullable: killerUuid().

Wandering rivals

plugin.siren.API.RivalEvents v0.9.1 - 2 events (1 cancellable) covering challenging a Wandering Rival Cultivator NPC, and its defeat payout. Player-facing docs: Wandering Rival Cultivators.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreRivalChallengeEventYesA player has right-clicked to challenge a Wandering Rival Cultivator. Cancel to refuse the challenge entirely - the rival stays ROAMING and the interaction is a no-op, the same "silence is a valid answer" shape DuelEvents.PreDuelChallengeEvent gives a duel challenge.encounterId(), challengerUuid(), npcRef().
RivalDefeatedEventNoA challenged Wandering Rival Cultivator has been defeated - the winner's reward has already been paid.encounterId(), winnerUuid(), worldName(), qiAwarded(), manualAwarded(), materialAwarded().

Secret realms

plugin.siren.API.SecretRealmEvents v0.9.0 - 2 events (none cancellable) covering a site's barrier coming down (openable) or going back up (closed). Post-only: opening and closing are deterministic scheduler outcomes, not requests anything downstream could meaningfully veto. Player-facing docs: Secret Realms.

EventCancellableFiresExposes
SecretRealmOpenEventNoA Secret Realm site opened - the barrier is already down and it can be entered.siteId(), tier(), world(), chunkX(), chunkZ(), source(), sectName(), closesAtMillis().
SecretRealmCloseEventNoA Secret Realm site closed. forced is true only for an admin's immediate override (SecretRealmManager.forceClose); false for the realm's own natural close after its duration/grace window.siteId(), tier(), world(), chunkX(), chunkZ(), forced().

Secret realm depths

plugin.siren.API.DepthsEvents v0.9.1 - 4 events (none cancellable) covering a solo Depths run: starting, a floor clearing (with the escrow reward it just rolled), extraction actually paying out, and the run ending for any reason. Post-only, for the same reason as SecretRealmEvents. Player-facing docs: Depths.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
DepthsRunStartEventNoA solo Depths run has started, floor 1 about to spawn.runId(), ownerUuid(), siteId(), world().
DepthsFloorClearEventNoA floor's beasts are all dead - escrowSize is the run's total unbanked escrow count after this floor's roll (0 or 1 higher than before it, since a roll can miss).runId(), ownerUuid(), floor(), escrowSize().
DepthsExtractEventNoThe run's escrow was just actually granted - fired only when rewardsGranted is above zero, whether the player chose Extract or the run auto-extracted (logout/realm-close).runId(), ownerUuid(), depthReached(), rewardsGranted().
DepthsRunEndEventNoThe run is over, for any reason - fired once, after any DepthsExtractEvent the same ending also produced.runId(), ownerUuid(), reason(), depthReached().

Trial Pagoda

plugin.siren.API.PagodaEvents v0.10.2 - 3 events (1 cancellable) covering clearing a floor, a run ending, and the floor-clear reward, which is cancellable and re-scalable. Player-facing docs: Trial Pagoda.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

One enum lives here: PagodaRunEndReason (DIED, LEFT, TIMED_OUT, DISCONNECTED).

EventCancellableFiresExposes
PrePagodaRewardEventYesA floor-clear reward is about to be granted. Cancel to grant nothing at all; the caller still advances the run and the record either way.playerUuid(), floor(), spiritStoneAmount() / setSpiritStoneAmount(long), qiAmount() / setQiAmount(float).
PagodaFloorClearedEventNoA Trial Pagoda floor was cleared - newRecord is true only on a first clear (the highest-floor record actually advanced), false on a replay.playerUuid(), floor(), newRecord().
PagodaRunEndedEventNoA Trial Pagoda run ended, for any reason. floorReached is the floor the run was on when it ended, not necessarily a cleared floor - a death mid-floor never advances the player's own record.playerUuid(), floorReached(), reason().

Treasure and ruins

plugin.siren.API.TreasureEvents v0.9.1 - 2 events (1 cancellable) covering claiming a Buried Cache or entering a Ruin Vault - one pair covers both tiers, since both are claiming the same kind of site. Player-facing docs: Treasure and Ruin Exploration.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
PreTreasureClaimEventYesA player is about to claim a Buried Cache or enter a Ruin Vault. Cancel to refuse it entirely - the site stays unclaimed and the command is a no-op, the same "silence is a valid answer" shape RivalEvents.PreRivalChallengeEvent gives a rival challenge.siteId(), tier(), playerUuid().
TreasureClaimedEventNoA Treasure site has been claimed/entered - the reward has already been paid.siteId(), tier(), playerUuid(), worldName(), qiAwarded(), manualAwarded(), materialAwarded().

Transmission array

plugin.siren.API.ArrayEvents v0.10.3 - 2 events (1 cancellable) covering traveling through the Transmission Array network between a Cave Abode, a sect hall, a Secret Realm site, the Sea of Consciousness or the Heavenly Realm. The trip is cancellable and re-priceable. Player-facing docs: Transmission Array.

EventCancellableFiresExposes
PreArrayTravelEventYesA cultivator is about to travel through the array. Cancel to refuse the trip (nothing is charged and nothing moves); setQiCost re-prices it. The cost is charged, and re-checked against the traveler's banked Qi, after this fires.ref(), player(), destinationKind(), destinationId(), qiCost() / setQiCost(float).
ArrayTravelEventNoA cultivator successfully traveled through the array. qiCost is the final amount actually charged (0 for an admin bypass).ref(), player(), destinationKind(), destinationId(), qiCost().

Declared @Nullable: ref() (every event that carries it); player() (every event that carries it).

Quests, seasons and the store

Story, the shared season clock, and purchases arriving from the web store.

Wandering-NPC quests

plugin.siren.API.QuestEvents v0.9.1 - 5 events (1 cancellable) covering accepting a quest chain from a wandering NPC giver, advancing through its steps, completing it with the reward fully paid, or abandoning it. Only the accept is cancellable. Player-facing docs: Quest Lines.

PreQuestAcceptEvent is the one vetoable decision (an addon has real reasons to refuse a particular chain to a particular player); the rest report the next state of a machine that is already running.

EventCancellableFiresExposes
PreQuestAcceptEventYesA player is about to take on a chain. Every built-in refusal (realm gate, once-per-account, cooldown, chain cap) has already passed; cancel to refuse it anyway. Nothing is written when a listener cancels.ref(), player(), chainId(), giverRoleId().
QuestAcceptEventNoA player has taken on a quest chain - the progress row is written and, for a site chain, its site is already rolled.ref(), player(), chainId(), giverRoleId().
QuestStepAdvanceEventNoA step of a chain just cleared.ref(), player(), chainId(), completedStepIndex(), nextStepIndex(), finalStep().
QuestCompleteEventNoA chain is fully finished AND fully paid - every reward component landed. Fires exactly once per run, at the moment the run turns COMPLETED.ref(), player(), chainId(), completionCount().
QuestAbandonEventNoA player gave up on an in-progress run. Never fires for a run that was awaiting a reward - that one cannot be abandoned.ref(), player(), chainId(), stepIndex().

Declared @Nullable: player() (every event that carries it).

Narrative campaign

plugin.siren.API.CampaignEvents v0.9.1 - 3 events (1 cancellable) covering the quest-line system's chapter advances (including a campaign's very first chapter) and campaign completion. Only the advance is cancellable. Player-facing docs: Quest Lines.

PreCampaignChapterAdvanceEvent is the one vetoable decision. It also fires for a campaign's very first chapter, with fromChapterIndex() of -1, so an addon can refuse a campaign's start the same way it refuses any later advance.

EventCancellableFiresExposes
PreCampaignChapterAdvanceEventYesA player is about to move into a new chapter (or begin the campaign's first one, when fromChapterIndex is -1). Every built-in refusal (realm gate, already active/completed) has already passed; cancel to refuse it anyway.ref(), player(), campaignId(), fromChapterIndex(), toChapterIndex().
CampaignChapterAdvanceEventNoA chapter just became current for a player - either the campaign's very first chapter (via CampaignManager.start) or an advance off a finished one. newChapterIndex may still be locked behind its own realm floor; check CampaignManager.getProgress if that matters to a listener.ref(), player(), campaignId(), previousChapterIndex(), newChapterIndex().
CampaignCompleteEventNoEvery chapter of a campaign is finished. Fires exactly once per run, the moment the last chapter's chain(s) complete.ref(), player(), campaignId().

Declared @Nullable: player() (every event that carries it).

Seasons

plugin.siren.API.SeasonEvents v0.10.2 - 2 events (none cancellable) covering the shared season cadence opening and closing a season. Post-only. Player-facing docs: Seasonal Cycle.

EventCancellableFiresExposes
SeasonOpenEventNoA new season opened and its start timestamp is already persisted. seasonId is the season now running (the closing one's id plus one, or 1 on a fresh install). On a rollover it fires immediately after SeasonCloseEvent for the season before; on a fresh install, or the first tick after Season-Enabled is switched on, it fires alone.seasonId(), startedAtMillis().
SeasonCloseEventNoA season closed: its Hall of Fame rows and champion set are already written, and the leaderboard baselines are about to be invalidated by the next open. seasonId is the season that just ended. Fires before Path War's season totals are reset, so a listener still sees the closing season's totals intact.seasonId(), closedAtMillis().
Ask, do not wait

A server that was offline across a season boundary closes and reopens it during Cultivation's own setup(), which runs before any addon's. That open reaches no addon listener, because the list is still empty. An addon that must know which season is running has to ask CultivationAPI.getCurrentSeasonId() from its own setup() or first tick, and treat these events purely as "it changed while I was watching".

Both fire on whichever world thread reached the season pump first - server-wide, not a particular player's world - with SeasonManager's own monitor held. Do not block, and hop per player (CompletableFuture.runAsync(task, theirWorld)) before touching anybody's components. These two are the head of the lock chain described under the Bounty board.

Store benefits

plugin.siren.API.StoreBenefitEvents v0.8.0 - 3 events (none cancellable) covering the Treasure Pavilion sync. None is cancellable: the entitlement list is a fact about a purchase, not a decision the server gets a vote on.

Players are identified by UUID here, not PlayerRef; resolve one with Universe.get().getPlayer(uuid) and check isValid().

EventCancellableFiresExposes
BenefitGrantedEventNoA player's entitlement to a benefit was seen for the first time this session.playerUuid(), benefit().
BenefitRevokedEventNoA player stopped appearing on the list — a refund, or a benefit disabled on this server.playerUuid(), benefit().
SyncCompletedEventNoA full sync pass finished, on the interval or forced with /cultivation store recheck.products(), granted(), revoked(), failedProducts().

failedProducts() is how a listener tells "nobody owns it" from "we could not reach the server" - the sync fails open, so an unreachable endpoint leaves everyone exactly as they were rather than revoking them.

Unlike the gameplay events, these fire on the remote checker thread, not on a world thread: grants and revokes are discovered by an HTTP sweep and there is no world in hand. A listener that touches a player must find them with Universe.get().getPlayer(uuid), check isValid(), and hop onto their world thread before reading or writing any component.

See also