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.
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:
event.setCancelled(true)vetoes the operation. The mod checks the flag the instant dispatch returns and abandons the operation, leaving no state changed. Nothing is rolled back because nothing was applied yet - that is the whole reason these fire before the fact. A cancelled pre-event means the matching post-event never fires.- The setters on the event re-tune the numbers driving the operation - a breakthrough's Qi cost, a technique's cooldown, a tribulation bolt's damage, a tame's odds. Whatever the listeners leave in those fields when dispatch finishes is what the mod actually uses. This is the supported way to reshape a mechanic from an addon without touching the Config files.
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:
| Method | Returns | Description |
|---|---|---|
isCancelled() | boolean | Whether 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:
| Class | Fires on | While holding | What to do |
|---|---|---|---|
SectEvents | The world thread of the player concerned. | SectManager's lock. | Read what you need, hand it off, return. Never call SectManager's mutating methods. |
MarketEvents | The world thread of the player concerned. | AuctionManager's lock. | The same: never call back into AuctionManager's mutators. |
BountyEvents | The 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. |
SeasonEvents | Whichever 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. |
WagerEvents | The 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. |
StoreBenefitEvents | The remote checker thread - not a world thread. | Nothing. | Find the player, check isValid(), hop onto their world thread before touching a component. |
ProfileEvents | The owning world thread, outside any ticking system. | Nothing. | Unusually, writing components directly through the Store is safe here. |
BodyTemperingEvents, FistEvents | Inside the damage pipeline, on the world thread that owns the entity being hit. | Nothing. | Keep it cheap; never write to the Store. |
LifespanEvents | LifespanSystem'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:
- Progression, ladders and saves - 8 classes, 84 events. The ladder itself, the two bare-body ladders beside it, injuries, a second life, and the saves a player keeps.
- The Dao, arts and oaths - 5 classes, 52 events. Elements, Yin-Yang and karma, the Heavenly and Personal Daos, techniques, combat depth and Heavenly Oaths.
- Items and crafting - 5 classes, 31 events. Drops, pills, cores and manuals, and the crafts that make something new: refinement, alchemy, forging and talismans, plus the spirit inside a weapon.
- Spirit beasts - 2 classes, 24 events. Companions, and the breeding that makes new ones.
- Sects, markets and company - 9 classes, 84 events. Sects and the wars between them, the people who serve a hall, Merit, parties and partners, the Auction House and the Bounty Board.
- Duels and contests - 5 classes, 26 events. Everything two players do against, or beside, each other by consent.
- The world - 13 classes, 56 events. Formations and abodes, the sky, the server-wide encounters, the places you travel to, and what you find there.
- Quests, seasons and the store - 4 classes, 13 events. Story, the shared season clock, and purchases arriving from the web store.
| Class | Covers | Events | Cancellable |
|---|---|---|---|
CultivationEvents | Cultivation core | 46 | 21 |
BodyTemperingEvents | Body tempering | 6 | 3 |
FistEvents | Fist arts | 4 | 2 |
LifespanEvents | Lifespan | 6 | 2 |
MeridianEvents | Meridian injuries | 5 | 3 |
SoulEscapeEvents | Nascent Soul Escape | 8 | 3 |
LegacyEvents | Legacy | 2 | 1 |
ProfileEvents | Cultivation profiles | 7 | 3 |
DaoEvents | Dao and karma | 14 | 6 |
DaoComprehensionEvents | Dao comprehension | 11 | 5 |
TechniqueEvents | Techniques | 15 | 7 |
CombatDepthEvents | Combat depth | 4 | 2 |
OathEvents | Heavenly oaths | 8 | 4 |
ItemEvents | Items | 12 | 6 |
AlchemyEvents | Alchemy | 5 | 2 |
ForgingEvents | Forging | 2 | 1 |
TalismanEvents | Talismans | 6 | 3 |
WeaponSpiritEvents | Weapon spirits | 6 | 3 |
BeastEvents | Beasts | 20 | 10 |
BreedingEvents | Beast breeding | 4 | 2 |
SectEvents | Sects | 40 | 18 |
WarEvents | Wars | 9 | 4 |
GuardianEvents | Sect guardians | 4 | 1 |
SermonEvents | Dao sermons | 4 | 1 |
MeritEvents | Merit | 3 | 1 |
PartyEvents | Parties | 5 | 1 |
PartnerEvents | Partnered cultivation | 3 | 1 |
MarketEvents | Auction House and Traveling Merchant | 12 | 3 |
BountyEvents | Bounty board | 4 | 2 |
DuelEvents | Duels | 8 | 3 |
DaoDuelEvents | Dao duels | 3 | 2 |
WagerEvents | Tournament wagers | 4 | 1 |
TeaEvents | Tea ceremony | 4 | 1 |
WeiqiEvents | Weiqi | 7 | 2 |
FormationEvents | Formations | 8 | 4 |
DwellingEvents | Dwellings | 12 | 6 |
CelestialEvents | Celestial events | 3 | 1 |
TideEvents | Beast tides | 6 | 3 |
WorldBossEvents | Calamity beasts | 4 | 1 |
RiftEvents | Void rifts | 5 | 1 |
RogueEvents | Rogue cultivators | 3 | 1 |
RivalEvents | Wandering rivals | 2 | 1 |
SecretRealmEvents | Secret realms | 2 | 0 |
DepthsEvents | Secret realm depths | 4 | 0 |
PagodaEvents | Trial Pagoda | 3 | 1 |
TreasureEvents | Treasure and ruins | 2 | 1 |
ArrayEvents | Transmission array | 2 | 1 |
QuestEvents | Wandering-NPC quests | 5 | 1 |
CampaignEvents | Narrative campaign | 3 | 1 |
SeasonEvents | Seasons | 2 | 0 |
StoreBenefitEvents | Store benefits | 3 | 0 |
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:
RitualType:BREAKTHROUGH- Peak stage → next realm;ADVANCEMENT- A sub-stage step within the current realm;REFINEMENT- Weapon refinement (炼器) - tempering the held weapon rather than the cultivator.MeditationStopReason:COMMAND- They toggled it off themselves with /cultivation meditate;MOVEMENT- They wandered away from the spot they sat down at;RITUAL_COMPLETE- A realm breakthrough or a sub-stage advancement finished, and the cultivator rises from the seat they earned it in.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreQiGainEvent | Yes | Qi 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). |
QiGainEvent | No | Qi was banked. | ref(), player(), amount(), totalQi(). |
PreMeditationStartEvent | Yes | A player is about to sit down to meditate. Cancel to keep them on their feet. | ref(), player(). |
MeditationStartEvent | No | A player sat down to meditate. | ref(), player(). |
PreMeditationStopEvent | Yes | A player is about to stop meditating. Cancel to make a ritual unbreakable, or to suppress the movement-cancel. | ref(), player(), reason(). |
MeditationStopEvent | No | A player stopped. Any ritual penalty has already been applied. | ref(), player(), reason(). |
PreRitualStartEvent | Yes | A 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). |
RitualStartEvent | No | The tick that first accrued ritual progress. | ref(), player(), type(). |
PreBreakthroughEvent | Yes | A 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). |
BreakthroughEvent | No | The breakthrough completed; their stage is EARLY. | ref(), player(), newRealm(). |
PreAdvancementEvent | Yes | A sub-stage advancement is about to complete. Cancel to hold them where they are. | ref(), player(), realm(), fromStage(), toStage(), qiCost() / setQiCost(float). |
AdvancementEvent | No | The advancement completed. | ref(), player(), realm(), newStage(). |
PreDemotionEvent | Yes | A 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(). |
DemotionEvent | No | The demotion landed; banked Qi is wiped and the granting skill points revoked. | ref(), player(), realm(), oldStage(), newStage(), wasBreakthrough(). |
PreTribulationStrikeEvent | Yes | Tribulation 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(). |
TribulationStrikeEvent | No | The bolt fell. | ref(), player(), damage(), breakthroughRitual(). |
PreHeartDevilTrialEvent | Yes | A 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(). |
HeartDevilTrialEvent | No | A pulse resolved. | ref(), player(), composureRemaining(), deviated(), breakthroughRitual(). |
PreQiDeviationEvent | Yes | Composure 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(). |
QiDeviationEvent | No | The penalty landed. Exactly one of demoted() / qiLost() carries it. | ref(), player(), demoted(), qiLost(), breakthroughRitual(). |
PreRaceChangeEvent | Yes | A race is about to change. Cancel and the race menu simply reports no change. | ref(), player(), oldRace(), newRace(), adminOverride(). |
RaceChangeEvent | No | The race changed. Not fired when an admin sets the race a player already has. | ref(), player(), oldRace(), newRace(), adminOverride(). |
PreSkillUnlockEvent | Yes | A skill tree node is about to be unlocked. Cancel and their points are not spent. | ref(), player(), node(), pointCost() / setPointCost(int). |
SkillUnlockEvent | No | The node is unlocked; points spent and modifiers re-applied. | ref(), player(), node(). |
PreRespecEvent | Yes | A skill tree respec is about to run. | ref(), player(), refundedPoints() / setRefundedPoints(int). |
RespecEvent | No | Every node was cleared and the points handed back. | ref(), player(), refundedPoints(). |
PreLifeBoundLevelUpEvent | Yes | A 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(). |
LifeBoundLevelUpEvent | No | The treasure leveled. | owner(), item() (already updated), newLevel(). |
PreAscensionEvent v0.8.0 | Yes | A 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.0 | No | An 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.0 | No | An attempt ended in failure. | ref(), player(), abandoned() (walked away, rather than killed). |
PreTribulationOmenEvent | Yes | A 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(). |
TribulationOmenEvent | No | A 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(). |
PreDreamTrialEvent | Yes | A 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(). |
DreamTrialEvent | No | The 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(). |
PreInnerDemonTrialEvent | Yes | An 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(). |
InnerDemonTrialEvent | No | An 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(). |
PreCleanseRiteEvent | Yes | A 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(). |
CleanseRiteEvent | No | A 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(). |
PreRitualAbortEvent | Yes | A 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(). |
RitualAbortEvent | No | A 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(). |
AscensionLegacyEvent | No | An 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(). |
PreReincarnationEvent | Yes | A 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(). |
ReincarnationEvent | No | A 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(). |
ReincarnationFailedEvent | No | A cultivator's Reincarnation attempt ended in failure. | ref(), player(), abandoned(). |
PlayerKillEvent | No | One 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreXpGainEvent | Yes | A 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(). |
XpGainEvent | No | The XP was banked. | ref(), player(), amount(), level(). |
PreLevelUpEvent | Yes | A tempering level is about to be gained. Cancel to hold the cultivator where they are. | ref(), player(), fromLevel(), toLevel(). |
LevelUpEvent | No | The level was gained. Fires once per level when a single blow crosses several. | ref(), player(), fromLevel(), toLevel(). |
PreStageBreakthroughEvent | Yes | About 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(). |
StageBreakthroughEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreXpGainEvent | Yes | About 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). |
XpGainEvent | No | XP was banked. amount is what was actually granted, after any pre-event scaling. | ref(), player(), level(), amount(). |
PreLevelUpEvent | Yes | About to gain a level. Cancelling holds the cultivator where they are; the XP stays banked. | ref(), player(), fromLevel(), toLevel(). |
LevelUpEvent | No | A 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:
ExtendSource:BREAKTHROUGH- A realm high-water-mark rise - purely informational (seeLifespanManager's own doc on why this skips the Pre event);PILL- The Longevity Pill;ADMIN-/cultivation admin extendlifespan.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreLifespanExtendEvent | Yes | About 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). |
LifespanExtendEvent | No | Bonus 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(). |
LifespanWitherEvent | No | A cultivator's Lifespan budget hit 0 and they entered the Withering grace state. | ref(), player(). |
LifespanRestoreEvent | No | A 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(). |
PreLifespanExpireEvent | Yes | A 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(). |
LifespanExpireEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreMeridianInjuryEvent | Yes | An 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). |
MeridianInjuryEvent | No | An injury was inflicted (or deepened) - the write has already landed. | ref(), player(), injury(), magnitude(), durationSeconds(). |
PreMeridianCureEvent | Yes | An injury is about to be cured. Cancel to refuse it (it stays active). | ref(), player(), injury(), cureSource(). |
MeridianCureEvent | No | An injury fully cleared (wait-it-out, meditation recovery, or an explicit cure). | ref(), player(), injury(), cureSource(). |
PreMeridianSpillEvent | Yes | A 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).
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreSoulEscapeBeginEvent | Yes | The 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). |
SoulEscapeBeginEvent | No | The 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(). |
PreSoulSurviveEvent | Yes | A 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). |
SoulEscapeSurviveEvent | No | The 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(). |
PreSoulExtinguishEvent | Yes | A 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). |
SoulEscapeExtinguishEvent | No | A 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(). |
SoulEscapeTimeoutEvent | No | The window ran out before the soul reached a sanctuary, so the escape ended in real death. | soul(), killer(). |
SoulEscapeForfeitEvent | No | The 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreLegacyPayoutEvent | Yes | A 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). |
LegacyPayoutEvent | No | A 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()).
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreProfileSwitchEvent | Yes | A 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(). |
ProfileSwitchEvent | No | The 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(). |
PreProfileCreateEvent | Yes | A fresh cultivator is about to be created. Cancel to refuse it - no slot is used. | ref(), player(). |
ProfileCreateEvent | No | The profile exists and the player is on it. | ref(), player(), profile(). |
PreProfileDeleteEvent | Yes | A save is about to be erased for good. Cancel to keep it. | ref(), player(), profile(). |
ProfileDeleteEvent | No | The save is gone. Erase your own state for it here. | ref(), player(), profile(). |
ProfileExpireEvent | No | A 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:
ElementChangeReason:CHOSEN- They picked it themselves - the free first choice, or a paid switch;DRIFT- Their deeds overwhelmed the chosen path and it converted on its own.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreDaoElementChangeEvent | Yes | A 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). |
DaoElementChangeEvent | No | The element changed. | ref(), player(), dao(), oldElement(), newElement(), reason(), qiCost(). |
PreDaoAffinityGainEvent | Yes | Deed affinity is about to be added toward an element - the pressure that eventually causes drift. | player(), dao(), element(), amount() / setAmount(float). |
DaoAffinityGainEvent | No | The affinity was added. | player(), dao(), element(), amount(). |
DaoDriftWarningEvent | No | A cultivator was warned their dao is drifting. Fires once per newly-threatening element. | player(), dao(), chosenElement(), driftingTo(). |
PreAlignmentShiftEvent | Yes | A Yin-Yang shift is about to be applied. The race-bias split happens after this. | dao(), amount() / setAmount(float) (the raw shift), towardYin(). |
AlignmentShiftEvent | No | The balance moved. | dao(), yin(), yang() - the amounts actually added after race bias split the shift. |
PrePathChangeEvent | Yes | A moral path is about to change. Cancel and the underlying balance is untouched, so this only suppresses the reclassification. | player(), dao(), oldPath(), newPath(). |
PathChangeEvent | No | The path changed and was announced. | player(), dao(), oldPath(), newPath(). |
PreKarmaGainEvent | Yes | Karma (业力) 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). |
KarmaGainEvent | No | Karma was charged. | player(), dao(), amount(), total() (after the Karma-Max cap), farmedKill(). |
KarmaClearedEvent | No | Karma was worked off - by enduring a tribulation strike, or by the wall-clock decay of not killing anyone. | dao(), amount(), total(), fromTribulation(). |
PreDevilHarvestEvent | Yes | A Devil-path (魔道) cultivator is about to harvest banked Qi from a slain player. | killer(), killerPlayer(), qi() / setQi(float). |
DevilHarvestEvent | No | The 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreHeavenlyDaoGainEvent | Yes | The Heavenly Dao track is about to advance. Cancel to refuse it; setAmount to re-scale. | player(), comprehension(), amount() / setAmount(float). |
HeavenlyDaoGainEvent | No | The 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(). |
PreHeavenlyDaoRankChangeEvent | Yes | The 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(). |
HeavenlyDaoRankChangeEvent | No | The player's HeavenlyDaoRank changed (and was announced to them). | player(), comprehension(), oldRank(), newRank(). |
PrePersonalDaoComprehensionEvent | Yes | A Personal Dao's comprehension is about to advance. Cancel to refuse it; setAmount to re-scale. | player(), comprehension(), dao(), amount() / setAmount(float). |
PersonalDaoComprehensionEvent | No | A Personal Dao's comprehension advanced. amount is what was actually applied; total is the value after. | player(), comprehension(), dao(), amount(), total(). |
PrePersonalDaoManifestEvent | Yes | A Personal Dao is about to manifest. Cancel to leave it comprehended but unmanifested. | player(), comprehension(), dao(). |
PersonalDaoManifestEvent | No | A Personal Dao manifested for this player. | player(), comprehension(), dao(). |
PersonalDaoSetAsideEvent | No | A manifested Personal Dao was set aside. | player(), comprehension(), dao(). |
PreDaoEnlightenmentEvent | Yes | A 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). |
DaoEnlightenmentEvent | No | A 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:
BuffType:IRON_BODY- Iron Body - flat incoming-damage reduction;QI_INFUSION- Qi Infusion - outgoing damage bonus;QI_BARRIER- Qi Barrier - an absorbing shield pool;CLOUD_STEP- Cloud Step - a movement speed multiplier.FlightStopReason:TOGGLE- They toggled it off themselves;QI_EXHAUSTED- Their Qi ran out mid-flight;DEATH- They died; flight is cleaned up defensively.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreTechniquePerformEvent | Yes | A 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). |
TechniquePerformEvent | No | The Qi is spent, the cooldown stamped, and the effect has run. | ref(), player(), technique(), qiCost(). |
PreTechniqueLearnEvent | Yes | A 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(). |
TechniqueLearnEvent | No | The art is learned. Sect-taught arts are resolved live and never fire this - listen for the sect inscription events instead. | ref(), player(), techniqueId(). |
PreSwordFlightStartEvent | Yes | A cultivator is about to take to the sky on their sword. | ref(), player(), horizontalSpeed() / setHorizontalSpeed(float), verticalSpeed() / setVerticalSpeed(float). |
SwordFlightStartEvent | No | They are airborne. | ref(), player(), horizontalSpeed(), verticalSpeed(). |
PreSwordFlightStopEvent | Yes | A 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(). |
SwordFlightStopEvent | No | They came down; their mount, if any, has already despawned. | ref(), player(), reason(). |
PreTechniqueBuffApplyEvent | Yes | A timed technique buff is about to be applied. | ref(), player(), type(), durationSeconds() / setDurationSeconds(float), magnitude() / setMagnitude(float). |
TechniqueBuffApplyEvent | No | The 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(). |
TechniqueBuffExpireEvent | No | Cloud Step's speed multiplier was reverted, on expiry or on cleanup. | ref(), player(), type(). |
PreTechniqueMasteryAdvanceEvent | Yes | An 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). |
TechniqueMasteryAdvanceEvent | No | An art’s mastery rose a rung. | ref(), player(), techniqueId(), stage(). |
PreTechniqueFusionEvent v0.8.0 | Yes | A 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.0 | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreTechniqueInterruptEvent | Yes | A 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). |
TechniqueInterruptEvent | No | A charging cultivator's gathering was broken by a hard-enough hit. damageAmount is the (possibly re-scaled) figure PreTechniqueInterruptEvent settled on. | ref(), player(), damageAmount(). |
PreWuxingPvpRewardEvent | Yes | A 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). |
WuxingPvpRewardEvent | No | A 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:
FlawCleanseRoute:EXPIRED- Its wall-clock timer simply ran out;ITEM- A cleansing item was consumed;COMPANION- Meditating beside the party the broken oath was owed to burned it down early.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreOathSwearEvent | Yes | A 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(). |
OathSwornEvent | No | A pending offer was accepted and the oath is now sworn (binding). | oathId(), type(), partyA(), partyB(), stake(), sectId(). |
PreOathBreachEvent | Yes | A 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). |
OathBreachEvent | No | A sworn oath was broken and its penalty has already been applied. | oathId(), type(), breaker(), victim(), qiLost(), karmaGained(). |
PreOathFlawCleanseEvent | Yes | A 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(). |
OathFlawCleanseEvent | No | A cultivator's Dao-Heart Flaw was cleansed by route. | player(), route(). |
PreOathDissolveEvent | Yes | A 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(). |
OathDissolveEvent | No | A 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:
LootType:CULTIVATION_CORE- A cultivation core of some tier - Spirit, Profound or Divine (readitemIdfor which);BEAST_EGG- A Spirit Beast Egg;SPIRIT_STONE- A Spirit Stone - the abode upkeep currency;MANUAL- A cultivation manual;TREASURE_MATERIAL- A crafting material found in a Buried Cache or a Ruin Vault - seeplugin.siren.Utils.Treasure.TreasureCacheManager.RefinementOutcome:SUCCESS- The weapon took the element and reached its target tier;DESTROYED- The attempt failed and the weapon was destroyed;DEMOTED- The attempt failed and the weapon lost a tier;FAILED- The attempt failed harmlessly - only the Qi was lost.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreLootDropEvent | Yes | A cultivation drop is about to be handed over. Cancel to deny it. | ref(), player(), type(), itemId() / setItemId(String) - substitute a different item entirely. |
LootDropEvent | No | The 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(). |
PreManualReadEvent | Yes | A 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). |
ManualReadEvent | No | The manual was read and its teaching applied. | ref(), player(), techniqueId(), skillNodeId(). |
PrePillConsumeEvent | Yes | A spirit pill is about to take effect. Cancel and the pill is not consumed. | ref(), player(), effect() - the interaction's configured effect id. |
PillConsumeEvent | No | The pill was consumed and its effect applied. | ref(), player(), effect(). |
PreSpiritCoreConsumeEvent | Yes | A 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(). |
SpiritCoreConsumeEvent | No | The core was absorbed. | ref(), player(), qi() - what was actually banked. |
PreRefinementStartEvent | Yes | A refinement ritual is about to begin. Cancel and no Qi is spent. | ref(), player(), element(), targetTier(), qiCost() / setQiCost(float). |
RefinementStartEvent | No | The ritual began; the Qi is spent and the cultivator seated. | ref(), player(), element(), targetTier(), qiCost(). |
PreRefinementCompleteEvent | Yes | A 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. |
RefinementCompleteEvent | No | The 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:
RefineOutcome:SUCCESS;FAILED- Failed outright - wastes the spent herbs and Qi, nothing else happens;BOTCH- Failed AND the cauldron turned on the alchemist - see AlchemyConfig'sRefine-Botch-*fields.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreRefineStartEvent | Yes | A refining ritual is about to begin. Cancel to refuse it (no herbs or Qi are spent). | ref(), player(), effect(), durationSeconds() / setDurationSeconds(float). |
RefineStartEvent | No | A refining ritual began; the herbs and Qi floor check already passed. | ref(), player(), effect(), qiDrainPerSecond(). |
PreRefineCompleteEvent | Yes | A 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). |
RefineCompleteEvent | No | A refining ritual resolved. grade is null unless outcome is SUCCESS. | ref(), player(), effect(), outcome(), grade(). |
TendPromptResolvedEvent | No | A 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:
ForgeOutcome:SUCCESS;FAILED- Failed outright - wastes the spent materials, and demotes the item a tier if Forge-Demote-On-Fail-From-Tier says so;BOTCH- Failed AND the forge turned on the smith - see ForgingConfig'sForge-Botch-*fields.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreForgeEvent | Yes | A 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(). |
ForgeCompleteEvent | No | A 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:
InscribeOutcome:SUCCESS;FAILED- Failed outright - wastes the spent materials and Qi, nothing else happens;BOTCH- Failed AND the flame turned on the inscriber - see TalismanConfig'sInscribe-Botch-*fields.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreInscribeStartEvent | Yes | An inscription ritual is about to begin. Cancel to refuse it (no materials or Qi are spent). | ref(), player(), talismanId(), element(). |
InscribeStartEvent | No | An inscription ritual began; materials and the Qi floor check already passed. | ref(), player(), talismanId(), element(), qiDrainPerSecond(). |
PreInscribeCompleteEvent | Yes | An 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). |
InscribeCompleteEvent | No | An inscription ritual resolved. grade/stack are null unless outcome is SUCCESS. | ref(), player(), talismanId(), element(), outcome(), grade(), stack(). |
PreTalismanUseEvent | Yes | A talisman is about to be used. Cancel to refuse it (the charge is not spent). | ref(), player(), talismanId(), grade(). |
TalismanUseEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreWeaponSpiritAwakenEvent | Yes | A 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(). |
WeaponSpiritAwakenEvent | No | A weapon's spirit just stirred awake; item is the stack after the awaken is written. | owner(), item(), killsAtAwaken(). |
PreWeaponSpiritLevelUpEvent | Yes | A weapon spirit is about to level up. Cancel to hold it at its current level (the Xp is still banked). | owner(), item(), oldLevel(), newLevel(). |
WeaponSpiritLevelUpEvent | No | A weapon's spirit just gained a level; item is the stack after the level-up is written. | owner(), item(), newLevel(), reachedMaturity(). |
PreWeaponSpiritNurtureEvent | Yes | A 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). |
WeaponSpiritNurtureEvent | No | A 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:
BindSource:TAME- Subdued and tamed in the wild;HATCH- Hatched from a Spirit Beast Egg;DEN- Claimed from a stocked Spirit Beast Den.DismissReason:DISMISSED- Sent home; it keeps everything it has cultivated;RELEASED- Set free for good - the bond itself is broken;EXPEDITION- Sent off on a Beast Expedition; it keeps everything it has cultivated and returns once the timer completes.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreBeastTameAttemptEvent | Yes | A 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. |
BeastTameAttemptEvent | No | The roll resolved. | ref(), player(), species(), chance(), success(). |
PreBeastBindEvent | Yes | A 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(). |
BeastBindEvent | No | The companion is bound, replacing whatever came before. | ref(), player(), species(), source(). |
PreBeastSummonEvent | Yes | A companion's body is about to be spawned. Cancel and the player is told the summon failed. | owner(), player(), species(). |
BeastSummonEvent | No | The body spawned beside its master. | owner(), player(), beast() (the spawned entity), species(). |
PreBeastDismissEvent | Yes | A companion is about to be sent home or freed. | owner(), player(), reason(). |
BeastDismissEvent | No | The body left the world. | owner(), player(), reason(). |
PreBeastXpGainEvent | Yes | A 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). |
BeastXpGainEvent | No | The XP was banked. | owner(), player(), amount(), stagesGained() (0 when it only banked progress). |
PreBeastAdvanceEvent | Yes | A 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(). |
BeastAdvanceEvent | No | The companion advanced. Fires once per stage. | owner(), player(), realm(), stage(). |
PreBeastArtEvent | Yes | A 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). |
BeastArtEvent | No | A companion performed one of its arts. | owner(), player(), beast(), art(), commanded(). |
PreBeastEvolveEvent | Yes | A 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). |
BeastEvolveEvent | No | The ritual resolved. succeeded() says which way, and to() is null on failure. | owner(), player(), beast(), from(), to(), succeeded(). |
PreBeastMountEvent | Yes | A companion is about to be summoned in its rideable body. Cancel to refuse the mount. | owner(), player(), beast(), species(). |
BeastMountEvent | No | The companion was summoned in its rideable body. | owner(), player(), beast(), species(). |
PreBeastDenBroodEvent | Yes | A 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(). |
BeastDenBroodEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreBeastBreedEvent | Yes | A 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(). |
BeastBreedEvent | No | A 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(). |
PreBeastEggHatchEvent | Yes | A 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(). |
BeastEggHatchEvent | No | A 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:
JoinMethod:INVITE- They accepted a pending invite;OPEN- They walked into an OPEN sect;REQUEST- A manager approved their pending join request.LeaveReason:LEFT- They left of their own accord;KICKED- A leader or elder removed them.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreSectCreateEvent | Yes | A 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. |
SectCreateEvent | No | The sect was founded and indexed. | leader(), sect(). |
PreSectDisbandEvent | Yes | A sect is about to be disbanded. Cancel to keep it standing. | leader(), sect(). |
SectDisbandEvent | No | Members are unindexed and formations released. sect is the now-orphaned object, still readable for its final roster. | leader(), sect(). |
PreSectInviteEvent | Yes | An invite is about to be issued. | inviter(), invitee(), sect(), expiryMillis() / setExpiryMillis(long) - wall-clock millis at which it lapses. |
SectInviteEvent | No | The invite is pending, not accepted. | inviter(), invitee(), sect(). |
PreSectJoinEvent | Yes | A player is about to join. Cancel and the invite or request survives, so they can try again. | player(), sect(), method(). |
SectJoinEvent | No | They are on the roster. | player(), sect(), method(). |
PreSectLeaveEvent | Yes | A player is about to leave or be kicked. Cancel to keep them on the roster. | player(), sect(), reason(), actor(). |
SectLeaveEvent | No | They are off the roster. actor is the kicker for KICKED, the player themselves for LEFT. | player(), sect(), reason(), actor(). |
PreSectJoinRequestEvent | Yes | A join request is about to be queued against a REQUEST-policy sect. | player(), sect(). |
SectJoinRequestEvent | No | The request is queued. | player(), sect(). |
SectJoinRequestDeniedEvent | No | A manager denied a pending request. | manager(), applicant(), sect(). |
PreSectRankChangeEvent | Yes | An elder rank is about to change. | leader(), target(), sect(), promoted() - true for member to elder. |
SectRankChangeEvent | No | The rank changed. | leader(), target(), sect(), promoted(). |
PreSectMottoChangeEvent | Yes | A motto is about to be set. | manager(), sect(), oldMotto(), motto() / setMotto(String) - the 60-character cap still applies afterward. |
SectMottoChangeEvent | No | The motto was replaced. | manager(), sect(), oldMotto(), newMotto(). |
PreSectJoinPolicyChangeEvent | Yes | A join policy is about to change. | leader(), sect(), oldPolicy(), policy() / setPolicy(Sect.JoinPolicy). |
SectJoinPolicyChangeEvent | No | The policy changed. | leader(), sect(), oldPolicy(), newPolicy(). |
PreSectRenameEvent | Yes | A sect is about to be renamed. | leader(), sect(), oldName(), newName() / setNewName(String) - re-validated afterward. |
SectRenameEvent | No | Renamed; formations, hall springs and pending invites already carried over. | leader(), sect(), oldName(), newName(). |
PreSectInscriptionChangeEvent | Yes | A hall inscription is about to change. | leader(), sect(), oldTechniqueId(), newTechniqueId() / setNewTechniqueId(String) - empty scours it away. |
SectInscriptionChangeEvent | No | The inscription changed. | leader(), sect(), oldTechniqueId(), newTechniqueId(). |
PreSectHallClaimEvent | Yes | A hall is about to be claimed. Cancel and it reports the chunk as already claimed. | leader(), sect(), world(), chunkX(), chunkZ(), veinTier(). |
SectHallClaimEvent | No | The sect claimed, or moved, its hall onto a spirit vein. | leader(), sect(), world(), chunkX(), chunkZ(), veinTier(). |
PreSectHallCaptureEvent | Yes | A 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(). |
SectHallCaptureEvent | No | A won siege transferred a hall. The defender is now hall-less. | attacker(), defender(), world(), chunkX(), chunkZ(), veinTier(). |
PreSectBannerChangeEvent | Yes | A 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). |
SectBannerChangeEvent | No | The banner changed. The hall's standing light swaps to it on its next pulse. | manager(), sect(), oldBannerId(), newBannerId(). |
PreSectAbbreviationChangeEvent v0.8.0 | Yes | A 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.0 | No | The tag changed. | leader(), sect(), oldAbbreviation(), newAbbreviation(). |
SectAllianceFormedEvent | No | Two sects formed a mutual Alliance. actor is whoever accepted the proposal. | actor(), sectA(), sectB(). |
SectAllianceBrokenEvent | No | A standing Alliance ended; either side's leader may break one unilaterally. actor is whoever broke it. | actor(), sectA(), sectB(). |
SectRelationChangedEvent | No | A 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(). |
PreSectLibraryResearchStartEvent | Yes | A 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(). |
SectLibraryResearchStartEvent | No | A sect's Library started researching a new subject (replacing whatever, if anything, it was researching before). | actor(), sect(), techniqueId(). |
PreSectLibraryCompileEvent | Yes | A 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(). |
SectLibraryCompileEvent | No | A sect's Library finished compiling an exclusive technique manual. | sect(), techniqueId(). |
PreSectLibraryRedeemEvent | Yes | A 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). |
SectLibraryRedeemEvent | No | A 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:
SiegeFailReason:LAPSED- The CONTEST window ran out before the attacker held the hall long enough;DEFENDER_GONE- The attacking or defending sect (or the defender's hall) no longer existed when checked;MUSTER_FAILED- MUSTER's deadline passed without both sides meeting their minimum presence - a total no-op, no cooldown for the defender;DEFENDER_ABSENT- CONTEST hard-stopped because live defender presence in the hall's own world stayed at zero past the grace period - resolves in the defender's favor with no loss;ABORTED- An admin or the Wars-Enabled master switch cancelled the siege outright - no consequence to either side, and the attacker's declaration cost is refunded.BannerBreakSide:ATTACKER;DEFENDER;THIRD_PARTY.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreWarDeclareEvent | Yes | A 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. |
WarDeclareEvent | No | The siege is live and both sides have been told. | attacker(), defender(), siege(). |
PreSiegeCaptureEvent | Yes | A 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(). |
SiegeCaptureEvent | No | The attacker held long enough. The hall transfer has already been attempted and the defender's immunity cooldown started. | attacker(), defender(), siege(). |
SiegeFailEvent | No | The siege ended with the hall still in its defender's hands. | siege(), reason(). |
PreSiegeBannerPlaceEvent | Yes | Formations 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(). |
SiegeBannerPlaceEvent | No | Formations 2.0: a Siege Banner was placed and is now standing. | banner(). |
SiegeBannerBreakEvent | No | Formations 2.0: a Siege Banner was broken - by anyone, no protection. breakerSide tells which side (if any) the breaker belonged to. | banner(), breaker(), breakerSide(). |
PreWarReinforcementRewardEvent | Yes | An 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreSectGuardianStationEvent | Yes | A 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(). |
SectGuardianStationedEvent | No | A 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(). |
SectGuardianFellEvent | No | One 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(). |
SectGuardiansFallenEvent | No | The 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreSermonStartEvent | Yes | A 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(). |
SermonStartEvent | No | A sect member successfully started a sermon at their own sect's hall. | sect(), lecturer(), element(), endsAtMillis(). |
SermonListenerQualifiedEvent | No | One 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(). |
SermonEndEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreMeritGainEvent | Yes | Merit is about to be credited for a deed. Cancel to pay nothing; setAmount to re-weigh the deed. | player(), deed(), amount() / setAmount(float). |
MeritGainEvent | No | Merit was actually credited - total/multiplier are the values after the throttle and caps were applied. | player(), deed(), amount(), total(), multiplier(). |
MeritRankUpEvent | No | This 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PrePartyInviteEvent | Yes | An invite is about to be sent. Cancel to refuse it silently (the inviter still receives the manager's own result). | inviterUuid(), targetUuid(). |
PartyFormedEvent | No | A solo leader's first invite was accepted - the party now exists as a group, not just a leader waiting alone. | leaderUuid(), firstMemberUuid(). |
PartyMemberJoinedEvent | No | A cultivator joined an already-formed party - not the party's very first member, which is PartyFormedEvent. | leaderUuid(), memberUuid(). |
PartyMemberLeftEvent | No | A cultivator left a party that still has members remaining afterward. If the leader left, leaderUuid is the newly promoted leader. | leaderUuid(), memberUuid(). |
PartyDisbandedEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PartnerPairedEvent | No | A 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(). |
PartnerUnpairedEvent | No | A 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(). |
PrePartnerQiBonusEvent | Yes | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreAuctionListEvent | Yes | A 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). |
AuctionListingCreatedEvent | No | A listing was created and is now on the shelf. | seller(), listing(). |
PreAuctionBuyEvent | Yes | A player is about to buy a listing. Cancel to refuse it - nothing has moved yet, the listing stays on the shelf. | buyer(), listing(). |
AuctionListingSoldEvent | No | A listing sold. listing is the now-removed shelf entry; sellerProceeds is what the seller was credited after the house cut. | listing(), buyer(), sellerProceeds(). |
AuctionListingCancelledEvent | No | A seller pulled their own still-active listing. | listing(). |
AuctionListingExpiredEvent | No | An unsold listing aged past Market-Auction-Listing-Duration-Hours and was returned to its seller as a claimable parcel. | listing(). |
MerchantOpenedEvent | No | The Traveling Merchant opened for business in a world. | world(), x(), y(), z(). |
MerchantClosedEvent | No | The Traveling Merchant's visit ended and the NPC despawned. | world(). |
PreAuctionBidEvent | Yes | A 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(). |
AuctionBidPlacedEvent | No | A bid was placed on a timed auction and is now the standing high bid. | listing(), bidder(), amount(). |
AuctionOutbidEvent | No | A 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(). |
AuctionClosedEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
BountyPostedEvent | No | A 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(). |
PreBountyClaimEvent | Yes | A 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(). |
PreBountyCreditEvent | Yes | A 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(). |
BountyClaimedEvent | No | A 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(). |
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:
DuelEndReason:DEATH- One duelist died; the wager transfers;YIELD- A duelist yielded (/cultivation duel yield); the wager transfers, exactly like DEATH - a decided outcome with a known winner, never "nothing happened";VOIDED- Undecidable - a participant left, or the duel ran past its max duration.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreDuelChallengeEvent | Yes | A challenge is about to be issued. | challenger(), challenged(), wager() / setWager(int) - the configured maximum is re-checked afterward. |
DuelChallengeEvent | No | The challenge is pending the other player's answer. | challenger(), challenged(), wager(). |
DuelDeclineEvent | No | The challenge was declined; no duel started. | challenger(), challenged(). |
PreDuelStartEvent | Yes | A 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. |
DuelStartEvent | No | The duel is live; both players are flagged as dueling. | challenger(), challenged(), wager(). |
DuelEndEvent | No | The 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. |
PreDuelPayoutEvent | Yes | A 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. |
DuelPayoutEvent | No | The 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:
DaoDuelEndReason:DEATH- One duelist died; the Spirit Stone pot was settled;YIELD- The loser yielded (/cultivation duel yield) rather than being killed; the Spirit Stone pot was still settled against them.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreDaoDuelChallengeEvent | Yes | A 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). |
PreDaoDuelPayoutEvent | Yes | A 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). |
DaoDuelEndEvent | No | A 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:
WagerVoidReason:FORFEIT- One duelist was declared the winner without a real duel being fought (a no-show / deadline forfeit);DOUBLE_ELIMINATION- Neither duelist was eligible at the deadline, so both were eliminated;VOIDED_DUEL- The underlying duel was voided - a disconnect, orDuel-Max-Duration-Seconds;YIELD- The loser yielded (/cultivation duel yield) instead of being beaten;NOT_OPPONENT_KILL- The loser died, but not to the opponent's own damage - lava, a fall, drowning, a formation trap, a third party, or a source the death path could not attribute at all;TOO_FEW_BETTORS- The match was genuinely decided, but a side ended up with fewer thanWager-Min-Bettors-Per-Sidedistinct bettors - the anti-collusion rule;POOL_TOO_THIN- The match was genuinely decided, but a side's combined stake was belowWager-Min-Pool-Per-Side- the anti-collusion rule that costs an attacker real Spirit Stones;TOURNAMENT_RESET- The whole tournament was reset, cancelled or re-opened while this market was live;ACCOUNTING_FAILURE- The conservation invariant failed its self-check, so nothing was paid and every stake was returned.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreWagerPlacedEvent | Yes | A 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(). |
WagerPlacedEvent | No | A spectator's Spirit Stones have been charged and their stake recorded. backed is the duelist they are backing. | matchNumber(), bettor(), backed(), amount(). |
WagerMarketResolvedEvent | No | A 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(). |
WagerMarketVoidedEvent | No | A 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(). |
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:
EndReason:COMPLETED- Every prompt was delivered and scored - the normal finish;LEFT- A participant ran/cultivation tea leave;DRIFTED- The two participants drifted pastTea-Radius-Blocksfor longer thanTea-Grace-Seconds;DISCONNECTED- A participant disconnected;COMBAT- A participant entered combat (a livePunishWindowComponent);DISABLED-Tea-Enabledwas switched off mid-ceremony.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreTeaCeremonyStartEvent | Yes | A 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). |
TeaCeremonyStartEvent | No | A ceremony actually began - herbs already spent, the session already live. | sessionId(), playerA(), playerB(). |
TeaStepResolvedEvent | No | One prompt (a single BOIL/STEEP/POUR/SERVE step) was scored. | sessionId(), round(), step(), playerACorrect(), playerBCorrect(), scoreDelta(). |
TeaCeremonyEndEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreWeiqiInviteEvent | Yes | An invite is about to be sent. Cancel to refuse it outright (e.g. an addon-enforced cooldown or block list). | inviter(), invitee(). |
WeiqiInviteEvent | No | An invite was sent and is now pending the other player's answer. | inviter(), invitee(). |
WeiqiDeclineEvent | No | An invite was declined; no match started. | inviter(), invitee(). |
PreWeiqiMatchStartEvent | Yes | A 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). |
WeiqiMatchStartEvent | No | A 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(). |
WeiqiMoveEvent | No | One 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(). |
WeiqiMatchEndEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreFormationPlaceEvent | Yes | An 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). |
FormationPlaceEvent | No | The array is live on its chunk. | owner(), sectName(), formation(). |
PreFormationRemoveEvent | Yes | An array is about to be dispersed by its controller. Cancel to leave it standing. | owner(), sectName(), formation(). |
FormationRemoveEvent | No | Dispersed. formation is the now-removed object. | owner(), sectName(), formation(). |
PreFormationTrapStrikeEvent | Yes | A 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. |
FormationTrapStrikeEvent | No | The intruder was wounded. | ref(), player(), world(), chunkX(), chunkZ(), damage(). |
PreFormationTierChangeEvent | Yes | An 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). |
FormationTierChangeEvent | No | An 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreDwellingClaimEvent | Yes | An abode is about to be claimed or moved. Cancel and it reports warded ground. | owner(), world(), chunkX(), chunkZ(), veinTier(), moved(), radiusChunks() / setRadiusChunks(int). |
DwellingClaimEvent | No | The abode was claimed, or an existing one moved - a move keeps the banked spring and paid upkeep. | owner(), dwelling(), moved(). |
PreDwellingAbandonEvent | Yes | An abode is about to be given up. Cancel to keep it standing. | owner(), dwelling(). |
DwellingAbandonEvent | No | Given up; whatever the spring held went with it. | owner(), dwelling(). |
PreDwellingLapseEvent | Yes | An 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(). |
DwellingLapseEvent | No | A personal abode was reclaimed by the world, past its grace period. | dwelling(). |
PreSpringCollectEvent | Yes | A 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. |
SpringCollectEvent | No | The spring was emptied. amount is the Qi handed over - the caller credits it. | dwelling(), amount(). |
PreUpkeepDepositEvent | Yes | Upkeep 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. |
UpkeepDepositEvent | No | Upkeep 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(). |
PreSeclusionSettleEvent | Yes | A 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). |
SeclusionSettleEvent | No | The 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreCelestialEventStartEvent | Yes | An 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(). |
CelestialEventStartEvent | No | It began. The sky is already switching for every player not under a tribulation storm. | type(), startedAtMillis(), endsAtMillis(), forcedByAdmin(). |
CelestialEventEndEvent | No | It 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreTideStartEvent | Yes | A 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). |
TideStartEvent | No | A tide's warning phase has begun - its target is locked and its beacon will spawn shortly. | assaultId(), worldName(), abode(), targetName(), waveCount(). |
PreTideWaveEvent | Yes | One 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(). |
TideWaveEvent | No | One wave was just triggered (or attempted - see PreTideWaveEvent). | assaultId(), waveIndex(), waveCount(). |
PreTideResolveEvent | Yes | The 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). |
TideResolveEvent | No | The 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreWorldBossStartEvent | Yes | A 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(). |
WorldBossStartEvent | No | A 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(). |
WorldBossSpawnEvent | No | The OMEN ended and the boss NPC now actually exists in the world (ACTIVE phase begun). | encounterId(), worldName(), roleId(), position(), bossRef(). |
WorldBossResolveEvent | No | The 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreRiftOpenEvent | Yes | A 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(). |
RiftOpenEvent | No | A 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(). |
RiftWaveEvent | No | One 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(). |
RiftWardenSpawnEvent | No | The ASSAULT phase ended and the Warden NPC now actually exists in the world (WARDEN phase begun). | riftId(), worldName(), roleId(), position(), wardenRef(). |
RiftResolveEvent | No | The 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreRogueCultivatorSpawnEvent | Yes | A 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). |
RogueCultivatorSpawnedEvent | No | A Rogue Cultivator has finished spawning and is tagged/live in the world. | encounterId(), npcRef(), worldName(), realmOrdinal(), daoElementName(). |
RogueCultivatorSlainEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreRivalChallengeEvent | Yes | A 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(). |
RivalDefeatedEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
SecretRealmOpenEvent | No | A Secret Realm site opened - the barrier is already down and it can be entered. | siteId(), tier(), world(), chunkX(), chunkZ(), source(), sectName(), closesAtMillis(). |
SecretRealmCloseEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
DepthsRunStartEvent | No | A solo Depths run has started, floor 1 about to spawn. | runId(), ownerUuid(), siteId(), world(). |
DepthsFloorClearEvent | No | A 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(). |
DepthsExtractEvent | No | The 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(). |
DepthsRunEndEvent | No | The 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).
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PrePagodaRewardEvent | Yes | A 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). |
PagodaFloorClearedEvent | No | A 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(). |
PagodaRunEndedEvent | No | A 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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreTreasureClaimEvent | Yes | A 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(). |
TreasureClaimedEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreArrayTravelEvent | Yes | A 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). |
ArrayTravelEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreQuestAcceptEvent | Yes | A 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(). |
QuestAcceptEvent | No | A 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(). |
QuestStepAdvanceEvent | No | A step of a chain just cleared. | ref(), player(), chainId(), completedStepIndex(), nextStepIndex(), finalStep(). |
QuestCompleteEvent | No | A 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(). |
QuestAbandonEvent | No | A 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
PreCampaignChapterAdvanceEvent | Yes | A 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(). |
CampaignChapterAdvanceEvent | No | A 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(). |
CampaignCompleteEvent | No | Every 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.
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
SeasonOpenEvent | No | A 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(). |
SeasonCloseEvent | No | A 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(). |
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().
| Event | Cancellable | Fires | Exposes |
|---|---|---|---|
BenefitGrantedEvent | No | A player's entitlement to a benefit was seen for the first time this session. | playerUuid(), benefit(). |
BenefitRevokedEvent | No | A player stopped appearing on the list — a refund, or a benefit disabled on this server. | playerUuid(), benefit(). |
SyncCompletedEvent | No | A 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
- API Registries - adding your own races, techniques, Qi absorption items, titles, sect banners, palettes, celestial events and store benefits.
- API Reference - reading a player's cultivation state directly.
- Config - every server-owner-facing key an event lets you override per-player.
- Commands - the commands most of these events sit behind.