For mod developers¶
Why Can't I Sleep? has a small Java API for other mods. With it you can read how badly a player is sleeping and how well they slept, find the monsters keeping them awake, put players into hiding spots and bring them out, add hiding spots of your own, score nights and give sleep effects from code, and listen to what happens: refused sleeps, wakings, night terrors, players hiding and the monsters they shake off.
Nothing here is needed for data packs. Sleep modifiers, sleep effects, hiding spots and the tags are all plain JSON: see Data packs.
| Class | Side | For | Page |
|---|---|---|---|
net.ixdarklord.whycantisleep.api.WhyCantISleepApi |
server, one method both | Sleep trouble, sleep quality, sleep blockers, hiding, night terrors. | Below |
net.ixdarklord.whycantisleep.api.SleepEvents |
server | Refused sleeps, lying down, scoring and waking, skipped nights, night terrors. | Events |
net.ixdarklord.whycantisleep.api.HidingEvents |
server | Players starting and stopping hiding, shaking off monsters, noise. | Events |
net.ixdarklord.whycantisleep.api.HidingSpots |
both | Registering kinds of hiding spot. | Hiding spots in code |
net.ixdarklord.whycantisleep.api.HidingSpotType, HidingSpot, HidingPosture |
both | A kind of hiding spot, the spot it finds, how the player is placed. | Hiding spots in code |
net.ixdarklord.whycantisleep.api.SleepTrouble |
server | How badly a player is sleeping, and its level. | Below |
net.ixdarklord.whycantisleep.api.SleepQuality, WakeReason |
server | How well a night went and why it ended. | Below |
net.ixdarklord.whycantisleep.api.SleepModifiers, SleepModifier, SleepContext |
server | Sleep quality modifiers in code. | Below |
net.ixdarklord.whycantisleep.api.SleepEffects, SleepEffect |
server | Sleep effects in code. | Below |
net.ixdarklord.whycantisleep.api.datagen.* |
data generation | Writing sleep effect and modifier files. | Data generation |
Only the api package is supported. Everything else in the mod is internal and may change in any release.
1.21.1 and 1.20.1
The examples use Identifier, as on 26.1 and later. On 1.21.1 and 1.20.1 the same methods take and return a
ResourceLocation. Other differences are noted where they come up.
Setup¶
Why Can't I Sleep? is built on CoolCatLib and needs it at runtime. The API's events are CoolCatLib events, so to listen to them you compile against CoolCatLib too.
The API will be published to the ModResources Maven with the mod's first release, free to use with no token, the same Maven Folios and CoolCatLib are on. Until then it is not on any public Maven. Once it is, the Maven's page lists every version, ready to copy:
repositories {
maven {
name = "ModResources"
url = "https://ixdarklord.github.io/ModResources/maven/"
}
}
dependencies {
// In the common module: the API, and CoolCatLib for its events.
compileOnly "net.ixdarklord.whycantisleep:whycantisleep-common:<whycantisleep version>"
compileOnly "net.ixdarklord.coolcatlib:coolcatlib-common:<coolcatlib version>"
}
The API is compileOnly: your mod doesn't ship it and doesn't need it to run. Each loader has its own artifact too
(whycantisleep-fabric, whycantisleep-neoforge, and
whycantisleep-forge on 1.21.1 and 1.20.1whycantisleep-fabric,
whycantisleep-neoforgewhycantisleep-fabric,
whycantisleep-neoforge, whycantisleep-forgewhycantisleep-fabric,
whycantisleep-forge), which you can add to your loader modules' development runtime to try your
code in game. Declare Why Can't I Sleep? as an optional dependency in your mod's metadata, not a required one.
1.21.1 and 1.20.1
Minecraft is obfuscated before 26.1, so with Loom the loader jars go in the mod* configurations
(modLocalRuntime, modCompileOnly). 1.21.1 has Fabric, NeoForge and Forge; 1.20.1 has Fabric and Forge.
Calling it only when the mod is loaded¶
Your mod must still load when Why Can't I Sleep? isn't installed. Keep every call to the API in a class of your own, and reach that class only after checking that the mod is loaded:
import net.ixdarklord.coolcatcore.api.core.ModConstructor;
import net.ixdarklord.coolcatcore.api.platform.Platform;
public final class MyMod implements ModConstructor {
@Override
public void onConstructMod() {
if (Platform.isModLoaded("whycantisleep")) MyModSleepCompat.init();
}
}
import net.ixdarklord.whycantisleep.api.HidingSpots;
import net.ixdarklord.whycantisleep.api.SleepEvents;
/** Everything that touches Why Can't I Sleep?, loaded only when it is installed. */
public final class MyModSleepCompat {
private MyModSleepCompat() {
}
public static void init() {
HidingSpots.register(new WardrobeHidingSpot());
SleepEvents.WOKE.register(woke -> {
if (woke.reason().rested() && woke.quality().score() >= 90) MyRewards.giveDreamcatcher(woke.player());
});
}
}
The JVM loads MyModSleepCompat, and with it the API's classes, only when init() is first called, so without the
mod nothing of it is touched. Write the mod id as a plain string in the check: WhyCantISleepApi.MOD_ID holds the same
value, but naming the API class where the mod may be missing is the habit to avoid. onConstructMod() runs on both
sides, which is where hiding spots must be registered. If your mod doesn't use CoolCatLib, your
loader's own check (FabricLoader.getInstance().isModLoaded, ModList.get().isLoaded) does the same job.
WhyCantISleepApi¶
Static methods, for reading and changing what the mod keeps on players. Most are server side: they take a
ServerPlayer and must be called on the server thread. A player on whom the server knows nothing gets an empty answer
(no trouble, no quality, not hiding), never an exception.
MOD_ID¶
The mod's id.
sleepTrouble¶
Server. How badly the player is sleeping: their refused sleeps in a row and the ticks since they last lay down, with the level those add up to. Refusals are kept for the server's session; the time since rest is the game's own statistic, so it survives restarts.
sleepBlockers¶
Server. Exactly the mobs that would keep the player from sleeping in this bed: the game's own monster check with
the mod's rules over it (#whycantisleep:blocks_sleep, #whycantisleep:does_not_block_sleep, the shared bed
tolerance). Pass the bed's head. It doesn't try to sleep, so it changes nothing; an empty list means no monster stands
in the way (the time of day, distance and the rest are not checked).
lastSleepQuality¶
Server. How well the player slept the last time they slept, or null when they haven't slept since the server
started. Every waking is scored, getting up early included.
isHiding¶
Both sides. Whether the player is hiding. A client knows it of every player it can see, not only its own.
hidingSpot¶
Server. Where the player hides, or null when they aren't hiding. See HidingSpot.
hiddenTicks¶
Server. How long the player has been hiding, in ticks; 0 when they aren't.
hideTicks¶
Server. How long the player must stay hidden in their spot before the monsters after them give up
(HidingEvents.SHAKEN_OFF), in ticks: the server's hide time scaled by the spot's
hideTimeScale and by the walls round it. 0 when they aren't hiding.
baseHideTicks¶
The server's hide time (the hideTime option) in ticks, before any spot or walls scale it. It reads the server
config, which is synced to clients, so it answers on either side.
hidingNoise¶
Server. The noise the hiding player makes, 0 to 1, as their client last reported it, in steps of a tenth. 0 when they aren't hiding. At 1, with monsters near, they are heard and found.
startHiding¶
public static boolean startHiding(ServerPlayer player, BlockPos pos)
public static boolean startHiding(ServerPlayer player, HidingSpot spot)
Server. Puts the player into hiding, as if they had got in themselves. The first form asks the hiding spot types
for the block at pos, as a sneak-use of it would; the second takes a spot a HidingSpotType found,
whose type must be the id of a registered or data pack type.
It returns false when they can't hide: no spot at the block, the spot's type unknown, already hiding, a
HidingEvents.START listener cancelled it, or every place in the spot is taken. It skips what a
player's own sneak-use checks: empty hands, sneaking and the hide cooldown. With the critter chance, something may be
in the spot already: the method then returns true, but the player is chased out rather than hidden, so check
isHiding if you need to know.
stopHiding¶
Server. Brings a hiding player out of their spot, where they went in. HidingEvents.STOP fires
with the reason FORCED, and the hide cooldown starts as it does for any other way out. Returns false when they
weren't hiding.
nightmare¶
Server. Gives the player a night terror now. Asleep, it is the night's terror: they wake shaken and the night is
scored for it. Awake, they only see it. SleepEvents.NIGHT_TERROR does not fire for it.
markNightmare¶
Server. Marks the player for a night terror the next time they lie down, whether or not they sleep badly, as
/whycantisleep nightmare does. The mark is a player tag, so it lasts through relogging. When they lie down,
SleepEvents.NIGHT_TERROR fires for it as for any other and can still cancel it. A player
already asleep has it at once instead, as with nightmare.
SleepTrouble¶
record SleepTrouble(int failedAttempts, int ticksSinceRest): how badly a player is sleeping.
| Member | What it is |
|---|---|
failedAttempts() |
Sleeps refused in a row since the player last lay down. |
ticksSinceRest() |
Ticks since they last lay down in a bed (the game's "time since rest", the one that brings phantoms). |
level() |
The SleepTrouble.Level the two add up to. |
isTroubled() |
Whether the level is at least RESTLESS. |
NONE |
No trouble at all. |
DAY_TICKS |
One in-game day, 24000 ticks. |
SleepTrouble.Level is RESTED, RESTLESS or SLEEPLESS. Either measure reaching a level's threshold puts the
player at that level:
| Level | Refusals in a row | Or days without lying down |
|---|---|---|
RESTED |
fewer than 2 | under 2 |
RESTLESS |
2 | 2 |
SLEEPLESS |
5 | 3, when phantoms come |
Each level has failedAttempts() and ticksSinceRest() for its thresholds, and atLeast(other) to compare.
SleepQuality¶
record SleepQuality(int score, List<Factor> factors): how well a player slept one night, 0 (awful) to 100
(perfect), and what made it so. Every night starts from SleepQuality.BASE (75); each factor adds its points, and the
sum is held to 0 to 100.
| Member | What it is |
|---|---|
score() |
0 to 100. |
factors() |
What made it so, in the order they were added. |
worst() |
The factor that cost the most, or null when none cost anything. |
best() |
The factor that helped the most, or null when none helped. |
of(factors) |
The quality of a night with these factors. |
A SleepQuality.Factor is (Identifier id, Component label, int points). The ids of the mod's own factors are
constants on SleepQuality: ENCLOSED, EXPOSED, DARK_CORNERS, TOO_BRIGHT, MONSTERS, CALM_MUSIC,
CREEPY_MUSIC, LOUD_MUSIC, SOOTHING, UNSETTLING, RESTLESS, SLEEPLESS, NIGHTMARE, WOKE_EARLY and ROUTINE
(the routine is what makes a player well rested). They are the ids of the mod's sleep
modifiers, so a data pack can change or remove them.
WakeReason says why a sleeper woke: MORNING (the night was skipped, or day came), GOT_UP, HURT, BED_GONE or
OTHER (a mod, a command, leaving the world). rested() is true only for MORNING.
Sleep modifiers in code¶
Every night is scored by every sleep modifier. The mod's own are data files, and a data pack adds more with no code (see Sleep modifiers). When a modifier needs something a file can't say, register one in code:
SleepModifiers.register(SleepModifier.of(MyMod.id("feather_pillow"),
Component.translatable("mymod.modifier.feather_pillow"),
context -> MyBlocks.hasFeatherPillow(context.player().level(), context.bed()) ? 8 : 0));
points returns what the modifier makes of the night: above 0 a buff, below 0 a debuff, 0 for no say (it isn't
listed on the morning card). The label is a name in Title Case ("Feather Pillow"). Register while your mod
initialises; a modifier with the id of another takes its place.
The SleepContext it is given is the night as its sleeper wakes:
| Field | What it is |
|---|---|
player, bed |
The sleeper and the bed they slept in. |
reason, sleptTicks |
Why they woke, and how long they lay asleep. |
enclosed, lit |
Whether the room was closed (walled, roofed, doors shut, no sky), and lit all through. |
pillowLight |
The block light at the pillow, 0 to 15. |
soothing, unsettling |
How many soothing and unsettling things were placed near the bed. |
monsters |
The most monsters within 24 blocks at any time of the night. |
calmMusic, creepyMusic, loudMusic |
Whether such music played from a jukebox nearby. |
trouble |
Their SleepTrouble.Level before they lay down. |
nightmare |
Whether they had a night terror. |
SleepModifiers also has remove(id) (only for modifiers registered in code: the mod's own are data, turned off with
a data pack), get(id), all() and score(context). To change a night's factors after every modifier has had its
say, listen to SleepEvents.SCORE_QUALITY.
Sleep effects in code¶
A sleep effect is what a night leaves a player with when they wake in the morning. Data packs add them with no code (see Sleep effects); in code, an effect can also run an action of its own:
SleepEffects.register(SleepEffect.of(MyMod.id("starry_dream"), SleepEffect.Kind.POSITIVE, SleepEffect.Rarity.EPIC,
Component.translatable("mymod.sleep_effect.starry_dream"),
new SleepEffect.Effect(MobEffects.NIGHT_VISION, 20 * 60 * 5, 0))
.withQuality(70, 100)
.withAction(player -> player.giveExperiencePoints(30)));
SleepEffect.of gives an effect the rarity's weight (common 60, uncommon 25, rare 10, epic 4, legendary 1) and every
night's score; withQuality(min, max) narrows the scores it can follow, withWeight(weight) sets its own weight, and
withAction(action) runs something after its mob effects. A SleepEffect.Effect is a mob effect, its duration in
ticks and its amplifier. A data pack effect with the same id takes the place of one registered in code.
SleepEffects also has get(id), all() and draw(score, random), which draws an effect as the mod does on waking.
To change or cancel the effect a player is given, listen to SleepEvents.SLEEP_EFFECT.
1.20.1
SleepEffect.Effect takes a MobEffect rather than a Holder<MobEffect>. MobEffects.NIGHT_VISION is the right
type on each version, so the example compiles unchanged.