Events¶
SleepEvents and HidingEvents tell your mod what happens to sleepers and hiders, and let it change some of it:
cancel a night terror or a player hiding, add to a night's score, swap a sleep effect, spare a monster that would
vanish.
Every event fires on the server, on the server thread (the integrated server in single player). None fires on the client.
Listening¶
Each event is a CoolCatLib net.ixdarklord.coolcatcore.api.event.v1.Event<T>, a plain list of listeners:
| Method | What it does |
|---|---|
register(Consumer<T> listener) |
Adds a listener. |
unregister(Consumer<T> listener) |
Takes it out again (pass the same instance). |
Listeners run in the order they were registered, and there are no priorities or return values. An event that can be
changed carries the change on the object it hands you: call cancel(), a setter, or add / remove on it. Every
listener sees the changes the ones before it made.
import net.ixdarklord.whycantisleep.api.HidingEvents;
import net.ixdarklord.whycantisleep.api.SleepEvents;
import net.ixdarklord.whycantisleep.api.SleepTrouble;
public static void init() {
SleepEvents.TROUBLE_CHANGED.register(changed -> {
if (changed.worsened() && changed.now() == SleepTrouble.Level.SLEEPLESS) {
changed.player().sendSystemMessage(Component.translatable("mymod.message.go_to_bed"));
}
});
HidingEvents.START.register(start -> {
if (start.player().hasEffect(MobEffects.GLOWING)) start.cancel(); // no hiding while glowing
});
}
Register once, while your mod initialises, from the class only reached when the mod is loaded (see Calling it only when the mod is loaded).
SleepEvents¶
| Event | Carries | Fires when | Can |
|---|---|---|---|
SLEEP_FAILED |
SleepFailed |
The game refused to let a player sleep. | read |
LAY_DOWN |
ServerPlayer |
A player lay down in a bed and fell asleep. | read |
TROUBLE_CHANGED |
TroubleChanged |
A player's sleep trouble level changed. | read |
NIGHT_TERROR |
NightTerror |
A player who lay down is about to have a night terror. | cancel |
SCORE_QUALITY |
QualityScoring |
A sleeper is waking and their night is being scored. | add and remove factors |
SLEEP_EFFECT |
SleepEffectDrawn |
A sleep effect was drawn for a player waking in the morning. | swap, cancel |
WOKE |
Woke |
A player woke up. | read |
NIGHT_SKIPPED |
NightSkipped |
Enough players slept that the night was skipped. | read |
NIGHT_VOTED |
NightVoted |
The night was skipped by a sleep vote. | read |
ROOM_SAFE |
RoomSafe |
A player dealt with every monster that blocked their sleep. | read |
SLEEP_FAILED¶
record SleepFailed(ServerPlayer player, BlockPos bed, Player.BedSleepingProblem problem, List<Mob> blockers, SleepTrouble trouble)
The game refused a player's sleep. bed is the bed's head and problem the game's own reason (NOT_SAFE,
NOT_POSSIBLE_NOW, TOO_FAR_AWAY, OBSTRUCTED, NOT_POSSIBLE_HERE). For NOT_SAFE, blockers is exactly the
monsters that kept them awake, with the mod's rules applied; for the rest it is empty. trouble already counts this
refusal. It doesn't fire when the player was already asleep or dead (the game's OTHER_PROBLEM).
SleepEvents.SLEEP_FAILED.register(failed -> {
if (failed.problem() == Player.BedSleepingProblem.NOT_SAFE) failed.blockers().forEach(Mob::setPersistenceRequired);
});
LAY_DOWN¶
Event<ServerPlayer>: the listener is given the player itself. They lay down in a bed and fell asleep, so their
refusals in a row start over (the game resets the time since rest). It fires before NIGHT_TERROR for the same lying
down.
TROUBLE_CHANGED¶
record TroubleChanged(ServerPlayer player, SleepTrouble.Level before, SleepTrouble.Level now, SleepTrouble trouble)
A player's SleepTrouble.Level went from before to now, for better or worse;
worsened() says which. The level is checked when a sleep is refused, when a player lies down, and every 5 seconds
(so a day passing without sleep is noticed within 5 seconds). The first check after a player joins only sets where
they stand, and fires nothing.
NIGHT_TERROR¶
A player who just lay down is about to have a night terror: they were restless or worse and the server's terror chance
came up, or they were marked for one (markNightmare, /whycantisleep nightmare).
trouble() is how badly they had been sleeping before they lay down. cancel() spares them: they sleep as usual.
A terror given at once with WhyCantISleepApi.nightmare doesn't fire this event.
SCORE_QUALITY¶
final class QualityScoring {
ServerPlayer player();
WakeReason reason();
List<SleepQuality.Factor> factors();
void add(SleepQuality.Factor factor);
void add(Identifier id, Component label, int points);
void remove(Identifier id);
}
A sleeper is waking, for any reason, and every sleep modifier has scored their
night. factors() holds what they made of it, then what listeners before you added. Add factors of your own, or remove
one by id: a mod that makes creepy music soothing removes SleepQuality.CREEPY_MUSIC and adds its own. The score is
SleepQuality.BASE plus the factors' points, held to 0 to 100, worked out once the listeners are done.
SleepEvents.SCORE_QUALITY.register(scoring -> {
if (MyBlocks.hasFeatherPillow(scoring.player())) {
scoring.add(MyMod.id("feather_pillow"), Component.translatable("mymod.quality.feather_pillow"), 8);
}
});
A factor's label is shown on the morning card, in Title Case. For something every night is scored by, a sleep modifier is simpler; this event is for the last word.
SLEEP_EFFECT¶
final class SleepEffectDrawn { player(); quality(); effect(); setEffect(SleepEffect effect); cancel(); isCancelled(); }
A player woke in the morning, the server's sleep effect chance came up, and an effect was drawn for the night's
quality(). setEffect gives another effect instead; cancel() gives none. It doesn't fire after waking early, or
when no effect fits the score. The effect is applied once the listeners are done, before WOKE.
WOKE¶
record Woke(ServerPlayer player, WakeReason reason, SleepQuality quality, int sleptTicks, boolean shaken, boolean wellRested,
@Nullable SleepEffect sleepEffect)
A player woke up, for any WakeReason. quality is the night's final score (after
SCORE_QUALITY), sleptTicks how long they lay asleep, shaken whether they had a night terror, wellRested whether
the bedtime routine earned them Regeneration, and sleepEffect the effect they were given, or null. It fires even
when the server's sleep quality option is off (the card just isn't shown).
SleepEvents.WOKE.register(woke -> {
if (woke.reason().rested() && woke.quality().score() >= 90) MyRewards.giveDreamcatcher(woke.player());
});
NIGHT_SKIPPED¶
The game is skipping the night in a level, by sleep or by vote; sleepers are those asleep, about to wake. WOKE
fires for each of them after it.
NIGHT_VOTED¶
The players in bed voted to skip the night, and those still up let them (or didn't answer in time). It fires after
the night was skipped: after NIGHT_SKIPPED and the sleepers' WOKE. sleepers are those who were in bed.
ROOM_SAFE¶
Every monster that blocked a player's sleep was dealt with inside the server's clearing time, and nothing else came
near: the room is safe. cleared is how many there were. Only with the server's room clearing on. It fires before the
player, if standing by the bed, lies straight down.
HidingEvents¶
| Event | Carries | Fires when | Can |
|---|---|---|---|
START |
HideStart |
A player is about to hide. | cancel, change the spot |
STOP |
HideStop |
A player stopped hiding. | read |
SHAKEN_OFF |
ShakenOff |
A player stayed hidden for their hide time. | keep or add hunters |
NOISE_CHANGED |
NoiseChanged |
A hiding player's noise changed. | read |
Entity types in #whycantisleep:ignores_hiding (the Wither, the Ender Dragon, the Warden, Elder Guardians) see
through hiding: they keep a hiding player as their target, are never counted as hunters and never vanish.
START¶
A player is about to get into a hiding spot, by sneak-using it or through
WhyCantISleepApi.startHiding. spot() is where. cancel() keeps them out (a sneak-use then
does nothing). setSpot changes what they get into: another squint (spot.withSquint(0.8F)), another hide time scale,
another place to be held.
It fires before the spot is checked for room, so a player who isn't cancelled may still not get in: every place in it taken, or something already in there that chases them out (the critter chance). A sneak-use too soon after hiding is refused for the cooldown before this fires.
HidingEvents.START.register(start -> {
// Deeper in the dark: a tighter squint in the Nether.
if (start.player().level().dimension() == Level.NETHER) start.setSpot(start.spot().withSquint(0.75F));
});
STOP¶
A player is out of their spot, back where they went in, and the hide cooldown has started. spot is where they hid,
hidTicks how long. The reason:
Reason |
Why |
|---|---|
LEFT |
They sneaked to come out. |
HURT |
They took damage. |
INTERRUPTED |
The spot stopped being one (broken, its floor gone), the server turned hiding off, or they died, slept, left or changed dimension. |
HEARD |
Their noise reached 1 with monsters near, and they were found. |
FOUND |
A monster that chased them in searched the spot and found them. |
DISCOVERED |
Another player opened the chest or closet door they hid behind. |
FORCED |
A mod brought them out with stopHiding. |
STARTLED |
Something was already in the spot. Declared, but not fired at present: a startled player never gets in, so they never come out. |
SHAKEN_OFF¶
final class ShakenOff { player(); spot(); List<Entity> hunters(); keep(Entity entity); add(Entity entity); }
A player stayed hidden for their hide time, and what was after them gives up. hunters() holds
what is about to vanish: the monsters that were targeting them as they hid, still within 48 blocks, and the monsters
within 12 blocks of the spot. Persistent mobs (named, tamed, kept), players and #whycantisleep:ignores_hiding are
never in it. When the listeners are done, every entity left in the list that is still alive vanishes in a puff of
smoke.
keep(entity) leaves one alone, to deal with it your own way; add(entity) sends one of yours with the rest.
HidingEvents.SHAKEN_OFF.register(shaken -> {
for (Entity hunter : List.copyOf(shaken.hunters())) {
if (hunter instanceof MyStalker stalker) {
shaken.keep(stalker); // not discarded: it slinks off instead
stalker.loseTrackOf(shaken.player());
}
}
});
Iterate over a copy when you keep inside the loop, since it takes from the list you are walking. The player stays
hidden afterwards. It fires only when the server's hidingClearsMonsters is on and every other player within 32 blocks
is hiding too; otherwise the monsters only lose the player and stay.
NOISE_CHANGED¶
The noise a hiding player makes changed, from previous to noise, 0 to 1 in steps of a tenth, as their client
reports it: trying to move, looking about, gasping when they fail the heartbeat minigame. At 1, with monsters near and
the server's noiseGivesAway on, they are found and STOP follows with HEARD.