API Events

Cultivation exposes roughly 135 events across ten *Events classes in plugin.siren.API. Nearly every mechanic in the mod is exposed twice - once as a cancellable Pre* event fired before the change, and once as a plain post-event fired after it is committed.

Every class follows the conventions documented on CultivationEvents, so learning one teaches you all ten.




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()booleanWhether some listener has vetoed this operation.
setCancelled(boolean cancelled) Vetoes, or with false un-vetoes, the operation.

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




Registering and threading

Each event has a matching on* registration method 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.

Two subsystems carry extra constraints, both stated in their own source:

  • Sects. Sect mutations run inside SectManager’s synchronized methods, so a SectEvents listener is holding that lock. Read what you need, hand it off, and return. Do not call back into SectManager’s mutating methods from a listener.
  • UUIDs instead of PlayerRefs. SectEvents, DuelEvents, FormationEvents and DwellingEvents identify players by java.util.UUID, because those operations routinely touch offline players - a kicked member, a disbanded sect’s roster, a duel that outlived a session. Resolve one with Universe.get().getPlayer(uuid) and check isValid().




Cultivation core

plugin.siren.API.CultivationEvents - 28 events covering Qi, meditation, rituals, breakthroughs, advancements, demotions, tribulations, the Heart-Devil Trial, Qi Deviation, races, Life-Bound Treasures and the skill tree. Player-facing docs: Cultivation Realms, Qi Gathering, Tribulations, Races, Life-Bound Treasures, Skill Tree.

Two enums live on this class: RitualType (BREAKTHROUGH, ADVANCEMENT, REFINEMENT) and MeditationStopReason (COMMAND, MOVEMENT).

Event:Cancellable:Fires:Exposes:
PreQiGainEventYesQi is about to be banked, for every source - meditation ticks, duel payouts, admin grants - after all the mod’s own multipliers.ref(), player(), amount() / setAmount(float), baseAmount() (what it was before any listener touched it).
QiGainEventNoQi was banked.ref(), player(), amount(), totalQi().
PreMeditationStartEventYesA player is about to sit down to meditate. Cancel to keep them on their feet.ref(), player().
MeditationStartEventNoA player sat down to meditate.ref(), player().
PreMeditationStopEventYesA player is about to stop meditating. Cancel to make a ritual unbreakable, or to suppress the movement-cancel.ref(), player(), reason().
MeditationStopEventNoA player stopped. Any ritual penalty has already been applied.ref(), player(), reason().
PreRitualStartEventYesA timed ritual is about to begin. Cancel and the player keeps meditating (banking Qi) but never enters the ritual.ref(), player(), type(), requiredSeconds() / setRequiredSeconds(float).
RitualStartEventNoThe tick that first accrued ritual progress.ref(), player(), type().
PreBreakthroughEventYesA realm breakthrough is about to complete. Cancel to hold them at Peak stage - progress resets and they may retry.ref(), player(), fromRealm(), toRealm(), qiCost() / setQiCost(float).
BreakthroughEventNoThe breakthrough completed; their stage is EARLY.ref(), player(), newRealm().
PreAdvancementEventYesA sub-stage advancement is about to complete. Cancel to hold them where they are.ref(), player(), realm(), fromStage(), toStage(), qiCost() / setQiCost(float).
AdvancementEventNoThe advancement completed.ref(), player(), realm(), newStage().
PreDemotionEventYesA player is about to lose a sub-stage for abandoning a ritual. Cancel and they walk away free - their banked Qi survives too.ref(), player(), realm(), oldStage(), newStage(), wasBreakthrough().
DemotionEventNoThe demotion landed; banked Qi is wiped and the granting skill points revoked.ref(), player(), realm(), oldStage(), newStage(), wasBreakthrough().
PreTribulationStrikeEventYesTribulation lightning is about to strike a mid-ritual cultivator. Cancel to spare them entirely - no bolt, no thunder, no damage. Set damage to 0 to let the bolt fall harmlessly.ref(), player(), damage() / setDamage(float) (post-lethality-cap, pre-armor), breakthroughRitual().
TribulationStrikeEventNoThe bolt fell.ref(), player(), damage(), breakthroughRitual().
PreHeartDevilTrialEventYesA Heart-Devil pulse is about to torment a mid-ritual cultivator. Set the drain to 0 to make the apparition purely cosmetic.ref(), player(), composureDrain() / setComposureDrain(float), leanFraction() (0-1), pulseIndex() (from 0), breakthroughRitual().
HeartDevilTrialEventNoA pulse resolved.ref(), player(), composureRemaining(), deviated(), breakthroughRitual().
PreQiDeviationEventYesComposure has shattered and Qi Deviation (走火入魔) is about to be applied. Cancel to spare the penalty - the ritual still ends.ref(), player(), demotes() / setDemotes(boolean), qiLoss() / setQiLoss(float), breakthroughRitual().
QiDeviationEventNoThe penalty landed. Exactly one of demoted() / qiLost() carries it.ref(), player(), demoted(), qiLost(), breakthroughRitual().
PreRaceChangeEventYesA race is about to change. Cancel and the race menu simply reports no change.ref(), player(), oldRace(), newRace(), adminOverride().
RaceChangeEventNoThe race changed. Not fired when an admin sets the race a player already has.ref(), player(), oldRace(), newRace(), adminOverride().
PreSkillUnlockEventYesA skill tree node is about to be unlocked. Cancel and their points are not spent.ref(), player(), node(), pointCost() / setPointCost(int).
SkillUnlockEventNoThe node is unlocked; points spent and modifiers re-applied.ref(), player(), node().
PreRespecEventYesA skill tree respec is about to run.ref(), player(), refundedPoints() / setRefundedPoints(int).
RespecEventNoEvery node was cleared and the points handed back.ref(), player(), refundedPoints().
PreLifeBoundLevelUpEventYesA Life-Bound Treasure is about to level. Cancel to hold it at its current level - the XP is still banked.owner(), item() (the stack before the level-up is written), oldLevel(), newLevel().
LifeBoundLevelUpEventNoThe treasure levelled.owner(), item() (already updated), newLevel().

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.

player() is @Nullable throughout this class - it is null only when the PlayerRef component was unavailable.




Dao and karma

plugin.siren.API.DaoEvents - 14 events covering elements, drift, the Yin-Yang balance, moral paths and karma. Player-facing docs: 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. The class carries one enum, ElementChangeReason (CHOSEN, DRIFT).

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

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.




Techniques

plugin.siren.API.TechniqueEvents - 11 events covering performing and learning arts (功法), 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. Two enums live here: BuffType (IRON_BODY, QI_INFUSION, QI_BARRIER, CLOUD_STEP) and FlightStopReason (TOGGLE, QI_EXHAUSTED, DEATH).

Event:Cancellable:Fires:Exposes:
PreTechniquePerformEventYesA technique is about to be performed - every gate has already passed. Cancel to refuse it silently: no Qi spent, no cooldown.ref(), player(), technique(), qiCost() / setQiCost(float), cooldownSeconds() / setCooldownSeconds(float) (0 or less stamps no cooldown).
TechniquePerformEventNoThe Qi is spent, the cooldown stamped, and the effect has run.ref(), player(), technique(), qiCost().
PreTechniqueLearnEventYesA technique is about to be learned for good. Cancel to refuse it - the manual is consumed either way, matching how a manual for an already-known art is spent.ref(), player(), techniqueId().
TechniqueLearnEventNoThe art is learned. Sect-taught arts are resolved live and never fire this - listen for the sect inscription events instead.ref(), player(), techniqueId().
PreSwordFlightStartEventYesA cultivator is about to take to the sky on their sword.ref(), player(), horizontalSpeed() / setHorizontalSpeed(float), verticalSpeed() / setVerticalSpeed(float).
SwordFlightStartEventNoThey are airborne.ref(), player(), horizontalSpeed(), verticalSpeed().
PreSwordFlightStopEventYesA cultivator is about to come down. Safe to cancel for TOGGLE, but cancelling a DEATH stop leaves flight state on a corpse - gate on reason().ref(), player(), reason().
SwordFlightStopEventNoThey came down; their mount, if any, has already despawned.ref(), player(), reason().
PreTechniqueBuffApplyEventYesA timed technique buff is about to be applied.ref(), player(), type(), durationSeconds() / setDurationSeconds(float), magnitude() / setMagnitude(float).
TechniqueBuffApplyEventNoThe buff is on. magnitude means whatever that buff measures - a reduction percent, a damage percent, a shield pool, a speed multiplier.ref(), player(), type(), durationSeconds(), magnitude().
TechniqueBuffExpireEventNoCloud Step’s speed multiplier was reverted, on expiry or on cleanup.ref(), player(), type().

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 only ever fires for techniques whose rule sets Requires-Manual.




Items

plugin.siren.API.ItemEvents - 12 events 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 above; a manual that teaches a skill node goes through the core skill-unlock events. This class covers the manual item itself. Two enums live here: LootType (CULTIVATION_CORE, BEAST_EGG, SPIRIT_STONE, MANUAL) and RefinementOutcome (SUCCESS, DESTROYED, DEMOTED, FAILED).

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

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.




Beasts

plugin.siren.API.BeastEvents - 12 events covering taming, hatching, summoning and a companion’s own cultivation. Player-facing docs: Spirit Beasts.

Two enums live here: BindSource (TAME, HATCH) and DismissReason (DISMISSED - sent home, keeps everything it cultivated; RELEASED - freed for good, the bond broken).

Event:Cancellable:Fires:Exposes:
PreBeastTameAttemptEventYesA tame is about to be rolled. Cancel to refuse the attempt outright - no talisman is spent.ref(), player(), species(), chance() / setChance(float) - 0-1, after realm and dao-resonance weighting.
BeastTameAttemptEventNoThe roll resolved.ref(), player(), species(), chance(), success().
PreBeastBindEventYesA companion is about to be bound. Cancel and the cultivator keeps whatever beast they already had - the talisman or egg is still spent.ref(), player(), species(), source().
BeastBindEventNoThe companion is bound, replacing whatever came before.ref(), player(), species(), source().
PreBeastSummonEventYesA companion’s body is about to be spawned. Cancel and the player is told the summon failed.owner(), player(), species().
BeastSummonEventNoThe body spawned beside its master.owner(), player(), beast() (the spawned entity), species().
PreBeastDismissEventYesA companion is about to be sent home or freed.owner(), player(), reason().
BeastDismissEventNoThe body left the world.owner(), player(), reason().
PreBeastXpGainEventYesA companion is about to gain XP. Fires for every source - meditation shares, kills and hand-feeding alike.owner(), player(), beast() (the live SpiritBeastComponent - realm, stage, banked XP), amount() / setAmount(float).
BeastXpGainEventNoThe XP was banked.owner(), player(), amount(), stagesGained() (0 when it only banked progress).
PreBeastAdvanceEventYesA companion is about to advance a stage. Cancel to hold it where it is - the XP for that stage is already spent, so this costs it the progress.owner(), player(), fromRealm(), fromStage().
BeastAdvanceEventNoThe companion advanced. Fires once per stage.owner(), player(), realm(), stage().

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.




Sects

plugin.siren.API.SectEvents - 27 events covering founding, membership, ranks, halls and hall inscriptions. Player-facing docs: Sects, and the /sect suite on the Commands page.

Remember the two constraints from above: listeners hold SectManager’s lock, and players are UUIDs here. Two enums live on this class: JoinMethod (INVITE, OPEN, REQUEST) and LeaveReason (LEFT, KICKED).

Event:Cancellable:Fires:Exposes:
PreSectCreateEventYesA player is about to found a sect (宗门). Cancel and it reports a refused creation.leader(), name() / setName(String) - re-validated for shape and uniqueness afterward.
SectCreateEventNoThe sect was founded and indexed.leader(), sect().
PreSectDisbandEventYesA sect is about to be disbanded. Cancel to keep it standing.leader(), sect().
SectDisbandEventNoMembers are unindexed and formations released. sect is the now-orphaned object, still readable for its final roster.leader(), sect().
PreSectInviteEventYesAn invite is about to be issued.inviter(), invitee(), sect(), expiryMillis() / setExpiryMillis(long) - wall-clock millis at which it lapses.
SectInviteEventNoThe invite is pending, not accepted.inviter(), invitee(), sect().
PreSectJoinEventYesA player is about to join. Cancel and the invite or request survives, so they can try again.player(), sect(), method().
SectJoinEventNoThey are on the roster.player(), sect(), method().
PreSectLeaveEventYesA player is about to leave or be kicked. Cancel to keep them on the roster.player(), sect(), reason(), actor().
SectLeaveEventNoThey are off the roster. actor is the kicker for KICKED, the player themselves for LEFT.player(), sect(), reason(), actor().
PreSectJoinRequestEventYesA join request is about to be queued against a REQUEST-policy sect.player(), sect().
SectJoinRequestEventNoThe request is queued.player(), sect().
SectJoinRequestDeniedEventNoA manager denied a pending request.manager(), applicant(), sect().
PreSectRankChangeEventYesAn elder rank is about to change.leader(), target(), sect(), promoted() - true for member to elder.
SectRankChangeEventNoThe rank changed.leader(), target(), sect(), promoted().
PreSectMottoChangeEventYesA motto is about to be set.manager(), sect(), oldMotto(), motto() / setMotto(String) - the 60-character cap still applies afterward.
SectMottoChangeEventNoThe motto was replaced.manager(), sect(), oldMotto(), newMotto().
PreSectJoinPolicyChangeEventYesA join policy is about to change.leader(), sect(), oldPolicy(), policy() / setPolicy(Sect.JoinPolicy).
SectJoinPolicyChangeEventNoThe policy changed.leader(), sect(), oldPolicy(), newPolicy().
PreSectRenameEventYesA sect is about to be renamed.leader(), sect(), oldName(), newName() / setNewName(String) - re-validated afterward.
SectRenameEventNoRenamed; formations, hall springs and pending invites already carried over.leader(), sect(), oldName(), newName().
PreSectInscriptionChangeEventYesA hall inscription is about to change.leader(), sect(), oldTechniqueId(), newTechniqueId() / setNewTechniqueId(String) - empty scours it away.
SectInscriptionChangeEventNoThe inscription changed.leader(), sect(), oldTechniqueId(), newTechniqueId().
PreSectHallClaimEventYesA hall is about to be claimed. Cancel and it reports the chunk as already claimed.leader(), sect(), world(), chunkX(), chunkZ(), veinTier().
SectHallClaimEventNoThe sect claimed, or moved, its hall onto a spirit vein.leader(), sect(), world(), chunkX(), chunkZ(), veinTier().
PreSectHallCaptureEventYesA hall is about to change hands to a victorious besieger. Cancel to leave it with its defender - the siege still resolves as won.attacker(), defender(), world(), chunkX(), chunkZ(), veinTier().
SectHallCaptureEventNoA won siege transferred the hall; the defender is now hall-less.attacker(), defender(), world(), chunkX(), chunkZ(), veinTier().

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.




Wars

plugin.siren.API.WarEvents - 5 events covering declaring a siege on a rival’s hall and how it resolves. Player-facing docs: Sect Wars.

The hall actually changing hands is SectEvents.SectHallCaptureEvent, fired from inside the capture - veto that one to let a siege be won without the hall moving. One enum lives here: SiegeFailReason (LAPSED, DEFENDER_GONE).

Event:Cancellable:Fires:Exposes:
PreWarDeclareEventYesA siege is about to be declared. Cancel and the caller is told wars are disabled.attacker(), defender(), windowMillis() / setWindowMillis(long) - how long the attacker has to complete their hold.
WarDeclareEventNoThe siege is live and both sides have been told.attacker(), defender(), siege().
PreSiegeCaptureEventYesA siege is about to be won. Cancel to leave it running - the attacker keeps holding and will trip this again on their next presence tick, so cancel only while some condition of yours is unmet.attacker(), defender(), siege().
SiegeCaptureEventNoThe attacker held long enough. The hall transfer has already been attempted and the defender’s immunity cooldown started.attacker(), defender(), siege().
SiegeFailEventNoThe siege ended with the hall still in its defender’s hands.siege(), reason().

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.




Duels

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

Players are UUIDs here because a duel routinely outlives one participant’s session - that is exactly what voids it. One enum lives here: DuelEndReason (DEATH - the wager transfers; VOIDED - a participant left or the duel ran past its max duration, and no wager moves).

Event:Cancellable:Fires:Exposes:
PreDuelChallengeEventYesA challenge is about to be issued.challenger(), challenged(), wager() / setWager(int) - the configured maximum is re-checked afterward.
DuelChallengeEventNoThe challenge is pending the other player’s answer.challenger(), challenged(), wager().
DuelDeclineEventNoThe challenge was declined; no duel started.challenger(), challenged().
PreDuelStartEventYesA duel is about to start. Cancel to refuse it - the challenge is consumed either way, so the challenger must issue a fresh one.challenger(), challenged(), wager() / setWager(int) - re-tuning it here is what the live duel actually pays out.
DuelStartEventNoThe duel is live; both players are flagged as dueling.challenger(), challenged(), wager().
DuelEndEventNoThe duel ended. For DEATH the winner and loser are meaningful and the payout is queued; for VOIDED they are simply the two participants.winner(), loser(), wager(), reason().
PreDuelPayoutEventYesA decided duel’s wager is about to move. Cancel to let the winner take nothing.winner(), loser(), amount() / setAmount(int) - what actually moves is still capped by what the loser holds.
DuelPayoutEventNoThe wager moved. amount is what the loser could cover, which is exactly what the winner gained.winner(), loser(), amount().

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.




Formations

plugin.siren.API.FormationEvents - 6 events covering laying and dispersing chunk-anchored spirit arrays, and the Trapping array wounding an intruder. Player-facing docs: Formations.

Event:Cancellable:Fires:Exposes:
PreFormationPlaceEventYesAn array is about to be laid. Cancel and it reports the ground as warded.owner(), sectName() (empty when the array is theirs personally), world(), chunkX(), chunkZ(), type(), radiusChunks() / setRadiusChunks(int).
FormationPlaceEventNoThe array is live on its chunk.owner(), sectName(), formation().
PreFormationRemoveEventYesAn array is about to be dispersed by its controller. Cancel to leave it standing.owner(), sectName(), formation().
FormationRemoveEventNoDispersed. formation is the now-removed object.owner(), sectName(), formation().
PreFormationTrapStrikeEventYesA Trapping array (困仙阵) is about to wound an intruder. Cancel to spare them this tick entirely - no particle, no debuff, no damage. Set damage to 0 to root them harmlessly.ref(), player(), world(), chunkX(), chunkZ(), damage() / setDamage(float) - post-lethality-cap, pre-armor; 0 when the intruder’s health could not be resolved.
FormationTrapStrikeEventNoThe intruder was wounded.ref(), player(), world(), chunkX(), chunkZ(), damage().

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

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

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.




See also

  • API Registries - adding your own races, techniques and Qi absorption items.
  • 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.