Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ikeepcalm/coi-client/llms.txt
Use this file to discover all available pages before exploring further.
Effects in COI Client are fully independent layers — multiple effects can be active on a player’s screen at the same time, each managed and rendered separately by the client. Sending a second coi-client:effect message while others are running does not interfere with existing effects. This layering model lets you build powerful compound experiences by combining effects that individually might be too subtle: a vignette alone is eerie, but pair it with a heartbeat and cracks and you have a full madness breakdown.
Madness Levels
A common use case is scaling visual intensity alongside a custom madness or sanity mechanic. The three-stage progression below uses increasingly aggressive combinations to communicate the player’s deteriorating mental state:
// Madness level 1: mild unease — edges close in slightly
sendEffect(player, "vignette", "intensity=0.4");
// Madness level 2: growing dread — add a racing heartbeat
sendEffect(player, "vignette", "intensity=0.6");
sendEffect(player, "heartbeat", "intensity=0.8,bpm=85");
// Madness level 3: full breakdown — cracks fracture reality, whispers invade
sendEffect(player, "vignette", "intensity=0.7");
sendEffect(player, "heartbeat", "intensity=0.9,bpm=95");
sendEffect(player, "cracks", "intensity=0.6,pulse=true");
sendEffect(player, "whispers", "intensity=0.8");
When escalating a player from one madness level to the next, you don’t need to stop the previous effects first. Sending a new vignette or heartbeat while one is already active will replace it from scratch with the new parameters — the transition happens seamlessly.
Combat and Action
Short, punchy effects communicate moment-to-moment gameplay events. Use finite duration values so these clean themselves up automatically without any extra bookkeeping:
// Ability cast flash — use the player's pathway color
sendEffect(player, "flash", "color=FFDD00,intensity=0.6,duration=300"); // Sun
sendEffect(player, "flash", "color=8B00FF,intensity=0.6,duration=300"); // Fool
sendEffect(player, "flash", "color=FF2200,intensity=0.6,duration=300"); // Demoness
sendEffect(player, "flash", "color=0055FF,intensity=0.6,duration=300"); // Door
sendEffect(player, "flash", "color=00CCCC,intensity=0.6,duration=300"); // Tyrant
sendEffect(player, "flash", "color=FF8800,intensity=0.6,duration=300"); // Priest
// Taking heavy damage — cracks appear and blood rains down
sendEffect(player, "cracks", "intensity=0.7,pulse=true,duration=5000");
sendEffect(player, "bloodrain", "intensity=0.6,duration=4000");
// Teleportation — brief but heavy VHS glitch
sendEffect(player, "glitch", "intensity=0.8,duration=1500");
Rely on duration to auto-expire combat effects rather than scheduling manual stop calls. This keeps your plugin code simpler and avoids accidentally clearing a persistent madness effect that was already running.
Environmental States
Use persistent effects (no duration, or duration=-1) to mark environmental zones. Apply them on entry and clear them on exit.
// Entering a frozen zone — persistent frost
sendEffect(player, "frost", "intensity=0.7");
// Entering a cursed area — layered paranoia
sendEffect(player, "eyes", "count=3,duration=10000"); // eyes fade on their own
sendEffect(player, "whispers", "intensity=0.6"); // persistent whispers
sendEffect(player, "vignette", "intensity=0.5"); // persistent dark edges
// Haunted location — maximum dread combo
sendEffect(player, "vignette", "intensity=0.6");
sendEffect(player, "whispers", "intensity=0.9,text=leave now|you are not welcome|it sees you");
sendEffect(player, "bloodrain", "intensity=0.4");
sendEffect(player, "cracks", "intensity=0.5,pulse=true");
When the player leaves the zone, clear only the persistent effects you started — finite effects like eyes will already have expired on their own:
// Leaving a cursed area — clear the persistent layers
sendEffect(player, "vignette", "stop");
sendEffect(player, "whispers", "stop");
// Note: eyes already expired automatically after their duration
Clearing Effects
To clear every active effect at once — for example on respawn, sanity restore, or zone exit — use the "all" shorthand:
// On respawn or full sanity restore — wipe the slate clean
sendEffect(player, "all", "stop");
A complete respawn handler might look like this:
@EventHandler
public void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
sendEffect(player, "all", "stop");
}
All effects render each frame in sequence on the client’s render thread. For most combinations this is negligible, but keep the following in mind when stacking several effects:
tunnel renders via scanline fill at approximately screenHeight ÷ 3 draw calls per frame (the scanline step is 3 pixels). Use it for short, impactful moments rather than long ambient background states.
glitch and cracks are both lightweight — glitch uses a small fixed number of fill calls per burst, and cracks are pre-generated line segments drawn with single rotated rectangles.
bloodrain, frost, and whispers have moderate per-frame costs that scale with intensity. Running all three simultaneously at maximum intensity on a slow machine may be noticeable.
vignette, heartbeat, and flash are the most lightweight effects — each uses at most ~14 fill calls per frame and is safe to layer freely.
Avoid combining tunnel with bloodrain at high intensities for extended durations. tunnel alone generates ~screenHeight ÷ 3 fill calls per frame, and pairing it with bloodrain adds further per-frame cost that together can impact client performance on lower-end hardware.